사전학습을 돌리면 로그가 이렇게 흘러갑니다.
step 11800 | loss 2.118 | ppl 8.32
step 11900 | loss 2.111 | ppl 8.26
step 12000 | loss 2.104 | ppl 8.20
두 숫자가 언제나 붙어 다니고 언제나 같이 내려갑니다. 그럴 만한 것이 둘은 같은 값입니다 — 이니 오른쪽은 왼쪽에 지수함수를 씌운 것일 뿐입니다.
그러면 왜 굳이 둘 다 찍는가. loss는 «최소화할 대상»이라 학습이 보는 숫자이고, perplexity는 읽을 수 있는 숫자이기 때문입니다. 8.20은 「모델이 다음 토큰을 고를 때 평균적으로 8.2개 중에 고민하고 있다」로 읽힙니다. 어휘가 5만 개인데 실질 후보가 8.2개까지 좁혀졌다는 뜻입니다.
지난 글까지가 확률분포를 만들고 거기서 뽑는 이야기였다면, 이 글은 그 분포가 얼마나 좁혀졌는지를 재는 자를 세웁니다.
정보량 — 놀람의 크기
어떤 일이 일어났다는 소식이 있습니다. 그 소식이 얼마나 «값어치»가 있는가.
- 「내일 해가 뜬다」 — 이미 아는 일이라 알려 준 것이 없습니다.
- 「내일 서울에 눈이 온다」 — 8월이라면 아주 놀랍습니다.
드문 일일수록 알려 준 것이 많습니다. 그러니 값어치는 확률이 작을수록 커야 합니다. 여기에 조건을 하나 더 붙입니다 — 독립인 두 소식을 함께 들으면 값어치가 더해져야 합니다. 동전 두 개의 결과를 듣는 것은 한 개의 결과를 두 번 듣는 것과 같아야 하니까요.
확률은 독립이면 곱해집니다(). 그런데 값어치는 더해져야 합니다. 곱을 합으로 바꾸는 함수는 로그이고, 지수와 로그를 다룬 글에서 본 성질 그대로입니다.
정의. 확률 인 사건이 일어났을 때의 정보량(자기정보량)은 이다.
앞에 음수 부호가 붙은 것은 이라 가 0 이하이기 때문입니다. 뒤집어 놓아야 «드물수록 크다»가 됩니다.
로그의 밑을 2로 잡으면 단위가 비트입니다. 이면 1비트, 이면 3비트, 이면 0비트입니다. 「예/아니오 질문 몇 번으로 알아낼 수 있는가」라고 읽으면 감이 잡힙니다 — 여덟 개 중 하나를 맞히려면 반씩 좁히는 질문 세 번이 필요하고, 그것이 정확히 3비트입니다.
엔트로피 — 놀람의 평균
정보량은 결과 하나에 붙는 값입니다. 분포 전체를 하나의 수로 요약하려면 평균을 냅니다.
정의. 확률분포 의 엔트로피는 각 결과의 정보량을 그 확률로 가중평균한 값이다.
기댓값의 정의를 그대로 쓴 것뿐입니다 — 각 결과의 값 에 그 확률 를 곱해 더했습니다. 손으로 세 개만 세어 보겠습니다.
공정한 동전. 이므로
사지선다. 이고 각 정보량이 이므로
치우친 분포. 이면 정보량이 각각 1, 2, 3, 3비트이므로
후보가 똑같이 넷인데 엔트로피는 2비트에서 1.75비트로 줄었습니다. 한쪽에 무게가 쏠릴수록 «어차피 A겠지»가 되어 새로 알게 되는 양이 줄어듭니다. 동전 하나로 그 관계를 다 볼 수 있습니다.
양 끝에서 0인 것이 중요합니다. 결과를 이미 아는 분포의 엔트로피는 0입니다. 놀랄 일이 없으니까요.
비트와 내트 — 밑만 다르다
로그의 밑을 무엇으로 잡느냐에 따라 단위 이름이 달라집니다.
| 밑 | 단위 | 쓰는 곳 |
|---|---|---|
| 2 | 비트(bit) | 정보이론, 압축, bits-per-byte |
| 내트(nat) | 딥러닝의 손실값 |
밑을 바꾸는 것은 상수를 곱하는 일이라 입니다. 그러니
입니다. 딥러닝 프레임워크의 log가 자연로그라서 손실값은 언제나 내트입니다. 로그의 밑은 «자를 몇 등분해 읽는가»의 문제일 뿐 재는 양 자체는 같습니다.
균등분포에서 최대인 이유
동전 그림에서 가 꼭대기였습니다. 일반적으로도 그런지 증명합니다.
정리. 결과가 개인 분포의 엔트로피는 이고, 등호는 일 때만 성립한다.
재료는 부등식 하나입니다.
로그 곡선이 에서 그은 접선 아래에 놓인다는 말입니다. 의 기울기가 이므로 에서 기울기가 1이고 값이 0이라 접선이 이며, 로그는 위로 볼록한 곡선이라 접선 위로 올라가지 못합니다.
이제 을 한 덩어리로 묶습니다.
둘째 등호는 이라 을 합 안으로 넣을 수 있어서입니다. 여기에 로 부등식을 씁니다.
따라서 입니다. 등호가 성립하려면 모든 에서 이어야 하는데 그것은 에서만이므로 , 즉 입니다. ∎
두 줄짜리 증명입니다. 그리고 결론이 직관과 정확히 맞습니다 — 아무것도 모를 때, 즉 모든 결과가 똑같이 그럴듯할 때 가장 헷갈립니다.
perplexity — 몇 개 중에 고민하는가
균등분포의 엔트로피가 이라는 사실이 다음 정의를 낳습니다.
정의. 분포 의 perplexity(당혹도)는 이다. 로그와 지수의 밑은 맞춰 쓴다.
균등분포 개를 넣어 보면 입니다. 균등분포의 perplexity는 정확히 선택지 개수입니다. 그러니 임의의 분포에 대해서도 perplexity는 이렇게 읽힙니다.
이 분포에서 고르는 어려움은, 균등하게 놓인 후보 PPL개 중에서 고르는 어려움과 같다.
앞의 치우친 분포 는 비트였으므로
입니다. 후보는 넷인데 실질은 3.36개입니다. 0.36개라는 것이 이상하게 들리지만, perplexity는 개수를 세는 값이 아니라 개수의 단위로 어려움을 옮겨 적은 값입니다.
loss 2.104와 ppl 8.20
이제 서두의 로그로 돌아갑니다. 사전학습의 손실은 토큰마다 정답 토큰에 모델이 준 확률의 음의 로그를 계산해 평균한 값입니다.
모양을 보십시오. 정보량 를 평균한 것입니다. 즉 손실은 이미 엔트로피와 같은 종류의 양이고, 단위도 자연로그를 썼으니 내트입니다. 정확히 말하면 이것은 «정답 분포와 모델 분포 사이의» 교차엔트로피인데, 그 유도는 이 트랙의 뒤편이 맡습니다 — 중급 27번 · 최대가능도와 중급 28번 · 교차엔트로피 = 음의 로그가능도가 그 자리입니다.
지금 필요한 것은 하나입니다. 손실이 엔트로피와 같은 양이므로 perplexity의 정의를 그대로 쓸 수 있습니다.
| loss (내트) | perplexity | 토큰당 비트 |
|---|---|---|
| 0.693 | 2.00 | 1.00 |
| 1.000 | 2.72 | 1.44 |
| 2.000 | 7.39 | 2.89 |
| 2.104 | 8.20 | 3.04 |
| 3.000 | 20.09 | 4.33 |
| 10.820 | 50000 | 15.61 |
맨 아래 줄이 기준점입니다. 어휘 5만 개를 균등하게 찍는 모델의 손실이 이고 perplexity가 5만입니다. 학습이 손실을 10.82에서 2.104로 내렸다는 것은 실질 후보를 5만 개에서 8.2개로 좁혔다는 말입니다.
여기서 loss 눈금의 성질이 하나 드러납니다. 손실이 0.1 줄어드는 것은 언제나 perplexity가 같은 비율로 줄어드는 일입니다 — 이므로 약 9.5%씩입니다. 손실 2.1에서 2.0으로 가는 것과 4.1에서 4.0으로 가는 것이 «같은 만큼»의 개선인 셈입니다. 로그 눈금이라 그렇습니다.
bits-per-byte와 «언어모델은 압축기다»
perplexity에는 불편한 점이 하나 있습니다. 토크나이저에 딸려 다닙니다. 토큰을 잘게 쪼개는 토크나이저를 쓰면 토큰당 고민이 줄어 perplexity가 낮게 나오지만, 같은 문장을 쓰는 데 토큰이 더 많이 듭니다. 그래서 토크나이저가 다른 두 모델의 perplexity는 맞대 놓고 비교할 수 없습니다.
빠져나갈 길은 토큰이 아니라 바이트를 기준으로 삼는 것입니다.
정의. bits-per-byte(bpb)는 텍스트 한 바이트를 표현하는 데 드는 평균 비트 수다.
우리 예의 숫자로 세어 봅시다. 손실 2.104는 토큰당 3.04비트였고, 영어 텍스트에서 토큰 하나가 대략 4바이트쯤이니
입니다. 원래 한 바이트는 8비트입니다. 그것을 0.76비트로 줄였으니 10배 넘게 압축한 셈이고, 같은 텍스트에서 gzip이 보통 바이트당 2~3비트쯤을 씁니다.
이 계산이 「언어모델은 압축기다」라는 주장의 실체입니다. 모델이 각 토큰에 확률을 매길 수 있으면 그 확률로 산술부호화를 돌려 정확히 비트로 그 토큰을 적을 수 있습니다. 다음 토큰을 잘 맞힐수록 짧게 적히니, 손실을 낮추는 일과 압축률을 높이는 일이 글자 그대로 같은 일입니다. 학습 로그의 loss는 그러니까 「이 모델로 코퍼스를 압축하면 토큰당 몇 내트가 드는가」를 실시간으로 찍고 있는 셈입니다.
코드로 확인하기
import numpy as np
def entropy(p, base=2):
p = np.asarray(p, dtype=np.float64)
p = p[p > 0] # 0 log 0 = 0 으로 약속한다
return float(-(p * np.log(p)).sum() / np.log(base))
print(entropy([0.5, 0.5])) # 1.0 공정한 동전
print(entropy([0.25] * 4)) # 2.0 사지선다
print(entropy([0.5, 0.25, 0.125, 0.125])) # 1.75 치우친 넷
print(entropy([0.97, 0.01, 0.01, 0.01])) # 0.2419 거의 확실한 넷
# 균등분포가 최대 — 한쪽으로 조금만 기울여도 값이 내려간다
for e in (0.0, 0.05, 0.10, 0.20):
print(e, round(entropy([0.25 + e, 0.25 - e, 0.25, 0.25]), 4))
# 0.0 2.0
# 0.05 1.9855
# 0.1 1.9406
# 0.2 1.7345
# loss ↔ perplexity ↔ bits
loss = 2.104
print(np.exp(loss).round(3)) # 8.199 perplexity
print((loss / np.log(2)).round(3)) # 3.035 토큰당 비트
print(np.log(50_000).round(3)) # 10.820 균등하게 찍는 모델의 손실
네 번째 줄이 눈여겨볼 만합니다. 한 후보가 0.97을 가져가면 엔트로피가 0.24비트까지 내려가고 perplexity는 입니다 — 후보가 넷이지만 사실상 하나만 보고 있다는 뜻입니다.
정리
- 정보량 는 «드물수록 크고 독립이면 더해지는» 유일한 모양이다. 곱을 합으로 바꾸는 함수가 로그라서 그렇다.
- 엔트로피는 그 정보량의 기댓값이다. 분포가 치우칠수록 작아지고 결과를 이미 아는 분포에서는 0이다.
- 밑이 2면 비트, 면 내트다. 딥러닝 손실은 자연로그를 쓰므로 언제나 내트다.
- 엔트로피는 균등분포에서 최대이고, 하나로 두 줄에 증명된다.
- perplexity는 엔트로피를 지수로 되돌린 값이라 «균등한 후보 몇 개와 같은 어려움인가»로 읽힌다.
- loss와 ppl은 같은 숫자다. , 토큰당 비트는 .
- loss가 0.1 줄면 perplexity는 언제나 9.5%씩 줄어든다. 로그 눈금이라 절대 차이가 아니라 비율이 일정하다.
- bpb는 토크나이저를 지운 눈금이고, 압축률과 손실이 같은 값의 두 표현임을 보여 준다.
로그의 두 숫자로 다시 돌아가 봅시다. loss 2.104 | ppl 8.20은 「이 모델은 다음 토큰을 평균 8.2개 후보로 좁혔고, 토큰당 3.04비트로 코퍼스를 적을 수 있다」는 한 문장의 세 가지 표현이었습니다.
그런데 이 손실 한 줄은 어디서 온 것인가. 「정답 토큰의 확률에 음의 로그를 씌워 평균한다」는 규칙을 누가 정했는가. 다음 글은 그 출처를 캡니다 — 「학습」이라는 말을 가장 정확하게 정의하는 원리에서 시작해, 정규분포를 가정하면 MSE가 떨어져 나오고 범주분포를 가정하면 이 손실이 떨어져 나오는 과정을 끝까지 따라갑니다.
읽어주셔서 감사합니다. 😊

