读完这篇,你能做什么
计算极端分数下的稳定偏好损失;在 CPU 训练中观察损失下降、四组训练偏好排序正确,并说明这不能证明在新数据上也有效。
学习理由、准备要求与知识点
为什么学它
奖励模型只是在近似标注中的偏好。要看清哪些分数关系能由数据确定、标注覆盖了什么,不能把模型高分直接当成真实质量。
先读这些内容
知识点清单
- 偏好对与同提示比较
- Bradley-Terry概率
- 成对交叉熵
- 分数平移不变性
- 标注一致性与平局
- 提示级数据划分
- 奖励过优化
- 独立人工审计
运行前先确认论文机制按文中版本说明;Java 17 完整算法,Python 3.14 与 PyTorch 2.9.1+cpu 的小型 CPU 实验。未执行 GPU、多机或大规模模型训练。
01先弄懂它是什么
想训练模型判断“哪份回答更好”,首先要弄清这个“好”是按什么标准。奖励模型把提示 x 和回答 y 转成一个分数 r(x,y),但分数本身没有“正确率百分比”的单位。例如标注者在同题的 A、B 两份回答里更喜欢 A,我们知道的是 A 优于 B,并不知道 A 就该得九十分。因此要一起保存题目、两份原始回答、比较标准和标注结果,才能解释分数来自哪里。
常见方法用两份回答的分数差来预测偏好:P(A优于B|x)=sigmoid(rA-rB),其中 sigmoid(z)=1/(1+exp(-z))。一批有 B 组比较时,两组分数的形状都是 [B],相减后也一样。分数差为零,预测偏好概率就是二分之一;差为二时约为 0.881。这个概率说的是模型认为哪份更受偏好,不能直接证明回答内容真实或没有风险。
偏好来自具体的人、任务和准则。有人偏爱简短,有人需要完整推导;把这些差异混在一起并丢掉来源,模型可能学习到平均风格而非业务目标。训练前先规定事实、帮助程度和格式等评价维度,允许平局、无法判断与争议复核。严格二选一损失只是其中一种数据约定,不能把不确定标签伪造成强偏好。
文中这几个词,先认一认
- 偏好对
- 同一个提示或条件下的两份候选回答,以及哪份更好的比较标签。
- 奖励分数
- 模型学出来的一个数,用来比较回答的相对偏好;数值本身没有固定的百分制含义。
- 可辨识性
- 现有的观察或标签,能不能唯一确定想估计的参数或分数尺度。例如只知道谁更好,未必能确定绝对分数。
02看看它怎么工作
对明确偏好A的样本,负对数似然为L=-log(sigmoid(d))=softplus(-d),d=rA-rB。最小化损失会增加d;对d的导数是sigmoid(d)-1,负值会推动优选分数上升、拒选分数下降。无需先计算一个可能下溢为零的概率再取对数,稳定写法是max(-d,0)+log1p(exp(-abs(d)))。当d=-1000时损失约1000而非无穷大。
如果给两份回答的分数同时加上 c,它们的差值和成对偏好概率都不变。所以只靠“谁更好”的比较,确定不了绝对零点,加一个公共偏置也不会让模型更会区分回答。把分数差拉得很大,虽可能继续压低训练损失,却可能让新类型数据上的判断更差。评估时应看保留提示上的排序、置信度和错误类型,必要时加入正则化与独立校准。
CPU实验用两个数表示候选特征,线性层把[B,2]映射成[B,1],再压成[B]计算差值。四组人工构造的偏好是可分的,实验只要求训练损失从log(2)下降并全部排对,不声称学会评价自然语言。真实数据应按提示或来源家族划分,同一提示的多个回答不能分散到训练与测试,否则评估很可能只是记住题目。
先跑通一个最小例子
保存为experiment.py,以已安装PyTorch 2.9.1+cpu的解释器执行 python experiment.py。实验只训练二维线性奖励头;不下载模型或数据,也不执行GPU训练。
import torch
import torch.nn.functional as F
torch.manual_seed(7)
torch.set_num_threads(1)
chosen = torch.tensor([[2., 0.], [3., 1.], [1., 0.], [2., 1.]], dtype=torch.float64)
rejected = torch.tensor([[1., 1.], [1., 2.], [0., 2.], [0., 3.]], dtype=torch.float64)
head = torch.nn.Linear(2, 1, bias=False, dtype=torch.float64)
with torch.no_grad():
head.weight.zero_() # 从相同分数开始,初始损失应为 log(2)。
optimizer = torch.optim.SGD(head.parameters(), lr=0.1)
def objective():
difference = head(chosen).squeeze(-1) - head(rejected).squeeze(-1)
return F.softplus(-difference).mean()
initial = objective().item()
assert abs(initial - 0.6931471805599453) < 1e-12
for _ in range(120):
optimizer.zero_grad()
loss = objective()
loss.backward()
optimizer.step() # 真实更新一个小型线性奖励头,不是语言模型训练。
final = objective().item()
assert final < 0.1 and final < initial
assert torch.all(head(chosen) > head(rejected))
scores = torch.tensor([[-1000., 1000.], [2., 1.]], dtype=torch.float64)
losses = F.softplus(-(scores[:, 0] - scores[:, 1]))
assert torch.isfinite(losses).all() # 极端分数不会令指数计算溢出。
shifted = scores + 17
assert torch.allclose(losses, F.softplus(-(shifted[:, 0] - shifted[:, 1])))
assert torch.allclose(F.softplus(torch.zeros(2)), torch.full((2,), 0.6931471805599453))
print(f"initial={initial:.6f} final={final:.6f} ranking=4/4")
print("PASS 6 checks; CPU linear reward head only")
核对运行结果初始损失约0.693147,最终小于0.1,ranking=4/4,并输出PASS 6 checks;训练集拟合不等于泛化。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
N0["确定比较准则与平局策略"]
N1["按提示家族划分偏好对"]
N2["输出优选与拒选分数"]
N3["以稳定成对损失更新"]
N4["在保留提示与审计样本评价"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4按顺序看,每一步做了什么
- 确定比较准则与平局策略
- 按提示家族划分偏好对
- 输出优选与拒选分数
- 以稳定成对损失更新
- 在保留提示与审计样本评价
03对照实现代码
PyTorch v2.9.1;Christiano等2017论文v4 · torch.nn.functional.logsigmoid / softplus;论文偏好概率与交叉熵
原创成对损失片段,调用固定版本PyTorch真实算子;不是原样框架源码,不包含语言模型与人工标注平台。
import torch.nn.functional as F
def reward_pair_loss(chosen_score, rejected_score):
delta = chosen_score - rejected_score # 同一提示的一对分数,形状均为[B]。
per_pair = -F.logsigmoid(delta) # 稳定计算负对数概率,不先做sigmoid再log。
return per_pair.mean() # 此处每对等权;标注置信度加权需另定契约。
真实functional.py把logsigmoid绑定到torch._C._nn.log_sigmoid,Python层提供接口与文档,底层数值实现并不是这三行教学函数。教学函数调用真正的PyTorch算子构造成对损失,自动微分再沿分数回传到奖励网络。不要把Java中的标量稳定公式称为PyTorch训练内核。
Christiano等人的原研究比较的是轨迹片段,片段分数由预测奖励累计得到,再以两个累计分数构成偏好概率。语言回答的成对评分沿用相同的比较思想,但输入结构、长度和标注任务不同。本篇用回答级标量说明目标,没有复制论文完整环境、集成模型或主动查询流程。
mean是学习目标的一部分而非无关语法。如果一个提示有十对回答、另一个只有一对,逐对平均会让前者具有更高权重。可以按提示分组平均或使用明确权重,但必须同步改变采样和评测说明;把所有配对展开后宣称每个提示贡献相同是不准确的。
数据张量不应包含人工标签之外的未来评价结果,优选和拒选的截断规则也要一致。若一个回答的关键信息被截掉,训练仍会按原标签更新,产生错误监督。沿真实调用链检查分词、掩码、序列聚合、奖励头和损失,比只确认某个函数名字存在更能发现问题。
代码先调用谁,再调用谁
- 构造同条件偏好对
- 奖励网络分别输出标量
- 计算分数差与logsigmoid
- 按约定权重归约
- 反向传播并更新可训练参数
04动手写一个例子
数值稳定的加权偏好损失
给定有限分数差和非负权重,计算成对负对数似然的加权平均;无正权重或总权重溢出拒绝。
- 输入 1
差值0,权重1- 输出 1
log(2)
- 输入 2
差值-1000,权重1- 输出 2
约1000
直接写-log(1/(1+exp(-d)))会在很负的d处溢出。先利用softplus恒等式把指数参数限制为非正,最大损失部分单独保留,就能处理正负一千的输入。该变换保持同一数学目标,没有把难样本裁掉或改变标签。
权重平均也有数值问题:先累计w×loss可能溢出,即使最后均值仍有限。代码维护已有总权重total和均值mean,新样本用w/(total+w)调整均值。更新前检查新增总权重有限,零权重样本不参与平均但仍检查输入,避免无效数值隐藏在数据中。
归纳地看,处理前i项后mean等于这些正权重项的加权损失。加入第i+1项时,新权重占比乘上“新损失减旧均值”,恰好把旧平均改成扩大集合的平均。浮点运算仍有舍入误差,所以自测使用容差;算法不提供任意实数精确算术保证。
为什么这样写是对的已处理正权重样本的total是权重和,mean是其加权损失均值。
每对只计算常数次标量运算,不保留全部损失。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
static void near(double expected,double actual) {
if (!Double.isFinite(actual) || Math.abs(expected-actual)>1e-9*Math.max(1,Math.abs(expected)))
throw new AssertionError(expected+" != "+actual);
}
static void check(boolean ok) { if(!ok) throw new AssertionError(); }
static void bad(Runnable r) {
try { r.run(); } catch(IllegalArgumentException e) { return; }
throw new AssertionError("invalid input accepted");
}
static double pairLoss(double[] margin,double[] weight) {
if(margin==null || weight==null || margin.length!=weight.length)
throw new IllegalArgumentException("shape");
double total=0,mean=0;
for(int i=0;i<margin.length;i++) {
double d=margin[i],w=weight[i];
if(!Double.isFinite(d)||!Double.isFinite(w)||w<0)
throw new IllegalArgumentException("nonfinite or negative");
if(w==0) continue; // 零权重不参与归一化,但输入仍然检查。
double next=total+w;
if(!Double.isFinite(next)) throw new IllegalArgumentException("weight overflow");
double loss=Math.max(-d,0)+Math.log1p(Math.exp(-Math.abs(d)));
// 指数的参数非正,避免直接计算 exp(1000) 溢出。
mean += (w/next)*(loss-mean); // 在线加权均值,避免大损失乘权重。
total=next;
}
if(total==0) throw new IllegalArgumentException("no positive weight");
return mean;
}
public static void main(String[] args) {
near(Math.log(2),pairLoss(new double[]{0},new double[]{1}));
near(1.1269280110429725,pairLoss(new double[]{2,-2},new double[]{1,1}));
near(0,pairLoss(new double[]{1000},new double[]{1}));
near(1000,pairLoss(new double[]{-1000},new double[]{1}));
near(1000,pairLoss(new double[]{1000,-1000},new double[]{0,1}));
bad(()->pairLoss(new double[]{},new double[]{}));
bad(()->pairLoss(new double[]{1},new double[]{-1}));
bad(()->pairLoss(new double[]{Double.NaN},new double[]{1}));
System.out.println("PASS 8 cases");
}
}
这些特殊情况也要试一试
- 空输入或全零权重拒绝
- 极端正负差值仍应有限
- 负权重与
NaN拒绝 - 权重和溢出明确失败
进阶选读 · 带平局和标注者差异的偏好模型
场景不同标注者对相同候选给出冲突结果。
做法保存软标签与标注者分组,对称平局可使用两方向交叉熵;分别评估组内一致性和跨群体泛化,不靠删除全部争议样本美化结果。
复杂度每轮N对、特征维度D的线性头计算O(ND),标注与神经编码成本另外计算。
好处与代价更丰富标签减少信息损失,但引入建模假设和校准需求。
05项目里怎么用
奖励模型的保留集应覆盖真实任务类型、长度与语言分布。用总体准确率可能掩盖长回答偏好、格式捷径或少数类别失效;还要检查分数差与人工信心是否匹配。用于选择候选的模型和最终审计者应尽量独立,不能让同一错误准则同时负责优化与证明成功。
当策略反复优化某个奖励模型时,输入分布会逐步移动到它没见过的区域。模型可能学会提高奖励却降低真实质量,这称为奖励过优化。应限制更新预算,定期收集当前策略的新样本做人工审计,并保留不可由奖励分数抵消的业务约束,例如引用存在性与权限校验。
建立数据版本、标注指南和争议记录,使一次回归能够追到具体偏好变化。模型权重、训练种子、提示模板和截断配置一同保存。小实验的四对全部正确只是程序和优化方向的证据,研究报告仍需独立测试集及置信区间,不能把这个数值写成自然语言评价能力。
容易踩的坑
- 把奖励分数当成事实正确率
- 同提示候选泄漏到测试集
- 强行将平局改成严格偏好
- 忽略回答长度与截断偏差
- 只优化奖励而不做独立审计
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 成对偏好 | 标注者通常较易比较候选 | 需要大量配对并处理冲突 | 相对质量优化 |
| 绝对打分 | 可表达多个维度 | 标尺在标注者间容易漂移 | 明确评分规范的任务 |
| 规则校验加偏好 | 把硬条件与软质量分开 | 规则覆盖不完整 | 有客观格式或执行约束的系统 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01两个奖励都加10会怎样?
展开参考答案与推导
概率与成对损失不变,因为仅分数差进入目标。
02奖励高代表事实正确吗?
展开参考答案与推导
不代表;它表示对标注分布的相对偏好预测。
03为何不用先sigmoid再log?
展开参考答案与推导
极端差值可能导致概率下溢,logsigmoid或softplus保持稳定。
换个情况,还会处理吗
01训练排序很好、线上偏爱冗长怎么办?
展开参考答案与推导
按长度分桶审计,检查标注准则、截断和长度捷径,再加入对照候选。
02多人意见不一致是否删样本?
展开参考答案与推导
先区分标注错误与真实偏好分歧,保留来源并允许软标签或复核。
03策略奖励上升、人工评价下降怎么办?
展开参考答案与推导
暂停扩大优化,审计分布偏移与漏洞,收集当前策略样本并用独立标准复评。
写代码试一试
01实现稳定单对损失。
- 输入 / 条件
d=-1000- 输出 / 预期
约1000
提示 避免正大数进入exp
展开参考答案与推导
按正文softplus恒等式计算并检查有限性。
02验证分数平移不变。
- 输入 / 条件
(3,1)与(103,101)- 输出 / 预期
两者损失相等
提示 先求差
展开参考答案与推导
构造同差值,容差比较两份结果。
03加入零权重样本。
- 输入 / 条件
差值[1000,-1000],权重[0,1]- 输出 / 预期
1000
提示 归一化只计正权重
展开参考答案与推导
跳过零权重贡献,全部为零则拒绝。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
计算极端分数下的稳定偏好损失;在 CPU 训练中观察损失下降、四组训练偏好排序正确,并说明这不能证明在新数据上也有效。
本篇重点加练
评分的可识别性与提示级隔离共同限制泛化声明,重点考查模型是否学到比较关系。
- 分数平移不变性
- 提示级数据划分
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01代码推演
同提示下三个回答的评分为 [-1,0.5,2]。选取三对偏好标签,逐对写 Bradley–Terry 概率和损失定义。
- 提交内容
- 成对计算表
- 怎样算完成
- 同提示比较前提明确
- 标签方向一致
练到的知识点偏好对与同提示比较 · Bradley-Terry概率 · 成对交叉熵
- 作业 02原理问答
设计包含平局、标注冲突和置信度的偏好记录格式;说明分数绝对零点与标注者一致性各自能否由成对数据识别。
- 提交内容
- 数据契约与可识别性说明
- 怎样算完成
- 不确定标签保留证据
- 评分与事实正确性分开
练到的知识点分数平移不变性 · 标注一致性与平局
- 作业 03原理问答
只有同提示成对偏好时,分别给每个提示所有回答加一个不同常数,分析哪些观测仍能确定、哪些跨提示比较需要额外假设。
- 提交内容
- 可识别量与不可识别量表
- 怎样算完成
- 变换作用范围明确
- 组内和跨组比较分开
练到的知识点分数平移不变性
综合应用
- 作业 04编程实践
用 CPU 训练一个线性偏好评分器,按提示 ID 划分数据;报告逐提示排序准确率、损失及保留集结果。
- 提交内容
- 脚本、切分清单与报告
- 怎样算完成
- 提示不跨集合泄漏
- 预测和标签可逐对复核
练到的知识点Bradley-Terry概率 · 成对交叉熵 · 提示级数据划分
- 作业 05排错实验
构造答案长度与偏好标签强相关的训练集,再做相关性被打破的保留集;观察奖励模型的变化并记录失败类型。
- 提交内容
- 合成数据与受控实验
- 怎样算完成
- 偏差因素可控制
- 保留集不参与调参
练到的知识点标注一致性与平局 · 奖励过优化
- 作业 06面试场景
策略训练分数持续升高,但独立人工评估不变。设计奖励过优化审计,规定抽样、盲评和异常样本归档方式。
- 提交内容
- 审计协议与记录表
- 怎样算完成
- 评价器与奖励模型独立
- 抽样不只挑有利样本
练到的知识点奖励过优化 · 独立人工审计
- 作业 07编程实践
在 CPU 小评分器上为每个提示加入不同的常量分数偏移,核对组内损失及跨提示排序;评价集按未见提示划分。
- 提交内容
- 评分变换实验
- 怎样算完成
- 提示隔离真实执行
- 损失不变与泛化质量分开
练到的知识点分数平移不变性 · 提示级数据划分
- 作业 08设计评审
同一提示的多种改写使用不同 ID。设计提示家族划分规则与人工复核样本,明确训练/验证/测试的来源身份。
- 提交内容
- 提示家族映射与审计表
- 怎样算完成
- 字面 ID 不代替独立性
- 规则不使用保留集结果调优
练到的知识点提示级数据划分
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
制定跨标注人偏好泛化实验:一部分标注人仅出现在保留集;比较分歧强度下的表现并声明结论针对的人群。
- 提交内容
- 实验设计与分析计划
- 怎样算完成
- 标注人身份隔离
- 分歧不被一概视作噪声
练到的知识点偏好对与同提示比较 · 标注一致性与平局 · 提示级数据划分
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 反例构造
能否只依靠偏好对训练出的标量奖励,对完全不同任务做统一资源分配?构造组内排序合理但跨任务分数不可比的例子,提出新增证据需求。
- 提交内容
- 反例与跨任务校准需求
- 怎样算完成
- 评分等价类明确
- 新增假设可被检验
练到的知识点分数平移不变性 · 提示级数据划分
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Deep RL from Human Preferences (新标签页)↗
阅读偏好概率、轨迹片段与反馈采集边界。
- 实践参考文档英文
PyTorch v2.9.1 functional.py (新标签页)↗
核对logsigmoid与softplus绑定及数值说明。
- 深入参考文档英文
DPO原论文背景 (新标签页)↗
对照Bradley-Terry偏好模型与奖励的等价类,不把绝对分数当唯一真值。