返回 AICAP-180
B3 · Day 28agent loop + 评测统计

paired bootstrap + CI

Day 27 拿到了两模型在同一 29 任务上的 Δcompletion,但一个点估计(比如 Δ=+8pp)不能直接拿来选型——它有抽样误差,可能真值横跨 0。

阶段: B3 · agent loop + 评测统计(Day 21-30) 标签: #paired-bootstrap #confidence-interval #ab-testing #variance-reduction

今日导引(由浅入深)

Day 27 拿到了两模型在同一 29 任务上的 Δcompletion,但一个点估计(比如 Δ=+8pp)不能直接拿来选型——它有抽样误差,可能真值横跨 0。

今天 Day 28 给这个 Δ 加误差棒:用配对 bootstrap 重采样 2000 次,取 Δ 分布的 2.5/97.5 百分位得 95% CI;CI 含 0 则差异不显著

在 B1→B18 曲线上,这是从「看上去高」到「统计上真高」的关键一跳——承接 Day 23 的 Wald CI(单率误差棒),升级为「两率之差的误差棒」。明天 Day 29 接着问「要稳定看到 Δ5pp 需要多少任务」(power/样本量)。今天的最小可判定产出是:src/agent/eval/stats.tspairedBootstrap + 单测在确定性种子下绿(已达成),Δ 真实数值待 Day 27 真模型跑数填入。

1. 机理精读

为什么用配对(paired)而非独立两样本。

两模型跑的是同一批任务——任务难度(有的题天生好做、有的天生坑)是两模型共享的方差源:

  • 用独立两样本检验,任务难度方差会进到两个样本里,把信噪比压低;
  • 配对设计直接对同一任务的 a、b 成对取差 diff_i = a_i − b_i,任务难度作为公共项被消去,只剩「同一题上两模型的差」。

结果是 Δ 的 CI 更紧、检验更敏感——同样的 N,配对能检出更小的真实差异。这是方差缩减(variance reduction)的经典手法,也是 A/B 评测必须配对的根本原因。

bootstrap 的直觉:用重采样模拟「重跑实验」。

我们只有一份 29 个配对差。bootstrap 的核心假设是:这 29 个差就是总体的经验分布。

  • 从中有放回地抽 29 个、求均值,重复 B=2000 次;
  • 得到 2000 个「Δ 均值」——近似了「如果重做 2000 次实验,Δ 会落在哪」;
  • 把这 2000 个 Δ 排序,取第 2.5 和第 97.5 百分位,就是 percentile 95% CI。

若这个区间跨 0,说明「Δ 可能是正、可能是负、也可能恰好是 0」,差异不显著,不能据此选型。

为什么是 2000 次,而不是 200 或 20000。

bootstrap 迭代数控制的是 CI 端点的蒙特卡洛噪声,不是统计功效本身。Miller《Adding Error Bars to Evals》(2024-11) 把 2000 次作为稳健下限:再少端点会抖,再多边际收益递减。本仓 pairedBootstrap 默认 iters=2000,与该建议一致。

percentile bootstrap 的边界与替代。

  • 对极少样本,percentile CI 会偏窄(低估不确定性);
  • 更严谨可用 BCa(偏差校正 + 加速);
  • 但本套件 29 任务下 percentile 已够用——这是工程取舍:BCa 实现复杂、收益边际,29 任务不值得。

真正的硬约束是:绝不用裸点估计下选型结论,无 CI 的 Δ 不予采信(评审硬要求误差棒,承接 Day 23 的 Wald CI 思路)。

这一步与 power 的边界。

今天解决的是「拿到 Δ 后,它显不显著」;它回答「为了能稳定检出某个 Δ,我该准备多少任务」——那是 Day 29 的 power/样本量反推。stats.tsrequiredNForDelta 已经为明天埋好,但 Day 28 的产出聚焦在 pairedBootstrap 本身。

2. 推导 / 手算 / 代码走读

代码在 src/agent/eval/stats.ts(纯函数、RNG 可注入、无 key):

  • pairedBootstrap(a, b, { iters, rng })(第 36–55 行):
    • 先校验 a.length === b.length(否则 throw 'arrays must be equal length'),n=0 也 throw(需 ≥1 对);
    • diffs = a.map((x,i)=>x - b[i]),默认 iters=2000rng=Math.random
    • 重采样循环:每轮内层 acc += diffs[Math.floor(rng()*n)] 累加 n 个有放回抽样、means.push(acc/n)
    • 最后 means.sort((x,y)=>x-y),返回 { deltaMean: mean(diffs), ci95: [percentile(means,2.5), percentile(means,97.5)], n, iters }
  • percentile(sorted, p)(第 17–26 行):线性插值百分位,idx=(p/100)*(len-1),对 lo/hi 加权——保证 2.5/97.5 不是粗暴取整。
  • mean / sd(第 5–15 行):sdn-1(样本标准差),n<2 返回 0。
  • requiredNForDelta(delta, sdDiff)(第 59–65 行):明天 power 用,公式 n = ((z_{α/2}+z_β)·sd/|Δ|)²zAlpha=1.959963985(双侧 α=.05)、zBeta=0.841621234(power=.8),delta=0→Infinity,sdDiff<=0→2,结果 Math.max(2, ceil(...))

手算最小例(验直觉):设 5 个任务,模型 a 的逐题 0/1 得分与 b 成对:

diffs = [+1, 0, 0, +1, −1]
mean(diffs) = (+1+0+0+1−1)/5 = +0.2   // a 比 b 高 20pp 点估计

这 5 个差里有正有负,bootstrap 重采样均值会在 0 附近大幅抖动,95% CI 很可能跨 0 → 不显著。这正说明「+0.2 的点估计」在 N=5 下毫无说服力——CI 才是真相。把同样的 +0.2 放到 29 个一致为正的差上,CI 才可能不含 0。

为什么单测要注入固定 rng。 pairedBootstrap 默认用 Math.random,结果每次不同,无法断言。单测注入确定性种子 rng(如线性同余或固定序列),让 2000 次重采样完全可复现,CI 端点变成可断言的定值——这是「纯函数 + RNG 注入」设计的直接收益。

配对差为什么必须等长。 pairedBootstrap 开头就 throw 不等长输入——这不是防御性洁癖,而是配对的语义要求:a[i]b[i] 必须是同一个任务上两模型的得分,错位一格整组差就失去「消去任务难度」的意义。所以两份报告必须按 taskId 对齐后再取 completion 向量,不能假设两次跑的 perTask 顺序天然一致(虽然本仓 EVAL_TASKS 顺序固定,但对齐是更稳的做法)。

CI 的三种读法(落到结论):

  1. [+0.03, +0.18] 全正不含 0 → A 显著优于 B,可据此选 A;
  2. [−0.05, +0.21] 跨 0 → 未检出显著差异,转看成本/延迟/unknown 率;
  3. [−0.20, −0.04] 全负不含 0 → B 显著优于 A。

第二种是最常见也最容易被误读的——它不是「两者一样好」,而是「这个实验还没本事区分它们」(power 不足,见 Day 29)。

3. 今日实战

  1. src/agent/eval/stats.ts 已写:mean/sd/pairedBootstrap(a,b)→Δ点估计 + 95% CI、requiredNForDelta
  2. 单测覆盖已知小样本(确定性种子,注入固定 rng 使结果可复现),运行 pnpm test 验证 stats.test 绿。
  3. 待 Day 27 真模型跑出 DeepSeek-vs-Qwen 的逐题 0/1 completion 向量后,把两向量喂 pairedBootstrap 得 Δcompletion 的真实 95% CI;Day 30 据 CI 是否含 0 写选型结论。

4. 今日实测 / 产出

  • stats.ts + 单测已建pnpm test 该文件绿(mean/sd/pairedBootstrap+95%CI/requiredNForDelta,测试通过)。已完成
  • 全套件 378 tests passing、tsc clean已完成
  • Δ 的真实数值仍待 Day 27 真模型跑数填入——不臆造。

5. 常见误区 / 陷阱

  • 用独立两样本而非配对:白白让任务难度方差进来,CI 变宽、检验变钝。
  • 报点估计不报 CI:评审硬要求误差棒;裸 Δ 不予采信。
  • CI 含 0 还硬下「A 比 B 强」结论:含 0 = 不显著,必须如实说「未检出显著差异」。
  • bootstrap 不固定 rng 做单测:结果不可复现;本仓单测注入确定性种子正是为此。
  • 以为 percentile CI 永远对:极少样本下偏窄,必要时上 BCa——但 29 任务 percentile 够用。
  • 把 bootstrap 迭代数当统计功效:2000 次只是控蒙特卡洛噪声,与样本量 N(功效)是两回事。

6. 学习资源(每条带 YYYY-MM)

  • Miller, Adding Error Bars to Evals(2024-11)—— eval 误差棒权威,2000 次 bootstrap 为稳健下限。
  • Efron & Tibshirani, An Introduction to the Bootstrap(经典专著)—— percentile / BCa 区间原理(打底,非主线)。
  • 本仓 src/agent/eval/stats.ts + src/agent/__tests__/eval/stats.test.ts —— 实现 + 确定性单测走读。
  • Anthropic, Demystifying evals(2026-01)—— 选型须配 CI、勿用裸点估计的方法学背书。

SOTA检查 (2026-06 更新)

  • 当前主流:paired bootstrap 是 eval 对比的稳健标准做法,无过时风险,仍是 SOTA。
  • 是否仍 SOTA:是。配对设计 + 2000 次 percentile bootstrap + 报 CI 仍是 2026 评审基线做法。
  • 边界:对极少样本 percentile CI 偏窄,必要时配 BCa——但 29 任务下 percentile 足够。
  • 过时黑名单:勿用裸点估计下选型结论;勿用独立两样本检验代替配对设计;勿把迭代数当功效。
  • 下次复查点:Day 29 把 requiredNForDelta 用于 power/样本量反推;Day 30 接真实报告判 Δcompletion 95% CI 是否含 0。

衔接

  • 昨天:Day 27 — Qwen3 对比与稳健性(产出两模型逐任务 Δ)。
  • 今天:配对 bootstrap 2000 次给 Δ 加 95% CI,CI 含 0 即不显著;stats.ts 单测绿,全套 378 测试 + tsc clean。
  • 明天:Day 29 — 统计功效 power(反推检出 Δ5pp 所需任务数,量化「29 任务够不够」)。