趋近智
直接偏好优化(DPO)为多阶段RLHF流程提供了一个很好的替代方案。DPO不训练单独的奖励模型,而是直接使用偏好数据优化语言模型策略。这里提供了一份实践指南,帮助您理解和实现DPO损失函数,这是此方法的主要组成部分。
回想一下,DPO旨在提高给定提示(x)下首选回复(yw)相对于拒绝回复(yl)的相对对数概率。它通过基于策略模型(πθ)的概率与参考模型(πref)的概率对于给定补全的比率,隐式定义奖励函数来实现这一点。由此公式推导出的目标函数是:
LDPO(πθ;πref)=−E(x,yw,yl)∼D[logσ(βlogπref(yw∣x)πθ(yw∣x)−βlogπref(yl∣x)πθ(yl∣x))]让我们分析各项:
logσ 函数内部的项可以改写为:
β((logπθ(yw∣x)−logπref(yw∣x))−(logπθ(yl∣x)−logπref(yl∣x)))这表明我们正在最大化首选回复与拒绝回复的对数概率比之间的差异,并按β进行缩放。
要在PyTorch或TensorFlow等常见深度学习框架中实现此损失,您需要获取在训练中的策略模型(πθ)和冻结的参考模型(πref)下,首选(yw)和拒绝(yl)序列的对数概率。
以下是计算一批偏好数据损失的步骤:
获取对数概率:对提示(x)以及首选(yw)和拒绝(yl)补全,分别通过策略模型和参考模型执行前向传播。这会为批次中的每个样本生成四组对数概率:
policy_chosen_logps: logπθ(yw∣x)policy_rejected_logps: logπθ(yl∣x)ref_chosen_logps: logπref(yw∣x)ref_rejected_logps: logπref(yl∣x)
请记住,参考模型πref在训练期间不更新;其参数保持固定,并且不为其计算梯度。策略模型πθ是其参数正在被优化的模型。计算对数比:计算首选和拒绝回复相对于参考模型的对数概率比:
log_ratio_w = policy_chosen_logps - ref_chosen_logpslog_ratio_l = policy_rejected_logps - ref_rejected_logps计算差异:找到这些对数比之间的差异并按β缩放:
diff = beta * (log_ratio_w - log_ratio_l)应用逻辑损失:计算差异的负对数Sigmoid。这是每个样本的核心DPO损失。使用 logsigmoid 等标准库函数有助于保持数值稳定性。
loss_per_sample = -torch.nn.functional.logsigmoid(diff) (在PyTorch中) 或等效函数。请注意,−logσ(z) 等同于 softplus(−z)。对损失求平均值:计算批次中 loss_per_sample 的平均值,以获得训练步骤的最终损失值。
下面是一个使用PyTorch的Python函数,它说明了DPO损失的计算,假设您已经计算出所需的对数概率。
import torch
import torch.nn.functional as F
def compute_dpo_loss(policy_chosen_logps: torch.Tensor,
policy_rejected_logps: torch.Tensor,
ref_chosen_logps: torch.Tensor,
ref_rejected_logps: torch.Tensor,
beta: float) -> torch.Tensor:
"""
计算直接偏好优化(DPO)损失。
参数:
policy_chosen_logps: 策略模型下首选回复的对数概率
。形状: (batch_size,)
policy_rejected_logps: 策略模型下拒绝回复的对数概率
。形状: (batch_size,)
ref_chosen_logps: 参考模型下首选回复的对数概率
。形状: (batch_size,)
ref_rejected_logps: 参考模型下拒绝回复的对数概率
。形状: (batch_size,)
beta: 控制偏离参考模型的温度参数。
返回:
批次上的平均DPO损失。
"""
# 计算首选和拒绝回复的对数概率比
# pi_logratios = policy_chosen_logps - policy_rejected_logps # 不直接按此公式使用
# ref_logratios = ref_chosen_logps - ref_rejected_logps # 不直接按此公式使用
# 计算相对于基础模型 (pi_ref) 的对数比
log_ratio_chosen = policy_chosen_logps - ref_chosen_logps
log_ratio_rejected = policy_rejected_logps - ref_rejected_logps
# 计算差异,并按beta缩放
# 该项表示 beta * (reward_chosen - reward_rejected)
# 其中奖励隐式定义为 log(pi_policy / pi_ref)
diff = beta * (log_ratio_chosen - log_ratio_rejected)
# 使用负对数Sigmoid函数计算损失
# loss = -log(sigmoid(diff)) = softplus(-diff)
# 使用log_sigmoid以提高数值稳定性: log_sigmoid(x) = -softplus(-x)
# 因此,损失 = -log_sigmoid(diff)
loss = -F.logsigmoid(diff)
# 对批次中的损失求平均
average_loss = loss.mean()
return average_loss
# --- 用法 ---
# 假设这些张量来自模型的正向传播
# (例如,使用 model.forward(input_ids, labels=labels).logits)
# 通常,对数概率是为每个回复在序列长度上求和的。
batch_size = 8
# 示例对数概率(请确保在实际中正确计算)
policy_chosen_logps = torch.tensor([-10.5, -12.1, -9.8, -11.0, -13.5, -10.1, -11.8, -12.5], requires_grad=True)
policy_rejected_logps = torch.tensor([-11.2, -11.9, -10.5, -11.5, -12.8, -10.9, -12.3, -13.0], requires_grad=True)
ref_chosen_logps = torch.tensor([-10.2, -11.8, -9.5, -10.7, -13.0, -9.8, -11.5, -12.1]) # 不需要梯度
ref_rejected_logps = torch.tensor([-11.0, -11.5, -10.1, -11.1, -12.2, -10.5, -11.9, -12.5]) # 不需要梯度
beta_value = 0.1
# 计算DPO损失
dpo_loss_value = compute_dpo_loss(policy_chosen_logps, policy_rejected_logps,
ref_chosen_logps, ref_rejected_logps, beta_value)
print(f"计算得到的DPO损失: {dpo_loss_value.item():.4f}")
# --- 在训练循环中 ---
# optimizer.zero_grad()
# dpo_loss_value.backward() # 计算策略模型参数的梯度
# optimizer.step()
ref_model.eval()),以便在计算对数概率的前向传播期间禁用dropout或其他训练特定行为。transformers等框架通常提供获取序列似然的方法。F.logsigmoid等库函数可以防止直接计算log(1 / (1 + exp(-diff)))可能导致的潜在下溢/上溢问题。考虑修改一个标准语言模型微调脚本(例如使用Hugging Face transformers的脚本)。修改训练循环以整合DPO损失计算。您将需要:
policy_model),一个用于参考(ref_model)。确保ref_model被冻结。compute_dpo_loss函数(或类似函数)集成到您的训练步骤中。Hugging Face的trl等库提供了预构建的DPOTrainer类,这些类抽象化了大部分复杂性,但自己实现核心损失能让您更透彻地理解其内部工作原理。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造