稍微了解机器学习或深度学习的同学,应该对AUC不陌生,它算是二分类问题中最为常用的评价指标之一。之所以专门写文章来解释其含义,是因为上次面试时被问到如何自己用代码实现AUC时没有答上来,才发现自己一直是似懂非懂的状态。痛定思痛,决定写一篇文章来解释AUC的含义以及给出详细的Python代码实现
除了AUC之外,二分类场景里用得更多的一个评价指标是accuracy,也就是准确率。比方说经典的用朴素贝叶斯模型去预测邮件是否是垃圾邮件的场景,我们先假设在现实生活中正常邮件和垃圾邮件出现的比例是接近1:1。在模型训练好之后,每来一个样本,我们会用已训练好的模型去预测这个样本是垃圾邮件的概率,所以每个样本都会有一个对应的预测结果prob(概率):当prob > 0.5时,认为对应的样本(邮件)是垃圾邮件,此时令该样本的label为1
当prob < 0.5时,认为对应的样本(邮件)不是垃圾邮件,此时令该样本的label为0
然后我们就根据预测出来的label和样本真实的label是否相等来计算准确率,准确率越高,我们认为模型的效果越好
也就是说,计算准确率我们需要知道每个样本对应的预测label,而为了获得label,我们需要人为指定一个阈值来对prob进行划分对吧,那为什么上面的例子里阈值是0.5呢?为啥不能设置成0.6、0.7,甚至0.9?
答案当然是可以!!!这个值是人为给定的。但是你会发现,当阈值设定为0.5的时,最终计算出的accuracy会不错,比方说80%;但是如果你随便设定阈值为一个其他值,比方说0.9,你会发现accuracy会很低,也是说即使是相同的模型,选择不同的阈值,最终从准确率上看模型的性能会差别很大
究竟阈值设置成多少会比较合适呢?一般情况下应该设置成先验概率:在上面垃圾邮件分类的例子里,这个先验概率是样本里所有垃圾邮件的个数/总邮件的个数,前面提到假设在现实生活中正常邮件和垃圾邮件出现的比例是接近1:1,那么这个先验概率值就是0.5。但是不幸的是,现实中往往会因为可获得的样本过少等原因,导致在这些样本上统计得到的先验概率是有偏的,从而得到一个不是那么合理的阈值,无法反映模型的真实性能
有没有什么评价指标不需要给定阈值呢?AUC就是,它是基于对样本的预测概率进行排序后计算得到的,所以并不需要知道阈值,具体怎么排序怎么计算,稍后会结合代码详细解释,这里先说说怎么理解auc
AUC(Area Under Curve)被定义为ROC曲线下的面积。其中,ROC曲线全称为受试者工作特征曲线 (receiver operating characteristic curve),它是根据一系列不同的二分类方式(分界值或决定阈),以真阳性率(敏感性)为纵坐标,假阳性率(1-特异性)为横坐标绘制的曲线,我从百度上截取了一段关于ROC曲线的示例:
这个应该我们最常见到的关于auc的解释。TPR和FPR的计算公式我就不写了,网上很多。这里简单解释下这个图是怎么画出来的:
假设你的数据有两部分,一部分是预测值probs,例如:probs = [0.9, 0.8, 0.6, 0.7, 0.95];另外一部分是对应的labels,例如:labels = [1, 1, 0, 1, 0]。注意,以下思路或者伪代码都是以Python的形式给出先将probs和labels组成一个tuple(暂且命名为t)
以probs为key对t从大到小进行排序,例如排序后 t = [(0.95, 0), (0.9,1), (0.8,1), (0.7,1), (0.6,0)]
按probs从高到低计算阈值,过程如下:第一个阈值为1(其实这个阈值只要大于0.95即可),大于该阈值的视为正例,小于该阈值为负例,然后按公式计算TPR和FPR,得到ROC图上的一个坐标,此时TPR和FPR都为0
第二个阈值为(0.95+0.9)/2, 大于该阈值的视为正例,小于该阈值为负例(实际这个阈值只要是小于0.95且大于0.9即可,不一定要是均值),然后按公式计算TPR和FPR,得到ROC图上的第二个坐标
第三个阈值为(0.9+0.8)/2, 大于该阈值的视为正例,小于该阈值为负例,然后按公式计算TPR和FPR,得到ROC图上的第三个坐标
重复上述过程,最后一个阈值为0(其实这个阈值只要比probs的最小值小即可),依然是大于该阈值的视为正例,小于该阈值为负例,此时所有样本都被判定为正例,按公式计算TPR和FPR可知,TPR=FPR=1,得到ROC图上的最后一个坐标
也就是说,ROC曲线肯定是从坐标(0,0)到坐标(1,1)的一条曲线,而曲线越靠近左上角,说明正样本中混入的负样本越少,模型对正负样本的区分能力越强,此时ROC曲线下方的面积越大;当给定某一个阈值时,正负样本刚好被隔开,即预测值大于该阈值的刚好都是正样本,预测值小于该阈值的刚好都是负样本,这个模型就可以认为是一个完美的模型,此时ROC曲线下方的面积就是1。至于这个完美的阈值是多少,我们并不需要知道,只需要存在这样一个阈值就行了
看完上面的解释是不是对AUC有一些理解了?但是如果现在要你自己写一个函数去实现AUC的计算,该怎么写?你该不会去计算曲线下方的面积吧?
别慌,我们先从概率的角度再去理解下AUC:假设有M个正样本和N个负样本
从M个正样本中随机抽取一个样本a,其prob为p_a, 然后从N个负样本中随机抽取一个样本b,其prob为p_b,这样一共有
个抽取结果,每个抽取结果是一个pair (p_a, p_b)
假设所有抽取结果中有K个结果满足p_a > p_b,那么
这么理解的话,AUC的计算就变成一个比较简单的排列组合问题了。通过上面这种方式,我们很容易就能计算AUC:
import numpy as np
from collections import Counter
class Metrics(object):
@classmethod
def auc(cls, labels, probs):
c = Counter(labels)
pos_cnt, neg_cnt = c[1], c[0]
t = sorted(zip(probs, labels), key=lambda x: (x[0], x[1]), reverse=True)
pos_than_neg, find_neg = 0, 0
tmp_set = set()
for i in range(len(t)):
"""处理多个样本的预测概率值相同的情况"""
if i + 1 < len(t) and t[i][0] == t[i + 1][0]:
tmp_set.add(i)
tmp_set.add(i + 1)
continue
if len(tmp_set) > 1:
c = Counter([t[i][1] for i in tmp_set])
pos, neg = c[1], c[0]
find_neg = find_neg + neg
pos_than_neg += pos * (neg_cnt - find_neg + neg / 2)
tmp_set.clear()
continue
if t[i][1] == 1:
pos_than_neg += (neg_cnt - find_neg)
else:
find_neg += 1
return 1.0 * pos_than_neg / (pos_cnt * neg_cnt)
通过抽样的方式来解释AUC,其实跟上面通过ROC曲线下的面积来理解AUC是异曲同工的:假设所有正样本的预测概率值都是分布在0.6-1.0这个区别,那么0.6-1.0这个区间里混入的负样本越少,表明模型对正负样本的区分能力越强,ROC曲线越靠近左上角,满足p_a > p_b的K越大,AUC越大;相反,0.6-1.0这个区间里混入的负样本越多,表明模型对正负样本的区分能力越差,ROC曲线越靠近对角线,满足p_a > p_b的K越少,AUC越小