ROC 曲线和 PR(Precision - Recall)曲线皆为类别不平衡问题中常用的评估方法,二者既有相同也有不同点。本篇文章先给出 ROC 曲线的概述、实现方法、优缺点,再阐述 PR 曲线的各项特点,最后给出两种方法各自的使用场景。

ROC 曲线

[未看完]ROC和PR曲线 - 图1

ROC 曲线常用于二分类问题中的模型比较,主要表现为一种真正例率 (TPR) 和假正例率 (FPR) 的权衡。具体方法是在不同的分类阈值 (threshold) 设定下分别以 TPR 和 FPR 为纵、横轴作图。由 ROC 曲线的两个指标, [未看完]ROC和PR曲线 - 图2
[未看完]ROC和PR曲线 - 图3
可以看出,当一个样本被分类器判为正例,若其本身是正例,则 TPR 增加;若其本身是负例,则 FPR 增加,因此 ROC 曲线可以看作是随着阈值的不断移动,所有样本中正例与负例之间的 “对抗”。曲线越靠近左上角,意味着越多的正例优先于负例,模型的整体表现也就越好。

AUC (Area Under the Curve)

[未看完]ROC和PR曲线 - 图4

先看一下 ROC 曲线中的随机线,图中[0,0]到[1,1]的虚线即为随机线,该线上所有的点都表示该阈值下 TPR=FPR,根据定义, [未看完]ROC和PR曲线 - 图5
,表示所有正例中被预测为正例的概率; [未看完]ROC和PR曲线 - 图6
,表示所有负例中被被预测为正例的概率。若二者相等,意味着无论一个样本本身是正例还是负例,分类器预测其为正例的概率是一样的,这等同于随机猜测(注意这里的 “随机” 不是像抛硬币那样 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,即 [未看完]ROC和PR曲线 - 图7
; 如果该样本为负例,则 FP+1,即 [未看完]ROC和PR曲线 - 图8
。最后的到所有样本点的 TPR 和 FPR 值,用线段相连。

下面进行实现,先模拟生成一个正例:负例 = 10:1 的数据集,用 PCA 降到 2 维进行可视化:

  1. X,y = make_classification(n_samples=2000, n_features=10, n_informative=4,
  2. n_redundant=1, n_classes=2, n_clusters_per_class=1,
  3. weights=[0.9,0.1], flip_y=0.1, random_state=2018)
  4. sns.lmplot("pca_a","pca_b",data=X_pca, hue="y", fit_reg=False, markers=["o","x"],size=8,aspect=1.5,legend=False)
  5. plt.legend(fontsize=20,bbox_to_anchor=(0.98, 0.6),edgecolor ='r')
  6. plt.xlabel("axis_1",fontsize=17)
  7. plt.ylabel("axis_2",fontsize=17)

[未看完]ROC和PR曲线 - 图9

将数据分成训练集和测试集,使用 Logistic Regression 和 Random Forest 作图:

  1. kf = StratifiedKFold(n_splits=2, random_state=42)
  2. for train_index, test_index in kf.split(X,y):
  3. X_train, X_test = X[train_index], X[test_index]
  4. y_train, y_test = y[train_index], y[test_index]
  5. lr = LogisticRegression()
  6. lr.fit(X_train,y_train)
  7. pos_prob_lr = lr.predict_proba(X_test)[:,1] # Logistic Regression的正例预测概率
  8. rf = RandomForestClassifier(random_state=42)
  9. rf.fit(X_train,y_train)
  10. pos_prob_rf = rf.predict_proba(X_test)[:,1] # Random Forest的正例预测概率
  11. def get_roc(pos_prob,y_true):
  12. pos = y_true[y_true==1]
  13. neg = y_true[y_true==0]
  14. threshold = np.sort(pos_prob)[::-1] # 按概率大小逆序排列
  15. y = y_true[pos_prob.argsort()[::-1]]
  16. tpr_all = [0] ; fpr_all = [0]
  17. tpr = 0 ; fpr = 0
  18. x_step = 1/float(len(neg))
  19. y_step = 1/float(len(pos))
  20. y_sum = 0 # 用于计算AUC
  21. for i in range(len(threshold)):
  22. if y[i] == 1:
  23. tpr += y_step
  24. tpr_all.append(tpr)
  25. fpr_all.append(fpr)
  26. else:
  27. fpr += x_step
  28. fpr_all.append(fpr)
  29. tpr_all.append(tpr)
  30. y_sum += tpr
  31. return tpr_all,fpr_all,y_sum*x_step # 获得总体TPR,FPR和相应的AUC
  32. tpr_lr,fpr_lr,auc_lr = get_roc(pos_prob_lr,y_test)
  33. tpr_rf,fpr_rf,auc_rf = get_roc(pos_prob_rf,y_test)
  34. plt.figure(figsize=(10,6))
  35. plt.plot(fpr_lr,tpr_lr,label="Logistic Regression (AUC: {:.3f})".format(auc_lr),linewidth=2)
  36. plt.plot(fpr_rf,tpr_rf,'g',label="Random Forest (AUC: {:.3f})".format(auc_rf),linewidth=2)
  37. plt.xlabel("False Positive Rate",fontsize=16)
  38. plt.ylabel("True Positive Rate",fontsize=16)
  39. plt.title("ROC Curve",fontsize=16)
  40. plt.legend(loc="lower right",fontsize=16)

[未看完]ROC和PR曲线 - 图10

ROC 曲线的优点

放一张混淆矩阵图可能看得更清楚一点:

[未看完]ROC和PR曲线 - 图11

  1. 兼顾正例和负例的权衡。因为 TPR 聚焦于正例,FPR 聚焦于与负例,使其成为一个比较均衡的评估方法。
  2. ROC 曲线选用的两个指标, [未看完]ROC和PR曲线 - 图12
    [未看完]ROC和PR曲线 - 图13
    ,都不依赖于具体的类别分布。

注意 TPR 用到的 TP 和 FN 同属 P 列,FPR 用到的 FP 和 TN 同属 N 列,所以即使 P 或 N 的整体数量发生了改变,也不会影响到另一列。也就是说,即使正例与负例的比例发生了很大变化,ROC 曲线也不会产生大的变化,而像 Precision 使用的 TP 和 FP 就分属两列,则易受类别分布改变的影响。

参考文献 [1] 中举了个例子,负例增加了 10 倍,ROC 曲线没有改变,而 PR 曲线则变了很多。作者认为这是 ROC 曲线的优点,即具有鲁棒性,在类别分布发生明显改变的情况下依然能客观地识别出较好的分类器。

[未看完]ROC和PR曲线 - 图14

下面我们来验证一下是不是这样:

  1. 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]))
  2. y_test_dup = np.array(y_test.tolist() + y_test[y_test==0].tolist()*9) # 10x倍负例的测试集
  3. pos_prob_lr_dup = lr.predict_proba(X_test_dup)[:,1]
  4. pos_prob_rf_dup = rf.predict_proba(X_test_dup)[:,1]
  5. tpr_lr_dup,fpr_lr_dup,auc_lr_dup = get_roc(pos_prob_lr_dup,y_test_dup)
  6. tpr_rf_dup,fpr_rf_dup,auc_rf_dup = get_roc(pos_prob_rf_dup,y_test_dup)
  7. plt.figure(figsize=(10,6))
  8. plt.plot(fpr_lr_dup,tpr_lr_dup,label="Logistic Regression (AUC: {:.3f})".format(auc_lr_dup),linewidth=2)
  9. plt.plot(fpr_rf_dup,tpr_rf_dup,'g',label="Random Forest (AUC: {:.3f})".format(auc_rf_dup),linewidth=2)
  10. plt.xlabel("False Positive Rate",fontsize=16)
  11. plt.ylabel("True Positive Rate",fontsize=16)
  12. plt.title("ROC Curve",fontsize=16)
  13. plt.legend(loc="lower right",fontsize=16)

[未看完]ROC和PR曲线 - 图15

Logistic Regression 的曲线几乎和先前一模一样,但 Random Forest 的曲线却产生了很大变化。个中原因看一下两个分类器的预测概率就明白了:

  1. pos_prob_lr_dup[:20]
  2. array([0.15813023, 0.12075471, 0.02763748, 0.00983065, 0.06201179,
  3. 0.04986294, 0.09926128, 0.05632981, 0.15558692, 0.05856262,
  4. 0.08661055, 0.00787402, 0.1617371 , 0.04063957, 0.14103442,
  5. 0.07734239, 0.0213237 , 0.03968638, 0.03771455, 0.04874451])
  6. pos_prob_rf_dup[:20]
  7. array([0. , 0. , 0.1, 0.1, 0. , 0.1, 0.2, 0. , 0.1, 0.1, 0.1, 0. , 0. ,
  8. 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 曲线了:

  1. index = np.random.permutation(len(X_test_dup))
  2. X_test_dup = X_test_dup[index]
  3. y_test_dup = y_test_dup[index]

ROC 曲线的缺点

  1. 上文提到 ROC 曲线的优点是不会随着类别分布的改变而改变,但这在某种程度上也是其缺点。因为负例 N 增加了很多,而曲线却没变,这等于产生了大量 FP。像信息检索中如果主要关心正例的预测准确性的话,这就不可接受了。
  2. 在类别不平衡的背景下,负例的数目众多致使 FPR 的增长不明显,导致 ROC 曲线呈现一个过分乐观的效果估计。ROC 曲线的横轴采用 FPR,根据 FPR = [未看完]ROC和PR曲线 - 图16
    = [未看完]ROC和PR曲线 - 图17
    ,当负例 N 的数量远超正例 P 时,FP 的大幅增长只能换来 FPR 的微小改变。结果是虽然大量负例被错判成正例,在 ROC 曲线上却无法直观地看出来。(当然也可以只分析 ROC 曲线左边一小段)

举个例子,假设一个数据集有正例 20,负例 10000,开始时有 20 个负例被错判, [未看完]ROC和PR曲线 - 图18
,接着又有 20 个负例错判, [未看完]ROC和PR曲线 - 图19
,在 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 面积计算公式为:

[未看完]ROC和PR曲线 - 图20

下面仍使用上面的数据集画图:

  1. def get_pr(pos_prob,y_true):
  2. pos = y_true[y_true==1]
  3. threshold = np.sort(pos_prob)[::-1]
  4. y = y_true[pos_prob.argsort()[::-1]]
  5. recall = [] ; precision = []
  6. tp = 0 ; fp = 0
  7. auc = 0
  8. for i in range(len(threshold)):
  9. if y[i] == 1:
  10. tp += 1
  11. recall.append(tp/len(pos))
  12. precision.append(tp/(tp+fp))
  13. auc += (recall[i]-recall[i-1])*precision[i]
  14. else:
  15. fp += 1
  16. recall.append(tp/len(pos))
  17. precision.append(tp/(tp+fp))
  18. return precision,recall,auc
  19. precision_lr,recall_lr,auc_lr = get_pr(pos_prob_lr,y_test)
  20. precision_rf,recall_rf,auc_rf = get_pr(pos_prob_rf,y_test)
  21. plt.figure(figsize=(10,6))
  22. plt.plot(recall_lr,precision_lr,label="Logistic Regression (AUC: {:.3f})".format(auc_lr),linewidth=2)
  23. plt.plot(recall_rf,precision_rf,label="Random Forest (AUC: {:.3f})".format(auc_rf),linewidth=2)
  24. plt.xlabel("Recall",fontsize=16)
  25. plt.ylabel("Precision",fontsize=16)
  26. plt.title("Precision Recall Curve",fontsize=17)
  27. plt.legend(fontsize=16)

[未看完]ROC和PR曲线 - 图21

可以看到上文中 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 等指标,去调整模型的阈值,从而得到一个符合具体应用的模型。
    https://zhuanlan.zhihu.com/p/34655990
    https://zhuanlan.zhihu.com/p/34655990