对齐与评测:RLHF / DPO / 基准
两件事:让模型「更符合人类偏好」,以及「证明它真的变好了」。
一、为什么 SFT 不够
SFT 教模型「照着示例说」。但它没教模型:
| 缺失的能力 | 表现 |
|---|---|
| 判断哪个回答更好 | 两个都通顺时选不出更优的 |
| 拒绝不当请求 | 有问必答,包括不该答的 |
| 承认不知道 | 编造而非说「不确定」 |
| 简洁 | 啰嗦、堆砌、过度礼貌 |
对齐(Alignment)就是补这一层。
二、RLHF:三阶段流水线
① SFT → 得到会听话的模型
② 奖励模型 RM → 用人类偏好对 (chosen, rejected) 训练「打分器」
③ PPO 强化 → 用 RM 当裁判,优化策略模型| 阶段 | 数据 | 产出 |
|---|---|---|
| SFT | 指令-回答对 | 初始策略 |
| RM | 同一 prompt 下的「好回答 / 差回答」对 | 打分模型 |
| PPO | prompts + RM 在线打分 | 最终模型 |
RLHF 的工程痛苦:要同时跑 4 个模型(策略、参考、奖励、价值),显存与调参难度都高,PPO 还不稳定。
三、DPO:为什么现在更常用
DPO(Direct Preference Optimization)跳过奖励模型,直接在偏好对上优化。
核心思路:把「用 RL 优化奖励」这一步数学等价地重参数化成一个简单的分类损失 —— 好回答的概率要高于差回答。
$$\mathcal{L}{\text{DPO}} = -\log\sigma!\left(\beta\left[\log\frac{\pi\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \log\frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}\right]\right)$$
直觉:让模型对好回答更自信、对差回答更不自信,同时别偏离原模型太远(β 控制)。
python
from trl import DPOTrainer, DPOConfig
args = DPOConfig(
output_dir="out/dpo", beta=0.1, # 0.05~0.5,越大越保守
num_train_epochs=1, per_device_train_batch_size=1,
gradient_accumulation_steps=16, learning_rate=5e-5,
bf16=True, report_to="none")
trainer = DPOTrainer(
model=policy_model, ref_model=ref_model, # ref 通常是 SFT 后的冻结副本
args=args, train_dataset=pref_ds, tokenizer=tok)
trainer.train()偏好数据格式
json
{
"prompt": "帮我写一段催款话术,要强硬",
"chosen": "建议采用分层沟通:先确认对方付款计划,再明确逾期后果……(专业、不失控)",
"rejected": "直接威胁对方,说不还钱就上门……(不当)"
}| 数据来源 | 说明 |
|---|---|
| 人工标注 | 质量最高,成本最高 |
| 强模型对比 + 人工抽检 | 主流做法 |
| 规则构造(如安全拒答对) | 冷启动 |
RLHF vs DPO 对照
| RLHF (PPO) | DPO | |
|---|---|---|
| 需要奖励模型 | 需要 | 不需要 |
| 训练稳定性 | 差 | 好 |
| 显存 | 高(4 模型) | 中(2 模型) |
| 效果上限 | 略高 | 接近 |
| 推荐 | 大厂精调 | 个人/团队首选 |
四、评测:整个 AI 工程最难的部分
一句实话
没有评测集的 AI 项目,等于没有测试的软件项目。 「感觉变好了」不是工程判断。这一节请逐条落实。
三层评测体系
| 层级 | 评什么 | 方法 | 频率 |
|---|---|---|---|
| 单元级 | 单个能力点 | 固定测试集 + 精确指标 | 每次改动 |
| 任务级 | 端到端完成度 | 人工评分 / LLM-as-Judge | 每次发布 |
| 线上级 | 真实用户价值 | A/B 测试、留存、任务成功率 | 持续 |
指标怎么选
| 任务 | 指标 |
|---|---|
| 分类/抽取 | 准确率、字段级 F1、格式合法率 |
| 生成(有参考答案) | ROUGE/BLEU(弱)、语义相似度、LLM 评分 |
| RAG | 召回率@k、忠实度(faithfulness)、答案相关性 |
| Agent | 任务成功率、平均步数、工具调用正确率 |
| 通用对话 | 有用性/无害性/诚实性 人工评分 |
LLM-as-Judge:可用但必须校准
用强模型给弱模型打分,是当前最实用的规模化评测手段。
python
JUDGE_PROMPT = """你是严格的技术评审。请对比【参考答案】与【模型回答】。
评分维度(1-5 分):
- 事实正确性:是否有错误或编造
- 完整性:是否覆盖参考要点
- 格式合规:是否符合要求格式
只输出 JSON:{"fact": 0, "complete": 0, "format": 0, "reason": ""}
【问题】{q}
【参考答案】{ref}
【模型回答】{ans}"""| 注意 | 说明 |
|---|---|
| 必须与人工评分对齐验证 | 先抽 50 条人工打分,算与 judge 的一致性 |
| 位置偏差 | 对比两个答案时,交换顺序各评一次 |
| 长度偏差 | 长答案常被偏好,需控制或指出 |
| 严格模式 | 加「宁可低分」的指令能减轻宽松倾向 |
| 成本 | 评测也花钱,缓存参考结果 |
一个最小可用的评测集
| 组成 | 数量 | 说明 |
|---|---|---|
| 常规样本 | 50–100 | 覆盖主要场景 |
| 边界样本 | 20–30 | 空输入、超长、歧义 |
| 对抗样本 | 20–30 | 提示注入、恶意请求、诱导编造 |
| 回归样本 | 20 | 曾经出过错的历史 case(最重要,防退化) |
每个 case 必须有可判定的期望结果(精确值、必含要点、必须拒答等),否则无法自动化。
五、基准测试的正确用法
| 基准 | 测什么 | 陷阱 |
|---|---|---|
| MMLU | 学科知识 | 与业务无关 |
| GSM8K / MATH | 数学推理 | 易被训练数据污染 |
| HumanEval | 代码 | 题目已公开,可能进训练集 |
| MT-Bench | 对话质量 | 主观 |
| C-Eval / CMMLU | 中文知识 | 同上 |
公开基准的三大坑
- 数据污染:题目在训练集里,分数虚高
- 与业务无关:MMLU 高 ≠ 你的客服场景好
- 饱和:头部模型分数挤在一起,区分度低
结论:公开基准只用来做粗筛,选型必须用你自己的评测集。
六、评测驱动的迭代闭环
定义指标 → 建评测集 → 跑基线 → 定位失败类型
↑ ↓
└── 验证提升 ← 改一处(Prompt/检索/微调)铁律:
- 每次只改一个变量
- 改完必须跑完整评测集(防「修好 A、弄坏 B」)
- 保留回归集,历史错题不许再错
- 记录每次改动的指标,形成表格
七、安全对齐:不能只靠 Prompt
| 风险 | 措施 |
|---|---|
| 输出违法/有害内容 | 输入输出双向过滤 + 模型拒答训练 |
| 提示注入 | 见 Prompt 工程 |
| 数据泄露 | 输出脱敏、日志脱敏、最小权限 |
| 越权操作 | Agent 的写操作必须人工确认 |
| 幻觉造成事实错误 | RAG + 引用溯源 + 置信度提示 |