ROC-AUC 为 0.97、精确率为 9%:基准率的数学

通过一个阳性率为 1% 的检测器,逐步把阈值转换为 ROC 和精确率-召回率点,并为 30 个告警的审核预算选择运行点。

分享这篇文章

这里有一个 ROC-AUC 为 0.97080.9708 的检测器。在一个包含 1,000 行、其中 10 行为阳性的测试集上,它的 90% 召回率阈值会发送 99 个告警。其中 9 个是真阳性,90 个是假阳性。

这两句话描述的是同一个分数排序。第一句话奖励的是:在所有阈值上,阳性示例通常排在阴性示例之上的程度。第二句话描述的是:在一个阈值下,人们实际需要审核的队列。在阳性样本稀少时,这两者可能代表截然不同的运营故事。

本课通过一个完整的合成示例来构建这种差距。你将把阈值计数转换为 ROC 和精确率-召回率坐标,计算 ROC-AUC 和平均精确率,展示患病率如何改变精确率却不改变 ROC 点,并选择一个适合 30 个告警审核预算的阈值。

一个阈值产生 99 个告警

想象一个为每一行分配 0011 之间分数的模型。这个集合包含 10 个阳性和 990 个阴性,因此患病率

π=101000=0.01.\pi=\frac{10}{1000}=0.01.

在阈值 0.550.55 下,所有分数大于或等于 0.550.55 的行都会成为告警。所得混淆矩阵如下:

1,000 个示例在阈值 0.55 下的混淆矩阵

预测实际为阳性实际为阴性总计
告警TP = 9FP = 9099
无告警FN = 1TN = 900901
总计109901,000

这个阈值捕获了 10 个阳性中的 9 个。听起来很强,直到把审核队列算进去:99 个告警中有 90 个是假阳性。模型并没有自相矛盾。召回率和精确率回答的是不同的问题。

recall=TPTP+FN=910=0.90,\operatorname{recall} =\frac{TP}{TP+FN} =\frac{9}{10} =0.90, precision=TPTP+FP=9990.0909.\operatorname{precision} =\frac{TP}{TP+FP} =\frac{9}{99} \approx0.0909.

召回率问:“在所有实际阳性中,告警系统捕获了多大比例?”精确率问:“在所有告警中,有多大比例为阳性?”在这个运行点,答案分别是 90%90\%9.1%9.1\%

混淆矩阵提供两套坐标系

ROC 曲线绘制真阳性率(也就是召回率)与假阳性率的关系:

TPR=TPTP+FN.\operatorname{TPR}=\frac{TP}{TP+FN}. FPR=FPFP+TN.\operatorname{FPR}=\frac{FP}{FP+TN}.

精确率-召回率曲线绘制精确率与召回率的关系。对于 0.550.55 阈值,同一个混淆矩阵给出

FPR=909900.0909\operatorname{FPR}=\frac{90}{990}\approx0.0909

以及 TPR=9/10=0.90\operatorname{TPR}=9/10=0.90。因此它的 ROC 坐标是 (0.0909,0.90)(0.0909,0.90)。从精确率-召回率的角度看,

precision=9990.0909.\operatorname{precision}=\frac{9}{99}\approx0.0909.

与召回率 0.900.90 合在一起,就得到精确率-召回率坐标 (0.90,0.0909)(0.90,0.0909)

现在在同一组排序分数中扫描六个阈值。计数是累积的:降低阈值会同时把阳性和阴性示例加入告警集合。

1,000 个示例(10 个阳性、990 个阴性)的阈值扫描

阈值TPFP告警数召回率FPR精确率
0000.00.0

1.000*

0.954150.40.00100.800
0.75820280.80.02020.286
0.55990990.90.09090.091
0.35102903001.00.29290.033
0.10109901,0001.01.00.010

^* 没有预测为阳性的样本时,精确率没有定义。数值 1 是绘制这条曲线时采用的约定起点;它并不证明分类器有用。

scikit-learn 的精确率-召回率曲线文档使用相同的定义,并包含完整曲线的端点。它的阈值是不同的分数值;真实数据通常会提供比这个简化示例多得多的点。

同一阈值扫描的 ROC 和精确率-召回率视图两张图展示合成分类器在一千个样本和十个阳性样本上的六个工作点。0.75 阈值在 ROC 图上的假阳性率为 2%、召回率为 80%,在精确率-召回率图上的精确率为 29%、召回率为 80%。一次阈值扫描,两个坐标系1,000 个样本 · 10 个阳性 · 990 个阴性ROC 空间召回率对假阳性率阈值 0.75FPR 2.0% · 召回率 80%00.20.40.60.81.000.20.40.60.81.0假阳性率召回率(真阳性率)精确率-召回率空间仅显示工作点;不作线性插值1% 患病率基线阈值 0.75召回率 80% · 精确率 29%00.20.40.60.81.000.20.40.60.81.0召回率精确率

橙色点在两个面板中都表示阈值 0.75。坐标发生变化,而底层混淆矩阵仍为 8 个真阳性、20 个假阳性、2 个假阴性和 970 个真阴性。

ROC 把 90 个假警报压缩成 9.1%

在阈值 0.550.55 下,FPR 的分母包含全部 990 个阴性。90 个假阳性最终只有 9.1%9.1\% 的 FPR。精确率则把同样的 90 个假阳性与审核者会看到的 9 个真阳性放在一起,得到 9.1%9.1\% 的精确率。

这就是算术中的基准率效应。一个非常大的阴性类别中的小比例,可能超过一个很小的阳性类别中的大多数。ROC 点是有效的,但既不包含告警纯度,也不包含队列大小。

ROC-AUC 总结的是整条 ROC 曲线,而不是某个阈值。对六个点应用梯形法则得到

ROC-AUC=0.9708.\operatorname{ROC\text{-}AUC}=0.9708.

scikit-learn 将 ROC-AUC 定义为根据预测分数计算的 ROC 曲线下面积。对于二元分类器,它也可以理解为:随机选出的阳性获得高于随机选出的阴性的分数的概率,并按通常方式处理平局。这种排序解释没有说明阈值、告警数和审核成本。

ICML 2006 中 Jesse Davis 和 Mark Goadrich 的分析展示了这种区别为何会在类别不平衡时更加明显。对于固定数据集,ROC 曲线和精确率-召回率曲线在数学上相关,但大的阴性分母可能让 ROC 图在视觉上显得过于乐观。他们还展示了在精确率-召回率空间中使用直线插值通常是不正确的;观测运行点之间的曲线不是装饰性的线段。

对于这次离散扫描,不插值的平均精确率为

AP=n(RnRn1)Pn=0.4467.\operatorname{AP} =\sum_n(R_n-R_{n-1})P_n =0.4467.

这远高于这种患病率下无信息排序的 1%1\% 精确率。AP 仍然总结的是排序;人员配置决策需要一个运行点。scikit-learn 的平均精确率文档使用上面的召回率加权求和,并警告梯形插值可能过于乐观。

患病率改变精确率,却不改变 ROC 点

将患病率写作 π=Pr(Y=1)\pi=\Pr(Y=1)。如果一个运行点的真阳性率为 tt、假阳性率为 ff,那么在一个大型总体中:

  • 预期的阳性告警占比为 tπt\pi
  • 预期的假告警占比为 f(1π)f(1-\pi)

精确率是用阳性告警占比除以所有告警所得的结果:

precision=tπtπ+f(1π).\operatorname{precision} =\frac{t\pi}{t\pi+f(1-\pi)}.

使用 0.750.75 阈值,此时 t=0.80t=0.80f=20/9900.0202f=20/990\approx0.0202。保持这些条件率不变,得到:

固定 80% TPR 和 2.02% FPR 时,三个患病率水平下的预期精确率

患病率预期精确率
0.1%3.8%
1%28.6%
10%81.5%

三个行中的 ROC 坐标都保持在 (0.0202,0.80)(0.0202,0.80) 附近,因为 TPR 以阳性为条件,而 FPR 以阴性为条件。精确率坐标发生巨大变化,因为它的分母混合了真告警和假告警。

这个计算假设 TPR 和 FPR 能不变地迁移到新总体。迁移必须经过测试:分数分布、标签或数据质量的变化都可能移动这两个率。在代表预期用途的数据上估计运行点,并把阈值选择与用于最终性能报告的未触碰样本分开。在校准预测概率时,同样的人口纪律也很重要。

30 个审核名额选择一个阈值

假设团队每 1,000 行最多能审核 30 个告警,并要求至少 75%75\% 的召回率。决策规则现在很明确:

TP+FP30.TP+FP\le30.

召回率要求是

TPTP+FN0.75.\frac{TP}{TP+FN}\ge0.75.

阈值 0.950.95 产生 5 个告警,符合队列容量,但其 40%40\% 的召回率未达到要求。阈值 0.750.75 产生 28 个告警并达到 80%80\% 的召回率,因此符合条件。阈值 0.550.55 达到 90%90\% 的召回率,却产生 99 个告警,超过可用容量的三倍。

因此,对于这些行和这些约束,选定的运行点是 0.750.75。它的精确率为 8/28=28.6%8/28=28.6\%:审核者应该预计 28 个告警中大约有 20 个是假告警,而不是仅仅引用 ROC-AUC 0.97080.9708 所带来的安慰。

容量也可以写成假阳性限额。一旦最少真阳性数确定,预算 BB 至多允许

FPmax=BTP.FP_{\max}=B-TP.

B=30B=30 且有 8 个真阳性时,限额是 22 个假阳性。阈值 0.750.75 产生 20 个。这种转换把模型指标连接到实际资源约束,而不是把普遍的重要性赋予某一条曲线。

重现扫描和预算门槛

下面的 NumPy 程序计算两条曲线、ROC-AUC、不插值的平均精确率以及符合条件的阈值。数组保存每个递减阈值下的累积计数。

import numpy as np

thresholds = np.array([np.inf, 0.95, 0.75, 0.55, 0.35, 0.10])
tp = np.array([0, 4, 8, 9, 10, 10])
fp = np.array([0, 1, 20, 90, 290, 990])
positives, negatives = 10, 990

recall = tp / positives
fpr = fp / negatives
precision = np.divide(
    tp,
    tp + fp,
    out=np.ones_like(tp, dtype=float),
    where=(tp + fp) > 0,
)

roc_auc = np.trapezoid(recall, fpr)
average_precision = np.sum(np.diff(recall) * precision[1:])

budget = 30
recall_floor = 0.75
alerts = tp + fp
eligible = (alerts <= budget) & (recall >= recall_floor)

print(f"ROC-AUC: {roc_auc:.6f}")
print(f"average precision: {average_precision:.6f}")
print("eligible thresholds:", thresholds[eligible])

输出是:

ROC-AUC: 0.970808
average precision: 0.446710
eligible thresholds: [0.75]

如果有多个阈值符合条件,就根据声明过的次要目标来选择,例如在预算内取得最高召回率,或最低预期成本,而不是选择让图表看起来最好的点。如果分数存在平局,阈值上的所有行会一起移动;容量目标可能无法精确达到。

没有阈值符合条件时

把同一个预算收紧为 25 个告警,同时要求 80%80\% 的召回率。阈值 0.750.75 捕获 8 个阳性,却产生 28 个告警。更高的阈值符合队列容量,却只能捕获 4 个阳性。扫描中的任何一行都无法同时满足两个约束。

这个结果具有可操作性。移动阈值无法创造出缺失的运行点。团队必须改善排序、增加第二阶段过滤器、提高审核容量,或接受不同的召回率要求。在真实系统中,假阳性和假阴性的成本也可能不相等,因此成本函数或决策分析可以取代简单的队列上限。

更广泛的测量原则既是当前的,也是数学上的。NIST AI 800-3强调,评估者必须选择适合目标和数据的性能量与不确定性方法;不存在适用于每次评估的单一公式。在这里,相关量并不是抽象的“AUC”,而是一个具有明确患病率和真实审核预算的阈值下的召回率、精确率和告警量。

资料来源

  1. The Relationship Between Precision-Recall and ROC Curves
  2. scikit-learn precision-recall curve documentation
  3. scikit-learn ROC AUC documentation
  4. scikit-learn average precision documentation
  5. NIST AI 800-3 expands the AI evaluation toolbox