微调效果评估与部署
微调完了,怎么知道效果好不好?怎么部署到生产?
微调效果评估与部署
微调完了,怎么知道效果好不好?怎么部署到生产?
1. 为什么评估至关重要:不评估就上线是最危险的做法
先说一个真实的反面教材类型的场景:
你花了一周时间收集数据、准备环境、跑完训练,loss 曲线看起来不错,随手测了几个问题,感觉比基础模型好了很多,兴奋地上线了。
三天后,你收到用户反馈:某类问题的回答质量变差了,有时候格式乱掉,有时候答非所问。你去查,发现是微调后的模型在训练数据没有覆盖到的边界情况上表现很差——这是过拟合,模型对训练样本"死记硬背"了,遇到新问题就出错。
更糟糕的是,因为微调用的是领域专有数据,模型还"遗忘"了一部分原来的通用能力——这叫灾难性遗忘(Catastrophic Forgetting)。用户说"自从上次更新,这个 AI 感觉变笨了",他们说的是真的。
没有系统评估,你完全不知道:
- 模型在哪些问题上还是会出错(覆盖盲区)
- 微调后有没有遗忘原来的通用能力
- 新模型比基础模型到底好多少(可能只是心理安慰)
- 上线后会不会让某些用户的体验变差
评估的目标是:用数据说话,而不是靠感觉。
2. 为什么评估比训练更重要
很多人微调完模型,跑几个例子感觉不错就上线了。这是最危险的做法。
没有系统评估,你不知道:
- 模型在哪些问题上还是会出错
- 微调后有没有"遗忘"原来的能力
- 新模型比基础模型到底好多少(可能只是心理安慰)
- 上线后用户体验会不会更差
评估的目的是:用数据说话,而不是靠感觉。
3. 第一步:建立评估集
微调前就要准备好评估集,而不是训练完了再想。
评估集的要求:
- 数量:至少50条,覆盖目标任务的各种情况
- 多样性:包含简单、中等、困难三个难度
- 代表性:覆盖真实用户会问的问题类型
- 黄金答案:每条都有人工标注的参考答案
# 评估集格式示例
eval_dataset = [
{
"id": "eval_001",
"question": "我们公司的年假政策是什么?",
"reference_answer": "根据公司政策,工作满1年享有5天年假,满3年10天,满5年15天。",
"category": "HR政策",
"difficulty": "easy"
},
{
"id": "eval_002",
"question": "请帮我写一封拒绝供应商报价的邮件,语气要委婉",
"reference_answer": "感谢贵方提供的报价方案...", # 参考答案
"category": "邮件写作",
"difficulty": "medium"
},
# ... 至少50条
]
4. 第二步:三层评估体系
微调效果三层评估体系 — 人工评估、LLM 自动评估与 A/B 测试
4.1. 层级1:人工评估(最可靠,最费时)
适用场景:第一次评估新模型,快速判断方向是否正确。
操作步骤:
- 准备20-30个测试问题(覆盖主要使用场景)
- 对比基础模型和微调后模型的输出
- 用1-5分打分,记录具体理由
# 批量生成对比输出,方便人工评估
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
import json
def generate_comparison(questions, base_model_name, finetuned_model_path):
"""生成基础模型和微调模型的对比输出"""
results = []
# 加载两个模型
base_tokenizer = AutoTokenizer.from_pretrained(base_model_name)
base_model = AutoModelForCausalLM.from_pretrained(
base_model_name, torch_dtype=torch.float16, device_map="auto"
)
ft_tokenizer = AutoTokenizer.from_pretrained(finetuned_model_path)
ft_model = AutoModelForCausalLM.from_pretrained(
finetuned_model_path, torch_dtype=torch.float16, device_map="auto"
)
for q in questions:
# 基础模型输出
base_input = base_tokenizer(q, return_tensors="pt").to(base_model.device)
base_output = base_model.generate(**base_input, max_new_tokens=200, temperature=0.1)
base_response = base_tokenizer.decode(base_output[0], skip_special_tokens=True)
# 微调模型输出
ft_input = ft_tokenizer(q, return_tensors="pt").to(ft_model.device)
ft_output = ft_model.generate(**ft_input, max_new_tokens=200, temperature=0.1)
ft_response = ft_tokenizer.decode(ft_output[0], skip_special_tokens=True)
results.append({
"question": q,
"base_model": base_response,
"finetuned_model": ft_response,
"human_score_base": None, # 人工填写
"human_score_ft": None, # 人工填写
"notes": ""
})
# 保存为JSON,方便人工评分
with open("comparison_results.json", "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"对比结果已保存到 comparison_results.json,请人工打分")
return results
人工评分标准:
| 分数 | 含义 |
|---|---|
| 5分 | 完全正确,表述清晰,超出预期 |
| 4分 | 基本正确,有小瑕疵 |
| 3分 | 方向对但有明显不足 |
| 2分 | 部分正确,有较大错误 |
| 1分 | 完全错误或答非所问 |
判断标准:微调后平均分比基础模型高0.5分以上,才算有实质性提升。
4.2. 层级2:LLM自动评估(效率高,适合大批量)
用 GPT-4o 或 Claude 作为"评委",自动评估模型输出质量。
为什么 LLM 评估比 ROUGE 更适合中文?
ROUGE(Recall-Oriented Understudy for Gisting Evaluation)是为英文设计的指标,通过词重叠率衡量相似度。但它有两个问题:
- 中文需要分词:不分词直接用字符级别计算,准确率很低
- 语义不敏感:两个意思相同但用词不同的答案,ROUGE 分可能很低
LLM 评估能理解语义,更适合开放式问答的评估。
# pip install openai
from openai import OpenAI
import json
from typing import List, Dict
client = OpenAI()
def llm_evaluate_batch(eval_cases: List[Dict]) -> List[Dict]:
"""
批量用 LLM 评估模型输出
eval_cases 格式:
[{"question": "...", "reference": "...", "response": "..."}, ...]
"""
results = []
for case in eval_cases:
prompt = f"""你是一个专业的AI输出质量评估员。
请评估以下AI助手的回答质量。
【用户问题】
{case['question']}
【参考答案(标准答案)】
{case['reference']}
【AI回答(待评估)】
{case['response']}
请从以下维度评分(每项1-10分):
1. 准确性:回答是否与参考答案一致,有无事实错误
2. 完整性:是否覆盖了参考答案的关键信息
3. 表达质量:语言是否清晰、流畅、专业
以JSON格式返回,不要有其他文字:
{{"accuracy": 8, "completeness": 7, "quality": 9, "overall": 8, "reason": "简要说明评分理由"}}"""
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0 # 评估要稳定,用temperature=0
)
scores = json.loads(resp.choices[0].message.content)
results.append({
**case,
"scores": scores
})
return results
def summarize_evaluation(results: List[Dict]) -> Dict:
"""汇总评估结果,计算平均分"""
if not results:
return {}
metrics = ["accuracy", "completeness", "quality", "overall"]
summary = {}
for metric in metrics:
scores = [r["scores"][metric] for r in results if metric in r.get("scores", {})]
if scores:
summary[f"avg_{metric}"] = sum(scores) / len(scores)
summary["total_cases"] = len(results)
summary["pass_rate"] = sum(1 for r in results if r.get("scores", {}).get("overall", 0) >= 7) / len(results)
return summary
# 使用示例
eval_cases = [
{
"question": "我们公司的年假政策是什么?",
"reference": "工作满1年5天,满3年10天,满5年15天",
"response": "根据公司规定,入职满一年后可享有5天带薪年假..."
},
# ... 更多测试案例
]
results = llm_evaluate_batch(eval_cases)
summary = summarize_evaluation(results)
print(f"平均综合分:{summary['avg_overall']:.1f}/10")
print(f"通过率(7分以上):{summary['pass_rate']:.1%}")
评分解读指南:
| 平均综合分 | 含义 | 建议 |
|---|---|---|
| 8.5分以上 | 优秀,超过预期 | 可以上线 |
| 7.0-8.5分 | 良好,满足需求 | 可以上线,持续迭代 |
| 5.5-7.0分 | 一般,有明显不足 | 需要补充训练数据再微调 |
| 5.5分以下 | 较差,不符合预期 | 检查数据质量和训练配置 |
4.3. 层级3:ROUGE 指标(适合摘要、翻译类任务)
对于摘要、翻译等有标准答案的任务,ROUGE 仍有参考价值,但中文需要先分词。
# pip install rouge-score jieba
import jieba
from rouge_score import rouge_scorer
def evaluate_rouge_chinese(predictions: list, references: list) -> dict:
"""
中文 ROUGE 评估
注意:必须先用 jieba 分词,否则结果不准确
ROUGE 是为英文设计的,中文需要先分词才能正确计算词重叠率
"""
scorer = rouge_scorer.RougeScorer(
['rouge1', 'rouge2', 'rougeL'],
tokenizer=None # 我们自己处理分词
)
all_scores = {"rouge1": [], "rouge2": [], "rougeL": []}
for pred, ref in zip(predictions, references):
# 用 jieba 分词,用空格连接(让 rouge_score 按空格切分)
pred_tokenized = " ".join(jieba.cut(pred))
ref_tokenized = " ".join(jieba.cut(ref))
score = scorer.score(ref_tokenized, pred_tokenized)
all_scores["rouge1"].append(score["rouge1"].fmeasure)
all_scores["rouge2"].append(score["rouge2"].fmeasure)
all_scores["rougeL"].append(score["rougeL"].fmeasure)
return {
"rouge1": sum(all_scores["rouge1"]) / len(all_scores["rouge1"]),
"rouge2": sum(all_scores["rouge2"]) / len(all_scores["rouge2"]),
"rougeL": sum(all_scores["rougeL"]) / len(all_scores["rougeL"]),
}
# ROUGE 分数参考基准(摘要任务)
# ROUGE-L > 0.4:较好
# ROUGE-L > 0.5:很好
# ROUGE-L > 0.6:优秀
# 注意:不同任务的基准不同,这里是摘要任务的参考值
⚠️ ROUGE 的局限性:
- 只衡量词重叠,不理解语义(同义词、换一种说法得分会低)
- 对中文必须先分词,否则结果不可信
- 不适合开放式问答(参考答案不唯一的场景)
- 推荐:开放式问答用 LLM 评估,摘要/翻译可用 ROUGE 辅助
5. 第三步:常见问题诊断
训练完发现效果不好,怎么找原因?
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 训练集好,测试集差(过拟合) | 训练数据太少或重复太多 | 增加数据多样性、减少 epoch、增大 dropout |
| 效果没有提升(欠拟合) | 学习率太低、数据格式错误 | 检查数据格式、适当提高学习率 |
| 微调后失去原有能力(灾难遗忘,Catastrophic Forgetting,模型学了新任务后忘记了原有的通用能力) | 训练数据太单一 | 混入10-20%通用对话数据 |
| 输出格式不稳定 | 格式示例不足 | 增加更多格式一致的训练样本 |
| 回答太短/太长 | 训练数据长度分布不均 | 检查并平衡训练数据的长度分布 |
6. 第四步:A/B 测试(上线后的验证)
内部评估通过后,上线前要做 A/B 测试,用真实用户数据验证效果。
A/B 测试方案:
用户请求
↓
流量分配(50% vs 50%)
├── A组:基础模型(对照组)
└── B组:微调模型(实验组)
↓
收集用户反馈
├── 显式反馈:点赞/点踩
└── 隐式反馈:对话轮数、任务完成率
↓
统计分析(至少运行1-2周,收集足够样本)
↓
决策:全量切换 / 继续迭代 / 回滚
关键指标:
- 用户满意度:点赞率、点踩率
- 任务完成率:用户是否完成了他们想做的事
- 对话轮数:轮数少通常说明一次就解决了问题
统计显著性判断:
样本量不够时,差异可能只是随机波动。一般需要至少300-500个有效样本,才能判断A/B差异是否显著。
# 简单的A/B测试统计显著性检验
from scipy import stats
def ab_test_significance(a_successes, a_total, b_successes, b_total):
"""
检验A/B测试结果是否统计显著
a_successes: A组成功数(如点赞数)
a_total: A组总样本数
b_successes: B组成功数
b_total: B组总样本数
"""
a_rate = a_successes / a_total
b_rate = b_successes / b_total
# 使用卡方检验
contingency_table = [
[a_successes, a_total - a_successes],
[b_successes, b_total - b_successes]
]
chi2, p_value, dof, expected = stats.chi2_contingency(contingency_table)
print(f"A组满意率: {a_rate:.1%}")
print(f"B组满意率: {b_rate:.1%}")
print(f"差异: {(b_rate - a_rate):.1%}")
print(f"p值: {p_value:.4f}")
if p_value < 0.05:
if b_rate > a_rate:
print("✅ 结论:B组(微调模型)显著优于A组,建议全量切换")
else:
print("❌ 结论:B组(微调模型)显著差于A组,建议回滚")
else:
print("⚠️ 结论:差异不显著(p > 0.05),需要更多样本或继续观察")
# 示例
ab_test_significance(
a_successes=180, a_total=300, # A组:300次,180次满意
b_successes=225, b_total=300 # B组:300次,225次满意
)
7. 第五步:部署方案
评估通过后,选择合适的部署方式。
7.1. 方案1:本地部署(Ollama)
适合:个人使用、内网部署、对隐私要求高的场景
# 第1步:合并 LoRA 权重(如果用了 LoRA 微调)
# 在 Python 中执行:
# from peft import AutoPeftModelForCausalLM
# model = AutoPeftModelForCausalLM.from_pretrained("./lora-output")
# merged = model.merge_and_unload()
# merged.save_pretrained("./merged-model")
# 第2步:转换为 GGUF 格式(llama.cpp 支持的格式)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp && make
python convert_hf_to_gguf.py ../merged-model --outfile ../model.gguf --outtype f16
# 第3步:量化(可选,减小文件体积)
./quantize ../model.gguf ../model-q4_k_m.gguf Q4_K_M
# 第4步:创建 Ollama 模型
# 先写 Modelfile
cat > Modelfile << 'EOF'
FROM ./model-q4_k_m.gguf
# 系统提示词(根据你的微调任务修改)
SYSTEM """你是一个专业的企业内部助手,熟悉公司的政策和流程。
请用简洁、专业的语言回答员工的问题。"""
# 推理参数
PARAMETER temperature 0.1
PARAMETER top_p 0.9
PARAMETER num_ctx 4096
EOF
ollama create my-assistant -f Modelfile
ollama run my-assistant "我们公司的年假政策是什么?"
7.2. 方案2:API 服务(vLLM)
适合:需要高并发、生产环境部署
vLLM:一个专为高并发 LLM 推理设计的开源推理引擎,通过 PagedAttention 内存管理和连续批处理技术,相比普通部署方式可提升 10-30 倍吞吐量,是目前工业界最主流的 LLM 生产服务方案。
# 安装 vLLM
pip install vllm
# 启动 OpenAI 兼容的 API 服务
python -m vllm.entrypoints.openai.api_server \
--model ./merged-model \
--port 8000 \
--max-model-len 4096 \
--gpu-memory-utilization 0.9
# 调用方式和 OpenAI API 完全兼容
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed" # vLLM 本地部署不需要真实 API key
)
response = client.chat.completions.create(
model="./merged-model",
messages=[
{"role": "system", "content": "你是一个专业的企业内部助手"},
{"role": "user", "content": "我们公司的年假政策是什么?"}
],
temperature=0.1,
max_tokens=500
)
print(response.choices[0].message.content)
7.3. 方案3:云端 API(最简单)
适合:快速验证、不想管基础设施
将微调后的模型上传到 Hugging Face,然后使用 Inference API 或部署到 Hugging Face Spaces。
8. 完整评估决策流程
训练完成
↓
【第1步】人工评估(20-30个case)
├── 平均分 < 3.5 → 检查训练数据,重新微调
└── 平均分 ≥ 3.5 → 继续
↓
【第2步】LLM 自动评估(50+ case)
├── 综合分 < 6.0 → 补充训练数据,重新微调
└── 综合分 ≥ 6.0 → 继续
↓
【第3步】灾难遗忘检测(通用能力测试)
├── 通用能力明显下降 → 混入通用数据,重新微调
└── 通用能力正常 → 继续
↓
【第4步】小流量 A/B 测试(10%流量,1周)
├── 用户满意度下降 → 回滚
└── 用户满意度持平或提升 → 继续
↓
【第5步】全量上线 + 持续监控
9. 小结
| 评估方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 人工评估 | 最准确,发现细微问题 | 费时,难以大规模 | 首次评估,判断方向 |
| LLM 评估 | 效率高,可大批量 | 有一定误差 | 批量评估,快速迭代 |
| ROUGE | 客观,可复现 | 不理解语义,中文需分词 | 摘要、翻译类任务 |
| A/B 测试 | 最真实的用户反馈 | 需要上线才能做 | 最终上线前验证 |
微调的完整流程:
数据准备 → 微调训练 → 人工评估 → LLM自动评估 → A/B测试 → 全量上线 → 持续迭代
后续应用:评估通过后,可以参考:
- 第17章 MLOps(模型版本管理、监控、持续迭代)
- 第18章 生产化部署(高并发服务、容器化部署)