標籤:

機器學習之類別不平衡問題 (2) —— ROC和PR曲線

完整代碼

ROC曲線和PR(Precision - Recall)曲線皆為類別不平衡問題中常用的評估方法,二者既有相同也有不同點。本篇文章先給出ROC曲線的概述、實現方法、優缺點,再闡述PR曲線的各項特點,最後給出兩種方法各自的使用場景。

ROC曲線

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曲線可以看作是隨著閾值的不斷移動,所有樣本中正例與負例之間的「對抗」。曲線越靠近左上角,意味著越多的正例優先於負例,模型的整體表現也就越好。

AUC (Area Under the Curve)

先看一下ROC曲線中的隨機線,圖中[0,0]到[1,1]的虛線即為隨機線,該線上所有的點都表示該閾值下TPR=FPR,根據定義, TPR = frac{TP}{P} ,表示所有正例中被預測為正例的概率; FPR = frac{FP}{N} ,表示所有負例中被被預測為正例的概率。若二者相等,意味著無論一個樣本本身是正例還是負例,分類器預測其為正例的概率是一樣的,這等同於隨機猜測(注意這裡的「隨機」不是像拋硬幣那樣50%正面50%反面的那種隨機)。

上圖中B點就是一個隨機點,無論是樣本數量和類別如何變化,始終將75%的樣本分為正例。

ROC曲線圍成的面積 (即AUC)可以解讀為:從所有正例中隨機選取一個樣本A,再從所有負例中隨機選取一個樣本B,分類器將A判為正例的概率比將B判為正例的概率大的可能性。可以看到位於隨機線上方的點(如圖中的A點)被認為好於隨機猜測。在這樣的點上TPR總大於FPR,意為正例被判為正例的概率大於負例被判為正例的概率。

從另一個角度看,由於畫ROC曲線時都是先將所有樣本按分類器的預測概率排序,所以AUC反映的是分類器對樣本的排序能力,依照上面的例子就是A排在B前面的概率。AUC越大,自然排序能力越好,即分類器將越多的正例排在負例之前。

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和相應的AUCtpr_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)

ROC曲線的優點

放一張混淆矩陣圖可能看得更清楚一點:

  1. 兼顧正例和負例的權衡。因為TPR聚焦於正例,FPR聚焦於與負例,使其成為一個比較均衡的評估方法。
  2. 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曲線的缺點

  1. 上文提到ROC曲線的優點是不會隨著類別分布的改變而改變,但這在某種程度上也是其缺點。因為負例N增加了很多,而曲線卻沒變,這等於產生了大量FP。像信息檢索中如果主要關心正例的預測準確性的話,這就不可接受了。
  2. 在類別不平衡的背景下,負例的數目眾多致使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 Recall) 曲線

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,aucprecision_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的影響。

使用場景

  1. ROC曲線由於兼顧正例與負例,所以適用於評估分類器的整體性能,相比而言PR曲線完全聚焦於正例。
  2. 如果有多份數據且存在不同的類別分布,比如信用卡欺詐問題中每個月正例和負例的比例可能都不相同,這時候如果只想單純地比較分類器的性能且剔除類別分布改變的影響,則ROC曲線比較適合,因為類別分布改變可能使得PR曲線發生變化時好時壞,這種時候難以進行模型比較;反之,如果想測試不同類別分布下對分類器的性能的影響,則PR曲線比較適合。
  3. 如果想要評估在相同的類別分布下正例的預測情況,則宜選PR曲線。
  4. 類別不平衡問題中,ROC曲線通常會給出一個樂觀的效果估計,所以大部分時候還是PR曲線更好。
  5. 最後可以根據具體的應用,在曲線上找到最優的點,得到相對應的precision,recall,f1 score等指標,去調整模型的閾值,從而得到一個符合具體應用的模型。

Reference:

  1. Tom Fawcett. An introduction to ROC analysis
  2. Jesse Davis, Mark Goadrich0. The Relationship Between Precision-Recall and ROC Curves
  3. Haibo He, Edwardo A. Garcia. Learning from Imbalanced Data
  4. 周志華. 《機器學習》
  5. Pang-Ning Tan, etc. Introduction to Data Mining
  6. stats.stackexchange.com

推薦閱讀:

[貝葉斯一]之貝葉斯定理
1-3 What is Machine Learning
Fenchel-Lengendre Duality觀點下的優化演算法們(I):前言
談談機器學習與數據分析中的問題定義

TAG:機器學習 |