1.什么是AUC?
AUC是一个模型评价指标,只能用于二分类模型的评价,对于二分类模型,还有很多其他评价指标,比如logloss,accuracy,precision。如果你经常关注数据挖掘比赛,比如kaggle,那你会发现AUC和logloss基本是最常见的模型评价指标。为什么AUC和logloss比accuracy更常用呢?因为很多机器学习的模型对分类问题的预测结果都是概率,如果要计算accuracy,需要先把概率转化成类别,这就需要手动设置一个阈值,如果对一个样本的预测概率高于这个预测,就把这个样本放进一个类别里面,低于这个阈值,放进另一个类别里面。所以这个阈值很大程度上影响了accuracy的计算。使用AUC或者logloss可以避免把预测概率转换成类别。
AUC是Areaundercurve的首字母缩写。Areaundercurve是什么呢,从字面理解,就是一条曲线下面区域的面积。所以大家要先来弄清楚这条曲线是什么。这个曲线有个名字,叫ROC曲线。ROC曲线是统计里面的概率,最早由电子工程师在二战中提出来(更多关于ROC的资料可以参考维基百科)。
ROC曲线是基于样本的真实类别和预测概率来画的,具体来说,ROC曲线的x轴是伪阳性率(falsepositiverate),y轴是真阳性率(truepositiverate)。那么问题来了,什么是真、伪阳性率呢?对于二分类问题,一个样本的类别只有两种,大家用0,1分别表示两种类别,0和1也可以分别叫做阴性和阳性。当大家用一个分类器进行概率的预测的时候,对于真实为0的样本,大家可能预测其为0或1,同样对于真实为1的样本,大家也可能预测其为0或1,这样就有四种可能性:
真阳性率=(真阳性的数量)/(真阳性的数量+伪阴性的数量)
伪阳性率=(伪阳性的数量)/(伪阳性的数量+真阴性的数量)
有了上面两个公式,大家就可以计算真、伪阳性率了。但是如何根据预测的概率得到真伪阳性、阴性的数量。
大家来看一个具体例子,比如有5个样本:
真实的类别(标签)是y=c(1,1,0,0,1)
一个分类器预测样本为1的概率是p=c(0.5,0.6,0.55,0.4,0.7)
如文章一开始所说,大家需要选定阈值才能把概率转化为类别,选定不同的阈值会得到不同的结果。如果大家选定的阈值为0.1,那5个样本被分进1的类别,如果选定0.3,结果仍然一样。如果选了0.45作为阈值,那么只有样本4被分进0,其余都进入1类。一旦得到了类别,大家就可以计算相应的真、伪阳性率,当大家把所有计算得到的不同真、伪阳性率连起来,就画出了ROC曲线,大家不需要手动做这个,因为很多程序包可以自动计算真、伪阳性率,比如在R里面,下面的代码可以计算以上例子的真、伪阳性率并且画出ROC曲线:
library(ROCR)
p=c(0.5,0.6,0.55,0.4,0.7)
y=c(1,1,0,0,1)
pred=prediction(p,y)
perf=performance(pred,"tpr","fpr")
plot(perf,col="blue",lty=3,lwd=3,cex.lab=1.5,cex.axis=2,cex.main=1.5,main="ROCplot")
得到了ROC曲线,那么曲线下面的面积也就可以算出来了,同样,大家可以通过程序得到面积:
auc=performance(pred,"auc")
auc=unlist(slot(auc,"y.values"))
这个ROC的面积也就是大家要计算的AUC值。
通过AUC的定义大家知道了AUC是什么,怎么算,但是它的意义是什么呢。如果从定义来理解AUC的含义,比较困难,实际上AUC和Mann–WhitneyUtest有密切的联系,偶会在第三部说明。从Mann–WhitneyUstatistic的角度来解释,AUC就是从所有1样本中随机选取一个样本,从所有0样本中随机选取一个样本,然后根据你的分类器对两个随机样本进行预测,把1样本预测为1的概率为p1,把0样本预测为1的概率为p0,p1>p0的概率就等于AUC。所以AUC反应的是分类器对样本的排序能力。根据这个解释,如果大家完全随机的对样本分类,那么AUC应该接近0.5。另外值得注意的是,AUC对样本类别是否均衡并不敏感,这也是不均衡样本通常用AUC评价分类器性能的一个原因。
有朋友用python,下面代码用于在python里面计算auc:
fromsklearnimportmetrics
defaucfun(act,pred):
fpr,tpr,thresholds=metrics.roc_curve(act,pred,pos_label=1)
returnmetrics.auc(fpr,tpr)
2.可以直接优化AUC来训练分类器吗?
答案是肯定的,偶在这里给出一个简单的例子,通过直接优化AUC来训练一个逻辑回归模型。大家应该知道逻辑回归通常是通过极大似然估计来训练的,也就是最大化极大似然函数,这是统计里的概念,在机器学习里,对应logloss函数。大家通过下面的例子来训练一个逻辑回归是的AUC最大化:
#生成训练数据
set.seed(1999)
x1=rnorm(1000)
x2=rnorm(1000)
z=1+2*x1+3*x2
pr=1/(1+exp(-z))
y=rbinom(1000,1,pr)
#使用logloss作为训练目标函数
df=data.frame(y=y,x1=x1,x2=x2)
glm.fit=glm(y~x1+x2,data=df,family="binomial")
#下面使用auc作为训练目标函数
library(ROCR)
CalAUC<-function(real,pred){
rocr.pred=prediction(pred,real)
rocr.perf=performance(rocr.pred,'auc')
as.numeric(rocr.perf@y.values)
}
#初始值
beta0=c(1,1,1)
loss<-function(beta){
z=beta[1]+beta[2]*x1+beta[3]*x2
pred=1/(1+exp(-z))
-CalAUC(y,pred)
}
res=optim(beta0,loss,method="Nelder-Mead",control=list(maxit=100))
cat("直接用AUC训练:",-res$value)
cat("使用glm函数",CalAUC(y,glm.fit$fitted.values))
程序输出结果:
直接用AUC训练:0.9339833
使用glm函数0.9338208
可见,通过直接优化AUC,大家得到的AUC比直接优化logloss稍大一点点点点。
根据@西方不得不败提示,glmnet包自带根据AUC来训练逻辑回归的功能,这里就不展开说啦。
理论上讲,直接优化AUC在一定条件下就是rankboost算法(感兴趣可以参见paper)。
对于最近十分火热的xgboost包,也提供了直接优化AUC的功能,使用起来很简单,只要把目标函数设置为:
objective='rank:pairwise'
3.从AUC到真实类别(label)?
最开始思考这个问题是做一个网上的比赛,二分类问题,每次提交一组预测系统会计算出一个AUC值,因为这个比赛只有5000样本,并且系统显示的AUC值有小数点后面7、8位,所以偶想是否可以通过可能通过AUC值计算出样本的真实label来。也许并没有实际价值,但是问题本身是很有趣的,像是在破解密码。
一个naive但是可行但是效率很低的办法,就是每次生成一组预测值,里面只有一个样本预测为1,其余都是0,然后提交预测计算AUC,然后根据这个AUC来判断此样本的label,但是这样效率太低了,5000个样本,需要5000次提交。
思考了很久,最后发现可以通过AUC的另一个计算公式入手。也就是第一部分说的Ustatistic。
3.1根据一个AUC值计算样本中0,1的数目
根据AUC与Ustatistic的联系,大家可以用下面的代码计算AUC:
auc=(sum(rank(c(p(label==1),p(label==0)))[1:n1])-n1*(n1+1)/2)/n0/n1
上面label表示样本的真实类别,p表示预测为1的概率,rank是R的内置函数,n0表示真实数据中0的数目,n1表示1的数目,n0+n1=n表示数据所有样本数目,根据这个简单的一行代码,大家可以不用任何包来计算AUC。
上面公式很有趣,n0,n1还有label都是固定的,p不同导致auc不一致,观察sum里面,可以发现这个sum本质是什么?就是计算pred里面对应的真实label为1的那些预测值的rank的和。
继续使用第一部分的例子,5个样本的预测值的rank:
rank(c(0.5,0.6,0.55,0.4,0.7))
[1]24315
其中真实为1的样本(1,2,5)的对应rank是2,4,5,这三个rank值的和2+4+5=11,n0=2,n1=3,于是根据上面的AUC公式:(11-3*(3+1)/2)/2/3=5/6=0.83333,这个结果与大家在第一部分用AUC定义算的值完全一样。
所以说,真正影响auc的,就是预测值里面对本来是1的样本的预测的rank的和。
要破解真实label,第一部要做的是找到样本里面0和1的数目,也就是n0和n1等于多少。这个问题不复杂。要知道相同预测值的rank也一致,就是说如果所有样本的预测只取0或者1,那对应的rank只有两个unique数值。
再观察AUC公式里面的sum:
sum(rank(c(pred(label==1),pred(label==0)))[1:n1])
这个sum是n1个数值的和,这n1个数值,当大家的pred只有两个不同取值的时候,仅包括两个unique的数值。继续用上面例子,一共有5个样本,大家生成第一组预测p如下:
>p=c(1,1,1,0,0)
>rank(p)
[1]4.04.04.01.51.5
可见p的rank只有两个不同取值,1.5和4,这是因为预测概率p也只有两个不同取值。
然后大家还知道sum是n1个数的sum,大家不知道的是这n1个数,里面有几个1.5,几个4,假设其中有k1个1.5,k2个4,可以列出一个方程:
k1+k2=n1
k1*1.5+k2*4=sum(rank(c(p(label==1),p(label==0)))[1:n1])=auc*n0*n1+n1*(1+n1)/2
所以最终得到下面的方程组:
k1+k2=n1
k1*1.5+k2*4=0.833333*n0*n1+n1*(1+n1)/2
n0+n1=5
其中k1,k2和n1未知,两个方程,3个未知数,属于不定方程组,但是要知道k1,k2,和n1都是整数,并且有取值范围,要借出来很简单,写一个for循环,1秒中就可以找到一组满足3个方程多k1,k2以及n1。
如果大家变更p,比如p=c(rep(1,m),rep(0,5-m)),通过一样的算法,可以计算出来前m个样本中1的数量。
通过这个算法,偶可以算出来这个贷款预测比赛测试数据中有509个1和4491个0。
做到这里,差点就放弃了,但是后来突然又有了灵感,找到了下面的算法:
3.2根据AUC破解样本的真实label
这里就省略思考过程了,直接来破解算法:
对于一组总数为n的测试样本,大家先来计算
m=floor(log(n,base=2))+1
这个m表示大家通过两次auc计算可以计算出至少多少个样本的真实label,比如n=5000,那么m=13
也就是说通过大家两次提交,可以最少得到13个样本的label。这13个样本是可以自己随便指定的,比如大家对前13个样本感兴趣,那么具体做法如下:
fix1=2^c(0:12)
fix2=c(fix1[-1],fix1[1])
unfixed=setdiff(1:5000,fix1)
p1=c(fix1,unfixed)#第1组预测
p2=c(fix2,unfixed)#第2组预测
使用上面的两组预测p1和p2分别计算AUC,得到auc1和auc2,根据上面给出的auc算法:
sum(rank(c(p1(label==1),p1(label==0)))[1:n1])-n1*(1+n1)/2=auc1*n0*n1
sum(rank(c(p2(label==1),p2(label==0)))[1:n1])-n1*(1+n1)/2=auc2*n0*n1
两个公式相减:
sum(rank(c(p1(label==1),p1(label==0)))[1:n1])-sum(rank(c(p2(label==1),p2(label==0)))[1:n1])-n1*(1+n1)/2=(auc1-auc2)*n0*n1
得到的这个等式里,大家已经通过上面的方法找到了n0和n1,auc1和auc2是已知,所以等式右面值可以算出,那么等式左面呢,因为大家两个预测结果p1和p2只有前三个点的预测之不一样,其余点的预测值一样,rank也一样,那么等式左面的两个sum的差,其实只由前13个样本的真实label决定,具体来说:
sum1-sum2=y1*(fix1[1]-fix2[1])+y2*(fix1[2]-fix2[2])+…+y13*(fix1[13]-fix2[13])
=y1*(-1)+y2*(-2)+…+y12*(-2048)+y13*(4095)
上面的方程里面yi代表样本i的真实label,有且只有唯一解,以为这个方程本质上就是10进制数用2进制表达。所以通过两次auc计算,大家可以找到13个点的真实标签。比如对上面提到的贷款预测比赛,选定前13个label,auc1=0.50220853,auc2=0.5017588,然后就可以算出来前13个test样本只有第三个样本是0,其余都是1。
但是13并不是上限,偶有一些更好的结果,比较复杂,在这就不展开说了。