1. 程式人生 > >分類模型的評價之AUC

分類模型的評價之AUC

關於AUC,想寫的東西有點多,本來計劃分3篇文章來寫完,但是微信公眾平臺每天只能發一篇文章,等不及了,所以先寫一篇,儘量把想寫的都寫出來,以後有需要再補充。

這篇文章分三部分,第一部分是對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的角度來解釋,從所有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([email protected])
}
#初始值
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演算法(感興趣可以參見http://papers.nips.cc/paper/2518-auc-optimization-vs-error-rate-minimization.pdf)。
對於最近十分火熱的xgboost包,也提供了直接優化AUC的功能,使用起來很簡單,只要把目標函式設定為:
objective = 'rank:pairwise'

3. 從AUC到真實類別(label)?
最開始思考這個問題是做一個網上的比賽(pkbigdata.com/common/competition/148.html),二分類問題,每次提交一組預測系統會計算出一個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*(-2018)+y13*(4095)


上面的方程裡面yi代表樣本i的真實label,有且只有唯一解,以為這個方程本質上就是10進位制數用2進製表達。所以通過兩次auc計算,我們可以找到13個點的真實標籤。比如對上面提到的貸款預測比賽,選定前13個label,auc1=0.50220853,auc2= 0.5017588,然後就可以算出來前13個test樣本只有第三個樣本是0,其餘都是1。

但是13並不是上限,我有一些更好的結果,比較複雜,在這篇文章就不展開說了。

原文轉自: http://mp.weixin.qq.com/s?__biz=MzI4MTE1MzQwMg==&mid=404000626&idx=1&sn=71dbbfd48ec28f909ed84adb542e1216#rd