LabHub
배우기 러닝패스 코스

트랜스포머 — 어텐션을 손으로 계산한다 · 교차 엔트로피와 퍼플렉시티 · 실습

손실 하나를 손으로 만든다

LabHub 에서 이어서 보기

목표

로짓에서 출발해 "이 모델이 얼마나 잘한다" 를 숫자 하나로 만드는 과정을 표준 라이브러리만으로 직접 짠다. 안정적인 log-softmax, 한 자리의 손실, 여러 자리의 평균인 교차 엔트로피, 지수로 되돌린 퍼플렉시티까지 올라간 뒤, 라벨을 한 칸 미는 일과 패딩 자리를 빼는 일이 그 숫자를 얼마나 바꾸는지 나란히 놓고 잰다. 마지막으로 밑이 2 인 로그로 옮겨 비트/토큰으로도 읽는다.

왜 중요한가

학습도 평가도 이 숫자 하나를 보고 움직인다. 그런데 이 숫자를 만드는 과정에는 오류를 내지 않으면서 조용히 틀리는 자리가 네 곳 있다. 로그를 언제 취했는가, 라벨을 한 칸 밀었는가, 패딩을 뺐는가, 로그의 밑이 무엇인가. 넷 다 코드로는 한 줄이고, 틀려도 예외가 나지 않으며, 대개 숫자가 좋아지는 쪽으로 틀린다. 그래서 의심할 계기가 없다.
이 실습은 실제 모델을 부르지 않는다. 이 파드의 시스템 파이썬에는 numpy·torch·transformers 가 없다. 대신 자리마다 어휘 전체에 대한 점수 한 줄을 결정적으로 만들어 두고 그 위에서 같은 계산을 손으로 짠다. 그래서 "어느 모델의 퍼플렉시티는 얼마" 같은 말은 여기서 하지 않는다. 나오는 숫자는 전부 여러분이 만든 자료로 잰 것이다.
이웃 모듈이 분포에서 하나를 고르는 법(온도·top-k·top-p)이라면 여기는 그 분포가 얼마나 틀렸는지 재는 법이다. 고르기 전에 재는 일이 먼저다.
채점기는 여러분이 적어 둔 설명을 믿지 않는다. 여러분의 모듈을 실제로 불러 매번 다른 로짓으로 함수를 직접 두드려 보고, 채점기가 따로 계산한 값과 대조한다. 입력은 실행마다 바뀌므로 값을 외워 넣을 수 없다.

단계

1. /root/work/tf-loss/loss.pyVOCAB·PAD_ID·SEQ·dataset()log_softmax(xs) 를 만드세요. 확률을 거치지 않고 로짓에서 바로 로그 확률로 갑니다.
2. NEG_INFnaive_log_softmax(xs) 를 더해 일부러 틀린 순서의 판을 만드세요. 확률을 먼저 구한 뒤 로그를 취해 바닥에서 -inf 가 나는 것을 재현합니다.
3. token_loss(logits, target) 을 더해 한 자리의 손실을 재게 하세요. 정답 토큰의 로그 확률을 뒤집은 값입니다.
4. cross_entropy(rows, targets) 를 더해 여러 자리의 손실을 평균 내게 하세요.
5. perplexity(rows, targets)uniform_perplexity(vocab_size, length) 를 만드세요. 균등 분포에서 퍼플렉시티가 어휘 크기와 같아지는 것을 확인합니다.
6. shift_pairs(rows, ids)·shifted_loss(rows, ids)·unshifted_loss(rows, ids) 를 만들어 라벨을 한 칸 밀었을 때와 안 밀었을 때를 나란히 재세요.
7. kept_positions(targets, pad_id)masked_cross_entropy(rows, targets, pad_id) 를 만들어 패딩 자리를 빼고 재게 하세요. 분자와 분모 둘 다에서 뺍니다.
8. bits_per_token(loss)·nats_per_token(bits) 를 더하고, /root/work/tf-loss/loss_report.json/root/work/tf-loss/loss_report.md 에 잰 값을 기록하세요.

참고

단계 8개

  1. 로짓에서 바로 로그 확률로
  2. 일부러 무너뜨려 본다
  3. 한 자리의 손실
  4. 여러 자리의 평균
  5. 퍼플렉시티의 눈금
  6. 라벨은 한 칸 밀려 있다
  7. 패딩을 빼고 잰다
  8. 네 숫자를 나란히 놓는다