前九篇训了很多模型,每次都报一个”准确率”。这一篇专门讲怎么评估——准确率什么时候会骗人,一个分类器的”好”怎么拆成几个能各自回答问题的数,一个概率输出可不可信,两个模型差 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 配对检验;多重比较
九 案例:银行电话营销 45,211 次电话、11.7% 成交:一个泄漏特征、准确率为什么没用、按成本定阈值(0.07 而不是 0.5)、校准、两个模型差 0.001 AUC 是不是真的
十 本文小结  
十一 自测 七道题

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;决定用哪个阈值用精确率 / 召回率。两件事不要混。

四、类别不平衡

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\) 的尺度。

六、奖励模型与 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 取平均能降低随机误差(方差),但如果它们都偏好长回答,平均之后还是偏好长回答——系统偏差只能靠设计消掉。

七、一个分数稳不稳

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. 标准误: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 个点也在噪声里。先看区间宽度再谈领先。

3. 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、平均分都行),不需要推公式。

八、两个模型怎么比

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。

九、案例:银行电话营销——一份 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 的领先是噪声。要分出高下:更多折、更多数据;或者承认两者一样好,按别的标准选(随机森林几乎不用调参、梯度提升快 10 倍)。“谁更好”看的是同一份数据上成对差值的分布,不是各报一个数比大小。第八章那句话在真实数据上的样子就是这张表。

6. 落地还差什么

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

十、本文小结

  • 混淆矩阵四个格子拆出精确率(判为正的有多准)、召回率(真正的抓了多少)、F1;12 行手写与 scikit-learn 全同。
  • 阈值是一个旋钮:高则精确率升、召回率降;同一模型不同阈值就是不同的过滤器;比模型本身用 AUC(随机一正一负、正例分数更高的概率,实测 0.9721 vs AUC 0.9726),定阈值用精确率 / 召回率。
  • 类别不平衡时准确率没有信息(正例 3%,全判负 95.6%);看 P / R / AUC,挪阈值,按类别分开报。
  • 校准:可靠性图与 ECE;逻辑回归天然校准好;过度自信的模型 AUC 不变、ECE 0.069,Platt / isotonic 校准后 0.019——AUC 不看概率,校准要单独查;RLHF 后模型置信度变差是同一现象。
  • judge:一致率要扣掉随机基线——Cohen’s κ;永远选 A 的 judge 一致率 92%、κ = 0。位置偏差可以量(对换顺序差 21 个点)、可以消(两次一致才算数);系统偏差平均不掉。
  • 一个分数稳不稳:交叉验证(5 折 0.763 ± 0.022 vs 单次抖 0.078);标准误 \(\sqrt{p(1-p)/n}\)——MMLU ±0.8 个点、100 题私有集 ±9 个点;bootstrap 对任何统计量都能用。
  • 两个模型比:同一套题永远用配对(McNemar 只看分歧题,\(z\) 从 1.38 到 3.13);多个 benchmark 先问随机情况下期望几个显著、几个领先(20 个里期望领先 10 ± 2.2,12 不算什么)。

  • 案例:银行营销 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\)——不显著。

配套代码:本文全部数字与图由 classical-ml/10_evaluation.py(metrics / threshold / imbalance / calibration / kappa / cv / bootstrap / paired / multiple 九个子实验)与 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% 的水平)。

下一篇

下一篇是系列总结与通关自测:把十篇的核心问题、一句话结论、必记数字放在一起,再用一套自测检验是否真的能用这些工具想 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)。同一套题上比要用配对检验。详见第七章、第八章。 ↩

这篇对你有用?

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


COMMENTS

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

×