前九篇训了很多模型,每次都报一个”准确率”。这一篇专门讲怎么评估——准确率什么时候会骗人,一个分类器的”好”怎么拆成几个能各自回答问题的数,一个概率输出可不可信,两个模型差 3 个点算不算真的差。每个概念都用几行 NumPy 手写一遍、与 scikit-learn 对数,然后对到 LLM 评测里那些反复出现的坑:过滤器的漏放与误杀、奖励模型准确率的上限是什么、judge 的一致性与位置偏差、benchmark 分数的置信区间、”20 个 benchmark 里领先 12 个”。

全篇的核心问题是:

一个安全分类器准确率 95%,能上线吗?1 LLM-as-judge 与人的一致率 80%,够不够?2 两个模型在 20 个 benchmark 上比,领先 12 个算赢吗?3

一、总览

1. 四个问题、一张表

本文按”评估要回答的四个问题”组织:分类器好不好(拆成哪几个数)、概率可不可信(校准)、估计稳不稳(区间)、两个模型怎么比(检验):

评估的四个问题、工具与本文里的数字
问题 工具 本文里的数字
分类器好不好 混淆矩阵 → 精确率 / 召回率 / F1(依赖阈值)· AUC(不依赖阈值) 同一个模型阈值 0.1 → 0.9,召回 0.985 → 0.721、精确率 0.765 → 0.980
准确率会骗人 类别不平衡 正例 3% 时”全判负”准确率 95.6%
概率可信吗 可靠性图、ECE、Platt / isotonic 校准 过度自信的模型 ECE 0.069 → 校准后 0.019,AUC 不变
人与 judge 一致吗 Cohen’s κ、位置偏差 一致率 0.918 的”永远选 A”judge,κ = 0
估计稳不稳 交叉验证、标准误、bootstrap MMLU 14042 题 ±0.8 个点;100 题私有集 ±9 个点
两个模型比 独立比较 vs 配对检验(McNemar)· 多重比较 500 题差 4.2 个点:独立 \(z = 1.38\) 不显著,配对 \(z = 3.13\) 显著

2. 本文的章节安排

本文的章节安排
章 主题 内容
二 混淆矩阵与四个指标
  • 四个格子(10 封邮件手算)
  • 精确率 / 召回率 / F1 各回答什么
  • 12 行手写与 scikit-learn 对数
三 阈值与 AUC
  • 阈值扫描曲线;ROC 怎么画、AUC 的概率含义(手写并验证)
  • 并列分数:一正一负同分算半对,逐个放行的手写版为什么随顺序变
四 类别不平衡 正例 3% 时准确率的陷阱
五 校准
  • 可靠性图与 ECE
  • 一个过度自信的模型
  • Platt 与 isotonic 校准前后
  • 校准映射成立的条件:同分布、先验不变
六 奖励模型与 judge
  • 准确率的上限是什么、不是什么
  • Cohen’s κ 手算
  • 位置偏差怎么量、怎么抵消;对换为什么消不掉长度偏好
七 一个分数稳不稳
  • 交叉验证;各折不是独立重复实验(图)
  • 标准误公式与 MMLU 的 ±0.8
  • bootstrap
  • 相关样本:按来源重抽(图)
八 两个模型怎么比 独立比较 vs 配对检验;多重比较
九 一条完整流程 定阈值 → 校准 → 留出评估 → 成对比较:每一步用哪份数据;偷看测试集的阈值为什么只能当上界;区间含 0 不是”一样好”
十 案例:银行电话营销
  • 45,211 次电话、11.7% 成交:一个泄漏特征、准确率为什么没用、按成本定阈值(0.07 而不是 0.5)、校准、两个模型差 0.001 AUC 是不是真的
  • 在验证集上重做:阈值与校准不碰测试集,配对 bootstrap
十一 本文小结  
十二 自测 八道题

3. 来龙去脉:每个指标都是一次翻车的产物

评估方法的来历
年 谁 / 什么 当时的问题 留下的东西
1940s 二战雷达操作员;Peterson、Birdsall、Fox 1954 屏幕上一个亮点是敌机还是噪声?把灵敏度调高抓得多但误报也多 ROC 曲线(receiver operating characteristic——”接收机”就是雷达接收机):每个阈值一个(误报率,命中率)点(第三章)
1950 Brier 天气预报说”70% 会下雨”,怎么评一个概率说得准不准 Brier 分数与校准的思想:说 70% 的那些天里应该有 70% 下雨(第五章)
1960 Cohen 两个医生给同一批病人下诊断,一致率 80% 有多少是碰巧 Cohen’s κ:扣掉随机一致之后的一致率(第六章);今天用来评 LLM judge 与人的一致性
1970s–1980s Swets 等,医学诊断 一项检查的”准确率”取决于病人里有多少真有病 ROC / AUC 进入医学;不平衡数据上准确率没用成为常识(第四章)
1998 / 2006 Dietterich;Demšar 两个算法各报一个准确率,差 1 个点,能说谁更好吗 配对检验、多数据集上的比较方法(第七、八章);”报一个数不报方差”被点名
1999 / 2017 Platt;Guo 等 SVM 不输出概率;现代神经网络的概率普遍过度自信 Platt scaling、温度缩放:事后校准(第五章)
2020s LLM 评测 benchmark 差 0.5 个点、judge 与人一致 75%、榜单一周变一次 以上每一条都在 LLM 评测里重现:标准误、配对、κ、多重比较(第六至八章)

这张表的每一行都是一次”分数骗了人”的事故:雷达上把灵敏度调到 100% 命中率、误报也 100%;天气预报永远说 50%;两个医生”高度一致”其实是都在瞎猜;癌症筛查”准确率 99%”是因为 99% 的人没病。评估方法不是数学家闭门造的,是每一次被数字骗过之后加的一道检查。第十章的案例把它们串在一份真实数据上走一遍。

二、混淆矩阵与四个指标

1. 四个数

二分类的结果是一张 2×2 的混淆矩阵:

二分类的混淆矩阵
  预测为正 预测为负
真实为正 TP(真正例,抓到了) FN(假反例,漏掉了)
真实为负 FP(假正例,误杀了) TN(真反例,放对了)

记法:第二个字母是预测(P 判为正、N 判为负),第一个字母是这个预测对不对(T 对、F 错)。”假正例”= 判为正、判错了 = 其实是负例被误杀。

用 10 封邮件数一遍(1 = 垃圾邮件):

10 封邮件的真实标签与预测
邮件 1 2 3 4 5 6 7 8 9 10
真实 1 1 1 1 1 0 0 0 0 0
预测 1 1 1 0 0 1 0 0 0 0
格子 TP TP TP FN FN FP TN TN TN TN

\(TP = 3, FN = 2, FP = 1, TN = 4\):5 封垃圾邮件抓到 3 封、漏了 2 封;5 封正常邮件误杀 1 封。

2. 派生指标

混淆矩阵的派生指标
指标 定义 回答的问题 LLM 上的实例
准确率 \((TP + TN) / \text{全部}\) 总体对了多少 类别不平衡时会误导(第四章)
精确率 \(TP / (TP + FP)\) 判为正的里有多少真的是正 安全过滤:拦下来的里有多少真有害(误杀率 \(= 1 -\) 精确率)
召回率 \(TP / (TP + FN)\) 真正的正例抓到了多少 安全过滤:有害内容漏放了多少;污染检测:泄漏的题找到了多少
F1 精确率与召回率的调和平均 两者的折中 阈值不好定时的单一指标
AUC ROC 曲线下的面积 分类器把正负例排开的能力,与阈值无关 比较两个质量分类器本身的好坏

代入 10 封邮件:准确率 \((3 + 4) / 10 = 0.7\);精确率 \(3 / (3 + 1) = 0.75\)——判为垃圾的 4 封里 3 封真是;召回率 \(3 / (3 + 2) = 0.6\)——5 封垃圾邮件抓到 3 封;F1 \(= 2 \times 0.75 \times 0.6 / (0.75 + 0.6) = 0.667\)。

为什么 F1 用调和平均而不是普通的算术平均:一个把所有邮件都判为垃圾的过滤器召回率 1.0、精确率 0.5,算术平均 0.75 看起来不错;如果精确率是 0.01(一万封里只抓对一封)、召回率 1.0,算术平均仍有 0.5,调和平均只有 0.02——调和平均要求两个都不差,一个很低就整体很低。

精确率与召回率是一对:精确率问”我说是的有多准”,召回率问”真的是的我抓了多少”。安全过滤器要召回(不能漏放),推荐系统要精确率(推的要准),数据质量过滤在两者之间。

3. 十二行手写

def confusion(y, pred):
    tp = int(((pred == 1) & (y == 1)).sum()); fp = int(((pred == 1) & (y == 0)).sum())   # ① 四个格子就是四次计数
    fn = int(((pred == 0) & (y == 1)).sum()); tn = int(((pred == 0) & (y == 0)).sum())
    return tp, fp, fn, tn

def prf(y, pred):
    tp, fp, fn, tn = confusion(y, pred)
    prec = tp / max(1, tp + fp); rec = tp / max(1, tp + fn)                              # ② 精确率;召回率
    f1 = 2 * prec * rec / max(1e-9, prec + rec)                                          # ③ 调和平均
    return prec, rec, f1

def roc_auc(y, p):
    order = np.argsort(-p)                                                              # ④ 按分数从高到低扫阈值
    y = y[order]
    tpr = np.cumsum(y) / y.sum(); fpr = np.cumsum(1 - y) / (1 - y).sum()                 # ⑤ 每个阈值处的召回与假正率
    return float(np.trapezoid(tpr, fpr)), fpr, tpr                                      # ⑥ 曲线下面积

混淆矩阵 (TP, FP, FN, TN) 手写 (1131, 98, 75, 1096);sklearn (1131, 98, 75, 1096)
精确率 0.9203 召回率 0.9378 F1 0.9290
AUC 手写 0.9726;sklearn 0.9726

三、阈值与 AUC

1. 同一个分类器、不同阈值

分类器输出的是概率(第三篇的 \(\sigma(w^T x + b)\)),”判为正”要一个阈值。同一个逻辑回归,阈值从 0.1 到 0.9——把它读成一个有害内容过滤器:

同一个逻辑回归在阈值 0.3 / 0.5 / 0.7 下的三张混淆矩阵——阈值升高,FP 从 173 降到 53(误杀少),FN 从 46 升到 142(漏放多);右侧是把阈值从 1 扫到 0 画出的 ROC 曲线,三个圆点是这三个阈值在曲线上的位置,AUC = 0.973

同一分类器在不同阈值下的混淆矩阵与指标
阈值 TP FP FN TN 精确率 召回率 F1 含义
0.1 1188 365 18 829 0.765 0.985 0.861 宽松:漏放少、误杀多
0.3 1160 173 46 1021 0.870 0.962 0.914  
0.5 1131 98 75 1096 0.920 0.938 0.929  
0.7 1064 53 142 1141 0.953 0.882 0.916 严格:误杀少、漏放多
0.9 870 18 336 1176 0.980 0.721 0.831  

把阈值从 0.02 扫到 0.98,四个指标连成曲线:

左:横轴阈值,精确率(蓝)单调上升、召回率(红)单调下降,两条线在 0.5 附近交叉,F1(绿)在中间最高、两端下坠,准确率(灰虚线)与 F1 形状相近;右:ROC 曲线紧贴左上角,AUC 0.973,三个蓝点是阈值 0.3 / 0.5 / 0.7 的位置,灰色对角线是随机分类器

阈值高,精确率高、召回率低;阈值低,反过来。 同一个模型,不同阈值就是不同的过滤器。要求”漏放不到 5%”(召回 ≥ 95%),阈值就得放到 0.4、接受精确率 90%(每 10 个拦下的有 1 个是误杀)。

数据过滤是这个权衡的直接应用——质量分类器阈值取高,留下的数据干净但少;取低,数据多但脏。FineWeb-Edu 按教育分 3 分切留下约 1.3T token,切在 2 分留下几倍多。这个决定没有标准答案,取决于你有多少数据、模型多大。

2. AUC:不依赖阈值的指标

ROC 曲线是把阈值从 1 扫到 0,每个阈值画一个点(横轴假正率 \(FP / (FP + TN)\)——所有负例里被误判为正的比例,10 封邮件的例子是 \(1/5 = 0.2\);纵轴召回率)连成的线:阈值 1 时什么都不判正,在左下角 (0, 0);阈值 0 时全判正,在右上角 (1, 1)。上面手写代码的 ④⑤ 就是这个扫描:按分数从高到低排序,每往下放一个样本就是把阈值降到它的分数,累计的正例数 / 负例数就是当前的召回 / 假正率。

AUC 是它下面的面积,取值 \([0, 1]\):0.5 是对角线(随机)、1 是完美,低于 0.5 说明分数方向反了(把分数取负就回到 \(1 - \text{AUC}\))。它有一个直觉的解释:随机取一个正例一个负例,分类器给正例更高分数的概率。验证一下——随机抽 20 万对(一正一负):

随机抽 20 万对(一正一负),正例分数更高的比例 0.9721——这就是 AUC 的含义(AUC 0.9726)

比较两个分类器本身(不是两个阈值)用 AUC;决定用哪个阈值用精确率 / 召回率。两件事不要混。

3. 并列分数:一正一负同分算半对

上面的手写版按分数排序后逐个放行,隐含了一个假设:分数两两不同。LLM judge 给 1–5 分、分类器只输出 0/1、概率四舍五入到小数点后一位——都会出现大量并列,这时 AUC 该怎么算?4 回到概率定义:随机一正一负,正例分数更高算 1,同分算 0.5(Mann–Whitney U 的约定)。6 个样本手算:

y = [1, 1, 0, 1, 0, 0], p = [0.9, 0.8, 0.8, 0.5, 0.5, 0.2]:3 正 × 3 负 = 9 对,正例分数更高 6 对、同分 2 对 → AUC = (6 + 0.5 × 2) / 9 = 0.7778
手写逐个放行版:原顺序 0.8889,把数组倒过来再算 0.6667——同一组数据两个答案,因为同分的正负例谁先放行是任意的
并列一起放行版 0.7778,倒序 0.7778;sklearn roc_auc_score 0.7778
连续概率         不同分数  2400 个:手写逐个放行 0.9726 / 倒序 0.9726,并列版 0.9726,sklearn 0.9726
四舍五入到 1 位小数  不同分数    11 个:手写逐个放行 0.9688 / 倒序 0.9704,并列版 0.9699,sklearn 0.9699
四舍五入到 0.5    不同分数     3 个:手写逐个放行 0.9523 / 倒序 0.9519,并列版 0.9515,sklearn 0.9515
分数分档越粗并列越多,差异越大;LLM judge 给 1–5 分、分类器输出 0/1 时全是并列,必须用并列版(或等价的 Mann–Whitney U)

\(y = [1, 1, 0, 1, 0, 0]\)、\(p = [0.9, 0.8, 0.8, 0.5, 0.5, 0.2]\):3 正 × 3 负 = 9 对。正例 0.9 高于全部 3 个负例(3 对),正例 0.8 高于 0.5、0.2 两个负例、与负例 0.8 同分(2 + 0.5 对),正例 0.5 高于 0.2、与负例 0.5 同分(1 + 0.5 对):\((6 + 0.5 \times 2) / 9 = 0.7778\)。

逐个放行的手写版得 0.8889——它把同分的正例排在了负例前面,等于把那 2 个 0.5 都算成了 1;把数组倒过来再喂一次就变成 0.6667,同一组数据两个答案。修法是并列一起放行:按不同的分数值分组,每组整体算一个 ROC 点,曲线在并列处走斜线、面积恰好把同分对算成一半:

def roc_auc_ties(y, p):
    order = np.argsort(-p, kind="stable")
    y, p = y[order], p[order]
    last = np.r_[np.where(np.diff(p) != 0)[0], len(p) - 1]        # 每个不同分数的最后一个位置
    tpr = np.r_[0, np.cumsum(y)[last] / y.sum()]                   # 只在这些位置取 ROC 点
    fpr = np.r_[0, np.cumsum(1 - y)[last] / (1 - y).sum()]
    return float(np.trapezoid(tpr, fpr))

只在每个分数值的最后一个样本处取点,同分的几个样本就一起进入 ROC 点,中间用梯形补面积——这正是 sklearn.metrics.roc_auc_score 的做法(它的 roc_curve 默认 drop_intermediate=True 会再去掉共线的中间点,不改变面积),三者在 6 个样本和 2400 个连续分数上都一致。分数分档越粗差异越大:四舍五入到 0.5 后只剩 3 个不同分数,逐个放行版两种顺序差 0.0004,并列版唯一。报 AUC 时顺带报分数有几个不同取值——取值少的 AUC 本来就是粗粒度的排序质量。

四、类别不平衡

1. 准确率的陷阱

正例只占 3%:

类别不平衡时准确率的陷阱
分类器 准确率 召回率 精确率 说明
全判负 0.956 0 — 测试集 2400 条,正例 105 条,什么都不做就 95.6%
逻辑回归,阈值 0.5 0.959 0.105 — 漏掉 94 / 105 个正例;AUC 0.784
逻辑回归,阈值 0.1 0.911 0.533 0.253 准确率反而低了,但抓到了一半正例

一个什么都不做、全判负的”分类器”准确率 95.6%。逻辑回归 95.9%——看起来更好,但它只抓到了 105 个正例里的 11 个。类别不平衡时准确率几乎没有信息:99% 的文本是”正常”,全判正常准确率就是 99%。

2. 该看什么、该做什么

看精确率 / 召回率 / AUC;把阈值从 0.5 挪开(上面挪到 0.1,召回从 10% 到 53%);训练时给少数类加权或重采样;报告时按类别分开报。LLM 上的对应:安全分类器(有害内容是极少数)、污染检测(泄漏的题是极少数)、过滤器对少数语体的误杀(第六篇)——全是不平衡问题,准确率在这些地方都不能看。

五、校准

1. 说 80% 的时候是不是十次八次对

一个分类器说”90% 是正例”,实际上是不是十次里有九次对?是,就叫校准良好(calibrated)。度量:可靠性图——把预测概率分成若干档,每档画预测概率的均值 vs 实际正例率,理想是对角线;期望校准误差(ECE)——各档偏差按样本数加权平均:

def ece(y, p, bins=10):
    edges = np.linspace(0, 1, bins + 1)
    e = 0.0
    for lo, hi in zip(edges[:-1], edges[1:]):
        m = (p >= lo) & (p < hi)                                  # ① 落在这一档的样本
        if m.any():
            e += m.mean() * abs(y[m].mean() - p[m].mean())        # ② |实际正例率 − 平均预测概率|,按样本占比加权
    return e

三个模型的可靠性图。横轴是预测概率(分成 10 档取均值),纵轴是该档样本的实际正例率,虚线是完美校准;三条线都贴着对角线,ECE 在 1–3%

三个模型的 AUC 与 ECE
模型 AUC ECE 预测 0.7–0.8 那一档的实际正例率
逻辑回归 0.947 0.026 0.80
梯度提升 0.978 0.014 0.83
SVM(概率) 0.978 0.016 0.79

三个模型在这份数据上校准都不错。一般规律:逻辑回归天然校准好(它的 loss 就是对数似然——最大似然估计出来的概率就是频率);树模型与 SVM 的分数排序对但概率不准,要校准。

2. 一个过度自信的模型,与两种校准

把逻辑回归的 logit 乘 3 倍——排序完全不变(AUC 不变),概率被推向 0 和 1(模拟 RLHF 之后”学会自信”的模型)。再用两种方法校准:Platt scaling(在分数上再套一个逻辑回归)与 isotonic regression(拟合一个单调的分段常数映射),都只用一半数据拟合、另一半评估:

platt = LogisticRegression().fit(logit[:half, None], yte[:half])             # Platt:分数 → 概率,一个 1 维逻辑回归
p_platt = platt.predict_proba(logit[half:, None])[:, 1]
iso = IsotonicRegression(out_of_bounds="clip").fit(p_over[:half], yte[:half]) # isotonic:单调分段常数映射
p_iso = iso.predict(p_over[half:])

可靠性图:过度自信的模型(红)在低概率档高于对角线、高概率档低于对角线;Platt(蓝)与 isotonic(绿)校准后两条线都贴回对角线

过度自信模型与两种校准后的 ECE
模型 AUC ECE
过度自信(logit × 3) 0.947 0.069
Platt 校准后 0.947 0.025
isotonic 校准后 0.946 0.019

AUC 一点没变,ECE 从 0.069 降到 0.019——AUC 只看排序,校准要单独查、单独修。

3. LLM 上的两处

  1. 模型对自己答案的置信度:预训练后的模型校准通常不错(说 70% 的题大约 70% 对),RLHF 之后变差——模型学会了”自信”,对错都说 90%,正是上面 logit × 3 的形态。这是后训练的一个已知副作用。
  2. 奖励模型:分差 \(r_w - r_l\) 过 sigmoid 后应该等于人类偏好 \(y_w\) 的比例(第三篇:它就是逻辑回归)。不等就是校准问题,会影响 RL 里奖励的尺度——\(\beta\) 的含义依赖 \(r\) 的尺度。

4. 校准映射成立的条件

Platt / isotonic 学的是一个映射 \(g: \text{分数} \mapsto P(y = 1 \mid \text{分数})\),在拟合它的那份数据上成立。它不会自动迁移:把一份正例率 30% 的数据抽成正例率 10%(每个分数下的分数分布不变,只是先验变了),验证集上拟合的 Platt 映射在新分布上 ECE 从 0.016 变成 0.065,而 AUC 0.963 不受影响(第九章的完整流程里实测,见那里的输出)。原因是 \(P(y \mid \text{分数}) \propto P(\text{分数} \mid y)\,P(y)\)——先验 \(P(y)\) 变了,同一个分数对应的后验就变了,排序却不变。校准映射成立的条件是:拟合校准的数据与使用它的数据来自同一分布(至少先验与条件分布 \(P(\text{分数} \mid y)\) 都稳定);上线后正例率漂移、换了流量来源、模型重训但校准器没重拟合,都会让”概率可信”失效。校准要用一份独立于训练的数据拟合(否则映射会学到训练集的过拟合分数),并随分布变化重拟合。

六、奖励模型与 judge

1. 奖励模型的评估是准确率

在留出的偏好对上,\(r_w > r_l\) 的比例就是准确率。典型值 65%–80%——看起来不高,因为标签有噪声。但要按第三篇第八章修正后的说法:上限是标签与真值的一致率(不可观测),不是标注员之间的一致率——两个独立 80% 正确的标注员互相只有 68% 一致,学到真值的 RM 对他们的标签仍能 80%。人际一致率是噪声线索,不是天花板;判断过拟合看训练 / 验证 gap 与按难度分桶。RewardBench 一类基准按类别(对话、安全、推理)分开报准确率。

2. judge 的评估是一致性:Cohen’s κ

LLM-as-judge 的评估是一致性:judge 的判断与人类标注一致的比例。但一致率有个问题——两个随机打分者在二选一上也有 50% 一致。Cohen’s κ 把这个随机基线扣掉:

\[\kappa = \frac{p_o - p_e}{1 - p_e}\]
  • \(p_o\):观察到的一致率;
  • \(p_e\):随机一致的期望——两人各自选每个类别的比例相乘再相加;
  • \(\kappa = 1\) 完全一致,0 与随机无异。
def cohen_kappa(a, b):
    po = (a == b).mean()                                                            # ① 观察到的一致率
    pe = sum((a == c).mean() * (b == c).mean() for c in np.unique(np.r_[a, b]))     # ② 随机一致的期望
    return (po - pe) / (1 - pe)                                                     # ③ 扣掉随机一致再归一化

手算第二种情形:人类 90% 的题选 A、10% 选 B;judge 100% 选 A。随机一致的期望 \(p_e = 0.9 \times 1.0 + 0.1 \times 0 = 0.9\)(两人”都选 A”的概率加”都选 B”的概率);观察到的一致率 \(p_o = 0.9\)(judge 选 A 的题里人也选 A 的占 90%);\(\kappa = (0.9 - 0.9) / (1 - 0.9) = 0\)——一致率 90%,\(\kappa\) 为零:这个 judge 什么都没看。

二选一、人类偏好各半:judge 与人一致率 0.818,κ = 0.636(随机猜也有 50% 一致,κ 把它扣掉)
若 90% 的题人类都选 A,一个永远选 A 的 judge:一致率 0.917,κ = 0.000——一致率高、κ 为零

第二行是关键:类别不平衡时一致率会骗人(第四章的准确率陷阱换了个名字)——一个什么都不看、永远选 A 的 judge,一致率 92%,κ 恰好 0。”一致率 80%”够不够,要跟人与人之间的一致率比,并且看 κ。

3. 三种系统偏差,与怎么量位置偏差

要警惕的偏差都是经典评估里有名字的系统误差(第一篇:偏差,集成消不掉):

  1. 位置偏差:先出现的回答得分高;
  2. 长度偏差:长回答得分高——控制长度、或在 prompt 里明确、或报”长度控制后的胜率”;
  3. 自我偏好:judge 偏好与自己同源的模型——用不同家族的 judge 交叉。

位置偏差可以直接量出来:同一批题,把 A、B 的顺序对换再评一次。模拟一个有 20% 概率无脑选”先出现的那个”的 judge:

位置偏差:A 排前面时 judge 选 A 的比例 0.604,B 排前面时 0.392——同一批题,只换顺序,差 +0.212
两次判断不一致的题占 43.4%;对换顺序各评一次、只信两次一致的(或取平均),位置偏差就消掉了:一致题上与人的一致率 0.906

人类偏好各半,judge 却在 A 排前时选 A 60%、B 排前时 39%——21 个点的差就是位置偏差的大小。修法是对换顺序各评一次:两次一致才算数(一致题上与人的一致率从 0.82 升到 0.91),或两次取平均。多个 judge 取平均能降低随机误差(方差),但如果它们都偏好长回答,平均之后还是偏好长回答——系统偏差只能靠设计消掉。

对换顺序只抵消位置这一种偏差。 再模拟一个既偏爱靠前、又偏爱更长答案的 judge,对照只有位置偏好的 judge:

只有位置偏好:单次(Y 在前)选 Y 的比例 0.76、与人类一致 0.677;对换两次一致的 43%,一致部分与人类一致 0.914,其中选了更长答案的比例 0.50(单次 0.49)
位置 + 长度偏好:单次(Y 在前)选 Y 的比例 0.74、与人类一致 0.679;对换两次一致的 48%,一致部分与人类一致 0.869,其中选了更长答案的比例 0.81(单次 0.65)
对换后「Y 在前」的优势没了(选 Y 比例回到 0.5 附近),但更长答案仍被多选——长度、自我偏好、评分标准措辞这些与位置无关的偏差,对换一次也测不出来;要分别设计对照(等长答案、匿名模型、改写 rubric)才量得到

对换后”Y 在前”的优势消失(选 Y 的比例回到 0.5 附近),一致部分与人类的一致率也回升;但在只有长度偏好的那一行,两次一致的题里 81% 选了更长的答案,比单次的 65% 还高——对换把位置噪声过滤掉之后,留下的恰恰是长度偏好最坚定的判断。长度、自我偏好、评分标准的措辞、judge 家族效应,这些与位置无关的偏差,对换一次也测不出来;要分别设计对照(等长答案、匿名模型名、改写 rubric 后重评、换家族的 judge 交叉)才量得到,量到了也只能说”修正了这几种”,不能说”消除了偏差”。

七、一个分数稳不稳

1. 交叉验证

数据少时一次划分的分数抖得厉害:

单次划分与交叉验证的分数抖动
做法 结果
5 次不同的单次 70/30 划分 0.722、0.722、0.756、0.722、0.678——同一个模型,分数抖 0.078
5 折交叉验证 均值 0.763 ± 0.022——每条数据都当过一次验证,估计更稳,代价是训 5 次

交叉验证:把数据分成 \(k\) 折,轮流用一折当验证、其余训练,\(k\) 个分数取平均。LLM 上大模型不做交叉验证(训不起),但小规模实验、分类器训练、奖励模型的评估照常用——尤其是偏好数据只有几千对的时候。

2. 各折不是独立重复实验

“0.763 ± 0.022”里的 ± 是 5 个折分数的标准差。很多人再除以 \(\sqrt{5}\) 当作均值的标准误、拿去做 \(t\) 检验——这把 5 折当成了 5 次独立实验。它们不是:\(k\) 折里任意两折的训练集共享 \((k - 2)/(k - 1) = 75\%\) 的样本,训出的模型高度相关;验证集虽然互斥,但都来自同一份 300 条数据。量一下”换划分”与”换数据”各抖多少:

5 折里任意两折的训练集共享 75% 的样本((k−2)/(k−1)),验证集互斥但模型高度相关
一次 5 折:各折 [0.717, 0.767, 0.8, 0.783, 0.75],均值 0.763,若当 5 次独立实验:标准误 0.014
换 200 种划分重跑 5 折:均值的标准差 0.013,范围 [0.713, 0.780]——同一份 300 条数据,仅换划分就抖这么多
再从同一分布抽 200 份新的 300 条各做一次 5 折:均值的标准差 0.028——这才是「换一份数据会差多少」,是折间标准差/√5(0.014)的 2.0 倍
折间标准差只反映同一份数据内部的划分噪声;拿它做 t 检验比较两个模型,显著性会虚高。要么报区间宽度时说明口径,要么用重复 CV / 新数据

左图:200 次只换划分重跑 5 折得到的均值直方图(灰色,标准差 0.013)与 200 份新抽数据各做一次 5 折的均值直方图(蓝色,标准差 0.028),蓝色明显更宽;两条竖虚线标出一次 5 折的均值 0.763 及按折间标准差/√5 得到的 ±0.014 区间,比蓝色分布窄得多。右图:5 折中任意两折训练集重叠比例的示意,k = 5 时为 75%

折间标准差 / \(\sqrt{5}\) 给 0.014,与”同一份数据换划分”的抖动 0.013 一个量级——它量的只是划分噪声;而”换一份同分布的数据”的抖动是它的两倍。所以 5 折的 ± 回答的是”这份数据上换种切法会差多少”,不是”换一批数据会差多少”;拿它做两个模型的 \(t\) 检验,显著性会虚高(第九章案例里 5 折配对 \(p\) 值要这样读)。想要后者:重复 CV(多种划分取平均,再报划分间抖动)、嵌套 CV,或者承认区间口径、把 \(k\) 折当作一次更稳的点估计而不是 \(k\) 个样本。

3. 标准误:benchmark 分数的置信区间

抛一枚公平的硬币 10 次,正面可能是 3 次、也可能是 7 次;抛 10000 次,正面比例几乎一定在 49%–51% 之间。同一件事发生在 benchmark 上:一个 benchmark 有 \(n\) 道题、正确率 \(p\),每道题对错是一次伯努利试验(第三篇),如果换一批”同样难度”的 \(n\) 道题重测,正确率会抖——抖动的标准差叫标准误(standard error),公式是 \(\sqrt{p(1-p)/n}\)(第九篇 MinHash 的估计误差是同一个公式)。真实值有 95% 的把握落在”测得的 \(p\) ± 1.96 个标准误”之内——1.96 来自钟形曲线:正态分布的 95% 面积在均值两侧 1.96 个标准差以内。这个范围叫 95% 置信区间:

常见 benchmark 的标准误与 95% 区间
benchmark 题数 \(n\) 正确率 \(p\) 标准误 95% 区间
MMLU 14,042 0.70 0.39 个点 ±0.8 个点
GSM8K 1,319 0.85 0.98 个点 ±1.9 个点
一个 100 题的私有集 100 0.70 4.58 个点 ±9.0 个点

MMLU 上差 0.5 个点在噪声里;100 题的私有集上差 8 个点也在噪声里。先看区间宽度再谈领先。

4. bootstrap

公式假设题目独立同分布;更通用的办法是 bootstrap——把 \(n\) 道题的结果有放回地重抽 \(n\) 个、算一次正确率,重复几千次,看它抖多大:

boots = np.array([correct[r.integers(0, n, n)].mean() for _ in range(5000)])   # 有放回重抽 5000 次
lo, hi = np.percentile(boots, [2.5, 97.5])                                      # 2.5%–97.5% 分位 = 95% 区间

直方图:500 题有放回重抽 5000 次的正确率分布,钟形,中心 0.614,红色虚线标出 2.5% 与 97.5% 分位 0.570 与 0.656

bootstrap:500 题正确率 0.614,重抽 5000 次的 2.5%–97.5% 分位 [0.570, 0.656];公式 ±0.043——两者一致

bootstrap 的好处是对任何统计量都能用(不只是正确率——胜率、Elo、平均分都行),不需要推公式。

5. 相关样本:按来源重抽

公式和上面的 bootstrap 都假设 \(n\) 道题独立。评测集常常不是:同一个模板改写出 10 道题、同一篇文档切出的多段、同一用户的多轮对话——同来源的题一起对、一起错。模拟 50 个模板 × 10 道改写题 = 500 题,每个模板一个难度:

总体正确率 0.593(对模板难度分布取期望);最后一次抽样的组内相关 ICC ≈ 0.36:设计效应 1 + (10 − 1) × ICC ≈ 4.3,500 题只相当于约 117 道独立题
公式标准误 √(p(1−p)/n) 平均 0.022,按模板整簇重抽的 bootstrap 标准误平均 0.046
500 次模拟里 95% 区间盖住真值的比例:公式 61%,簇 bootstrap 94%——公式区间名义 95%、实际 61%;簇 bootstrap 把名义值找回来
同一来源的样本(同模板、同篇文档的多段、同一用户的多轮)要按来源重抽或用聚类标准误;独立性是公式的前提,不是默认成立的事实

两组重叠直方图:灰色是 500 次模拟里公式标准误 √(p(1−p)/n) 的分布,集中在 0.022 附近;蓝色是按模板整簇重抽的 bootstrap 标准误分布,集中在 0.046 附近、约为灰色的两倍。图例写出两者 95% 区间盖住真值的比例:公式 61%、簇 bootstrap 94%

设计效应 \(1 + (m - 1)\,\rho\)(\(m\) 为簇大小、\(\rho\) 为组内相关)告诉你 500 题值多少道独立题:\(\rho \approx 0.36\) 时约 4.3,500 题只相当于 117 道。公式标准误 0.022 对应的”95% 区间”实际只盖住真值 61% 的次数;把题按模板整簇重抽(簇 bootstrap)得到 0.046,覆盖率回到 94%。规则是按独立的单位重抽:题有模板就按模板、段有文档就按文档、对话有用户就按用户。独立性是公式的前提,不是默认成立的事实;拿到一个评测集先问”这些题是怎么造出来的”。

八、两个模型怎么比

1. 独立比较 vs 配对检验

500 道题上模型 A 65.2%、模型 B 61.0%,差 4.2 个点:

独立比较与配对检验的结论差别
比较方式 用到的数 结论
独立比较 差值标准误 0.030,\(z = 1.38\) 不显著(\(\lvert z \rvert > 1.96\) 才显著)
配对比较(McNemar) A 对 B 错 33 题、A 错 B 对 12 题,其余 455 题一致;\(z = 3.13\) 显著
独立比较把两个正确率当成独立的估计,差值的标准误是 \(\sqrt{\text{SE}_A^2 + \text{SE}_B^2} = 3.0\%\)。\(z\) 是”差了几个标准误”:\(4.2 / 3.0 = 1.38\)。如果两个模型其实一样好,差值只是随机抖动,那么它超过 1.96 个标准误的概率不到 5%——这就是”[显著](# “tip: statistically significant:观察到的差异大到「如果其实没有差异,随机抖动几乎不可能抖出这么大」。常用门槛是 5%:随机抖出这么大差异的概率小于 5% 才算显著,对应 z > 1.96”)”的门槛。1.38 不到 1.96——4.2 个点的差异随机抖动就能抖出来,不显著。

配对检验利用了”同一套题”:两个模型答错的题高度重叠,455 题两者一致,差异全在 45 道分歧题上——33 道 A 对 B 错、12 道 A 错 B 对。如果两个模型一样好,分歧题应该一半一半(22.5 : 22.5);33 : 12 偏离一半的程度用 McNemar 检验:

\[z = \frac{n_{01} - n_{10}}{\sqrt{n_{01} + n_{10}}} = \frac{33 - 12}{\sqrt{45}} = 3.13\]

显著。同一套题上比两个模型,永远用配对——它只看分歧题,噪声小得多。但结论的量级不变:分歧题只有 45 道,再灵敏也分辨不出 1 个点的差异。

2. 多重比较

在 20 个 benchmark 上比两个模型,即使它们完全一样:

20 个 benchmark 上比较两个相同模型的模拟结果
2000 次模拟:两个完全相同的模型,20 个 benchmark,5% 显著性水平 结果
“显著”不同的 benchmark 个数 平均 0.92 个(期望 \(20 \times 5\% = 1\));至少一个显著的概率 61%
“20 个里领先 12 个” 随机情况下期望领先 10 个,标准差 2.2——12 个不算什么

5% 显著性意味着每个 benchmark 有 5% 的概率”碰巧显著”;20 个就期望 1 个、至少一个的概率 61%。所以论文里”在 20 个 benchmark 中的 12 个上领先”,先问:随机情况下期望领先 10 个、标准差 2.2,12 在一个标准差以内——不算什么。修正的方法:Bonferroni(显著性水平除以 benchmark 数)、或看平均分的置信区间而不是数领先个数、或预先指定一个主 benchmark。

九、一条完整流程:定阈值 → 校准 → 留出评估 → 成对比较

前面每一节各讲一个工具,真正落地时它们要串起来,而串起来的第一个问题是每一步用哪份数据。为什么阈值不能在测试集上选?5 原则只有一条:用来做决定的数据,不能再用来评价这个决定。

完整流程里每一步用哪份数据
步骤 用哪份数据 做什么 不能做什么
训练 训练集 拟合模型参数 —
定阈值 验证集 按业务代价扫阈值,取代价最小的 不能看测试集
校准 验证集 拟合 Platt / isotonic 映射 不能用训练集(分数过拟合)、不能用测试集
留出评估 测试集,只用一次 用验证集定好的阈值与映射,报代价 / AUC / ECE 不能据此回头改阈值再报
成对比较 同一份测试集 两个模型的逐样本差:McNemar、配对 bootstrap 不能各报一个数比大小

8000 条合成数据切成训练 4000 / 验证 2000 / 测试 2000,业务代价”漏一个正例 5、误判一个负例 1”,两个模型走一遍:

训练 4000 / 验证 2000 / 测试 2000,正例率 0.31;业务代价:漏一个正例 5、误判一个负例 1
A 梯度提升:验证集最优阈值 0.23(验证代价 0.201/样本)→ 测试代价 0.212;若在测试集上挑阈值得 0.20、代价 0.205——偷看测试集的数字更漂亮但不能报
    留出评估:AUC 0.956,ECE 校准前 0.018 → Platt 后 0.016;阈值 0.5 的代价 0.291(默认阈值不是业务阈值)
B 逻辑回归:验证集最优阈值 0.28(验证代价 0.304/样本)→ 测试代价 0.332;若在测试集上挑阈值得 0.21、代价 0.304——偷看测试集的数字更漂亮但不能报
    留出评估:AUC 0.929,ECE 校准前 0.019 → Platt 后 0.019;阈值 0.5 的代价 0.413(默认阈值不是业务阈值)
校准的前提:把测试集正例率从 0.30 抽到 0.10(分数分布不变、先验变了),验证集上拟合的 Platt 映射在新分布上 ECE 0.065,AUC 0.963 不受影响——校准修的是 P(y | 分数),先验一变映射就得重拟合
成对比较(同一测试集):各用自己的验证集阈值,A 对 B 错 157 条、A 错 B 对 63 条,McNemar z = 6.34(|z| > 1.96 为显著);AUC 差 +0.0266,配对 bootstrap 95% 区间 [+0.0182, +0.0349]
区间不含 0 才能说 A 更好;若含 0,只说明这份测试集分不出两者,不是证明两者相同——区间宽度告诉你能排除多大的差距

逐行读:

  1. 定阈值:A 在验证集上代价最小的阈值是 0.23,拿到测试集上代价 0.212;若直接在测试集上扫,能找到 0.20、代价 0.205。后者更漂亮,但它是在测试集上选出来的,测试集已经不再是”没见过的数据”,这个数字只能当上界。默认阈值 0.5 的代价 0.291——第三章说的”阈值是业务决定”在这里是 40% 的代价差。
  2. 校准:Platt 在验证集上拟合,测试集上 ECE 0.018 → 0.016(梯度提升本来就校准得不错)。接着是第五章第 4 节那个条件的实测:把测试集正例率从 0.30 抽到 0.10,同一个 Platt 映射的 ECE 变成 0.065,AUC 0.963 不动——校准修的是 \(P(y \mid \text{分数})\),先验一变就得重拟合。
  3. 留出评估:测试集上每个模型报一次代价、AUC、ECE,之后不再回头调参数。
  4. 成对比较:两个模型在同一份测试集上逐样本比。McNemar:A 对 B 错 157、A 错 B 对 63,\(z = 6.34\);AUC 差 +0.0266,配对 bootstrap(每次重抽同一批样本、同时算两个模型的 AUC 再相减)的 95% 区间 [+0.0182, +0.0349],不含 0。

最后一句要单独说:区间含 0 不是”两者一样好”的证据,只说明这份测试集分不出两者。要判断”差不多”,看区间宽度能排除多大的差距——第十章案例里两个模型的 AUC 差区间是 [−0.0073, +0.0046],能说的是”差距大概率不超过 0.007 个 AUC 点”,而不是”一样”。同理,”不显著”的 \(p\) 值只回答”数据够不够推翻无差异”,不回答”有没有差异”;要证明等价得用等价检验(预先定义”多小算一样”,看区间是否落在里面)。

十、案例:银行电话营销——一份 11.7% 正类的数据怎么评

前八章每个概念各有一个小实验。这一章把它们串起来用在一份真实数据上:UCI Bank Marketing(Moro 等 2014),一家葡萄牙银行 2008–2010 年的 45,211 次电话营销记录,目标是客户是否订了定期存款——5,289 人订了,11.7%。模型的用途:下一轮营销,决定给谁打电话。完整脚本 case_10_bank_marketing.py。

1. 先排除一个泄漏

特征里有一列 duration——通话时长。带上它,梯度提升的 AUC 0.934;但通话时长要打完电话才知道,而模型的用途是决定打不打。UCI 的数据说明自己也写了:要做真实预测必须去掉它。这是第一篇第五章的泄漏、第四篇泰坦尼克 boat 列的同款——一个特征好得不像真的,先查它是什么时候产生的。下面全部不用它。

2. 准确率没用

三个模型(第三、六篇的逻辑回归、随机森林、梯度提升)在 11,303 人的测试集上:

银行营销——三个模型的四个指标
模型 准确率(阈值 0.5) AUC AP ECE
全判”不订” 0.883 0.500 0.117 —
逻辑回归 0.892 0.769 0.415 0.014
随机森林 0.894 0.795 0.440 0.012
梯度提升 0.893 0.796 0.448 0.009

三个模型的准确率都和”全判不订”的 88.3% 差不到一个点——第四章:不平衡数据上准确率不说话。AUC 0.77–0.80、AP 0.42–0.45 才分得出高下。AP(PR 曲线下面积)的基线是正类比例 0.117 而不是 0.5,它更能体现”从 12% 里挑人”有多难:

左:三个模型的 ROC 曲线,AUC 0.77–0.80;右:PR 曲线,基线是 0.117 的水平线,AP 0.42–0.45

3. 阈值不是 0.5:按成本定

第三章说阈值是业务决定。这里给它算一笔账:假设打一次电话成本 5 欧、成交一单收益 100 欧,用梯度提升的概率 \(p\),阈值从 0.02 扫到 0.9,每个阈值算”利润 = 100 × 成交数 − 5 × 打的电话数”:

电话 5 欧、成交 100 欧时,利润随阈值
阈值 打的电话 成交 召回 精确率 利润(欧)
0.05 7,294 1,170 89% 16% 80,530
0.07 5,171 1,073 81% 21% 81,445
0.10 3,223 923 70% 29% 76,185
0.30 1,120 560 42% 50% 50,400
0.50 425 270 20% 64% 24,875
全打 11,303 1,322 100% 11.7% 75,685

左:两种成本假设下利润随阈值的曲线——5 欧时最优阈值 0.07、全打也不亏;25 欧时最优 0.19、全打亏 15 万;虚线是默认的 0.5。右:三个模型的可靠性图,都贴着对角线

两点:

  • 最优阈值 0.07,不是 0.5。正类只有 12%,模型给大多数人的概率都很低,0.5 只挑出 425 人、利润 2.5 万;0.07 打 47% 的人拿到 81% 的成交,利润 8.1 万。用默认阈值等于扔掉 2/3 的利润。
  • 模型值多少钱也由成本决定。电话便宜(5 欧)时全打也不亏(7.6 万),模型只多赚 8%;把成本换成 25 欧,全打亏 15 万,模型在阈值 0.19 处赚 3.2 万——电话越贵,”挑人”越值钱。阈值和模型的价值都是业务参数,不是模型参数。

4. 概率准不准

上一步的算术建在”\(p\) 就是成交概率”上——\(p = 0.07\) 的人里真有 7% 成交,5 欧 / 100 欧的账才算得对。第五章的可靠性图(右上图):三个模型的 ECE 都在 0.01 上下,概率最高的那一档平均预测 0.45–0.49、实际成交 0.49–0.50,贴着对角线。逻辑回归天生校准好;树的集成不一定(第五章的过度自信例子),这里随机森林每叶至少 5 个样本、梯度提升用对数损失训,所以也还好——但这是要检查的,不是默认的。\(p\) 不准,算出来的阈值就是错的。

5. 两个模型差 0.001 AUC,是真的吗

测试集上梯度提升 0.796、随机森林 0.795——梯度提升”赢了”。第八章说同一套数据要用配对检验。5 折交叉验证,每一折两个模型都训一遍、在同一份验证集上算 AUC:

随机森林 vs 梯度提升的 5 折配对比较
折 1 2 3 4 5 均值 ± 标准差
随机森林 0.7889 0.7897 0.8052 0.7915 0.7969 0.7945 ± 0.0061
梯度提升 0.7886 0.7967 0.8046 0.7973 0.8033 0.7981 ± 0.0057
同一折上的差 −0.0003 +0.0069 −0.0006 +0.0058 +0.0064 配对 \(t\) 检验 \(p = 0.096\)

差值有正有负、\(p = 0.1\)——这个差别不显著,换一份测试集可能反过来。测试集上那个 0.001 的领先是噪声。注意两点:不显著不是”一样好”的证明,只是这份数据分不出来(下一节用配对 bootstrap 给出能排除多大差距);而且 5 折的 \(p\) 把共享 75% 训练样本的 5 折当成了独立实验,本身偏乐观(第七章第 2 节)。要分出高下:更多折、更多数据;或者接受”分不出”,按别的标准选(随机森林几乎不用调参、梯度提升快 10 倍)。“谁更好”看的是同一份数据上成对差值的分布,不是各报一个数比大小。第八章那句话在真实数据上的样子就是这张表。

6. 在验证集上重做:阈值和校准不碰测试集

第 3 节的最优阈值 0.07 / 0.19 是在测试集上扫出来的——这正是第九章说不能做的事。按第九章的流程重切:训练 22,605 / 验证 11,303 / 测试 11,303,阈值与 Platt 都只看验证集,测试集只评一次,最后在同一份测试集上配对 bootstrap:

训练 22,605 / 验证 11,303 / 测试 11,303;上面第 2 节的最优阈值是在测试集上扫出来的,这里改成只看验证集
随机森林:验证集最优阈值 0.23 → 测试集利润 28,975 欧;在测试集上直接挑的阈值 0.22 利润 29,450 欧(偷看的数字,只能当上界)
    留出评估:AUC 0.7916,ECE 校准前 0.016 → 验证集 Platt 后 0.006(本来就校准好,映射近似恒等;校准前提是验证集与测试集同分布)
梯度提升:验证集最优阈值 0.21 → 测试集利润 30,000 欧;在测试集上直接挑的阈值 0.21 利润 30,025 欧(偷看的数字,只能当上界)
    留出评估:AUC 0.7902,ECE 校准前 0.012 → 验证集 Platt 后 0.008(本来就校准好,映射近似恒等;校准前提是验证集与测试集同分布)
成对比较(同一测试集,配对 bootstrap 1000 次):梯度提升 − 随机森林 的 AUC 差 -0.0014,95% 区间 [-0.0073, +0.0046]
区间含 0:这份测试集分不出两者;注意「分不出」不是「一样好」——区间宽 0.0119,只能说差距大概率不超过 0.007 个 AUC 点。第 4 节的 5 折配对 t 检验 p = 0.030 要同样读:任意两折的训练集共享 75% 的样本,不是 5 次独立实验,折间标准差算出的显著性偏乐观

三点:(一)验证集上定的阈值拿到测试集,利润比”在测试集上直接挑”少 475 欧(随机森林)/ 25 欧(梯度提升)——后者更漂亮的数字是偷看来的,只能当上界,差距小是因为利润曲线在最优点附近很平;(二)两个模型 ECE 本来就在 0.01–0.02,Platt 映射近似恒等,校准没什么可修的,但它成立的前提仍是验证集与测试集同分布——第 7 节会说这份数据按时间切就不是;(三)AUC 差 −0.0014、配对 bootstrap 区间 [−0.0073, +0.0046] 含 0:这份测试集分不出两者,但这不是两者一样好的证明,能说的只是差距大概率不超过 0.007。第 5 节的 5 折配对 \(p\) 值要同样读——任意两折共享 75% 的训练样本,不是 5 次独立实验,那个 \(p\) 本身就偏乐观(第七章第 2 节)。

7. 落地还差什么

(一)时间:数据是 2008–2010 年的、按时间排列,而我们随机切了训练 / 测试——真实上线应该按时间切(第一篇第三章),用 2008–2009 训、2010 测,AUC 大概率会比 0.80 低,因为 2010 年的客户行为已经变了(金融危机期间);(二)成本模型:5 欧 / 100 欧是拍脑袋的,真实的收益是存款金额 × 利差 × 存期,每个客户不同——阈值应该按每人的期望利润定,而不是一个全局阈值;(三)反馈回路:模型只给高分的人打电话,之后收集到的数据就只有高分的人有标签,下一轮训练的数据是有偏的——这是第一篇 reward hacking 的远亲,解法是留一小部分随机打的电话当无偏样本。评估不是训练完做一次的事,是上线之后每一轮都要做的事。

十一、本文小结

  • 混淆矩阵四个格子拆出精确率(判为正的有多准)、召回率(真正的抓了多少)、F1;12 行手写与 scikit-learn 全同。
  • 阈值是一个旋钮:高则精确率升、召回率降;同一模型不同阈值就是不同的过滤器;比模型本身用 AUC(随机一正一负、正例分数更高的概率,实测 0.9721 vs AUC 0.9726),定阈值用精确率 / 召回率。并列分数算半对(6 样本手算 7/9 = 0.7778);逐个放行的手写版随输入顺序变,并列一起放行才与 sklearn 一致。
  • 类别不平衡时准确率没有信息(正例 3%,全判负 95.6%);看 P / R / AUC,挪阈值,按类别分开报。
  • 校准:可靠性图与 ECE;逻辑回归天然校准好;过度自信的模型 AUC 不变、ECE 0.069,Platt / isotonic 校准后 0.019——AUC 不看概率,校准要单独查;RLHF 后模型置信度变差是同一现象。校准映射只在同分布下成立:先验 0.30 → 0.10 后同一映射 ECE 0.016 → 0.065、AUC 不变,要重拟合。
  • judge:一致率要扣掉随机基线——Cohen’s κ;永远选 A 的 judge 一致率 92%、κ = 0。位置偏差可以量(对换顺序差 21 个点)、可以抵消(两次一致才算数);但对换只抵消位置这一种——偏爱长答案的 judge 对换后一致题里仍 81% 选长的,长度 / 自我偏好 / rubric 措辞要各自设计对照;系统偏差平均不掉。
  • 一个分数稳不稳:交叉验证(5 折 0.763 ± 0.022 vs 单次抖 0.078),但各折共享 75% 训练样本、不是独立实验——折间标准差/√5 的 0.014 只量划分噪声,换一份数据的抖动是它的两倍;标准误 \(\sqrt{p(1-p)/n}\)——MMLU ±0.8 个点、100 题私有集 ±9 个点;bootstrap 对任何统计量都能用;样本有簇结构时按来源重抽(50 模板 × 10 题:公式区间实际覆盖率 61%,簇 bootstrap 94%)。
  • 两个模型比:同一套题永远用配对(McNemar 只看分歧题,\(z\) 从 1.38 到 3.13);多个 benchmark 先问随机情况下期望几个显著、几个领先(20 个里期望领先 10 ± 2.2,12 不算什么)。
  • 完整流程:训练集训模型 → 验证集定阈值、拟合校准 → 测试集只评一次 → 同一测试集上配对比较。在测试集上挑的阈值只能当上界;区间含 0 / 不显著只说明分不出,不是证明相同——看区间宽度能排除多大差距。

  • 案例:银行营销 45,211 次电话、11.7% 成交——duration 是泄漏;三个模型准确率都 ≈ 全判 0 的 88%,AUC 0.77–0.80 / AP 0.42–0.45 才分高下;按 5 欧 / 100 欧算最优阈值 0.07 不是 0.5(默认阈值扔掉 2/3 利润),电话越贵挑人越值钱;ECE 0.01 概率可信;测试集差 0.001 AUC,5 折配对 \(p = 0.1\)——不显著;按验证 / 测试重做:验证集阈值到测试集少赚 475 / 25 欧,配对 bootstrap 的 AUC 差区间 [−0.0073, +0.0046]——分不出,但不是一样好。

配套代码:本文全部数字与图由 classical-ml/10_evaluation.py(metrics / threshold / imbalance / calibration / kappa / cv / bootstrap / paired / multiple / ties / protocol / cvdep / cluster / judgebias 十四个子实验)与 case_10_bank_marketing.py(第十章案例,首次运行下载 1 MB)产生,CPU 上两分钟内跑完。

十二、自测

  1. 一个安全过滤器精确率 98%、召回率 72%。它每拦 100 条有几条误杀?每 100 条有害内容漏放几条?哪个指标更该关心?

    答案

    2 条误杀、28 条漏放;安全过滤更该关心召回(漏放)。

  2. 两个质量分类器 AUC 分别 0.90 与 0.93,但在阈值 0.5 上前者 F1 更高。该选哪个?

    答案

    AUC 高的那个模型本身更好,F1 的差别是阈值选择的问题——选 0.93 的,再选合适的阈值。

  3. 正例 1% 的数据上分类器准确率 99.2%,说明什么?该看什么?

    答案

    几乎没有信息(全判负就 99%);看召回、精确率、AUC。

  4. 一个模型 AUC 0.95、ECE 0.10。它的问题在哪?怎么修?会不会影响 AUC?

    答案

    排序好但概率不可信(过度自信或过度保守);Platt 或 isotonic 校准;不影响 AUC(单调映射不改变排序)。

  5. 一个 judge 与人类一致率 82%,人与人之间一致率 80%,但人类 85% 的题都选 A。judge 够好了吗?还该看什么?

    答案

    先算 κ——85% 选 A 时随机一致率已很高,82% 的一致率 κ 可能很低;再对换顺序量位置偏差、控制长度、换家族。

  6. 一个 200 题的 benchmark 上模型 A 72%、B 68%。差异显著吗?该怎么比?

    答案

    标准误约 \(\sqrt{0.7 \times 0.3 / 200} = 3.2\) 个点,独立比较 4 个点不显著;用配对(McNemar)看分歧题——它可能显著,也可能不。

  7. 论文在 15 个 benchmark 上报了结果、其中 3 个”显著领先”。随机情况下期望几个显著?这个结果可信吗?

    答案

    期望 0.75 个;3 个略多于随机但不强——看效应大小与平均分的区间,或做 Bonferroni(每个用 0.33% 的水平)。

  8. 一个 1–5 分的 judge 给 4 道题打分 [5, 4, 4, 2],人工标签 [1, 1, 0, 0]。AUC 是多少?逐个放行的手写版可能给出哪两个答案?

    答案

    2 正 × 2 负 = 4 对:正例 5 高于两个负例(2 对),正例 4 高于负例 2(1 对)、与负例 4 同分(0.5 对),AUC = 3.5 / 4 = 0.875。逐个放行版把同分的正例排前面得 1.0,排后面得 0.75。

下一篇

下一篇是系列总结与通关自测:把十篇的核心问题、一句话结论、必记数字放在一起,再用一套自测检验是否真的能用这些工具想 LLM 的问题。

  1. 只说对了 95% 的样本,没说错在哪一边;有害内容占 5% 时全判「无害」也是 95%。要看混淆矩阵拆出的精确率(拦下来的里多少真有害)与召回率(有害的漏了多少),以及它们随阈值的权衡(要求召回 95% 时精确率掉到 90%);类别不平衡时看 AUC 或 F1。详见第二至四章。 ↩

  2. 先扣掉随机一致——算 Cohen’s κ(永远选 A 的 judge 在 90% 选 A 的题上一致率 92%、κ = 0);再与人和人的一致率比——人之间也只有 70–80%,但那不是 judge 准确率的上限(两个各 80% 正确的人互相一致 68%),80% 高于它不说明过拟合;再量系统偏差——对换顺序各评一次看位置偏差(模拟里差 21 个点),控制长度,换家族。系统偏差平均不掉。详见第六章。 ↩

  3. 不一定算。两个一样好的模型各有 50% 概率领先,20 个里领先 12 个在随机情况的一个标准差以内(期望 10 ± 2.2);每个 benchmark 各做一次检验还要多重比较校正(5% 水平下期望 1 个「显著」、至少一个的概率 61%);而且很多 benchmark 差几个点在置信区间内(MMLU ±0.8、100 题集 ±9)。同一套题上比要用配对检验。详见第七章、第八章。 ↩

  4. 按概率定义:随机一正一负,正例分数更高算 1、同分算 0.5,再取平均(Mann–Whitney U)。6 样本 \(y = [1, 1, 0, 1, 0, 0]\)、\(p = [0.9, 0.8, 0.8, 0.5, 0.5, 0.2]\) 有 9 对,6 对正例更高、2 对同分,AUC \(= (6 + 1) / 9 = 0.7778\)。实现上要”并列一起放行”:只在每个不同分数值的最后一个样本处取 ROC 点,与 sklearn.metrics.roc_auc_score 一致;逐个放行的手写版会把同分对全算成 1 或 0,同一组数据得 0.8889 或 0.6667。详见第三章。 ↩

  5. 阈值是用数据做出的一个决定;在测试集上扫阈值等于让测试集参与了模型选择,之后再用它报的代价就不再是”没见过的数据上的表现”,而是对这一份数据的最优拟合——合成数据上在测试集挑的阈值代价 0.205 vs 验证集阈值 0.212,银行数据上多赚 475 欧,这些更漂亮的数字只能当上界。正确做法是验证集定阈值、拟合校准,测试集只评一次。详见第九章。 ↩

这篇对你有用?

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/evaluation-from-confusion-matrix-to-judge-agreement.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×