系列 《LLM 时代的经典机器学习:只讲它在哪里重现》 第 10 / 11 篇
上一篇:去重——MinHash 与 LSH 的概率下一篇:系列总结与通关自测
前九篇训了很多模型,每次都报一个”准确率”。这一篇专门讲怎么评估——准确率什么时候会骗人,一个分类器的”好”怎么拆成几个能各自回答问题的数,一个概率输出可不可信,两个模型差 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. 本文的章节安排
| 章 | 主题 | 内容 |
|---|---|---|
| 二 | 混淆矩阵与四个指标 |
|
| 三 | 阈值与 AUC | 阈值扫描曲线;ROC 怎么画、AUC 的概率含义(手写并验证) |
| 四 | 类别不平衡 | 正例 3% 时准确率的陷阱 |
| 五 | 校准 |
|
| 六 | 奖励模型与 judge |
|
| 七 | 一个分数稳不稳 |
|
| 八 | 两个模型怎么比 | 独立比较 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 = 垃圾邮件):
| 邮件 | 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——把它读成一个有害内容过滤器:
| 阈值 | 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,四个指标连成曲线:
阈值高,精确率高、召回率低;阈值低,反过来。 同一个模型,不同阈值就是不同的过滤器。要求”漏放不到 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
| 模型 | 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:])
| 模型 | 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 上的两处
- 模型对自己答案的置信度:预训练后的模型校准通常不错(说 70% 的题大约 70% 对),RLHF 之后变差——模型学会了”自信”,对错都说 90%,正是上面 logit × 3 的形态。这是后训练的一个已知副作用。
- 奖励模型:分差 \(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. 三种系统偏差,与怎么量位置偏差
要警惕的偏差都是经典评估里有名字的系统误差(第一篇:偏差,集成消不掉):
- 位置偏差:先出现的回答得分高;
- 长度偏差:长回答得分高——控制长度、或在 prompt 里明确、或报”长度控制后的胜率”;
- 自我偏好: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 | 题数 \(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% 区间
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 上比两个模型,即使它们完全一样:
| 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% 里挑人”有多难:
3. 阈值不是 0.5:按成本定
第三章说阈值是业务决定。这里给它算一笔账:假设打一次电话成本 5 欧、成交一单收益 100 欧,用梯度提升的概率 \(p\),阈值从 0.02 扫到 0.9,每个阈值算”利润 = 100 × 成交数 − 5 × 打的电话数”:
| 阈值 | 打的电话 | 成交 | 召回 | 精确率 | 利润(欧) |
|---|---|---|---|---|---|
| 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 |
两点:
- 最优阈值 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:
| 折 | 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 上一分钟跑完。
十一、自测
-
一个安全过滤器精确率 98%、召回率 72%。它每拦 100 条有几条误杀?每 100 条有害内容漏放几条?哪个指标更该关心?
答案
2 条误杀、28 条漏放;安全过滤更该关心召回(漏放)。
-
两个质量分类器 AUC 分别 0.90 与 0.93,但在阈值 0.5 上前者 F1 更高。该选哪个?
答案
AUC 高的那个模型本身更好,F1 的差别是阈值选择的问题——选 0.93 的,再选合适的阈值。
-
正例 1% 的数据上分类器准确率 99.2%,说明什么?该看什么?
答案
几乎没有信息(全判负就 99%);看召回、精确率、AUC。
-
一个模型 AUC 0.95、ECE 0.10。它的问题在哪?怎么修?会不会影响 AUC?
答案
排序好但概率不可信(过度自信或过度保守);Platt 或 isotonic 校准;不影响 AUC(单调映射不改变排序)。
-
一个 judge 与人类一致率 82%,人与人之间一致率 80%,但人类 85% 的题都选 A。judge 够好了吗?还该看什么?
答案
先算 κ——85% 选 A 时随机一致率已很高,82% 的一致率 κ 可能很低;再对换顺序量位置偏差、控制长度、换家族。
-
一个 200 题的 benchmark 上模型 A 72%、B 68%。差异显著吗?该怎么比?
答案
标准误约 \(\sqrt{0.7 \times 0.3 / 200} = 3.2\) 个点,独立比较 4 个点不显著;用配对(McNemar)看分歧题——它可能显著,也可能不。
-
论文在 15 个 benchmark 上报了结果、其中 3 个”显著领先”。随机情况下期望几个显著?这个结果可信吗?
答案
期望 0.75 个;3 个略多于随机但不强——看效应大小与平均分的区间,或做 Bonferroni(每个用 0.33% 的水平)。
下一篇
下一篇是系列总结与通关自测:把十篇的核心问题、一句话结论、必记数字放在一起,再用一套自测检验是否真的能用这些工具想 LLM 的问题。
-
只说对了 95% 的样本,没说错在哪一边;有害内容占 5% 时全判「无害」也是 95%。要看混淆矩阵拆出的精确率(拦下来的里多少真有害)与召回率(有害的漏了多少),以及它们随阈值的权衡(要求召回 95% 时精确率掉到 90%);类别不平衡时看 AUC 或 F1。详见第二至四章。 ↩
-
先扣掉随机一致——算 Cohen’s κ(永远选 A 的 judge 在 90% 选 A 的题上一致率 92%、κ = 0);再与人和人的一致率比——人之间也只有 70–80%,但那不是 judge 准确率的上限(两个各 80% 正确的人互相一致 68%),80% 高于它不说明过拟合;再量系统偏差——对换顺序各评一次看位置偏差(模拟里差 21 个点),控制长度,换家族。系统偏差平均不掉。详见第六章。 ↩
-
不一定算。两个一样好的模型各有 50% 概率领先,20 个里领先 12 个在随机情况的一个标准差以内(期望 10 ± 2.2);每个 benchmark 各做一次检验还要多重比较校正(5% 水平下期望 1 个「显著」、至少一个的概率 61%);而且很多 benchmark 差几个点在置信区间内(MMLU ±0.8、100 题集 ±9)。同一套题上比要用配对检验。详见第七章、第八章。 ↩
系列 《LLM 时代的经典机器学习:只讲它在哪里重现》 第 10 / 11 篇
上一篇:去重——MinHash 与 LSH 的概率下一篇:系列总结与通关自测
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/evaluation-from-confusion-matrix-to-judge-agreement.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。