본문 바로가기
TYLER SONGBlog
블로그 목록
LLM Research

확률·정보이론: Cross Entropy가 왜 LLM의 loss function이 되는가

언어모델은 결국 "다음 단어의 확률분포"를 예측하는 기계다. 오늘은 그 예측이 얼마나 정확한지를 재는 도구인 MLE, entropy, cross entropy, KL divergence, perplexity를 수식과 NumPy 코드로 직접 확인한다. 이 다섯 개념은 이후 나올 LLM의 학습 loss와 평가 지표의 근본 뿌리다.

송민성9분 읽기

오늘의 목표

언어모델(language model)이 하는 일을 한 문장으로 요약하면 "주어진 문맥 다음에 어떤 단어가 올지에 대한 확률분포를 만드는 것"이다. 오늘은 그 확률분포를 다루고 비교하는 수학 도구—MLE, entropy, cross entropy, KL divergence, perplexity—를 배워서, 앞으로 나올 "loss가 왜 cross entropy인가", "perplexity가 왜 평가지표인가"에 답할 수 있게 만든다.

개념과 직관

확률과 조건부확률 동전을 던지면 앞면/뒷면이 나올 확률이 있듯이, 문장에서도 "다음 단어가 무엇일지"에 대한 확률이 있다. 조건부확률(conditional probability, 어떤 조건이 주어졌을 때의 확률) P(A|B) = P(A,B) / P(B) 는 "B가 일어났다는 걸 알 때 A가 일어날 확률"이다. 언어모델은 정확히 이 조건부확률 P(다음 단어 | 이전 단어들)을 학습한다.

기댓값과 분산 기댓값(expectation, 평균적으로 기대되는 값) E[X] = 모든 x에 대해 p(x)*x를 더한 것. 분산(variance, 값이 평균에서 얼마나 퍼져있는지) Var(X) = E[(X - E[X])^2]. 오늘은 개념만 짚고, 실제 계산은 뒤에서 loss의 통계량을 볼 때 다시 쓴다.

분포(distribution) 동전은 {앞, 뒤} 두 값에 확률을 부여하는 분포다. 언어모델은 어휘(vocabulary) 전체—수만 개의 단어/토큰—에 확률을 부여하는 거대한 분포를 매 스텝 출력한다. 이걸 그림으로 생각하면, 막대그래프가 있고 각 막대(단어)의 높이가 확률이며, 모든 막대 높이의 합은 항상 1이다.

MLE (최대우도추정, Maximum Likelihood Estimation) "동전을 10번 던져서 앞면이 7번 나왔다면, 이 동전의 앞면 확률은 얼마라고 추정하는 게 가장 그럴듯한가?" — 답은 0.7이다. 이게 MLE의 직관이다: 관찰된 데이터가 나올 확률(우도, likelihood)이 가장 커지는 파라미터를 찾는 것.

식으로 쓰면: theta_hat = argmax_theta [ p(x_1; theta) * p(x_2; theta) * ... * p(x_n; theta) ]

곱셈은 숫자가 작아지면 컴퓨터에서 다루기 힘들어지므로(수천 개를 곱하면 거의 0이 됨), log를 씌워 곱을 합으로 바꾼다:

theta_hat = argmax_theta [ log p(x_1;theta) + log p(x_2;theta) + ... + log p(x_n;theta) ]

이걸 로그우도(log-likelihood)라고 부른다. 언어모델 학습은 "정답 단어에 모델이 높은 확률을 주도록" 이 로그우도를 최대화하는 것과 같고, 이는 곧 음의 로그우도(negative log-likelihood, NLL)를 최소화하는 것과 동일하다. 이 NLL이 바로 다음에 나올 cross entropy다.

Entropy H(p) entropy(엔트로피, 분포가 가진 평균적인 불확실성/정보량)는 다음과 같이 정의한다:

H(p) = -( p(x1)*log p(x1) + p(x2)*log p(x2) + ... )

직관: 공정한 동전(50:50)은 다음에 뭐가 나올지 정말 모른다 → entropy가 크다(최대). 항상 앞면만 나오는 동전은 결과가 뻔하다 → entropy가 0. 즉 entropy는 "이 분포가 얼마나 확실한가"의 반대 개념이다.

Cross Entropy H(p, q) 실제 정답 분포를 p, 모델이 예측한 분포를 q라 하자. cross entropy(교차 엔트로피, 실제 분포를 모델의 예측 분포로 표현하는 데 드는 평균 비용)는:

H(p, q) = -( p(x1)*log q(x1) + p(x2)*log q(x2) + ... )

언어모델 학습에서 정답은 보통 원-핫(one-hot, 정답 위치만 1이고 나머지는 0인 벡터)이라, 이 식은 결국 "정답 단어에 모델이 부여한 확률의 -log"만 남는다. 이게 바로 우리가 매일 보게 될 cross entropy loss다.

KL Divergence KL(p || q) (Kullback-Leibler divergence, 두 분포가 얼마나 다른지 p 기준으로 잰 거리 비슷한 것)는:

KL(p||q) = H(p,q) - H(p) = sum_x p(x) * log( p(x) / q(x) )

즉 cross entropy에서 "원래 갖고 있던 불확실성(entropy)"을 빼면, "q가 p를 잘못 예측해서 추가로 드는 비용"만 남는다. q가 p와 완전히 같으면 KL은 0. 중요한 점: KL(p||q) ≠ KL(q||p) — 이건 거리(distance)가 아니라 비대칭적인 척도다.

Perplexity perplexity(퍼플렉시티, 모델이 평균적으로 몇 가지 선택지 사이에서 헷갈리는지를 나타내는 지표) = exp(cross entropy). cross entropy가 2.0(자연로그 기준)이면 perplexity는 약 7.39 — "모델이 매 순간 평균적으로 약 7개 정도의 후보 단어 사이에서 고민하는 수준"이라고 직관적으로 읽으면 된다.

코드로 직접 해보기

python
import numpy as np # 두 개의 확률분포 정의 (반드시 합이 1이어야 함) p = np.array([0.5, 0.25, 0.25]) # "실제" 분포라고 가정 q = np.array([0.4, 0.4, 0.2]) # 모델이 예측한 분포라고 가정 assert np.isclose(p.sum(), 1.0) assert np.isclose(q.sum(), 1.0) def entropy(p, eps=1e-12): p = np.clip(p, eps, 1.0) # log(0) 방지 return -np.sum(p * np.log(p)) def cross_entropy(p, q, eps=1e-12): q = np.clip(q, eps, 1.0) # log(0) 방지 return -np.sum(p * np.log(q)) def kl_divergence(p, q, eps=1e-12): p = np.clip(p, eps, 1.0) q = np.clip(q, eps, 1.0) return np.sum(p * np.log(p / q)) H_p = entropy(p) H_pq = cross_entropy(p, q) KL_pq = kl_divergence(p, q) PPL = np.exp(H_pq) print(f"H(p) = {H_p:.4f}") print(f"H(p,q) = {H_pq:.4f}") print(f"KL(p||q) = {KL_pq:.4f}") print(f"H(p,q)-H(p) = {H_pq - H_p:.4f} # 이 값이 KL(p||q)와 같아야 한다") print(f"Perplexity = {PPL:.4f}")

실행하면 H(p,q) - H(p)KL(p||q)가 (부동소수점 오차 수준까지) 같은 값으로 나오는 걸 확인할 수 있다. 이게 "cross entropy = entropy + KL"이라는 관계를 직접 눈으로 보는 과정이다.

오늘의 실험

목표: KL(p‖q)와 KL(q‖p)가 다르다는 것을 직접 확인한다.

python
KL_pq = kl_divergence(p, q) KL_qp = kl_divergence(q, p) print(f"KL(p||q) = {KL_pq:.4f}") print(f"KL(q||p) = {KL_qp:.4f}") print(f"같은가? {np.isclose(KL_pq, KL_qp)}")

여기서 멈추지 말고 p, q 조합을 최소 3~4가지 바꿔가며 표로 기록해본다.

pqKL(p‖q)KL(q‖p)
[0.5,0.25,0.25][0.4,0.4,0.2]
[0.9,0.05,0.05][0.33,0.33,0.34]
[0.33,0.33,0.34][0.9,0.05,0.05]

관찰 포인트: p가 한쪽에 확률이 뾰족하게 몰려있고(예: [0.9, 0.05, 0.05]) q가 평평할 때와, 반대로 p가 평평하고 q가 뾰족할 때 KL 값이 얼마나 다르게 나오는지 비교한다. 이 비대칭성은 나중에 RLHF/정렬(alignment) 단계에서 "정책이 기준 분포에서 벗어나는 걸 어느 방향으로 벌점 줄지" 설계할 때 실제로 영향을 준다.

왜 이걸 배우나 (LLM / Post-training 연결)

오늘 배운 cross entropy는 그대로 LLM pretraining의 loss function이다 — 모델이 다음 토큰 자리에 정답 토큰의 확률을 얼마나 높게 주는지를 -log 확률로 벌점 매기는 것이 전부다. Perplexity는 언어모델 성능을 비교하는 가장 기본적인 지표로 계속 등장한다. KL divergence는 이후 배울 RLHF/DPO 같은 post-training 방법에서 "새로 학습된 모델이 원래 모델(reference model)에서 너무 멀어지지 않도록" 잡아주는 정규화 항으로 다시 만나게 된다. 즉 오늘 식들은 이 커리큘럼 전체에서 계속 재활용되는 뼈대다.

초보자가 막히는 지점

  • log(0) 에러: q(x)에 0이 하나라도 있으면 np.log(0)-inf가 되어 계산이 깨진다. 실전 코드에서는 항상 아주 작은 값(eps)으로 clip한다.
  • cross entropy와 KL을 같은 것으로 착각: cross entropy는 KL에 entropy를 더한 것(H(p,q) = H(p) + KL(p‖q))이다. p가 고정된 정답 분포(원-핫)라서 H(p)=0이 되는 경우가 많아 두 값이 같아 보이는 상황이 자주 생기는데, 일반적으로는 다르다.
  • 분포 합이 1인지 확인 안 함: p, q를 아무 숫자나 넣고 계산하면 결과가 이상하게 나온다. 항상 sum()이 1인지 assert로 확인하는 습관을 들인다.
  • 로그의 밑(base) 혼동: 자연로그(ln, np.log)를 쓰면 단위가 "nat", 밑이 2인 log2를 쓰면 단위가 "bit"다. perplexity = exp(cross entropy)는 자연로그를 썼을 때만 성립하는 식이니, 밑을 섞어 쓰지 않도록 주의한다.
  • KL의 비대칭성을 실수로 대칭이라 생각: KL(p‖q)와 KL(q‖p)는 일반적으로 다르다. 코드로 직접 두 값을 비교해보기 전까지는 "그냥 거리 아닌가"라고 오해하기 쉽다.

알고리즘 (병행 1시간)

  • Easy: Two Sum — 해시맵으로 빈도/보수(complement)를 저장하며 탐색하는 문제. 오늘 확률 계산에서 값을 저장하고 조회하는 패턴과 감각이 비슷하다.
  • Medium: Top K Frequent Elements — 빈도수(frequency)를 세는 문제라서, "관찰된 데이터로부터 분포를 추정한다"는 오늘의 MLE 직관과 자연스럽게 연결된다.

풀이 후 반드시 기록할 것:

  • 접근: 어떤 자료구조/전략을 썼는지 (예: 해시맵, 힙, 정렬)
  • 시간복잡도: Big-O로 표기하고 왜 그렇게 되는지 한 줄 설명
  • 틀린 이유: 처음에 틀렸다면 무엇을 놓쳤는지(엣지 케이스, 자료구조 선택 실수 등) 구체적으로 적어둔다. 이 기록이 나중에 비슷한 문제를 만났을 때 가장 빠른 복습 자료가 된다.
© 2026 Tyler Song