机器学习之类别不平衡问题 (2) —— ROC和PR曲线


机器学习之类别不平衡问题 (1) —— 各类评估指标

机器学习之类别不平衡问题 (2) —— ROC和PR曲线

机器学习之类别不平衡问题 (3) —— 采样方法

完整代码



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


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曲线能够看做是随着阈值的不断移动,全部样本中正例与负例之间的“对抗”。曲线越靠近左上角,意味着越多的正例优先于负例,模型的总体表现也就越好。python



AUC (Area Under the Curve)

先看一下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)






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




使用场景

  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. https://stats.stackexchange.com/questions/7207/roc-vs-precision-and-recall-curves

/

相关文章
相关标签/搜索