设计机器学习应用系统设计机器学习应用系统
首页
讨论区
首页
讨论区
  • 目录
  • 前言

    • 关于作者
    • 关于本文档
  • 机器学习数学基础

    • 线性代数

      • 向量基础
      • 矩阵基础
    • 微积分

      • 极限、导数与微分
      • 多元函数与复合函数求导
    • 统计与概率

      • 概率基础
      • 统计推断
  • 经典统计学习方法

    • 线性模型

      • 线性回归
      • 逻辑回归
      • 正则化与广义线性模型
    • 贝叶斯方法

      • 朴素贝叶斯
      • 贝叶斯网络
      • EM 算法
    • 支持向量机

      • 支持向量机
      • 核技巧
    • 决策树与集成

      • 决策树
      • 随机森林
      • 提升方法
    • 无监督学习

      • 聚类
      • 降维
  • 神经网络与深度学习

    • 神经网络结构

      • 神经网络基础原理
      • 线性感知机
      • 多层感知机
      • 前向传播
      • 反向传播
      • 激活函数与损失函数
    • 优化神经网络

      • 梯度下降
      • 自适应优化器
    • 深层网络稳定性

      • 权重初始化
      • Dropout 正则化
      • 批归一化
    • 卷积神经网络

      • CNN 基础原理
      • AlexNet 与 CNN 复兴
      • VGG 与 GoogLeNet
      • ResNet 残差网络
      • 工程实训:AlexNet 图像分类实验
    • 生成式模型

      • 变分自编码器
      • 生成式对抗网络
      • 工程实训:DCGAN 图像生成实验
    • 序列模型

      • 词嵌入与表示学习
      • RNN 基础原理
      • LSTM 与 GRU 门控机制
      • Seq2Seq 序列映射
      • 工程实训:LSTM 古诗词生成实验
  • 语言模型的奇点

    • Transformer 架构

      • Transformer 基础原理
      • Transformer 演进与变体
      • 语言模型与分词
      • 工程实训:Transformer 模型训练实验
    • 预训练与微调

      • 预训练数据工程
      • 缩放定律
      • 分布式训练基础设施
      • 监督微调
      • 工程实训:SFT 模型对话实验
    • 对齐训练

      • 人类反馈强化学习
      • 对齐方法的演进
      • 工程实训:DPO 对齐训练实验
    • 推理能力

      • 思维链与推理模型
      • 推理缩放定律
      • 推理效率优化
      • 工程实训:LLM 推理效率优化实验
    • 模态融合与安全

      • 多模态大模型
      • 模型评估与安全
      • 工程实训:视觉语言模型训练实验
  • AI 基础设施与工程化

    • 模型服务化

      • 推理服务架构
      • 请求调度与批处理
      • GPU 资源管理
      • 工程实训:部署 LLM 推理服务
    • 工程化实践

      • 数据版本管理
      • 实验追踪与模型管理
      • 自动化调参
      • 模型性能监控
      • 漂移检测
  • Agentic 应用系统

    • 向量检索与增强生成

      • 嵌入与向量检索
      • 检索质量评估与优化
      • 检索增强生成
      • 工程实训:构建知识库问答系统
    • 构建 Agent 应用

      • 从 LLM 到 Agent
      • 工具调用
      • 规划与推理
      • 记忆系统
      • 协作与通讯
      • 编排与容错
      • 工程实训:技术调研 Agent 协作系统
  • 附录

    • 构建沙箱环境
    • Numpy 数学实践

      • 数据处理实践
      • 微积分计算实践
      • 概率统计实践

DPO 对齐训练实验

在 SFT 模型对话实验中,我们通过监督微调教会了模型遵循对话格式,实现了从续写到对话的转变。SFT 的训练数据是"用户问 → 模型答"的指令回答对,模型学到的是模仿训练数据中的回答模式。但同一个问题可以有多种合理的回答方式,SFT 无法告诉模型哪一种更符合人类的偏好,回答更准确、语气更友好、拒绝更礼貌,这些维度上的差异 SFT 都捕捉不到。

本实验使用直接偏好优化方法,在 SFT 模型的基础上进行对齐训练。DPO 的训练数据是同一个问题的两个回答,人类标注哪个更好,模型从中学会区分回答的优劣,在生成时更倾向于选择偏好的回答风格。

实验准备

在开始实验之前,请确保已完成以下准备工作:

  1. 已完成 SFT 模型对话实验,模型权重文件 full_sft_768.pth 已在数据目录中正确生成。
  2. 已挂载数据目录并下载好 DPO 偏好语料。
# 选择 "下载数据集" -> 选择 "MiniMind Alignment (LLM对齐语料)"
dmla data

MiniMind 项目的 DPO 偏好语料(dpo.jsonl)包含约 20K 条偏好对比数据,数据抽样自 DPO-En-Zh-20k,体积约 53 MB。数据集下载完成后,以下代码可验证 SFT 模型和 DPO 语料是否完整:

import os

# 检查 SFT 模型(由上一章实验生成)
sft_dir = os.path.join(DATA_DIR, 'models', 'minimind', 'sft')
sft_path = os.path.join(sft_dir, 'full_sft_768.pth')
if os.path.exists(sft_path):
    size_mb = os.path.getsize(sft_path) / (1024 ** 2)
    print(f"SFT 模型: 已存在 ({size_mb:.1f} MB)")
else:
    # 尝试 epoch checkpoint
    for epoch in [2, 1]:
        ckp = os.path.join(sft_dir, f'sft_epoch{epoch}.pth')
        if os.path.exists(ckp):
            size_mb = os.path.getsize(ckp) / (1024 ** 2)
            print(f"SFT 模型: 使用 epoch {epoch} checkpoint ({size_mb:.1f} MB)")
            break
    else:
        print("SFT 模型: 未找到!请先完成 SFT 实验")

# 检查 DPO 语料
dpo_dir = os.path.join(DATA_DIR, 'datasets', 'minimind-alignment')
if os.path.exists(dpo_dir):
    print(f"DPO 语料目录: 已存在")
    for f in os.listdir(dpo_dir):
        fpath = os.path.join(dpo_dir, f)
        if os.path.isfile(fpath):
            size_mb = os.path.getsize(fpath) / (1024 ** 2)
            print(f"  {f}: {size_mb:.1f} MB")
else:
    print("DPO 语料: 未下载,请运行 'dmla data' 下载 MiniMind Alignment 数据集")

# 检查 tokenizer(复用预训练的)
tokenizer_dir = os.path.join(DATA_DIR, 'datasets', 'minimind-pretrain')
tokenizer_json = os.path.join(tokenizer_dir, 'tokenizer.json')
print(f"Tokenizer: {'已存在' if os.path.exists(tokenizer_json) else '未找到'}")
点击 Run 按钮执行代码,点击代码区域可编辑

第一阶段:偏好对比数据集

DPO 的训练数据格式与 SFT 不同。SFT 的每条样本是一个指令回答对 (x,y)(x, y)(x,y),DPO 的每条样本是一个偏好对比三元组 (x,yw,yl)(x, y_w, y_l)(x,yw​,yl​),其中 xxx 是用户指令,ywy_wyw​ 是被选中的好回答(chosen),yly_lyl​ 是被拒绝的差回答(rejected)。chosen 和 rejected 对应同一个用户指令,只有 assistant 的回答不同。

数据以 JSONL 格式存储,每行一条偏好对:

{
  "chosen": [
    {"role": "user", "content": "什么是机器学习"},
    {"role": "assistant", "content": "机器学习是人工智能的一个分支,它使计算机能够从数据中学习规律..."}
  ],
  "rejected": [
    {"role": "user", "content": "什么是机器学习"},
    {"role": "assistant", "content": "机器学习就是让电脑自己学东西"}
  ]
}

下面代码实现了 DPODataset,将偏好对比数据转换为模型可训练的格式,每条样本包含 chosen 和 rejected 两条对话,分别 tokenize 后生成对应的输入序列和掩码。掩码的作用是只在 assistant 回答部分计算对数概率,用户提问部分不参与 DPO 损失计算。这段代码会在训练阶段被调用,无需手动运行。

import os
import torch
from torch.utils.data import Dataset
from datasets import load_dataset, Features, Value
from datasets import logging as datasets_logging

class DPODataset(Dataset):
    """
    DPO 数据集:将偏好对比数据 tokenize 为模型可训练的格式

    每条样本格式:{"chosen": [{role, content}, ...], "rejected": [{role, content}, ...]}
    输出 chosen 和 rejected 的 input_ids、目标 ids 和 loss_mask
    loss_mask 仅在 assistant 回答部分为 1,其余为 0
    """
    CHATML_TEMPLATE = (
        "{% for message in messages %}<|im_start|>{{ message.role }}\n"
        "{{ message.content }}<|im_end|>\n"
        "{% endfor %}"
        "{% if add_generation_prompt %}<|im_start|>assistant\n{% endif %}"
    )

    def __init__(self, jsonl_path, tokenizer, max_length=768):
        super().__init__()
        os.environ["TOKENIZERS_PARALLELISM"] = "false"
        self.tokenizer = tokenizer
        if not tokenizer.chat_template:
            tokenizer.chat_template = self.CHATML_TEMPLATE
        self.max_length = max_length
        self.padding = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0