[LLM 9/10] Benchmarking:没有置信区间的 accuracy 只是传闻
从第 1 章开始,这个系列每次报告数字时都会重复同一句话: "没有置信区间的 accuracy 不是实验结果,它只是传闻", 并且承诺会在第 9 章完整解释——就是这一章。 我们一个 step 都不会训练,而是要从零搭出一套三种模式的评测系统, 把整个系列训练出来的每一个 checkpoint 都拿到真实的泰语考题上测一遍, 然后证明那些从来没人写进 paper 的设置,能让同一个模型的分数移动得比 leaderboard 上大家争来争去的差距还要大。
Open in Colab09_benchmarking.ipynb
1. 问题(Problem statement)
来读一句每周都能刷到的话:"模型 X 在 ThaiExam 上拿到 71.2%,超过了 69.8% 的模型 Y。"
唯一该问的问题是一共测了多少道题。如果测试集只有 100 道题, 每个数字的 95% 置信区间宽度大约是 ±8–10 个点, 这意味着 71.2% 和 69.8% 是同一个数字,只是随机抽样恰好抽出了不一样的结果。 拿 100 道题上 1.4 个点的差距去宣布胜者,和抛十次硬币就断定硬币不均匀没有区别。
问题还不止于测试集的大小,因为"benchmark 分数"这一个数字, 其实是由好几层几乎没人报告的决策共同产生的:
| 藏起来的决策 | 对分数的影响 |
|---|---|
| 用 log-likelihood 还是 generative 打分 | 可以差好几个点 |
| 要不要对选项长度做 normalize() | 足以让 leaderboard 的名次对调 |
| 0-shot 还是 5-shot、template 怎么写 | 可以差好几个点 |
| 给哪些模型套 chat template | 系统性地偏袒其中某一个模型 |
| 考题有没有泄漏进训练数据(contamination) | 整根柱子都是虚高的分数 |
一个掩盖了五层决策的数字,再加上没人画的 error bar—— 这就是我们平时所说的 leaderboard。
2. 我们要做什么(Solution)
这一章完全不训练,而这是优点,不是缺点—— 评测和训练是两种不同性质的工作,它值得拥有属于自己的一章。
我们要做四件事:
- 从零写出三种模式的打分系统——log-likelihood 多选题、generative exact-match (带泰语 normalization),以及配有白纸黑字 rubric 的 LLM-as-judge, 然后让你看到这三种模式给同一个模型打出的分数并不相同。
- 把第 1–8 章的每一个 checkpoint 放到同一根标尺上测,用真实的泰语考题
(
scb10x/thai_exam)、泰语数学题(VISAI-AI/gsm8k-thai)以及本系列一直在用的 KobEval-TH。 - 给每一个数字都配上 Wilson 95% CI,再看看这个系列的哪些结论能从 error bar 里活下来。
- 尝试复现公开 leaderboard 上的数字——Qwen3-0.6B 在 ThaiExam 上的成绩, 如果对不上,我们会去把原因一个个查出来,而不是默默略过。(ThaiExam 是泰语考试基准,详见 6.1。)
benchmark 分数不是模型的属性,它是 (模型 × 评测方法 × 题目集合 × 题目数量) 的属性。 只报告分数而不报告另外三样东西,等于只报告了四分之一的真相。 而置信区间,是"实验结果"这四个字的最低价码。
3. 公式(Equation)
3.1 Wilson score interval——本系列的专用 CI
如果在 道题里答对了 道,得到 ,那么置信水平 (95% → )下的 Wilson 区间是
为什么不用更好记的正态近似公式()? 因为它恰恰在我们最需要用到它的地方崩掉:接近 0 或 1 的边界,以及 很小的时候。
来看第 8 章的一个真实情形:guardrail 在 30 道测试题上一次危险请求都没有放过去。
from kobeval import wilson_ci # 与第 1 章开始一直使用的是同一个函数
wilson_ci(0, 30) # → (0.000, 0.114)
- 正态近似: ——区间宽度是零, 好像我们只看了 30 个样本就 100% 确信漏放率就是 0%。
- Wilson: ——"看了 30 次都没漏,但真实值有可能高到 11%"。
第二个区间是诚实的说法,第一个区间是公式自动生产出来的谎言。
在公式里可以看到,Wilson 用 这一项把中心点往 拉(相当于补进 道虚拟题目,一半对一半错),
又用 这一项防止宽度塌缩成零——这就是 kobeval 从头到尾都用 Wilson 的原因。
3.2 Length-normalised multiple-choice scoring——悄悄决定 leaderboard 的那个数字
log-likelihood 模式让模型读题目 ,然后比较各个选项 整句话的概率:
- → 直接用原始的总 log-prob,这偏向更短的选项(token 少 = 被乘上概率的次数少)
- → 除以 token 数,也就是使用每 token 平均 log-prob
这两行就是 lm-evaluation-harness 里的 acc 和 acc_norm,而在真实的 benchmark 上,
它们给出的分数并不相同,有时甚至会让模型的名次对调——
当你看到两篇 paper 报告的 ThaiExam 数字不一致时,头号原因就是 取值不同,
而两篇都根本没写自己用的是哪一个值。
3.3 无偏的 pass@k——用于可以自动判分的题目
数学题和代码题允许我们采样多个答案,然后问一句"里面有没有对的"。 采样 次、对了 次,pass@ 的正确估计量是
凭直觉写出来的估计量 是有偏的: 函数 关于 是凹函数(concave), 因此根据 Jensen 不等式 —— 把估计值塞进一个非线性函数里,期望值就不会等于真值了。 而上面那个二项式公式表示的是"从 个里面抽 个、整组全错的比例",可以证明它恰好是无偏的。
3.4 McNemar's test——在同一套题上比较两个模型
模型 A 和 B 做同一套题,令 = A 对但 B 错的题数, = B 对但 A 错的题数:
拿它去和 比较(带 continuity correction)。关键在于 paired 这个词: 两个都答对的题和两个都答错的题根本没有出现在公式里,因为它们说明不了谁更强。 如果你用 t-test 去比较两个 accuracy,好像它们来自两套不同的题目, 那你就是把"同一道题"这个结构给扔掉了,然后要达到同样的 power 就得多用好几倍的题目。
3.5 Contamination check——考题有没有泄漏进训练数据
对于考题 和训练语料 ,定义 -gram 的重合率:
其中 是全部 -gram 的集合。对泰语我们使用字符级 k-gram(例如 20 个字符), 因为泰语的分词本身就有歧义。如果 很高(例如超过 0.7), 就要怀疑模型已经"见过答案"了——这道题上的分数衡量的是记忆,不是能力。
这篇文章大约是本章的前 30%。其余部分——环境准备、数据准备、核心代码、实测结果与总结——都在免费的 LLM Finetuning 课程中,使用 Google 登录即可阅读。
在课程中阅读完整章节 →