InstructGPT 论文
OpenAI 的 RLHF 开创性工作
探索 RLHF 三阶段流程,对比 DPO 的简化方案。
| 特性 | RLHF | DPO |
|---|---|---|
| 需要 RM | ✅ 是 | ❌ 否 |
| 需要 RL | ✅ PPO | ❌ 直接优化 |
| 训练稳定性 | 🟡 较难 | 🟢 简单 |
| 代表模型 | ChatGPT | Llama 2 |
💡 点击阶段查看详情,或点击播放自动演示
一句话定义:RLHF (Reinforcement Learning from Human Feedback) 和 DPO (Direct Preference Optimization) 是让大语言模型对齐人类价值观的关键技术,使模型输出更有帮助、更安全、更诚实。
想象你在训练一只聪明的狗:
| 阶段 | 类比 | AI 对应 |
|---|---|---|
| 预训练 | 狗天生会很多动作 | GPT 学会了语言能力 |
| SFT | 教狗基本指令(坐、握手) | 指令微调,学会对话格式 |
| RLHF | 给狗零食奖励好行为 | 用奖励模型强化好回答 |
| DPO | 直接告诉狗”这样好,那样不好” | 从偏好对直接学习 |
| 问题 | 示例 |
|---|---|
| 有害内容 | 教用户制作危险物品 |
| 幻觉 | 一本正经地胡说八道 |
| 不服从 | 不回答问题,说一堆废话 |
| 偏见 | 输出歧视性内容 |
用户: 如何制作炸弹?
❌ 未对齐: 首先你需要准备以下材料...
✅ 已对齐: 抱歉,我无法提供任何关于制造危险物品的信息。 如果你对化学感兴趣,我可以推荐一些安全的实验...┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Stage 1 │ │ Stage 2 │ │ Stage 3 ││ SFT │ → │ Reward │ → │ PPO ││ 监督微调 │ │ 奖励模型 │ │ 强化学习 │└─────────────┘ └─────────────┘ └─────────────┘用高质量对话数据微调预训练模型:
# 示例数据格式{ "prompt": "解释什么是黑洞", "response": "黑洞是一种..." # 人工编写的高质量回答}训练一个模型给回答打分:
训练数据:人类对比两个回答,选择更好的那个。
Prompt: "如何学习编程?"Response A: "多写代码,从简单项目开始..." ← 人类选择 ✓Response B: "编程很难,你可能学不会..."损失函数:
用奖励模型指导 LLM 优化:
DPO 的核心洞察:奖励模型可以被隐式表示,不需要单独训练!
| 特性 | RLHF | DPO |
|---|---|---|
| 复杂度 | 高(3阶段) | 低(1阶段) |
| 训练稳定性 | 较差(RL 不稳定) | 好 |
| 计算资源 | 多(需要多个模型) | 少 |
| 效果 | 优秀 | 接近 RLHF |
| 代表应用 | ChatGPT, Claude | Llama 2, Zephyr |
import torchimport torch.nn.functional as F
def dpo_loss( policy_chosen_logps: torch.Tensor, # π(y_w|x) policy_rejected_logps: torch.Tensor, # π(y_l|x) ref_chosen_logps: torch.Tensor, # π_ref(y_w|x) ref_rejected_logps: torch.Tensor, # π_ref(y_l|x) beta: float = 0.1) -> torch.Tensor: """ DPO 损失函数
Args: policy_*_logps: 当前策略的 log 概率 ref_*_logps: 参考模型的 log 概率 beta: 温度系数 """ # 计算 log ratio chosen_logratios = policy_chosen_logps - ref_chosen_logps rejected_logratios = policy_rejected_logps - ref_rejected_logps
# DPO 损失 losses = -F.logsigmoid(beta * (chosen_logratios - rejected_logratios))
return losses.mean()
# 使用 TRL 库更简单from trl import DPOTrainer
trainer = DPOTrainer( model=model, ref_model=ref_model, beta=0.1, train_dataset=dataset, tokenizer=tokenizer,)trainer.train()import torchimport torch.nn as nn
class RewardModel(nn.Module): """奖励模型: 给回答打分"""
def __init__(self, base_model): super().__init__() self.backbone = base_model # 添加一个输出头,将隐藏状态映射到标量 self.reward_head = nn.Linear( base_model.config.hidden_size, 1 )
def forward(self, input_ids, attention_mask): # 获取最后一个 token 的隐藏状态 outputs = self.backbone( input_ids=input_ids, attention_mask=attention_mask, output_hidden_states=True ) last_hidden = outputs.hidden_states[-1]
# 取最后一个非 padding token batch_size = input_ids.shape[0] seq_lens = attention_mask.sum(dim=1) - 1 last_token_hidden = last_hidden[ torch.arange(batch_size), seq_lens ]
# 输出奖励分数 reward = self.reward_head(last_token_hidden) return reward.squeeze(-1)
def reward_model_loss(rewards_chosen, rewards_rejected): """Bradley-Terry 损失""" return -torch.log( torch.sigmoid(rewards_chosen - rewards_rejected) ).mean()