Learning AI Quality 返回 KuthorX Blog II博客首页

第 06 章

第 1 周:要跑多少次才够?

样本量估算:零失败证明上界、把成功率估到 ±E、检测两个版本的差距,以及允许少量失败的发版门禁。一段讲解视频,一个样本量计算器和一个门禁模拟器。

“这个 Agent 上线前要跑多少次评测?“答"100 次"或者"越多越好”,面试官都会接着问:凭什么是 100?样本量不是拍脑袋定的,它由你要下的结论反推出来。要证明失败率低于 10%,30 次全过就够;要把成功率估到 ±5%,最多要 385 个样本;要测出两个 prompt 版本差 5 个点,每个版本要 900 多个。这一章讲这三种问题怎么算 n,再把它落到发版门禁上。

讲解视频

互动演示

上面是样本量计算器:切换三种场景,拖动置信度、目标精度、基线成功率、最小可检测差距,n 和曲线实时更新。下面是门禁模拟器:设定"跑 n 次最多挂 f 次"的规则和真实失败率,看漏放率、误拦率和 OC 曲线,也可以模拟 200 轮发版。页面底部有自动判分的练习。

互动演示:样本量估算 在新标签页打开

先问要证明什么

测开和 Agent 评测里常见的结论只有三种,它们需要的样本量差了一个数量级(95% 置信度):

想说的话统计问题典型答案
“退款误操作率 ≤ 5%”零失败证明上界59 次全过
“任务成功率约 80%,误差 ±5%”估计到指定精度246 个;不知道成功率时按最坏情况 385 个
“新 prompt 比旧的好 5 个点”检测两个版本的差距80% → 85%:每个版本 906 个;配对能省多少看两个版本有多一致

场景一:零失败,证明失败率 ≤ p

这是 rule of three 那一章的内容。如果真实失败率等于 p,n 次全过的概率是 \((1-p)^n\)。要让这件事"巧合到只有 α 的概率”:

$$ n \ge \frac{\ln \alpha}{\ln(1-p)} \approx \frac{-\ln\alpha}{p} $$

α = 5% 时 \(-\ln 0.05 \approx 3\),就是 n ≈ 3/p。99% 置信度时常数变成 4.6。

要证明失败率 ≤90% 置信95% 置信99% 置信速算 3/p
10%22294430
5%45599060
2%114149228150
1%230299459300

表里是精确法向上取整后的次数,速算 3/p 总是略多一点。这个公式只管"一次都没挂"。挂了 1 次,就要换 Wilson 区间或精确二项区间,或者事先设计一个允许少量失败的门禁(见后文)。

场景二:把成功率估到 ±E

想回答"成功率 80%,误差 ±5%"。误差(区间半宽)约为 \(z\sqrt{p(1-p)/n}\),令它等于 E,反解 n:

$$ n \approx \frac{z^2\,p(1-p)}{E^2} $$

z 是置信度对应的正态分位数:90% 取 1.645,95% 取 1.96,99% 取 2.576。

预估成功率 p±10%±5%±3%±2.5%
50%(最坏情况)9738510681537
80%62246683984
90%35139385554

两个规律:

  1. p = 50% 是最坏情况。\(p(1-p)\) 在 0.5 处最大,等于 0.25。不知道成功率大概多少,就按 50% 算,只会偏保守。
  2. 精度减半,样本量 ×4。E 在分母上是平方。±5% 要 385 个,±2.5% 要 1537 个;把误差从 5 个点压到 1 个点,要 25 倍的样本(9604 个)。

这个公式用来事前规划。跑完以后报区间,用 Wilson 区间,尤其是成功率接近 0 或 100% 的时候。成功率 98% 时问"±5%“没有意义,应该改问"失败率上界是多少”,回到场景一。

场景三:检测两个版本的差距

改了 prompt,想知道新版本是不是真的更好。有两种犯错方式:

错误含义控制它的参数常用取值
误报(第一类错误)其实没变好,你说变好了显著性水平 α5%(双侧)
漏报(第二类错误)其实变好了,你没测出来β;功效 power = 1 − β80% 或 90%

第三个量是最小可检测差距 δ(效应量):你关心"好 5 个点"还是"好 1 个点"?

两个版本各跑一批互不相关的样本(独立两组),基线 \(p_1\),新版本 \(p_2 = p_1 + \delta\),每个版本需要:

$$ n = \frac{\left(z_{1-\alpha/2}\sqrt{2\bar p(1-\bar p)} + z_{1-\beta}\sqrt{p_1(1-p_1)+p_2(1-p_2)}\right)^2}{\delta^2},\qquad \bar p = \frac{p_1+p_2}{2} $$
基线 → 新版本power 80%power 90%
80% → 90%199266
80% → 85%9061212
90% → 95%435582
50% → 55%15652095
80% → 82%60398084

α = 5% 双侧,正态近似、不带连续性校正,数字是每个版本的样本数。差距减半,样本量约 ×4。反过来看:每边只跑 100 条时,80% 对 85% 的功效只有约 15%,“新版本高了 5 个点"多半是噪声。

配对设计:同一批用例,两个版本都跑

离线评测里,两个 prompt 版本几乎总是跑同一批用例。这时应该逐条比较:大部分用例两个版本结果一样,对"谁更好"没有信息;有用的是结果不一致的用例。设不一致的用例占比为 ψ,配对设计需要的用例数(Connor, 1987):

$$ n = \frac{\left(z_{1-\alpha/2}\sqrt{\psi} + z_{1-\beta}\sqrt{\psi-\delta^2}\right)^2}{\delta^2} $$
80% → 85%,power 80%每个版本要跑的用例数
独立两组906
配对,ψ = 20%626
配对,ψ = 10%312
配对,ψ = 5%(只修好、没改坏)155
配对,ψ = 29%(两个版本的结果互不相关)909

两个版本行为越接近,配对省得越多:ψ = 10% 时省约三分之二,ψ = 20% 时只省约三成;两个版本的结果互不相关时(这里 ψ = 0.8 × 0.15 + 0.85 × 0.2 ≈ 29%)完全不省。改一版 prompt,大部分用例的结果通常不会变,ψ 往往不大,所以离线评测值得按配对来设计和检验。配对数据怎么做检验,是下一章 McNemar 的内容。

允许少量失败的发版门禁

把场景一变成流水线里的门禁:“跑 n 次,失败不超过 f 次就放行”。门禁有两个错误率:

  • 漏放率:失败率为 q坏 的坏版本碰巧通过,\(P(\text{失败数} \le f \mid q_{坏})\)。
  • 误拦率:失败率为 q好 的好版本碰巧被拦,\(1 - P(\text{失败数} \le f \mid q_{好})\)。

失败数服从二项分布,两个概率都能精确算出来。通过概率随失败率变化的曲线,就是抽样验收里的 OC 曲线。f = 0 时漏放率就是 \((1-q_{坏})^n\),也就是 rule of three 里的 α。

以 q好 = 2%、q坏 = 10% 为例:

门禁规则漏放率误拦率
5 次全过59.0%9.6%
30 次全过4.2%45.5%
30 次最多挂 1 次18.4%12.1%
89 次最多挂 4 次5.0%3.3%

“30 次全过"漏放率够低,但好版本将近一半会被拦下,团队很快就会习惯"挂了再跑一遍”。最多跑 3 轮、有一轮全过就放行,坏版本的漏放率从 4.2% 涨到 12.2%,门禁的保证就没了。

想让两个错误率同时 ≤ 5%,先定好 q好 和 q坏,再找最小的 (n, f)。两者离得越近 n 越大:2% 对 10% 要 89 次最多挂 4 次,1% 对 5% 要 181 次最多挂 4 次。

样本量数的是独立样本

上面所有公式里的 n,都是独立样本的个数(见「独立性假设」一章)。同一个输入重跑 400 次,只说明这一个输入的 flaky 率;对"Agent 在这类任务上的成功率”,换算成独立样本也只有个位数(公式上限约 1/ρ ≈ 3.3),而且只有 1 个用例,根本推不到这类任务。

每个用例跑 m 次、用例内相关系数为 ρ 时,要凑够 \(n_{\text{req}}\) 个有效样本,需要的用例数是:

$$ C = \frac{n_{\text{req}}\,\bigl(1+(m-1)\rho\bigr)}{m} $$
目标 ±5%(385 个有效样本),ρ = 0.3用例数总运行次数
每个用例跑 1 次385385
每个用例跑 5 次170850
1 个用例跑 385 次做不到:有效样本上限约 1/ρ ≈ 3.3—

重跑能省一些用例,但要多花运行成本,而且省得有上限。先算需要多少个不同的用例,再决定每个跑几次;报告里写"170 个用例 × 每个 5 次",不要写"850 次"。

from math import ceil, log, sqrt
from scipy.stats import binom, norm

def n_zero_failure(p: float, conf: float = 0.95) -> int:
    """零失败时,证明失败率 <= p 需要的次数(精确法)。"""
    return ceil(log(1 - conf) / log(1 - p))

def n_precision(p: float, e: float, conf: float = 0.95) -> int:
    """把成功率估到 ±e 需要的独立样本数;p 未知时用 0.5。"""
    z = norm.ppf(1 - (1 - conf) / 2)
    return ceil(z ** 2 * p * (1 - p) / e ** 2)

def n_two_groups(p1: float, p2: float, alpha: float = 0.05, power: float = 0.8) -> int:
    """独立两组比较成功率,每组需要的样本数(双侧、无连续性校正)。"""
    za, zb = norm.ppf(1 - alpha / 2), norm.ppf(power)
    pbar = (p1 + p2) / 2
    num = za * sqrt(2 * pbar * (1 - pbar)) + zb * sqrt(p1 * (1 - p1) + p2 * (1 - p2))
    return ceil(num ** 2 / (p1 - p2) ** 2)

def n_paired(psi: float, delta: float, alpha: float = 0.05, power: float = 0.8) -> int:
    """配对设计(McNemar)需要的用例数,psi 是结果不一致的用例占比(Connor, 1987)。"""
    if not 0 < abs(delta) <= psi:
        raise ValueError(f"需要 0 < |delta| <= psi,收到 delta={delta}, psi={psi}")
    za, zb = norm.ppf(1 - alpha / 2), norm.ppf(power)
    return ceil((za * sqrt(psi) + zb * sqrt(psi - delta ** 2)) ** 2 / delta ** 2)

def gate_errors(n: int, f: int, q_good: float, q_bad: float) -> tuple[float, float]:
    """门禁"n 次里最多 f 次失败"的(漏放率, 误拦率)。"""
    return binom.cdf(f, n, q_bad), 1 - binom.cdf(f, n, q_good)

print(n_zero_failure(0.05))            # 59
print(n_precision(0.5, 0.05))          # 385
print(n_two_groups(0.80, 0.85))        # 906
print(n_paired(0.10, 0.05))            # 312
print(gate_errors(89, 4, 0.02, 0.10))  # (0.0497, 0.0334)

面试怎么答

  • “失败率 < 10% 要跑几次?” 零失败 30 次(精确法 29 次)。要证明 ≤ 5% 就是 60 次。
  • “两个 prompt 版本差 5 个点要多少条用例?” 基线 80%、α = 5%、功效 80% 时,独立两组每边约 900 条;同一批用例两个版本都跑、做配对检验,不一致率 10% 时约 310 条就够。
  • 所有 n 数的都是不同的用例,不是重跑次数。

常见错误说法

  • “失败率 < 10%,跑 100 次”:零失败 30 次就够,100 次是没算过的数。
  • “估计误差 ±1%,跑 1000 次”:±1% 最坏要 9604 个,1000 个只能到 ±3.1%。
  • “新版本 85%、旧版本 80%,跑了 100 条,新版本更好”:每边 100 条时差 5 个点的功效只有约 15%,这点差距多半是噪声。
  • “30 次全过的门禁很严格”:对好版本也一样严格。失败率 2% 的好版本有 45% 会被拦,最后变成"挂了重跑"。
  • “同一个用例跑 400 次,样本量 400”:有效样本看的是用例数。