配对 Bootstrap 从模型差值到置信区间
NumPy 统计基础解释怎样计算均值和分位数;模型回归评测强调固定题集与逐题比较。假如新模型的平均得分高了 0.02,接下来还需要问:换一批同来源的样本,这个差值可能怎样波动?
Bootstrap 从已观察样本构造经验分布,反复有放回地抽样并重算统计量。它能帮助分析抽样不确定性,但不能自动修复偏斜题集、错误标签、相关样本或反复挑选模型造成的偏差。本篇只处理两个固定模型在配对评分上的均值差。
先定义要估计的差值
同一条样本 i,旧模型得分 a_i,新模型得分 b_i。规定越高越好,定义 d_i=b_i−a_i,观察到的均值差为:
1 | Δ_hat = mean(d_i) = mean(b_i) − mean(a_i) |
目标是指定总体和抽样机制下的期望差值 Δ。若得分是通过与否,Δ_hat=0.2 表示提高 20 个百分点,不是相对提高 20%。若比较的是越小越好的损失或延迟,必须另外说明方向,不能沿用“差值为正就是改进”的解释。
下面是假设的五条合成评分,不是某个真实模型的成绩:
| 样本 | 旧 a | 新 b | d=b−a |
|---|---|---|---|
| A | 0 | 1 | 1 |
| B | 1 | 1 | 0 |
| C | 0 | 1 | 1 |
| D | 0 | 0 | 0 |
| E | 1 | 0 | −1 |
旧模型均值 0.4,新模型均值 0.6,Δ_hat=0.2。这个算术没有不确定性;我们不确定的是从这五条推断到所定义总体时,样本选择带来的误差。
为什么两边必须用相同抽样索引
一次配对重采样若抽到 [A,A,C,E,B],就同时取这五条的旧、新评分:旧均值 0.4、新均值 0.8,本次 Δ*=0.4。A 出现两次是有放回抽样的正常结果,不应自行去重。
如果旧模型抽一组索引、新模型另抽一组索引,就破坏了同一样本的配对结构。这种独立两样本重采样对应另一种假设,不再保留当前样本上的协方差。差值方差满足:
1 | Var(b−a) = Var(b) + Var(a) − 2 Cov(a,b) |
两模型在哪些样本上一起成功或一起失败,会影响差值波动;只看两个总体通过率无法还原这种关系。SciPy bootstrap 官方文档明确说明 paired=True 会对所有输入使用同一组重采样索引。
本文先逐行计算 d,再对 d 重采样;对于均值差,这与同时抽取配对的 a、b 数学等价。这个简化不适用于所有指标:F1、相关系数或复杂排序指标通常需要重采样原始配对记录,再完整重算指标。
flowchart TD A[固定样本 ID 与两模型评分] --> B[逐条计算新减旧] B --> C[确认独立抽样单位] C --> D[有放回抽取相同单位] D --> E[重算一次均值差] E --> F[重复得到 bootstrap 分布] F --> G[取分位数 并报告假设与限制]
百分位区间怎样得到
把 B 次均值差排序为 z_0 到 z_(B−1)。本篇取 95% percentile 区间,即 2.5% 和 97.5% 分位数;线性插值约定如下:
1 | h = (B−1)q |
明确插值方法很重要,尤其在离散小样本中,换一种分位数约定可能得到不同端点。Bootstrap 标准误则可用 B 个重采样统计量的样本标准差估计;它不是原始评分的标准差。
五条数据一共只有 5^5=3125 种等概率有序重采样,可以全部枚举,免去本例的 Monte Carlo 抽样误差。精确得到经验重采样分布的均值为 0.2、总体标准差约为 0.334664,95% 百分位区间为 [−0.4,0.8]。它仍然只是经验分布下的计算,不是总体分布已被精确知道。
还能独立验证这个标准差:d 的经验方差(分母为 5)为 0.56;从该经验分布独立抽五次的均值方差为 0.56/5=0.112,开平方就是 0.334664。这里计算的是完整经验分布的总体标准差,和有限 B 次抽样使用分母 B−1 的标准误估计要分清。
区间包含零,能够说明什么
本例的区间跨过零,不能据此宣称总体平均差值已可靠为正。也不能反向宣称两个模型完全等效:区间很宽,可能同时容纳有实际意义的正、负差值。
置信区间是一种重复抽样程序的覆盖性质,依赖适用条件与近似精度;不能把已经算出的这一个区间解释为“参数有 95% 概率在里面”。在五条这样的小样本、离散统计和边界情形下,名义 95% 与实际覆盖率可能差很大。
本篇选择 percentile 是为了把流程手算清楚,不宣称它优于 basic 或 BCa。SciPy 文档提供三种方法,默认使用 BCa;BCa 也可能在退化分布中出现无法计算的端点。方法名称不能替代对数据生成和统计量的检查。
一百行不一定是一百个独立样本
把 A 到 E 每条记录机械复制 20 次,得到 100 行。信息并没有增加;如果把每一行都独立抽样,经验均值的重采样标准差会缩小到约 0.074833,恰好是原先的 1/√20。区间可能因此错误地看起来已经支持正差值。
若保留五个原始组,按组抽样,每次抽五个组、选中的组内 20 行一起保留,均值差分布就与原五条配对数据相同。这个反例说明:复制记录不能创造独立证据。
| 数据结构 | 本篇建议先识别的抽样单位 |
|---|---|
| 随机独立抽取的题目 | 一条完整配对题目 |
| 同文档生成的多道题 | 文档组,保留组内所有配对题目 |
| 同一用户的多次交互 | 用户组,前提是组之间适合视为独立 |
| 连续时间观测 | 可能需要时间块或其他依赖模型,不能直接套普通行抽样 |
下面代码允许指定 cluster,按组有放回抽相同数量的组。统计目标仍为行加权均值差:每次用抽中组的全部差值之和,除以抽中行数。组大小不等时,这不是每组均值的等权平均,重采样行数也不必等于原始行数。它假设组是抽样单位,并不自动适用于固定分层设计、任意时间依赖或只有极少几个组的可靠推断。
flowchart TD
A[看到很多评分行] --> B{行之间是否可以视为独立}
B -- 是 --> C[每条配对记录作为单位]
B -- 否 --> D[寻找文档 用户或时间结构]
D --> E[明确成组或成块的抽样设计]
C --> F[重复采样并重算原定统计量]
E --> F
F --> G[报告独立单位数 不只报告行数]
完整标准库实验
Python 3,无第三方依赖。每行显式保存唯一 key、旧新评分及 cluster。若逐题独立抽样,就给每条题目不同的 cluster。代码能检查 ID 重复和数值格式,不能判断你是否把两模型的评分按正确题目配在一起;输入前仍须核对 ID 与版本。
1 | from dataclasses import dataclass, replace |
输出中,原五条与成组重复后的结果使用相同随机种子,会有相同的区间和标准误;naive_hundred_rows 错把重复记录当成独立单位,区间会窄得多。exact_empirical 给出前面的 3125 种精确结果。固定种子便于复现计算,不会使样本变得更有代表性。
设 N 为行数、G 为组数、B 为重采样次数。代码先汇总组内总分与大小,每次只抽 G 个组,总时间 O(N+BG+B log B),末项来自区间计算的排序;内存 O(N+B+G)。逐行独立时 G=N。完整枚举仅用于五条教学数据,复杂度随 N^N 增长,不能用于大数据。
从区间到决策还缺哪些检查
| 风险 | 需要另行处理的事情 |
|---|---|
| 数据分布偏斜 | 明确目标总体、抽样框与关键分组,不靠增大 B 修复 |
| 标签不可靠 | 先核对标注一致性与裁决,重采样不会自动识别错误标签 |
| 固定训练出的模型 | 本文不包含训练随机种子造成的波动 |
| 训练或调参复用测试集 | 固定最终评测口径,避免反复选择后的乐观偏差 |
| 小样本、少数组 | 区间可能离散且覆盖不佳,不能把两组也能运行当成推断可靠 |
| 所有观察差值相同 | 区间会退化;不代表总体误差必然为零 |
| 多个切片反复寻找正结果 | 需要预先计划与相应的多重比较处理 |
| 不含零的区间 | 仍要检查实际意义、关键题退步和依赖结构 |
tests/verify-paired-bootstrap-article.cjs 运行正文代码,再用多项式频数计数独立重建精确经验分布,核对配对关系、不同组大小、重复记录、分位数插值和随机数可复现性。它验证计算,不宣称小样本 percentile 区间已经具有准确的 95% 覆盖率。
自检:把五条数据复制二十遍,为什么不能因此获得一百条独立证据?
复制没有引入新的独立观察。逐行抽样会人为缩小波动;保留原始五个组并成组抽样,才能在这个例子中保留原来的经验不确定性。实际数据还需要根据生成过程判断独立单位。
先用NumPy 统计核对均值与分位数,再用本文理解差值的不确定性,最后接回回归评测与发布门槛。如果比较的是BatchNorm 等训练改动,还要把训练设置、随机种子和测试集来源一起写清楚。