读完这篇,你能做什么
核验奖励塑形的折扣边界,并训练一个按任务选择调用或跳过的两动作CPU策略。
学习理由、准备要求与知识点
为什么学它
最终答对,不说明每次工具调用都有帮助。奖励设置不当,还可能鼓励反复重试或调用用不上的工具。
先读这些内容
知识点清单
- 调用监督与任务奖励
- Toolformer损失筛选
- 动作和观测掩码
- 延迟奖励与信用分配
- 折扣回报
- 势函数塑形边界
- 工具成本权衡
- 反事实与奖励模型误差
运行前先确认论文机制按文中版本说明;Java 17 完整算法,Python 3.14 与 PyTorch 2.9.1+cpu 的小型 CPU 实验。未执行 GPU、多机或大规模模型训练。
01先弄懂它是什么
会写合法工具参数,只是会用工具的第一步。还要判断当前是否需要调用、何时调用,以及怎样根据返回结果继续做事。例如参数和接口都写对了,但重复查询无关信息,仍会浪费预算。因此训练数据应分别记录参数是否合法、工具执行怎样、最终任务是否完成;一次调用成功,不能直接代替整个任务成功。
多步操作最后拿到奖励后,要判断前面哪些选择真正有帮助,这叫信用分配。设第 t 步动作后的奖励是 r_t,折扣为 gamma,则起点回报 G 为各项 gamma 的 t 次方乘 r_t 之和。如果只在最后给奖励,早期关键检索和后期偶然成功会混在一起,估计可能波动很大。但把奖励随意平均分给每一步,也没有证明每一步的贡献真的一样。
Toolformer通过采样候选调用、执行工具、比较后续文本预测损失来筛选训练例,再做语言建模微调。它给出一种工具数据构造办法,不等价于针对完整业务成功做在线强化学习。RUDDER研究延迟奖励的回报分解;两者面对不同监督信号,不能因为都出现“工具”或“奖励”就当作可互换算法。
文中这几个词,先认一认
- 信用分配
- 看到整条任务路线的结果后,估计前面各个决策应得到多少奖励或惩罚的问题。
- 势函数
- 本篇给每个状态指定一个辅助数 Phi,再把“下一状态的数乘折扣系数,减当前状态的数”作为附加奖励。这个数只是构造学习信号的工具,不自动等于真实回报;保证原有回报排序不变还需要正文中的起点与终点条件。
- 动作掩码
- 标出哪些位置属于策略真正选择的动作,因而应参与对应的策略损失计算。
02看看它怎么工作
轨迹中的用户输入、工具返回和策略动作必须有明确角色。做动作策略梯度时,环境观测不应被当作策略采样的动作计算似然比;这与某个语言建模数据集是否训练所有文本位置是不同设计。一次失败还要区分参数错误、服务不可用和结果推理错误,否则模型可能因网络故障被惩罚正确调用。
可以给每个状态指定一个辅助数 Phi,再用前后状态的差构造附加奖励,这叫势函数奖励塑形:r'_t=r_t+gamma×Phi(s_{t+1})-Phi(s_t)。把它代入整段折扣回报时,中间项会前后抵消,剩下 G'=G-Phi(s_0)+gamma^T×Phi(s_T),T 是这一段的步数。
因此,对起点相同、终止状态的 Phi 为零的完整回合,各条路线只减去同一个起点常数,回报排序保持不变。但若只是预算耗尽就停止记录,末状态未必是真正终点,最后一项通常不能删掉;漏掉它后,就不能继续声称排序没有改变。
Python用一个形状为2×2的参数表表示两类任务下两个动作的logits,softmax把每行变成概率。第一类任务必须调用工具才得分,第二类无需工具,调用要支付成本。枚举全部动作后直接微分期望净奖励,能观察正确选择概率上升;它展示目标与梯度关系,没有模拟长链路探索,也没有复现Toolformer的数据生成训练。
先跑通一个最小例子
共享PyTorch CPU环境执行experiment.py。两个任务、两个动作的小参数表进行100次精确期望梯度更新,无外部工具调用。
import torch
torch.set_num_threads(1)
torch.manual_seed(40)
logits=torch.nn.Parameter(torch.zeros((2,2),dtype=torch.float64))
# 两种任务,每行两个动作:跳过工具、调用工具;调用成本已计入净奖励。
reward=torch.tensor([[0.,.8],[1.,.8]],dtype=torch.float64)
optimizer=torch.optim.SGD([logits],lr=1.)
def value(): return (logits.softmax(-1)*reward).sum(-1).mean()
initial=value().item()
for _ in range(100):
optimizer.zero_grad()
loss=-value() # 枚举全部动作的精确期望梯度,不是采样REINFORCE或Toolformer。
loss.backward();optimizer.step()
prob=logits.softmax(-1).detach()
assert value().item()>initial
assert prob[0,1]>.9 and prob[1,0]>.8
assert torch.allclose(prob.sum(-1),torch.ones(2,dtype=torch.float64))
assert torch.isfinite(logits).all()
# 常数基线不会改变期望梯度,因为概率之和为一。
g1=torch.autograd.grad(value(),logits)[0]
v2=(logits.softmax(-1)*(reward-3.)).sum(-1).mean()
g2=torch.autograd.grad(v2,logits)[0]
assert torch.allclose(g1,g2,atol=1e-12)
mask=torch.tensor([True,False,True]) # 环境观测不属于策略选择的动作。
assert torch.tensor([-1.,-99.,-2.])[mask].sum().item()==-3.
print(f"initial={initial:.6f} final={value().item():.6f}")
print("PASS 6 checks; exact tiny policy gradient, no language-model training")
核对运行结果输出initial与更大的final,PASS 6 checks;两行分别偏好调用和跳过。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
N0["记录策略动作与环境观测"]
N1["分开任务结果和调用成本"]
N2["定义折扣与终止势边界"]
N3["计算回报或优势并检查掩码"]
N4["用保留任务审计调用收益"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4按顺序看,每一步做了什么
- 记录策略动作与环境观测
- 分开任务结果和调用成本
- 定义折扣与终止势边界
- 计算回报或优势并检查掩码
- 用保留任务审计调用收益
03对照实现代码
Ng、Harada、Russell,ICML 1999 · Potential-based shaping,Equation 2与策略不变性定理
原创公式模型;来源为ICML1999原论文,非Toolformer或RUDDER框架源码。
def shaped_reward(reward, current_phi, next_phi, gamma):
future = gamma * next_phi # 下一状态势必须乘相同折扣。
shaping = future - current_phi # 只使用相邻状态的势差结构。
return reward + shaping # 保留原始任务奖励,不另造成功定义。
原论文讨论何种奖励变换在所述马尔可夫决策过程条件下保持最优策略。核心是带折扣的相邻势差,而不是看到中间进度就随意加分。本文源码桥梁是公式的原创标量实现,Java再验证有限轨迹的相消恒等式,没有宣称实现完整强化学习框架。
证明里怎样处理回合末端,代码就要采用相同约定。任务真正终止时,可以约定末端势为零;如果只是时间到了,后面仍可能有价值,不能因为日志到此结束就一并清空。即使恒等式成立,也只说明两个目标之间的关系,不保证有限样本下优化更快,不证明势函数估计准确,也不代表工具策略已经通过安全检查。
Toolformer的筛选将有调用结果时的后续加权交叉熵,与无调用及只有调用无结果两种基线中的较小损失比较,再用阈值保留有帮助的例子。这个设计避免仅凭调用文本本身有用就归功于工具返回,但仍使用语言预测效益作为代理,业务成功要另测。
RUDDER用回报预测与贡献分析重新分配延迟奖励,其理论与学习过程比固定势函数复杂。阅读时要区分总回报保持、期望未来奖励减少与预测器误差;本文引用它用于认识长链路难点,没有用短数组相消实验冒称已实现RUDDER。
代码先调用谁,再调用谁
- 定义原始回报与折扣
- 构造相邻状态势差
- 沿轨迹求和核对边界
- 在原始任务奖励上评估策略
04动手写一个例子
验证有限轨迹的奖励塑形恒等式
输入T个奖励、T+1个状态势和0至1折扣,返回原回报、塑形回报及末端修正,拒绝非有限值与长度错误。
- 输入 1
r=[0,0,1],Phi=[2,3,4,0],gamma=1- 输出 1
原回报1,塑形回报-1
- 输入 2
相同奖励,末端势5,gamma=.9- 输出 2
边界=-2+.9^3×5
逐步维护discount=gamma^t,把原奖励和塑形奖励分别乘它累加,然后推进折扣。计算结束保留discount×Phi(s_T)-Phi(s_0),独立核对塑形总和等于原总和加边界。两个方向分别计算能发现索引偏移或遗漏折扣,而不是只把答案复制一遍。
在第t次循环前,两个累加器分别包含区间零到t之前的折扣奖励,discount恰好对应第t项。加入当前奖励后不变量保持,遍历结束覆盖全部回报。相邻势项的代数消去解释恒等式;浮点程序只在容差内验证,不承诺任意极大数都精确。
空轨迹要求恰有一个状态势,回报为零且边界为零;gamma为零时只保留首步项。终止与截断由调用者解释,程序不会自动把末端势改零。输入、累加和最终边界全部拒绝非有限值,防止溢出被误当成巨大训练收益。
为什么这样写是对的第t步开始时discount=gamma^t,两个和已覆盖此前全部步骤。
奖励和势各扫描一次,常数个标量;输入数组不计额外空间。
保存为 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");
}
record Result(double original,double shaped,double boundary) {}
static Result shape(double[] rewards,double[] potential,double gamma) {
if(rewards==null||potential==null||potential.length!=rewards.length+1||!Double.isFinite(gamma)||gamma<0||gamma>1)
throw new IllegalArgumentException("shape");
for(double x:potential) if(!Double.isFinite(x)) throw new IllegalArgumentException("potential");
double original=0,shaped=0,discount=1;
for(int t=0;t<rewards.length;t++) {
if(!Double.isFinite(rewards[t])) throw new IllegalArgumentException("reward");
double adjusted=rewards[t]+gamma*potential[t+1]-potential[t]; // 相邻势函数必须使用同一个折扣。
original+=discount*rewards[t];
shaped+=discount*adjusted; // 先按当前时刻加权,再推进折扣指数。
discount*=gamma;
if(!Double.isFinite(original)||!Double.isFinite(shaped)) throw new IllegalArgumentException("overflow");
}
double boundary=-potential[0]+discount*potential[rewards.length]; // 截断时保留末端项,不能假装终止。
if(!Double.isFinite(boundary)) throw new IllegalArgumentException("overflow");
return new Result(original,shaped,boundary);
}
public static void main(String[] args) {
Result a=shape(new double[]{0,0,1},new double[]{2,3,4,0},1);
near(1,a.original());near(-1,a.shaped());
Result b=shape(new double[]{0,0,1},new double[]{2,3,4,0},.9);
near(b.original()+b.boundary(),b.shaped());
Result c=shape(new double[]{0,0,1},new double[]{2,3,4,5},.9);
near(-2+Math.pow(.9,3)*5,c.boundary());near(c.original()+c.boundary(),c.shaped());
near(0,shape(new double[]{},new double[]{7},.9).shaped());
near(3,shape(new double[]{5,9},new double[]{2,8,0},0).shaped());
bad(()->shape(new double[]{1},new double[]{0},1));
bad(()->shape(new double[]{1},new double[]{0,0},1.1));
bad(()->shape(new double[]{Double.NaN},new double[]{0,0},.9));
System.out.println("PASS 8 cases");
}
}
这些特殊情况也要试一试
- 空轨迹要求一个状态
- gamma零与一分别自测
- 截断末端势保持
NaN及溢出拒绝
进阶选读 · 带工具成本和反事实基线的长链路实验
场景成功奖励稀疏且不同工具耗时差距很大。
做法先定义成本单位与硬预算,再比较终局奖励、势塑形和学得价值基线;以相同初始状态复放可替换工具,审计信用是否跟随真实贡献。
复杂度回报扫描O(T),但训练价值网络、环境执行与反事实重放另计;分支枚举可能指数增长。
好处与代价更密信号可能减少方差,也可能引入代理漏洞;成本项会改变目标,不能借塑形定理称其无影响。
05项目里怎么用
上线前应把训练用工具替换成稳定的可回放接口,记录接口版本、参数、返回摘要和故障类别。奖励模型或验证器应只读取合法可见信息,不能通过隐藏测试答案、未来状态或数据库管理员权限帮助策略。权限控制仍由执行层负责,学会少调用不意味着可以绕过授权。
比较调用策略时同时报告任务成功、平均调用数、失败重试和尾延迟。净奖励上升可能来自更少调用,也可能牺牲少数复杂任务;按任务难度分层能识别这一点。不要把所有重试都惩罚成坏动作,先区分瞬时故障与语义上重复调用,并设置独立硬上限。
研究结论应写清训练目标与验证目标的差距。两状态参数表能证明梯度和成本权衡的实现行为,却没有语言理解、工具参数生成或分布外鲁棒性。进一步做真实工具训练时,应保留固定测试任务和原始成功奖励,避免只报告塑形后容易变大的分数。
容易踩的坑
- 将工具响应纳入策略动作似然
- 把接口200当任务成功
- 截断时任意删除末端势
- 奖励每次调用导致循环刷分
- 将预测损失下降说成业务收益
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 监督工具轨迹 | 稳定、可离线检查 | 依赖示范覆盖 | 先学格式和常见流程 |
| 终局强化学习 | 直接面向任务结果 | 探索稀疏、方差较大 | 可验证且可重放环境 |
| 塑形或价值基线 | 提供更密信用线索 | 需要检查偏差和边界 | 长链路实验改进 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01合法调用等于有效调用吗?
展开参考答案与推导
不等于,参数合法只说明可执行,还要检查信息增益、成本和最终结果。
02势差为什么包含gamma?
展开参考答案与推导
它必须与回报折扣匹配,才能让相邻状态项相消。
03Toolformer是在线任务RL吗?
展开参考答案与推导
不是,原方法筛选有助后续预测的调用数据并做语言建模微调。
换个情况,还会处理吗
01奖励变高但成功率下降怎么办?
展开参考答案与推导
拆分原成功奖励、塑形项和成本项,检查代理目标与边界是否改变排序。
02服务超时是否惩罚参数选择?
展开参考答案与推导
先区分服务故障与无效调用,控制环境后再估计策略贡献。
03轨迹结束能否将Phi设零?
展开参考答案与推导
真实终止且设计如此才可以,预算截断要保留边界或相应自举。
写代码试一试
01推导边界项。
- 输入 / 条件
T步奖励和T+1势值- 输出 / 预期
-Phi0+gamma^T×PhiT
提示 逐项相消
展开参考答案与推导
保留起始负项与末端正项,其余折扣势差抵消。
02过滤策略位置。
- 输入 / 条件
动作、观测、动作三项- 输出 / 预期
只累加第1和第3项
提示 使用角色掩码
展开参考答案与推导
观测不作为策略采样动作参与相应策略梯度。
03验证常数基线。
- 输入 / 条件
每行概率和1、奖励减3- 输出 / 预期
梯度不变
提示 常数期望也是常数
展开参考答案与推导
计算两次梯度并比较,不能把依赖动作的任意函数当常数基线。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
核验奖励塑形的折扣边界,并训练一个按任务选择调用或跳过的两动作CPU策略。
本篇重点加练
延迟信用与奖励塑形需要稳定的轨迹边界和势函数定义,重点训练版本变化下的归因。
- 延迟奖励与信用分配
- 势函数塑形边界
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
分别为工具调用格式监督、预测损失筛选和最终任务奖励写出训练样本单位、目标及所需证据,说明三者的学习信号差异。
- 提交内容
- 训练目标对照表
- 怎样算完成
- 动作与观察归属明确
- 代理信号不冒充任务收益
练到的知识点调用监督与任务奖励 · Toolformer损失筛选 · 动作和观测掩码
- 作业 02代码推演
一条轨迹包含三次工具动作和最后的成功奖励,折扣为 0.9;自选势函数值,分别记录原回报与塑形后回报的边界项。
- 提交内容
- 逐步奖励与回报表
- 怎样算完成
- 初末状态约定完整
- 终止与截断分别处理
练到的知识点延迟奖励与信用分配 · 折扣回报 · 势函数塑形边界
- 作业 03原理问答
同一轨迹中途更换势函数版本,评审原有塑形回报比较是否仍有效;自行给出状态和两版势值,标注每项奖励依赖哪个版本。
- 提交内容
- 版本账本与有效性论证
- 怎样算完成
- 奖励函数身份可追踪
- 不默认跨版本相消
练到的知识点延迟奖励与信用分配 · 势函数塑形边界
综合应用
- 作业 04编程实践
实现 CPU 轨迹到策略训练样本的转换器,输入交错动作/观察事件与延迟奖励;输出动作掩码、回报及有效长度。
- 提交内容
- 转换器与带身份的样例
- 怎样算完成
- 环境观察不误作策略动作
- 截断数据有明确的处理规则
练到的知识点动作和观测掩码 · 延迟奖励与信用分配 · 折扣回报
- 作业 05排错实验
在小环境中加入有成本但无信息的工具,比较仅成功奖励与成功减成本两种目标;记录调用数和任务成功率。
- 提交内容
- 受控奖励实验
- 怎样算完成
- 成本单位与权重显式
- 奖励和真实效果分别报告
练到的知识点调用监督与任务奖励 · 工具成本权衡
- 作业 06面试场景
一次工具失败可能来自参数错误、服务故障或信息无用。制定信用分配数据标注方案,说明哪些责任不能从单条日志确定。
- 提交内容
- 标注规则与证据缺口表
- 怎样算完成
- 可归因范围明确
- 基础设施故障单独记录
练到的知识点延迟奖励与信用分配 · 工具成本权衡 · 反事实与奖励模型误差
- 作业 07编程实践
CPU 小环境保存原奖励、塑形奖励和轨迹边界,编写重算审计器;改变回放时的势函数版本,核查旧训练样本是否可原样复用。
- 提交内容
- 重算程序与兼容性报告
- 怎样算完成
- 原始奖励独立保留
- 训练时和回放时定义分开
练到的知识点延迟奖励与信用分配 · 势函数塑形边界
- 作业 08面试场景
一段成功轨迹包含一个在结果发生后才返回的工具响应。设计时间信用审计,判断该响应是否可能为当前成功提供因果证据。
- 提交内容
- 事件依赖图与归因范围
- 怎样算完成
- 动作生效与日志到达区分
- 时间相关不代替因果证据
练到的知识点延迟奖励与信用分配
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
设计对同一初始状态替换一条工具结果的反事实实验,检验某奖励塑形是否改变策略偏好;限制为可重放的 CPU 环境。
- 提交内容
- 干预协议与配对结果
- 怎样算完成
- 其他条件固定
- 反事实结论不扩大到真实因果全集
练到的知识点势函数塑形边界 · 反事实与奖励模型误差
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 跨域迁移
迁移到延迟反馈推荐:奖励只在用户一周后购买时出现,采样窗口已结束。怎样区分数据截止与真正无奖励,并检验塑形引入的偏好变化?
- 提交内容
- 反馈时间模型与实验协议
- 怎样算完成
- 未成熟标签保持未知
- 代理奖励与最终效果分别评估
练到的知识点延迟奖励与信用分配 · 势函数塑形边界
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
Toolformer v1 (新标签页)↗
阅读第二节调用采样、执行与损失筛选,区分监督目标。
- 深入参考文档英文
ICML1999奖励塑形 (新标签页)↗
核对势差形式与策略保持的假设。
- 深入参考文档英文
RUDDER论文 (新标签页)↗
深入阅读回报等价与贡献分解,不把其学习过程简化成固定势差。