파인튜닝된 모델의 성능을 자동 메트릭, LLM 평가, 인간 평가를 통해 다각적으로 측정하고 벤치마킹하는 체계적인 방법을 다룹니다.
파인튜닝된 모델의 평가는 일반적인 머신러닝 모델 평가보다 복잡합니다. 단순히 정확도 하나로 성능을 판단할 수 없으며, 모델이 사전 학습에서 획득한 범용 능력을 유지하면서 동시에 새로운 능력을 얼마나 잘 습득했는지를 함께 측정해야 합니다.
파인튜닝 평가의 세 가지 축:
1. 타겟 성능 (Target Performance)
- 파인튜닝 목표 작업에서의 성능
- "원래 하려던 것을 잘 하는가?"
2. 범용 능력 보존 (General Capability Retention)
- 사전 학습에서 획득한 지식과 능력 유지 여부
- "기존에 할 수 있던 것을 잃어버리지 않았는가?"
3. 안전성과 정렬 (Safety & Alignment)
- 유해 콘텐츠 생성, 편향, 환각 등의 문제
- "위험하거나 잘못된 응답을 하지 않는가?"학습 과정에서 자동으로 추적되는 기본 메트릭입니다.
import numpy as np
from transformers import Trainer
def compute_perplexity(eval_pred):
"""퍼플렉시티 계산"""
logits, labels = eval_pred
# 손실에서 퍼플렉시티 계산
loss = Trainer.compute_loss(logits, labels)
perplexity = np.exp(loss)
return {"perplexity": perplexity}주요 학습 메트릭:
Training Loss:
- 학습 데이터에 대한 손실
- 지속적으로 감소해야 함
- 급격한 감소 후 정체는 정상
Validation Loss:
- 검증 데이터에 대한 손실
- train_loss와의 갭이 과적합 지표
- 증가 시 학습 중단 고려
Perplexity:
- exp(loss)로 계산
- 낮을수록 좋음
- 모델이 데이터를 얼마나 잘 예측하는지 측정
- 도메인 간 비교에 유용파인튜닝의 목표 작업에 따라 적절한 메트릭을 선택합니다.
from evaluate import load
def evaluate_classification(predictions, references):
"""분류 작업 평가"""
accuracy = load("accuracy")
f1 = load("f1")
results = {}
results.update(accuracy.compute(
predictions=predictions, references=references
))
results.update(f1.compute(
predictions=predictions, references=references, average="weighted"
))
return results
def evaluate_generation(predictions, references):
"""텍스트 생성 작업 평가"""
rouge = load("rouge")
bleu = load("bleu")
results = {}
results.update(rouge.compute(
predictions=predictions, references=references
))
results.update(bleu.compute(
predictions=predictions, references=[[r] for r in references]
))
return results작업별 권장 메트릭:
분류/추출:
- Accuracy, F1 Score, Precision, Recall
- 정확한 매칭 기반 평가
텍스트 생성:
- ROUGE (요약 품질)
- BLEU (번역 품질)
- BERTScore (의미적 유사도)
대화/지시 따르기:
- LLM-as-Judge (GPT-4, Claude 기반 평가)
- 인간 평가 (가장 신뢰할 수 있음)
코드 생성:
- pass@k (실행 성공률)
- CodeBLEU (구문/의미 유사도)LLM을 평가자로 활용하는 방법은 인간 평가의 확장성 문제를 해결하면서도 높은 품질의 평가를 제공합니다.
import anthropic
client = anthropic.Anthropic()
def evaluate_with_llm(
instruction: str,
response: str,
criteria: list[str]
) -> dict:
"""LLM을 사용한 응답 품질 평가"""
criteria_text = "\n".join(
str(i + 1) + ". " + c for i, c in enumerate(criteria)
)
prompt = (
"다음 지시문에 대한 응답의 품질을 평가해 주세요.\n\n"
"지시문:\n" + instruction + "\n\n"
"응답:\n" + response + "\n\n"
"평가 기준:\n" + criteria_text + "\n\n"
"각 기준에 대해 1~5점으로 점수를 매기고, "
"간단한 이유를 설명해 주세요.\n"
"마지막에 종합 점수 (1~5)를 제시해 주세요.\n\n"
"형식:\n"
"기준 1: [점수]/5 - [이유]\n"
"기준 2: [점수]/5 - [이유]\n"
"...\n"
"종합: [점수]/5"
)
result = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return {"evaluation": result.content[0].text}두 모델의 응답을 직접 비교하는 방법입니다. 절대적 점수보다 상대적 선호도가 더 신뢰할 수 있는 경우가 많습니다.
def pairwise_comparison(
instruction: str,
response_a: str,
response_b: str
) -> dict:
"""두 응답의 쌍별 비교"""
prompt = (
"다음 지시문에 대한 두 응답을 비교해 주세요.\n\n"
"지시문:\n" + instruction + "\n\n"
"응답 A:\n" + response_a + "\n\n"
"응답 B:\n" + response_b + "\n\n"
"평가 기준:\n"
"1. 정확성: 정보가 사실에 부합하는가\n"
"2. 완전성: 질문에 충분히 답변했는가\n"
"3. 명확성: 설명이 이해하기 쉬운가\n"
"4. 유용성: 실질적으로 도움이 되는가\n\n"
"각 기준에서 어떤 응답이 더 나은지, "
"그리고 종합적으로 어떤 응답이 더 나은지 판단해 주세요.\n\n"
"결론은 다음 중 하나로 제시해 주세요:\n"
"A >> B (A가 확실히 우수)\n"
"A > B (A가 약간 우수)\n"
"A = B (동등)\n"
"B > A (B가 약간 우수)\n"
"B >> A (B가 확실히 우수)"
)
result = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return {"comparison": result.content[0].text}LLM-as-Judge에는 위치 편향(Position Bias)이 존재합니다. 응답 A와 B의 순서를 바꿔서 두 번 평가하고, 결과가 일관된 경우만 채택하는 것이 좋습니다. 또한 자기 자신이 생성한 응답에 대한 평가는 편향될 수 있으므로, 가능하면 다른 모델을 평가자로 사용합니다.
파인튜닝이 모델의 범용 능력에 미친 영향을 측정하기 위해 표준 벤치마크를 사용합니다.
주요 LLM 벤치마크:
지식/추론:
- MMLU: 57개 과목의 객관식 문제
- ARC: 초등학교~중학교 과학 문제
- HellaSwag: 상식 추론
수학:
- GSM8K: 초등학교 수학 문제
- MATH: 고등학교~대학교 수학
코드:
- HumanEval: Python 코드 생성
- MBPP: 간단한 프로그래밍 문제
한국어:
- KMMLU: 한국어 MMLU
- KoBEST: 한국어 자연어 이해EleutherAI의 lm-evaluation-harness는 LLM 벤치마크 평가를 위한 표준 도구입니다.
# lm-evaluation-harness 설치
pip install lm-eval
# 기본 벤치마크 실행
lm_eval --model hf \
--model_args pretrained=./merged-model,dtype=float16 \
--tasks mmlu,arc_easy,arc_challenge,hellaswag \
--batch_size 8 \
--output_path ./eval_results# Python API로 벤치마크 실행
import lm_eval
results = lm_eval.simple_evaluate(
model="hf",
model_args="pretrained=./merged-model,dtype=float16",
tasks=["mmlu", "arc_easy", "hellaswag"],
batch_size=8,
)
# 결과 출력
for task, metrics in results["results"].items():
acc = metrics.get("acc,none", metrics.get("acc_norm,none", "N/A"))
print(task + ": " + str(acc))체계적인 비교를 위한 평가 프레임워크를 구축합니다.
import json
from dataclasses import dataclass
from datetime import datetime
@dataclass
class EvaluationResult:
model_name: str
timestamp: str
target_metrics: dict
benchmark_metrics: dict
qualitative_samples: list[dict]
def comprehensive_evaluation(
model,
tokenizer,
test_dataset,
model_name: str
) -> EvaluationResult:
"""종합 평가 실행"""
# 1. 타겟 작업 평가
target_metrics = evaluate_target_task(
model, tokenizer, test_dataset
)
# 2. 정성적 샘플 생성
samples = generate_qualitative_samples(
model, tokenizer, test_dataset[:10]
)
return EvaluationResult(
model_name=model_name,
timestamp=datetime.now().isoformat(),
target_metrics=target_metrics,
benchmark_metrics={},
qualitative_samples=samples,
)
def evaluate_target_task(model, tokenizer, test_dataset):
"""타겟 작업 성능 평가"""
predictions = []
references = []
for example in test_dataset:
# 입력 생성
messages = example["messages"][:-1] # 마지막 응답 제외
inputs = tokenizer.apply_chat_template(
messages, return_tensors="pt", add_generation_prompt=True
)
# 응답 생성
outputs = model.generate(
inputs.to(model.device),
max_new_tokens=512,
temperature=0.1,
do_sample=False,
)
prediction = tokenizer.decode(
outputs[0][inputs.shape[1]:],
skip_special_tokens=True
)
reference = example["messages"][-1]["content"]
predictions.append(prediction)
references.append(reference)
# 메트릭 계산
results = evaluate_generation(predictions, references)
return results
def generate_qualitative_samples(model, tokenizer, examples):
"""정성적 평가를 위한 샘플 생성"""
samples = []
for example in examples:
messages = example["messages"][:-1]
inputs = tokenizer.apply_chat_template(
messages, return_tensors="pt", add_generation_prompt=True
)
outputs = model.generate(
inputs.to(model.device),
max_new_tokens=512,
temperature=0.7,
)
generated = tokenizer.decode(
outputs[0][inputs.shape[1]:],
skip_special_tokens=True
)
samples.append({
"input": example["messages"][-2]["content"],
"expected": example["messages"][-1]["content"],
"generated": generated,
})
return samplesdef generate_comparison_report(
base_eval: EvaluationResult,
ft_eval: EvaluationResult
) -> str:
"""파인튜닝 전후 비교 보고서 생성"""
report_lines = [
"파인튜닝 평가 보고서",
"=" * 50,
"",
"베이스 모델: " + base_eval.model_name,
"파인튜닝 모델: " + ft_eval.model_name,
"평가 일시: " + ft_eval.timestamp,
"",
"--- 타겟 작업 성능 ---",
]
for metric in ft_eval.target_metrics:
base_val = base_eval.target_metrics.get(metric, "N/A")
ft_val = ft_eval.target_metrics[metric]
if isinstance(base_val, float) and isinstance(ft_val, float):
diff = ft_val - base_val
sign = "+" if diff >= 0 else ""
report_lines.append(
" " + metric + ": "
+ str(round(base_val, 4)) + " -> "
+ str(round(ft_val, 4))
+ " (" + sign + str(round(diff, 4)) + ")"
)
report_lines.extend([
"",
"--- 정성적 샘플 ---",
])
for i, sample in enumerate(ft_eval.qualitative_samples[:3]):
report_lines.extend([
"",
"샘플 " + str(i + 1) + ":",
" 입력: " + sample["input"][:100] + "...",
" 기대: " + sample["expected"][:100] + "...",
" 생성: " + sample["generated"][:100] + "...",
])
return "\n".join(report_lines)파인튜닝에서 가장 흔한 문제인 과적합을 진단하고 대응하는 방법입니다.
과적합 증상과 대응:
증상 1: train_loss는 낮지만 eval_loss가 높거나 증가
원인: 학습 데이터에만 최적화됨
대응:
- 데이터 양 늘리기
- 드롭아웃 높이기 (0.1 -> 0.2)
- LoRA 랭크 줄이기
- 조기 종료 적용
- weight_decay 높이기
증상 2: 학습 데이터와 유사한 입력에만 잘 응답
원인: 다양성 부족
대응:
- 데이터 다양성 확보
- 데이터 증강 적용
- 에포크 수 줄이기
증상 3: 반복적이거나 고정적인 패턴의 응답
원인: 데이터 내 패턴 편중
대응:
- 시작 문구/구조가 다양한 데이터 추가
- 학습률 낮추기
- temperature 조절 (추론 시)
증상 4: 범용 벤치마크 성능 하락 (Catastrophic Forgetting)
원인: 원래 지식이 덮어써짐
대응:
- 학습률 낮추기
- LoRA 랭크 줄이기
- 범용 데이터를 학습 데이터에 일부 포함
- 에포크 수 줄이기반복 실험에서 평가를 자동으로 수행하는 파이프라인을 구축합니다.
# evaluate.py
import json
import sys
from pathlib import Path
def run_evaluation_pipeline(model_path: str, output_dir: str):
"""자동 평가 파이프라인"""
output = Path(output_dir)
output.mkdir(parents=True, exist_ok=True)
# 1. 모델 로드
print("1. 모델 로드 중: " + model_path)
model, tokenizer = load_model(model_path)
# 2. 타겟 작업 평가
print("2. 타겟 작업 평가 중...")
test_data = load_test_data("data/test.jsonl")
target_results = evaluate_target_task(model, tokenizer, test_data)
# 3. 정성적 샘플 생성
print("3. 정성적 샘플 생성 중...")
samples = generate_qualitative_samples(
model, tokenizer, test_data[:20]
)
# 4. 결과 저장
results = {
"model_path": model_path,
"target_metrics": target_results,
"num_samples": len(samples),
"samples": samples,
}
results_path = str(output / "eval_results.json")
with open(results_path, "w") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print("평가 완료. 결과: " + results_path)
return results
if __name__ == "__main__":
model_path = sys.argv[1] if len(sys.argv) > 1 else "./best-adapter"
run_evaluation_pipeline(model_path, "./eval_output")이번 장에서는 파인튜닝된 모델의 평가와 벤치마킹 방법을 체계적으로 다루었습니다.
다음 장에서는 파인튜닝된 모델을 체계적으로 관리하기 위한 모델 레지스트리와 버전 관리 시스템을 구축합니다. 모델 아티팩트, 메타데이터, 실험 기록을 효과적으로 관리하는 방법을 안내합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
파인튜닝된 모델을 체계적으로 관리하기 위한 모델 레지스트리 구축, 버전 관리, 메타데이터 추적, 아티팩트 저장 전략을 다룹니다.
파인튜닝 학습 파이프라인의 전체 구조를 설계하고, 학습률, 배치 크기, 스케줄링 등 핵심 하이퍼파라미터를 최적화하는 전략을 다룹니다.
파인튜닝의 학습, 평가, 배포 전체 과정을 CI/CD 파이프라인으로 자동화하고, 데이터 변경이나 코드 변경 시 자동으로 모델이 업데이트되는 체계를 구축합니다.