关于AUC

maxuewei2 / 2024-03-03 / 原文

分类阈值->混淆矩阵

在做二分类任务时,模型一般会对每个样本输出一个分值s(有时这个分值也表示样本是正例的概率)。
在这个分值区间里,设置一个阈值t,就可以把在阈值之上的预测为正例,阈值之下的预测为负例。
根据样本真实的标签和预测的结果,可以分为四种情况,统计四种情况的样本个数,就可以得到一个混淆矩阵(confusion matrix):

正样本 负样本
预测为正 TP(真正例) FP(假正例)
预测为负 FN(假负例) TN(真负例)

比如下图,正样本和负样本在分值区间上是两个正态分布,阈值t会把这两个分布切分成四块。

image

左右调整阈值t的大小,这四个值也会随之变化。于是,每个阈值t都会得到一个混淆矩阵。

假如我们要比较N个模型,每个模型设置K个阈值,那么就有N*K个混淆矩阵。用这么多混淆矩阵来比较这些模型的效果显然不现实,更何况阈值的选取还可以有无限多个。

混淆矩阵->TPR/FPR

所以接下来,我们根据混淆矩阵里的四个值,定义两个指标
True Positive Rate (TPR,真正例率) = \(\frac{TP}{TP+FN}\)
False Positive Rate (FPR,假正例率) = \(\frac{FP}{TN+FP}\)
对于真正例率TPR,分子是得分>t里面正样本的数目,分母是总的正样本数目。
对于假正例率FPR,分子是得分>t里面负样本的数目,分母是总的负样本数目。
一个好的模型,肯定是TPR越大越好,FPR越小越好。

分类阈值->TPR/FPR->ROC

那么调整阈值t时,因为混淆矩阵里四个值的变化,这两个指标也会随之变化。
比如,当t变小时,因为FN变小,TPR会变大;因为TN变小,FPR也会变大。反之,当t变大时,TPR和FPR都会变小。
那么如果我们取各种不同的阈值t,并以TPR和FPR作为坐标轴,画出各个t值对应的点,就可以得到这么一个图:

image

因为好的模型肯定是TPR大而FPR小,所以这些点都在左上的部分。又因为t变大时,TPR和FPR都会变小,所以这些点分布的形状是上凸的。
连接这些点,可以得到一个上凸的曲线,称为ROC曲线。
当t很小时,TN和FN是0,那么TPR和FPR都是1。当t很大时,TP和FP都是0,那么TPR和FPR都是0。所以曲线是从右上角的(1,1)直到左下角的(0,0)。

随机模型的ROC曲线

随机的模型不区分正负样本,所以在得分>t的样本里,正负样本的比例应该与总体正负样本的比例相同。
\(\frac{TP}{FP}=\frac{P}{N}=\frac{TP+FN}{TN+FP}\)。因此\(\frac{TP}{TP+FN}=\frac{FP}{TN+FP}\),即TPR=FPR。
所以随机模型的ROC曲线是一条连接(0,0)和(1,1)的直线。
image

我们也可以画出正负例在随机模型的分值上的分布,如下图的两种情况,正负例在所有分值上均匀分布,或者正负例在分值上的分布相同,TPR和FPR都是随着t的变化始终相等。

image

ROC->AUC

有了ROC曲线,比较多个模型的效果,就是比较多条ROC曲线。
曲线越凸,模型效果越好。
但二维的曲线比较起来也还是不方便。

所以接下来我们定义ROC曲线下的面积为AUROC(Area Under the ROC Curve),大部分时候简写为AUC。
如果现在有两个模型,一个A模型始终比另一个模型B好,也就是在所有阈值t下,A的TPR都更高,FPR都更小。
那么A的ROC曲线就比B的更凸,A的曲线下面积,即AUC就更大。

随机模型的AUC是对角线下的面积,即0.5。所以任何合理的模型的AUC都应该大于0.5。

AUC的另一种解释

AUC还可以解释为,随机选取一个正样本和一个负样本,模型给正样本的分值高于负样本分值的概率。

这个解释与以上用ROC曲线面积来做的定义之间的关系,见StackExchange。

AUC的计算

根据以上对AUC的解释,可以实现一种最简单的\(O(n^2)\)复杂度的计算方法。

点击查看代码
import numpy as np

y    = np.array([1,   0,   0,   0,   1,    0,   1,    0,    0,   1  ])
pred = np.array([0.9, 0.4, 0.3, 0.9, 0.35, 0.6, 0.65, 0.32, 0.8, 0.7])

def auc():
    pos = pred[y==1]
    neg = pred[y==0]
    correct = 0
    for i in pos:
      for j in neg:
        if i > j:
          correct += 1
        elif i == j:
          correct += 0.5
    return correct / (len(pos) * len(neg))
print(auc())

from sklearn import metrics
fpr, tpr, thresholds = metrics.roc_curve(y, pred, pos_label=1)
print(metrics.auc(fpr, tpr))

AUC的适用与不适用

适用

  • 仅关心相对序,不考虑分值大小时
  • 与阈值无关时
  • 要求对正负样本比例不敏感时,如对负样本下采样

不适用

  • 关心分值大小时
  • 某类正样本或负样本更重要时,如email spam classification

参考

https://stats.stackexchange.com/questions/132777/what-does-auc-stand-for-and-what-is-it/133435#133435
https://developers.google.com/machine-learning/crash-course/classification/roc-and-auc
https://www.dataschool.io/roc-curves-and-auc-explained/
https://tracholar.github.io/machine-learning/2018/01/26/auc.html
https://stats.stackexchange.com/questions/190216/why-is-roc-auc-equivalent-to-the-probability-that-two-randomly-selected-samples
https://stats.stackexchange.com/questions/180638/how-to-derive-the-probabilistic-interpretation-of-the-auc
https://ccrma.stanford.edu/workshops/mir2009/references/ROCintro.pdf
https://zhuanlan.zhihu.com/p/411010918
https://medium.com/@penggongting/understanding-roc-auc-pros-and-cons-why-is-bier-score-a-great-supplement-c7a0c976b679
https://stats.stackexchange.com/questions/105501/understanding-roc-curve
https://en.wikipedia.org/wiki/Receiver_operating_characteristic