跳转到主要内容
前面出现了不少通过率:留出任务上的 0.84、某次消融的 0.71 与默认设置的 0.76。怎样判断这些差异是否可信?本章从测量本身入手:通过率估计哪个量、误差有多大、如何比较检查点,以及该怎样使用验证集和测试集。尤其要分清,10 个任务各采样 32 次,不能直接当作 320 次针对不同任务的独立试验。 本章在 TinyAgent 上回答这些问题,使用的是第 9 章默认设置下的 GRPO 检查点;它还用同一套工具处理推理模型强化学习中争议最大的一项测量:强化学习究竟提升了大 kk 下的 pass@kk,还是只是让基座模型原本就有的分布变得更尖锐。这里的每个数字都是一个 158k 参数模型在 10 个留出任务上的机制演示(mechanism demonstration)。任务只有 10 个,正好能让样本量不足带来的不确定性清楚地显现。
检查点来自一次 80 步的默认 GRPO 训练。train_snapshots 以每 10 步为一段调用 arl.train_rl,每段结束后保存一份冻结副本。每次调用都会重启 Adam 优化器并使用自己的随机种子,这与一次不间断的 80 步训练略有不同,但对本章要测量的东西没有影响。

17.1 通过率估计的是什么

评测估计的是策略在某个评测框架下的性质。评测框架 HH 指权重之外的一切:任务集、提示模板、环境及其预算(轮数、编辑次数、上下文长度)、采样温度,以及成功判据。对任务 jj,记 pj(π,H)=Pr⁡[an episode of π on task j under H succeeds].p_j(\pi, H) = \Pr\big[\text{an episode of } \pi \text{ on task } j \text{ under } H \text{ succeeds}\big]. 基准的 pass@1 是任务平均 pˉ=1N∑j=1Npj\bar p = \frac{1}{N}\sum_{j=1}^{N} p_j。除非每个任务的回合(episode)数相同,否则它并不是“通过的回合所占比例”;它也不是权重单独具有的性质:改变 HH,每个 pjp_j 都会改变。这里的成功判据是第 4 章为评测固定下来的那一个,与任何训练奖励无关:智能体提交了答案,并且所有隐藏测试都通过。 因此,一次评测的原始数据是每个任务的成功次数:在 NN 个任务上各采样 nn 个回合,记录成功的次数 cjc_j。本章其余内容(pass@1、pass@kk、区间、配对检验)都是向量 cc 的函数。sample_counts 用训练时的评测框架 cfg、通过训练所用的同一个 sample_groups 来产生它,这样模板、重命名、编辑预算、轮数预算和温度就不会悄无声息地出现差异。
我们在 10 个留出任务上,分别为 RL 基座智能体和第 80 步的检查点各采样 32 个回合。
Output:
两个通过率 0.144 和 0.819,是两个差别很大的向量的任务平均。基座智能体的成功几乎均匀分布,每个任务都是 32 次中成功 1 到 8 次:它在猜,而每个任务大约同样好猜。训练后的智能体在 8 个任务上几乎每个回合都成功,under_cap 是 32 次中 17 次,speed 是 32 次中 1 次。单个数字掩盖了一个任务基本未被解决的事实;下面的统计会表明,决定训练后智能体得分不确定性的,正是这种异质性,而不是回合数。

17.2 pass@kk 及其无偏估计量

编码系统通常允许多次尝试:在有验证器(verifier)的前提下,采样 kk 个补丁并保留一个通过的。pass@kk 是 kk 次独立尝试中至少一次成功的概率,再对任务取平均: pass@k=1N∑j=1N(1−(1−pj)k).\text{pass@}k = \frac{1}{N}\sum_{j=1}^{N} \big(1 - (1 - p_j)^k\big). 它需要一个能挑出通过尝试的预言机,因此衡量的是覆盖率,即任何选择器所能达到的上界,而不是可部署的成功率(第 20 章将它与 Best@kk 区分开)。 代入估计量是有偏的。 最直接的估计是用 p^j=cj/n\hat p_j = c_j/n 替代 pjp_j。当 k≥2k \ge 2 时,映射 f(p)=1−(1−p)kf(p) = 1 - (1-p)^k 在 (0,1)(0, 1) 上严格凹,因此由 Jensen 不等式,只要 0<pj<10 < p_j < 1,就有 E[f(p^j)]<f(E[p^j])=f(pj)\mathbb{E}[f(\hat p_j)] < f(\mathbb{E}[\hat p_j]) = f(p_j):代入估计会系统性地低估 pass@kk,nn 越小、kk 越大,低估越严重。 无偏估计量。 Chen et al. (2021) 采样 n≥kn \ge k 个样本,并对它们提出另一个问题:如果从 nn 个样本中无放回地均匀随机挑出 kk 个,它们全部失败的概率是多少?若有 cc 个成功,该概率为 (n−ck)/(nk)\binom{n-c}{k}\big/\binom{n}{k},于是 pass@k^j=1−(n−cjk)(nk).\widehat{\text{pass@}k}_j = 1 - \frac{\binom{n-c_j}{k}}{\binom{n}{k}}. 无偏性一行即可证明。对任意固定的 kk 个样本下标集合 SS,事件“SS 中每个样本都失败”的概率为 (1−pj)k(1-p_j)^k,因为样本相互独立。估计量中的失败项是该事件指示函数在全部 (nk)\binom{n}{k} 个集合 SS 上的平均。期望是线性的,所以它的期望恰好是 (1−pj)k(1-p_j)^k。(它是一个 U 统计量,即由样本构造的最小方差无偏估计量。)无偏性对每个任务成立,对任务取平均后依然成立。 数值稳定的形式。 远在 nn 变大之前,二项式系数就会让浮点数溢出:(20001000)\binom{2000}{1000} 有 600 位数字。把两个系数写成阶乘并约分, (n−ck)(nk)=(n−c)! (n−k)!n! (n−c−k)!=∏i=n−c+1ni−ki=∏i=n−c+1n(1−ki),\frac{\binom{n-c}{k}}{\binom{n}{k}} = \frac{(n-c)!\,(n-k)!}{n!\,(n-c-k)!} = \prod_{i=n-c+1}^{n} \frac{i-k}{i} = \prod_{i=n-c+1}^{n}\Big(1 - \frac{k}{i}\Big), 当 n−c≥kn - c \ge k 时,这是 cc 个落在 (0,1)(0, 1) 内的因子之积。当 n−c<kn - c < k 时,每个 kk 元子集都包含一个成功,估计值恰好为 1。
下面的检查对一个任务、在几个不同的真实通过率下,各抽取 100,000 次 n=10n = 10 个样本的评测,并把每个 pass@5 估计量的平均值与真值比较。
Output:
代入估计在每个 pp 下都偏低,低 0.04 到 0.08,这比第 9 章试图解读的大多数差异都大。无偏估计量在蒙特卡洛误差(这里约 0.001)之内与真值一致。偏差在中间的 pp 处最大,因为在 p^\hat p 的分布范围内,ff 在那里弯曲得最厉害。 乘积形式与精确的有理数运算一致,并且在二项式系数之比无法表示的地方依然可用。
Output:
对 n=10,c=3,k=5n = 10, c = 3, k = 5,乘积形式给出 1−21/252=0.91671 - 21/252 = 0.9167,即附录 B 问题 21 中算出的值。对 n=2000n = 2000,(20001000)\binom{2000}{1000} 超过 600 位,无法转换为浮点数,而 5 个接近 1/21/2 的因子之积给出 0.9689≈1−2−50.9689 \approx 1 - 2^{-5}。Chen et al. 使用 n=200n = 200、k≤100k \le 100,并指出了同样的不稳定问题。nn 要选得远大于你报告的最大 kk:当 kk 接近 nn 时,估计值几乎就是每个任务的单个指示变量,方差很大。

17.3 置信区间

没有区间的通过率无法与任何东西比较。三种区间覆盖了本书遇到的情形。

单个任务的二项区间

对 nn 个成功概率为 pp 的独立回合,p^=c/n\hat p = c/n 的标准误为 p(1−p)/n\sqrt{p(1-p)/n}。Wald 区间 p^±zp^(1−p^)/n\hat p \pm z\sqrt{\hat p(1-\hat p)/n} 代入了 p^\hat p,而它恰恰在智能体评测所处的区域失效:当 p^=0\hat p = 0 或 11 时宽度为零,靠近边界时覆盖不足。Wilson 区间不做代入,而是反转检验:它收集得分检验不会拒绝的所有 pp,即 ∣p^−p∣≤zp(1−p)/n|\hat p - p| \le z\sqrt{p(1-p)/n}。两边平方得到关于 pp 的二次方程,其根为 p^+z22n1+z2n±z1+z2np^(1−p^)n+z24n2.\frac{\hat p + \frac{z^2}{2n}}{1 + \frac{z^2}{n}} \pm \frac{z}{1 + \frac{z^2}{n}}\sqrt{\frac{\hat p(1-\hat p)}{n} + \frac{z^2}{4n^2}}. 区间中心被拉向 1/2,宽度永不坍缩,这正是小 nn 所需要的。
我们在 20,000 次模拟评测(每次 40 个回合)上测量两种区间的覆盖率。
Output:
在 p=0.03p = 0.03 和 p=0.97p = 0.97 处,Wald 区间只在 70% 的评测中覆盖真值,因为大约 30% 的 40 回合评测一次失败(或一次成功)都没有观察到,于是报告宽度为零的区间。Wilson 区间在每个 pp 下都保持在 0.93 到 0.97 之间。对单个任务或少量回合,请使用 Wilson 区间或精确区间;只有当 np^n\hat p 和 n(1−p^)n(1-\hat p) 都足够大时,Wald 区间才可以接受。

自助法,以及该重采样哪个单元

**自助法(bootstrap)**通过在从观测数据中有放回重采样得到的数据上重新计算统计量,来估计该统计量的抽样分布。百分位区间取重算值的 α/2\alpha/2 和 1−α/21-\alpha/2 分位数。这里首先要检查:被重采样的单元是否可以看作从目标总体中独立抽取。样本量过小等问题仍会影响区间的可靠性。bootstrap_ci 接收每个单元一个值;paired_bootstrap_diff 为两个系统重采样相同的单元。
在一次包含 NN 个任务、每个任务 nn 个回合的评测中,回合之间并不独立:同一任务的回合共享该任务的难度。把任务视为从一个均值为 μ\mu、任务间方差为 σtask2=Var(pj)\sigma^2_{\text{task}} = \mathrm{Var}(p_j) 的总体中抽取。由全方差公式,估计值 μ^=1N∑jcj/n\hat\mu = \frac{1}{N}\sum_j c_j/n 满足 Var(μ^)=1N(σtask2+E[pj(1−pj)]n),\mathrm{Var}(\hat\mu) = \frac{1}{N}\Big(\sigma^2_{\text{task}} + \frac{\mathbb{E}[p_j(1-p_j)]}{n}\Big), 而把全部 NnNn 个回合当作独立样本,则假设方差为 μ(1−μ)/(Nn)\mu(1-\mu)/(Nn)。由于 μ(1−μ)=σtask2+E[pj(1−pj)]\mu(1-\mu) = \sigma^2_{\text{task}} + \mathbb{E}[p_j(1-p_j)],真实方差与朴素方差之比就是设计效应(design effect)1+(n−1) ρ1 + (n-1)\,\rho,其中任务内相关系数 ρ=σtask2/(μ(1−μ))\rho = \sigma^2_{\text{task}}/(\mu(1-\mu))。由此得到两个推论。当任务之间存在差异(ρ>0\rho > 0)时,朴素区间偏窄 1+(n−1)ρ\sqrt{1 + (n-1)\rho} 倍。并且当 n→∞n \to \infty 时,方差并不趋于零,而是趋于 σtask2/N\sigma^2_{\text{task}}/N:每个任务采样再多,也弥补不了任务太少。解决办法是聚类自助法(cluster bootstrap):对任务做重采样,每个任务的回合随之一起带走,也就是把 bootstrap_ci 应用于每个任务的通过率。 哪种区间正确取决于问题,Miller (2024) 针对语言模型评测强调了这一点。“这个检查点在这 10 个任务上表现如何?”是以任务为条件的,只有回合采样是随机的。“它在类似的任务上会表现如何?”则把任务视为一个样本。讨论对新任务的泛化时,应回答第二个问题。一个总体已知的模拟可以把两者分开:任务通过率从 U 形的 Beta(0.4, 0.3) 分布中抽取(大多数任务几乎总是或几乎从不被解决,与智能体基准中的情形一样),共 10 个任务,每个任务 32 个回合。
Output:
重采样回合只在 38% 的评测中覆盖总体均值:它的区间太窄,通常会错过。它们在 99.8% 的评测中覆盖这 10 个任务的均值,这才是它们回答的问题(而且即使对这个问题也偏保守,因为二项方差 μ(1−μ)\mu(1-\mu) 高估了任务内方差)。重采样任务在 93% 的评测中覆盖总体均值,接近名义水平;剩下的不足是百分位自助法在 N=10N = 10 时已知的覆盖不足。图 17.1 对每任务回合数 nn 从 1 到 64 重复了这个实验。每个任务只有一个回合时,两种区间重合;随着 nn 增大,回合级区间的宽度缩向零,而真实的不确定性停留在 σtask2/N\sigma^2_{\text{task}}/N,其覆盖率降到 0.3 以下。 图 17.1:随着每任务回合数增加,名义 95% 自助区间对总体通过率的覆盖率,分别对回合重采样和对任务重采样。模拟设置为 10 个任务,其通过率从 Beta(0.4, 0.3) 中抽取,每个点 300 次评测。 在 17.1 节实测的计数上做同样的比较,并给出任务内相关系数的矩估计:
Output:
对基座智能体,两种区间一致(宽度 0.075 和 0.081):它的各个任务同样难,ρ≈0.01\rho \approx 0.01,它的回合表现得像独立的抛硬币。对训练后的智能体,ρ=0.63\rho = 0.63,设计效应为 20:任务级区间 [0.60,0.96][0.60, 0.96] 的宽度是回合级区间的 4.4 倍。只有 10 个任务、其中一个几乎没被解决,诚实的说法是:训练后的智能体在类似任务上的留出通过率大约介于 0.6 和 0.96 之间。继续增加每个任务的回合数,只能减少任务内的采样噪声;这里更大的不确定性来自任务差异,需要更多独立任务才能有效缩小。

17.4 比较两次训练

把两个数字区分开的随机性有两种。评测噪声来自从固定检查点采样回合,更多回合能缩小它。训练噪声来自训练的随机种子(任务顺序、采样的组、小批次顺序),它是训练配方的性质:同一配方的两次训练会停在不同的检查点。训练噪声是否应计入误差棒,取决于所做的论断。“检查点 B 优于检查点 A”是关于两个固定检查点的论断。“配方 B 优于配方 A”是关于检查点分布的论断,它的重复单元是随机种子。 我们用默认配方再训练三个随机种子,各 30 步——此时训练尚未饱和——并对四个第 30 步检查点各做两次相互独立的评测(每次 320 个回合)。重复评测衡量评测噪声;跨种子的离散则包含两种噪声。
Output:
同一检查点的两次评测相差最多 0.028,一次 320 回合评测的标准差为 0.016,接近二项式的 0.71⋅0.29/320=0.025\sqrt{0.71 \cdot 0.29/320} = 0.025(它估计的是条件噪声,即“这 10 个任务”上的噪声,17.3 节已表明它更小)。四个种子的均值从 0.706 到 0.720,离散程度小于同一种子的两次评测,估计出的训练噪声标准差为零。在这个设置下、在第 30 步,默认配方在不同种子之间高度可复现,几乎所有可见的离散都是评测噪声。这也让我们重新解读第 9 章的消融表:其默认组的三个留出值 0.76 到 0.81 各来自 80 个回合,其评测标准误约为 0.045,所以那里的离散也主要是评测噪声。这两个结果都不表示随机种子在一般情况下无关紧要。种子方差取决于配方、规模和训练所处的阶段,必须像这里一样用相互独立的评测测量过,才能忽略它。

配对比较

当两个系统在相同任务上运行时,应逐任务比较。每个任务的差 dj=p^jA−p^jBd_j = \hat p^{A}_j - \hat p^{B}_j 消去了两个系统共享的任务难度,因此 Var(dˉ)=1N(Var(p^jA)+Var(p^jB)−2 Cov(p^jA,p^jB)),\mathrm{Var}(\bar d) = \frac{1}{N}\big(\mathrm{Var}(\hat p^A_j) + \mathrm{Var}(\hat p^B_j) - 2\,\mathrm{Cov}(\hat p^A_j, \hat p^B_j)\big), 协方差项在任务难度差异大时很大,而非配对比较恰恰丢掉了它。我们在相同的 10 个任务上比较第 60 步和第 80 步的检查点,并让两者使用同一个随机数生成器。两个相似的策略在相同随机数驱动下往往采样出相同的回合,这进一步提高了它们结果之间的相关性。这就是仿真中的公共随机数(common random numbers)技术;它可以降低配对差值的方差,也正因如此,当目标是测量评测噪声时(如上一个代码块),不能重复使用同一个生成器。
Output:
第 80 步检查点比第 60 步检查点高 0.041,配对区间 [+0.006,+0.087][+0.006, +0.087] 不包含零。若当作两个非配对的 10 任务样本,同样的差异的标准误是 0.132,是配对标准误 0.023 的六倍,差异将完全看不出来。两个检查点的逐任务通过率相关系数为 0.97:对一个难的任务对另一个也难,配对把这部分共享难度减掉了。配对区间回答的是关于这些任务和这两个检查点的问题;它对配方一无所知,而且只有 10 个任务,它本身也只是一个粗略的区间。

需要多少任务或随机种子?

功效分析(power calculation)把比较反过来:给定我们想检测的效应 δ\delta,需要多少个单元?对显著性水平 α\alpha、功效 1−β1-\beta 的双侧检验,NN 个配对差(每单元标准差为 σd\sigma_d)的均值必须满足 δ/(σd/N)≥z1−α/2+z1−β\delta/(\sigma_d/\sqrt N) \ge z_{1-\alpha/2} + z_{1-\beta},因此 N≥((z1−α/2+z1−β) σdδ)2.N \ge \Big(\frac{(z_{1-\alpha/2} + z_{1-\beta})\,\sigma_d}{\delta}\Big)^2. 对两个独立组(非配对的任务,或各用一组种子的两个配方),两均值之差的每单元标准差为 2 σ\sqrt2\,\sigma,公式给出的是每一组的大小。
我们代入实测的离散度:两个检查点之间的逐任务差、单个检查点的逐任务离散(用于非配对设计),以及跨种子的离散。
Output:
在配对任务下,要检测这类两个检查点之间 2 个百分点的差异,大约需要 103 个任务,5 个百分点大约需要 17 个。非配对设计(例如,把论文里的数字与你自己评测框架下、在不同任务样本上得到的数字比较)需要约 30 倍的数量:5 个百分点每组需要 533 个任务。种子一列很小,因为实测的跨种子离散很小;公式给出的“一个种子”是关于功效的陈述,而不是关于实践的建议。光是估计 σseed\sigma_{\text{seed}} 每组就至少需要三个种子,配方比较绝不应建立在更少的种子上。
生产实践。 同样的算术决定了真实基准的规模。在 SWE-bench Verified 的 500 个任务上,若每个任务尝试一次,通过率在 0.4 附近时的二项标准误约为 0.022,因此两次独立的单次尝试运行之差的标准误约为 0.031,2 个百分点的差距完全落在其内。逐任务配对、每任务多次尝试(如 DeepSWE 取 16 次运行的平均,见第 20 章),以及多个训练种子,是可用的手段。这是由二项公式算出的结果,不是测量。

17.5 评测框架定义了这个数字

评测框架选择是指权重之外任何会改变 pj(π,H)p_j(\pi, H) 的设置。如果训练与评测之间、或被比较的两次评测之间有一项不同,那么比较测到的就是评测框架。我们在训练评测框架以及七种单字段改动下评测同样的两个检查点。
Output:
只改动评测框架的一个字段,就能让训练后智能体的数字最多变化 0.13。温度取 1.3 而非训练温度 1.0 会损失 0.10;降到 0.7 或贪心解码则会增加 0.03 到 0.04,因为这些设置更偏向当前策略的高概率选择,而这些选择在大多数任务上是正确的。三轮预算损失 0.13:学到的流程是读取、编辑、测试、提交,所以在提交之前先测试的回合会用完轮数,只有跳过测试的回合仍能通过。智能体不用的设置则不会改变它的数字:它从不叠加编辑,所以不限编辑次数没有影响;它忽略函数名,所以重命名没有影响。对基座智能体,同样的设置使数字在 0.116 到 0.200 之间变动,但那个智能体是在猜,所以允许更多尝试或更多轮数的评测框架会给它更多回报。图 17.2 展示了这些数字及其任务级区间。在真实系统中,每一处评测框架改动都只是几行配置;而每一处带来的变化都大于团队试图测量的大多数效应。 图 17.2:RL 基座智能体与 80 步 GRPO 检查点在训练评测框架及七种单字段改动下的留出 pass@1,附任务级自助法 95% 区间。虚线:盲猜一次的通过率 0.2。

贪心评测与采样评测

贪心解码(温度 0)评测的是每一步都选择最高概率 token 时的表现;这不保证整段序列的概率全局最高。在训练温度下采样,评测的才是训练所优化的随机策略,也是使用相同采样配置的 best-of-kk 系统会采样的分布。二者回答不同的问题,逐任务计数显示了区别所在。
Output:
给定提示,贪心解码是确定性的,所以除非提示本身发生变化,其逐任务计数只能是 0 或 32。这里提示确实会变:重命名为每个回合随机抽取一个函数名,这就是为什么基座智能体的贪心计数在四个任务上是 10 到 16,在其余六个任务上是 0。它的贪心行为本质上是一个固定的猜测,只在 compare 和 zero 任务上、且只对部分函数名是对的;而它的采样行为则分散在全部五种修复操作上。对训练后的智能体,贪心解码消除了大多数任务上的残余错误(under_cap 从 25 升到 30),并把训练后智能体偶尔能成功的 speed 变成了彻底的零。对于贪心解码的部署,贪心 pass@1 是正确的数字。对于采样部署或 best-of-kk 系统,它是错误的数字,而且它恰好掩盖了 17.7 节 pass@kk 之争所关注的那些低概率成功。本书的默认做法是在训练温度下做采样评测,并报告所用温度。

17.6 留出划分与泄漏

留出集回答的是一个泛化问题,而这个问题取决于留出了什么。第 4 章的默认划分留出的是变体:每种缺陷类型六个变体中的两个,函数名、常数和测试都不同。它衡量的是智能体能否在训练过的 bug 的不同表面形式之间泛化。留出一种缺陷类型,问的则是它能否泛化到一类从未因修复而获得奖励的 bug。我们在 zero(缺少除零保护)以外的四种缺陷类型上训练 30 步,然后在全部六个 zero 任务以及其他缺陷类型的八个留出变体上评测。
Output:
两种划分对同一个检查点给出了相反的结论。在训练过的缺陷类型的留出变体上,智能体从 0.148 提升到 0.930。在未见过的缺陷类型上,它降到 0.031,低于基座智能体的 0.125。它仍然会读取(每回合 1.41 次调用),但第一次编辑只在 3% 的回合中正确:训练让它对那四种获得过奖励的操作充满信心,并消除了让基座智能体得到 0.125 的猜测行为。变体划分会报告一个强大的智能体;按缺陷类型划分报告的则是一个学会了四种映射、却对缺少除零保护一无所知的智能体。真实编程基准在更大规模上面临同样的选择:来自训练仓库的留出 issue、留出仓库、留出编程语言衡量的是不同的东西,报告中应说明所用的划分。 污染与泄漏。 只有当留出任务的任何信息都没有进入训练时,留出数字才真正是留出的。在 gym 中存在三条渠道,值得逐一检查。分词器是用全部 30 个任务构建的(build_tokenizer(make_tasks('all'))),因此留出任务中的词都有嵌入;一个从未在训练中出现的词保持随机初始化,这对答案无害,但属于词表泄漏。基座智能体只在训练任务上做了行为克隆。第三条渠道就是第 9 章练习 3 指出的那一条:randomize_names 从全部 30 个函数名中抽取,所以留出任务的函数名会作为随机名字出现在训练回合中。下面的代码块测量这种情况出现的频率,然后把重命名池限制为只含训练函数名或只含留出函数名进行评测,以检验它是否有影响。
Output:
大约三分之一的训练回合(0.34,29 个候选名字中的 10 个)带有留出任务的函数名。把留出评测限制为训练函数名或留出函数名,分别得到 0.844 和 0.863,二者之差在 17.4 节的评测噪声之内(每次评测的标准差约 0.016)。训练后的策略不使用函数名,因为重命名在训练中让名字失去了信息量,所以这个泄漏是真实存在的,但对这个指标没有作用。这就是泄漏审计的一般形态:列出各条渠道,然后测量每一条能否移动这个数字。在真实基准上能移动数字的泄漏携带的是答案,而不是名字:包含修复方法的 issue 文本、出现在预训练或强化学习数据中的基准仓库,或智能体可见的测试文件(第 9 章和第 20 章引用了实测案例)。要按仓库、issue 和补丁对训练任务与每个评测集做去污染,并保留一个在训练数据冻结之后才构建的新评测集。

17.7 强化学习是否让分布变窄?TinyAgent 上的 pass@kk

第 20 章总结了一场公开争论。Yue et al. (2025) 报告,在数学基准上,经 RLVR 训练的模型在小 kk 时占优,但“base models achieve a higher pass@k score when k is large”(大 kk 时基座模型的 pass@k 更高),并将此解读为强化学习只是让基座模型的分布更尖锐,而非增加了能力;ProRL (2025) 则报告,在加入参考策略重置、训练更长时间后得到了相反的结论。两者都是作者在数学任务和大模型上报告的结果,本书没有复现。用本章的工具,这项测量本身并不昂贵:用每个任务相同的 64 个回合,在留出任务上计算基座智能体和强化学习检查点的无偏 pass@kk。我们加上一条参考曲线:在五种修复操作中均匀随机猜测,每次尝试的通过概率为 0.2。
Output:
曲线发生了交叉。在 k=1k = 1 时,80 步检查点领先基座智能体 0.70;在 k=16k = 16 时仍然领先,0.958 对 0.941;在 k=32k = 32 时,基座智能体反超,0.998 对 0.988。20 步检查点在每个 kk 上都不低于两者。逐任务计数把交叉定位到一个任务上:speed。基座智能体在 64 个回合中解决它 10 次,20 步检查点 13 次,80 步检查点 3 次。因此 80 步检查点在 speed 上的 pass@32 约为 0.88,而基座智能体基本为 1,大 kk 处的全部差距都来自这一个任务。训练在这个任务上做了什么?
Output:
speed 同时包含乘法和整数除法;它的 bug 是缺少除零保护。基座智能体在它上面的第一次编辑分散在全部六种编辑操作上,add_zero_guard 出现在 64 个回合中的 13 个。训练后的智能体在 64 个回合中有 59 次使用 fix_comparison,用对操作的只有 4 次。在第 20 步到第 80 步之间的某个时候,策略从源码到操作的映射对这个任务变得自信而错误,正确操作的概率从约 0.2 降到了 0.06。这正是那场争论中精确意义上的变窄:强化学习把概率集中到其他经验所奖励的行为上,并从一个偶尔有效的替代方案中拿走了大部分概率质量。图 17.3 展示了平均曲线和 speed 任务。 图 17.3:左:RL 基座智能体以及第 20 步和第 80 步 GRPO 检查点在 10 个留出任务上的无偏 pass@k,每个任务 64 个回合,基座智能体和 80 步检查点附任务级自助法区间带;虚线为在五种操作中均匀猜测。右:仅 zero/speed 一个任务上的相同曲线。 那么,强化学习在 TinyAgent 上是否让分布变窄了?本书测得:在 10 个任务中的 1 个上是的,而且变窄出现得很晚:20 步检查点在每个 kk 上都优于基座智能体。有两点注意事项限定了这一解读。第一,基座智能体在大 kk 下的覆盖率并不是一种能力:它的曲线紧贴在均匀猜测曲线之下,因为在五种操作的动作空间中,32 次猜测几乎总会包含正确的操作。只要答案空间很小,大 kk 下的 pass@kk 衡量的就是一个猜测者需要多少次尝试——这也是对短答案基准上大 kk 通过率的一项长期批评。第二,交叉只建立在一个任务和一次训练之上;只有 10 个任务时,80 步曲线的任务级区间带在 k≥16k \ge 16 处覆盖了基座智能体的曲线。尽管如此,这里的机制正是那场争论所讨论的机制,而逐任务视角就是找到它的方法:寻找在训练过程中成功次数下降的任务,并检查策略现在转而在做什么。
证据。 本书测得:一个 158k 参数模型的一次 80 步 GRPO 训练,10 个留出任务,每个检查点每个任务 64 个回合。文献报告:Yue et al. 与 ProRL(数学任务、大模型;见第 20 章)。这个小规模结果说明了测量方法和一种变窄机制;它不能作为这场争论中任何一方在大规模上的证据。

17.8 不碰测试集地选择检查点

每一个通过查看数字做出的选择,都会把那个数字变成训练信号。选择测试分数最高的检查点、再报告这个分数,报告的是多个带噪声估计中的最大值,它是向上有偏的:这就是赢家诅咒(winner’s curse)。若有 mm 个真实水平相同的检查点、评测噪声为 σ\sigma,期望最大值约为 μ+σ E[max⁡i≤mZi]\mu + \sigma\,\mathbb{E}[\max_{i \le m} Z_i],当 m=8m = 8 时为 μ+1.42σ\mu + 1.42\sigma。解决办法与超参数相同:把留出任务划分为用于每一个决策的验证集,以及只在最终报告时评测一次的测试集。只有 10 个留出任务,我们只能五五分,每一侧包含每种缺陷类型的一个变体。
Output:
验证列与测试列在水平上的分歧大于在形状上的分歧。验证分数在第 80 步达到 0.975,而测试分数从第 40 步起一直停留在 0.74 附近,因为测试那一半包含 speed,验证那一半不包含:每个划分只有五个任务时,划分的构成决定了水平。按验证选择会选中第 80 步,其测试分数 0.744 对所选检查点是无偏估计,因为测试集没有参与选择。按测试选择会选中第 70 步,报告 0.762。从一次评测无法看出这个具体数字是否被高估。赢家诅咒是关于这一选择流程期望值的陈述,所以我们通过重复这个流程来测量它:做 10 次小规模测试评测(每个任务 8 个回合),每次用来挑选一个检查点,再用另外 9 次没有参与挑选的评测给所选检查点打分。
Output:
不同的小规模评测挑出了不同的检查点(第 30 步到第 80 步,最常见的是第 50 步),这说明现有评测精度不足以稳定区分后期检查点。被选中的检查点在挑选它的评测上得 0.795,在其余评测上得 0.766:在测试集上做选择把报告的数字抬高了 0.03,大约是一次这种 40 回合评测标准误的一半。用 160 回合测试集和八个同样好的检查点做的模拟给出了相同的量级,比真值高 0.04。针对同一测试集尝试的检查点、配置和提示越多,高估就越大;这就是为什么一个团队每周都拿来评测的测试集,会不再是测试集。图 17.4 展示了附任务级区间的检查点表,以及 17.4 节的四个随机种子及其两种区间。 图 17.4:左:一次 GRPO 训练各检查点的验证与测试 pass@1(各 5 个任务,每个任务 32 个回合),附任务级自助法 95% 区间。右:第 30 步的四个随机种子,每个都附有在全部 10 个留出任务上的回合级与任务级自助区间。 左图中的区间从第 40 步起几乎完全重叠。只有五个测试任务时,这些检查点的任何排序都没有依据,站得住脚的报告是按验证集选出的检查点的测试分数及其区间。在更大的评测中,同样的规程可以照搬:在验证集上做决策(包括早停、检查点平均和解码设置),对每个要报告的模型只在测试集上评测一次,并报告有多少个模型在该测试集上评测过。

17.9 实践中的设计问题

问题:评测噪声大于效应。 现象: 消融表中各组相差几个百分点,周期性评测曲线呈锯齿状,或者一次“胜出”在重跑后消失。 原因: 在正确单元上计算的评测标准误(17.3 节)与效应一样大,而比较是非配对或没有重复的。train_rl 的周期性 eval_pass 每个任务用 4 个回合,在留出集上共 40 个;下面的代码块以这个规模对同一个检查点重复评测八次。 可选方案: (a) 增加每个任务的回合数,这在任务项 σtask2/N\sigma^2_{\text{task}}/N 占主导之前有效;(b) 增加任务数,这是减小该项的唯一办法;(c) 在相同任务上做配对比较,比较检查点时使用公共随机数;(d) 当论断针对配方时,增加训练种子数。 建议做法: 在做消融之前,先从一次试点评测计算配对标准差,并用 required_units 确定规模;在任务级别报告区间。
Output:
同一个检查点在八次 40 回合评测中得分介于 0.80 和 0.90 之间,极差 0.10。第 9 章的预设表和消融表比较的各组都是用 80 个回合评测的,噪声水平相当,而它们的大多数差异都低于这个极差;这就是第 9 章无法对其变体排序的原因。
问题:评测框架漂移。 现象: 同一检查点的数字在两个监控面板之间不一致,复现论文时差了几个百分点,或者改进恰好出现在评测代码改动之时。 原因: pj(π,H)p_j(\pi, H) 依赖于评测框架,而某个评测框架字段不同了:温度、预算、模板、环境规则或成功判据(17.5 节)。当被改动的字段是弱策略可以利用的字段时,漂移最危险。下面的代码块运行一个没有技能的策略——它按固定顺序施加三种操作然后提交——分别在训练评测框架下和允许无限编辑的普通 BugFixEnv 下运行。第 15 章的 pass_rate 辅助函数在普通环境中评测,这与第 15 章自己的训练设置一致,但与第 9 章起的默认设置不一致,所以两者的数字不能直接比较。 可选方案: (a) 从训练配置对象构建评测,就像 sample_counts(..., cfg) 那样,使每个字段在构造上就是共享的;(b) 为评测框架加版本号,并随每个数字一起记录;(c) 让一个固定的参考策略(脚本策略或一个冻结的检查点)经过每一次评测框架改动,并要求它的数字保持不变。 建议做法: (a) 和 (c)。一个分数已知的金丝雀策略能捕捉到的漂移,无论怎样审视被评测的模型都发现不了。
Output:
同一个没有技能的策略,在训练评测框架下通过 0.20 的留出任务,在普通环境下通过 0.60,因为普通环境允许它叠加三次猜测(9.4 节)。一个在这两种评测框架之间比较的模型,会被记上一个它并未取得的 40 个百分点的提升。
问题:评测复用了训练奖励。 现象: 训练奖励和评测数字一起上升,而背后的行为并不是预期的技能。 原因: 当评测用策略被优化所针对的同一个函数来计算成功时,它会继承优化器找到的每一个缺陷:可以被特判的公开测试、可以被讨好的评判模型、不解决任何问题也能满足的格式检查(第 11 章)。下面的代码块让第 4 章的脚本化投机策略(它对公开测试的输入做特判)分别在公开测试奖励和隐藏测试奖励下运行 gym。 可选方案: (a) 用比训练奖励更强的留出验证器评测(隐藏测试、更多测试、不同的测试生成器);(b) 人工或借助行为统计抽查通过的轨迹;(c) 把训练奖励与留出验证器之间的差距作为监控面板指标来跟踪。 建议做法: 始终采用 (a),并以 (c) 作为警报。无论 cfg.reward 是什么,sample_counts 都用隐藏测试打分,所以即使强化学习是在可投机的奖励上训练的,评测仍然是诚实的。
Output:
在自己的训练奖励下,投机策略得到满分 1.00;在隐藏测试下得 0.00。复用公开测试奖励的评测会报告出一个完美的智能体。
问题:用测试集选择检查点。 现象: 报告的测试分数再也没有被重现过,无论是重跑、新的测试集还是用户。 原因: 每一个通过查看测试数字做出的决策(检查点、解码温度、提示、报告哪一次训练)都是在噪声上做选择,报告的最大值大约向上偏 σ E[max⁡iZi]\sigma\,\mathbb{E}[\max_i Z_i](17.8 节中为 0.03 到 0.04)。 可选方案: (a) 用验证集做所有决策,测试集只用一次;(b) 任务太少无法划分时,报告预先登记的最终检查点,而不是最好的那个;(c) 报告在测试集上评测过的模型数量,让读者判断选择偏差可能有多大。 建议做法: (a),并在测试评测之前写下选择规则。对于长期使用的基准,要定期更新测试集,因为即使每个决策都很谨慎,团队累积的决策仍会泄漏进去。

小结

  • 评测估计的是 pj(π,H)p_j(\pi, H),即某个评测框架下每个任务的成功概率,其原始数据是每个任务的成功次数。sample_counts 用训练配置生成这些数据,并且无论训练奖励是什么,都用隐藏测试打分。
  • pass@kk 需要无偏估计量 1−(n−ck)/(nk)1 - \binom{n-c}{k}/\binom{n}{k},以乘积形式计算;在 n=10n = 10 时,代入估计 1−(1−p^)k1 - (1-\hat p)^k 把 pass@5 低估了 0.04 到 0.08。
  • 同一任务的回合是相关的。对训练后智能体这样异质的任务,设计效应为 20,任务级区间的宽度是回合级区间的 4.4 倍;在模拟评测中,对回合重采样只在 38% 的情况下覆盖总体均值。单个任务用 Wilson 区间,基准用任务级自助法。
  • 在相同任务上比较:配对把检查点差异的标准误从 0.132 降到 0.023。用功效分析确定比较的规模(检测 5 个百分点需要 17 个配对任务,非配对时每组 533 个),并用相互独立的评测测量种子方差;在这里第 30 步时,它与评测噪声无法区分。
  • 评测框架的一个字段就让训练后智能体的留出通过率最多变化 0.13,一个更宽松的环境让无技能策略的分数变为三倍。留出变体(0.93)和留出类型(0.03)对同一个检查点给出了相反的结论。
  • 基座智能体在 k=32k = 32 时反超 80 步检查点(0.998 对 0.988),原因是强化学习把一个任务上正确操作的出现次数从 64 次中的 13 次压到了 4 次;当 kk 足够大时,五种操作空间中的猜测者能覆盖一切。在测试集上做选择把报告分数抬高了约 0.03;应在验证集上决策,在测试集上只报告一次。

练习

  1. 利用 cc 是完备充分统计量这一事实,证明 1−(n−ck)/(nk)1 - \binom{n-c}{k}/\binom{n}{k} 是基于 nn 个伯努利样本的 1−(1−p)k1-(1-p)^k 的最小方差无偏估计量。然后通过模拟计算 n=64n = 64、k=32k = 32、p=0.05p = 0.05 时它的方差,并与 k=4k = 4 时比较。
  2. 改变一个假设:以每个任务 n=256n = 256 个回合、kk 最大到 128 重跑 17.7 节,并加入第 40 步检查点。交叉点会移动吗?除 speed 之外,是否还有其他任务失去覆盖?绘制所有检查点的逐任务成功次数,找出 speed 开始下降的步数。
  3. 实现任务级自助法的贝叶斯替代方案:每个任务一个 Beta 先验,任务通过率采用层次模型,在 torch 中用简单的 Gibbs 采样或重要性采样求解。在 17.1 节的计数上,将其区间与 bootstrap_ci 比较。
  4. 对五种缺陷类型中的每一个,重复 17.6 节的留出类型实验。跌到基座智能体猜测率以下的现象是否普遍?把结果与未见过的缺陷类型回合开始时第一次编辑的熵联系起来(第 10 章)。
  5. 编写一个评测框架金丝雀:一个函数,让一组固定的脚本策略(oracle、blind、17.9 节的 stacker)通过 make_env(cfg) 运行,并断言它们的通过率。它能检测出 RLConfig 中哪些字段的改动,又会漏掉哪些?
  6. 公共随机数:比较第 60 步与第 80 步差异的配对标准误,一种是两个检查点使用同一个生成器,另一种是使用不同的生成器。用 17.4 节的方差公式解释其差别。
  7. 设计评审:一位同事报告,一种新的优势估计量把 SWE-bench Verified 的 pass@1 从 41.0% 提升到 43.4%,每组只有一次训练、每个任务只采样一次,而且检查点是“最后五个中最好的那个”。运用本章内容,列出你在接受这一论断之前会要求补充的内容,分别在非配对和配对分析下估计所报告差异的标准误,并说明你会要求多少个随机种子和多少次采样。