---
title: "用配对评测和统计功效判断 LLM 变更是否有效"
canonical: "https://siyuanailab.com/curation/translations/7e925c307e0f97b05fb0d1daffd19132-943b013c594a6c7bb50423dabd385de513913f2329ea3ffc041352f905447a46/"
dateModified: "2026-10-27"
datePublished: "2026-10-27"
---

[返回日报条目](https://siyuanailab.com/curation/#entry/7e925c307e0f97b05fb0d1daffd19132)

 

资料节译

 

# 用配对评测和统计功效判断 LLM 变更是否有效

 

[阅读原文](https://dev.to/dima_kruglov_608741d9a416/your-eval-suite-has-a-flake-rate-gating-llm-changes-on-paired-powered-tests-j7p)

 

现有资料无法确认原文完整，本页只翻译已取得的材料。

 

现有原件无法确认图片是否齐全。

 

我看着同一次发布连续三次出问题。有人调整提示词或更换模型，评测平均分从 0.81 升到 0.83，PR 获得批准，两周后某个特定意图却在生产环境中失效了。回归确实存在，所谓改进却并不存在。没人粗心大意——评测给出了一个看似测量结果、实际上却像抛硬币一样随机的数字。

 

LLMOps 中不起眼的部分不是提示词管理或追踪，而是大多数评测套件无法检测团队据以发布的那些变化。解决这个问题只需半天统计工作和一个 CI 任务。

 

比较两种配置之前，先弄清楚哪些因素在变化：

 

-

 

## **示例抽样噪声。** 你的黄金集有 200 个示例，每个示例值 0.5 分。所谓“提高 3 分”，可能只是 6 个示例的结果翻转，其中好几个还是边界案例。

 

## **评判器噪声。** 如果用 LLM 给输出打分，而温度参数设为高于 0，那么同一输出可能周二通过、周三却不通过。顺序也有影响：先问评判器“A 和 B 哪个更好”，再问“B 和 A 哪个更好”，你就会发现它前后不一致。

 

**任务噪声。** 采样温度、工具超时和检索索引漂移都会改变输出，即使你这边没有改动任何代码。

 

这三种噪声无法全部消除。通过比较**配对**结果，你几乎可以彻底消除第一种：在*相同*示例上运行基线和候选配置，并分析每个示例的差值。示例难度的影响会相互抵消。现在，能修复一个难例、又弄坏另一个的提示词，会表现为一个 +1 和一个 -1，而不再是需要你费心解读的汇总分数。

 

对于通过/不通过结果的配对比较，平均差值的标准误取决于**不一致率**——也就是两种配置在同一示例上结果不同的频率——而不是准确率。

 

如果两种配置在 15% 的示例上结果不同，并且你有 200 个配对示例：

 

- SE = sqrt(0.15 × 0.85 / 200) = 2.5 个百分点
 
- 在 80% 检验功效、α=0.05 时的最小可检测效应 = 2.8 × SE = 7 个百分点

 

所以在这种设置下，提升 2 个百分点是检测不到的。若要在相同假设下检测 5 个百分点的变化，大约需要 400 个配对示例；检测 3 个百分点则需要约 1,100 个。这些只是算术结果，不是基准数据——代入你自己的不一致率；对于提示词变更，它通常比你预想的更高。

 

由此得出两个结论。第一，停止以原始平均分作为门槛。第二，如果你的黄金集只有 200 个示例，而且无法扩大，那么门槛应设为“不得回退超过 X”，而不是“必须有所提升”。

 

整个实现大约只需三十行 Python 标准库代码。对配对差值进行自助抽样，查看区间，并且只在区间不包含零时发布。

 

```
python
"""paired_gate.py â decide whether an LLM change is real or noise.
Run: python paired_gate.py    Deps: none (stdlib only)."""
import random
def paired_bootstrap(baseline, candidate, iters=5000, seed=7):
    """Scores aligned on the SAME example ids, 0/1 or 0..1.
    Returns (mean delta, 2.5th pct, 97.5th pct)."""
    diffs = [c - b for b, c in zip(baseline, candidate)]
    n = len(diffs)
    rng = random.Random(seed)  # fixed seed: the gate must be reproducible
    means = sorted(
        sum(diffs[rng.randrange(n)] for _ in range(n)) / n
        for _ in range(iters)
    )
    return sum(diffs) / n, means[int(0.025 * iters)], means[int(0.975 * iters)]
def required_n(disagreement_rate, mde, z_alpha=1.96, z_power=0.84):
    """Paired binary comparison: examples needed per side."""
    se = mde / (z_alpha + z_power)
    p = disagreement_rate
    return int((p * (1 - p)) / se**2) + 1
if __name__ == "__main__":
    # 200 paired examples; candidate looks "+3 points" better.
    baseline  = [1] * 160 + [0] * 40
    candidate = [1] * 166 + [0] * 34
    delta, lo, hi = paired_bootstrap(baseline, candidate)
    print(f"delta
```

 

AI 翻译；具体表述请与原文对照。
