详解支持向量机-基于SVM的ROC曲线和AUC面积【菜菜的sklearn课堂笔记】

时间:2022-11-30 11:04:27

视频作者:[菜菜TsaiTsai] 链接:[【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili]

手动绘制SVM的ROC曲线

对于ROC曲线,我们要注意的是正类的概率和阈值

我们理解了什么是阈值(threshold),了解了不同阈值会让混淆矩阵产生变化,也了解了如何从我们的分类算法中获取概率。ROC是一条以不同阈值下的假正率FPR为横坐标,不同阈值下的召回率Recall为纵坐标的曲线。简单地来说,只要我们有数据和模型,我们就可以在python中绘制出我们的ROC曲线。

先用之前的回归案例,提取FPR和Recall

cm = CM(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
cm
---
array([[4, 0],
       [2, 5]], dtype=int64)

# FPR
cm[1,0]/cm[1,:].sum()
---
0.2857142857142857

# Recall
cm[0,0]/cm[0,:].sum()
---
1.0

换到SVM上

# 概率 clf.predict_proba(X)[:,1] 类别1下的概率,因为我们一开始生成数据的时候少数类为1
# 阈值 基于概率的最大最小值,阈值就是概率的最大最小值之前的多个值,每一个阈值都对应着一次循环,每一次循环,都有一个混淆矩阵,要有一组假正率和召回率
recall = []
FPR = []
probrange = np.linspace(clf.predict_proba(X)[:,1].min(),clf.predict_proba(X)[:,1].max(),endpoint=False)
# 设置endpoint=False,不取到最大值(我感觉没啥影响,因为0也不再分母上)

for i in probrange: # 概率从最小值到最大值,也就是阈值的取值
    y_predict = []
    for j in range(X.shape[0]): # 样本从第一个到最后一个
        if clf.predict_proba(X)[j,1] > i:
            y_predict.append(1)
        else:
            y_predict.append(0)
    cm = CM(y,y_predict,labels=[1,0])
    recall.append(cm[0,0]/cm[0,:].sum())
    FPR.append(cm[1,0]/cm[1,:].sum())

FPR
# 一开始假正率是最大值,因为一开始阈值很小,也就是说即使有极小的概率这个样本是少数类,我们也认为它是少数类,因此有很多可能只有1%概率的样本因为阈值的限定被认定为少数类,导致假正率极大
# 到最后阈值极大,判定条件非常严格,正好与上面相反,因此假正率也就接近甚至达到0%
---
[0.998,
 0.302,
 0.206,
 0.166,
 0.148,
 ……
 0.006,
 0.002,
 0.0,
 0.0,
 0.0] 

plt.plot(list(reversed(FPR)),list(reversed(recall)),c='red')
# 这里FPR和recall列表是否翻转都可以,结果一样
plt.plot(probrange+0.05,probrange+0.05,c='k',linestyle='--')
plt.show()
# 选择x值小,y值大的位置
# 也就是假正率小,召回率大
# 也就是认定的少数类大多确实是真实地少数类,多数少数类被召回

详解支持向量机-基于SVM的ROC曲线和AUC面积【菜菜的sklearn课堂笔记】

之前我们说,通常来说,降低阈值能够升高Recall,有通常二字。 看上面这个图,显然是不平滑的,如果之前linspace切片数量进一步减少,甚至会出现下图的情况 详解支持向量机-基于SVM的ROC曲线和AUC面积【菜菜的sklearn课堂笔记】 根据我们之前生成的数据集来解释一下,为什么线是直上直下的,为了方便解释,我们这里的阈值使用距离也就是decision_function来衡量置信度 详解支持向量机-基于SVM的ROC曲线和AUC面积【菜菜的sklearn课堂笔记】 假设现在有一条超平面在红紫交界处,方向从左上到右下,我们向左下移动一点,假设只有一个样本点从超平面右侧移动到左侧

  • 如果说这个点是紫色点,那么受影响的是FPR,分母不变,分子变大,但此时recall不变,呈现在ROC曲线上就是向右的一小段线段
  • 如果说这个点是红色点,那么受影响的是recall,分母不变,分子变大,但此时FPR不变,呈现在ROC曲线上就是向上的一小段线段

这也就导致了即使我们移动了阈值,recall不一定升高,可能不变

图一作者:hedgehog小子 链接:ROC曲线与AUC - hedgehog小子 - 博客园 (cnblogs.com)

横坐标是FPR,代表着模型将多数类判断错误的能力,纵坐标Recall,代表着模型捕捉少数类的能力,所以ROC曲线代表着,随着Recall的不断增加,FPR如何增加。我们希望随着Recall的不断提升,FPR增加得越慢越好,这说明我们可以尽量高效地捕捉出少数类,而不会将很多地多数类判断错误。所以,我们希望看到的图像是,纵坐标急速上升,横坐标缓慢增长,也就是在整个图像左上方的一条弧线。这代表模型的效果很不错,拥有较好的捕获少数类的能力。 中间的虚线代表着,当recall增加1%,我们的FPR也增加1%,也就是说,我们每捕捉出一个少数类,就会有一个多数类被判错。 ROC曲线通常都是凸型的。对于一条凸型ROC曲线来说,曲线越靠近左上角越好,越往下越糟糕,曲线如果在虚线的下方,则证明模型完全无法使用。但是它也有可能是一条凹形的ROC曲线。对于一条凹型ROC曲线来说,应该越靠近右下角越好,凹形曲线代表模型的预测结果与真实情况完全相反,那也不算非常糟糕,只要我们手动将模型的结果逆转,就可以得到一条左上方的弧线了。最糟糕的就是,无论曲线是凹形还是凸型,曲线位于图像中间,和虚线非常靠近,那我们拿它无能为力

sklearn中的ROC曲线和AUC面积

上面我们是手动计算ROC曲线上的点然后连线得到的,这里我们使用sklearn中包装好的方法来实现。能明显的感觉到,使用起来更便利,速度比之前更快

roc_curve(
    ['y_true', 'y_score', 'pos_label=None', 'sample_weight=None', 'drop_intermediate=True'],
)
# y_true:真实标签
# y_score:置信度分数,可以是正类样本的概率值,或置信度分数,或者decision_function返回的距离
# pos_label:整数或者字符串,表示被认为是正类样本的类别
# drop_intermediate:布尔值,默认True,如果设置为True,表示会舍弃一些ROC曲线上不显示的阈值点,这对于计算一个比较轻量的ROC曲线来说非常有用
# 注意这个类返回:FPR,Recall,阈值。
from sklearn.metrics import roc_curve

FPR,recall,thresholds = roc_curve(y,clf.decision_function(X),pos_label=1)

FPR.shape
---
(45,)

recall.shape
---
(45,)

thresholds.shape # 此时的threshold不是一个概率值,而是距离的阈值,可正可负
---
(45,)

thresholds
---
array([  3.18236076,   2.18236076,   1.48676267,   1.35964325,
	     0.88438995,  -0.91257798,  -1.01417607,  -1.08601917,
        -10.31959605])

判断ROC的好坏可以通过AUC面积来判定

AUC面积是可以看做ROC曲线在$(0,1)$上的积分,面积越大越接近1越好(根据之前的理论,越小越接近0也可以越好,只要将模型翻转即可)

roc_auc_score(y_true, y_score, average='macro', sample_weight=None, max_fpr=None)
# y_true:真实标签
# y_score:置信度分数,可以是正类样本的概率值,或置信度分数,或者decision_function返回的距离
area = AUC(y,clf.decision_function(X))
area
---
0.9696400000000001

画图

plt.figure()
plt.plot(FPR,recall,color='red'
        ,label='ROC curve (area = %.2f)'%area)
plt.plot([0,1],[0,1],color='k',linestyle='--')
plt.xlim([-0.05,1.05])
plt.ylim([-0.05,1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('Recall')
plt.legend(loc='lower right')
plt.show()

详解支持向量机-基于SVM的ROC曲线和AUC面积【菜菜的sklearn课堂笔记】

如此就得到了我们的ROC曲线和AUC面积,可以看到,SVM在这个简单数据集上的效果还是非常好的。并且大家可以通过观察我们使用decision_function画出的ROC曲线,对比一下我们之前强行使用概率画出来的曲线,两者非常相似,所以在无法获取模型概率的情况下,其实不必强行使用概率,如果有置信度,那也使可以完成我们的ROC曲线的

利用ROC曲线找出最佳阈值

ROC曲线反应的是recall增加的时候FPR如何变化。我们的希望是,模型在捕获少数类的能力变强的时候,尽量不误伤多数类,也就是说,随着recall的变大,FPR的大小越小越好(同一模型下这是不可能的)。所以我们希望找到的最佳的阈值,其实是Recall和FPR差距最大的点。这个点,又叫做约登指数

recall - FPR
---
array([0.   , 0.02 , 0.014, 0.054, 0.052, 0.152, 0.15 , 0.19 , 0.186,
	   ……
	   0.814, 0.854, 0.848, 0.868, 0.866, 0.886, 0.874, 0.914, 0.   ])

maxindex = (recall - FPR).tolist().index(max(recall - FPR))
maxindex
---
43

thresholds[maxindex] # 用decision_function生成的置信度来说
---
-1.0860191749391461

plt.figure()
plt.plot(FPR,recall,color='red'
        ,label='ROC curve (area = %.2f)'%area)
plt.plot([0,1],[0,1],color='k',linestyle='--')
plt.scatter(FPR[maxindex],recall[maxindex],s=50,color='k')
# 画出最佳阈值,也就是根据约登指数得到的点
plt.xlim([-0.05,1.05])
plt.ylim([-0.05,1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('Recall')
plt.legend(loc='lower right')
plt.show()

详解支持向量机-基于SVM的ROC曲线和AUC面积【菜菜的sklearn课堂笔记】

这个点,其实是图像上离左上角最近的点,离中间的虚线最远的点,也是ROC曲线的转折点。如果没有时间进行计算,或者横坐标比较清晰的时候,我们就可以观察转折点来找到我们的最佳阈值。

到这里为止,SVC的模型评估指标就介绍完毕了。但是,SVC的样本不均衡问题还可以有很多的探索。另外,我们还可以使用KS曲线,或者收益曲线(profit chart)来选择我们的阈值,都是和ROC曲线类似的用法。大家若有余力,可以自己深入研究一下。模型评估指标,还有很多深奥的地方。