ROC(Receiver Operating Characteristic)曲線和AUC常被用來評價一個二值分類器(binary classifier)的優劣,對二者的簡單介紹見這裏。這篇博文簡單介紹ROC和AUC的特色,以及更爲深刻地,討論如何做出ROC曲線圖以及計算AUC。python
ROC曲線
須要提早說明的是,咱們這裏只討論二值分類器。對於分類器,或者說分類算法,評價指標主要有precision,recall,F-score1,以及咱們今天要討論的ROC和AUC。下圖是一個ROC曲線的示例2。git
正如咱們在這個ROC曲線的示例圖中看到的那樣,ROC曲線的橫座標爲false positive rate(FPR),縱座標爲true positive rate(TPR)。下圖中詳細說明了FPR和TPR是如何定義的。github
接下來咱們考慮ROC曲線圖中的四個點和一條線。第一個點,(0,1),即FPR=0, TPR=1,這意味着FN(false negative)=0,而且FP(false positive)=0。Wow,這是一個完美的分類器,它將全部的樣本都正確分類。第二個點,(1,0),即FPR=1,TPR=0,相似地分析能夠發現這是一個最糟糕的分類器,由於它成功避開了全部的正確答案。第三個點,(0,0),即FPR=TPR=0,即FP(false positive)=TP(true positive)=0,能夠發現該分類器預測全部的樣本都爲負樣本(negative)。相似的,第四個點(1,1),分類器實際上預測全部的樣本都爲正樣本。通過以上的分析,咱們能夠斷言,ROC曲線越接近左上角,該分類器的性能越好。算法
下面考慮ROC曲線圖中的虛線y=x上的點。這條對角線上的點其實表示的是一個採用隨機猜想策略的分類器的結果,例如(0.5,0.5),表示該分類器隨機對於一半的樣本猜想其爲正樣本,另一半的樣本爲負樣本。dom
如何畫ROC曲線
對於一個特定的分類器和測試數據集,顯然只能獲得一個分類結果,即一組FPR和TPR結果,而要獲得一個曲線,咱們實際上須要一系列FPR和TPR的值,這又是如何獲得的呢?咱們先來看一下Wikipedia上對ROC曲線的定義:函數
In signal detection theory, a receiver operating characteristic (ROC), or simply ROC curve, is a graphical plot which illustrates the performance of a binary classifier system as its discrimination threshold is varied.性能
問題在於「as its discrimination threashold is varied」。如何理解這裏的「discrimination threashold」呢?咱們忽略了分類器的一個重要功能「機率輸出」,即表示分類器認爲某個樣本具備多大的機率屬於正樣本(或負樣本)。經過更深刻地瞭解各個分類器的內部機理,咱們總能想辦法獲得一種機率輸出。一般來講,是將一個實數範圍經過某個變換映射到(0,1)區間3。測試
假如咱們已經獲得了全部樣本的機率輸出(屬於正樣本的機率),如今的問題是如何改變「discrimination threashold」?咱們根據每一個測試樣本屬於正樣本的機率值從大到小排序。下圖是一個示例,圖中共有20個測試樣本,「Class」一欄表示每一個測試樣本真正的標籤(p表示正樣本,n表示負樣本),「Score」表示每一個測試樣本屬於正樣本的機率4。ui
接下來,咱們從高到低,依次將「Score」值做爲閾值threshold,當測試樣本屬於正樣本的機率大於或等於這個threshold時,咱們認爲它爲正樣本,不然爲負樣本。舉例來講,對於圖中的第4個樣本,其「Score」值爲0.6,那麼樣本1,2,3,4都被認爲是正樣本,由於它們的「Score」值都大於等於0.6,而其餘樣本則都認爲是負樣本。每次選取一個不一樣的threshold,咱們就能夠獲得一組FPR和TPR,即ROC曲線上的一點。這樣一來,咱們一共獲得了20組FPR和TPR的值,將它們畫在ROC曲線的結果以下圖:spa
當咱們將threshold設置爲1和0時,分別能夠獲得ROC曲線上的(0,0)和(1,1)兩個點。將這些(FPR,TPR)對鏈接起來,就獲得了ROC曲線。當threshold取值越多,ROC曲線越平滑。
其實,咱們並不必定要獲得每一個測試樣本是正樣本的機率值,只要獲得這個分類器對該測試樣本的「評分值」便可(評分值並不必定在(0,1)區間)。評分越高,表示分類器越確定地認爲這個測試樣本是正樣本,並且同時使用各個評分值做爲threshold。我認爲將評分值轉化爲機率更易於理解一些。
AUC值的計算
AUC(Area Under Curve)被定義爲ROC曲線下的面積,顯然這個面積的數值不會大於1。又因爲ROC曲線通常都處於y=x這條直線的上方,因此AUC的取值範圍在0.5和1之間。使用AUC值做爲評價標準是由於不少時候ROC曲線並不能清晰的說明哪一個分類器的效果更好,而做爲一個數值,對應AUC更大的分類器效果更好。
在瞭解了ROC曲線的構造過程後,編寫代碼實現並非一件困難的事情。相比本身編寫代碼,有時候閱讀其餘人的代碼收穫更多,固然過程也更痛苦些。在此推薦scikit-learn中關於計算AUC的代碼。
AUC意味着什麼
那麼AUC值的含義是什麼呢?根據(Fawcett, 2006),AUC的值的含義是:
The AUC value is equivalent to the probability that a randomly chosen positive example is ranked higher than a randomly chosen negative example.
這句話有些繞,我嘗試解釋一下:首先AUC值是一個機率值,當你隨機挑選一個正樣本以及一個負樣本,當前的分類算法根據計算獲得的Score值將這個正樣本排在負樣本前面的機率就是AUC值。固然,AUC值越大,當前的分類算法越有可能將正樣本排在負樣本前面,即可以更好的分類。
爲何使用ROC曲線
既然已經這麼多評價標準,爲何還要使用ROC和AUC呢?由於ROC曲線有個很好的特性:當測試集中的正負樣本的分佈變化的時候,ROC曲線可以保持不變。在實際的數據集中常常會出現類不平衡(class imbalance)現象,即負樣本比正樣本多不少(或者相反),並且測試數據中的正負樣本的分佈也可能隨着時間變化。下圖是ROC曲線和Precision-Recall曲線5的對比:
在上圖中,(a)和(c)爲ROC曲線,(b)和(d)爲Precision-Recall曲線。(a)和(b)展現的是分類其在原始測試集(正負樣本分佈平衡)的結果,(c)和(d)是將測試集中負樣本的數量增長到原來的10倍後,分類器的結果。能夠明顯的看出,ROC曲線基本保持原貌,而Precision-Recall曲線則變化較大。
說明,文中除了第一張圖來自Wikipedia外,其餘的圖都來自論文(Fawcett, 2006)6截圖.
引用及其餘連接:
- 維基百科中對ROC的介紹: http://en.wikipedia.org/wiki/Receiver_operating_characteristic
- ROC曲線及AUC評價指標 by 冒泡的崔:http://bubblexc.com/y2011/148/
-
我避免將precision,recall等評價指標翻譯成中文,由於它們可能對應多箇中文解釋,極易產生混淆。 ↩
-
圖片來源:http://en.wikipedia.org/wiki/File:Roccurves.png ↩
-
這種映射不必定都是可靠的,即你不必定真的獲得了某個樣本是正樣本的機率。 ↩
-
注意這裏使用了「Score」,而不是機率,咱們暫且能夠認爲「Score」值就是是正樣本的機率。 ↩
-
Davis, J., & Goadrich, M. (2006, June). The relationship between Precision-Recall and ROC curves. In Proceedings of the 23rd international conference on Machine learning (pp. 233-240). ACM. ↩
-
(Fawcett, 2006),Fawcett, T. (2006). An introduction to ROC analysis. Pattern recognition letters, 27(8), 861-874. ↩
對於每個給定的閾值threshold,咱們均可以算出有關的TPR、FPR參數,這裏我寫了如下函數來實現該功能,函數的輸入有result和thres兩部分。前一部分是包含兩個array,第一個array用來存儲每個樣本是正樣本機率,第二個array則是每一個樣本的label屬性(0或1);後一部分則是選取的閾值,代碼實現原理同參考文獻中相同:
- def cal_rate(result, thres):
- all_number = len(result[0])
- # print all_number
- TP = 0
- FP = 0
- FN = 0
- TN = 0
- for item in range(all_number):
- disease = result[0][item]
- if disease >= thres:
- disease = 1
- if disease == 1:
- if result[1][item] == 1:
- TP += 1
- else:
- FP += 1
- else:
- if result[1][item] == 0:
- TN += 1
- else:
- FN += 1
- # print TP+FP+TN+FN
- accracy = float(TP+FP) / float(all_number)
- if TP+FP == 0:
- precision = 0
- else:
- precision = float(TP) / float(TP+FP)
- TPR = float(TP) / float(TP+FN)
- TNR = float(TN) / float(FP+TN)
- FNR = float(FN) / float(TP+FN)
- FPR = float(FP) / float(FP+TN)
- # print accracy, precision, TPR, TNR, FNR, FPR
- return accracy, precision, TPR, TNR, FNR, FPR
這只是對一個閾值進行的計算,要想設置連續的閾值計算,則須要對樣本正確率進行一個升序遍歷取值當閾值就能夠了:
- #prob是樣本正確率的array,label則是樣本label的array
- threshold_vaule = sorted(prob)
- threshold_num = len(threshold_vaule)
- accracy_array = np.zeros(threshold_num)
- precision_array = np.zeros(threshold_num)
- TPR_array = np.zeros(threshold_num)
- TNR_array = np.zeros(threshold_num)
- FNR_array = np.zeros(threshold_num)
- FPR_array = np.zeros(threshold_num)
- # calculate all the rates
- for thres in range(threshold_num):
- accracy, precision, TPR, TNR, FNR, FPR = cal_rate((prob,label), threshold_vaule[thres])
- accracy_array[thres] = accracy
- precision_array[thres] = precision
- TPR_array[thres] = TPR
- TNR_array[thres] = TNR
- FNR_array[thres] = FNR
- FPR_array[thres] = FPR
最後,利用計算公式根畫圖函數能夠畫出ROC曲線以及咱們用來評估模型的AUC和ERR參數。
- AUC = np.trapz(TPR_array, FPR_array)
- threshold = np.argmin(abs(FNR_array - FPR_array))
- EER = (FNR_array[threshold]+FPR_array[threshold])/2
- plt.plot(FPR_array, TPR_array)
- plt.title('roc')
- plt.xlabel('FPR_array')
- plt.ylabel('TPR_array')
- plt.show()
最後的最後,附上總的代碼。總的代碼和上面幾個有所不一樣,是我用來處理醫療數據多分類標籤的問題,可忽略~
- import numpy as np
- import cv2
- import os
- import matplotlib.pyplot as plt
- import re
- '''''
- calculate each rate
- '''
- def cal_rate(result, num, thres):
- all_number = len(result[0])
- # print all_number
- TP = 0
- FP = 0
- FN = 0
- TN = 0
- for item in range(all_number):
- disease = result[0][item,num]
- if disease >= thres:
- disease = 1
- if disease == 1:
- if result[1][item,num] == 1:
- TP += 1
- else:
- FP += 1
- else:
- if result[1][item,num] == 0:
- TN += 1
- else:
- FN += 1
- # print TP+FP+TN+FN
- accracy = float(TP+FP) / float(all_number)
- if TP+FP == 0:
- precision = 0
- else:
- precision = float(TP) / float(TP+FP)
- TPR = float(TP) / float(TP+FN)
- TNR = float(TN) / float(FP+TN)
- FNR = float(FN) / float(TP+FN)
- FPR = float(FP) / float(FP+TN)
- # print accracy, precision, TPR, TNR, FNR, FPR
- return accracy, precision, TPR, TNR, FNR, FPR
- disease_class = ['Atelectasis','Cardiomegaly','Effusion','Infiltration','Mass','Nodule','Pneumonia','Pneumothorax']
- style = ['r-','g-','b-','y-','r--','g--','b--','y--']
- '''''
- plot roc and calculate AUC/ERR, result: (prob, label)
- '''
- prob = np.random.rand(100,8)
- label = np.where(prob>=0.5,prob,0)
- label = np.where(label<0.5,label,1)
- count = np.count_nonzero(label)
- label = np.zeros((100,8))
- label[1:20,:]=1
- print label
- print prob
- print count
- for clss in range(len(disease_class)):
- threshold_vaule = sorted(prob[:,clss])
- threshold_num = len(threshold_vaule)
- accracy_array = np.zeros(threshold_num)
- precision_array = np.zeros(threshold_num)
- TPR_array = np.zeros(threshold_num)
- TNR_array = np.zeros(threshold_num)
- FNR_array = np.zeros(threshold_num)
- FPR_array = np.zeros(threshold_num)
- # calculate all the rates
- for thres in range(threshold_num):
- accracy, precision, TPR, TNR, FNR, FPR = cal_rate((prob,label), clss, threshold_vaule[thres])
- accracy_array[thres] = accracy
- precision_array[thres] = precision
- TPR_array[thres] = TPR
- TNR_array[thres] = TNR
- FNR_array[thres] = FNR
- FPR_array[thres] = FPR
- # print TPR_array
- # print FPR_array
- AUC = np.trapz(TPR_array, FPR_array)
- threshold = np.argmin(abs(FNR_array - FPR_array))
- EER = (FNR_array[threshold]+FPR_array[threshold])/2
- print ('disease %10s threshold : %f' % (disease_class[clss],threshold))
- print ('disease %10s accracy : %f' % (disease_class[clss],accracy_array[threshold]))
- print ('disease %10s EER : %f AUC : %f' % (disease_class[clss],EER, -AUC))
- plt.plot(FPR_array, TPR_array, style[clss], label=disease_class[clss])
- plt.title('roc')
- plt.xlabel('FPR_array')
- plt.ylabel('TPR_array')
- plt.legend()
- plt.show()