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 适合直接用人类偏好对比数据,工程实现更简单,效果优秀。
  • 两者都能提升大模型对齐能力,建议根据实际需求选择。
posted @ 2025-07-31 15:59  hsr0316  阅读(367)  评论(0)    收藏  举报