跳转至

微调模型评估

模型训练完了,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 重合度:

pip install evaluate sacrebleu
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(摘要常用)

衡量生成摘要与参考摘要的重合度:

pip install rouge-score
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 官方推荐)

pip install lm-eval
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 个样本,盲评 + 多人交叉
  • 警惕训练集泄漏到测试集

评论