欢迎光临
我们一直在努力

直接偏好优化算法深度解析

DPO — 直接偏好优化

1. 引言

RLHF(Reinforcement Learning from Human Feedback)是将大语言模型与人类偏好对齐的主流范式,但其三阶段流程(SFT → 奖励模型训练 → PPO 微调)存在显著的工程复杂性和训练不稳定性。

DPO(Direct Preference Optimization, Rafailov et al., 2023) 提出了一种革命性的简化:将 RLHF 的强化学习阶段重新参数化为一个简单的分类损失,完全绕过奖励模型的训练和 PPO 的在线采样。其核心数学洞察是:

最优 RLHF 策略可以用奖励函数的闭式解表示,而这个闭式解可以代入 Bradley-Terry 偏好模型,将 RL 问题转化为监督学习。

这一设计带来了三个关键优势:

  • 训练简洁:无需奖励模型、无需 PPO、无需在线采样——仅用一个二元交叉熵损失
  • 稳定性高:避免了 PPO 的超参数敏感性和奖励模型的误差传播
  • 理论优雅:与 RLHF 在数学上等价(在 Bradley-Terry 模型假设下)

  • 2. 理论基础 — RLHF 的数学框架

    2.1 问题定义

    给定:

    • 参考策略 πref\\pi_{\\text{ref}}πref(通常是 SFT 模型)
    • 偏好数据集 D={(q,yw,yl)}\\mathcal{D} = \\{(q, y_w, y_l)\\}D={(q,yw,yl)},其中 ywy_wyw 是人类偏好的回答,yly_lyl 是不被偏好的回答
    • KL 约束系数 β>0\\beta > 0β>0

    RLHF 的目标是:

    max⁡πθ  Eq∼D, y∼πθ(⋅∣q)[rϕ(q,y)]−β DKL[πθ(⋅∣q)∥πref(⋅∣q)]
    \\max_{\\pi_\\theta} \\; \\mathbb{E}_{q \\sim \\mathcal{D}, \\, y \\sim \\pi_\\theta(\\cdot | q)} \\left[ r_\\phi(q, y) \\right] – \\beta \\, D_{\\text{KL}} \\left[ \\pi_\\theta(\\cdot | q) \\| \\pi_{\\text{ref}}(\\cdot | q) \\right]
    πθmaxEqD,yπθ(q)[rϕ(q,y)]βDKL[πθ(q)πref(q)]

    其中第一项最大化奖励,第二项防止策略偏离参考模型太远。

    2.2 KL 约束的展开

    将 KL 散度展开:

    DKL[πθ∥πref]=Ey∼πθ[log⁡πθ(y∣q)πref(y∣q)]
    D_{\\text{KL}} \\left[ \\pi_\\theta \\| \\pi_{\\text{ref}} \\right] = \\mathbb{E}_{y \\sim \\pi_\\theta} \\left[ \\log \\frac{\\pi_\\theta(y | q)}{\\pi_{\\text{ref}}(y | q)} \\right]
    DKL[πθπref]=Eyπθ[logπref(yq)πθ(yq)]

    代入目标函数:

    max⁡πθ  Eq,y∼πθ[rϕ(q,y)−βlog⁡πθ(y∣q)πref(y∣q)]
    \\max_{\\pi_\\theta} \\; \\mathbb{E}_{q, y \\sim \\pi_\\theta} \\left[ r_\\phi(q, y) – \\beta \\log \\frac{\\pi_\\theta(y | q)}{\\pi_{\\text{ref}}(y | q)} \\right]
    πθmaxEq,yπθ[rϕ(q,y)βlogπref(yq)πθ(yq)]

    2.3 最优策略的闭式解

    定理(最优 KL 约束策略):对于固定的奖励函数 r(q,y)r(q, y)r(q,y),上述优化问题的最优解为:

    π∗(y∣q)=1Z(q)πref(y∣q)exp⁡(1βr(q,y))
    \\pi^*(y | q) = \\frac{1}{Z(q)} \\pi_{\\text{ref}}(y | q) \\exp\\left( \\frac{1}{\\beta} r(q, y) \\right)
    π(yq)=Z(q)1πref(yq)exp(β1r(q,y))

    其中配分函数:

    Z(q)=∑yπref(y∣q)exp⁡(1βr(q,y))
    Z(q) = \\sum_{y} \\pi_{\\text{ref}}(y | q) \\exp\\left( \\frac{1}{\\beta} r(q, y) \\right)
    Z(q)=yπref(yq)exp(β1r(q,y))

    证明:

    目标函数等价于最小化:

    min⁡πθ  DKL[πθ(⋅∣q)∥π∗(⋅∣q)]
    \\min_{\\pi_\\theta} \\; D_{\\text{KL}} \\left[ \\pi_\\theta(\\cdot | q) \\| \\pi^*(\\cdot | q) \\right]
    πθminDKL[πθ(q)π(q)]

    其中 π∗\\pi^*π 定义为上述闭式解。验证:

    DKL[πθ∥π∗]=Ey∼πθ[log⁡πθ(y∣q)π∗(y∣q)]=Ey∼πθ[log⁡πθ(y∣q)1Z(q)πref(y∣q)exp⁡(r(q,y)β)]=Ey∼πθ[log⁡πθ(y∣q)πref(y∣q)−r(q,y)β+log⁡Z(q)]=1β[−Ey∼πθ[r(q,y)]+β DKL[πθ∥πref]]+log⁡Z(q)
    \\begin{aligned}
    D_{\\text{KL}}[\\pi_\\theta \\| \\pi^*] &= \\mathbb{E}_{y \\sim \\pi_\\theta} \\left[ \\log \\frac{\\pi_\\theta(y | q)}{\\pi^*(y | q)} \\right] \\\\
    &= \\mathbb{E}_{y \\sim \\pi_\\theta} \\left[ \\log \\frac{\\pi_\\theta(y | q)}{\\frac{1}{Z(q)} \\pi_{\\text{ref}}(y | q) \\exp\\left(\\frac{r(q, y)}{\\beta}\\right)} \\right] \\\\
    &= \\mathbb{E}_{y \\sim \\pi_\\theta} \\left[ \\log \\frac{\\pi_\\theta(y | q)}{\\pi_{\\text{ref}}(y | q)} – \\frac{r(q, y)}{\\beta} + \\log Z(q) \\right] \\\\
    &= \\frac{1}{\\beta} \\left[ -\\mathbb{E}_{y \\sim \\pi_\\theta}[r(q, y)] + \\beta \\, D_{\\text{KL}}[\\pi_\\theta \\| \\pi_{\\text{ref}}] \\right] + \\log Z(q)
    \\end{aligned}
    DKL[πθπ]=Eyπθ[logπ(yq)πθ(yq)]=EyπθlogZ(q)1πref(yq)exp(βr(q,y))πθ(yq)=Eyπθ[logπref(yq)πθ(yq)βr(q,y)+logZ(q)]=β1[Eyπθ[r(q,y)]+βDKL[πθπref]]+logZ(q)

    这正是 RLHF 目标函数(差一个常数和缩放因子)。最小化 KL 散度等价于最大化 RLHF 目标。

    πθ=π∗\\pi_\\theta = \\pi^*πθ=π 时,DKL[πθ∥π∗]=0D_{\\text{KL}}[\\pi_\\theta \\| \\pi^*] = 0DKL[πθπ]=0,达到全局最优。■\\blacksquare

    2.4 最优策略的直觉理解

    最优策略 π∗\\pi^*π 的形式具有深刻的物理直觉:

    π∗(y∣q)∝πref(y∣q)⋅exp⁡(r(q,y)β)
    \\pi^*(y | q) \\propto \\pi_{\\text{ref}}(y | q) \\cdot \\exp\\left(\\frac{r(q, y)}{\\beta}\\right)
    π(yq)πref(yq)exp(βr(q,y))

    • πref(y∣q)\\pi_{\\text{ref}}(y | q)πref(yq) 是"先验":模型本来就会生成什么
    • exp⁡(r/β)\\exp(r / \\beta)exp(r/β) 是"似然":奖励越高的回答,概率越大
    • β\\betaβ 控制两者的权衡:β\\betaβ 大时策略更保守(接近 πref\\pi_{\\text{ref}}πref),β\\betaβ 小时更激进(更依赖奖励)

    这与贝叶斯推断的后验 = 先验 × 似然 形式完全一致。


    3. DPO 的核心推导

    3.1 从最优策略反解奖励函数

    从闭式解 π∗\\pi^*π 反解 r(q,y)r(q, y)r(q,y)

    π∗(y∣q)=1Z(q)πref(y∣q)exp⁡(r(q,y)β)
    \\pi^*(y | q) = \\frac{1}{Z(q)} \\pi_{\\text{ref}}(y | q) \\exp\\left(\\frac{r(q, y)}{\\beta}\\right)
    π(yq)=Z(q)1πref(yq)exp(βr(q,y))

    两边取对数:

    log⁡π∗(y∣q)=−log⁡Z(q)+log⁡πref(y∣q)+1βr(q,y)
    \\log \\pi^*(y | q) = -\\log Z(q) + \\log \\pi_{\\text{ref}}(y | q) + \\frac{1}{\\beta} r(q, y)
    logπ(yq)=logZ(q)+logπref(yq)+β1r(q,y)

    解出 r(q,y)r(q, y)r(q,y)

    r(q,y)=βlog⁡π∗(y∣q)πref(y∣q)+βlog⁡Z(q)
    r(q, y) = \\beta \\log \\frac{\\pi^*(y | q)}{\\pi_{\\text{ref}}(y | q)} + \\beta \\log Z(q)
    r(q,y)=βlogπref(yq)π(yq)+βlogZ(q)

    关键洞察:奖励函数可以完全用策略和参考策略的对数概率比来表示!最后一项 βlog⁡Z(q)\\beta \\log Z(q)βlogZ(q) 只依赖于问题 qqq,不依赖于回答 yyy

    3.2 Bradley-Terry 偏好模型

    人类偏好通常用 Bradley-Terry 模型 建模。给定问题 qqq 和两个回答 yw,yly_w, y_lyw,yl,人类偏好 ywy_wyw 胜过 yly_lyl 的概率为:

    p(yw≻yl∣q)=σ(r(q,yw)−r(q,yl))
    p(y_w \\succ y_l | q) = \\sigma(r(q, y_w) – r(q, y_l))
    p(ywylq)=σ(r(q,yw)r(q,yl))

    其中 σ(z)=11+e−z\\sigma(z) = \\frac{1}{1 + e^{-z}}σ(z)=1+ez1 是 sigmoid 函数。

    直觉:两个回答的奖励之差越大,人类越可能偏好奖励高的那个。

    3.3 DPO 损失函数的推导

    将奖励的闭式解代入 Bradley-Terry 模型:

    p(yw≻yl∣q)=σ(βlog⁡π∗(yw∣q)πref(yw∣q)−βlog⁡π∗(yl∣q)πref(yl∣q))
    p(y_w \\succ y_l | q) = \\sigma\\left( \\beta \\log \\frac{\\pi^*(y_w | q)}{\\pi_{\\text{ref}}(y_w | q)} – \\beta \\log \\frac{\\pi^*(y_l | q)}{\\pi_{\\text{ref}}(y_l | q)} \\right)
    p(ywylq)=σ(βlogπref(ywq)π(ywq)βlogπref(ylq)π(ylq))

    注意 βlog⁡Z(q)\\beta \\log Z(q)βlogZ(q) 项在作差时完全消去!

    简化后:

    p(yw≻yl∣q)=σ(βlog⁡πθ(yw∣q)πref(yw∣q)−βlog⁡πθ(yl∣q)πref(yl∣q))
    p(y_w \\succ y_l | q) = \\sigma\\left( \\beta \\log \\frac{\\pi_\\theta(y_w | q)}{\\pi_{\\text{ref}}(y_w | q)} – \\beta \\log \\frac{\\pi_\\theta(y_l | q)}{\\pi_{\\text{ref}}(y_l | q)} \\right)
    p(ywylq)=σ(βlogπref(ywq)πθ(ywq)βlogπref(ylq)πθ(ylq))

    其中 πθ\\pi_\\thetaπθ 是我们正在训练的策略(近似 π∗\\pi^*π)。

    DPO 损失函数:对偏好数据取负对数似然:

    LDPO(θ)=−E(q,yw,yl)∼D[log⁡σ(βlog⁡πθ(yw∣q)πref(yw∣q)−βlog⁡πθ(yl∣q)πref(yl∣q))]
    \\boxed{
    \\mathcal{L}_{\\text{DPO}}(\\theta) = -\\mathbb{E}_{(q, y_w, y_l) \\sim \\mathcal{D}} \\left[ \\log \\sigma\\left( \\beta \\log \\frac{\\pi_\\theta(y_w | q)}{\\pi_{\\text{ref}}(y_w | q)} – \\beta \\log \\frac{\\pi_\\theta(y_l | q)}{\\pi_{\\text{ref}}(y_l | q)} \\right) \\right]
    }
    LDPO(θ)=E(q,yw,yl)D[logσ(βlogπref(ywq)πθ(ywq)βlogπref(ylq)πθ(ylq))]

    这就是 DPO 的全部:一个二元交叉熵损失,输入是两个回答的隐式奖励之差。

    3.4 DPO 的梯度分析

    θ\\thetaθ 求梯度:

    ∇θLDPO=−β E(q,yw,yl)[σ(r^l−r^w)⏟权重(∇θlog⁡πθ(yw∣q)−∇θlog⁡πθ(yl∣q))]
    \\nabla_\\theta \\mathcal{L}_{\\text{DPO}} = -\\beta \\, \\mathbb{E}_{(q, y_w, y_l)} \\left[ \\underbrace{\\sigma(\\hat{r}_l – \\hat{r}_w)}_{\\text{权重}} \\left( \\nabla_\\theta \\log \\pi_\\theta(y_w | q) – \\nabla_\\theta \\log \\pi_\\theta(y_l | q) \\right) \\right]
    θLDPO=βE(q,yw,yl)权重σ(r^lr^w)(θlogπθ(ywq)θlogπθ(ylq))

    其中隐式奖励 r^=βlog⁡πθ(y∣q)πref(y∣q)\\hat{r} = \\beta \\log \\frac{\\pi_\\theta(y | q)}{\\pi_{\\text{ref}}(y | q)}r^=βlogπref(yq)πθ(yq)

    直觉解释:

    • 当模型已经正确排序时(r^w>r^l\\hat{r}_w > \\hat{r}_lr^w>r^l):σ(r^l−r^w)≈0\\sigma(\\hat{r}_l – \\hat{r}_w) \\approx 0σ(r^lr^w)0,梯度很小——无需进一步调整
    • 当模型排序错误时(r^w<r^l\\hat{r}_w < \\hat{r}_lr^w<r^l):σ(r^l−r^w)≈1\\sigma(\\hat{r}_l – \\hat{r}_w) \\approx 1σ(r^lr^w)1,梯度很大——强烈修正
    • 梯度方向:增大 log⁡πθ(yw∣q)\\log \\pi_\\theta(y_w | q)logπθ(ywq),减小 log⁡πθ(yl∣q)\\log \\pi_\\theta(y_l | q)logπθ(ylq)

    这实现了一种自动加权机制:模型越困惑的样本,梯度贡献越大。


    4. DPO 与 RLHF 的等价性

    4.1 等价性定理

    定理(Rafailov et al., 2023):在 Bradley-Terry 偏好模型假设下,DPO 和 RLHF 的最优解相同。

    证明:

    RLHF 的目标是:

    max⁡π  Eq,y∼π[r(q,y)]−βDKL[π∥πref]
    \\max_\\pi \\; \\mathbb{E}_{q, y \\sim \\pi} [r(q, y)] – \\beta D_{\\text{KL}}[\\pi \\| \\pi_{\\text{ref}}]
    πmaxEq,yπ[r(q,y)]βDKL[ππref]

    其最优解为:

    π∗(y∣q)=1Z(q)πref(y∣q)exp⁡(r(q,y)β)
    \\pi^*(y | q) = \\frac{1}{Z(q)} \\pi_{\\text{ref}}(y | q) \\exp\\left(\\frac{r(q, y)}{\\beta}\\right)
    π(yq)=Z(q)1πref(yq)exp(βr(q,y))

    DPO 通过最大似然估计学习一个策略 πθ\\pi_\\thetaπθ,使得:

    πθ=arg⁡max⁡π  E(q,yw,yl)[log⁡σ(βlog⁡π(yw∣q)πref(yw∣q)−βlog⁡π(yl∣q)πref(yl∣q))]
    \\pi_\\theta = \\arg\\max_\\pi \\; \\mathbb{E}_{(q, y_w, y_l)} \\left[ \\log \\sigma\\left( \\beta \\log \\frac{\\pi(y_w | q)}{\\pi_{\\text{ref}}(y_w | q)} – \\beta \\log \\frac{\\pi(y_l | q)}{\\pi_{\\text{ref}}(y_l | q)} \\right) \\right]
    πθ=argπmaxE(q,yw,yl)[logσ(βlogπref(ywq)π(ywq)βlogπref(ylq)π(ylq))]

    当偏好数据足够多且 Bradley-Terry 模型成立时,DPO 的最优解 πθ∗\\pi_\\theta^*πθ 恰好满足:

    πθ∗(y∣q)∝πref(y∣q)exp⁡(r(q,y)β)
    \\pi_\\theta^*(y | q) \\propto \\pi_{\\text{ref}}(y | q) \\exp\\left(\\frac{r(q, y)}{\\beta}\\right)
    πθ(yq)πref(yq)exp(βr(q,y))

    这与 RLHF 的最优解形式相同。■\\blacksquare

    4.2 为什么可以绕过奖励模型

    传统 RLHF 流程:

    偏好数据→训练奖励模型→RL最优策略
    \\text{偏好数据} \\xrightarrow{\\text{训练}} \\text{奖励模型} \\xrightarrow{\\text{RL}} \\text{最优策略}
    偏好数据训练奖励模型RL最优策略

    DPO 的洞察:

    偏好数据→直接训练最优策略
    \\text{偏好数据} \\xrightarrow{\\text{直接训练}} \\text{最优策略}
    偏好数据直接训练最优策略

    奖励模型是中间变量,可以通过最优策略的闭式解被"积掉"(marginalized out)。这类似于概率模型中的经验贝叶斯:不需要显式估计先验参数,直接优化边缘似然。

    4.3 隐式奖励

    虽然 DPO 不显式训练奖励模型,但我们可以从训练好的 DPO 策略中提取隐式奖励:

    r^(q,y)=βlog⁡πθ(y∣q)πref(y∣q)
    \\hat{r}(q, y) = \\beta \\log \\frac{\\pi_\\theta(y | q)}{\\pi_{\\text{ref}}(y | q)}
    r^(q,y)=βlogπref(yq)πθ(yq)

    这个隐式奖励满足:

    r^(q,y)=r(q,y)−βlog⁡Z(q)
    \\hat{r}(q, y) = r(q, y) – \\beta \\log Z(q)
    r^(q,y)=r(q,y)βlogZ(q)

    即真实奖励减去一个只依赖于 qqq 的常数。因此隐式奖励与真实奖励在偏好排序上完全一致。


    5. DPO 的数学性质

    5.1 DPO 损失的凸性分析

    DPO 损失关于隐式奖励 r^w,r^l\\hat{r}_w, \\hat{r}_lr^w,r^l 是凸的:

    ℓ(r^w,r^l)=−log⁡σ(r^w−r^l)
    \\ell(\\hat{r}_w, \\hat{r}_l) = -\\log \\sigma(\\hat{r}_w – \\hat{r}_l)
    (r^w,r^l)=logσ(r^wr^l)

    证明:

    z=r^w−r^lz = \\hat{r}_w – \\hat{r}_lz=r^wr^l,则 ℓ(z)=−log⁡σ(z)=log⁡(1+e−z)\\ell(z) = -\\log \\sigma(z) = \\log(1 + e^{-z})(z)=logσ(z)=log(1+ez)

    ℓ′′(z)=e−z(1+e−z)2=σ(z)(1−σ(z))>0
    \\ell''(z) = \\frac{e^{-z}}{(1 + e^{-z})^2} = \\sigma(z)(1 – \\sigma(z)) > 0
    ′′(z)=(1+ez)2ez=σ(z)(1σ(z))>0

    因此 ℓ\\ell 是严格凸的。■\\blacksquare

    5.2 DPO 的隐式正则化

    DPO 损失隐含了对策略偏离参考模型的正则化。考虑极限情况:

    β→0\\beta \\to 0β0 时:DPO 退化为直接最大化奖励差,策略可能剧烈偏离 πref\\pi_{\\text{ref}}πref

    β→∞\\beta \\to \\inftyβ 时:策略被强制接近 πref\\pi_{\\text{ref}}πref,几乎不学习偏好

    适中的 β\\betaβ:在奖励最大化和策略保守性之间取得平衡

    5.3 DPO 的梯度消失问题

    问题:当模型已经正确排序所有偏好对时,DPO 损失的梯度趋近于零,训练停止。这可能导致:

  • 模型停留在"局部最优"——虽然排序正确,但奖励差距不够大
  • 无法进一步提升回答质量
  • 数学分析:当 r^w≫r^l\\hat{r}_w \\gg \\hat{r}_lr^wr^l 时:

    σ(r^l−r^w)→0⇒∇θLDPO→0
    \\sigma(\\hat{r}_l – \\hat{r}_w) \\to 0 \\quad \\Rightarrow \\quad \\nabla_\\theta \\mathcal{L}_{\\text{DPO}} \\to 0
    σ(r^lr^w)0θLDPO0


    6. 完整可运行实现

    6.1 DPO 核心实现

    """
    DPO (Direct Preference Optimization) — 完整可运行实现
    依赖: torch >= 2.0, numpy, matplotlib, transformers
    """

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import numpy as np
    import matplotlib.pyplot as plt
    from typing import List, Tuple, Optional, Dict
    from dataclasses import dataclass
    import math

    @dataclass
    class DPOConfig:
    """DPO 配置"""
    beta: float = 0.1 # KL 约束系数
    lr: float = 5e-7 # 学习率
    max_grad_norm: float = 1.0 # 梯度裁剪
    label_smoothing: float = 0.0 # 标签平滑
    loss_type: str = "sigmoid" # 损失类型: sigmoid, hinge, ipo

    def compute_log_probs(
    model: nn.Module,
    input_ids: torch.Tensor,
    attention_mask: torch.Tensor,
    labels: torch.Tensor,
    ) > torch.Tensor:
    """计算序列的平均对数概率"""
    outputs = model(input_ids=input_ids, attention_mask=attention_mask)
    logits = outputs.logits[:, :1] # (B, L-1, V)
    labels = labels[:, 1:] # (B, L-1)
    log_probs = F.log_softmax(logits, dim=1)
    token_log_probs = log_probs.gather(2, labels.unsqueeze(1)).squeeze(1)
    return token_log_probs

    class DPO:
    """Direct Preference Optimization"""

    def __init__(
    self,
    model: nn.Module,
    ref_model: nn.Module,
    config: DPOConfig,
    ):
    self.model = model
    self.ref_model = ref_model
    self.config = config

    # 冻结参考模型
    for param in self.ref_model.parameters():
    param.requires_grad = False

    self.optimizer = torch.optim.AdamW(
    model.parameters(), lr=config.lr, weight_decay=0.01
    )

    def compute_loss(
    self,
    chosen_input_ids: torch.Tensor,
    chosen_attention_mask: torch.Tensor,
    chosen_labels: torch.Tensor,
    rejected_input_ids: torch.Tensor,
    rejected_attention_mask: torch.Tensor,
    rejected_labels: torch.Tensor,
    ) > Dict[str, torch.Tensor]:
    """
    计算 DPO 损失。

    L = -E[ log σ( β·(log π(y_w)/π_ref(y_w) – log π(y_l)/π_ref(y_l)) ) ]
    """
    # 计算当前模型的对数概率
    chosen_log_probs = compute_log_probs(
    self.model, chosen_input_ids, chosen_attention_mask, chosen_labels
    )
    rejected_log_probs = compute_log_probs(
    self.model, rejected_input_ids, rejected_attention_mask, rejected_labels
    )

    # 计算参考模型的对数概率(无梯度)
    with torch.no_grad():
    ref_chosen_log_probs = compute_log_probs(
    self.ref_model, chosen_input_ids, chosen_attention_mask, chosen_labels
    )
    ref_rejected_log_probs = compute_log_probs(
    self.ref_model, rejected_input_ids, rejected_attention_mask, rejected_labels
    )

    # 计算隐式奖励差
    chosen_rewards = self.config.beta * (chosen_log_probs ref_chosen_log_probs)
    rejected_rewards = self.config.beta * (rejected_log_probs ref_rejected_log_probs)

    logits = chosen_rewards rejected_rewards

    # 计算损失
    if self.config.loss_type == "sigmoid":
    # 标准 DPO 损失
    if self.config.label_smoothing > 0:
    loss = (
    F.logsigmoid(logits) * (1 self.config.label_smoothing)
    F.logsigmoid(logits) * self.config.label_smoothing
    )
    else:
    loss = F.logsigmoid(logits)

    elif self.config.loss_type == "hinge":
    # Hinge 损失变体
    loss = torch.clamp(1 logits, min=0)

    elif self.config.loss_type == "ipo":
    # IPO (Identity Preference Optimization)
    loss = (logits 1 / (2 * self.config.beta)) ** 2

    loss = loss.mean()

    # 计算统计量
    chosen_reward_mean = chosen_rewards.mean().item()
    rejected_reward_mean = rejected_rewards.mean().item()
    reward_margin = (chosen_rewards rejected_rewards).mean().item()
    accuracy = (logits > 0).float().mean().item()

    return {
    "loss": loss,
    "chosen_reward": chosen_reward_mean,
    "rejected_reward": rejected_reward_mean,
    "reward_margin": reward_margin,
    "accuracy": accuracy,
    }

    def train_step(
    self,
    chosen_input_ids: torch.Tensor,
    chosen_attention_mask: torch.Tensor,
    chosen_labels: torch.Tensor,
    rejected_input_ids: torch.Tensor,
    rejected_attention_mask: torch.Tensor,
    rejected_labels: torch.Tensor,
    ) > Dict[str, float]:
    """执行一步 DPO 训练"""
    self.optimizer.zero_grad()
    metrics = self.compute_loss(
    chosen_input_ids, chosen_attention_mask, chosen_labels,
    rejected_input_ids, rejected_attention_mask, rejected_labels,
    )
    metrics["loss"].backward()
    torch.nn.utils.clip_grad_norm_(
    self.model.parameters(), self.config.max_grad_norm
    )
    self.optimizer.step()
    return {k: v.item() if torch.is_tensor(v) else v for k, v in metrics.items()}

    6.2 DPO 损失函数变体

    class DPOLossVariants:
    """DPO 损失函数变体的数学分析"""

    @staticmethod
    def sigmoid_loss(logit_w: torch.Tensor, logit_l: torch.Tensor) > torch.Tensor:
    """
    标准 DPO (Bradley-Terry):
    L = -log σ(β · (log π(y_w)/π_ref(y_w) – log π(y_l)/π_ref(y_l)))
    """

    return F.logsigmoid(logit_w logit_l)

    @staticmethod
    def hinge_loss(logit_w: torch.Tensor, logit_l: torch.Tensor, margin: float = 1.0) > torch.Tensor:
    """
    Hinge 变体:
    L = max(0, margin – (logit_w – logit_l))
    """

    return torch.clamp(margin (logit_w logit_l), min=0)

    @staticmethod
    def ipo_loss(
    logit_w: torch.Tensor, logit_l: torch.Tensor, beta: float = 0.1
    ) > torch.Tensor:
    """
    IPO (Identity Preference Optimization):
    L = ((logit_w – logit_l) – 1/(2β))²

    IPO 避免了 DPO 的过拟合问题,通过直接正则化奖励差。
    """
    return (logit_w logit_l 1 / (2 * beta)) ** 2

    @staticmethod
    def sppo_loss(logit_w: torch.Tensor, logit_l: torch.Tensor) > torch.Tensor:
    """
    SPPO (Self-Play Preference Optimization):
    L = (logit_w – 1)² + (logit_l + 1)²
    """

    return (logit_w 1) ** 2 + (logit_l + 1) ** 2

    @staticmethod
    def orpo_loss(
    log_odds_w: torch.Tensor,
    log_odds_l: torch.Tensor,
    beta: float = 0.1,
    ) > torch.Tensor:
    """
    ORPO (Odds Ratio Preference Optimization):
    L = -log σ(β · (log odds_w – log odds_l))

    ORPO 直接使用概率比而非对数概率比,无需参考模型。
    """
    return F.logsigmoid(beta * (log_odds_w log_odds_l))

    6.3 DPO 训练器

    class DPOTrainer:
    """完整的 DPO 训练器"""

    def __init__(
    self,
    model: nn.Module,
    ref_model: nn.Module,
    config: DPOConfig,
    tokenizer=None,
    ):
    self.dpo = DPO(model, ref_model, config)
    self.config = config
    self.tokenizer = tokenizer
    self.training_history = {
    "loss": [], "accuracy": [], "reward_margin": [],
    "chosen_reward": [], "rejected_reward": [],
    }

    def compute_sequence_log_probs(
    self,
    model: nn.Module,
    input_ids: torch.Tensor,
    labels: torch.Tensor,
    response_mask: torch.Tensor,
    ) > torch.Tensor:
    """计算响应部分的平均对数概率"""
    with torch.set_grad_enabled(model.training):
    outputs = model(input_ids=input_ids)
    logits = outputs.logits[:, :1]
    labels = labels[:, 1:]
    log_probs = F.log_softmax(logits, dim=1)
    token_log_probs = log_probs.gather(2, labels.unsqueeze(1)).squeeze(1)
    masked_log_probs = token_log_probs * response_mask[:, 1:]
    seq_log_probs = masked_log_probs.sum(dim=1) / response_mask[:, 1:].sum(dim=1).clamp(min=1)
    return seq_log_probs

    def train_epoch(
    self,
    dataset: List[Dict],
    batch_size: int = 4,
    epoch: int = 0,
    ) > Dict[str, float]:
    """训练一个 epoch"""
    self.dpo.model.train()
    epoch_metrics = {"loss": [], "accuracy": [], "reward_margin": []}

    for i in range(0, len(dataset), batch_size):
    batch = dataset[i:i + batch_size]

    chosen_ids = torch.stack([b["chosen_input_ids"] for b in batch])
    chosen_mask = torch.stack([b["chosen_attention_mask"] for b in batch])
    chosen_labels = torch.stack([b["chosen_labels"] for b in batch])
    chosen_response_mask = torch.stack([b["chosen_response_mask"] for b in batch])

    rejected_ids = torch.stack([b["rejected_input_ids"] for b in batch])
    rejected_mask = torch.stack([b["rejected_attention_mask"] for b in batch])
    rejected_labels = torch.stack([b["rejected_labels"] for b in batch])
    rejected_response_mask = torch.stack([b["rejected_response_mask"] for b in batch])

    # 计算对数概率
    chosen_log_probs = self.compute_sequence_log_probs(
    self.dpo.model, chosen_ids, chosen_labels, chosen_response_mask
    )
    rejected_log_probs = self.compute_sequence_log_probs(
    self.dpo.model, rejected_ids, rejected_labels, rejected_response_mask
    )

    with torch.no_grad():
    ref_chosen_log_probs = self.compute_sequence_log_probs(
    self.dpo.ref_model, chosen_ids, chosen_labels, chosen_response_mask
    )
    ref_rejected_log_probs = self.compute_sequence_log_probs(
    self.dpo.ref_model, rejected_ids, rejected_labels, rejected_response_mask
    )

    # 计算 DPO 损失
    chosen_rewards = self.config.beta * (chosen_log_probs ref_chosen_log_probs)
    rejected_rewards = self.config.beta * (rejected_log_probs ref_rejected_log_probs)
    logits = chosen_rewards rejected_rewards
    loss = F.logsigmoid(logits).mean()

    self.dpo.optimizer.zero_grad()
    loss.backward()
    torch.nn.utils.clip_grad_norm_(self.dpo.model.parameters(), self.config.max_grad_norm)
    self.dpo.optimizer.step()

    epoch_metrics["loss"].append(loss.item())
    epoch_metrics["accuracy"].append((logits > 0).float().mean().item())
    epoch_metrics["reward_margin"].append(logits.mean().item())

    return {k: np.mean(v) for k, v in epoch_metrics.items()}

    6.4 实验代码

    def experiment_dpo_synthetic():
    """
    DPO 合成实验:在一个简化的偏好学习任务上验证 DPO 的有效性。
    """

    torch.manual_seed(42)

    vocab_size = 100
    seq_len = 20
    hidden_dim = 64
    num_samples = 500
    device = torch.device("cpu")

    # 创建简单的语言模型
    class SimpleLM(nn.Module):
    def __init__(self):
    super().__init__()
    self.embed = nn.Embedding(vocab_size, hidden_dim)
    self.layers = nn.ModuleList([
    nn.TransformerEncoderLayer(
    d_model=hidden_dim, nhead=4, dim_feedforward=hidden_dim * 4,
    batch_first=True
    ) for _ in range(2)
    ])
    self.head = nn.Linear(hidden_dim, vocab_size)

    def forward(self, input_ids, attention_mask=None):
    h = self.embed(input_ids)
    for layer in self.layers:
    h = layer(h, src_key_padding_mask=(attention_mask == 0) if attention_mask is not None else None)
    return type('Output', (), {'logits': self.head(h)})()

    # 生成偏好数据
    def generate_preference_data():
    data = []
    for _ in range(num_samples):
    chosen = torch.randint(1, vocab_size, (seq_len,))
    rejected = torch.randint(1, vocab_size, (seq_len,))
    chosen_labels = chosen.clone()
    rejected_labels = rejected.clone()
    data.append({
    "chosen_input_ids": chosen,
    "chosen_attention_mask": torch.ones(seq_len),
    "chosen_labels": chosen_labels,
    "chosen_response_mask": torch.ones(seq_len),
    "rejected_input_ids": rejected,
    "rejected_attention_mask": torch.ones(seq_len),
    "rejected_labels": rejected_labels,
    "rejected_response_mask": torch.ones(seq_len),
    })
    return data

    dataset = generate_preference_data()

    # 初始化模型
    model = SimpleLM()
    ref_model = SimpleLM()
    ref_model.load_state_dict(model.state_dict())

    config = DPOConfig(beta=0.1, lr=1e-4, max_grad_norm=1.0)
    trainer = DPOTrainer(model, ref_model, config)

    # 训练
    print("DPO 训练开始…")
    for epoch in range(10):
    metrics = trainer.train_epoch(dataset, batch_size=32, epoch=epoch)
    print(f"Epoch {epoch+1}/10 | Loss: {metrics['loss']:.4f} | "
    f"Acc: {metrics['accuracy']:.4f} | Margin: {metrics['reward_margin']:.4f}")

    return trainer

    def compare_dpo_variants():
    """对比 DPO 损失函数变体"""
    torch.manual_seed(42)

    beta = 0.1
    num_points = 1000

    margins = torch.linspace(3, 3, num_points)
    chosen_log_probs = torch.randn(num_points) * 0.5
    rejected_log_probs = chosen_log_probs margins

    variants = DPOLossVariants()

    losses = {
    "Sigmoid (DPO)": variants.sigmoid_loss(chosen_log_probs, rejected_log_probs),
    "Hinge": variants.hinge_loss(chosen_log_probs, rejected_log_probs),
    "IPO": variants.ipo_loss(chosen_log_probs, rejected_log_probs, beta=beta),
    "SPPO": variants.sppo_loss(chosen_log_probs, rejected_log_probs),
    }

    for name, loss in losses.items():
    print(f"{name:20s} | Mean Loss: {loss.mean().item():.4f} | Std: {loss.std().item():.4f}")

    return losses


    7. DPO 的进阶变体

    7.1 IPO(Identity Preference Optimization)

    动机:DPO 在偏好数据上容易过拟合,因为 Bradley-Terry 模型假设可能不成立。

    IPO 损失(Azar et al., 2024):

    LIPO(θ)=E(q,yw,yl)[(log⁡πθ(yw∣q)πref(yw∣q)−log⁡πθ(yl∣q)πref(yl∣q)−12β)2]
    \\mathcal{L}_{\\text{IPO}}(\\theta) = \\mathbb{E}_{(q, y_w, y_l)} \\left[ \\left( \\log \\frac{\\pi_\\theta(y_w | q)}{\\pi_{\\text{ref}}(y_w | q)} – \\log \\frac{\\pi_\\theta(y_l | q)}{\\pi_{\\text{ref}}(y_l | q)} – \\frac{1}{2\\beta} \\right)^2 \\right]
    LIPO(θ)=E(q,yw,yl)[(logπref(ywq)πθ(ywq)logπref(ylq)πθ(ylq)2β1)2]

    直觉:IPO 不追求让奖励差趋向无穷,而是让奖励差趋向一个固定目标 12β\\frac{1}{2\\beta}2β1。这避免了 DPO 的"奖励膨胀"问题。

    7.2 KTO(Kahneman-Tversky Optimization)

    动机:偏好数据的获取成本高,有时只有"好/坏"的标签(无需成对比较)。

    KTO 损失(Ethayarajh et al., 2024):

    LKTO(θ)=E(q,y)[{λwσ(−βr^(q,y))if y is desirableλlσ(βr^(q,y))if y is undesirable]
    \\mathcal{L}_{\\text{KTO}}(\\theta) = \\mathbb{E}_{(q, y)} \\left[ \\begin{cases} \\lambda_w \\sigma(-\\beta \\hat{r}(q, y)) & \\text{if } y \\text{ is desirable} \\\\ \\lambda_l \\sigma(\\beta \\hat{r}(q, y)) & \\text{if } y \\text{ is undesirable} \\end{cases} \\right]
    LKTO(θ)=E(q,y)[{λwσ(βr^(q,y))λlσ(βr^(q,y))if y is desirableif y is undesirable]

    其中 r^(q,y)=log⁡πθ(y∣q)πref(y∣q)\\hat{r}(q, y) = \\log \\frac{\\pi_\\theta(y | q)}{\\pi_{\\text{ref}}(y | q)}r^(q,y)=logπref(yq)πθ(yq)

    关键优势:不需要成对偏好数据,只需要独立的"好/坏"标签。

    7.3 ORPO(Odds Ratio Preference Optimization)

    动机:完全消除对参考模型的依赖。

    ORPO 损失(Hong et al., 2024):

    LORPO(θ)=LSFT(yw)+λ⋅LOR(yw,yl)
    \\mathcal{L}_{\\text{ORPO}}(\\theta) = \\mathcal{L}_{\\text{SFT}}(y_w) + \\lambda \\cdot \\mathcal{L}_{\\text{OR}}(y_w, y_l)
    LORPO(θ)=LSFT(yw)+λLOR(yw,yl)

    其中:

    LOR(yw,yl)=−log⁡σ(log⁡oddsθ(yw∣q)oddsθ(yl∣q))
    \\mathcal{L}_{\\text{OR}}(y_w, y_l) = -\\log \\sigma\\left( \\log \\frac{\\text{odds}_\\theta(y_w | q)}{\\text{odds}_\\theta(y_l | q)} \\right)
    LOR(yw,yl)=logσ(logoddsθ(ylq)oddsθ(ywq))

    oddsθ(y∣q)=πθ(y∣q)1−πθ(y∣q)
    \\text{odds}_\\theta(y | q) = \\frac{\\pi_\\theta(y | q)}{1 – \\pi_\\theta(y | q)}
    oddsθ(yq)=1πθ(yq)πθ(yq)

    关键优势:无需参考模型,将 SFT 和偏好学习合并为一步。

    7.4 SimPO(Simple Preference Optimization)

    动机:进一步简化 DPO,使用序列长度归一化的对数概率作为隐式奖励。

    SimPO 损失(Meng et al., 2024):

    LSimPO(θ)=−E[log⁡σ(β∣yw∣log⁡πθ(yw∣q)−β∣yl∣log⁡πθ(yl∣q)−γ)]
    \\mathcal{L}_{\\text{SimPO}}(\\theta) = -\\mathbb{E} \\left[ \\log \\sigma\\left( \\frac{\\beta}{|y_w|} \\log \\pi_\\theta(y_w | q) – \\frac{\\beta}{|y_l|} \\log \\pi_\\theta(y_l | q) – \\gamma \\right) \\right]
    LSimPO(θ)=E[logσ(ywβlogπθ(ywq)ylβlogπθ(ylq)γ)]

    其中 γ>0\\gamma > 0γ>0 是目标奖励间距。

    关键改进:

  • 长度归一化:避免模型偏好长回答
  • 无参考模型:直接使用对数概率
  • 目标间距 γ\\gammaγ:确保足够的奖励分离

  • 8. DPO 与其他对齐方法的理论对比

    8.1 方法对比表

    特性RLHF (PPO)DPOIPOKTOORPO
    需要奖励模型
    需要参考模型
    需要成对偏好
    训练方式 在线 RL 监督学习 监督学习 监督学习 监督学习
    训练稳定性
    计算成本 高(3×模型) 中(2×模型) 中(2×模型) 中(2×模型) 低(1×模型)
    理论等价性 基准 ≡ RLHF (BT) 不同目标 不同目标 不同目标

    8.2 在线 DPO vs 离线 DPO

    离线 DPO(标准):使用固定的偏好数据集训练,不生成新数据。

    在线 DPO(Iterative DPO):

  • 用当前策略 πθ\\pi_\\thetaπθ 生成回答
  • 用奖励模型或人类标注偏好
  • 用新的偏好数据更新 DPO
  • 数学形式:

    πθk+1=arg⁡min⁡π  E(q,yw,yl)∼Dk[LDPO(π,πθk)]
    \\pi_{\\theta_{k+1}} = \\arg\\min_\\pi \\; \\mathbb{E}_{(q, y_w, y_l) \\sim \\mathcal{D}_k} \\left[ \\mathcal{L}_{\\text{DPO}}(\\pi, \\pi_{\\theta_k}) \\right]
    πθk+1=argπminE(q,yw,yl)Dk[LDPO(π,πθk)]

    其中 Dk\\mathcal{D}_kDk 是第 kkk 轮生成的偏好数据。

    优势:在线 DPO 可以持续改进,避免分布偏移问题。


    9. DPO 的局限性与前沿发展

    9.1 分布偏移问题

    问题:DPO 使用离线数据训练,但测试时策略需要自己生成回答。训练数据的分布(来自 πref\\pi_{\\text{ref}}πref)与测试时的分布(来自 πθ\\pi_\\thetaπθ)不一致。

    数学表述:

    Ey∼πθ[r^(q,y)]≠Ey∼πref[r^(q,y)]
    \\mathbb{E}_{y \\sim \\pi_\\theta} [\\hat{r}(q, y)] \\neq \\mathbb{E}_{y \\sim \\pi_{\\text{ref}}} [\\hat{r}(q, y)]
    Eyπθ[r^(q,y)]=Eyπref[r^(q,y)]

    解决方案:

  • 在线 DPO:定期用新策略生成数据
  • 镜像下降:在 DPO 损失中加入更强的 KL 正则化
  • 9.2 推理能力与 DPO

    DPO 主要优化偏好对齐,不一定能提升推理能力。结合 DPO 和推理训练的前沿方法:

  • DPO + CoT:在 Chain-of-Thought 数据上进行 DPO
  • Step-DPO:对推理的每一步进行偏好优化
  • DPO + MCTS:用树搜索生成高质量推理路径,再用 DPO 训练

  • 10. DPO 数学公式总结

    ╔══════════════════════════════════════════════════════════════════════════════════════════╗
    ║ DPO (Direct Preference Optimization) 数学总结 ║
    ╠══════════════════════════════════════════════════════════════════════════════════════════╣
    ║ ║
    ║ 1. RLHF 目标函数: ║
    ║ max_π E_{q,y~π}[r(q,y)] – β·D_KL[π || π_ref] ║
    ║ ║
    ║ 2. 最优策略闭式解: ║
    ║ π*(y|q) = (1/Z(q)) · π_ref(y|q) · exp(r(q,y)/β) ║
    ║ Z(q) = Σ_y π_ref(y|q) · exp(r(q,y)/β) ║
    ║ ║
    ║ 3. 奖励反解: ║
    ║ r(q,y) = β·log(π*(y|q)/π_ref(y|q)) + β·log Z(q) ║
    ║ ║
    ║ 4. Bradley-Terry 偏好模型: ║
    ║ P(y_w ≻ y_l | q) = σ(r(q,y_w) – r(q,y_l)) ║
    ║ ║
    ║ 5. DPO 损失函数: ║
    ║ L_DPO(θ) = -E_{(q,y_w,y_l)} [ log σ(β · ( ║
    ║ log π_θ(y_w|q)/π_ref(y_w|q) – log π_θ(y_l|q)/π_ref(y_l|q) ║
    ║ )) ] ║
    ║ ║
    ║ 6. 隐式奖励: ║
    ║ r̂(q,y) = β·log(π_θ(y|q) / π_ref(y|q)) ║
    ║ 性质: r̂(q,y) = r(q,y) – β·log Z(q) (偏好排序一致) ║
    ║ ║
    ║ 7. DPO 梯度: ║
    ║ ∇L = -β·E[σ(r̂_l – r̂_w)·(∇log π(y_w) – ∇log π(y_l))] ║
    ║ 自动加权: 模型越困惑 → 梯度越大 ║
    ║ ║
    ║ 8. 等价性: ║
    ║ DPO ≡ RLHF (在 Bradley-Terry 模型假设下) ║
    ║ DPO 绕过奖励模型, 直接从偏好数据学习策略 ║
    ║ ║
    ║ 9. DPO 变体: ║
    ║ IPO: L = (r̂_w – r̂_l – 1/(2β))² (避免过拟合) ║
    ║ KTO: L = σ(-β·r̂) for desirable (无需成对数据) ║
    ║ ORPO: L = -log σ(log odds_w/odds_l) (无需参考模型) ║
    ║ SimPO: L = -log σ(β·log π(y_w)/|y_w| – β·log π(y_l)/|y_l| – γ) ║
    ║ ║
    ║ 10. 超参数 β 的作用: ║
    ║ β 大 → 策略保守 (接近 π_ref) ║
    ║ β 小 → 策略激进 (更依赖奖励) ║
    ║ ║
    ╚══════════════════════════════════════════════════════════════════════════════════════════╝


    参考文献

    DPO

  • Rafailov, R., Sharma, A., Mitchell, E., Manning, C.D., Ermon, S., & Finn, C. (2023). Direct Preference Optimization: Your Language Model is Secretly a Reward Model. NeurIPS 2023.
  • Azar, M.G., Rowland, M., Piot, B., et al. (2024). A General Theoretical Paradigm to Understand Learning from Human Feedback. AISTATS 2024.
  • Ethayarajh, K., Xu, W., Muennighoff, N., Jurafsky, D., & Kiela, D. (2024). KTO: Model Alignment as Prospect Theoretic Optimization. ICML 2024.
  • Hong, J., Lee, N., & Thorne, J. (2024). ORPO: Monolithic Preference Optimization without Reference Model. EMNLP 2024.
  • Meng, Y., Xia, M., & Chen, D. (2024). SimPO: Simple Preference Optimization with a Reference-Free Reward. NeurIPS 2024.
  • Schulman, J., Wolski, F., Dhariwal, P., Radford, A., & Klimov, O. (2017). Proximal Policy Optimization Algorithms. arXiv:1707.06347.
  • Bradley, R.A. & Terry, M.E. (1952). Rank Analysis of Incomplete Block Designs: I. The Method of Paired Comparisons. Biometrika, 39(3/4), 324-345.
  • 赞(0)
    未经允许不得转载:171主机测评 » 直接偏好优化算法深度解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址