理论

DPO: Direct Perference Optimization

概念

DPO 和监督微调一样,我们可以从任何大语言模型开始(通常建议使用指令微调大语言模型,这种模型已经可以回答用户的一些基本问题。
目标是整理标注员准备的一些对比数据来改变模型身份,即:

提高模型的回答质量(使模型的回答偏向于想要的内容)

我们需要准备至少两个回答:negative可以使用模型当前的输出,positive则可以用更好的输出(见数据策划原则)
“用监督学习的方式,去实现强化学习中的偏好对齐。”

  • SFT(Supervised Fine-Tuning) 让模型学“正确答案”;
  • DPO 让模型学“更好的答案”。

损失

DPO 的优化目标是区分 正样本(preferred response)负样本(dispreferred response)。旨在最小化对比损失,该损失对负面回复进行惩罚,并鼓励正面回复。DPO损失实际上是对重参数化奖励模型的奖励差异的交叉熵损失:

L DPO = − log ⁡ σ ( β ( log ⁡ π θ ( y pos ∣ x ) π ref ( y pos ∣ x ) − log ⁡ π θ ( y neg ∣ x ) π ref ( y neg ∣ x ) ) ) \mathcal{L}_{\text{DPO}} = -\log \sigma \left(\beta \left(\log \frac{\pi\theta(y_{\text{pos}} \mid x)}{\pi_{\text{ref}}(y_{\text{pos}} \mid x)} -\log \frac{\pi_\theta(y_{\text{neg}} \mid x)}{\pi_{\text{ref}}(y_{\text{neg}} \mid x)}\right)\right) LDPO=logσ(β(logπref(yposx)πθ(yposx)logπref(ynegx)πθ(ynegx)))
模型回答“positive”回答将使得loss变小,相反模型回答“negative”的回答将会使得loss变大。
通过最小化损失函数,模型逐渐学会生成“positive”的回答。

含义解释:

  • π θ \pi_\theta πθ:当前微调模型;
  • π ref \pi_{\text{ref}} πref:参考模型(通常是初始的 SFT 模型,冻结不更新);
  • y pos y_{\text{pos}} ypos:正样本回答;
  • y neg y_{\text{neg}} yneg:负样本回答;
  • β \beta β:平衡超参数,控制模型更新的“激进程度”。 β \beta β越大,对数差异越重要

以第一项为例:
分子: 对于微调后的模型,在给定提示的情况下,产生正面回复的概率是多少
分母: 分母是一个参考模型,它是原始模型的副本权重固定,不可调整。我们只关注原始模型在给定提示的情况下,产生那些正面回复的概率。

直观理解:

模型被训练成:在相同输入 x x x 下,使得“正样本”的相对概率比“负样本”更大。

若我们定义:

r ( x , y ) = log ⁡ π θ ( y ∣ x ) π ref ( y ∣ x ) r(x, y) = \log \frac{\pi_\theta(y \mid x)}{\pi_{\text{ref}}(y \mid x)} r(x,y)=logπref(yx)πθ(yx)
则 DPO 实际上相当于对奖励差 r ( x , y pos ) − r ( x , y neg ) r(x, y_{\text{pos}}) - r(x, y_{\text{neg}}) r(x,ypos)r(x,yneg) 施加 sigmoid 函数,使正样本获得更高“奖励”。
因此,DPO ≈ 奖励学习的显式形式化

使用场景

1. 改变模型行为(identity / style / safety)

  • 调整模型回答风格、语气、身份或安全相关行为。
  • 例如:
    • 将模型从“助手”改为“研究助理”;
    • 提升模型的多语言能力;
    • 限制模型输出某类内容。

实质:微调模型的“人格”或“语气”

2. 提升模型能力(quality / alignment)

  • DPO 能通过对比学习看到“好回答”和“坏回答”的差异,因此在提升回答质量时常优于 SFT。
  • 若在 高质量偏好数据 上进行,DPO 能显著改善 factuality、coherence 和安全性。
  • 若在线生成样本(Online DPO),模型还能不断提升自身的偏好判断能力。

数据策划原则

DPO 数据 = 每条样本包含两个回答:
→ (chosen / positive) 与 (rejected / negative)。

1. 纠正correction

  • 从模型原始输出中挑选“低质量”回答作为负样本;
  • 人工改写为更好的回答作为正样本。
  • 常用于小规模、高质量调优。

2. 在线 DPO(Online / On-policy DPO)

  • 模型自己生成多个回答;
  • 通过人类选择或自动评分系统(如 reward model)选出优劣;
  • 将最优与最差配对形成 DPO 数据。
  • 更适合大规模自进化。

潜在问题与对策

DPO本质是奖励学习,会学会“偷懒”找到一些“捷径”:例如可能positive样本相对于negative样本包含一些特殊词,那么模型倾向于在自己的回答中加上这些特殊词而不是真正地去生成更好的回答

问题 说明 对策
过拟合捷径 模型可能学会简单规则(如出现某个关键词)来“讨好”DPO信号 加强数据多样性,或引入对比正则项
数据偏差 若正负样本差异仅体现在无关特征上,模型将强化这些伪特征 保持数据公平、覆盖不同主题
ref_model选择 若参考模型太弱或太强,会影响梯度方向 通常选用同一 SFT 模型的冻结副本

实践

目标

改变模型身份,将 “Qwen” 改为 “Deep Qwen”

1. 加载模型与分词器

与SFT实践一样,我们首先导入模型和分词器,使用相同的辅助函数:

#构建Qwen2.5-0.5B-Instruct模型和分词器
model, tokenizer = load_model_and_tokenizer("./models/Qwen/Qwen2.5-0.5B-Instruct",
                                            USE_GPU)
#测试模型
test_model_with_questions(model, tokenizer, questions,
                          title="Instruct Model (Before DPO) Output")

2. 加载原始数据

#使用transformers库的load_dataset函数加载identity数据集
raw_ds = load_dataset("mrfakename/identity", split="train")

在这里插入图片描述

在这里插入图片描述

3. 构建 DPO 数据——替换关键词

这里采用的是“纠正”的方法,将模型的名字相应替换成"Deep Qwen"。

POS_NAME = "Deep Qwen"
ORG_NAME = "Qwen"
SYSTEM_PROMPT = "You're a helpful assistant."

使用不同格式的数据集时,应该改成Qwen的ChatML形式(即“role”“content”格式)

#构建DPO的ChatML格式数据
def build_dpo_chatml(example):
    msgs = example["conversations"]
    prompt = next(m["value"] for m in reversed(msgs) 
                  if m["from"] == "human")#获取prompt
    try:
        rejected_resp = generate_responses(model, tokenizer, prompt)#生成拒绝响应
    except Exception as e:
        rejected_resp = "Error: failed to generate response."
        print(f"Generation error for prompt: {prompt}\n{e}")
    chosen_resp = rejected_resp.replace(ORG_NAME, POS_NAME)#生成选择响应
    chosen = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": chosen_resp},
    ]
    rejected = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": rejected_resp},
    ]

    return {"chosen": chosen, "rejected": rejected}
    
    #将原始数据集转换为DPO的ChatML格式
dpo_ds = raw_ds.map(build_dpo_chatml, remove_columns=raw_ds.column_names)

4. 配置与训练

与SFT大同小异:

config = DPOConfig(
    beta=0.2, # beta参数控制选择和拒绝响应的权重
    per_device_train_batch_size=1,# 每个设备的训练批次大小
    gradient_accumulation_steps=8,# 梯度累积步数
    num_train_epochs=1,# 训练的总轮数
    learning_rate=5e-5,# 学习率
    logging_steps=2,# 日志记录步数
)
#创建DPO训练器
dpo_trainer = DPOTrainer(
    model=model,# 模型
    ref_model=None,# 参考模型(如果有的话)
    args=config,    # 训练参数配置
    processing_class=tokenizer,  # 分词器
    train_dataset=dpo_ds# 训练数据集
)
#训练DPO模型
dpo_trainer.train()

心得与体会

  • 定位:DPO 是 SFT 之后的“微调微调”,核心在于对齐而非学习新知识。
  • 优势:可以快速改善模型行为或质量,而不需训练奖励模型。
  • 关键点
    • 数据的正负对比质量决定 DPO 成效;
    • 过高的 β 或过于相似的样本会导致学习不稳定;
    • 对于 identity、tone、style 等任务,DPO 的效果尤为显著;
    • DPO 也可以结合 SFT 联合训练(SFT+DPO)提升稳健性。

“SFT教模型说话,DPO教模型说得更好。”

post-training的流程大多大同小异,和不同网络的搭建一样,更加重要的应该是数据的处理以及不断地调参。

参考资料

https://github.com/datawhalechina/Post-training-of-LLMs/

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐