如何理解機器學習和統計中的AUC?
分三部分,第一部分是對AUC的基本介紹,包括AUC的定義,解釋,以及算法和代碼,第二部分用邏輯回歸作為例子來說明如何通過直接優化AUC來訓練,第三部分,內容完全由@李大貓原創——如何根據auc值來計算真正的類別,換句話說,就是對auc的反向工程。
1. 什么是AUC?
AUC是一個模型評價指標,只能用于二分類模型的評價,對于二分類模型,還有很多其他評價指標,比如logloss,accuracy,precision。如果你經常關注數據挖掘比賽,比如kaggle,那你會發現AUC和logloss基本是最常見的模型評價指標。為什么AUC和logloss比accuracy更常用呢?因為很多機器學習的模型對分類問題的預測結果都是概率,如果要計算accuracy,需要先把概率轉化成類別,這就需要手動設置一個閾值,如果對一個樣本的預測概率高于這個預測,就把這個樣本放進一個類別里面,低于這個閾值,放進另一個類別里面。所以這個閾值很大程度上影響了accuracy的計算。使用AUC或者logloss可以避免把預測概率轉換成類別。
AUC是Area under curve的首字母縮寫。Area under curve是什么呢,從字面理解,就是一條曲線下面區域的面積。所以我們要先來弄清楚這條曲線是什么。這個曲線有個名字,叫ROC曲線。ROC曲線是統計里面的概率,最早由電子工程師在二戰中提出來(更多關于ROC的資料可以參考維基百科)。
ROC曲線是基于樣本的真實類別和預測概率來畫的,具體來說,ROC曲線的x軸是偽陽性率(false positive rate),y軸是真陽性率(true positive rate)。那么問題來了,什么是真、偽陽性率呢?對于二分類問題,一個樣本的類別只有兩種,我們用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="ROC plot")
得到了ROC曲線,那么曲線下面的面積也就可以算出來了,同樣,我們可以通過程序得到面積:
auc = performance(pred,"auc")
auc = unlist(slot(auc, "y.values"))
這個ROC的面積也就是我們要計算的AUC值。
通過AUC的定義我們知道了AUC是什么,怎么算,但是它的意義是什么呢。如果從定義來理解AUC的含義,比較困難,實際上AUC和Mann–Whitney U test有密切的聯系,我會在第三部說明。從Mann–Whitney U statistic的角度來解釋,AUC就是從所有1樣本中隨機選取一個樣本, 從所有0樣本中隨機選取一個樣本,然后根據你的分類器對兩個隨機樣本進行預測,把1樣本預測為1的概率為p1,把0樣本預測為1的概率為p0,p1>p0的概率就等于AUC。所以AUC反應的是分類器對樣本的排序能力。根據這個解釋,如果我們完全隨機的對樣本分類,那么AUC應該接近0.5。另外值得注意的是,AUC對樣本類別是否均衡并不敏感,這也是不均衡樣本通常用AUC評價分類器性能的一個原因。
有朋友用python,下面代碼用于在python里面計算auc:
from sklearn import metrics
def aucfun(act,pred):
fpr, tpr, thresholds = metrics.roc_curve(act, pred, pos_label=1)
return metrics.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的另一個計算公式入手。也就是第一部分說的U statistic。
3.1 根據一個AUC值計算樣本中0,1的數目
根據AUC與U statistic的聯系,我們可以用下面的代碼計算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] 2 4 3 1 5
其中真實為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.0 4.0 4.0 1.5 1.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并不是上限,我有一些更好的結果,比較復雜,在這就不展開說了。