第一次看到DPO的论文时,我脑子里就一个想法:“还能这样玩?”

传统的RLHF做对齐,流程是:训练Reward Model → PPO强化学习 → 模型对齐。中间那个Reward Model,训练起来又贵又麻烦,还特别不稳定。

DPO(Direct Preference Optimization)直接把这个步骤砍了。不训练Reward Model,不搞强化学习,一个公式搞定对齐。

去年我在一个对话模型项目上试了DPO,效果配得上它的名气。这篇文章聊聊实战经验和踩过的坑。

为什么Reward Model让人头疼?

先说说传统RLHF为什么烦人。

做RLHF的标准流程是:

  1. SFT:先用高质量数据做监督微调
  2. 训练Reward Model:收集人类偏好数据,训练一个打分模型
  3. PPO:用Reward Model的分数指导模型优化

每一步都有坑。尤其是RM这一步——你要训练一个专门的打分模型,这个模型本身需要大量的偏好数据,而且训练过程中还经常出现"reward hacking"(模型找到了作弊的方式获得高分,而不是真正变好了)。

更离谱的是,我发现Reward Model过拟合的速度特别快。训练了2个epoch后,RM给出的人类偏好准确率确实上去了,但对模型质量的引导效果反而下降了。

DPO的精髓就是:不要Reward Model,直接用偏好数据优化策略。

DPO的数学直觉

不讲复杂公式,只说直觉。

RLHF的本质是:我们希望模型更倾向于输出"人类喜欢的回答",同时不要偏离原始SFT模型太远(防止灾难性遗忘)。

传统做法是学一个RM来打分,然后让模型跟着分数走。

DPO的做法是:既然最终目的是让模型直接学习"哪个回答更好",为什么不直接优化这个目标?

DPO推导出了一个可以直接用的损失函数:

偏好数据中,"好回答"的log-probability - "差回答"的log-probability

加上一个KL散度限制(防止模型跑偏)。

这个损失函数直接告诉模型:“这两个回答里,第一个比第二个好,你的输出应该更靠近第一个的分布。”

不需要Reward Model,不需要PPO的actor-critic框架,不需要经验回放缓存。 直接一个损失函数搞定。

实战:DPO微调的完整流程

数据准备

DPO需要偏好数据集。每条数据包含三个字段:

  • prompt: 用户输入
  • chosen: 人类偏好的回答
  • rejected: 人类不偏好的回答

数据来源有两种方式:

方式一:人工标注

找标注人员对模型的多条输出排序。质量最高,但成本也最高。

方式二:对比采样 + 自动标注

用不同模型或参数生成多个回答,然后用GPT-4作为判断器打分。效果不错,成本可控。

# 偏好数据格式示例
preference_data = [
    {
        "prompt": "解释一下监督学习和无监督学习的区别",
        "chosen": "监督学习用带标签的数据训练,无监督学习用无标签的数据...",
        "rejected": "监督学习嘛就是有老师教,无监督学习就是自学..."
    },
    # ...更多数据
]

训练代码

用Hugging Face的TRL库做DPO,代码比想象中简单:

from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import DPOTrainer

# 加载模型
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B")

# DPO参数
training_args = {
    "per_device_train_batch_size": 2,
    "gradient_accumulation_steps": 8,
    "learning_rate": 5e-7,
    "max_length": 2048,
    "max_prompt_length": 1024,
    "beta": 0.1,  # KL散度系数
}

trainer = DPOTrainer(
    model=model,
    ref_model=None,  # 如果不提供,DPO会自动从model复制一个
    args=training_args,
    train_dataset=preference_dataset,
    tokenizer=tokenizer,
)

trainer.train()

代码不多,但参数调起来很讲究。下面说参数的选择。

核心参数调优

Beta:KL散度系数

这是DPO最重要的超参数。

  • Beta越大:对模型原始分布的限制越严格,模型变化较小
  • Beta越小:模型可以更大程度地偏离原始分布,对齐更激进

我的经验:

  • 如果SFT模型质量还行,只需要微调偏好:beta=0.1~0.2(保守)
  • 如果SFT模型比较差,需要大幅改善:beta=0.05~0.1(激进)
  • 不知道选什么:从beta=0.1开始试

学习率

DPO的学习率通常比SFT要小。我一般从5e-7开始试,效果不行再调。

注意:DPO对学习率很敏感。lr大了模型会迅速"遗忘"原始能力,小了训练半天看不到效果。

数据质量 >> 数据数量

这点特别重要。500条高质量的偏好数据,效果远好于5000条掺水数据。

我踩过的坑就是以为"数据越多越好",结果花了大量精力做了5000条标注数据,里面很多标注人员的偏好其实不一致。模型训练出来后,不但没变好,反而更"分裂"了。

好的偏好数据要求标注一致性 > 80%,意见分歧大的数据直接丢掉。

和PPO的对比

在同一个对话模型上,我同时试了DPO和PPO,主观感受如下:

维度DPOPPO
训练复杂度低(一个训练脚本搞定)高(RM+PPO两阶段)
训练稳定性稳定容易崩(reward explosion)
对齐效果不错更好(上限更高)
数据效率
资源消耗低(一个模型)高(RM+Policy两个模型)

我的建议

  • 小团队、预算有限、快速验证 → DPO
  • 大团队、追求极致对齐效果、有训练RM的资源 → PPO
  • 实际做法:先用DPO快速上线,有资源了再上PPO追赶

写在最后

DPO是个很聪明的设计。它没有发明什么全新的东西,而是换了一个角度看问题——“既然Reward Model也是从人类偏好中学来的,为什么不让模型直接学偏好?”

有时候,最好的解决方案就是砍掉中间层。

如果你已经在做模型对齐,强烈建议从DPO开始。成本低、效果好、上手快。等搞清楚了偏好优化的门道,再决定是否要上PPO。

毕竟,先跑起来,再跑快,才是工程正确的姿势。

更多推荐