🏠 总目录📚 本教程 13 · 评测集也是数据
📑 本页目录(点开跳转)

13 · 评测集也是数据

92 分钟 | ⭐⭐ 全站评测方法论的总入口


🎯 一句话

评测集是你唯一的尺子,而尺子本身也是一份数据 —— 它会被抽错、被污染、被磨损。 全站有四个地方在讲评测,各说各的一块; 这一章讲它们共同的底层:这把尺子怎么造、怎么验、什么时候该扔掉重造。


🗺️ 一、先把全站四处评测的分工说清楚

第 13 章(你在这里) 评测集【本身】是怎么造出来的 抽样 · 大小 · 污染 · 评分器校准 置信区间 · 什么时候该换 造好的尺子,被这四处用 大模型导论 11 别人的榜单 智能体 14 Agent 怎么评 Kaggle 22 榜单博弈 上线之后 02 离线 → 线上
这一章造尺子,全站另外四处用这把尺子 —— 底层错了,上面四层全部无效
去哪 它负责什么 / 什么时候去读
大模型全景导论 11 · 评测体系 公开榜单那一侧:四类 benchmark、五个陷阱、条件不对等。选型时看到别人的分数,想知道能不能信
智能体工程教程 14 · 评测 Evals 评分器那一侧:三类评分器、评产出不评路径、pass@k vs pass^k、读转录。要给 Agent 建评测套件时
Kaggle 22 · 竞赛元策略 榜单博弈那一侧:Public/Private 分裂、shake-up、信任 CV。你的分数要和别人排名次时
模型上线之后 02 · 离线好不等于线上好 下游那一侧:五道坎、幸存者偏差、校准与 ECE。尺子测出来是好的、线上却不是时

一句话分工导论 11 回答「别人的分数能不能信」,智能体 14 回答「怎么给它打分」, Kaggle 22 回答「排名次时会出什么事」,上线之后 02 回答「测对了为什么线上还不对」—— 而这一章回答最前面那个问题:「你手里这把尺子,本身准不准」。 💀 如果这一层是错的,后面四层全部无效,因为它们都建在同一个数字上。


🎲 二、抽样:评测集不是"随机切一刀"

大多数评测集是这么来的train_test_split(X, y, test_size=0.2)。 这一行有三个隐含假设,而它们经常都不成立。

   ① 训练分布 = 线上流量分布?
      💀 训练数据是历史的、被旧模型筛过的、被采样过的
      ⭐ 评测集要对齐的是【线上流量】,不是训练集

   ② 每条样本独立?
      💀 同一用户/会话/文档的多条会跨集(第 14 章的分组泄漏)
      ⭐ 切分单位必须和线上"遇到一个新东西"的单位一致

   ③ 总体准确率就够了?
      💀 它被头部样本主导,长尾层根本没被测到

假设 ③ 值得用数字看一眼。设想一个搜索场景,query 分三层: 头部 60%(模型准确率 0.95)、腰部 30%(0.80)、长尾 10%(0.45)。 600 条的评测集,两种造法:

头部 腰部 长尾 总体估计的标准误
简单随机 600 条 落到 360 条
±2.25pp
落到 180 条
±5.84pp
落到 60 条
±12.59pp
💀
±2.55pp
分层设计 200/200/200 ±3.02pp ±5.54pp ±6.89pp ±2.55pp

⭐⭐ 这张表是本节的全部内容同样 600 条,只是换了个分配方式,长尾层的置信区间从 ±12.59pp 收窄到 ±6.89pp, 而总体估计的精度一分没亏(都是 ±2.55pp)。 💀 简单随机抽样下,长尾层的 CI 有 25 个百分点宽 —— 那一层等于根本没测。 而长尾恰恰是你最想知道模型行不行的地方。

再优化一步(Neyman 最优分配,按 层权重 × 层内标准差:算出来是 43.5% / 39.9% / 16.6%,总体标准误 ±2.40pp —— ⭐ 只比均分好 0.15pp,说明「每层给够」已经拿到了绝大部分收益,别过度优化。

分层该按什么切(按优先级)

维度 为什么
业务价值 / 流量占比 ⭐⭐ 报告时要能按流量加权还原总体,也要能看清高价值那一撮
难度梯度 只放难题 → 分数全在 20% 上下没区分度;只放简单题 → 很快饱和。经验:20% 简单 / 60% 中等 / 20% 难,并且分开报
时间 至少留一段比训练数据更晚的样本,它才能反映"未来"
已知的失败模式 每一类线上事故都该在评测集里留下一组"回归题"

⚠️ 一条最容易错、代价也最大的评测集的切分单位要和线上一致。 线上遇到的是新用户 → 按用户切;线上永远是老用户的新行为 → 按时间切。 切错了不是误差,是第 14 章讲的分组泄漏, 实测能凭空多出 0.14 个 AUC。


📏 三、多大才够:把置信区间算出来

先接受一件事:任何评测分数都是一个估计,它有误差棒。 下面是真跑的 bootstrap(真实准确率 0.82,B = 10000 次重采样):

评测集 n 点估计 bootstrap 95% CI 半宽 Wilson 区间
50 0.8200 [0.700, 0.920] ±11.0pp [0.692, 0.902]
100 0.8400 [0.760, 0.910] ±7.5pp [0.756, 0.899]
300 0.8100 [0.767, 0.853] ±4.3pp [0.762, 0.850]
1000 0.8300 [0.806, 0.852] ±2.3pp [0.806, 0.852]
10000 0.8192 [0.812, 0.826] ±0.74pp [0.812, 0.827]

第二行就是导论第 11 章那个结论的出处100 题的评测集误差棒有 ±7.5 个百分点 ——「A 得 82 分、B 得 79 分」 在 100 题上是完全没有意义的一句话。 💡 而 n ≥ 300 时 Wilson 和 bootstrap 几乎一模一样,Wilson 还是一行公式 —— ⭐ 准确率这种简单比例用 Wilson;bootstrap 留给 F1/NDCG/AUC 和成对比较。

想分辨 x 个百分点的差距,各需要多少条(80% 功效、5% 显著性):

想分辨的差距 每个模型需要(独立评测) 换成成对评测(一致率 0.95)
10pp 277 条
5pp 1,022 条
3pp 2,734 条
1.5pp 约 11,000 条 863 条 ⭐⭐
1pp / 0.5pp 23,641 条 / 93,574 条 💀

成对比较:同一套题跑两个模型,然后只看每条题上的胜负差。 它能省多少,取决于两个模型有多像:

两模型判定一致率 成对差的标准误 各测各的标准误 省多少样本
0.97 0.00551 0.01718 9.7 倍
0.95 0.00711 0.01718 5.8 倍
0.90 0.00994 0.01718 3.0 倍
0.80 / 0.70 0.01432 / 0.01747 0.01718 1.4 倍 / 白搭

⭐⭐ 两个模型越像,成对比较越值钱 —— 而日常研发里绝大多数改动 (换个提示词、多训两个 epoch、加一个特征)恰恰一致率极高同一份 1000 条的评测集,用成对比较能分辨 1.5pp,各测各的只能分辨 5pp。 💀 如果你现在报的是"新模型 82.4%,旧模型 79.9%"这种各测各的口径, 你其实是把一把好尺子当成差尺子在用。

import numpy as np


def paired_bootstrap(correct_a, correct_b, B=10000, seed=0):
    """成对 bootstrap:两个模型跑【同一套题】,重采样的是题不是模型。
    correct_a / correct_b: 0/1 数组,第 i 位是两模型在第 i 题上对不对"""
    a, b = np.asarray(correct_a, float), np.asarray(correct_b, float)
    assert len(a) == len(b), "必须是同一套题,顺序一致"
    idx = np.random.default_rng(seed).integers(0, len(a), size=(B, len(a)))
    d = a[idx].mean(1) - b[idx].mean(1)             # ⭐ 两个模型共用同一批下标
    lo, hi = np.quantile(d, [0.025, 0.975])
    return {"差": round(float(a.mean() - b.mean()), 4),
            "差的95%CI": (round(float(lo), 4), round(float(hi), 4)),
            "显著": bool(lo > 0 or hi < 0),          # ⭐ CI 含 0 = 没证据说 A 更好
            "一致率": round(float((a == b).mean()), 4)}   # 越高,成对越值钱


def cluster_bootstrap(correct, group, B=10000, seed=0):
    """⚠️ 评测集不独立时(同来源/同用户/同文档的多条题)必须用这个:
    重采样的单位是【组】,不是【条】"""
    correct, group = np.asarray(correct, float), np.asarray(group)
    keys = np.unique(group)
    buckets = [correct[group == k] for k in keys]
    rng = np.random.default_rng(seed)
    stats = [np.concatenate([buckets[j] for j in
             rng.integers(0, len(keys), size=len(keys))]).mean() for _ in range(B)]
    lo, hi = np.quantile(stats, [0.025, 0.975])
    return round(float(correct.mean()), 4), (round(float(lo), 4), round(float(hi), 4))

💀 cluster_bootstrap 不是可选项。 实跑:3000 条题来自 100 个来源 (每源 30 条同质题),点估计 0.8073 —— 按【条】bootstrap ±1.42pp(假的);按【来源】±2.92pp(真的),宽 2.1 倍。 ⭐⭐ 评测集里有多少"条"不重要,有多少个【独立单位】才重要: 3000 题只来自 100 个模板 / 用户 / 文档,有效样本量是 100,不是 3000。


🦠 四、污染:你的评测集可能已经被看过了

污染 = 评测集的信息,以某种方式进过训练。 三种形态,越往下越难查:

   ① 训练集里混进了评测样本(或它的近似重复)
      —— 回填、多次导出、宽表 join、数据增强都会造成
      ⭐ 就是第 14 章的"跨集重复行",哈希/MinHash 就能查

   ② 评测集被爬进了预训练语料                 💀 最常见
      —— 你换了个开源基座,而评测集三年前发在了 GitHub 上

   ③ 软污染:你自己反复看着评测集调参         ⭐⭐ 最难查
      —— 没有一条样本泄漏,但【选择】泄漏了(第七节)

污染有多贵?实跑(真实能力 0.72,被污染的题几乎全对)

污染率 你报出来的分数 真实能力 虚高 见过组 vs 没见过组
2% 0.7170 0.7112 +0.6pp 1.000 vs 0.711(差 28.9pp
10% 0.7490 0.7222 +2.7pp 0.990 vs 0.722(差 26.8pp)
30% 0.8095 0.7293 +8.0pp 0.997 vs 0.729(差 26.7pp)

注意最后一列比第三列有用得多总分的虚高(+0.6pp)在低污染率下小得看不见,但"见过组 vs 没见过组"的差 从 2% 污染率起就稳定在 27 个百分点。 💀 所以查污染不能看总分,要看分组差 —— 这个信号在污染率很低时就已经满格了。

四种探针,从便宜到贵

探针 怎么做 / 判读
① 去重 哈希 / MinHash / 感知哈希查训练集 ∩ 评测集。期望 0,非 0 就先别看指标
② n-gram 重叠 评测样本的 n-gram 有没有出现在训练语料里(n 怎么选见下表)
③ 改写测试 把评测题同义改写。原题比改写题高 > 3pp 就该查污染
④ 新鲜题对照 ⭐⭐ 发布日期晚于模型训练截止的题做对照组。新鲜组明显更低 = 老题被背过

n 该取多少(实跑:500 条评测题,真污染 70 条 = 40 条原样 + 30 条改了两个词)

n 命中 其中真污染 精确率 召回率
2 500 70 0.140 💀 1.000
4 75 70 0.933 1.000
8 ~ 20 70 70 1.000 1.000
30 40 40 1.000 0.571 ⚠️

n 的选择有一个很窄的甜区n 太小(2–3)全是误报(自然语言里短片段本来就到处重合,精确率只有 0.14); n 太大(30)漏掉所有被改写过的题(召回掉到 0.571)。 实践取 8–13。 而且要同时跑改写测试,因为 n-gram 对语义级的改写完全无效。

改写测试的实跑(同一个模型,原题 vs 同义改写题):

污染率 原题 改写题
0% 0.7083 0.7103 −0.2pp(噪声)
10% 0.7287 0.7140 +1.5pp
20% 0.7720 0.7140 +5.8pp
40% 0.8220 0.6973 +12.5pp 💀

改写测试是这四种探针里唯一不需要访问训练语料的。 用开源基座、用别人的 API 时你根本拿不到训练数据,①② 全部失效,③④ 是你仅有的工具。 💡 做法:抽 200 条评测题用另一个模型同义改写、人工核一遍语义没变,两套一起跑。 原题比改写题高 3 个百分点以上,就按有污染处理。


🛑 读到这里可以停 —— 前半章讲完了(约 30 分钟)。 后半章还有:评分器也要校准 · 结果怎么报:别拿单点数字比大小 · 什么时候该换评测集 · 事故复盘:一次涨了 6.9 个点的"重大突破" 回来的时候不用重读,直接从下一节接着看就行。


⚖️ 五、评分器也要校准

智能体 14 讲了三类评分器的分工, 以及那句「LLM 评委上线前必须先和人工对齐一次」。 这一节讲"对齐"具体怎么做,以及不对齐会亏多少。

把评委当成一个二分类器,用一小份人工双标数据量出它的 sens(真通过的判成通过)和 spec(真不通过的判成不通过):

评委 报出来的通过率(真值 0.700) 偏差 一致率 Cohen κ
sens .95 / spec .95 0.6817 −1.8pp 0.951 0.885
sens .90 / spec .90 0.6613 −3.9pp 0.901 0.773
sens .95 / spec .80 0.7266 +2.7pp 0.906 0.770
sens .80 / spec .95 0.5771 −12.3pp 💀 0.846 0.672

💀 注意第 2、3 行:两个评委的一致率几乎一样(0.901 vs 0.906), 但一个把通过率压低 3.9pp,另一个抬高 2.7pp —— 方向相反。 只报"评委和人工的一致率 90%"是不够的,必须分开报 sens 和 spec, 因为偏差的方向完全由它们的不对称决定

更贵的一件事:有偏评委会系统性压缩模型之间的差距(两个模型真实差 5pp):

评委 测出的差 只剩真差的
完美 +4.7pp 94%
sens .95 / spec .95 +4.35pp 87%
sens .90 / spec .90 +3.85pp 77%
sens .80 / spec .90 +3.43pp 69%

💀 第 11 章那个"评测集噪声让真实改进打折"是同一个机制 —— 评分器不准,本质上就是给评测集加了一层噪声。 ⭐ 后果一样:真实的改进会被判成"没效果"然后被放弃。

好消息:只要你知道 sens 和 spec,偏差可以直接校正回来。

import numpy as np


def calibrate_judge(judge_pred, human_gold):
    """拿一小份人工双标数据,量出评委的 sens / spec / kappa(都是 0/1 数组)"""
    j, g = np.asarray(judge_pred, int), np.asarray(human_gold, int)
    sens = float(j[g == 1].mean())                 # 真通过里,评委判通过的比例
    spec = float(1 - j[g == 0].mean())             # 真不通过里,评委判不通过的比例
    po = float((j == g).mean())
    pe = j.mean() * g.mean() + (1 - j.mean()) * (1 - g.mean())
    # ⭐ 分辨力 = sens + spec − 1,它决定校正后误差棒被放大多少
    return {"sens": round(sens, 4), "spec": round(spec, 4), "一致率": round(po, 4),
            "kappa": round((po - pe) / (1 - pe), 4), "分辨力": round(sens + spec - 1, 4)}


def correct_rate(observed, sens, spec, n=None):
    """把评委报出来的通过率校正回真实通过率。
    ⚠️ 校正会把置信区间放大 1/(sens+spec-1) 倍"""
    power = sens + spec - 1
    assert power > 0, "评委还不如抛硬币,校正没有意义"
    out = {"校正后": round(float(np.clip((observed - (1 - spec)) / power, 0, 1)), 4),
           "放大倍数": round(1 / power, 2)}
    if n:
        se = np.sqrt(observed * (1 - observed) / n) / power
        out["校正后95%CI半宽"] = round(float(1.96 * se), 4)
    return out


# 实跑:sens=.90 spec=.90 的评委,真值 0.70 时报 0.6607 → 校正回 0.7009(误差 +0.0009)
#      sens=.95 spec=.80 的评委,真值 0.70 时报 0.7254 → 校正回 0.7005(误差 +0.0005)

⚠️ 校正不是免费的:它按 1/(sens+spec−1) 放大方差。 n = 300 时实跑: 分辨力 0.90 → CI 从 ±5.28pp 变 ±5.87pp(1.11 倍);0.70 → ±7.54pp(1.43 倍); 0.50 → ±10.56pp(2.00 倍)。红线:sens + spec − 1 低于 0.6 就别校正了 —— 换评委、改 rubric,或交给人工。 💡 而量出 sens/spec 只要 150–300 条人工双标(每一边至少 60 条), 这是所有评测投入里回报最高的一笔。


📋 六、结果怎么报:别拿单点数字比大小

前面五节的所有工作,最后都落在一句报告怎么写上。

   ❌ 「新模型 82.4%,旧模型 79.9%,涨了 2.5 个点」

   ✅ 「新模型 82.4% [80.1, 84.6],旧模型 79.9% [77.5, 82.2];
       成对 bootstrap 的差 = +2.5pp [+0.9, +4.1],两模型一致率 0.93;
       评测集 v3.2(n=1000,分层:头部 400 / 腰部 400 / 长尾 200),
       评分器 sens .93 / spec .91(对齐样本 220 条,κ=0.81),
       污染探针:跨集重复 0,13-gram 命中 0,改写测试差 +0.4pp」

上面两句话的区别,不是"严谨",是"能不能做决策": 第一句里,你不知道 2.5 个点是不是噪声; 第二句告诉你 CI 下界是 +0.9pp,所以"确实有提升"这个结论成立, 但"提升有 2.5 个点"这个说法不成立(它可能只有 0.9)。

评测报告的最小字段清单(少一个就退回)

字段 为什么
n(以及独立单位数)+ 95% CI 没有 n 和区间的百分比不能比大小 ⭐⭐
成对差的 CI + 一致率 比两个各自的 CI 有用得多(能省 3–10 倍样本)
评测集版本号 + 生效日期 换过评测集的两个数字不能直接比
分层分数(至少 3 层) 总体持平但某层塌了,是最常见的形状
评分器的 sens/spec/κ 决定这个数字要不要校正、能不能信
污染探针的三个数字 都为 0 才有资格看分数
这套评测集被用来选择过多少次 第七节的过拟合程度 ⭐

🔄 七、什么时候该换评测集

评测集会磨损。 磨损的方式不是样本变了,是你看它的次数变多了

实跑:从 N 个真实水平几乎一样的候选里,挑评测分最高的那个, 它在一套新的评测集上会掉多少(n=500):

试过的候选数 选中那个的虚高
1 −0.09pp(无)
5 +1.93pp
20 +3.09pp
50 +3.71pp
200 +4.52pp 💀

⭐⭐ 这就是 Kaggle 22 讲的 shake-up 在日常研发里的样子:你的评测集就是 Public LB,线上就是 Private LB。 竞赛选手一年经历一次会印象深刻;日常研发里这件事每天都在发生, 只是没有人在结束那天公布 Private 榜。 💀 被跑过 200 次实验的评测集,分数至少虚高 4.5 个百分点, 而这个虚高不会出现在任何置信区间里 —— CI 只管抽样误差,不管选择偏差。

四个该换(或该补)评测集的信号

   ① 饱和:分数进了 0.95+,或连续三次实验的差都落在 CI 内
      → 该【毕业】进回归套件(只保证不退化),另建一套更难的
        ⭐ 这就是智能体 14 的"毕业"机制

   ② 选择次数超标:被用来做过 20 次以上的取舍
      → 按上表,此时虚高已达 3 个百分点

   ③ 污染探针命中,或者换了基座模型 / 换了数据源

   ④ 离线和线上的相关性断了                  ⭐⭐ 最硬的信号
      → 攒 5–10 组「离线变化 vs 线上变化」画散点
      → 相关性消失 = 这把尺子已经不指向线上了
        (做法见【上线之后 02】的"换算率")

一个便宜到没有借口不做的办法:把评测集拆成 dev 和 test。 dev 随便看随便调;test 有配额 —— 每季度只准跑 3 次,每次记录在案。 这就是"日常研发版的 Public/Private 分裂", 成本只是把评测集切两半,收益是你终于有了一个没被自己看烂的数字。 💡 而换集的正确姿势不是全部重造,是滚动替换:每季度按线上流量重抽 20% 替换掉最老的一批,并且新旧两版并跑一次记录分差 —— 有了这个偏移量, 历史分数才还能拿来比。


💀 八、事故复盘:一次涨了 6.9 个点的"重大突破"

   系统:站内搜索的相关性排序模型
   评测集:2023 Q1 建的 12,000 条 <query, doc, 相关性 0–3> 人工标注
   用法:两年里所有实验都在它上面比,跑过 300+ 次
   ⭐ 这套评测集在 2023 年被整理成公开数据集发布过(为了招人和写论文)

发生了什么

时间 事件
2025 Q2 上了一版基于开源预训练模型的新排序器,评测集 NDCG@10 从 0.712 跳到 0.781(+6.9pp) —— 两年来最大的一次提升,评审全票通过
第 3–4 周 灰度 5%:人工满意度和点击深度都没动,GMV −0.3%
第 6 周 被解释为"灰度样本量不够",扩到 50%
第 12 周 全量 6 周后仍无正向,开始查评测集

查出来两件事(一次事故,两个根因)

   ① 污染
      新排序器的预训练语料里,有团队自己两年前公开的那份评测集
      13-gram 探针:12,000 条里 3,180 条(26.5%)精确匹配上  💀
      分组:命中组 NDCG@10 = 0.923,未命中组 0.729(差 19.4pp)
      ⭐ 剔除污染后,真实提升是 +1.7pp,不是 +6.9pp

   ② 老化 + 选择偏差
      两年前的 query 分布,而业务已从 3C 扩到生鲜和本地生活
      → 评测集里【0 条】生鲜 query,线上生鲜占 22% 搜索量
      → 加上 300 次实验的选择偏差,按第七节至少再虚高 4pp

为什么没被发现

   ① 评测集从建好那天起就没变过,也没有 owner
      💀 它不在任何数据版本和血缘系统里(第 17 章)——
         只是某个人电脑上的一个 parquet 文件

   ② 团队【自己】把它公开了
      ⚠️ 而"我们自己的评测集"和"公开语料",在所有人的心智里
         是两件事 —— 没人把它们连起来想过

   ③ 只报单点数字。+6.9pp 看起来"远超噪声",没人算过区间,
      也没人看过"提升是不是集中在某一部分题上"

   ④ 灰度只看 GMV(噪声大、要几周)
      ⭐ 没看"离线提升 vs 线上提升"这个更快也更硬的信号

代价

   回滚前全量运行   6 周,GMV 影响约 −0.4%(按季度反推 ≈ ¥340 万)
   重建评测集       按当前流量分层重抽 15,000 条 + 仲裁,¥41 万,7 周
   ⭐ 但最贵的是回溯:
     过去两年 300+ 次实验里,有 47 次的结论落在 ±2pp 以内
     —— 全部无法判断真假 = 两年的方向决策没有有效证据

该补什么

缺失 补上之后
评测集没有版本、没有 owner ⭐⭐ 进数据版本系统:owner、changelog、生效日期、被引用的实验列表(第 17 章
从没查过污染 每次换基座模型必跑三件事:去重、n-gram 探针(n=8~13)、改写测试
评测集永不更新 每季度按线上流量重抽 20%;query 分布与线上的 JS 散度 > 0.15 强制重建
300 次实验共用一套 ⭐⭐ dev / test 分离,test 有季度配额(3 次),每次使用记录在案
只报点估计 报告必须带 n + 95% CI + 成对差的 CICI 重叠一律标记为"未验证"
公开发布过评测集 ⭐ 公开的那份和内部用的必须是两套不相交的题;公开即视为"已污染,永久退役"

💀 这个事故一句话总结他们两年里一直在优化的,不是搜索质量,是那 12,000 条题的分数。 ⭐ 而这两件事在评测集是干净、新鲜、独立的时候是同一件事 —— 在它被污染、老化、看烂之后,就不是了。


🔗 这一章连到哪里

去哪 为什么
大模型全景导论 11 · 评测体系 公开榜单那一侧:四类 benchmark、五个陷阱、报告条件不对等。它回答「别人的分数能不能信」,本章回答「你的尺子准不准」;那里的「100 题只能分辨 8 个点」就是本章第三节那张表的第二行
智能体工程教程 14 · 评测 Evals 评分器那一侧:三类评分器、评产出不评路径、pass@k vs pass^k、读转录、基础设施噪声。本章第五节的"校准",就是它那句"LLM 评委上线前必须先和人工对齐"的具体做法
Kaggle 22 · 竞赛元策略 榜单博弈那一侧:Public/Private 分裂、shake-up、信任 CV。本章第七节实测的"试 200 个候选虚高 4.5pp",就是 shake-up 的日常研发版
模型上线之后 02 · 离线好不等于线上好 下游那一侧:五道坎、幸存者偏差、校准与 ECE。本章负责把尺子造对,那一章回答"尺子对了为什么线上还是不一样"
模型上线之后 12 · AB 实验你以为你懂了 置信区间、peeking、多重比较的统计基础;本章第六、七节的数学在那里讲透
ML 基础 05 · 评估与过拟合 切分、交叉验证的基本功。本章是它在"评测集本身也会坏"这个前提下的加强版
第 11 章 · 标签噪声 评测集的标签也会错;那一章实测了 10% 评测噪声如何把 3000 条的尺子变成 1250 条
第 14 章 · 数据泄漏的七种来源 污染是泄漏的一种;那一章的"切分体检三个数字",第一道就是跨集重复行
第 15 章 · 采样偏差与幸存者偏差 第二节的抽样一旦抽偏,后果就是那一章讲的东西
第 17 章 · 数据版本与血缘 评测集必须有版本号和 owner —— 本章事故复盘里最根本的一条

✅ 检查点

  1. 全站四处讲评测的地方各负责什么?这一章在它们中间是什么位置?
  2. train_test_split 造评测集有哪三个隐含假设?各自怎么不成立?
  3. 分层那个例子里,简单随机 600 条和分层 200/200/200,长尾层的 CI 各是多少?总体精度差多少?Neyman 分配又能再好多少?
  4. 100 条 / 1000 条 / 10000 条评测集的 95% CI 半宽各是多少?什么时候用 Wilson、什么时候用 bootstrap?
  5. 想分辨 3 个百分点需要多少条?成对比较在一致率 0.95 时能省多少倍?为什么这对日常研发特别重要?
  6. 什么时候必须用 cluster bootstrap?那个实跑的两个区间分别是多少、差几倍?
  7. 污染的三种形态是什么?为什么查污染要看"见过组 vs 没见过组"而不是看总分?
  8. n-gram 探针的 n 该取多少?n=2 和 n=30 各会怎样(给数字)?拿不到训练语料时该用哪两个探针?
  9. 评委的一致率一样、sens/spec 不同会怎样?有偏评委对"模型间差距"做了什么?校正公式是什么、代价是什么、红线在哪?
  10. 一个被跑过 200 次实验的评测集虚高多少?为什么置信区间抓不到这个虚高?dev/test 分离该怎么做?
  11. 那个搜索事故的两个根因分别是什么?污染组和未污染组的 NDCG 差多少?真实提升是多少?最贵的代价是什么?
👀 答案
  1. 导论 11 = 公开榜单那一侧(别人的分数能不能信)、智能体 14 = 评分器那一侧(怎么给它打分)、Kaggle 22 = 榜单博弈那一侧(排名次时会出什么事)、上线之后 02 = 下游那一侧(测对了为什么线上还不对)。这一章在它们下面:回答「你手里这把尺子本身准不准」—— 💀 这一层错了,上面四层全部无效,因为它们都建在同一个数字上。
  2. 训练分布 = 线上分布(不成立:训练数据是历史的、被旧模型筛过的;评测集要对齐的是线上流量)②样本独立(不成立:同用户/会话/文档的多条会跨集,就是第 14 章的分组泄漏)③总体准确率就够(不成立:会被头部主导,长尾根本没被测到)。
  3. 简单随机:长尾只落到 60 条,CI ±12.59pp(等于没测);分层 200/200/200:长尾 ±6.89pp总体精度完全一样,都是 ±2.55pp —— 换个分配方式,长尾精度翻倍而总体一分没亏。Neyman 最优分配(43.5/39.9/16.6)只把总体做到 ±2.40pp,只好 0.15pp —— 说明「每层给够」已经拿到绝大部分收益,别过度优化。
  4. 100 条 ±7.5pp / 1000 条 ±2.3pp / 10000 条 ±0.74ppn ≥ 300 时 Wilson 和 bootstrap 几乎一样,而 Wilson 是一行公式 —— 准确率这类简单比例用 Wilson;F1、NDCG、AUC 这些没有解析式的,以及要做成对比较的,用 bootstrap
  5. 分辨 3pp 需要 2,734 条(独立评测)。成对比较在一致率 0.95 时省 5.8 倍样本(一致率 0.97 时省 9.7 倍):分辨 1.5pp,成对只要 863 条,各测各的要约 11,000 条。对日常研发特别重要是因为换提示词、多训两个 epoch、加一个特征这类改动,两个模型的一致率极高 —— ⭐ 报"新模型 82.4% 旧模型 79.9%"这种各测各的口径,等于把一把好尺子当差尺子用
  6. 评测集不独立时(同来源/同用户/同文档/同模板的多条题)。实跑:3000 条来自 100 个来源,按条 bootstrap ±1.42pp(假),按来源 cluster bootstrap ±2.92pp(真),宽 2.1 倍。⭐⭐ 有多少"条"不重要,有多少个独立单位才重要 —— 3000 题只来自 100 个模板,有效样本量是 100。
  7. ①训练集混进评测样本或其近似重复 ②评测集被爬进预训练语料(最常见)③软污染:自己反复看着评测集调参(最难查,没有样本泄漏但选择泄漏了)。要看分组差是因为:总分虚高在低污染率下小得看不见(2% 污染只虚高 0.6pp),而"见过组 vs 没见过组"的差从 2% 污染率起就稳定在 27 个百分点 —— 这个信号在污染率很低时就已经满格。
  8. 取 8–13n=2 时精确率只有 0.140(500 条全命中,自然语言里短片段本来就到处重合);n=30 时召回掉到 0.571(漏掉所有被改写过的题)。拿不到训练语料(开源基座、别人的 API)时,去重和 n-gram 全部失效,只剩③改写测试④新鲜题对照 —— 改写测试实跑:20% 污染时原题比改写题高 5.8pp,40% 时高 12.5pp,⭐ 高 3pp 以上就按有污染处理
  9. 一致率 0.901 和 0.906 几乎一样的两个评委,一个把通过率压低 3.9pp,另一个抬高 2.7pp,方向相反 —— 💀 只报一致率不够,必须分开报 sens 和 spec。有偏评委系统性压缩模型间的差距:真差 5pp,sens/spec 各 .90 的评委只测出 3.85pp(77%),.80/.90 只剩 69% —— 和第 11 章"评测噪声让真实改进打折"是同一个机制,真改进会被判成没效果然后被放弃。校正公式:真实 = (观测 − (1−spec)) / (sens + spec − 1),实跑校正后误差 < 0.1pp。代价是按 1/(sens+spec−1) 放大方差:分辨力 0.90 → CI 放大 1.11 倍,0.70 → 1.43 倍,0.50 → 2.00 倍。⭐ 红线:sens + spec − 1 低于 0.6 就别校正了,换评委、改 rubric 或交给人工。 量 sens/spec 只要 150–300 条人工双标。
  10. 至少虚高 4.52pp(试 20 个候选就已经 +3.09pp,试 5 个 +1.93pp)。CI 抓不到是因为 ⭐ 置信区间只管抽样误差,不管选择偏差 —— 你在一堆噪声里挑了最大的那个,这个动作本身不产生 CI。dev/test 分离:dev 随便看随便调,test 有使用配额,每季度只准跑 3 次且每次记录在案 —— 这是"日常研发版的 Public/Private 分裂"。换集用滚动替换:每季度按线上流量重抽 20%,并且新旧两版并跑一次记录分差,否则历史分数就不能比了。
  11. 污染:团队两年前把这套评测集公开发布过,新排序器的预训练语料里就有它 —— 13-gram 探针命中 3,180 / 12,000(26.5%)命中组 NDCG@10 = 0.923,未命中组 0.729,差 19.4pp,剔除后真实提升是 +1.7pp 而不是 +6.9pp。②老化 + 选择偏差:评测集是两年前的 query 分布,里面 0 条生鲜 query 而线上生鲜占 22% 搜索量;加上两年 300 次实验的选择偏差至少虚高 4pp。最贵的代价不是那 6 周 ¥340 万的 GMV,也不是 ¥41 万的重建费,而是过去两年 300+ 次实验里有 47 次的结论落在 ±2pp 以内,全部无法判断真假 —— 等于两年的技术方向决策没有有效证据支撑

🛑 可以停在这里

走神救援

⭐⭐评测集是你唯一的尺子,而尺子本身也是一份数据——会被抽错、被污染、被磨损。全站四处评测的分工:导论 11 = 公开榜单能不能信 / 智能体 14 = 怎么给它打分(三类评分器、评产出不评路径、pass@k vs pass^k)/ Kaggle 22 = 排名次时会出什么事(Public-Private、shake-up)/ 上线之后 02 = 测对了为什么线上还不对(五道坎、幸存者偏差、ECE);这一章在它们下面——💀这一层错了,上面四层全部无效①抽样train_test_split 有三个隐含假设,全都常常不成立——训练分布 ≠ 线上流量(⭐评测集要对齐的是线上,不是训练集)、样本不独立(同用户/会话跨集 = 第 14 章的分组泄漏)、总体准确率不够。⭐⭐分层实跑:600 条评测集,简单随机时长尾只落到 60 条、CI ±12.59pp(等于没测),改成分层 200/200/200 后长尾收到 ±6.89pp,而总体精度一分没亏(都是 ±2.55pp);Neyman 最优分配只再好 0.15pp,⭐说明"每层给够"就够了,别过度优化。分层按业务价值/流量占比、难度梯度(20% 简单 / 60% 中等 / 20% 难,分开报)、时间、人群、已知失败模式。②多大才够(bootstrap 实跑)100 条 ±7.5pp(这就是导论 11「100 题只能分辨 8 个点」的出处)、300 条 ±4.3pp、1000 条 ±2.3pp、3000 条 ±1.4pp、10000 条 ±0.74pp;⭐n≥300 时 Wilson 和 bootstrap 几乎一样,准确率用 Wilson,F1/NDCG/AUC 和成对比较才用 bootstrap。样本量:分辨 10pp 要 277 条、5pp 要 1022、3pp 要 2734、1pp 要 23641、0.5pp 要 93574。⭐⭐成对比较(同一套题跑两个模型,只看每条的胜负差)能省 3–10 倍样本:一致率 0.97 省 9.7 倍、0.95 省 5.8 倍、0.90 省 3.0 倍、0.70 白搭;分辨 1.5pp,成对只要 863 条,各测各的要约 11000 条——而日常研发的改动(换提示词、多训两个 epoch)一致率恰恰极高,💀报"新模型 82.4% 旧模型 79.9%"就是把好尺子当差尺子用。⚠️cluster bootstrap 不是可选项:3000 条题来自 100 个来源时,按条 bootstrap ±1.42pp(假)、按来源 ±2.92pp(真),差 2.1 倍——⭐⭐有多少"条"不重要,有多少个独立单位才重要,3000 题来自 100 个模板,有效样本量是 100③污染三形态:训练集混进评测样本 / 评测集被爬进预训练语料(最常见) / 软污染——自己反复看着评测集调参(最难查)。实跑:污染 5% 总分虚高 1.4pp、30% 虚高 8.0pp,⭐但真正的信号是"见过组 vs 没见过组差 27 个百分点",它从 2% 污染率起就满格——💀查污染要看分组差,不能看总分。四种探针:去重 / n-gram 重叠(⭐ n 有很窄的甜区:n=2 精确率只有 0.140 全是误报,n=30 召回掉到 0.571 漏掉改写过的题,实践取 8–13) / 改写测试(20% 污染时原题比改写题高 5.8pp、40% 时高 12.5pp,⭐ 高 3pp 以上就按有污染处理) / 新鲜题对照。⭐拿不到训练语料时(开源基座、别人的 API),前两个全失效,改写测试和新鲜题是你仅有的工具④评分器校准:把评委当二分类器量 sens/spec。💀一致率一样(0.901 vs 0.906)的两个评委,一个把通过率压低 3.9pp、另一个抬高 2.7pp,方向相反——只报一致率是不够的,必须分开报 sens 和 spec。更贵的是:有偏评委系统性压缩模型间的差距,真差 5pp 时 .90/.90 的评委只测出 3.85pp(77%)、.80/.90 只剩 69%——和第 11 章"评测噪声让真改进打折"是同一个机制,真提升会被判成没效果然后被放弃。⭐⭐校正公式:真实 =(观测 − (1−spec))/(sens + spec − 1),实跑校正后误差 < 0.1pp;⚠️代价是按 1/(sens+spec−1) 放大方差(分辨力 0.90 → CI 放大 1.11 倍,0.70 → 1.43 倍,0.50 → 2.00 倍),⭐红线:sens+spec−1 < 0.6 就别校正,换评委或交给人工;量 sens/spec 只要 150–300 条人工双标,是所有评测投入里回报最高的一笔。⑤怎么报:❌"涨了 2.5 个点" → ✅"+2.5pp [+0.9, +4.1],n=1000,一致率 0.93,评测集 v3.2,评委 sens .93/spec .91(κ=0.81),污染探针全 0"——⭐ 区别不是严谨,是能不能做决策:CI 下界 +0.9pp 说明"有提升"成立,但"提升 2.5 个点"不成立。⑥什么时候该换:⭐⭐评测集会磨损,磨损的方式是你看它的次数变多了。实跑:从 N 个水平几乎一样的候选里挑分最高的,它在新评测集上试 5 个虚高 1.93pp、20 个 3.09pp、200 个 4.52pp——这就是 Kaggle 的 shake-up 在日常研发里的样子:你的评测集是 Public LB,线上是 Private LB,只是没人在结束那天公布私榜。💀而这个虚高不会出现在任何置信区间里,因为 CI 只管抽样误差、不管选择偏差。四个换集信号:饱和(该"毕业"进回归套件)/ 选择次数 > 20 / 污染探针命中或换了基座 / ⭐⭐离线和线上的相关性断了。⭐最便宜的办法:dev/test 分离,test 每季度只准跑 3 次并记录在案;换集用滚动替换(每季度按线上流量重抽 20%,新旧并跑一次记录分差)。💀事故:搜索排序模型,新基座让评测集 NDCG@10 从 0.712 跳到 0.781(+6.9pp),灰度却毫无正向。两个根因:①团队两年前把这套评测集公开发布过,新基座的预训练语料里就有它——13-gram 探针命中 3180/12000(26.5%)命中组 0.923 vs 未命中组 0.729,差 19.4pp,剔除后真实提升只有 +1.7pp;②老化:两年前的 query 分布,里面 0 条生鲜 query 而线上生鲜占 22% 搜索量,加上 300 次实验的选择偏差至少再虚高 4pp。没被发现是因为:评测集没有 owner、不在任何版本和血缘系统里(只是某人电脑上的一个 parquet)、团队自己公开了它而"我们的评测集"和"公开语料"在心智里是两件事、只报单点数字、灰度只看 GMV 这种慢指标。代价:6 周全量 ≈ ¥340 万 GMV、重建评测集 ¥41 万 + 7 周,⭐而最贵的是——过去两年 300+ 次实验里有 47 次结论落在 ±2pp 以内,全部无法判断真假,等于两年的方向决策没有证据。💀一句话:他们两年里优化的不是搜索质量,是那 12000 条题的分数——而这两件事,只在评测集干净、新鲜、独立的时候才是同一件事。

下一节 👉 14-数据泄漏的七种来源.md

打卡记录保存在你的浏览器里,首页能看到总进度