返回 AICAP-180
B14 · Day 137外部 ground-truth AML eval

bootstrap 置信区间

在 B1→B18 的能力曲线上,今天是 B14「外部 ground-truth」段把点估计升级成区间估计的一跳。昨天(Day 136)在真标签 balanced_300 上用 binaryEval 出了第一张混淆矩阵 + recall/precision/FPR 三个点估计——但 N=300 的单点指标带抽样噪声,单看一个数字不能下结论。今天接着学 bootstrap 置信区间:对预测对有放回重采

阶段: B14 · 外部 ground-truth AML eval(Day 131-140) 标签: #bootstrap #confidence-interval #eval-rigor #percentile

今日导引(由浅入深)

在 B1→B18 的能力曲线上,今天是 B14「外部 ground-truth」段把点估计升级成区间估计的一跳。昨天(Day 136)在真标签 balanced_300 上用 binaryEval 出了第一张混淆矩阵 + recall/precision/FPR 三个点估计——但 N=300 的单点指标带抽样噪声,单看一个数字不能下结论。今天接着学 bootstrap 置信区间:对预测对有放回重采样 1000+ 次,每次重算三指标,取 2.5/97.5 分位得 95% CI。这直接服务于 Day 140 的 leaderboard——CI 重叠时不能宣称差异显著。今天的最小可判定产出是:recallCI95/precisionCI95/fprCI95 三对区间 + groundTruthEval.test.ts 的 CI 单测绿。其中 CI 单测已 built+tested,但喂进去的真实区间数字仍待跑/待数据。

一句话定位本日在评测严谨度链条上的位置:Day 136 回答「指标是多少」,今天回答「这个指标可信到什么程度」,Day 138 回答「这个指标有没有系统性偏置」,Day 140 才把三者合起来排榜。今天是从「一个数」走向「一个数 ± 它的不确定性」——这是 amateur eval 和 rigorous eval 的分水岭。

1. 机理精读

为什么点估计不够。 N=300 上算出的 recall=0.82 只是「这一组样本」上的观测值;换一组同分布的 300 条,会得到 0.79 或 0.85。这个波动幅度(抽样噪声)才是判断「两个模型谁更好」的关键——如果两个模型的 recall 差 3pp 但各自的抽样波动有 ±10pp,那这 3pp 差异毫无意义。报点估计而不报区间,等于把噪声当信号,是 eval 失信的头号原因。Anthropic《Demystifying evals》(2026-01)明确要求报告区间而非点估计。

举个 AML 场景的具体后果:假设规则基线 recall 点估计 0.78、LLM judge 0.83,看上去 LLM 赢 5pp。但若两者 N=300 的 CI 分别是 [0.70, 0.85] 与 [0.75, 0.90],区间大幅重叠——「LLM 更强」这个结论根本立不住,可能只是这一批 300 条恰好对 LLM 有利。若拿这个未经 CI 检验的 5pp 去做「弃规则、上 LLM」的架构决策,就是在噪声上下赌注。这正是今天为什么必须先建 CI、再到 Day 140 才允许排榜的根本原因。

bootstrap 的核心思想。 真实世界里我们只有一组样本,没法「再抽 1000 组」来观察波动。bootstrap 用一个巧妙的替身:把手头这组样本当作总体的近似,从它里面有放回重采样出 N 条构成一个「伪样本」,重复 B 次,每个伪样本算一次三指标,得到 B 个指标值的经验分布。这个分布的 2.5/97.5 分位就是 95% 置信区间。它不假设指标服从正态分布——这对极不平衡数据的 FPR 尤其重要(FPR 在小 FP 计数下分布高度偏斜,正态近似会给出越界或失真的区间)。

CI 宽度由三个因素共同决定,把它们记牢能快速诊断「这个区间为什么这么宽」:

  1. 样本量 N——宽度 ∝ 1/√N,这是最主要的杠杆。N 翻 4 倍,CI 宽度才减半。
  2. 某类的支撑数(support)——recall 的有效样本量是真正例数(TP+FN),FPR 的是真负例数(FP+TN)。balanced_300 平衡了两类,但 FPR 在误报极少时 FP 计数小、区间仍可能偏宽。
  3. 指标本身的取值位置——比例在 0.5 附近方差最大、靠近 0 或 1 时方差小(伯努利方差 p(1-p))。recall=0.95 的 CI 通常比 recall=0.5 的窄。

诊断时按这三条对号入座:CI 宽,先看是 N 不够、还是某类 support 太少、还是指标恰好落在 0.5 高方差区。

为什么是 percentile 法而非正态近似。 正态近似 CI = 点估计 ± 1.96·SE,但它要求指标近似正态、SE 可估。AML 的 FPR 分母 (FP+TN) 里 FP 可能是个位数,重采样分布严重右偏,正态法会算出负的下界或超过 1 的上界——荒谬。percentile bootstrap 直接取经验分布的分位数,天然落在 [0,1] 且尊重偏斜形状。本仓 binaryEvalWithCI 走的就是 percentile 法。

N=300 这个样本量是怎么定的(与 Day 134 呼应)。 Day 134 抽 balanced_300(约 150 正 / 150 负)不是随手定的:CI 宽度大致随 1/√N 收窄,N=300 在「平衡子集上每类约 150 例」时,对 recall/precision 这类比例指标能给出可用宽度的 CI;但对 FPR(分母是真负例)在误报极少时仍可能偏宽。这是个工程权衡——再大要更多模型调用成本,再小 CI 宽到无法下结论。今天的 CI 正是验证「这个 N 够不够」的体温计:若三指标 CI 都还可用,说明 N=300 选对了;若某指标 CI 跨越半个区间,下次得加样本。

CI 刻画的是方差(噪声),不是偏差(系统性偏置)。 这是一个常被混淆的边界:bootstrap CI 回答「换一组同分布样本,这个指标会抖多少」——它度量抽样方差。它回答「我的裁判是不是系统性地偏严/偏松」——那是偏差,CI 再窄也消不掉。一个有系统性偏置的裁判可以给出又窄又稳的 CI,但整体偏移了真值。所以 CI 是必要条件不是充分条件:今天给指标加 CI 控方差,Day 138 用第二模型交叉验证去查偏差,两者合起来才完整。

CI 与 leaderboard 可比性的直接关系。 Day 140 要把规则基线 vs deepseek-v4-flash vs Qwen3 排成榜单。判断「A 比 B 好」的合法依据不是「A 的点估计更高」,而是「A 的 CI 下界高于 B 的 CI 上界」(或更严格的配对检验)。CI 重叠 → 只能说「方向性更优」,不能宣称显著。今天建好 CI,是为明天的 leaderboard 立下「不许凭点估计宣称胜出」的纪律。

与相邻概念的边界。 今天 ≠ Day 136(那是点估计,今天加区间);今天 ≠ 配对 bootstrap(pairedBootstrap 是 A/B 同任务配对求 Δ 的 CI,今天是单指标的无配对 CI);今天的 1000 次重采样 ≠ 训练迭代。今天只回答:三个指标各自的 95% 置信区间有多宽。

2. 手算 + 代码走读

2.1 手算:bootstrap 一遍 recall 的 CI

为了把「重采样→分位」钉死,用一个微型例子手过一遍。设 6 条预测对(正=可疑),其中 5 个 TP、1 个 FN(全是真可疑),点估计 recall = 5/6 ≈ 0.833。bootstrap:每次从这 6 条有放回抽 6 条,算这次的 recall。

  • 抽到 [TP,TP,TP,TP,TP,TP](恰好把那个 FN 漏掉)→ recall=6/6=1.0;
  • 抽到 [TP,TP,TP,TP,TP,FN] → recall=5/6≈0.833;
  • 抽到 [TP,TP,TP,FN,FN,TP](FN 被抽中两次)→ recall=4/6≈0.667;
  • ……重复 B=1000 次,得到 1000 个 recall 值。

把这 1000 个值排序,取第 2.5 百分位与第 97.5 百分位——比如得到 [0.5, 1.0]。这就是 recall 的 95% CI。注意区间这么宽(0.5~1.0)正是因为 N=6 太小;N=300 时区间会窄得多。这演示了一个关键直觉:CI 宽度主要由样本量驱动,小样本上即便点估计漂亮,区间也宽到不能下结论——这正是 Day 140 leaderboard 要警惕的。以上数字为手算演示,非真实跑分。

2.2 代码走读:binaryEvalWithCI 的 percentile bootstrap

Read 了 src/aml/groundTruthEval.tssrc/agent/eval/stats.ts,关键走读:

  • binaryEvalWithCI(items, opts) 先调 binaryEval(items, normalLabel) 拿点估计,再做 bootstrap。opts{normalLabel, bootstrap, rng}——bootstrap 默认 2000 次,rng 可注入(默认 Math.random,测试里注入确定性 PRNG)。
  • 入参校验 fail-closedif (!Number.isInteger(B) || B < 1) throw new Error('binaryEvalWithCI: bootstrap must be a positive integer')——非正整数直接抛错,绝不静默返回 NaN 区间。测试 throws on a non-positive bootstrap count 验证了 bootstrap: 0/positive integer/
  • 默认 bootstrap=2000const B = opts.bootstrap ?? 2000——默认 2000 次足以让分位估计稳定,测试里为提速用 1500/500。次数越多分位越稳,但有边际递减,2000 是常见甜点。
  • 重采样主循环for (let s=0; s<B; s++) 内,sample[i] = items[Math.floor(rng()*n)]——有放回抽 n 条构成伪样本(伪样本大小恒等于原样本 n),对每个伪样本调 binaryEvalrecall/precision/fpr 进三个数组。
  • 取分位:三个数组各自 .sort((a,b)=>a-b),再 ci(arr) = [percentile(arr, 2.5), percentile(arr, 97.5)],返回 recallCI95/precisionCI95/fprCI95 + bootstrap: B
  • percentile(sorted, p)(在 stats.ts 用线性插值:idx = (p/100)*(len-1)lo=floor(idx)hi=ceil(idx)sorted[lo]*(1-w)+sorted[hi]*w。空数组返回 NaN、单元素直接返回该元素——边界处理干净。
  • 测试佐证(groundTruthEval.test.tsCIs are ordered, in [0,1], and bracket the point estimate 断言每个区间 lo>=0hi<=1lo<=hi、且 lo<=point<=hiis deterministic for a fixed seedmulberry32(3) 跑两次断言 recallCI95 完全相等。
  • 测试用的 PRNG 是 mulberry32(seed):一个 32 位确定性伪随机数发生器(在测试文件里手写实现),注入到 binaryEvalWithCI({ rng }) 后,整个 bootstrap 重采样路径变成可复现的——这是「评测可复现」从口号变成代码事实的关键一环。生产侧也应传一个定 seed 的 PRNG,而非 Math.random
  • percentile 的边界行为stats.tspercentile 对空数组返回 NaN、单元素返回该元素本身、否则线性插值。这保证了即便某个 bootstrap 样本退化(全同类),分位计算也不抛错,而是返回有意义的边界值。
  • 三指标各自独立排序取分位:注意 recalls/precisions/fprs 是三个独立数组、各自 sort、各自取分位——不是对同一次重采样的三指标联合取分位。这意味着三个 CI 之间没有联合协方差信息,若后续要问「recall 高时 precision 是否也高」需另做联合分析。

走读结论:binaryEvalWithCI 已 built+tested,RNG 可注入保证确定性、入参校验防 NaN、区间天然落 [0,1]。今天工作量在「拿 day136 的预测对喂进去出真实区间」,逻辑无需改。

值得强调的工程纪律有三条,全部已固化在代码里:(1) fail-closed——非正 bootstrap 次数抛错而非静默返回 NaN 区间,避免「看似有 CI 实则全是垃圾值」;(2) RNG 注入——可复现是评测可信的底线,代码把随机源做成参数而非硬编码 Math.random;(3) 点估计与区间同源——binaryEvalWithCI 先调 binaryEval 拿点估计再 bootstrap,保证报告里的点估计和 CI 出自同一份数据、同一套口径,不会出现「点估计用一套口径、CI 用另一套」的错配。这三条是把「会用 bootstrap」升级成「能交付可信 eval」的关键。

3. 今日实战

  1. 取 Day 136 落盘的 {label, predicted} 预测对(balanced_300 上 deepseek-v4-flash 的判定结果)。
  2. binaryEvalWithCI(items, { bootstrap: 1000, normalLabel: 'normal', rng: <定 seed PRNG> })(生产可用 2000,seed 固定以可复现),拿到 recall/precision/fpr 点估计 + recallCI95/precisionCI95/fprCI95
  3. 落盘三指标 ±CI 进 confusion matrix JSON(扩展 Day 136 的输出),与 seed/bootstrap 次数同档记录,作为 Day 140 leaderboard 的可比单元。
  4. 跑/补 src/aml/__tests__/groundTruthEval.test.ts 确认 CI 单测绿(确定性,不需 model);确认 mulberry32 固定 seed 下两次跑结果完全一致。
  5. 把三指标的 CI 宽度单独记一栏——宽度是 Day 140 判「能否宣称胜出」的直接输入,比点估计本身更重要。若某指标 CI 宽到跨越半个 [0,1],说明 N=300 对该指标还不够,需在 README 里诚实标注「该指标样本量不足」。

4. 今日实测 / 产出

  • bootstrap 单测 = 已 built+testedbinaryEvalWithCI + groundTruthEval.test.ts(CI 有序、落 [0,1]、夹住点估计、固定 seed 可复现、非正 bootstrap 抛错)已在 423 测试套件内绿
  • recall/precision/FPR ±CI 真实区间数字 = 待跑/待数据产生:需 day136 的真实预测对(依赖 N=300 数据 + 模型跑),不预填
  • 方法论已与本仓 A/B 同源验证:A/B 用的是配对 bootstrap(pairedBootstrap),本日用无配对 bootstrap(binaryEvalWithCI),两者底层都走 percentile + 可注入 RNG,算法路径一致、都已 built+tested——只是配对/无配对的输入结构不同。
  • 可借鉴的真实 CI 范式(来自本仓已落地 A/B):A/B Δ+10.3pp 95% CI[0,20.7]——区间跨 0 故不显著。同理,本日三指标的 CI 一旦算出,宽度才决定能否在 Day 140 宣称胜出。
  • 功效视角的配套工具已就位stats.tsrequiredNForDelta(delta, sdDiff) 用正态近似(alpha=.05 双侧、power=.8,z_{α/2}=1.959963985z_β=0.841621234)反推「要检出某个 Δ 需要多少配对样本」。那次 A/B 据此算出约需 ~70 任务才有 power——这条工具同样适用于回答「leaderboard 上两系统的差异,N 要多大才能宣称显著」。今天先建 CI,明天若发现重叠,就用它估「还差多少样本」。

5. 常见误区 / 陷阱

  • 拿 bootstrap 当「免费样本」:重采样不会凭空增加信息——它只是估计已有样本的波动。N=50 的数据 bootstrap 一万次,CI 还是 N=50 的宽度,不会因为重采样次数多就变窄。次数 B 影响的是分位估计的精度,不是 CI 宽度本身。
  • 报无区间的点估计:直接拿 recall=0.82 去比模型,等于把抽样噪声当真信号。2026 eval-rigor 共识要求必带 CI。
  • 对极不平衡 FPR 用正态近似:FP 是个位数时重采样分布严重右偏,正态法会给出负下界或越界上界。必须用 percentile bootstrap。
  • 混淆「bootstrap 次数 B」与「样本量 N」:B 多 = 分位估计精确;N 多 = CI 真的变窄。把 B 调大却以为 CI 会窄是经典误解(见上文「免费样本」陷阱)。
  • bootstrap 次数太少:几十次重采样的分位数本身波动大,CI 不稳。1000+ 次是底线(本仓默认 2000)。
  • 三指标 CI 当成联合区间:本仓三指标各自独立排序取分位,没有联合协方差。别据此推断「recall 高且 precision 高同时成立的概率」——那需另做联合 bootstrap。
  • 忘了固定 seed:CI 不可复现就失去了「一条命令重跑得同样区间」的可信底线。binaryEvalWithCIrng 可注入正是为此——生产里要传定 seed 的 PRNG,不能裸用 Math.random
  • 以为窄 CI = 判定无偏:CI 只控方差不控偏差。系统性偏置的裁判可以又窄又稳却整体偏移——必须靠 Day 138 的跨模型交叉验证查偏差,CI 单独不够。
  • 拿 CI 当「真值区间」:bootstrap CI 是「在当前样本估计下的抽样波动」,不是「真值有 95% 概率落在此」的贝叶斯区间。频率派 CI 的解读要小心,别在汇报里偷换成后验置信。

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

  • Anthropic — Demystifying evals(2026-01):强调报告区间而非点估计、可复现报告规范。
  • OpenAI — Evals 方法论博客 / evals 仓库文档(2025-2026,执行当周复查):行业对 eval 区间报告的趋同实践,可作交叉印证。
  • Efron & Tibshirani — An Introduction to the Bootstrap(经典,1993;percentile 法原始来源,作打底)。
  • DiCiccio & Efron — Bootstrap Confidence Intervals(Statistical Science, 1996;BCa 等改进法,比朴素 percentile 更准,作进阶参考)。
  • 本仓代码:src/aml/groundTruthEval.tsbinaryEvalWithCI)、src/agent/eval/stats.tspercentilepairedBootstraprequiredNForDelta)、src/aml/__tests__/groundTruthEval.test.ts(已 built+tested)。

SOTA检查 (2026-06 更新)

  • 现役主线:bootstrap CI 是 eval 报告标配,2026 eval-rigor 共识要求区间;percentile 法对不平衡指标稳健;binaryEvalWithCI 已落地并测试。
  • 行业趋同:主流厂商(Anthropic、OpenAI 等)的 eval 报告与开源 harness 已普遍带区间/误差棒,「带 CI 才算正经 eval」是 2026 现役共识,无区间的榜单被视为不可信。
  • 进阶替代:percentile bootstrap 在小样本/偏斜下覆盖率会有偏,BCa(bias-corrected and accelerated)法更准;若 N=300 下的 FPR 区间出现明显偏斜,可考虑升级到 BCa。当前 percentile 法对本 block 足够,记为「可选改进」非「过时」。
  • 配对 vs 无配对的选择仍是现役判断点:比较「同一系统在两套数据上」用无配对;比较「两系统在同一套数据上」应优先配对 bootstrap(去任务难度方差、CI 更窄)。Day 140 leaderboard 横比三系统时,配对口径比今天的无配对单系统 CI 更有 power——这是一个明确的可升级方向。
  • 避免/禁用:避免报无区间的点估计;避免用正态近似处理极不平衡的 FPR;避免太少的 bootstrap 次数或裸用未定 seed 的 RNG(不可复现);避免把频率派 CI 解读成贝叶斯后验区间。
  • 下次复查点:执行当周复查《Demystifying evals》是否有 2026 更新版对小样本 CI 的新建议;确认本仓 leaderboard 是否需要从无配对 CI 升级到配对 bootstrap(pairedBootstrap)以收窄区间;若真实 FPR 区间偏斜严重,评估升级 percentile→BCa 的成本收益。
  • 与下游的接口约定:Day 140 的 leaderboard 直接消费本日的 recallCI95/precisionCI95/fprCI95 三对区间,三系统必须用同一 seed、同一 bootstrap 次数binaryEvalWithCI,否则 CI 不在同一基准上不可横比——这条接口约定今天就要写进落盘元数据。

衔接

  • 昨天:Day 136 — 混淆矩阵与指标(真标签 balanced_300 上的 recall/precision/FPR 点估计,binaryEval 已 built+tested)。
  • 今天:给三指标各加 percentile bootstrap 95% CI(binaryEvalWithCI 已 built+tested,真实区间待跑/待数据);明确 CI 控的是方差不是偏差。
  • 明天:Day 138 — 第二模型交叉验证(用 Qwen3 复跑同一 balanced_300,比双模型指标 delta 与 per-case agreement %)——补上今天 CI 控不到的「系统性偏置」那一维。
  • 收口:今天的 CI + 元数据(seed/bootstrap 次数)是 Day 140 leaderboard 可比性的直接前提。
  • 一句话记忆点:点估计回答「是多少」,CI 回答「信多少」——评测严谨度的分水岭。