✨ 复制成功!

以文会友,打造好学人设!

📋 已复制到剪贴板:

《大模型入门精华》


1 前言
    1.1  大模型的发展历史

    1.2  Hugging Face简介

    1.3  大模型技术总览

    1.4  GPT模型的发展历程

    1.5  BERT和GPT之战

2 Transformer技术
    2.1  词嵌入技术简介

    2.3  Transformer简介

3 预训练与监督微调
    3.1  预训练技术简介

    3.2  监督微调技术简介

4 大模型核心技术
    4.3  大模型的重复问题

    4.4  LoRA的诞生背景

    4.5  LoRA的数学原理

    4.6  LoRA的核心考点

    4.8  知识蒸馏常见考题

    4.9  模型量化常见考题

    4.10  大模型的剪枝问题

    4.12  RAG原理介绍

5 大模型压缩技术
    5.2  压缩技术全景图

    5.3  知识蒸馏入门介绍

    5.4  温度参数入门介绍

    5.5  落地选型的考量

    5.6  智能客服案例分析

    5.7  大模型量化

    5.8  模型剪枝入门介绍

    5.9  大模型压缩实战

6 大模型与强化学习
    6.1  大模型与强化学习

    6.2  强化学习面试要点

    6.3  RLHF核心考题

    6.4  RLHF的挑战

    6.5  GRPO入门介绍

    6.6  人类反馈强化学习

    6.8  PPO算法代码复现

    6.9  大模型与强化学习

    6.10  强化学习核心概念

    6.11  RLHF入门介绍

    6.12  PPO入门介绍

    6.13  RLAIF与自动化对齐

    6.16  规模定律详细介绍

    6.17  强化学习的未来

7 扩散模型与文生图
    7.1  文生图原理介绍

    7.2  扩散模型原理介绍

    7.3  扩散模型与文生图

8 手搓大模型

直接偏好优化算法介绍

创建时间:2026-06-28 更新时间:2026-06-29 阅读次数:1062 次

DPO 的英文全称是 Direct Preference Optimization,直译为直接偏好优化。它的含义就凝练在这个名字里:

Direct(直接):指它不需要像传统 RLHF(基于人类反馈的强化学习)那样,先单独训练一个奖励模型,再用强化学习去优化。它直接从数据中学习。

Preference(偏好):指它学习的核心信号是人类的偏好数据,即“回复 A 比回复 B 好”这种成对的比较。

Optimization(优化):它是一个优化过程,目标是让模型生成更符合人类喜好的回复。

用一句话概括其核心思想就是:将模型本身视为一个隐式奖励模型,直接利用人类的偏好对比数据来优化语言模型。

1、从 RLHF 说起:为什么需要更好的方法?

想象你正在训练一个助手模型,你不仅希望它知道“法国的首都是巴黎”,还希望当用户要求写一首诗时,它写出优美的作品而不是脏话。这就是“对齐”——让模型的行为符合人类偏好。

RLHF的传统路径分三步走:

1、监督微调:让模型学会基本对话格式

2、训练奖励模型:收集人类偏好数据,训练一个打分模型

3、PPO强化学习:用奖励模型作为裁判,优化生成策略

这个流程就像一个复杂的化工厂,中间环节多、容易出错。奖励模型可能被“欺骗”,PPO训练又需要同时加载四个模型,对计算资源是巨大考验。

DPO 的核心洞见十分深刻:人类偏好数据是成对的——“回复A比回复B好”。既然我们有了“更好”的直接比较,为什么还要绕远去训练一个“打分员”,再从分数中推导什么是好的?我们直接让模型从偏好对中学习岂不是更直接?

这就好比学下棋。传统方式是请一位教练(奖励模型),每次你走完一步,他给你一个分数,你根据分数改进。DPO 的方式是:直接给你看两盘棋——“这盘下得好,那盘下得不好”,让你直接从对比中领悟好棋的精髓。哪种更高效?显然是后者。

2、深度解析 DPO:数学原理之美

DPO 的核心是重新定义了损失函数。它不需要先训练奖励模型,而是巧妙地用模型本身来表示“隐式奖励”。这个损失函数的灵魂就隐藏在下面这段公式中。不要被它吓到,我们一步步拆解,你会发现它表达的思想其实非常直观:

$$ L_{DPO}(\pi_{\theta}; \pi_{\text{ref}}) = -\mathbb{E}_{(x, y_w, y_l) \sim D} \left[ \log \sigma \left( \beta \log \frac{\pi_{\theta}(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \log \frac{\pi_{\theta}(y_l|x)}{\pi_{\text{ref}}(y_l|x)} \right) \right] $$

我们用白话把这个公式“翻译”一遍:

  • 我们手头有什么:提示 $x$,人类更喜欢的回复 $y_w$(winner),不太喜欢的回复 $y_l$(loser)。
  • 我们想要什么:模型 $\pi_{\theta}$ 对 $y_w$ 的打分要比对 $y_l$ 的打分高得多。这里巧妙地将模型生成一个回复的概率视为“模型对该回复的隐式打分”。
  • 怎么评判分数:我们把获胜回复和失败回复分别代入公式,计算它们的一个“得分”差异。这个差异越大,说明模型越能区分好坏。
  • $\beta$ 是控制杆:它决定了模型能多大程度地偏离它最初的“参考模型” $\pi_{\text{ref}}$。因为 $\pi_{\text{ref}}$ 也出现在了公式的分母里,它起到了一个“锚点”和“约束”的作用,防止模型为了单纯优化偏好而“忘记”自己原来会说的话,出现严重的灾难性遗忘。
  • $\sigma$ 的作用:sigmoid 函数将得分差转化为一个 0 到 1 之间的概率,然后用对数损失来优化。

更直白的直觉理解:

DPO 的损失函数就是在做一件事:它拼命地让 $\frac{\pi_{\theta}(y_w|x)}{\pi_{\text{ref}}(y_w|x)}$ 这个比值远大于 $\frac{\pi_{\theta}(y_l|x)}{\pi_{\text{ref}}(y_l|x)}$。也就是说,经过训练后,模型 $\pi_{\theta}$ 相对于原始模型 $\pi_{\text{ref}}$,要极大地提升好回复的生成概率,同时极大地降低差回复的生成概率。

3、动手实践:从零实现一个 DPO 训练器

理论弄明白了,代码实现其实出奇地简洁。我们来搭建一个可以实际跑通的 DPO 训练器。

1、导入必要的库并定义一个玩具数据集

我们先用一个极其简化的场景来演示:让模型学会更倾向于“积极”的回复。

import torch
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from transformers import AutoModelForCausalLM, AutoTokenizer

# 定义一个超小的偏好数据集
# 每个样本包含:提示(prompt),好的回复(chosen),差的回复(rejected)
toy_dataset = [
    {
        "prompt": "今天天气真好,我感觉",
        "chosen": "心情非常舒畅,充满了活力!",
        "rejected": "也就那样吧,没什么特别的。"
    },
    {
        "prompt": "我刚收到一份礼物,",
        "chosen": "真是太开心了,拆礼物的感觉真好!",
        "rejected": "不知道是谁送的,有点烦。"
    },
    {
        "prompt": "关于人工智能的未来,",
        "chosen": "我充满希望,它将帮助我们解决许多难题。",
        "rejected": "我担心它会失控,带来很多灾难。"
    }
]

2、最核心的部分:DPO 损失函数

这里就是公式的代码化身。我们会逐行解释。

def dpo_loss(policy_chosen_logps, policy_rejected_logps,
             ref_chosen_logps, ref_rejected_logps, beta=0.1):
    """
    计算 DPO 损失。

    参数:
        policy_chosen_logps: 策略模型对好回复生成的对数概率
        policy_rejected_logps: 策略模型对差回复生成的对数概率
        ref_chosen_logps: 参考模型对好回复生成的对数概率
        ref_rejected_logps: 参考模型对差回复生成的对数概率
        beta: 控制与参考模型偏离程度的超参数
    """
    # 1. 计算策略模型和参考模型之间的相对概率比的对数
    policy_log_ratio = policy_chosen_logps - policy_rejected_logps
    ref_log_ratio = ref_chosen_logps - ref_rejected_logps

    # 2. 计算最终的隐式奖励差距,并乘以 beta
    # 这就是公式里面 sigmoid 函数里的那一大串
    logits = beta * (policy_log_ratio - ref_log_ratio)

    # 3. 计算损失。F.logsigmoid 等价于 log(sigmoid(x))
    # 由于我们想让 chosen 打败 rejected,所以用负号来最小化损失
    loss = -F.logsigmoid(logits)

    return loss.mean()
  • policy_log_ratio 就是 $\log \frac{\pi_\theta(y_w|x)}{\pi_\theta(y_l|x)}$。

  • ref_log_ratio 就是 $\log \frac{\pi_{\text{ref}}(y_w|x)}{\pi_{\text{ref}}(y_l|x)}$。

  • 两者相减,再乘以 $\beta$,就得到了公式里完整的内部项。

  • -F.logsigmoid(...) 是对 $\log \sigma(\cdot)$ 的损失计算,完全吻合公式。

3、构建一个完整的训练循环框架

这提供了一个可扩展的蓝图。

def train_dpo(model, ref_model, tokenizer, dataset, optimizer, beta=0.1, epochs=100):
    """一个简化的 DPO 训练循环示例"""
    model.train()
    ref_model.eval() # 参考模型冻结,不更新参数

    for epoch in range(epochs):
        total_loss = 0
        for batch in dataset:
            prompt = batch["prompt"]
            chosen = batch["chosen"]
            rejected = batch["rejected"]

            # ----- 1. 组装文本并分词 -----
            # 为了方便,这里把 prompt + response 拼在一起
            chosen_text = [p + c for p, c in zip(prompt, chosen)]
            rejected_text = [p + r for p, r in zip(prompt, rejected)]

            # 这里简化了:实际应用中需要更精细的掩码控制
            chosen_tokens = tokenizer(chosen_text, return_tensors="pt", padding=True, truncation=True)
            rejected_tokens = tokenizer(rejected_text, return_tensors="pt", padding=True, truncation=True)

            # ----- 2. 计算所有需要的对数概率 -----
            with torch.no_grad(): # 参考模型的输出不需要梯度
                ref_chosen_logps = get_log_probs(ref_model, **chosen_tokens)
                ref_rejected_logps = get_log_probs(ref_model, **rejected_tokens)

            policy_chosen_logps = get_log_probs(model, **chosen_tokens)
            policy_rejected_logps = get_log_probs(model, **rejected_tokens)

            # ----- 3. 计算损失并更新模型 -----
            loss = dpo_loss(policy_chosen_logps, policy_rejected_logps,
                           ref_chosen_logps, ref_rejected_logps, beta)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            total_loss += loss.item()

        if (epoch + 1) % 20 == 0:
            print(f"Epoch {epoch+1}/{epochs}, Average Loss: {total_loss/len(dataset):.4f}")

def get_log_probs(model, input_ids, attention_mask):
    """辅助函数:获取模型对输入序列的平均对数概率"""
    outputs = model(input_ids=input_ids, attention_mask=attention_mask)
    logits = outputs.logits # 形状: [batch, seq_len, vocab_size]

    log_probs = F.log_softmax(logits, dim=-1)
    # 获取每个位置真实 token 的对数概率
    # 将 input_ids 移位一位,以便 log_probs[:, :-1, :] 能预测 input_ids[:, 1:]
    target_log_probs = torch.gather(log_probs[:, :-1, :], dim=-1, index=input_ids[:, 1:].unsqueeze(-1)).squeeze(-1)

    # 计算序列的平均对数概率(这里简化了,只对非填充部分取平均)
    mask = attention_mask[:, 1:].float()
    avg_log_probs = (target_log_probs * mask).sum(dim=1) / mask.sum(dim=1)
    return avg_log_probs

4、总结:DPO 的优势与思考

到这里,DPO的整个图景应该很清晰了。最后我们梳理一下它的核心优势与局限。

优势

简洁稳定:无需训练和维护独立的奖励模型,直接优化策略,流程简洁,训练稳定。

计算高效:显存占用和计算开销显著降低,让偏好对齐不再是大厂的专利。

理论优雅:不基于采样和近似(PPO),而是直接在偏好数据上定义损失,优化目标明确。

局限与思考

数据就是一切:DPO的性能极度依赖偏好数据的质量和分布。如果数据有偏差,模型会直接学到这些偏差。

分布外泛化:当训练好的模型面对与训练时完全不同的新提示时,其性能可能变得难以预测。

并非万能钥匙:DPO适合让“好”与“更好”的模型对齐,但如果基础模型能力本身就弱,DPO也无能为力。

DPO 不仅是工程上的简化,更代表了从“奖励建模”到“直接偏好”的范式转变。希望这个章节能让大家对这项技术有一个扎实且清晰的理解。

本教程共55节,当前为第42节!
本教程最新修订时间为:2026-08-17 09:09:33

📌 面试天下网:一款服务于大一新生的口袋书,让大家在无聊的公共课上可以学习大模型技术!
📌 网站公告:程序出海:中国程序员当下最大的机遇>>>>>>