微调模型评估
模型训练完了,loss 下降了,但这并不代表模型真的好用。这一篇讲怎么系统地评估微调后的模型。
1. 评估的三个层面
| 层面 | 方法 | 说明 |
|---|---|---|
| 训练指标 | loss、perplexity | 训练时自动产出,反映拟合程度 |
| 客观指标 | BLEU、ROUGE、Exact Match | 在测试集上跑出量化分数 |
| 主观评价 | 人工评测、LLM-as-Judge | 真实使用体验 |
只看 loss 是远远不够的——loss 低不一定回答好,loss 高也不一定回答差。
2. Perplexity(困惑度)
Perplexity 衡量模型对一段文本的"惊讶程度",越低越好:
import torch
import math
from transformers import AutoModelForCausalLM, AutoTokenizer
def calculate_perplexity(model, tokenizer, text, device="cuda"):
encodings = tokenizer(text, return_tensors="pt").to(device)
input_ids = encodings.input_ids
with torch.no_grad():
outputs = model(input_ids, labels=input_ids)
loss = outputs.loss
return math.exp(loss.item())
model = AutoModelForCausalLM.from_pretrained("./output_sft", torch_dtype=torch.bfloat16, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained("./output_sft")
text = "Python 是一种高级编程语言,以简洁著称。"
ppl = calculate_perplexity(model, tokenizer, text)
print(f"Perplexity: {ppl:.2f}")
通常用在领域适配评估:在领域文本上 PPL 应该明显低于基座模型。
3. BLEU & ROUGE(文本相似度)
适合有标准答案的任务(翻译、摘要、问答)。
BLEU(机器翻译常用)
衡量生成文本和参考答案的 n-gram 重合度:
import evaluate
bleu = evaluate.load("sacrebleu")
predictions = ["Python is a high-level programming language."]
references = [["Python is a high-level language."]]
result = bleu.compute(predictions=predictions, references=references)
print(result)
# {'score': 65.43, 'counts': [...], 'totals': [...], ...}
ROUGE(摘要常用)
衡量生成摘要与参考摘要的重合度:
import evaluate
rouge = evaluate.load("rouge")
predictions = ["Python 是一种解释型编程语言。"]
references = ["Python 是一种高级编程语言。"]
result = rouge.compute(predictions=predictions, references=references)
print(result)
# {'rouge1': 0.66, 'rouge2': 0.50, 'rougeL': 0.66, ...}
| 指标 | 含义 |
|---|---|
| ROUGE-1 | 1-gram 重合度 |
| ROUGE-2 | 2-gram 重合度 |
| ROUGE-L | 最长公共子序列 |
4. 在测试集上批量评估
实际项目中需要在大量测试样本上跑评估:
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
import evaluate
from tqdm import tqdm
MODEL_DIR = "./output_sft"
TEST_DATA = "test.jsonl"
tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
model = AutoModelForCausalLM.from_pretrained(
MODEL_DIR, torch_dtype=torch.bfloat16, device_map="auto"
)
model.eval()
dataset = load_dataset("json", data_files=TEST_DATA, split="train")
rouge = evaluate.load("rouge")
predictions = []
references = []
for example in tqdm(dataset):
messages = [{"role": "user", "content": example["instruction"]}]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=512,
do_sample=False, # 评估用贪心解码,结果可复现
)
response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
predictions.append(response)
references.append(example["output"])
result = rouge.compute(predictions=predictions, references=references)
print(result)
5. 标准评测基准(Benchmark)
学术界有很多标准评测数据集,可以全方位评估模型能力:
| Benchmark | 测试能力 |
|---|---|
| MMLU | 综合知识(57 个学科多选题) |
| C-Eval | 中文综合能力 |
| CMMLU | 中文知识(科技/人文/社会) |
| GSM8K | 数学推理(小学数学题) |
| HumanEval | 代码能力 |
| MT-Bench | 多轮对话能力 |
| AlpacaEval | 指令跟随能力 |
用 OpenCompass 一键评测
OpenCompass 是国内常用的评测框架,支持上面所有 benchmark:
pip install opencompass
# 运行评测(C-Eval 为例)
python run.py --datasets ceval_gen \
--hf-path ./output_sft \
--hf-num-gpus 1 \
--max-out-len 512 \
--batch-size 8
用 lm-evaluation-harness(HuggingFace 官方推荐)
lm_eval --model hf \
--model_args pretrained=./output_sft,dtype=bfloat16 \
--tasks mmlu,gsm8k \
--batch_size 8 \
--output_path ./eval_results
6. LLM-as-Judge(用模型当裁判)
人工评估准确但慢、贵。用 GPT-4 / Claude 当评委是个折中方案,能批量评估自由生成质量:
from openai import OpenAI
client = OpenAI()
JUDGE_PROMPT = """你是一位严格的评委,请对模型回答打分(1-10 分)。
【问题】
{question}
【模型回答】
{answer}
【参考答案】
{reference}
请按以下维度评分:
1. 准确性(是否回答正确)
2. 完整性(是否覆盖关键信息)
3. 流畅度(语言是否自然)
只输出 JSON 格式:{{"score": 8, "reason": "回答准确但..."}}
"""
def judge(question, answer, reference):
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": JUDGE_PROMPT.format(
question=question, answer=answer, reference=reference
),
}],
response_format={"type": "json_object"},
)
return response.choices[0].message.content
两两对比评测(更稳定)
让裁判对比 A、B 两个模型的回答,比直接打分更可靠:
PAIRWISE_PROMPT = """对比两个回答,选择更好的一个。
【问题】{question}
【回答 A】{answer_a}
【回答 B】{answer_b}
只输出:A、B 或 TIE(平局)。
"""
7. 人工评估方法
人工评估虽然贵,但仍是金标准。常见做法:
Likert 量表
让评估员对每个维度打 1-5 分:
| 维度 | 1 | 3 | 5 |
|---|---|---|---|
| 相关性 | 完全不相关 | 部分相关 | 完全相关 |
| 准确性 | 多处错误 | 部分错误 | 完全正确 |
| 流畅度 | 语句不通 | 一般 | 自然流畅 |
A/B 测试
让评估员盲选哪个回答更好:
- 双方匿名(不告诉是哪个模型)
- 至少 100 个样本
- 多人交叉评估,避免主观偏差
8. 评估流程建议
一套完整的评估流程:
1. 自动指标(快速验证不退化)
↓
2. 标准 benchmark(量化通用能力)
↓
3. 业务测试集(针对具体场景)
↓
4. LLM-as-Judge(自由生成质量)
↓
5. 人工评估(最终把关,少量精选)
9. 评估结果对比模板
整理评估结果时建议表格化:
| 任务 | 基座模型 | SFT 模型 | SFT+DPO | 提升 |
|---|---|---|---|---|
| C-Eval | 65.2 | 68.5 | 70.1 | +4.9 |
| GSM8K | 45.0 | 48.3 | 49.0 | +4.0 |
| MT-Bench | 6.8 | 7.5 | 7.9 | +1.1 |
| 业务测试集(人工) | 6.2/10 | 7.8/10 | 8.4/10 | +2.2 |
10. 评估时的常见误区
误区 1:只看 loss
loss 是训练目标,不等于真实质量。Loss 还在下降但泛化能力可能在下降。
误区 2:只在训练集分布上测试
模型在训练相似的样本上效果好不算什么,要在分布外(OOD)样本上测试才能知道泛化能力。
误区 3:评测样本太少
少于 50 个样本的"评测"几乎没有统计意义。重要决策应基于 500+ 个样本。
误区 4:忽略通用能力
只测领域任务,可能没发现模型通用能力大幅下降。务必同时测一些通用 benchmark。
误区 5:评估数据泄漏
测试集出现在训练集中,结果会看起来很好但不真实。确保测试集和训练集严格分离。
总结
- 评估分三层:训练指标、客观指标、主观评价,不能只看 loss
- BLEU/ROUGE 适合有标准答案的任务,Perplexity 适合领域适配
- 用 OpenCompass、lm-evaluation-harness 跑标准 benchmark(MMLU、C-Eval、GSM8K)
- LLM-as-Judge 在自由生成任务上是性价比高的方案
- 人工评估是金标准,至少 100 个样本,盲评 + 多人交叉
- 警惕训练集泄漏到测试集