Δ 与置信区间
在 B1→B18 能力曲线上,今天是 B12 实验段的「下结论之前的最后一道数学关」:昨天(Day 116)已经把 tuned 候选(Qwen3 经 OpenRouter)在与 base 完全相同的配置下跑出 agent-evals/b12-tuned.json,于是手里有了两份按 taskId 可对齐的报告。今天要做的是把它们配对求差值 Δ,再用 bootstrap 估 95% 置信区间——只有
阶段: B12 · RLVR + 模型策略 memo(Day 111-120) 标签: #paired-bootstrap #confidence-interval #ab-compare #eval-rigor
今日导引(由浅入深)
在 B1→B18 能力曲线上,今天是 B12 实验段的「下结论之前的最后一道数学关」:昨天(Day 116)已经把 tuned 候选(Qwen3 经 OpenRouter)在与 base 完全相同的配置下跑出 agent-evals/b12-tuned.json,于是手里有了两份按 taskId 可对齐的报告。今天要做的是把它们配对求差值 Δ,再用 bootstrap 估 95% 置信区间——只有点估计的 Δ 是不够的,必须带上 CI 才能判断「这个差异是真信号还是采样噪声」。今天的最小可判定产出是一个会绿的测试 delta-ci.test.ts,它复用本仓已落地的 src/agent/eval/abCompare.ts,输出 Δ±CI 数字。这是 M5 L3 gate。明天(Day 118)才据 CI 是否跨 0 做显著性裁决。
1. 机理精读
为什么要配对(paired),而不是两组独立比较。
base 与 tuned 跑的是同一套任务集,于是每个 task 上都能算一个配对差值 Δᵢ = passᵢ(tuned) − passᵢ(base),取值 ∈ {−1, 0, +1}。
配对设计的关键好处是消除任务难度方差:同一道难题对两个模型都难,这部分共同方差在做差时被抵消掉,剩下的方差只反映「模型之间的真实差异」。
相比把 base 的所有分数当一组、tuned 当另一组做独立两样本检验,配对设计在相同样本量下功效更高、CI 更窄。
这正是 src/agent/eval/stats.ts 顶部注释写的「pairing on the same tasks removes task-difficulty variance, so the CI on the delta is tight」。
直觉上:独立比较要同时估两个均值的方差,配对比较只估一个差值序列的方差——少估一层,CI 自然窄。
bootstrap 怎么把「一份数据」变出置信区间。 我们只跑了一次实验,拿到一组配对差值 {Δ₁…Δₙ}。 bootstrap 的思路是:把这 n 个配对差值当作总体的近似,有放回地重采样 n 个、算一次均值,重复 B 次(这里 B=1000),得到 1000 个「均值的副本」。 这 1000 个副本的分布就近似了「如果重做很多次实验,Δ 均值会怎样波动」——即 Δ 均值的抽样分布。 取这 1000 个均值的 2.5% 和 97.5% 分位数,就是 95% 的 percentile CI。 它不依赖正态假设、不需要解析方差公式,对小 N、非正态的二元 pass/fail 差值特别适用——这正是评测场景的常态。
序贯停止判据决定「何时可以下结论」。 固定样本检验要求事先定 n、跑满再看;序贯检验(always-valid p-value,Statsig 2026-01)允许边跑边看而不膨胀假阳率。 在 base-vs-tuned 这种「跑一批攒一批」的离线评测里,序贯停止判据让我们能在 CI 收窄到能拒绝/接受 H0 时就停,而不必盲目跑满 n≈70。 但要警惕:用固定阈值 p 值反复偷看(peeking)会膨胀假阳——每偷看一次就多一次「碰巧越线」的机会,这正是 N=29 那次教训的根因之一。
与已落地 A/B 的关系:同一套算法、不同输入。 本仓真实跑过一次 A/B(V4-Pro vs V4-Flash),用的就是今天要复用的配对+CI 实现,产出 N=29、Δ+10.3pp、95% CI [0.0, 20.7]、3 wins / 0 losses / 26 ties。 注意三个细节:(1) CI 下界正好触 0,是「点估计可观、但不显著」的活教材;(2) wins=3、losses=0、ties=26 说明绝大多数任务两模型都对(天花板效应),真正区分两者的只有 3 道题;(3) 正因为区分信号只来自 3 道题,N=29 的功效极低,要把 CI 推离 0 就得加更多「能区分两模型」的难任务。 今天 b12 的 base-vs-tuned 走完全相同的算法,只是喂的是 b12-base/b12-tuned,所以 test 骨架与 CI 算法可以立刻绿,具体 Δ 数字则待两份报告跑出来。
2. 代码走读:abCompare + pairedBootstrap
Read 了 src/agent/eval/abCompare.ts 与 src/agent/eval/stats.ts,关键符号与行为:
abCompare(a, b, opts)(abCompare.ts)— 入参是两份AbReportLike(各含perTask: {taskId, judge:{label}})。流程:passMap(r)把每份报告压成Map<taskId, 0|1>(label==='pass' → 1,否则 0)。- 取两份报告 taskId 的交集并排序(
ids),无交集直接抛'no common taskIds'——这是「两份报告必须同任务集」的硬约束。 - 对齐后的
av/bv调pairedBootstrap求deltaMean与ci95。 - 逐 task 算
av[i]-bv[i]统计 wins(A 过 B 败)/ losses(B 过 A 败)/ ties。 - 返回
AbResult:modelA/modelB、nAligned、completionA/completionB、deltaMean、ci95、wins/losses/ties。
pairedBootstrap(a, b, opts)(stats.ts)—iters默认 2000(今天按 seed 用 1000)、rng可注入(默认Math.random,测试里注入确定性 rng 才能复现)。先算diffs = a[i]-b[i],再循环 iters 次「有放回重采样 n 个 diff、累加求均值」,把均值排序后取percentile(means, 2.5)与percentile(means, 97.5)做ci95,deltaMean是原始 diffs 的均值。requiredNForDelta(delta, sdDiff)(stats.ts)— 正态近似的样本量公式 n = ((z_{α/2}+z_β)·sd/|Δ|)²(α=.05 双侧、power=.8)。这是「N=29 不够、需 ~70」估算的算法出处:把已观测的 Δ 与配对差 SD 代进去就得到目标 n。- 二者皆纯函数、RNG 可注入、无网络——
delta-ci.test.ts注入固定 rng 即可确定性绿,无需 API key。
走读结论:今天不需要新写 CI 算法,delta-ci.test.ts 只是把 b12-base/b12-tuned 两份 json 喂给 abCompare、断言 Δ±CI 字段,复用的就是已产出 N=29 结果的同款实现。
一个最小手算例(验证你真懂 paired bootstrap)
设只有 5 道任务,配对差值(tuned − base)为 diffs = [+1, 0, 0, +1, 0],则:
deltaMean = (1+0+0+1+0)/5 = 0.4(即 tuned 比 base 多对 40% 的任务)。- bootstrap 一次:有放回抽 5 个下标,例如抽到
[0,0,3,1,4] → [+1,+1,+1,0,0],该次均值 = 0.6;再抽[1,2,4,2,1] → [0,0,0,0,0],该次均值 = 0.0。 - 重复 1000 次,把 1000 个均值排序,取第 25 个(≈2.5%)与第 975 个(≈97.5%)做
ci95。 - 因为 diffs 里有 0.0 这种全抽到 0 的可能,CI 下界很容易压到 0 甚至更低——这就是小 N 下界触 0 的机制,和 N=29 那次同源。
requiredNForDelta(0.4, sd(diffs)) 则给出「要把这个 Δ 测显著需要多少配对」——SD 越大、Δ 越小,需要的 n 越多。
3. 今日实战
- 新建
delta-ci.test.ts:读agent-evals/b12-base.json与agent-evals/b12-tuned.json,构造成AbReportLike({model, perTask:[{taskId, judge:{label}}]})。 - 调
abCompare(tuned, base, { rng: <固定 seed rng>, bootstrap: 1000 }),配对求 Δ 与 95% CI。 - 断言:
nAligned等于两份报告 taskId 交集大小;deltaMean与ci95为有限数;wins+losses+ties === nAligned。注入固定 rng 让 CI 可复现、test 确定性绿。 - 在 test 里同时调
requiredNForDelta(deltaMean, sdDiff)打印目标样本量,复刻「N=29→需 ~70」的算法路径,作为 memo 里 n 阈值的来源。 - 这是 M5 L3 gate——test 绿即门通过;具体 Δ 数字待 b12-base/tuned 真跑出来后填入。
4. 今日实测 / 产出
- 产出物:
delta-ci.test.ts通过 + Δ±CI 数字。 - 状态:test 骨架与 CI 算法已可绿(复用
abCompare.ts,纯函数注入固定 rng);b12 base-vs-tuned 的具体 Δ = 待跑(需 key 跑 b12-base/tuned)。 - 已落地真实参照 A/B(V4-Pro vs V4-Flash,同款配对+CI 实现):Δ+10.3pp、95% CI [0.0, 20.7]、3 wins / 0 losses / 26 ties、N=29。这是 b12 之外、已经跑出的锚点数字,可写入 memo 作对照;b12 自身的 Δ 未跑出前不得臆造。
5. 常见误区 / 陷阱
- 只报点估 Δ、不报 CI:N=29 的 +10.3pp 看着可观,但 CI 触 0([0.0, 20.7])——这是反例。Δ 必须永远带 CI。
- bootstrap 不注入 rng:用默认
Math.random跑 test,CI 每次不同,test 非确定性。务必注入固定 seed rng。 - 两份报告 taskId 集合不一致:
abCompare只取交集,错配会让nAligned缩水、CI 变宽甚至抛 'no common taskIds'。落盘前对齐任务集。 - peeking(反复偷看固定阈值 p 值):在小 N 上边跑边用固定阈值判显著,会膨胀假阳——要么跑满预定 n,要么用 always-valid 序贯检验。
- 把 wins 数当成显著性证据:wins=3/losses=0 看着「全胜」,但 26 ties 意味着区分信号只来自 3 道题,N 实质极小。wins/losses 是描述性辅助,定论看 CI。
- iters 太小导致 CI 端点抖动:小 N 时 1000 次 bootstrap 的分位数本身有蒙特卡洛噪声,必要时加大 iters 至 2000+ 让 CI 稳定。
6. 学习资源(每条带 YYYY-MM)
- Statsig — Sequential Testing / always-valid p-values(2026-01):序贯停止判据、避免 peeking 膨胀假阳。
- Efron & Tibshirani — An Introduction to the Bootstrap(经典方法论,配 2026 现役工程实践):percentile bootstrap CI 原理。
- Anthropic — Demystifying evals(2026-01):Δ 必带 CI、小 N 高方差的 eval 严谨性。
- 本仓代码:
src/agent/eval/abCompare.ts、src/agent/eval/stats.ts(pairedBootstrap/requiredNForDelta,已 built+tested)。
7. 面试视角 / 关键要点
AISA 面试常用本日内容追问「你怎么知道模型 A 真比 B 好」,几条标准答法:
- 「为什么配对而不是两组独立比较?」 → 同一任务集做配对,差值消除任务难度共同方差,CI 更窄、功效更高;独立比较要估两个均值方差,多一层噪声。
- 「为什么用 bootstrap 而不是 t 检验?」 → pass/fail 差值是离散非正态、N 小,bootstrap 不依赖正态假设,对二元配对差更稳。
- 「Δ+10.3pp 不是挺大吗,为什么说不显著?」 → 看 CI:[0.0, 20.7] 下界触 0,N=29 功效不足,区分信号只来自 3 道题;要把 CI 推离 0 需 ~70 任务。
- 「怎么决定要跑多少任务?」 →
requiredNForDelta(Δ, sdDiff):正态近似算目标 n(α=.05、power=.8),SD 大/Δ 小则需更多。
一句话总结:Δ 永远配 CI,配对 bootstrap 求 CI,CI 触 0 就别下结论——这是本日交付 delta-ci.test.ts 的全部精神。
SOTA检查 (2026-06 更新)
- 现役主线:bootstrap 配对 CI + 序贯停止(Statsig 2026-01)是离线/在线模型对比的现役统计方法。配对设计消除任务难度方差、CI 收窄,是 2026 eval 严谨性标配。
- 避免/禁用:避免只报点估 Δ 不报 CI——N=29 的 +10.3pp 触 0 即反例。避免在小 N 上用固定阈值 p 值反复偷看(peeking)。
- 下次复查点:复查 Statsig 序贯检验文档版本(执行当周);复查 bootstrap iters(1000/2000)在 b12 实际 N 下是否足够稳定(小 N 时 CI 端点抖动,可加大 iters)。
8. 产出状态速查
| 项 | 状态 | 备注 |
|---|---|---|
delta-ci.test.ts(骨架 + CI 算法) | 已可绿 | 复用 abCompare.ts,注入固定 rng 确定性 |
| b12 base-vs-tuned 具体 Δ | 待跑(需 key 跑 b12-base/tuned) | 两份 json 跑出后填入 |
abCompare / pairedBootstrap | 已 built+tested | 已产出 N=29 A/B 的同款实现 |
| 真实参照 A/B | 已落地 | Δ+10.3pp、CI[0.0,20.7]、3 wins/0 losses/26 ties、N=29 |
衔接
- 昨天:Day 116 — Tuned 模型对照(Qwen3 经 OpenRouter 跑同配置,产出
agent-evals/b12-tuned.json)。 - 今天:按 taskId 配对 base/tuned,bootstrap 1000 次求 Δ±95% CI,
delta-ci.test.ts绿(M5 L3 gate)。 - 明天:Day 118 — 结果显著性裁决(CI 是否跨 0 + Cohen's κ 复核两套标注一致性,写
verdict.md)。