모델¶
dt.Module은 nn.Module에 하이퍼파라미터 저장과 플롯 훅을 얹은 것이다.
PyTorch 모델 작성 방식은 그대로다.
채워야 할 세 가지¶
class MyNet(dt.Module):
def forward(self, X): ...
def loss(self, y_hat, y): ...
def configure_optimizers(self): ...
loss와 configure_optimizers는 기본 구현이 NotImplementedError를 던진다.
반드시 채워야 한다.
forward는 예외다. self.net을 정의하면 자동으로 위임된다.
class MyNet(dt.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Flatten(), nn.LazyLinear(256),
nn.ReLU(), nn.LazyLinear(10))
self.net도 없고 forward도 없으면 호출 시점에 막힌다.
save_hyperparameters() 와 호출 순서¶
__init__의 인자를 전부 인스턴스 속성으로 만들고 self.hparams에도 담는다.
class MyNet(dt.Module):
def __init__(self, lr=0.01, num_hiddens=256):
super().__init__()
self.save_hyperparameters()
self.net = nn.Sequential(nn.Flatten(), nn.LazyLinear(num_hiddens),
nn.ReLU(), nn.LazyLinear(10))
model = MyNet(lr=0.1)
model.lr, model.hparams
hparams는 체크포인트에 함께 저장되므로, 나중에 이 모델이 어떤 설정으로
학습됐는지 파일만 보고 알 수 있다.
순서를 지켜야 한다
super().__init__() 먼저, save_hyperparameters() 나중이다.
부모의 __init__도 자기 인자로 hparams를 덮어쓴다. 순서를 바꾸면
당신이 넘긴 값이 부모 기본값에 지워진다.
지역 변수는 잡히지 않는다. 선언된 인자만 읽는다.
특정 인자를 빼려면 ignore를 쓴다.
@add_to_class — 노트북 셀 사이에서 메서드 붙이기¶
노트북에서 클래스를 고칠 때마다 정의 셀로 돌아가 다시 실행하고, 그러면 아래 셀들도 전부 다시 돌려야 하는 문제를 없앤다.
# 셀 3
class MyNet(dt.Module):
def __init__(self, lr=0.01):
super().__init__()
self.save_hyperparameters()
self.net = nn.LazyLinear(10)
# 셀 7 — 한참 뒤에
@dt.add_to_class(MyNet)
def loss(self, y_hat, y):
return F.cross_entropy(y_hat, y)
클래스에 붙기 때문에 이미 만들어둔 인스턴스에도 즉시 적용된다.
데코레이터는 원래 함수를 그대로 돌려준다. 정의한 셀에서도 그 이름을 계속 쓸 수 있다는 뜻이다.
@dt.add_to_class(MyNet)
def loss(self, y_hat, y):
return F.cross_entropy(y_hat, y)
loss # <function loss at 0x...> — None 이 아니다
배치 규약¶
training_step과 validation_step의 기본 구현은 배치를 이렇게 해석한다.
self(*batch[:-1])로 forward를 부르므로, 입력이 두 개인 모델은
(X1, X2, y) 배치를 그대로 받는다.
정확도 곡선 추가하기¶
기본 구현은 손실만 그린다. 정확도도 보고 싶으면 validation_step을 덮어쓴다.
@dt.add_to_class(MyNet)
def validation_step(self, batch):
y_hat = self(*batch[:-1])
loss = self.loss(y_hat, batch[-1])
self.plot('loss', loss, train=False)
self.log('acc', (y_hat.argmax(-1) == batch[-1]).float().mean())
return loss
log로 찍은 스칼라는 현재 기록 구간의 평균으로 trainer.history['acc']에
남고, 보드가 있으면 현재 train/eval 상태를 보고 곡선도 함께 그린다. epoch
모드에서는 에폭 평균이고 step 모드에서는 기록/검증 경계 사이 평균이다. log_dir을 준
Trainer라면 history.jsonl에도 이름 그대로 acc 열이 생긴다.
같은 키를 학습과 검증 양쪽에서 쓰면 한 기록 구간 평균으로 합쳐진다. 따로 보관하려면
train_acc, val_acc처럼 모델이 직접 이름을 나눈다. deeptool은 지표 이름의
뜻을 검사하지 않는다.
반환값은 여전히 손실이어야 한다. Trainer가 이것으로 history를 채우고
최저점을 판정하기 때문이다.
plot은 history에 남지 않는다
그림만 필요하면 기존 plot(key, value, train)을 쓴다. 숫자도 보관하려면
log(key, value)를 쓴다. epoch, step, train_loss, val_loss, lr, sec는
Trainer가 기록하는 예약 이름이다.
라이브 보드의 x축은 Trainer의 학습 단위를 따른다. epoch 모드는 기존처럼 학습 배치를 fractional epoch으로, step 모드는 optimizer step으로 표시한다.