跑了 300 次,就是 300 个样本吗?
不一定。30 个用例各跑 10 次,如果失败总扎堆在几个难用例上,有效样本可能只有 80 个左右。
rule of three、Wilson 区间、pass^k 都默认每次运行相互独立。这个前提不成立时,算出来的区间会窄得离谱,名义 95% 实际可能只有 70%。
1前面的公式都有同一个前提
前面几个概念,每个公式都写着"每次试验相互独立、失败率相同"(统计上叫独立同分布,i.i.d.):
| 概念 | 用到独立性的地方 |
|---|---|
| rule of three | \(P(\text{全过}) = (1-p)^n\):n 次全过的概率等于单次通过概率连乘 |
| Wilson 区间 | 标准误 \(\sqrt{p(1-p)/n}\):n 个独立样本平均后,波动按 \(1/\sqrt n\) 缩小 |
| pass^k | \(p^k\):k 次都成功的概率等于单次成功概率连乘 |
这些公式从头到尾只用到 n 这个数字。n 到底是多少,没有人替你检查。
2"同一个用例跑 30 次"和"30 个用例各跑 1 次"回答的是两个问题
| 同一个用例跑 30 次 | 30 个不同用例各跑 1 次 | |
|---|---|---|
| 估的是什么 | 这一个用例上的不稳定率(flaky 率) | Agent 在这类任务上的平均失败率 |
| 随机性来自 | 模型采样、环境抖动 | 用例本身的难度差异 + 采样 |
| 30 次全过能说明 | 这个用例上失败率 ≤ 9.5% | 这类任务整体失败率 ≤ 9.5%(前提:用例是随机抽的) |
| 不能说明 | 换一个用例还行不行 | 某个具体用例是否稳定 |
实际评测一般两者都有:C 个用例,每个跑 m 次,一共 \(n = C \times m\) 次。麻烦在于:同一个用例的 m 次结果彼此相关,难的用例总是挂,简单的总是过。
3Agent 评测里,独立性是怎么被破坏的
| 来源 | 表现 | 后果 |
|---|---|---|
| 同一用例重跑 | 用例难度不同,失败集中在少数难用例上 | 失败扎堆,有效样本接近用例数,而不是运行次数 |
| temperature = 0 / 缓存 | 同样的输入给出几乎一样的输出,甚至命中 prompt 缓存或结果缓存 | 30 次运行实际上是 1 次运行复制了 30 份 |
| 共享环境状态 | 上一次运行留下的数据库记录、浏览器 cookie、文件 | 前一次失败会连带后一次失败(或者反过来掩盖失败) |
| LLM Judge 系统性误判 | Judge 对某种输出格式总是判过,或者总是判错 | 误判不会被平均掉,所有样本一起偏 |
| 同一时段 API 限流 / 故障 | 某 10 分钟内的运行一起超时 | 失败在时间上扎堆,和 Agent 本身能力无关 |
共同点:多次运行共享了某个随机因素(用例、种子、环境、Judge、时间段)。共享得越多,它们越像"同一次"。
4用数字衡量:簇内相关系数和设计效应
把同一个用例的 m 次运行看成一"簇"。簇内相关系数(ICC,记作 \(\rho\))衡量同一簇内两次运行的结果有多像:
$$\rho = \frac{\text{用例之间失败率的方差}}{\text{总方差}} = \frac{\operatorname{Var}(p_i)}{\mu(1-\mu)}$$\(p_i\) 是第 i 个用例自己的失败率,\(\mu\) 是所有用例的平均失败率。
- \(\rho = 0\):所有用例一样难,重跑一次就是一个全新的独立样本。
- \(\rho = 1\):每个用例要么永远挂、要么永远过,重跑一次没有任何新信息。
有了 \(\rho\),就能算出 n 次运行相当于多少个独立样本:
$$n_{\text{eff}} = \frac{n}{1 + (m-1)\rho}$$分母 \(1+(m-1)\rho\) 叫设计效应(design effect)。区间宽度按 \(1/\sqrt{n}\) 变化,所以区间会变宽约 \(\sqrt{\text{设计效应}}\) 倍。
前提:每个用例重跑次数 m 相同、所有用例共用同一个 ρ、用例是随机抽的。m 不相等时用平均簇大小近似,或者直接用 cluster bootstrap。
例子:30 个用例 × 10 次 = 300 次,挂了 30 次
| 按独立算(n = 300) | ρ = 0.3(n_eff ≈ 81) | |
|---|---|---|
| 设计效应 | 1 | 1 + 9 × 0.3 = 3.7 |
| 95% Wilson 区间 | [7.1%, 13.9%] | [5.2%, 18.4%] |
| 区间宽度 | 6.8 个百分点 | 13.3 个百分点(约 1.9 倍) |
| 实际覆盖率 | 按独立算的区间,只有约 70% 的时候罩住真实失败率(下面的模拟器可以验证) | |
加重跑次数,救不回来
固定 C = 30 个用例、\(\rho = 0.3\),只增加每个用例的重跑次数 m:
| m | 1 | 2 | 5 | 10 | 30 | 100 | ∞ |
|---|---|---|---|---|---|---|---|
| 总运行次数 | 30 | 60 | 150 | 300 | 900 | 3000 | ∞ |
| n_eff | 30 | 46 | 68 | 81 | 93 | 98 | 100 |
m 趋于无穷时,\(n_{\text{eff}} \to C/\rho = 30 / 0.3 = 100\)。跑 3000 次也只顶 98 个独立样本。想要更多有效样本,要加用例,不是加重跑。
5模拟器
每个用例的真实失败率从一个平均为 μ 的分布里抽(Beta 分布,簇内相关正好是 ρ),然后每个用例跑 m 次。所有随机数用固定种子,点"换一批"才会变。
如果每次运行都独立(ρ = 0)
实际(ρ = 0.30)
每一行是一个用例,每一格是一次运行,红色是失败。两边平均失败率相同,右边的失败会集中在某几行。
覆盖率:同样的设置重做 2000 次实验
每次实验重新抽用例、重新跑,看 95% 区间有多少次罩住真实平均失败率 μ。好方法应该贴着 95%。
金色竖线是 95%。修正用的是模拟器里真实的 ρ;实际工作中 ρ 要从数据里估,或者直接用按用例的 cluster bootstrap。
有效样本随重跑次数的变化
6对策
| 做法 | 解决什么 |
|---|---|
| 按用例聚类分析 | 先算每个用例自己的失败率,再在用例层面汇总;报告里写清"C 个用例 × m 次",而不是只写总次数 |
| 多加用例,少加重跑 | 有效样本的上限是 C/ρ,由用例数决定 |
| 分层抽样 | 按任务类型、难度分层抽用例,避免某一类用例占满样本 |
| cluster bootstrap | 重抽样时以用例为单位整组抽,不按单次运行抽,区间自动把簇内相关算进去 |
| 隔离环境 | 每次运行用干净的数据库、新的浏览器上下文,避免状态串到下一次 |
| 每次运行换种子、关缓存 | 避免"跑 30 次其实是同一次" |
| 记录 trace,事后检查扎堆 | 看失败是不是集中在某些用例、某个时间段、某种 Judge 输出;集中就说明独立性有问题 |
cluster bootstrap(按用例整组重抽)
import numpy as np
def cluster_bootstrap_ci(fails: np.ndarray, n_boot: int = 10_000, seed: int = 0):
"""fails: 形状 (C, m) 的 0/1 矩阵,每行一个用例。返回平均失败率的 95% 区间。"""
if fails.ndim != 2 or fails.size == 0:
raise ValueError("fails 需要是非空的 (用例数, 重跑次数) 矩阵")
rng = np.random.default_rng(seed)
per_case = fails.mean(axis=1) # 每个用例自己的失败率
idx = rng.integers(0, len(per_case), size=(n_boot, len(per_case)))
means = per_case[idx].mean(axis=1) # 整组抽用例,再求平均
return np.percentile(means, [2.5, 97.5])
关键是 per_case[idx]:抽中一个用例,就带上它的全部 m 次结果。如果按单次运行抽,就又回到了"假装独立"。
7面试要点与常见错误
一句话讲清楚
置信区间只认独立样本。同一个用例的多次运行彼此相关,有效样本是 \(n/(1+(m-1)\rho)\),上限是用例数除以 ρ;所以报告要写"几个用例 × 每个几次",区间要按用例聚类算,想提高精度要加用例。
追问准备
- ρ 怎么估?用单因素方差分析(ANOVA)的组间、组内方差估;或者绕开 ρ,直接用 cluster bootstrap 算区间。
- 什么时候可以不管?每个用例只跑 1 次(m = 1),设计效应就是 1;但这时要求用例本身是从任务分布里随机抽的。
- temperature = 0 就一定没用吗?对"这个用例稳不稳"没用,重跑 30 次几乎是同一个结果;但对"这批用例整体通过率"没影响,那时的样本是用例。
- 两个版本对比怎么办?让两个版本跑同一批用例,按用例配对比较(McNemar 检验、配对 bootstrap),用例难度的影响会在配对里抵消掉。这是下一步要学的。
常见错误说法
❌ "同一个用例跑了 30 次全过,所以 Agent 失败率 ≤ 10%":这只说明这一个用例,换个用例不一定成立。
❌ "区间太宽了,每个用例再多跑 100 次":ρ 大的时候加重跑几乎没用,要加用例。
❌ "bootstrap 按单次运行重抽":抽样单位必须是用例,否则区间和按独立算的一样窄。