ROC曲線和PR(Precision - Recall)曲線皆爲類別不平衡問題中經常使用的評估方法,兩者既有相同也有不一樣點。本篇文章先給出ROC曲線的概述、實現方法、優缺點,再闡述PR曲線的各項特色,最後給出兩種方法各自的使用場景。html
ROC曲線經常使用於二分類問題中的模型比較,主要表現爲一種真正例率 (TPR) 和假正例率 (FPR) 的權衡。具體方法是在不一樣的分類閾值 (threshold) 設定下分別以TPR和FPR爲縱、橫軸做圖。由ROC曲線的兩個指標,\(TPR = \frac{TP}{P} = \frac{TP}{TP+FN}\),\(FPR = \frac{FP}{N} = \frac{FP}{FP+TN}\) 能夠看出,當一個樣本被分類器判爲正例,若其自己是正例,則TPR增長;若其自己是負例,則FPR增長,所以ROC曲線能夠看做是隨着閾值的不斷移動,全部樣本中正例與負例之間的「對抗」。曲線越靠近左上角,意味着越多的正例優先於負例,模型的總體表現也就越好。python
先看一下ROC曲線中的隨機線,圖中[0,0]到[1,1]的虛線即爲隨機線,該線上全部的點都表示該閾值下TPR=FPR,根據定義,\(TPR = \frac{TP}{P}\),表示全部正例中被預測爲正例的機率;\(FPR = \frac{FP}{N}\),表示全部負例中被被預測爲正例的機率。若兩者相等,意味着不管一個樣本自己是正例仍是負例,分類器預測其爲正例的機率是同樣的,這等同於隨機猜想(注意這裏的「隨機」不是像拋硬幣那樣50%正面50%反面的那種隨機)。git
上圖中B點就是一個隨機點,不管是樣本數量和類別如何變化,始終將75%的樣本分爲正例。github
ROC曲線圍成的面積 (即AUC)能夠解讀爲:從全部正例中隨機選取一個樣本A,再從全部負例中隨機選取一個樣本B,分類器將A判爲正例的機率比將B判爲正例的機率大的可能性。能夠看到位於隨機線上方的點(如圖中的A點)被認爲好於隨機猜想。在這樣的點上TPR總大於FPR,意爲正例被判爲正例的機率大於負例被判爲正例的機率。app
從另外一個角度看,因爲畫ROC曲線時都是先將全部樣本按分類器的預測機率排序,因此AUC反映的是分類器對樣本的排序能力,依照上面的例子就是A排在B前面的機率。AUC越大,天然排序能力越好,即分類器將越多的正例排在負例以前。dom
ROC曲線的繪製方法:假設有P個正例,N個反例,首先拿到分類器對於每一個樣本預測爲正例的機率,根據機率對全部樣本進行逆序排列,而後將分類閾值設爲最大,即把全部樣本均預測爲反例,此時圖上的點爲 (0,0)。而後將分類閾值依次設爲每一個樣本的預測機率,即依次將每一個樣本劃分爲正例,若是該樣本爲真正例,則TP+1,即\(TPR + \frac{1}{P}\) ; 若是該樣本爲負例,則FP+1,即\(FPR + \frac{1}{N}\)。最後的到全部樣本點的TPR和FPR值,用線段相連。
下面進行實現,先模擬生成一個正例:負例=10:1的數據集,用PCA降到2維進行可視化:
機器學習
X,y = make_classification(n_samples=2000, n_features=10, n_informative=4, n_redundant=1, n_classes=2, n_clusters_per_class=1, weights=[0.9,0.1], flip_y=0.1, random_state=2018) sns.lmplot("pca_a","pca_b",data=X_pca, hue="y", fit_reg=False, markers=["o","x"],size=8,aspect=1.5,legend=False) plt.legend(fontsize=20,bbox_to_anchor=(0.98, 0.6),edgecolor ='r') plt.xlabel("axis_1",fontsize=17) plt.ylabel("axis_2",fontsize=17)
將數據分紅訓練集和測試集,使用Logistic Regression和Random Forest做圖:性能
kf = StratifiedKFold(n_splits=2, random_state=42) for train_index, test_index in kf.split(X,y): X_train, X_test = X[train_index], X[test_index] y_train, y_test = y[train_index], y[test_index] lr = LogisticRegression() lr.fit(X_train,y_train) pos_prob_lr = lr.predict_proba(X_test)[:,1] # Logistic Regression的正例預測機率 rf = RandomForestClassifier(random_state=42) rf.fit(X_train,y_train) pos_prob_rf = rf.predict_proba(X_test)[:,1] # Random Forest的正例預測機率 def get_roc(pos_prob,y_true): pos = y_true[y_true==1] neg = y_true[y_true==0] threshold = np.sort(pos_prob)[::-1] # 按機率大小逆序排列 y = y_true[pos_prob.argsort()[::-1]] tpr_all = [0] ; fpr_all = [0] tpr = 0 ; fpr = 0 x_step = 1/float(len(neg)) y_step = 1/float(len(pos)) y_sum = 0 # 用於計算AUC for i in range(len(threshold)): if y[i] == 1: tpr += y_step tpr_all.append(tpr) fpr_all.append(fpr) else: fpr += x_step fpr_all.append(fpr) tpr_all.append(tpr) y_sum += tpr return tpr_all,fpr_all,y_sum*x_step # 得到整體TPR,FPR和相應的AUC tpr_lr,fpr_lr,auc_lr = get_roc(pos_prob_lr,y_test) tpr_rf,fpr_rf,auc_rf = get_roc(pos_prob_rf,y_test) plt.figure(figsize=(10,6)) plt.plot(fpr_lr,tpr_lr,label="Logistic Regression (AUC: {:.3f})".format(auc_lr),linewidth=2) plt.plot(fpr_rf,tpr_rf,'g',label="Random Forest (AUC: {:.3f})".format(auc_rf),linewidth=2) plt.xlabel("False Positive Rate",fontsize=16) plt.ylabel("True Positive Rate",fontsize=16) plt.title("ROC Curve",fontsize=16) plt.legend(loc="lower right",fontsize=16)
放一張混淆矩陣圖可能看得更清楚一點 :
學習
兼顧正例和負例的權衡。由於TPR聚焦於正例,FPR聚焦於與負例,使其成爲一個比較均衡的評估方法。測試
ROC曲線選用的兩個指標,\(TPR = \frac{TP}{P} = \frac{TP}{TP+FN}\),\(FPR = \frac{FP}{N} = \frac{FP}{FP+TN}\),都不依賴於具體的類別分佈。
注意TPR用到的TP和FN同屬P列,FPR用到的FP和TN同屬N列,因此即便P或N的總體數量發生了改變,也不會影響到另外一列。也就是說,即便正例與負例的比例發生了很大變化,ROC曲線也不會產生大的變化,而像Precision使用的TP和FP就分屬兩列,則易受類別分佈改變的影響。
參考文獻 [1] 中舉了個例子,負例增長了10倍,ROC曲線沒有改變,而PR曲線則變了不少。做者認爲這是ROC曲線的優勢,即具備魯棒性,在類別分佈發生明顯改變的狀況下依然能客觀地識別出較好的分類器。
下面咱們來驗證一下是否是這樣:
X_test_dup = np.vstack((X_test,X_test[y_test==0],X_test[y_test==0],X_test[y_test==0],X_test[y_test==0],X_test[y_test==0],X_test[y_test==0],X_test[y_test==0],X_test[y_test==0],X_test[y_test==0])) y_test_dup = np.array(y_test.tolist() + y_test[y_test==0].tolist()*9) # 10x倍負例的測試集 pos_prob_lr_dup = lr.predict_proba(X_test_dup)[:,1] pos_prob_rf_dup = rf.predict_proba(X_test_dup)[:,1] tpr_lr_dup,fpr_lr_dup,auc_lr_dup = get_roc(pos_prob_lr_dup,y_test_dup) tpr_rf_dup,fpr_rf_dup,auc_rf_dup = get_roc(pos_prob_rf_dup,y_test_dup) plt.figure(figsize=(10,6)) plt.plot(fpr_lr_dup,tpr_lr_dup,label="Logistic Regression (AUC: {:.3f})".format(auc_lr_dup),linewidth=2) plt.plot(fpr_rf_dup,tpr_rf_dup,'g',label="Random Forest (AUC: {:.3f})".format(auc_rf_dup),linewidth=2) plt.xlabel("False Positive Rate",fontsize=16) plt.ylabel("True Positive Rate",fontsize=16) plt.title("ROC Curve",fontsize=16) plt.legend(loc="lower right",fontsize=16)
Logistic Regression的曲線幾乎和先前如出一轍,但Random Forest的曲線卻產生了很大變化。箇中緣由看一下兩個分類器的預測機率就明白了:
pos_prob_lr_dup[:20] array([0.15813023, 0.12075471, 0.02763748, 0.00983065, 0.06201179, 0.04986294, 0.09926128, 0.05632981, 0.15558692, 0.05856262, 0.08661055, 0.00787402, 0.1617371 , 0.04063957, 0.14103442, 0.07734239, 0.0213237 , 0.03968638, 0.03771455, 0.04874451])
pos_prob_rf_dup[:20] array([0. , 0. , 0.1, 0.1, 0. , 0.1, 0.2, 0. , 0.1, 0.1, 0.1, 0. , 0. , 0.2, 0. , 0. , 0.2, 0. , 0.1, 0. ])
能夠看到Logistic Regression的預測機率幾乎沒有重複,而Random Forest的預測機率則有不少重複,由於Logistic Regression能夠自然輸出機率,而Random Forest本質上屬於樹模型,只能輸出離散值。scikit-learn中樹模型的predict_proba() 方法表示的是一個葉節點上某一類別的樣本比例,但只顯示小數點後一位,導致大量樣本的預測機率都同樣。當畫ROC曲線時須要先將樣本根據預測機率排序,若幾個樣本的機率同樣,則只能按原來的順序排列。上面的操做就是將全部累加的負例都排在了原始數據後面,導致正例的順序都很靠前,形成Random Forest的結果好了很多。解決辦法就是將全部樣本隨機排序,就能產生和原來差很少的ROC曲線了:
index = np.random.permutation(len(X_test_dup)) X_test_dup = X_test_dup[index] y_test_dup = y_test_dup[index]
上文提到ROC曲線的優勢是不會隨着類別分佈的改變而改變,但這在某種程度上也是其缺點。由於負例N增長了不少,而曲線卻沒變,這等於產生了大量FP。像信息檢索中若是主要關心正例的預測準確性的話,這就不可接受了。
在類別不平衡的背景下,負例的數目衆多導致FPR的增加不明顯,致使ROC曲線呈現一個過度樂觀的效果估計。ROC曲線的橫軸採用FPR,根據FPR = \(\frac{FP}{N}\) = \(\frac{FP}{FP+TN}\),當負例N的數量遠超正例P時,FP的大幅增加只能換來FPR的微小改變。結果是雖然大量負例被錯判成正例,在ROC曲線上卻沒法直觀地看出來。(固然也能夠只分析ROC曲線左邊一小段)
舉個例子,假設一個數據集有正例20,負例10000,開始時有20個負例被錯判,\(FPR = \frac{20}{20+9980} = 0.002\),接着又有20個負例錯判,\(FPR_{2} = \frac{40}{40+9960} =0.004\),在ROC曲線上這個變化是很細微的。而與此同時Precision則從原來的0.5降低到了0.33,在PR曲線上將會是一個大幅降低。
PR曲線展現的是Precision vs Recall的曲線,PR曲線與ROC曲線的相同點是都採用了TPR (Recall),均可以用AUC來衡量分類器的效果。不一樣點是ROC曲線使用了FPR,而PR曲線使用了Precision,所以PR曲線的兩個指標都聚焦於正例。類別不平衡問題中因爲主要關心正例,因此在此狀況下PR曲線被普遍認爲優於ROC曲線。
PR曲線的繪製與ROC曲線相似,PR曲線的AUC面積計算公式爲: \[\sum_{n}(R_n-R_{n-1})P_n\]
下面仍使用上面的數據集畫圖:
def get_pr(pos_prob,y_true): pos = y_true[y_true==1] threshold = np.sort(pos_prob)[::-1] y = y_true[pos_prob.argsort()[::-1]] recall = [] ; precision = [] tp = 0 ; fp = 0 auc = 0 for i in range(len(threshold)): if y[i] == 1: tp += 1 recall.append(tp/len(pos)) precision.append(tp/(tp+fp)) auc += (recall[i]-recall[i-1])*precision[i] else: fp += 1 recall.append(tp/len(pos)) precision.append(tp/(tp+fp)) return precision,recall,auc precision_lr,recall_lr,auc_lr = get_pr(pos_prob_lr,y_test) precision_rf,recall_rf,auc_rf = get_pr(pos_prob_rf,y_test) plt.figure(figsize=(10,6)) plt.plot(recall_lr,precision_lr,label="Logistic Regression (AUC: {:.3f})".format(auc_lr),linewidth=2) plt.plot(recall_rf,precision_rf,label="Random Forest (AUC: {:.3f})".format(auc_rf),linewidth=2) plt.xlabel("Recall",fontsize=16) plt.ylabel("Precision",fontsize=16) plt.title("Precision Recall Curve",fontsize=17) plt.legend(fontsize=16)
能夠看到上文中ROC曲線下的AUC面積在0.8左右,而PR曲線下的AUC面積在0.68左右,類別不平衡問題中ROC曲線確實會做出一個比較樂觀的估計,而PR曲線則由於Precision的存在會不斷顯現FP的影響。
若是有多份數據且存在不一樣的類別分佈,好比信用卡欺詐問題中每月正例和負例的比例可能都不相同,這時候若是隻想單純地比較分類器的性能且剔除類別分佈改變的影響,則ROC曲線比較適合,由於類別分佈改變可能使得PR曲線發生變化時好時壞,這種時候難以進行模型比較;反之,若是想測試不一樣類別分佈下對分類器的性能的影響,則PR曲線比較適合。
若是想要評估在相同的類別分佈下正例的預測狀況,則宜選PR曲線。
類別不平衡問題中,ROC曲線一般會給出一個樂觀的效果估計,因此大部分時候仍是PR曲線更好。
最後能夠根據具體的應用,在曲線上找到最優的點,獲得相對應的precision,recall,f1 score等指標,去調整模型的閾值,從而獲得一個符合具體應用的模型。
/