RLHF 与 DPO 训练实战:从奖励模型到人类偏好对齐

RLHF(基于人类反馈的强化学习)是 ChatGPT、Claude 等对话模型「涌现」出 helpfulness 和 harmlessness 的核心技术。本文深入讲解 RLHF 的三阶段训练流程、PPO 算法的实现细节、奖励模型的标度律,以及 DPO(Direct Preference Optimization)这一无需强化学习的替代方案,附完整训练代码与超参调优策略。

为什么 SFT 不够:从模仿学习到偏好学习

监督微调(SFT, Supervised Fine-Tuning)是大语言模型对话能力的基础。通过在海量高质量指令-回答对上训练,模型学会了遵循指令、生成格式正确的输出。然而,SFT 存在一个根本性的局限:它只能模仿人类给出的示例回答,而无法超越示例的质量。

假设 SFT 数据包含两条回答:「还算可以」和「非常精彩」。SFT 损失函数(交叉熵)将两者同等对待——模型只需预测训练集中的 token 分布即可最小化损失。但人类明显偏好后者。SFT 无法表达「B 比 A 更好」这种比较关系,更无法激励模型探索训练集中不存在的、可能更优的回答。

RLHF 的核心洞见是:人类评估者虽然不擅长写出完美的回答,但擅长比较两个回答哪个更好。这种比较信号足以训练一个奖励模型(Reward Model),再用强化学习(PPO)优化策略模型,使其输出的回答获得尽可能高的奖励分数。

RLHF 三阶段训练流程

标准的 RLHF 流水线包含三个阶段,每个阶段使用不同的数据集和优化目标:

阶段一:SFT(Supervised Fine-Tuning)
  数据:高质量(指令,回答)对
  目标:学习指令遵循的基本能力
  输出:SFT 模型 π_SFT

阶段二:RM 训练(Reward Model Training)
  数据:同一指令的多个回答 + 人类偏好排序
  目标:学习预测人类偏好的标量奖励分数
  输出:奖励模型 r_θ

阶段三:PPO 强化学习(Proximal Policy Optimization)
  数据:新指令提示
  目标:最大化奖励模型的评分,同时保持与 SFT 模型的 KL 散度约束
  输出:RLHF 模型 π_RL

阶段一:SFT 基座

SFT 阶段通常使用 110 万条高质量的指令-回答对进行 13 个 epoch 的微调。数据质量远比数量重要:InstructGPT 的实验表明,使用 1 万条精心筛选的数据训练的模型,效果远优于使用 10 万条低质量数据。

SFT 的关键超参:

超参推荐值说明
学习率1e-5 ~ 5e-5远低于预训练,防止灾难性遗忘
Epoch1~3过多 epoch 导致过拟合训练数据
最大长度2048~4096覆盖大部分指令-回答场景
学习率调度Cosine with Warmupwarmup 比例 3%~6%

阶段二:奖励模型训练

奖励模型的输入是(提示,回答)对,输出是一个标量分数,表示该回答的人类偏好程度。具体实现上,奖励模型通常不是从头训练,而是在 SFT 模型之上添加一个价值头(Value Head)——一个线性层将隐藏状态映射为标量。

训练数据格式为「三元组」:(提示,chosen 回答,rejected 回答)。训练目标是比较损失(Ranking Loss):

L = -log σ(r_θ(x, y_w) - r_θ(x, y_l))

其中 y_w 是人类偏好的回答(win),y_l 是不偏好的回答(lose),σ 是 sigmoid 函数。损失函数鼓励模型对 preferred 回答给出更高的分数,对 rejected 回答给出更低的分数。

import torch
import torch.nn as nn

class RewardModel(nn.Module):
    def __init__(self, base_model):
        super().__init__()
        self.base = base_model
        self.value_head = nn.Linear(base_model.config.hidden_size, 1)

    def forward(self, input_ids, attention_mask):
        outputs = self.base(input_ids=input_ids, attention_mask=attention_mask)
        hidden = outputs.last_hidden_state[:, -1, :]  # 最后一个 token 的隐藏状态
        reward = self.value_head(hidden).squeeze(-1)
        return reward

def ranking_loss(reward_chosen, reward_rejected):
    """Bradley-Terry 模型损失"""
    return -torch.log(torch.sigmoid(reward_chosen - reward_rejected)).mean()

奖励模型的关键挑战是标度律(Scaling Law)。OpenAI 的实验表明,奖励模型的参数量应至少为策略模型的 1/10 到 1/5,且与人类偏好的相关性随模型规模增大而提升。一个 7B 的策略模型搭配 1B 的奖励模型往往效果不佳,而 7B 策略 + 7B 奖励或 13B 策略 + 7B 奖励能显著提升最终效果。

另一个关键问题是奖励黑客(Reward Hacking):模型可能发现奖励模型的漏洞,生成获得高分但对人类毫无价值的输出。例如,奖励模型对「详细解释」给分较高,策略模型可能学到输出冗长的重复内容来刷分。缓解策略包括:多样化的奖励模型集成(Ensemble)、KL 散度约束、以及在 PPO 阶段定期用人类评估校准。

阶段三:PPO 强化学习

PPO(Proximal Policy Optimization)是当前 RLHF 的主流算法。相比传统的策略梯度(Policy Gradient),PPO 通过裁剪目标函数限制了每次策略更新的步长,防止策略剧烈变化导致训练不稳定。

PPO 的核心目标函数:

L_CLIP(θ) = E_t [ min(r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1+ε) * A_t) ]

其中 r_t(θ) = π_θ(a_t|s_t) / π_θ_old(a_t|s_t)  (重要性采样比)
      A_t    = 优势函数(Advantage)

在 LLM 场景中,状态 s_t 是已生成的 token 序列 x, y_1, ..., y_{t-1},动作 a_t 是下一个 token y_t。

PPO 训练中的关键组件:

1. 优势函数估计(GAE)

广义优势估计(Generalized Advantage Estimation, GAE)平衡了优势估计的偏差和方差:

def compute_gae(rewards, values, gamma=0.99, lam=0.95):
    """计算 GAE 优势估计"""
    advantages = []
    gae = 0
    for t in reversed(range(len(rewards))):
        if t == len(rewards) - 1:
            next_value = 0
        else:
            next_value = values[t + 1]
        delta = rewards[t] + gamma * next_value - values[t]
        gae = delta + gamma * lam * gae
        advantages.insert(0, gae)
    return torch.tensor(advantages)

2. KL 散度约束

为了防止 RL 模型偏离 SFT 模型太远(遗忘通用语言能力),PPO 目标中添加 KL 散度惩罚:

L_total = L_CLIP - β * KL(π_RL || π_SFT) + c1 * L_value + c2 * S_entropy

β 是 KL 惩罚系数,通常初始设置为 0.01~0.02,随着训练动态调整。若 KL 散度超过阈值(如 0.02 bits/token),增大 β;若 KL 过小且奖励不再增长,减小 β 鼓励更多探索。

3. 完整的 PPO 训练循环

from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import PPOTrainer, PPOConfig

# 加载模型
model = AutoModelForCausalLM.from_pretrained("sft-model-path")
ref_model = AutoModelForCausalLM.from_pretrained("sft-model-path")  # 冻结的参考模型
reward_model = RewardModel.load("reward-model-path")

config = PPOConfig(
    model_name="sft-model-path",
    learning_rate=1e-5,
    batch_size=32,
    mini_batch_size=4,
    gradient_accumulation_steps=8,
    ppo_epochs=4,
    kl_penalty="kl",
)

ppo_trainer = PPOTrainer(config, model, ref_model, tokenizer)

for batch in dataloader:
    queries = batch["query"]

    # 1. 策略模型生成回答
    responses = ppo_trainer.generate(queries, max_new_tokens=256)

    # 2. 奖励模型打分
    rewards = []
    for query, response in zip(queries, responses):
        text = tokenizer.decode(query) + tokenizer.decode(response)
        reward = reward_model.score(text)
        rewards.append(reward)

    # 3. PPO 更新
    stats = ppo_trainer.step(queries, responses, rewards)

    # 4. 日志
    print(f"Reward: {stats['ppo/returns/mean']:.3f}, "
          f"KL: {stats['ppo/policy/kl']:.4f}, "
          f"Entropy: {stats['ppo/entropy']:.3f}")

PPO 训练的常见问题与对策

问题现象对策
奖励黑客模型输出无意义但得分高增加 KL 惩罚、多样化 RM、定期人工评估
模式崩溃所有输出趋同增大 Entropy 系数、使用较大的 mini_batch
训练发散KL 突然暴增、Reward 骤降降低学习率、减小 PPO epoch 数、梯度裁剪
长度偏差RM 偏好长回答对回答长度做归一化或显式控制

DPO:无需强化学习的偏好优化

PPO 虽然有效,但实现复杂度高:需要维护策略模型、参考模型、奖励模型和价值模型四个网络,超参众多且调参困难。2023 年提出的 DPO(Direct Preference Optimization)提供了一种优雅的替代方案。

DPO 的核心洞察是:奖励模型和最优策略之间存在闭式关系。Bradley-Terry 偏好模型下,最优策略可以表示为:

π*(y|x) = (1/Z(x)) * π_ref(y|x) * exp(β⁻¹ * r(x, y))

将奖励 r 用策略和参考策略隐式表达,DPO 的损失函数直接在整个偏好数据上优化策略:

L_DPO = -log σ(β * [log(π_θ(y_w|x) / π_ref(y_w|x)) - log(π_θ(y_l|x) / π_ref(y_l|x))])
def dpo_loss(policy_logps_w, policy_logps_l, ref_logps_w, ref_logps_l, beta=0.1):
    """
    policy_logps_w: 策略模型对 preferred 回答的对数概率
    policy_logps_l: 策略模型对 rejected 回答的对数概率
    ref_logps_w/l: 参考模型(SFT 模型,冻结)的对数概率
    """
    policy_ratio = policy_logps_w - policy_logps_l
    ref_ratio = ref_logps_w - ref_logps_l
    logits = beta * (policy_ratio - ref_ratio)
    return -torch.log(torch.sigmoid(logits)).mean()

DPO 的优势极其显著:

  • 训练稳定性:无需 GAE、Advantage 估计和重要性采样,训练更稳定
  • 实现简单:只需策略模型和参考模型,代码量约为 PPO 的 1/5
  • 计算效率高:不需要生成完整的回答序列再打分,可在偏好数据上直接做监督学习

实验表明,DPO 在大多数任务上能达到 PPO 相当甚至更好的效果,特别是在偏好数据质量较高的场景下。DPO 的劣势是对超参 β 敏感:β 过大导致策略过于保守(接近 SFT),β 过小导致策略不稳定。

IPO 与 KTO:DPO 的进化方向

DPO 之后,研究社区提出了多个改进方案:

IPO(Identity Preference Optimization):修改 DPO 损失为均方误差形式,避免 DPO 在远离参考策略时的过优化问题。

KTO(Kahneman-Tversky Optimization):摆脱配对偏好数据的需求,直接使用二元反馈(好/坏)优化策略。KTO 将前景理论(Prospect Theory)引入偏好优化,对「好」回答和「坏」回答使用不对称的损失权重。

SimPO(Simple Preference Optimization):进一步简化,移除参考模型,直接使用隐性奖励(Implicit Reward)进行优化,在保持效果的同时降低内存占用。

RLHF 数据工程

RLHF 的效果高度依赖数据质量。奖励模型训练所需的偏好数据通常通过以下方式收集:

人工标注:雇佣标注员对模型生成的回答进行两两比较或 Likert 量表评分。InstructGPT 使用了约 4 万条比较标注,标注员需遵循详细的标注指南——不仅比较有用性,还要评估真实性(truthfulness)和无害性(harmlessness)。

模型辅助标注:使用更强的模型(如 GPT-4)生成偏好判断,作为标注 seed 或替代人工。这种方法成本极低但质量参差不齐,通常需要人工校验关键样本。

自动化偏好信号:某些场景有天然的偏好信号:代码生成任务中代码是否通过测试编译、数学题回答是否正确、摘要任务中 ROUGE 分数。这些自动信号可以作为弱监督偏好数据。

数据多样性同样关键。若偏好数据全部来自单一领域(如客服对话),奖励模型在该领域表现优异但对领域外输入的泛化性差。好的偏好数据集应覆盖多个任务类型、多种难度级别和多样的用户画像。

评估 RLHF 效果

评估 RLHF 模型的挑战在于:它优化的目标是「人类偏好」,而非传统的 perplexity 或 BLEU 分数。推荐的多维度评估框架:

自动评估:

  • 奖励模型分数:RL 模型输出在冻结的 RM 上的得分,应高于 SFT 模型
  • KL 散度:度量 RL 模型与 SFT 模型的偏离程度,应保持在合理范围内
  • GPT-4 评估:使用 GPT-4 作为裁判,对比 RL 模型和基线模型的输出质量

人工评估:

  • 成对比较:评估者盲测两个模型的回答,选择更优者,统计胜率
  • Likert 量表:对有用性(Helpfulness)、无害性(Harmlessness)、诚实性(Honesty)分别评分
  • Elo 评分:通过多轮比较计算模型的 Elo 评分,建立模型排名

红队测试(Red Teaming):

组织专门团队尝试诱导模型生成有害、偏见或不真实的输出。记录模型在对抗性提示下的表现,作为安全评估的核心指标。

总结

RLHF 将人类的价值判断融入语言模型的训练过程,是 LLM 从「文本预测器」进化为「有用助手」的关键技术。从 SFT 基座到奖励模型再到 PPO 强化学习,三阶段流水线虽然复杂,但每一步都有清晰的数学原理和工程实践。

DPO 及其变体(IPO、KTO)为代表的新方向正在简化这一流程,使得中小团队也能实现高质量的偏好对齐。无论采用 PPO 还是 DPO,成功的关键在于:高质量的偏好数据、合理的超参调优、以及多维度的系统评估。RLHF 不是一次性的训练步骤,而是一个持续迭代的过程——随着人类偏好的演变和新场景的出现,模型需要不断地重新对齐。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 模型量化技术详解:INT8、FP16 与混合精度推理
  2. 模型剪枝与知识蒸馏:从压缩到加速全链路
  3. 推理引擎终极对比:TensorRT vs ONNX Runtime vs OpenVINO