DPO 与 PPO 算法原理与代码实现对比
DPO 与 PPO 算法原理与代码实现对比
近年来,大语言模型(LLM)训练中,强化学习(RL)技术被广泛应用于对齐模型输出与人类偏好。PPO(Proximal Policy Optimization)是 RLHF(Reinforcement Learning from Human Feedback)中的经典算法,而 DPO(Direct Preference Optimization)则是新兴的、无需奖励模型的直接偏好优化方法。本文将简要介绍两者原理,并给出简洁的 PyTorch 代码实现模板,帮助大家理解其核心思想与工程落地。
1. PPO(Proximal Policy Optimization)简介
PPO 是一种策略梯度方法,核心思想是通过限制新旧策略的变化幅度,保证训练过程的稳定性。PPO 主要用于 RLHF 流程中,通过奖励模型对模型输出进行打分,优化模型使其输出更符合人类偏好。
PPO 算法核心公式
$$
L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min \left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t \right) \right]
$$
其中 $r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$,$\hat{A}_t$ 为优势函数。
PPO 代码实现(PyTorch 简化版)
import torch
import torch.nn as nn
import torch.optim as optim
class PolicyNet(nn.Module):
def __init__(self, obs_dim, act_dim):
super().__init__()
self.fc = nn.Sequential(
nn.Linear(obs_dim, 64), nn.Tanh(),
nn.Linear(64, act_dim)
)
def forward(self, x):
return self.fc(x)
def compute_ppo_loss(policy, old_policy, obs, act, adv, eps=0.2):
logits = policy(obs)
dist = torch.distributions.Categorical(logits=logits)
logp = dist.log_prob(act)
with torch.no_grad():
old_logits = old_policy(obs)
old_dist = torch.distributions.Categorical(logits=old_logits)
old_logp = old_dist.log_prob(act)
ratio = torch.exp(logp - old_logp)
clip_adv = torch.clamp(ratio, 1-eps, 1+eps) * adv
loss = -torch.mean(torch.min(ratio * adv, clip_adv))
return loss
# 训练循环伪代码
policy = PolicyNet(obs_dim=4, act_dim=2)
old_policy = PolicyNet(obs_dim=4, act_dim=2)
optimizer = optim.Adam(policy.parameters(), lr=3e-4)
for epoch in range(epochs):
# 收集数据: obs, act, adv
loss = compute_ppo_loss(policy, old_policy, obs, act, adv)
optimizer.zero_grad()
loss.backward()
optimizer.step()
old_policy.load_state_dict(policy.state_dict())
2. DPO(Direct Preference Optimization)简介
DPO 是一种直接利用人类偏好对比数据进行优化的方法,无需训练奖励模型。它通过最大化人类偏好样本的对数概率差异,使模型直接学会“更优”输出。
DPO 算法核心公式
$$
L_{DPO} = -\mathbb{E}{(x, y^+, y^-)} \left[ \log \frac{\exp(\beta \log \pi\theta(y^+|x))}{\exp(\beta \log \pi_\theta(y^+|x)) + \exp(\beta \log \pi_\theta(y^-|x))} \right]
$$
其中 $y^+$ 为人类偏好输出,$y^-$ 为被拒绝输出,$\beta$ 为温度超参数。
DPO 代码实现(PyTorch 简化版)
import torch
import torch.nn as nn
import torch.optim as optim
class LMHeadModel(nn.Module):
def __init__(self, vocab_size, hidden_size):
super().__init__()
self.embed = nn.Embedding(vocab_size, hidden_size)
self.lm_head = nn.Linear(hidden_size, vocab_size)
def forward(self, input_ids):
x = self.embed(input_ids)
x = x.mean(dim=1) # 简化处理
logits = self.lm_head(x)
return logits
def compute_dpo_loss(model, x, y_pos, y_neg, beta=0.1):
logits_pos = model(torch.cat([x, y_pos], dim=1))
logits_neg = model(torch.cat([x, y_neg], dim=1))
logp_pos = torch.log_softmax(logits_pos, dim=-1).gather(1, y_pos)
logp_neg = torch.log_softmax(logits_neg, dim=-1).gather(1, y_neg)
logp_pos = logp_pos.sum(dim=1)
logp_neg = logp_neg.sum(dim=1)
diff = beta * (logp_pos - logp_neg)
loss = -torch.log(torch.sigmoid(diff)).mean()
return loss
# 训练循环伪代码
model = LMHeadModel(vocab_size=10000, hidden_size=768)
optimizer = optim.Adam(model.parameters(), lr=1e-4)
for epoch in range(epochs):
# x: prompt, y_pos: 优选回复, y_neg: 次选回复
loss = compute_dpo_loss(model, x, y_pos, y_neg)
optimizer.zero_grad()
loss.backward()
optimizer.step()
3. DPO vs PPO 总结对比
| 特点 | PPO | DPO |
|---|---|---|
| 训练流程 | 需奖励模型,RLHF三步走 | 直接用偏好对比,无需奖励模型 |
| 实现复杂度 | 较高,需采样、奖励、优势函数 | 简单,直接优化偏好概率 |
| 训练稳定性 | 依赖奖励模型质量,调参较多 | 更稳定,超参数少 |
| 工程落地 | HuggingFace TRL等库有成熟实现 | 新兴方法,已被多家大模型采用 |
4. 参考资料
5. 总结
- PPO 适合 RLHF 全流程,适用奖励模型已训练好的场景。
- DPO 适合直接用人类偏好对比数据,工程实现更简单,效果优秀。
- 两者都能提升大模型对齐能力,建议根据实际需求选择。

浙公网安备 33010602011771号