DPO(Direct Preference Optimization,直接偏好优化)是一种简化的强化学习对齐(RL Alignment)算法,由斯坦福大学等机构在2023年提出,核心用于解决“让语言模型输出符合人类偏好”的问题(如生成更安全、更有用的内容)。与传统的RLHF(基于人类反馈的强化学习)相比,DPO跳过了“训练奖励模型(RM)”和“强化学习(如PPO)”的复杂流程,直接从人类偏好数据中优化模型,大幅简化了训练链路并提升了稳定性。

一、背景:为什么需要DPO?

在大语言模型(LLM)训练中,“对齐”是核心挑战——如何让模型输出符合人类价值观(如无害、有用、真实)。传统的RLHF流程分为三步:

  1. 监督微调(SFT):用高质量标注数据训练初始模型(SFT模型);
  2. 奖励模型训练(RM):用人类偏好数据(如“哪个回答更好”)训练奖励模型,让模型能给“好回答”打高分、“差回答”打低分;
  3. 强化学习(RL):以奖励模型为指导,用PPO等算法优化SFT模型,使其生成更符合偏好的内容。

但RLHF存在明显缺陷:

  • 奖励模型容易过拟合(对未见过的样本泛化差);
  • RL阶段(如PPO)训练不稳定,容易导致模型“坍缩”(输出模式单一);
  • 流程复杂,工程实现成本高。

DPO的提出正是为了简化这一过程:直接从偏好数据中学习,跳过复杂的RL步骤

二、DPO的核心思想

DPO的核心是:通过最大化“偏好数据中较好的响应(y_w)相对于较差的响应(y_l)的概率”,直接优化策略模型

具体来说,给定偏好数据 D = { ( s , y w , y l ) } D = \{(s, y_w, y_l)\} D={(s,yw,yl)}(其中 s s s 是输入提示, y w y_w yw 是人类偏好的“好回答”, y l y_l yl 是“差回答”),DPO的目标是调整模型参数 θ \theta θ,让模型在输入 s s s 时,更可能生成 y w y_w yw 而非 y l y_l yl

三、DPO的数学原理与目标函数

1. 关键定义

  • 策略模型 π θ ( y ∣ s ) \pi_\theta(y|s) πθ(ys) 表示参数为 θ \theta θ 的模型在输入 s s s 时生成响应 y y y 的概率。
  • 参考模型 π ref ( y ∣ s ) \pi_{\text{ref}}(y|s) πref(ys) 通常是SFT模型(初始监督微调模型),作为优化的“基准线”。
  • 偏好数据:每个样本是三元组 ( s , y w , y l ) (s, y_w, y_l) (s,yw,yl),表示在输入 s s s 下, y w y_w yw y l y_l yl 更优。

2. 目标函数推导

DPO的目标函数源于“隐含的奖励模型假设”:假设存在一个奖励函数 r θ ( y ∣ s ) r_\theta(y|s) rθ(ys),使得人类偏好可以通过“好回答的奖励高于差回答”来表示(即 r θ ( y w ∣ s ) > r θ ( y l ∣ s ) r_\theta(y_w|s) > r_\theta(y_l|s) rθ(yws)>rθ(yls))。

通过推导可以证明(细节见原论文):在合理假设下,最大化这个奖励函数等价于最大化“策略模型与参考模型的概率比”的对数差。最终DPO的目标函数(损失函数)为:

L DPO ( θ ; β ) = − E ( s , y w , y l ) ∼ D [ log ⁡ σ ( 1 β ( log ⁡ π θ ( y w ∣ s ) − log ⁡ π ref ( y w ∣ s ) − ( log ⁡ π θ ( y l ∣ s ) − log ⁡ π ref ( y l ∣ s ) ) ) ) ] \mathcal{L}_{\text{DPO}}(\theta; \beta) = -\mathbb{E}_{(s, y_w, y_l) \sim D} \left[ \log \sigma \left( \frac{1}{\beta} \left( \log \pi_\theta(y_w|s) - \log \pi_{\text{ref}}(y_w|s) - \left( \log \pi_\theta(y_l|s) - \log \pi_{\text{ref}}(y_l|s) \right) \right) \right) \right] LDPO(θ;β)=E(s,yw,yl)D[logσ(β1(logπθ(yws)logπref(yws)(logπθ(yls)logπref(yls))))]

其中:

  • σ ( ⋅ ) \sigma(\cdot) σ() 是sigmoid函数,确保输出在 ( 0 , 1 ) (0, 1) (0,1) 之间,便于计算交叉熵损失;
  • β > 0 \beta > 0 β>0 是温度参数(超参数),控制策略模型与参考模型的偏离程度: β \beta β 越小,策略越可能偏离参考模型; β \beta β 越大,策略越接近参考模型;
  • 括号内的项 log ⁡ π θ ( y ∣ s ) − log ⁡ π ref ( y ∣ s ) \log \pi_\theta(y|s) - \log \pi_{\text{ref}}(y|s) logπθ(ys)logπref(ys) 称为“优势比”,表示策略模型相对于参考模型对 y y y 的偏好程度。

3. 目标函数的直观理解

对于每个偏好样本 ( s , y w , y l ) (s, y_w, y_l) (s,yw,yl),DPO的损失函数鼓励:
log ⁡ π θ ( y w ∣ s ) − log ⁡ π ref ( y w ∣ s ) > log ⁡ π θ ( y l ∣ s ) − log ⁡ π ref ( y l ∣ s ) \log \pi_\theta(y_w|s) - \log \pi_{\text{ref}}(y_w|s) > \log \pi_\theta(y_l|s) - \log \pi_{\text{ref}}(y_l|s) logπθ(yws)logπref(yws)>logπθ(yls)logπref(yls)

即:策略模型相对于参考模型,更应该“偏爱”好回答 y w y_w yw 而非差回答 y l y_l yl。当这个条件满足时,sigmoid函数的输出接近1,损失接近0;反之损失增大,模型会通过梯度下降调整参数以满足条件。

四、DPO的训练流程

DPO的训练步骤远比RLHF简单,核心分为两步:

  1. 训练参考模型( π ref \pi_{\text{ref}} πref
    用高质量监督数据(如人工编写的优质回答)进行监督微调(SFT),得到初始模型作为参考基准。

  2. 用DPO损失优化策略模型( π θ \pi_\theta πθ

    • 输入:偏好数据 D = { ( s , y w , y l ) } D = \{(s, y_w, y_l)\} D={(s,yw,yl)}、参考模型 π ref \pi_{\text{ref}} πref、超参数 β \beta β
    • 输出:优化后的策略模型 π θ \pi_\theta πθ
    • 过程:通过梯度下降最小化DPO损失函数 L DPO ( θ ; β ) \mathcal{L}_{\text{DPO}}(\theta; \beta) LDPO(θ;β),让模型逐渐学会偏好 y w y_w yw 而非 y l y_l yl

五、DPO的优势

  1. 流程极简:跳过奖励模型训练和RL阶段,直接从偏好数据优化,工程实现难度低。
  2. 性能优异:在多个对齐任务(如无害性、有用性)上,DPO性能接近甚至超过RLHF,且泛化能力更强。
  3. 可解释性:目标函数直接关联人类偏好,无需复杂的中间变量(如奖励模型的分数)。

六、个人理解

网上所说的DPO跳过Reward Model学习的过程,个人理解其实不太对,挑选偏好数据的过程其实就是用Reward Model来完成的,这里我理解一定会用到Reward Model,只不过DPO比PPO的训练过程确实简单很多。

DPO通过“直接从人类偏好数据中优化策略模型”,简化了大语言模型的对齐流程。其核心是通过最大化“好回答相对差回答的优势比”,让模型学会符合人类偏好的输出。相比传统RLHF,DPO在稳定性、简洁性和性能上均有明显优势,已成为大语言模型对齐领域的主流方法之一(如Anthropic的Claude、OpenAI的GPT系列均借鉴了类似思路)。

代码实现

demo代码地址为:dpo_example

代码结构比较简单,这里不做过多赘述,这里只是想再次强调DPO在构造偏好数据时,一定会用到Reward信号,这个信号要么由已有的规则获取,要么通过Reward Model获取,如下展示了由规则获取的源代码:

def create_preference_from_rewards(trajectory1, trajectory2):
    """
    Create preference pair from two trajectories based on total rewards

    Args:
        trajectory1: (states, actions, rewards) tuple
        trajectory2: (states, actions, rewards) tuple

    Returns:
        (states_preferred, actions_preferred, states_less, actions_less)
    """
    states1, actions1, rewards1 = trajectory1
    states2, actions2, rewards2 = trajectory2

    total_reward1 = np.sum(rewards1)
    total_reward2 = np.sum(rewards2)

    if total_reward1 > total_reward2:
        return (states1, actions1, states2, actions2)
    else:
        return (states2, actions2, states1, actions1)

更多推荐