查看全课大纲(第 28 / 40 节)
- 1.从规则到学习:深度学习解决什么问题
- 2.表示学习与端到端训练
- 3.张量、计算图与自动微分
- 4.训练三要素:数据、模型、目标函数
- 5.从感知机到多层感知机
- 6.反向传播:手推一个两层网络
- 7.激活函数与初始化
- 8.优化器:SGD → Momentum → Adam
- 9.正则化:权重衰减、Dropout、BN/LN
- 10.卷积的直觉与参数共享
- 11.池化、感受野与下采样
- 12.经典网络演进:LeNet → AlexNet → VGG → ResNet
- 13.残差连接与归一化为什么有效
- 14.迁移学习与特征可视化
- 15.RNN 与 BPTT
- 16.LSTM/GRU:门控如何缓解梯度问题
- 17.Seq2Seq 与编码器-解码器
- 18.注意力前史:Bahdanau 加性注意力
- 19.Self-Attention:QKV 与缩放点积
- 20.多头注意力:并行的多种关系
- 21.位置编码:正弦与可学习
- 22.残差、LayerNorm 与前馈层
- 23.从零实现一个最小 Transformer block
- 24.训练稳定性、复杂度与常见误区
- 25.自监督与语言建模目标
- 26.BERT:掩码语言模型与双向编码
- 27.GPT:自回归生成与缩放定律
- 28.指令微调与对齐:SFT → RLHF/DPO
- 29.参数高效微调:LoRA 与适配器
- 30.自编码器与 VAE
- 31.GAN:对抗训练与模式崩塌
- 32.扩散模型:前向加噪与反向去噪
- 33.扩散模型的应用与采样加速
- 34.对比学习与 CLIP
- 35.ViT:把图像当序列
- 36.多模态架构:对齐、融合与生成
- 37.能力边界、幻觉与评测
- 38.PyTorch 2.x 训练循环与混合精度
- 39.实验管理、评测与复现
- 40.推理部署与成本:量化/蒸馏概览
指令微调与对齐:SFT → RLHF/DPO
小象实战讲义 · 深度学习基础:从神经网络到 Transformer
预训练出来的模型只会“接龙”,不会“听话”。本节拆解让模型对齐人类意图的三段路:监督微调(SFT)、基于人类反馈的强化学习(RLHF),以及它的简化替代——直接偏好优化(DPO)。学完你将能回答:对齐到底在对什么?奖励模型在学什么?DPO 凭什么能省掉强化学习?
先修:自回归语言建模与缩放定律(本章 6.3 节)、交叉熵与 KL 散度。后续:下一节《参数高效微调:LoRA 与适配器》,之后进入第 7 章《生成模型》。
💡 核心导读
- 问题根源:大规模无监督语言模型(如 GPT-3)的训练目标(预测下一个词)与“遵循用户意图”的目标存在错位,导致模型可能生成不真实、有害或不遵循指令的内容。
- 经典解法:RLHF(Reinforcement Learning from Human Feedback)通过三步对齐模型:监督微调(SFT)建立基础、奖励模型(RM)学习人类偏好、强化学习(PPO)优化策略。
- 简化突破:DPO(Direct Preference Optimization)通过数学变换,将复杂的 RLHF 问题转化为一个简单的分类损失,无需训练独立的奖励模型或进行强化学习,大幅简化流程。
- 核心机制:理解 Bradley-Terry 偏好模型如何将人类对回答的排序转化为可优化的概率,以及 KL 散度约束如何防止优化过程偏离原始模型太远。
- 代码实践:我们将用极简代码模拟 SFT、奖励模型训练、PPO 优化步骤,并实现 DPO 的核心损失函数,直观感受其简洁性。
🧭 前置知识
- 交叉熵损失:用于分类任务的标准损失函数。
- softmax 函数:将 logits 转换为概率分布。
- KL 散度:衡量两个概率分布之间的差异。
- 逻辑回归与 Bradley-Terry 模型:理解如何用分数差异建模 pairwise 偏好概率(不展开数学推导,见站内数学基础课)。
- 策略梯度与 PPO(概念):知道强化学习可用于优化参数化策略,PPO 是一种稳定策略梯度算法(细节不展开)。
对齐问题:为什么语言模型需要“调教”?
大型语言模型(LLM)通过在互联网规模的文本上进行下一个词预测(语言建模)任务进行预训练。这个目标让模型掌握了丰富的语言知识和世界知识,但并未明确教导模型去遵循用户的特定指令或意图(Ouyang et al., 2022,arXiv:2203.02155)。
这导致了“对齐问题”:
- 不遵循指令:用户问“用 Python 写个快速排序”,模型可能开始解释快速排序的原理。
- 虚构事实:模型可能自信地编造不存在的信息(“幻觉”)。
- 生成有害内容:可能产生带有偏见、歧视或毒性的文本。
因此,我们需要一个“对齐”过程,使模型的行为与人类期望的“有帮助、诚实、无害”相一致(Askell et al., 2021,arXiv:2112.00861)。指令微调与对齐技术就是解决这一问题的关键。
经典路线:基于人类反馈的强化学习(RLHF)
RLHF 是 InstructGPT(ChatGPT 的技术基础)采用的方法。其流程分为三步,如下图所示(概念源自 Ouyang et al., 2022):
[人类标注者]
|
| 1. 撰写示范
v
[提示X] -> [期望回答Y]
|
| 监督学习
v
[SFT模型] (初始策略 π_SFT)
|
| 2. 采样并比较
v
[提示X] -> [回答A] vs [回答B]
| |
| | 人类标注偏好 (A ≻ B)
v v
[偏好数据集 D]
|
| 训练奖励模型
v
[奖励模型 r_φ(x, y)]
|
| 3. 强化学习优化
v
[PPO优化] -> [最终策略 π_RL]
(约束:不与 π_SFT 偏离太远)第一步:监督微调(SFT)
这套流程的效果有一个著名数字:InstructGPT 论文里,1.3B 的 PPO 模型在人类评测中比 175B 的 GPT-3 更受偏好——参数少 100 倍,回答却更受欢迎。它说明对齐信号的价值不在参数量,而在把“人类觉得好”这件事显式地喂给模型。下面看三步各自在做什么。
目标:收集人类标注者根据提示写出的高质量回答,用标准的监督学习(交叉熵损失)微调预训练模型,得到一个初步的指令跟随模型 π_SFT。
数据:提示 x 和对应的理想回答 y。 损失函数:语言建模损失。
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)
# 模拟一个极简的语言模型(仅用于示意流程)
class TinyLM(nn.Module):
def __init__(self, vocab_size=100, hidden_size=16):
super().__init__()
self.embed = nn.Embedding(vocab_size, hidden_size)
self.lm_head = nn.Linear(hidden_size, vocab_size)
def forward(self, input_ids):
# 简化:直接对每个token的embedding做线性投影
embeds = self.embed(input_ids) # (batch, seq, hidden)
logits = self.lm_head(embeds) # (batch, seq, vocab)
return logits
# 模拟SFT训练步骤(一个batch)
model_sft = TinyLM()
optimizer = torch.optim.Adam(model_sft.parameters(), lr=1e-4)
# 构造模拟数据:batch=2, seq_len=8
input_ids = torch.randint(0, 100, (2, 8)) # 提示+回答的token id
labels = torch.randint(0, 100, (2, 8)) # 下一个token的目标id,回答部分需要shift
# 前向传播
logits = model_sft(input_ids) # (2, 8, 100)
# 计算交叉熵损失(忽略padding等细节)
loss = F.cross_entropy(logits.view(-1, 100), labels.view(-1))
print(f"SFT步骤 - 损失值(示意): {loss.item():.4f}")
# 反向传播与优化(此处仅示意,不实际更新)
# optimizer.zero_grad()
# loss.backward()
# optimizer.step()SFT步骤 - 损失值(示意): 4.9099第二步:训练奖励模型(RM)
目标:学习一个反映人类偏好的奖励函数 r_φ(x, y)。
数据收集:用 π_SFT 对大量提示 x 生成多个回答(如 y_A, y_B),让标注者选出更好的那个,形成偏好对 (x, y_w, y_l),其中 y_w 优于 y_l。 建模偏好:常用 Bradley-Terry 模型 假设偏好概率与奖励差值的 sigmoid 函数相关: P(y_w ≻ y_l | x) = σ(r_φ(x, y_w) - r_φ(x, y_l)),其中 σ 是 sigmoid 函数。
损失函数:二元交叉熵损失。
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)
# 块自包含:重新定义极简语言模型(与上一块同结构)
class TinyLM(nn.Module):
def __init__(self, vocab_size=100, hidden_size=16):
super().__init__()
self.embed = nn.Embedding(vocab_size, hidden_size)
self.lm_head = nn.Linear(hidden_size, vocab_size)
def forward(self, input_ids):
embeds = self.embed(input_ids)
return self.lm_head(embeds)
# 奖励模型:通常基于SFT模型,在顶部加一个标量输出层
class RewardModel(nn.Module):
def __init__(self, base_lm):
super().__init__()
self.base_lm = base_lm
# 假设我们取最后一个token的隐藏状态作为序列表示
self.reward_head = nn.Linear(16, 1) # hidden_size=16 -> 1
def forward(self, input_ids):
# 获取序列表示(这里简化处理)
embeds = self.base_lm.embed(input_ids) # (batch, seq, hidden)
last_hidden = embeds[:, -1, :] # 取最后一个token (batch, hidden)
reward = self.reward_head(last_hidden).squeeze(-1) # (batch,)
return reward
# 模拟数据:一个偏好对 (x, y_w, y_l)
# 假设 prompt 长度为 4,回答长度为 4
prompt_ids = torch.randint(0, 100, (1, 4))
response_w_ids = torch.randint(0, 100, (1, 4)) # 优选回答
response_l_ids = torch.randint(0, 100, (1, 4)) # 劣选回答
# 拼接成完整序列 [prompt; response]
input_w = torch.cat([prompt_ids, response_w_ids], dim=1) # (1, 8)
input_l = torch.cat([prompt_ids, response_l_ids], dim=1) # (1, 8)
# 初始化模型
base_lm = TinyLM()
rm = RewardModel(base_lm)
# 计算奖励
reward_w = rm(input_w) # 标量
reward_l = rm(input_l) # 标量
# 计算奖励模型损失 (负对数似然)
# 根据 Bradley-Terry 模型:log σ(r_w - r_l)
loss_rm = -F.logsigmoid(reward_w - reward_l).mean()
print(f"奖励差值 (rw - rl): {(reward_w - reward_l).item():.4f}")
print(f"奖励模型损失: {loss_rm.item():.4f}")奖励差值 (rw - rl): 1.5127
奖励模型损失: 0.1991第三步:使用 PPO 进行强化学习优化
目标:微调 π_SFT(现在称为策略 π_θ),使其生成的回答能获得奖励模型 r_φ 给出的高奖励,同时防止策略变得与原始 π_SFT 相差太远(避免退化或遗忘通用能力)。
目标函数(简化): max_θ E_(x∼D, y∼π_θ(·|x)) [r_φ(x, y)] - β * KL(π_θ(y|x) || π_SFT(y|x)) 其中 β 是控制偏离程度的超参数,KL 散度作为约束。
这个 β 是 RLHF 里最敏感的一个旋钮:太小,模型会去钻奖励模型的空子(reward hacking,生成 RM 打高分但人类觉得莫名其妙的文本);太大,又几乎学不动。也正因如此,各家对齐配方里它的取值往往是最不肯公开的细节之一。
流程:使用近端策略优化(PPO)算法迭代地采样回答、计算奖励(来自 RM 和 KL 惩罚)、更新策略。
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)
# 块自包含:重新定义极简语言模型(与前面同结构)
class TinyLM(nn.Module):
def __init__(self, vocab_size=100, hidden_size=16):
super().__init__()
self.embed = nn.Embedding(vocab_size, hidden_size)
self.lm_head = nn.Linear(hidden_size, vocab_size)
def forward(self, input_ids):
embeds = self.embed(input_ids)
return self.lm_head(embeds)
# 为简化,我们直接模拟PPO中的一个核心概念:在奖励基础上增加KL惩罚
def compute_kl_penalty(logits_new, logits_ref):
"""计算 KL(π_θ ‖ π_ref)(每 token 平均,KL 不对称,方向不能反)"""
logp_new = F.log_softmax(logits_new, dim=-1)
logp_ref = F.log_softmax(logits_ref, dim=-1)
kl = (logp_new.exp() * (logp_new - logp_ref)).sum(dim=-1).mean()
return kl
# 初始化策略模型(待优化)和参考模型(固定的SFT模型)
policy_model = TinyLM()
ref_model = TinyLM()
# 模拟参考模型参数冻结(在实际中,ref_model就是SFT模型的副本)
for param in ref_model.parameters():
param.requires_grad = False
# 模拟一个提示
prompt_ids = torch.randint(0, 100, (1, 4))
# 策略模型生成回答(简化:直接前向传播,而非采样自回归)
# 假设我们"生成"了4个token的回答
generated_ids = torch.randint(0, 100, (1, 4))
input_full = torch.cat([prompt_ids, generated_ids], dim=1)
# 获取策略模型和参考模型对生成部分的logits
# 注意:这里为简化,我们用完整序列前向传播。实际PPO会更复杂,需要处理生成过程。
logits_policy = policy_model(input_full)[:, 4:, :] # 只取回答部分 (1, 4, vocab)
logits_ref = ref_model(input_full)[:, 4:, :]
# 模拟奖励模型给出奖励(使用之前定义的RewardModel,这里直接给个随机值示意)
reward_from_rm = torch.randn(1) * 0.5 + 2.0 # 假设正向奖励
# 计算KL惩罚
kl_penalty = compute_kl_penalty(logits_policy, logits_ref)
beta = 0.1 # KL惩罚系数
# PPO的总目标(要最大化,所以损失取其负)
total_reward = reward_from_rm - beta * kl_penalty
loss_ppo = -total_reward
print(f"模拟RM奖励: {reward_from_rm.item():.4f}")
print(f"KL散度惩罚: {kl_penalty.item():.4f}")
print(f"总奖励 (RM - β*KL): {total_reward.item():.4f}")
print(f"PPO损失 (负总奖励): {loss_ppo.item():.4f}")模拟RM奖励: 2.7433
KL散度惩罚: 0.4318
总奖励 (RM - β*KL): 2.7001
PPO损失 (负总奖励): -2.7001简化革命:直接偏好优化(DPO)
RLHF 流程复杂,需要训练额外的奖励模型,并依赖不稳定的强化学习训练。DPO(Rafailov et al., 2023,arXiv:2305.18290)提出了一种优雅的简化方案。
DPO 的核心洞察
DPO 的关键在于一个数学变换:将奖励函数 r(x, y) 用策略 π(y|x) 和参考策略 π_ref(y|x) 来表示。
对于带有 KL 约束的奖励最大化问题,其最优解具有如下形式: π*(y|x) ∝ π_ref(y|x) * exp(r(x, y) / β) 可以反解出奖励函数: r(x, y) = β * log(π*(y|x) / π_ref(y|x)) + constant
将这个表达式代入 Bradley-Terry 模型的偏好概率公式中,奖励函数 r 被消去,得到了一个只关于策略 π 的损失函数。
DPO 损失函数
给定偏好数据集 D = {(x, y_w, y_l)},DPO 直接优化策略模型 π_θ,其损失函数为:
L_DPO(π_θ; π_ref) = -E_(x,y_w,y_l) [ log σ( β * log (π_θ(y_w|x) / π_ref(y_w|x)) - β * log (π_θ(y_l|x) / π_ref(y_l|x)) ) ]
直观理解:损失函数鼓励模型增大优选回答 y_w 相对于参考策略的对数概率比值,同时减小劣选回答 y_l 的比值。β 参数同样控制着与参考策略的偏离程度。
DPO 实现
与 RLHF 相比,DPO 的实现异常简洁。
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)
def sequence_logp(logits, target_ids):
"""logπ(y|x) = Σ_t log_softmax(logits_t)[y_t]"""
log_probs = F.log_softmax(logits, dim=-1) # (B, 2, L, V)
picked = log_probs.gather(-1, target_ids.unsqueeze(-1)).squeeze(-1) # (B, 2, L)
return picked.sum(-1) # (B, 2)
def dpo_loss(policy_logps, ref_logps, beta=0.1):
"""
计算 DPO 损失。
policy_logps / ref_logps: 形状 (batch, 2),第 0 列是 logπ(y_w),第 1 列是 logπ(y_l)
"""
pi_logratios = policy_logps[:, 0] - policy_logps[:, 1] # log(π(y_w)/π(y_l))
ref_logratios = ref_logps[:, 0] - ref_logps[:, 1]
logits = beta * (pi_logratios - ref_logratios) # β × 奖励差值
return -F.logsigmoid(logits).mean() # -log σ(奖励差值)
# 玩具数据:2 个偏好对,每个回答 4 个 token,词表 8
batch_size, seq_len, vocab = 2, 4, 8
shape = (batch_size, 2, seq_len, vocab) # 第二维:0=y_w,1=y_l
policy_logits = torch.randn(shape)
ref_logits = torch.randn(shape)
target_ids = torch.randint(0, vocab, (batch_size, 2, seq_len))
policy_logps = sequence_logp(policy_logits, target_ids) # 对数概率,必 ≤ 0
ref_logps = sequence_logp(ref_logits, target_ids)
loss = dpo_loss(policy_logps, ref_logps, beta=0.1)
print("策略模型的对数概率 logπ(y)(log_softmax 求和,必 ≤0):")
for i in range(batch_size):
print(f" 样本{i}: logπ(y_w)={policy_logps[i,0]:.4f}, logπ(y_l)={policy_logps[i,1]:.4f}")
print(f"DPO 损失值: {loss.item():.4f}")策略模型的对数概率 logπ(y)(log_softmax 求和,必 ≤0):
样本0: logπ(y_w)=-10.1085, logπ(y_l)=-9.4395
样本1: logπ(y_w)=-10.1951, logπ(y_l)=-13.2401
DPO 损失值: 0.7283注意上面对数概率是用 log_softmax 后在目标 token 上求和得到的,因此全部为负值——这正是概率与对数概率的区别,也是后面练习要检查的点。
DPO 的优势:
- 简单:只需一个损失函数,像监督学习一样训练。
- 稳定:避免了 RL 训练中的不稳定性和高方差。
- 高效:无需在训练循环中从模型采样,也无需维护额外的奖励模型。
它不是银弹:DPO 只用一份离线偏好对,模型一旦偏离,数据分布就跟当前策略对不上了(off-policy 问题);需要探索、或奖励信号要在线更新的场景,PPO 派系仍有优势。工程上的常见折中是:先用 DPO 快速拿到对齐基线,再用少量在线反馈做迭代。
对比与总结:RLHF vs. DPO
| 方面 | RLHF (PPO) | DPO |
|---|---|---|
| 流程复杂度 | 高。三步 pipeline,需训练 RM 和运行 PPO。 | 低。单阶段训练,类似监督学习。 |
| 训练稳定性 | 较低。PPO 对超参数敏感,训练可能不稳定。 | 高。使用标准的梯度下降,更稳定。 |
| 计算开销 | 高。需要多次前向/后向传播(策略、价值网络、RM),并在训练中采样。 | 低。只需计算策略和参考模型的对数概率。 |
| 核心约束 | 通过 KL 散度项在目标函数中显式约束。 | 通过参考策略 π_ref 隐含在损失函数中。 |
| 实践效果 | 已被 ChatGPT/InstructGPT 验证有效。 | 在多项任务上匹配或超越 PPO,成为当前主流简化方案。 |
📝 动手练一练
题目:假设我们有一个极简的“语言模型”,它输出一个 3 维词表上的 logits。对于同一个提示 x,模型对两个候选回答 y_w(token序列 [0, 1])和 y_l(token序列 [2, 1])的 logits 如下(batch=1):
- 策略模型
π_θ的 logits: 对于y_w为[[1.0, 0.5, -0.5], [0.2, 0.8, 0.0]],对于y_l为[[-0.5, 0.0, 1.0], [0.2, 0.8, 0.0]]。 - 参考模型
π_ref的 logits 与策略模型初始相同(即π_ref是初始策略的副本)。
请手动计算(或写代码验证)该样本的 DPO 损失(β=0.5)。提示:先对每一步的 logits 做 log_softmax,取出目标 token 位置的对数概率,再沿序列求和得到 。
👉 点击查看参考答案
思路:
- 先用
log_softmax把 logits 变成对数概率,再取出目标 token 对应的值并逐 token 相加,得到整段回答的 。不能直接把原始 logits 相加——那不是概率的对数。 - 代入 DPO:,损失 。
- 初始时策略与参考模型完全相同,括号内必为 0,损失正好是 ——这就是训练的起点。再模拟”训练一步后”(把 首 token 的 logit 调高、 首 token 的 logit 调低),看损失怎么往下走。
import torch
import torch.nn.functional as F
beta = 0.5
logits_pi_w = torch.tensor([[1.0, 0.5, -0.5], [0.2, 0.8, 0.0]])
logits_pi_l = torch.tensor([[-0.5, 0.0, 1.0], [0.2, 0.8, 0.0]])
logits_ref_w = logits_pi_w.clone()
logits_ref_l = logits_pi_l.clone()
y_w_ids = torch.tensor([0, 1])
y_l_ids = torch.tensor([2, 1])
def seq_logprob(logits, ids):
logp = F.log_softmax(logits, dim=-1)
return logp[torch.arange(len(ids)), ids].sum()
def dpo_loss(pi_w, pi_l, ref_w, ref_l, beta):
return -F.logsigmoid(beta * ((pi_w - pi_l) - (ref_w - ref_l)))
pi_w, pi_l = seq_logprob(logits_pi_w, y_w_ids), seq_logprob(logits_pi_l, y_l_ids)
ref_w, ref_l = seq_logprob(logits_ref_w, y_w_ids), seq_logprob(logits_ref_l, y_l_ids)
print(f"初始: logπ_w={pi_w:.4f}, logπ_l={pi_l:.4f}")
print(f"初始: logπ_ref_w={ref_w:.4f}, logπ_ref_l={ref_l:.4f}")
print(f"初始 DPO 损失: {dpo_loss(pi_w, pi_l, ref_w, ref_l, beta):.4f}")
# 模拟训练一步后:y_w 首 token 的 logit 调高,y_l 首 token 的 logit 调低
logits_pi_w2 = logits_pi_w.clone(); logits_pi_w2[0, 0] = 3.0
logits_pi_l2 = logits_pi_l.clone(); logits_pi_l2[0, 2] = -1.0
pi_w2 = seq_logprob(logits_pi_w2, y_w_ids)
pi_l2 = seq_logprob(logits_pi_l2, y_l_ids)
print(f"\n一步之后: logπ_w={pi_w2:.4f}, logπ_l={pi_l2:.4f}")
print(f"一步之后 DPO 损失: {dpo_loss(pi_w2, pi_l2, ref_w, ref_l, beta):.4f}")初始: logπ_w=-1.2963, logπ_l=-1.1566
初始: logπ_ref_w=-1.2963, logπ_ref_l=-1.1566
初始 DPO 损失: 0.6931
一步之后: logπ_w=-0.7986, logπ_l=-2.3725
一步之后 DPO 损失: 0.3538分析:初始时策略与参考模型完全相同,括号里的差值必为 0,损失落在 ——这就是 DPO 训练的起点。把 首 token 的 logit 从 1.0 抬到 3.0、 首 token 的 logit 从 1.0 压到 −1.0 之后, 上升、 下降,差值变大,损失从 0.6931 降到 0.3538。这正是 DPO 在做的事:不用奖励模型,直接沿着“优选回答概率升、劣选回答概率降”的方向走。
常见错误
| 错误理解或写法 | 现象或报错 | 原因 | 正确做法 |
|---|---|---|---|
在 DPO 训练中忘记冻结参考模型 π_ref 的参数。 | 参考模型与策略模型一起更新,导致 KL 约束失效,模型可能快速退化。 | π_ref 必须保持固定,代表对齐前的初始策略。 | 初始化 ref_model 后,立即设置 ref_model.eval() 并遍历参数设置 param.requires_grad = False。 |
| 直接使用模型输出的原始 logits 计算对数概率,未经过 log_softmax。 | DPO 损失计算的值域错误,可能导致梯度爆炸或训练不稳定。 | π(y∣x) 是概率分布,其对数概率应为 log_softmax(logits) 后在目标 token 上的和。 | 使用 F.log_softmax(logits, dim=-1) 获取对数概率,再通过 gather 或索引取出目标 token 的值并求和。 |
| 在 RLHF 的奖励模型中,对长度差异巨大的回答直接比较原始奖励值。 | 模型可能倾向于生成冗长的回答以获得更高的累积奖励,而非真正优质的回答。 | 奖励值可能随序列长度增加而累积增加,存在长度偏差。 | 对奖励进行长度归一化(如除以序列长度),或使用在提示后特定位置的奖励(如 EOS token 的奖励)。 |
将偏好数据集中的 y_w 和 y_l 视为绝对好/坏,忽略其可能来自同一模型不同采样。 | 模型难以学习到细微的质量差别,收敛慢或效果不佳。 | 实际标注中,y_w 和 y_l 可能质量接近,只是相对偏好。 | 理解偏好数据的相对性,在损失函数中可使用 margin 或更复杂的排序损失。 |
本章小结
- 语言模型预训练目标与用户意图存在错位,指令微调与对齐是使其“有用、诚实、无害”的关键。
- RLHF 是经典的解决方案,包含 SFT、奖励模型训练、PPO 强化学习优化三个步骤,效果显著但流程复杂。
- DPO 通过数学变换,将 RLHF 问题转化为一个简单的二元分类损失,无需奖励模型和强化学习,大大简化了对齐流程。
- KL 散度约束 在对齐中至关重要,防止模型在优化偏好时过度偏离其原始知识分布。
- 无论是 RLHF 还是 DPO,其基础都是 Bradley-Terry 等偏好模型,将人类对回答的相对排序转化为可优化的概率目标。
- 对齐技术仍在快速发展,但其核心思想——利用人类反馈信号来指导模型行为——是通往更安全、更可控 AI 系统的基石。
📋 行动清单
- 尝试修改 DPO 代码示例中的
beta参数(例如设为 0.01 和 1.0),重新运行,观察损失值的变化,理解beta对模型偏离参考策略约束强度的影响。 - 在 RLHF 模拟代码中,将 KL 惩罚系数
beta设为 0,模拟没有约束的情况,思考这可能导致什么问题。 - 查阅
F.kl_div函数的文档,理解其reduction和log_target参数的含义,确保在计算 KL 散度时使用正确。
延伸阅读
- Training language models to follow instructions with human feedback(Ouyang et al., 2022,arXiv:2203.02155)—— RLHF 的奠基性论文,详细描述了 InstructGPT 的训练流程与实验。
- Direct Preference Optimization: Your Language Model is Secretly a Reward Model(Rafailov et al., 2023,arXiv:2305.18290)—— DPO 的原论文,包含了完整的理论推导与实验对比。
—— 小象教研组