borch

튜토리얼 · 2

밑바닥부터

같은 문제를 아무것도 없는 데서부터: 날텐서와 손으로 쓴 기울기 스텝, 그다음 nn.Linear, 그다음 optim, 그다음 DataLoader. 한 겹씩 얹을 때마다 방금 직접 본 일을 하던 코드가 지워진다.

"nn 이 실제로 무엇을 하는가" 에 답하는 튜토리얼이다. 아래에 마법은 없고, 끝나면 루프가 앞으로 쓸 모든 학습 루프와 같은 모양이 된다.

0 · 문제

y = 2x − 1 위에 잡음이 얹힌 점들에 직선을 맞춘다. 눈으로 확인할 만큼 작고, 모든 조각이 실제로 돌아야 할 만큼은 진짜다.

1 · 손으로

파라미터 둘, 직접 쓴 손실, 직접 적용하는 스텝. borch 가 여기서 하는 일은 미분 하나뿐이다 — autograd 가 그것이고, 손으로 쓰고 싶지 않은 유일한 조각이다.

갱신을 noGrad 로 감싼 것을 보라. 파라미터를 바꾸는 일은 기울기를 구하려는 그 계산의 일부가 아니고, torch 의 옵티마이저도 정확히 이렇게 한다.

borch 가 torch 와 갈리는 자리가 하나 있다. torch 라면 w -= lr * w.grad 라고 쓰겠지만, 여기서 sub_mul_를 받고 텐서를 받는 제자리 연산은 addcmul_ 이다. 그래서 스텝이 w.addcmul_(w.grad, one, -lr) 이고 이는 w += -lr · grad · 1 이다. sub_ 에 텐서를 넘기면 예외가 안 나고 조용히 NaN 이 된다 — 이 문단이 생긴 이유다.

2 · nn.Linear 가 파라미터를 들게

층이 weightbias 를 들고 순방향을 안다. 사라지는 것: 텐서 둘을 만드는 일, 그 모양을 기억하는 일, x @ w + b 를 적는 일. 그대로인 것: 나머지 전부.

3 · optim 이 걸음을 걷게

zeroGrad()step() 이 방금 쓴 두 고리를 대신한다. 이제 옵티마이저는 갈아 끼울 수 있는 것이다 — 아래 줄의 SGDAdam 으로 바꾸면 곡선의 모양이 달라진다.

4 · DataLoader 가 배치를 나눠 주게

마지막 조각. 64 점이면 전체를 한 번에 써도 됐지만 진짜 데이터는 안 들어간다. 루프 몸통은 하나도 안 바뀐다 — 바뀌는 것은 xy 가 어디서 오느냐뿐이다.

각 겹이 무엇을 사 주었나. autograd 는 미분을, 모듈은 파라미터 장부를, 옵티마이저는 갱신 규칙을 갈아 끼울 수 있는 것으로, 로더는 배치를 사 주었다. 산술은 아무것도 안 바뀌었다 — 1번 블록으로 돌아가면 같은 수가 나온다.