성능 평가
언어 모델이 잘 만들어졌는지, 다른 모델보다 나은지는 결국 숫자로 판단해야 합니다. 성능 지표는 크게 두 갈래입니다 — 모델이 텍스트에 부여하는 확률이 얼마나 정확한지 보는 내재적(intrinsic) 지표, 그리고 실제 과제를 얼마나 잘 푸는지 보는 외재적(extrinsic) 지표입니다.
1교차 엔트로피와 perplexity (내재적 지표)¶
언어 모델은 매 위치에서 다음 단어(토큰)의 확률 분포를 내놓습니다. 모델의 품질을 재는 가장 기본적인 척도는 교차 엔트로피(cross-entropy) — 실제로 등장한 토큰에 모델이 부여한 확률의 음의 로그를 전체 토큰에 대해 평균한 값입니다. 이 값은 학습이 직접 최소화하는 손실이기도 합니다.
이 손실을 사람이 직관적으로 읽기 위해 지수를 취한 것이 perplexity(의외도)입니다.
perplexity는 모델이 다음 토큰을 보고 평균적으로 얼마나 '의외’라고 느끼는지를 나타냅니다 — 잘 예측해 덜 의외일수록 낮고, 예측이 빗나가 더 의외일수록 높습니다. 같은 값을 '유효 분기 수’로도 읽을 수 있습니다 — 매 토큰을 평균 몇 개의 똑같이 그럴듯한 후보 사이에서 고르는 셈인지입니다. 텍스트를 완벽히 외웠다면 1에 가깝고, 아무 단서 없이 균등하게 찍는다면 어휘 크기에 가까워집니다 — 낮을수록 좋습니다.
2실제 모델로 재 보기 — 자연스러운 텍스트 vs 뒤죽박죽¶
직접 재 보면 의외도가 무엇을 잡아내는지 분명해집니다. 손실(평균 교차 엔트로피)에 지수를 취하면 곧 perplexity이므로, 함수 하나로 잽니다.
import numpy as np
def 의외도(model, ids): # = perplexity
로짓 = model.predict(np.array([ids], "int32"), verbose=0)[0].astype("float64")
예측, 정답 = 로짓[:-1], np.array(ids[1:]) # 다음 토큰과 맞추기
m = 예측.max(-1, keepdims=True)
로그합 = m[:, 0] + np.log(np.exp(예측 - m).sum(-1)) # log-sum-exp
손실 = (로그합 - 예측[np.arange(len(정답)), 정답]).mean() # 평균 음의 로그 확률
return float(np.exp(손실))실제 사전 훈련 모델인 Qwen3 base에 같은 한국어 위키 문단을 넣되, 한 번은 그대로, 한 번은 단어 순서만 뒤죽박죽 섞어 넣습니다. 어휘는 똑같고 어순만 깨졌으니, 모델이 한국어의 규칙을 익혔다면 뒤죽박죽 쪽이 훨씬 '의외’일 것입니다.
자연 = "\n\n".join(문단[10:22]) # 문단: 한국어 위키(KorQuAD) 발췌
단어 = 자연.split()
순서 = np.random.default_rng(0).permutation(len(단어)) # 고정 시드
뒤죽 = " ".join(단어[i] for i in 순서) # 어휘 동일, 어순만 파괴
for 이름, model in [("Qwen3-0.6B-Base", model_06), ("Qwen3-1.7B-Base", model_17)]:
print(이름,
"| 자연:", round(의외도(model, encode(자연)[:900]), 2),
"| 뒤죽박죽:", round(의외도(model, encode(뒤죽)[:900]), 2))모델을 만들고 가중치를 이식하는 과정(model_06·model_17)과 토크나이저 encode는 Qwen3 장에서 다룹니다.
Qwen3-0.6B-Base | 자연: 16.38 | 뒤죽박죽: 105.98
Qwen3-1.7B-Base | 자연: 11.57 | 뒤죽박죽: 88.47두 가지가 한눈에 드러납니다. 첫째, 어순을 망가뜨리면 의외도가 6~7배로 치솟습니다 — 모델이 자연스러운 한국어의 통계적 규칙을 실제로 학습했다는 증거입니다. 둘째, 같은 텍스트에서 더 큰 모델(1.7B)의 의외도가 더 낮습니다 — 규모가 커질수록 예측이 정확해집니다. 이 규모와 의외도의 관계를 여러 크기에 걸쳐 정량화한 것이 규모의 법칙 장입니다.
3토크나이저에 따라 달라진다 — bits-per-token vs bits-per-character¶
perplexity에는 함정이 하나 있습니다. 교차 엔트로피를 밑 2의 로그로 바꾸면 토큰당 정보량(bits-per-token = )이 되는데, 이 값도 perplexity도 모두 ‘토큰당’ 측정값입니다. 그런데 토크나이저(텍스트를 토큰으로 쪼개는 방식)가 다르면 같은 문장의 토큰 개수가 달라지므로, 토크나이저가 서로 다른 모델끼리는 perplexity를 직접 비교할 수 없습니다. 한 토큰에 더 많은 글자를 담는 토크나이저일수록 토큰당 부담이 커져 perplexity가 부풀려지기 때문입니다.
모델 간 공정한 비교가 필요하면, 토큰 수가 아니라 원문의 글자(또는 바이트) 수로 정규화한 bits-per-character(BPC)·bits-per-byte(BPB)를 씁니다. 이 값은 토크나이저와 무관하게 "같은 원문을 얼마나 압축했는가"를 재므로, 서로 다른 모델을 나란히 놓고 비교할 수 있습니다. 또한 perplexity는 평가에 쓴 텍스트의 도메인에도 좌우되므로, 비교는 언제나 같은 평가 말뭉치 위에서 이뤄져야 합니다.
4과제로 평가한다 — 벤치마크 (외재적 지표)¶
내재적 지표는 "모델이 텍스트 분포를 얼마나 잘 맞히는가"를 재지만, 그것이 곧 "사용자에게 얼마나 쓸모 있는가"를 뜻하지는 않습니다. 실제 쓸모는 구체적인 과제(benchmark)로 측정합니다.
지식·추론(객관식): MMLU, 한국어판 KMMLU 등 여러 분야 4지선다의 정답률(accuracy).
상식 추론: HellaSwag 등 자연스러운 다음 상황 고르기.
생성 품질: 번역·요약에서 정답과의 n-그램 겹침을 보는 BLEU·ROUGE.
코드: 생성한 코드가 단위 테스트를 통과하는 비율 pass@k.
개방형 응답: 정답이 하나가 아닌 대화·작문은 강한 모델이 채점하는 LLM-as-judge로 평가.
이들 벤치마크는 대부분 사후 훈련까지 마친(instruct) 모델의 능력을 잽니다 — 사전 훈련만 거친 base 모델은 지시를 따르지 않아 이 과제들로는 공정하게 잴 수 없습니다. 사후 훈련 모델을 Ollama로 직접 벤치마크하고 규모에 따른 변화까지 살피는 실습은 벤치마크 평가 장에서 다룹니다.
요약하면 perplexity는 모델의 학습 진척을 추적하는 내부 계기판이고, 벤치마크는 모델의 실제 가치를 재는 외부 잣대입니다 — 둘 다 필요합니다.