强化学习(PPO,DPO)在 LLM 训练中的应用
强化学习是 LLM 对齐(Alignment)的关键技术,让模型输出更符合人类偏好。
为什么需要 RL?
预训练模型只是"预测下一个词",可能产生有害、无帮助或不真实的输出。
RLHF(Reinforcement Learning from Human Feedback) 流程:
预训练 → 监督微调(SFT) → 奖励模型训练 → PPO 强化学习 → 最终模型核心概念
策略(Policy)
LLM 本身:输入提示,输出 token 分布(概率)。
目标:优化策略,使得奖励最大化。
奖励(Reward)
人类偏好模型(RM)对输出的评分。
训练 RM:
人类标注数据:(提示, 输出A, 输出B) → 人类偏好 A > B
训练二分类模型:p(A > B | 提示, A, B)PPO(Proximal Policy Optimization)
OpenAI 用于 ChatGPT 训练的算法。
目标函数
L(θ) = E[ 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)
- ε - 裁剪范围(通常 0.1-0.2)
关键:限制策略更新幅度,防止训练不稳定。
实现步骤
1. 准备数据
python
from datasets import load_dataset
from transformers import AutoTokenizer
# 人类偏好数据集(如 Anthropic HH-RLHF)
dataset = load_dataset("Anthropic/hh-rlhf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b")
def tokenize(example):
chosen = tokenizer(example["chosen"], truncation=True, max_length=512)
rejected = tokenizer(example["rejected"], truncation=True, max_length=512)
return {"chosen_input_ids": chosen.input_ids, "rejected_input_ids": rejected.input_ids}
dataset = dataset.map(tokenize)2. 奖励模型训练
python
import torch.nn as nn
from transformers import AutoModelForSequenceClassification
class RewardModel(nn.Module):
def __init__(self, model_name):
super().__init__()
self.model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=1
)
def forward(self, input_ids, attention_mask):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
return outputs.logits.squeeze(-1) # 标量奖励
# 对比损失
def reward_loss(chosen_reward, rejected_reward):
return -F.logsigmoid(chosen_reward - rejected_reward).mean()
# 训练循环
for batch in dataloader:
chosen_reward = rm(batch["chosen_input_ids"], batch["chosen_attention_mask"])
rejected_reward = rm(batch["rejected_input_ids"], batch["rejected_attention_mask"])
loss = reward_loss(chosen_reward, rejected_reward)
loss.backward()3. PPO 训练
使用 TRL(Transformer Reinforcement Learning)库:
python
from trl import PPOTrainer, AutoModelForCausalLMWithValueHead
from trl.core import respond_to_batch
# 加载模型和 tokenizer
model = AutoModelForCausalLMWithValueHead.from_pretrained("sft_model")
ref_model = AutoModelForCausalLMWithValueHead.from_pretrained("sft_model") # 参考模型
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b")
# PPO 配置
ppo_trainer = PPOTrainer(
model=model,
ref_model=ref_model,
tokenizer=tokenizer,
dataset=dataset,
batch_size=4,
mini_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=1.41e-5,
adap_kl_ctrl=True, # 自适应 KL 惩罚
init_kl_coef=0.2,
target_kl=0.1
)
# 训练循环
for epoch in range(10):
for batch in ppo_trainer.dataloader:
# 1. 生成响应
response_tensors = ppo_trainer.generate(batch["query"], **generation_kwargs)
batch["response"] = tokenizer.batch_decode(response_tensors)
# 2. 计算奖励
with torch.no_grad():
rewards = []
for prompt, response in zip(batch["query"], batch["response"]):
text = prompt + response
inputs = tokenizer(text, return_tensors="pt").to(device)
reward = reward_model(**inputs).item()
# 减去基线或惩罚
rewards.append(reward - kl_penalty)
batch["reward"] = torch.tensor(rewards)
# 3. PPO 步骤
stats = ppo_trainer.step(
batch["query"],
batch["response"],
batch["reward"],
batch["query"].shape[-1] # 序列长度
)DPO(Direct Preference Optimization)
简化版 RLHF,无需显式训练奖励模型和 PPO 优化。
原理
直接优化策略,使其偏好人类更喜欢的输出:
L_DPO = -E[log σ(β * (r_θ(chosen) - r_θ(rejected)))]其中:
- r_θ(x) = β * log π_θ(x) + const
- β - 温度参数(控制偏离程度)
优势:
- 无需训练单独的奖励模型
- 省去 PPO 复杂的训练流程
- 效果接近 RLHF
实现
python
from transformers import AutoModelForCausalLM
import torch.nn.functional as F
model = AutoModelForCausalLM.from_pretrained("sft_model")
ref_model = AutoModelForCausalLM.from_pretrained("sft_model") # 冻结
def dpo_loss(chosen_logps, rejected_logps, beta=0.1):
"""chosen_logps: 模型对偏好样本的 log probs
rejected_logps: 模型对拒绝样本的 log probs
"""
losses = -F.logsigmoid(beta * (chosen_logps - rejected_logps))
return losses.mean()
for batch in dataloader:
# chosen: 人类偏好的完整对话
# rejected: 人类拒绝的完整对话
# 1. 计算 log probs
chosen_logits = model(input_ids=batch["chosen"]).logits
rejected_logits = model(input_ids=batch["rejected"]).logits
chosen_logps = log_probs(chosen_logits, batch["chosen"])
rejected_logps = log_probs(rejected_logits, batch["rejected"])
# 2. DPO 损失
loss = dpo_loss(chosen_logps, rejected_logps)
loss.backward()使用 TRL 的 DPO
python
from trl import DPOTrainer
trainer = DPOTrainer(
model=model,
ref_model=ref_model,
args=training_args,
train_dataset=dpo_dataset,
beta=0.1 # DPO 温度参数
)
trainer.train()对齐策略对比
| 方法 | 是否需要 RM | 是否需要 PPO | 优点 | 缺点 |
|---|---|---|---|---|
| SFT 监督微调 | 否 | 否 | 简单快速 | 可能过拟合 |
| RLHF(PPO) | 是 | 是 | 效果最好 | 复杂,不稳定 |
| DPO | 否 | 否 | 简单稳定 | 依赖偏好数据质量 |
| KTO | 否 | 否 | 只需二元标签 | 数据利用效率低 |
选择建议:
- 快速验证:SFT
- 最佳效果:RLHF(RL from human feedback)
- 平衡:DPO(省去 RM 训练,效果接近 RLHF)
- 预算有限:KTO(仅需好/坏标签)
实践建议
1. 数据质量 > 算法
偏好数据质量决定上限:
- 多样化的场景(普通对话、安全性、有用性)
- 清晰的对比(显著差异)
- 人工审核,避免噪音
2. 超参数调优
| 参数 | 典型值 | 说明 |
|---|---|---|
| PPO 学习率 | 1e-5 ~ 1e-6 | 小学习率,稳定 |
| KL 惩罚系数 β | 0.1 ~ 0.5 | 平衡对齐与语言建模能力 |
| PPO epoch | 1 ~ 4 | 每个 batch 更新次数 |
| 梯度裁剪 | 0.1 ~ 0.3 | 防止梯度爆炸 |
3. 监控指标
- KL 散度:新旧模型差异,β 控制目标值
- 奖励均值:模型输出的平均奖励
- 胜率:模型 vs 参考模型的偏好比例
- ** perplexity**:语言建模能力是否下降
4. 避免奖励 hacking
模型可能钻奖励模型空子:
人类偏好: 长、详细、有帮助的回答
奖励模型: 给这类回答高分
模型学会: 堆砌无意义的长句子防护:
- 多样化的奖励模型
- 多次迭代训练
- 人工定期评估
工具与资源
开源实现
TRL(Transformer Reinforcement Learning):HuggingFace 官方 RLHF 库
bashpip install trlOpenRLHF:多节点分布式 RLHF
bashpip install openrlhf
示例代码
python
# DPO 完整示例(TRL)
from trl import DPOTrainer, DPOConfig
config = DPOConfig(
beta=0.1,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=5e-5,
num_train_epochs=3
)
trainer = DPOTrainer(
model=model,
ref_model=ref_model,
args=config,
train_dataset=dpo_dataset,
tokenizer=tokenizer
)
trainer.train()案例分析
ChatGPT
- SFT:监督微调 1.3M 对话示例
- RM:训练基于排名的奖励模型(45K 人工标注)
- PPO:使用 RM 作为奖励,多阶段迭代优化
Claude(Anthropic)
- Constitutional AI:用宪法原则替代人工反馈
- Self-Supervision:模型自我改进
- 减少人工标注成本
总结
- RLHF(PPO):效果好,复杂昂贵,适合大规模对齐
- DPO:简化版 RLHF,省去 RM 训练,适合快速迭代
- KTO:最简化,仅需好/坏标签
对于大多数项目,建议从 DPO 开始,效果足够且稳定。若追求极致效果,再用 PPO 优化。
