교차엔트로피: 잘못된 부호표
“딥러닝에서 늘 줄이는 cross-entropy loss. 그 이름에 왜 엔트로피가 들어 있고, 왜 0까지는 내려가지 않는가?”
약어 사전으로 먼저: 잘못 믿고 만든 부호표
자주 쓰는 말에 짧은 약어를 붙여 두면 글이 짧아진다. 네 가지 말 「가」「나」「다」「라」를 0과 1로 적어 보내는데, 네 말이 실제로 나오는 비율이 (½, ¼, ⅛, ⅛)라고 하자. 스무고개에서 자주 나오는 답부터 물었듯, 가장 흔한 말에 한 자리, 다음 말에 두 자리, 나머지 둘에 세 자리 부호를 준다(0, 10, 110, 111). 이런 목록을 정보이론에서는 부호표(결과마다 붙인 0·1 부호의 목록)라 부른다. 그런데 비율을 모르고 네 말이 똑같이 나온다고 믿었다면, 모두 두 자리 부호(00, 01, 10, 11)를 주었을 것이다.
| 말 | 실제로 나오는 비율 | 비율대로 만든 부호표 | 고르게 나온다고 믿고 만든 부호표 |
|---|---|---|---|
| 가 | ½ | 1자리 | 2자리 |
| 나 | ¼ | 2자리 | 2자리 |
| 다 | ⅛ | 3자리 | 2자리 |
| 라 | ⅛ | 3자리 | 2자리 |
| 한 말에 드는 평균 | 1.75자리 | 2자리 |
잘못 믿고 만든 부호표로 적으면 한 말에 2자리가 들고, 그 가운데 1.75자리는 비율을 정확히 알았어도 들었을 몫, 나머지 0.25자리는 잘못 믿은 탓에 괜히 더 쓴 몫이다. 한국어 빈도에 맞춘 약어 사전으로 영어를 적으면 자주 쓰는 말에 긴 약어가 붙어 비효율이 생기는 것과 같다. 부호 한 자리는 예/아니오 답 하나, 곧 1 bit다. 그래서 확률 q를 믿고 만든 부호의 길이는 −log₂ q, 곧 q를 믿는 사람의 놀라움이다.
정의
현실이 p인데 모형 q가 매긴 놀라움 −log qi로 평균을 내면 교차엔트로피다. 위 표에서는 현실 p = (½, ¼, ⅛, ⅛), 모형 q = (¼, ¼, ¼, ¼)이고, 교차엔트로피가 2 bit, 현실 자신의 엔트로피가 1.75 bit다.
KL의 정의가 여기서 뜻을 얻는다. log(pi/qi) = (−log qi) − (−log pi). 결과 i가 일어났을 때 모형의 놀라움에서 현실 자신의 놀라움을 뺀 것, "괜히 더 놀란 양"이다. KL은 그 초과 놀라움을 현실의 빈도로 평균 낸 것이다. 그리고 H(p, q) = H(p) + KL(p‖q). 교차엔트로피는 현실의 몫과 모형의 낭비로 나뉜다. 위 표의 괜히 더 쓴 0.25 bit가 이 KL이다.
단위는 log의 밑을 따른다. 세 양 모두 nat이면 nat, bit이면 bit다. qi = 0인데 pi > 0인 결과가 있으면 −log qi = ∞다. 교차엔트로피도 KL도 무한대가 된다. 현실에서 일어나는 일을 "불가능"이라 한 모형은 무한히 놀란다.
불러오는 중…
ML에서: 학습이 줄이는 것
딥러닝을 해본 사람이라면 cross-entropy loss라는 이름을 본 적이 있을 것이다. 바로 이것이다. 현실이 p인데 모델이 q라고 주장하면, 그 주장의 비용이 H(p, q)이고, 모델을 학습한다는 건 이 비용을 줄이는 것이다. 이 비용이 어디까지 내려갈 수 있는지는 아래 문제 9에서 따져 본다.
그런데 우리는 p를 모른다. 가진 것은 p에서 나온 표본 x₁, …, xN뿐이다. 표본마다 1/N의 확률을 준 분포를 경험분포 p̂라 부른다. p 자리에 p̂를 넣으면 교차엔트로피가 우리에게 익숙한 식이 된다.
평균 음의 로그우도(NLL, negative log-likelihood)가 곧 경험분포와의 교차엔트로피다. 그러니 최대우도추정(MLE, maximum likelihood estimation. 로그우도를 가장 크게 하는 모수를 고르는 방법)은 KL(p̂‖qθ)를 가장 작게 만드는 θ를 찾는 일이다. H(p̂)는 θ와 무관하기 때문이다. "내 모델이 현실에서 얼마나 먼가"라는 질문의 답이 여기 있다. 학습은 데이터가 그려 준 현실 p̂에서 모델이 내놓은 분포 qθ까지의 KL을 줄이는 과정이다.
파이썬
import numpy as np
p = np.array([0.5, 0.3, 0.2]) # 현실
q = np.array([0.25, 0.25, 0.5]) # 모형
H_p = -np.sum(p * np.log(p))
H_pq = -np.sum(p * np.log(q))
KL = np.sum(p * np.log(p / q))
print(f"H(p) = {H_p:.4f} H(p,q) = {H_pq:.4f} KL(p‖q) = {KL:.4f}")
print(f"H(p) + KL = {H_p + KL:.4f}")
# 경험분포와 최대우도: 교차엔트로피 = 평균 음의 로그우도
rng = np.random.default_rng(0)
x = rng.choice(3, size=1000, p=p) # 현실에서 1000개 관측
p_hat = np.bincount(x, minlength=3) / len(x) # 경험분포
nll = -np.mean(np.log(q[x])) # 모형 q 의 평균 음의 로그우도
print(f"경험분포 {p_hat} 평균 NLL {nll:.4f} H(p̂,q) {-np.sum(p_hat * np.log(q)):.4f}")
# H(p) = 1.0297 H(p,q) = 1.2477 KL(p‖q) = 0.2180
# H(p) + KL = 1.2477
# 경험분포 [0.473 0.315 0.212] 평균 NLL 1.2393 H(p̂,q) 1.2393
평균 NLL과 H(p̂, q)는 계산 순서만 다른 같은 숫자다.
수확
“KL = 교차엔트로피 − 엔트로피. 모형 탓에 괜히 더 놀란 양의 평균.” “최대우도추정은 경험분포에서 모형까지의 KL을 줄이는 일이다.”
문제 7. 거꾸로 믿은 부호표
위 약어 사전에서 네 말 「가」「나」「다」「라」는 실제로 (½, ¼, ⅛, ⅛)의 비율로 나오는데, 누군가 비율을 거꾸로 (⅛, ⅛, ¼, ½)로 믿고 부호표를 만들었다. (가) 말마다 몇 자리 부호를 주었겠는가? 한 말에 평균 몇 자리가 드는가? (나) 잘못 믿은 탓에 괜히 더 쓴 몫은 몇 bit인가? 고르게 믿었을 때의 0.25 bit와 견주어라.
함께 풀기

믿은 비율대로면 「라」가 가장 흔하다고 봤으니까 「라」 1자리, 「다」 2자리, 「가」「나」 3자리요. 평균은 (3 + 3 + 2 + 1)/4 = 2.25자리예요.

그 평균은 어느 비율로 낸 거예요?

네 말을 똑같이 쳤네요. 실제로는 「가」가 절반이니까 ½·3 + ¼·3 + ⅛·2 + ⅛·1 = 2.625자리예요. 부호 길이는 믿은 비율이 정하고, 평균은 실제 비율로 내야 하는구나.

그럼 괜히 쓴 몫은 2.625 − 1.75 = 0.875 bit야. 고르게 믿은 0.25보다 세 배 넘게 커. 모르는 것보다 틀리게 아는 게 훨씬 비싸네.

그래요. 부호 길이는 믿은 q가, 평균은 현실 p가 정해요. 교차엔트로피에서 log 안에 q, 앞에 p가 오는 까닭이에요.

조교님이 모르는 문제는 차라리 비워 두라던 게 이거였네요. 틀린 답을 자신 있게 쓰면 감점이 더 큰 채점 방식이요.
문제 8. 교차엔트로피를 쪼개기
(가) 모든 i에서 pi > 0, qi > 0일 때 H(p, q) = H(p) + KL(p‖q)임을 보여라. (나) p = (0.5, 0.5), q = (0.9, 0.1)로 세 양을 계산하라. (다) q에 0인 칸이 있으면 어떻게 되는가? p에 0인 칸이 있으면? (위 위젯의 「문제 8」 단추로 (나)의 두 분포를 불러올 수 있다. 셋째 결과의 확률은 0으로 둔다.)
함께 풀기

(나)부터 했어요. H(p,q) = 1.204, H(p) = 0.693, KL = 0.511. 더하면 1.204. 맞으니까 (가)도 성립해요.

그건 (가)의 증명이에요, 예시예요?

…예시네요. 숫자 하나 맞는 걸로는 모든 p, q를 말 못 하죠.

식으로 하면 한 줄이에요. −log qi = −log pi + log(pi/qi)를 pi로 가중해서 더하면 끝이에요. (다)도 같은 식이니까 그냥 성립하겠죠.

p = (0.5, 0.5), q = (1, 0)을 넣어 봐요.

−log q₂ = −log 0이라… 무한대요. 로그를 쪼개는 단계에서 log 0을 유한한 수처럼 다뤘어요. 양쪽이 둘 다 ∞라서 “∞ = 0.693 + ∞” 꼴이 되긴 하는데, 그건 증명이 아니라 약속이에요.

그래요. qi = 0이고 pi > 0인 칸이 있으면 교차엔트로피도 KL도 무한대로 정의해요. 반대로 pi = 0인 칸은요?

그 칸은 0 × log 0 = 0으로 약속했으니 합에서 빠져요. q가 거기에 뭘 주든 상관없어요. 현실에서 안 일어나는 일이니까요.

그래서 분류 모델 출력에 softmax를 쓰는 거네요. softmax는 어떤 클래스에도 정확히 0을 안 주니까 loss가 무한대로 안 가요.

해석학에서 "식이 성립한다"는 말 앞에 늘 "정의되는 범위에서"가 붙던 게 이런 이유였어요.
문제 9. 라벨이 엇갈리는 데이터의 loss 바닥
이진 분류 데이터에 똑같은 입력이 여러 번 나오는데, 사람 평가자들이 붙인 라벨은 70%가 「1」, 30%가 「0」으로 엇갈려 있다. 모델은 이 입력에 「1」일 확률 q 하나를 낸다. (가) q = 0.99일 때 이 입력에서의 평균 cross-entropy loss(nat)는? (나) q를 바꾸어 loss를 가장 작게 하면 q와 loss는 얼마인가? (다) 모델을 아무리 키워도 이 loss를 0으로 만들 수 없는 까닭을 교차엔트로피를 쪼갠 식으로 설명하라. (위 위젯의 「문제 9」 단추로 라벨 분포와 q = 0.99를 불러와 q를 옮겨 볼 수 있다.)
함께 풀기

모델을 충분히 키우면 결국 1.0을 내서 loss가 0이 되지 않아요? 일단 q = 0.99면 −(0.7 ln 0.99 + 0.3 ln 0.01) ≈ 0.007 + 1.382 = 1.389 nat이에요. 1에 가까이 갔는데 오히려 커졌네요.

라벨이 「0」인 30%에서 q = 1을 내면요?

−ln 0이라 끝없이 커져요. 0으로 가려다 무한대로 가네요.

(나)는 q로 미분하면 −0.7/q + 0.3/(1 − q) = 0이라 q = 0.7. loss는 −(0.7 ln 0.7 + 0.3 ln 0.3) ≈ 0.611 nat이에요. 라벨 분포 (0.7, 0.3)의 엔트로피 그대로네요.

그 0.611을 교차엔트로피를 쪼갠 식에 놓으면 어느 몫이에요?

H(p, q) = H(p) + KL(p‖q)에서 q = p라 KL이 0이고 H(p)만 남은 거예요. 모델이 줄일 수 있는 건 KL뿐이고, H(p)는 데이터가 정한 몫이라 모델 크기와 상관없어요. loss의 바닥은 0이 아니라 H(p)예요.

그래요. 학습 곡선이 어떤 값에서 더 내려가지 않는다고 늘 모델 탓은 아니에요. 라벨이 엇갈리는 데이터라면 그 바닥이 데이터의 엔트로피예요.

조교님 두 분의 채점이 엇갈리는 과제라면, 누가 점수를 예측해도 두 분 점수를 다 맞힐 수는 없는 거랑 같네요.