정보 이론: 엔트로피, 교차 엔트로피, KL 발산
놀라움을 비트로 재는 정보량에서 출발해 엔트로피가 왜 압축의 한계인지 확인하고, 모든 분류 모델과 언어 모델의 손실 함수인 교차 엔트로피, 두 분포의 차이를 재는 KL 발산, 소프트맥스 기울기가 "예측 빼기 정답"이 되는 이유, 생성 온도와 퍼플렉서티, 결정 트리의 정보 이득까지 계산으로 이어 봐요.
딥러닝 코드를 열어 보면 거의 언제나 이 줄이 있어요. loss = cross_entropy(logits, labels). 이미지 분류도, 스팸 분류도, ChatGPT 같은 언어 모델의 학습도 모두 교차 엔트로피를 줄이는 일이에요. 이 이름은 1948년 섀넌이 만든 정보 이론에서 왔어요. 원래는 전화선으로 메시지를 얼마나 효율적으로 보낼 수 있는지 연구하던 이론인데, 지금은 AI의 손실 함수, 압축, 암호, 통신의 공통 언어가 됐어요.
이번 강의는 확률과 통계 트랙의 마지막 강의이자, 머신러닝 트랙으로 넘어가는 다리예요.
놀라움을 숫자로: 정보량
"오늘 아침 해가 동쪽에서 떴다"는 소식은 아무 정보가 없어요. 원래 그러니까요. "7월에 서울에 눈이 왔다"는 엄청난 정보예요. 드문 일일수록 알게 됐을 때 얻는 정보가 크다는 직관을 식으로 쓰면 이래요.
왜 하필 로그일까요? 서로 독립인 두 사건이 함께 일어날 확률은 곱해지는데, 정보는 더해지는 게 자연스럽기 때문이에요. 동전 두 번의 결과를 알면 한 번의 결과보다 정보가 두 배여야 하죠. 곱셈을 덧셈으로 바꾸는 함수가 로그예요.
import math
for name, p in [("동전 앞면", 1 / 2), ("주사위 6", 1 / 6), ("카드 한 장", 1 / 52), ("로또 1등", 1 / math.comb(45, 6))]:
print(name, round(-math.log2(p), 2), "비트")
| 사건 | 확률 | 정보량 |
|---|---|---|
| 동전 앞면 | 1/2 | 1비트 |
| 주사위 6 | 1/6 | 2.58비트 |
| 카드 52장 중 한 장 | 1/52 | 5.70비트 |
| 로또 1등 | 1/8,145,060 | 22.96비트 |
1비트는 공정한 예/아니오 질문 하나의 답이에요. 로또 번호를 맞히는 건 예/아니오 질문 23개를 연속으로 맞히는 것만큼 드문 일이에요.
엔트로피: 평균 놀라움
어떤 확률 분포에서 결과를 하나 볼 때 평균적으로 얻는 정보량이 엔트로피예요.
import numpy as np
def entropy(ps):
ps = np.asarray(ps, dtype=float)
ps = ps[ps > 0] # 0 × log 0 은 0으로 쳐요
return float(-(ps * np.log2(ps)).sum())
print(entropy([0.5, 0.5])) # 1.0
print(round(entropy([0.9, 0.1]), 3)) # 0.469
print(round(entropy([0.99, 0.01]), 3)) # 0.081
print(round(entropy([1 / 6] * 6), 3)) # 2.585
- 공정한 동전은 1비트예요. 결과를 전혀 예측할 수 없어요.
- 앞면이 90%인 동전은 0.469비트예요. 대체로 앞면이니 결과를 봐도 덜 놀라요.
- 99%면 0.081비트, 거의 놀랄 일이 없어요.
- 모든 결과가 똑같이 그럴듯할 때 엔트로피가 가장 크고, 결과가 확실하면 0이에요.
엔트로피는 불확실성의 크기예요. 그리고 놀랍게도, 이 값은 정확히 "결과를 알아내는 데 필요한 예/아니오 질문 수의 평균 최솟값"이기도 해요.
스무고개와 이진 탐색
1부터 1,000,000 사이의 숫자 하나를 예/아니오 질문으로 맞힌다면 몇 번이면 될까요? 이니 20번이면 충분해요. "500,000보다 큰가요?"처럼 남은 후보를 정확히 반으로 가르는 질문을 하면, 답 하나가 1비트씩 정보를 주거든요. 알고리즘 트랙의 이진 탐색이 빠른 이유가 바로 이거예요.
반대로 "777,777인가요?" 같은 질문은 거의 항상 "아니오"라서 정보를 거의 주지 않아요. 좋은 질문은 답이 반반으로 갈리는 질문이에요. 아래에서 볼 결정 트리가 이 원리로 질문을 골라요.
엔트로피는 압축의 한계다
A, B, C, D 네 글자가 각각 50%, 25%, 12.5%, 12.5%의 비율로 나오는 메시지를 0과 1로 바꿔 보낸다고 해 볼게요.
| 글자 | 확률 | 고정 길이 부호 | 가변 길이 부호 |
|---|---|---|---|
| A | 50% | 00 | 0 |
| B | 25% | 01 | 10 |
| C | 12.5% | 10 | 110 |
| D | 12.5% | 11 | 111 |
고정 길이면 글자당 2비트예요. 자주 나오는 글자에 짧은 부호를 주면 평균 길이가 비트로 줄어요.
probs = [0.5, 0.25, 0.125, 0.125]
lengths = [1, 2, 3, 3] # 0, 10, 110, 111
print(sum(p * l for p, l in zip(probs, lengths)), entropy(probs)) # 1.75 1.75
평균 길이가 엔트로피와 정확히 같아요. 섀넌은 어떤 방법으로도 평균 길이를 엔트로피보다 짧게 만들 수 없다는 것을 증명했어요(정보원 부호화 정리). 이렇게 확률에 맞춰 부호를 만드는 허프만 부호는 지금도 zip, JPEG, MP3 같은 압축 형식 안에서 쓰여요.
실제 압축 프로그램으로 확인해 볼게요.
import zlib
rng = np.random.default_rng(0)
n = 100_000
rand = rng.integers(0, 256, n, dtype=np.uint8).tobytes() # 완전히 무작위
repeat = b"ABCD" * (n // 4) # 같은 패턴 반복
biased = rng.choice(np.frombuffer(b"ABCD", dtype=np.uint8), size=n,
p=[0.5, 0.25, 0.125, 0.125]).astype(np.uint8).tobytes() # 엔트로피 1.75비트
for name, data in [("무작위", rand), ("반복", repeat), ("치우친 글자", biased)]:
c = zlib.compress(data, 9)
print(name, len(c), "바이트,", round(len(c) * 8 / n, 2), "비트/글자")
| 데이터 (10만 글자) | 압축 후 | 글자당 |
|---|---|---|
| 완전히 무작위 | 100,041바이트 | 8.0비트 (오히려 늘어남) |
| ABCD 반복 | 125바이트 | 0.01비트 |
| 치우친 A, B, C, D | 26,956바이트 | 2.16비트 |
무작위 데이터는 전혀 줄지 않아요. 예측할 수 없는 것은 압축할 수 없어요. 반복 패턴은 거의 0이 되고, 치우친 글자는 2.16비트로 엔트로피 1.75비트 근처까지 가지만 그 아래로는 못 내려가요. 압축이 잘 된다는 건 예측할 수 있다는 뜻이에요. 이 생각은 아래 언어 모델 이야기에서 다시 나와요.
교차 엔트로피: 틀린 지도로 길 찾기
진짜 분포가 인데 우리가 라고 잘못 믿고 부호를 만들면 평균 길이가 어떻게 될까요? 그게 교차 엔트로피예요.
믿음 가 진짜 와 같을 때 가장 작고(엔트로피와 같아요), 다를수록 커져요. 그래서 "모델의 예측 분포 가 진짜 분포 에 얼마나 가까운가"를 재는 손실 함수로 쓸 수 있어요.
분류 모델의 손실
고양이, 개, 새를 구분하는 모델에 고양이 사진을 넣었어요. 정답 분포는 이라서 교차 엔트로피의 합에서 정답 항 하나만 남아요.
for q in ([0.7, 0.2, 0.1], [0.1, 0.6, 0.3], [0.98, 0.01, 0.01]):
print(q, round(-math.log(q[0]), 3)) # 정답(첫 번째 칸)에 준 확률의 -로그
| 모델의 예측 (고양이, 개, 새) | 손실 |
|---|---|
| 0.7, 0.2, 0.1 | 0.357 |
| 0.1, 0.6, 0.3 | 2.303 |
| 0.98, 0.01, 0.01 | 0.020 |
정답에 높은 확률을 줄수록 손실이 0에 가까워지고, 정답에 0.01을 주면 이 돼요. 자신 있게 틀리면 크게 벌을 받아요. 딥러닝에서는 밑이 2인 로그 대신 자연로그를 써서 단위가 "냇"이 되지만, 1냇은 약 1.44비트라 본질은 같아요.
왜 정확도를 그냥 손실로 쓰지 않을까요? 정확도는 맞으면 1, 틀리면 0이라 미분할 수 없고, 0.51로 맞힌 것과 0.99로 맞힌 것을 구분하지 못해요. 교차 엔트로피는 미분 가능하고, 확신의 정도까지 평가해요. 그리고 교차 엔트로피를 최소화하는 것은 데이터의 가능도를 최대화하는 것과 정확히 같아요. 회귀 강의의 "오차가 정규 분포면 최소제곱이 최대 가능도"와 짝을 이루는 사실이에요.
KL 발산: 두 분포의 차이
교차 엔트로피에서 엔트로피를 빼면, 틀린 믿음 때문에 추가로 낭비한 비트 수가 남아요. 이것이 KL 발산이에요.
def cross_entropy(p, q):
p, q = np.asarray(p, float), np.asarray(q, float)
return float(-(p * np.log2(q)).sum())
def kl(p, q):
return cross_entropy(p, q) - entropy(p)
p, q = [0.9, 0.1], [0.5, 0.5]
print(round(cross_entropy(p, q), 3), round(entropy(p), 3)) # 1.0 0.469
print(round(kl(p, q), 3), round(kl(q, p), 3)) # 0.531 0.737
- 두 분포가 같으면 0이고, 다르면 항상 양수예요.
- 방향에 따라 값이 달라요. 인데 이에요. 그래서 진짜 거리는 아니고 "발산"이라고 불러요.
- 학습 중에는 진짜 분포의 엔트로피 가 고정된 값이라, 교차 엔트로피를 줄이는 것과 KL 발산을 줄이는 것은 같은 일이에요.
KL 발산은 AI 곳곳에 나와요.
- 지식 증류: 작은 학생 모델이 큰 선생 모델의 예측 분포를 따라 하도록 둘의 KL 발산을 줄여요.
- RLHF: 사람의 선호로 언어 모델을 조정할 때, 원래 모델에서 너무 멀어지지 않게 KL 발산에 벌점을 줘요.
- VAE: 생성 모델이 만드는 잠재 변수의 분포를 단순한 정규 분포에 가깝게 묶어 둘 때 써요.
소프트맥스와 교차 엔트로피의 기울기
모델은 처음에 확률이 아니라 아무 실수 점수(로짓)를 내놓아요. 이것을 확률로 바꾸는 함수가 소프트맥스예요.
def softmax(z):
e = np.exp(z - z.max()) # 큰 수가 넘치지 않게 최댓값을 빼요
return e / e.sum()
z = np.array([2.0, 1.0, 0.1]) # 모델의 점수(로짓): 고양이, 개, 새
p = softmax(z)
y = np.array([1.0, 0.0, 0.0]) # 정답은 고양이
print(p.round(3), round(-np.log(p[0]), 3)) # [0.659 0.242 0.099] 0.417
print((p - y).round(3)) # 기울기 [-0.341 0.242 0.099]
손실을 로짓으로 미분하면 놀랄 만큼 단순한 결과가 나와요.
**"예측 확률 빼기 정답"**이에요. 정답 칸은 확률이 1보다 모자란 만큼 올리고, 오답 칸은 준 확률만큼 내리라는 뜻이죠. 미적분 트랙의 연쇄 법칙 강의처럼 수치 미분으로 확인해 볼게요.
eps = 1e-6
num = [(-np.log(softmax(z + eps * np.eye(3)[i])[0]) + np.log(softmax(z - eps * np.eye(3)[i])[0])) / (2 * eps) for i in range(3)]
print(np.round(num, 3)) # [-0.341 0.242 0.099]
공식과 똑같아요. 소프트맥스와 교차 엔트로피를 짝으로 쓰는 이유가 이 깔끔함이에요. 계산이 빠르고 안정적이에요. 그래서 딥러닝 라이브러리는 둘을 합친 함수를 따로 제공해요.
온도: 언어 모델의 창의성 다이얼
언어 모델에 "온도"를 설정해 본 적 있나요? 로짓을 온도 로 나눈 뒤 소프트맥스를 하는 거예요.
for T in (0.5, 1.0, 2.0):
pt = softmax(z / T)
print(T, pt.round(3), round(entropy(pt), 3))
| 온도 | 확률 (고양이, 개, 새) | 엔트로피 |
|---|---|---|
| 0.5 | 0.864, 0.117, 0.019 | 0.655비트 |
| 1.0 | 0.659, 0.242, 0.099 | 1.222비트 |
| 2.0 | 0.502, 0.304, 0.194 | 1.481비트 |
온도가 낮으면 분포가 뾰족해져서(엔트로피가 낮아서) 늘 가장 그럴듯한 답을 골라요. 온도가 높으면 평평해져서 의외의 선택이 자주 나와요. 사실 확인이나 코드처럼 정확해야 할 때는 낮게, 아이디어 내기처럼 다양함이 필요할 때는 높게 쓰는 이유예요. 온도를 0에 가깝게 내리면 매번 1등만 고르는 것과 같아요.
퍼플렉서티: 언어 모델의 헷갈림 지수
언어 모델은 다음 토큰을 맞히는 분류 모델이고, 손실은 토큰마다의 교차 엔트로피 평균이에요. 이것을 지수로 되돌린 값이 퍼플렉서티예요.
print(round(math.exp(2.0), 2)) # 손실 2.0 → 퍼플렉서티 7.39
print(round(math.log(50_000), 2)) # 단어 5만 개를 아무렇게나 고르는 모델의 손실 10.82
손실이 2.0이면 퍼플렉서티는 7.39예요. "매 토큰마다 평균 7개쯤의 후보 사이에서 헷갈리는 정도"로 읽을 수 있어요. 단어 5만 개 중 아무거나 고르는 모델은 손실 10.82, 퍼플렉서티 50,000이에요. 낮을수록 좋지만, 토크나이저와 평가 데이터가 같을 때만 비교할 수 있어요. 토큰을 자르는 방법이 다르면 같은 문장도 토큰 수가 달라지거든요.
예측은 곧 압축이다
압축 실험에서 "예측할 수 있으면 압축할 수 있다"를 봤죠. 다음 글자에 확률 를 주는 모델이 있으면, 실제 글자를 약 비트로 부호화할 수 있어요(산술 부호화). 그러니 교차 엔트로피가 낮은 언어 모델은 곧 좋은 압축기예요. 2023년에는 주로 텍스트로 학습한 대형 언어 모델이 이미지와 음성 데이터까지 PNG나 FLAC 같은 전용 압축 형식보다 작게 압축했다는 연구가 나오기도 했어요. 다음 토큰 예측이 생각보다 깊은 "이해"를 요구한다는 관점의 근거로 자주 인용돼요.
상호 정보와 결정 트리
를 알게 되면 에 대한 불확실성이 얼마나 줄어들까요? 그 양이 상호 정보예요.
결정 트리는 이 값(정보 이득)이 가장 큰 질문부터 던져요. 메일 10통(스팸 4통, 정상 6통)을 두 단어로 나눠 볼게요.
- "무료"가 있는 메일 4통 중 스팸 3통, 없는 메일 6통 중 스팸 1통
- "회의"가 있는 메일 3통은 모두 정상, 없는 메일 7통 중 스팸 4통
def h2(p):
return entropy([p, 1 - p])
before = h2(4 / 10) # 스팸 4통, 정상 6통
free = 4 / 10 * h2(3 / 4) + 6 / 10 * h2(1 / 6) # '무료'로 나눈 뒤
meet = 3 / 10 * h2(0 / 3) + 7 / 10 * h2(4 / 7) # '회의'로 나눈 뒤
print(round(before, 3), round(before - free, 3), round(before - meet, 3)) # 0.971 0.256 0.281
처음 불확실성은 0.971비트예요. "무료"로 나누면 0.256비트, "회의"로 나누면 0.281비트가 줄어요. 직관적으로는 "무료"가 스팸을 더 잘 잡을 것 같지만, "회의"는 한쪽 그룹을 완전히 깨끗하게 만들어서 정보 이득이 더 커요. 그래서 트리는 "회의가 들어 있나요?"를 먼저 물어요. 스무고개에서 좋은 질문을 고르는 것과 같은 원리예요. 머신러닝 트랙의 결정 트리 강의에서 이 계산이 그대로 나와요.
실습
- 한글 문장 하나를 골라 글자별 빈도로 엔트로피를 계산해 보세요. 같은 길이의 영어 문장과 비교하면 어느 쪽이 큰가요?
- 압축 실험에서 A, B, C, D의 비율을 25%씩으로 바꾸면 압축 결과가 몇 비트/글자로 변하나요? 엔트로피 2비트와 비교해 보세요.
- 소프트맥스 코드에서 로짓을
[20.0, 10.0, 1.0]처럼 크게 바꿔 보고, 최댓값을 빼지 않은 버전과 결과를 비교하세요. 왜 최댓값을 빼야 할까요? - , 일 때 두 방향의 KL 발산을 구해 보세요. 어느 쪽이 더 크고, 그 이유는 무엇일까요?
자주 하는 실수
- 확률이 0인 곳에 로그 취하기. 은 음의 무한대예요. 실전에서는 아주 작은 값을 더하거나, 로짓에서 바로 계산하는 함수를 써요.
- 소프트맥스를 두 번 적용하기. 라이브러리의 교차 엔트로피 함수는 보통 로짓을 받아 안에서 소프트맥스를 해요. 확률을 넣으면 이상한 결과가 나와요.
- KL 발산을 대칭인 거리로 생각하기. 방향을 바꾸면 값도 뜻도 달라요.
- 토크나이저가 다른 모델끼리 퍼플렉서티 비교하기. 같은 기준에서만 의미가 있어요.
점검 문제
- 확률 1/8인 사건의 정보량은 몇 비트인가요?
- 결과가 8가지이고 모두 같은 확률이면 엔트로피는?
- 정답 클래스에 확률 0.5를 준 분류 모델의 교차 엔트로피 손실(자연로그)은?
- 소프트맥스와 교차 엔트로피를 함께 쓸 때, 로짓에 대한 기울기는?
- 언어 모델의 평균 손실이 1.0에서 0.5로 줄었어요. 퍼플렉서티는 어떻게 변하나요?
정답
- 비트예요.
- 비트예요.
- 이에요.
- 예측 확률 빼기 정답, 예요.
- 에서 로 줄어요.
트랙을 마치며
확률의 기초에서 시작해 분포, 기댓값, 표본과 추정, 가설 검정, 회귀, 베이즈, 정보 이론까지 왔어요. 이제 머신러닝 논문이나 코드에서 "가능도", "사후 분포", "교차 엔트로피", "KL", "정규화" 같은 말을 만나도 무엇을 계산하는지 알 수 있어요.
다음 강의
다음은 머신러닝 트랙이에요. 첫 강의에서는 AI, 머신러닝, 딥러닝이라는 말이 각각 무엇을 가리키는지 바로잡고, 지금까지 배운 수학이 머신러닝의 어느 자리에 들어가는지 지도를 그려요.