写点什么

支持向量机 -ROC 曲线中的概率和阈值

  • 2022-11-29
    山东
  • 本文字数:4521 字

    阅读完需:约 15 分钟

基于混淆矩阵,我们学习了总共六个指标:准确率 Accuracy,精确度 Precision,召回率 Recall,精确度和召回度的平衡指标 F measure,特异度 Specificity,以及假正率 FPR。其中,假正率有一个非常重要的应用:我们在追求较高的 Recall 的时候,Precision 会下降,就是说随着更多的少数类被捕捉出来,会有更多的多数类被判断错误,但我们很好奇,随着 Recall 的逐渐增加,模型将多数类判断错误的能力如何变化呢?我们希望理解,我每判断正确一个少数类,就有多少个多数类会被判断错误。假正率正好可以帮助我们衡量这个能力的变化。相对的,Precision 无法判断这些判断错误的多数类在全部多数类中究竟占多大的比例,所以无法在提升 Recall 的过程中也顾及到模型整体的 Accuracy。因此,我们可以使用 Recall 和 FPR 之间的平衡,来替代 Recall 和 Precision 之间的平衡,让我们衡量模型在尽量捕捉少数类的时候,误伤多数类的情况如何变化,这就是我们的 ROC 曲线衡量的平衡。ROC 曲线,全称 The Receiver Operating Characteristic Curve,译为受试者操作特性曲线。这是一条以不同阈值下的假正率 FPR 为横坐标,不同阈值下的召回率 Recall 为纵坐标的曲线

概率(probability)与阈值(threshold)

要理解概率与阈值,最容易的状况是来回忆一下我们用逻辑回归做分类的时候的状况。逻辑回归的 predict_proba 接口对每个样本生成每个标签类别下的似然(类概率)。对于这些似然,逻辑回归天然规定,当一个样本所对应的这个标签类别下的似然大于 0.5 的时候,这个样本就被分为这一类。比如说,一个样本在标签 1 下的似然是 0.6,在标签 0 下的似然是 0.4,则这个样本的标签自然就被分为 1。逻辑回归的回归值本身,其实也就是标签 1 下的似然。在这个过程中,0.5 就被称为阈值。来看看下面的例子:


from sklearn.datasets import make_blobsimport matplotlib.pyplot as pltimport numpy as np
class_1 = 7class_2 = 4centers = [[0.0,0.0],[1,1]]clusters_std = [0.5,1]X, y = make_blobs(n_samples=[class_1,class_2] ,centers=centers ,cluster_std=clusters_std ,random_state=0 ,shuffle=False )
plt.scatter(X[:,0],X[:,1],c=y,cmap='rainbow',s=30)
复制代码



from sklearn.linear_model import LogisticRegression as LogiR
clf_lo = LogiR().fit(X,y)prob = clf_lo.predict_proba(X)prob,prob.shape # 返回11个样本属于类别0和类别1的概率(似然)# 谁的概率更大,就认为样本点是哪一类---(array([[0.60466356, 0.39533644], [0.45891589, 0.54108411], [0.71798647, 0.28201353], [0.67910911, 0.32089089], [0.66473898, 0.33526102], [0.56277457, 0.43722543], [0.66205409, 0.33794591], [0.35516738, 0.64483262], [0.38160618, 0.61839382], [0.58528378, 0.41471622], [0.50149311, 0.49850689]]), (11, 2))
import pandas as pd
prob = pd.DataFrame(prob,columns=['0','1'])prob # 往后只写前五行,有必要的话展示全部--- 0 10 0.604664 0.3953361 0.458916 0.5410842 0.717986 0.2820143 0.679109 0.3208914 0.664739 0.335261
for i in range(prob.shape[0]): if prob.loc[i,'1'] > 0.5: # 这里0.5就是我们设定的阈值 prob.loc[i,'pred'] = 1 else: prob.loc[i,'pred'] = 0prob--- 0 1 pred0 0.604664 0.395336 0.01 0.458916 0.541084 1.02 0.717986 0.282014 0.03 0.679109 0.320891 0.04 0.664739 0.335261 0.0
prob['y_true'] = yprob = prob.sort_values(by='1',ascending=False)# 按照'1'列数字大小排序,使用非升序(也就是降序)prob--- 0 1 pred y_true7 0.355167 0.644833 1.0 18 0.381606 0.618394 1.0 11 0.458916 0.541084 1.0 010 0.501493 0.498507 0.0 15 0.562775 0.437225 0.0 0# 得到一组pred和y_true就可以生成一个混淆矩阵,从而得到Recall和FPR,进而得到ROC曲线上的一个点# 但下面我们先计算precision和recall,说明阈值变化的一个问题
复制代码


confusion_matrix(y_true, y_pred, labels=None, sample_weight=None)# y_true:样本的真实标签,也就是上面的y_True# y_pred:样本指定模型和阈值下的预测标签,也就是上面的pred# labels:说明需要哪些类,注意,少数类在前
precision_score( ['y_true', 'y_pred', 'labels=None', 'pos_label=1', "average='binary'", 'sample_weight=None'],)# 同confusion_matrix
recall_score( ['y_true', 'y_pred', 'labels=None', 'pos_label=1', "average='binary'", 'sample_weight=None'],)# 同confusion_matrix
复制代码


from sklearn.metrics import confusion_matrix as CM, precision_score as P, recall_score as R
CM(prob.loc[:,'y_true'],prob.loc[:,'pred'],labels=[1,0])---array([[2, 2], [1, 6]], dtype=int64)
复制代码


通过这个表格我们可以先自己计算一下



用代码验证一下


P(prob.loc[:,'y_true'],prob.loc[:,'pred'],labels=[1,0])---0.6666666666666666
R(prob.loc[:,'y_true'],prob.loc[:,'pred'],labels=[1,0])---0.5
复制代码


改变阈值,观察 precision 和 recall 的变化


for i in range(prob.shape[0]):    if prob.loc[i,'1'] > 0.4:        prob.loc[i,'pred'] = 1    else:        prob.loc[i,'pred'] = 0
prob--- 0 1 pred y_true7 0.355167 0.644833 1.0 18 0.381606 0.618394 1.0 11 0.458916 0.541084 1.0 010 0.501493 0.498507 1.0 15 0.562775 0.437225 1.0 0
CM(prob.loc[:,'y_true'],prob.loc[:,'pred'],labels=[1,0])---array([[4, 0], [2, 5]], dtype=int64)# 显然我们的混淆矩阵发生了变化
P(prob.loc[:,'y_true'],prob.loc[:,'pred'],labels=[1,0])---0.6666666666666666# 但精确度没有发生变化,因此阈值变化不一定让参数单调变化# 因此这个参数绘制曲线可能会导致不单调,不好研究最优值# 因此之后我们换成假正率FPR
R(prob.loc[:,'y_true'],prob.loc[:,'pred'],labels=[1,0]) # 完全捕获少数类---1.0
复制代码


注意,降低或者升高阈值并不一定能够让模型的效果一定变好,一切基于我们要追求怎样的模型效果。通常来说,降低阈值能够升高 Recall 而要体现阈值的影响,首先必须的得到分类器在少数类下的预测概率。对于逻辑回归这样天生生成似然的算法和朴素贝叶斯这样就是在计算概率的算法,自然非常容易得到概率,但对于一些其他的分类算法,比如决策树,比如 SVM,他们的分类方式和概率并不相关。那在他们身上,我们就无法画 ROC 曲线了吗?并非如此。


决策树有叶子节点,一个叶子节点上可能包含着不同类的样本。假设一个样本被包含在叶子节点 a 中,节点 a 包含 10 个样本,其中 6 个为 1,4 个为 0,则 1 这个正类在这个叶子节点中的出现概率就是 60%,类别 0 在这个叶子节点中的出现概率就是 40%。对于所有在这个叶子节点中的样本而言,节点上的 1 和 0 出现的概率,就是这个样本对应的取到 1 和 0 的概率,大家可以去自己验证一下。但是思考一个问题,由于决策树可以被画得很深,在足够深的情况下,决策树的每个叶子节点上可能都不包含多个类别的标签了,可能一片叶子中只有唯一的一个标签,即叶子节点的不纯度为 0,此时此刻,对于每个样本而言,他们所对应的“概率”就是 0 或者 1 了。这个时候,我们就无法调节阈值来调节我们的 Recall 和 FPR 了。对于随机森林,也是如此。


不过其实,SVM 也可以生成概率

SVM 实现概率预测:重要参数 probability,接口 predict_proba 以及 decision_function

我们在画等高线,也就是决策边界的时候曾经使用 SVC 的接口 decision_function,它返回我们输入的特征矩阵中每个样本到决策边界的距离。我们在 SVM 中利用决策边界来判断我们的样本,本质上来说,当两个点的距离是相同的符号的时候,越远离超平面的样本点归属于某个标签类的概率就很大。比如说,一个距离超平面 0.1 的点,和一个距离超平面 100 的点,明显是距离为 0.1 的点更有可能是负类别的点混入了边界。同理,一个距离超平面距离为-0.1 的点,和一个离超平面距离为-100 的点,明显是-100 的点的标签更有可能是负类。所以,到超平面的距离一定程度上反应了样本归属于某个标签类的可能性。接口 decision_function 返回的值也因此被我们认为是 SVM 中的置信度(confidence)。不过,置信度始终不是概率,它没有边界,可以无限大,大部分时候也不是以百分比或者小数的形式呈现,而 SVC 的判断过程又不像决策树一样可以求解出一个比例。为了解决这个矛盾,SVC 有重要参数 probability。


SVC(    ['C=1.0', "kernel='rbf'", 'degree=3', "gamma='auto_deprecated'", 'coef0=0.0', 'shrinking=True', 'probability=False', 'tol=0.001', 'cache_size=200', 'class_weight=None', 'verbose=False', 'max_iter=-1', "decision_function_shape='ovr'", 'random_state=None'],)# 如果我们想要使用概率接口predict_proba和predict_log_proba,就要在实例化模型的时候设定probability=True# 启用此功能会减慢SVM的运算速度# 在二分类情况下,SVC将使用Platt缩放来生成概率,即在decision_function生成的距离上进行Sigmoid压缩,并附加训练数据的交叉验证拟合,来生成类逻辑回归的SVM分数。
复制代码


Sigmoid 函数由下列公式定义

可以看出 Sigmoid 函数值在,也正好符合我们的概率值的要求。即使一个数字无穷大,其代入 Sigmoid 函数后映射值也在内,那么,其属于另一个类的概率就是

链接:Sigmoid函数_百度百科 (baidu.com)


from sklearn.svm import SVC
class_1 = 500class_2 = 50centers = [[0.0,0.0],[2.0,2.0]]cluster_std = [1.5,0.5]X,y = make_blobs(n_samples=[class_1,class_2] ,centers=centers ,cluster_std=cluster_std ,random_state=0 ,shuffle=False)
plt.scatter(X[:,0],X[:,1],c=y,cmap='rainbow',s=10)
复制代码



clf = SVC(kernel="linear",C=1,probability=True).fit(X,y)clf.predict_proba(X).shape# 生成的各类标签下的概率# 因为我们之前设置了probability=True---(550, 2) # 我们有550个样本,标签的取值有0,1两个,因此shape为(550,2)# 我们可以使用和逻辑回归同样的方式来在SVM上设定和调节我们的阈值。
复制代码


Platt 缩放中涉及的交叉验证对于大型数据集来说非常昂贵,计算会非常缓慢。另外,由于 Platt 缩放的理论原因,在二分类过程中,有可能出现 predict_proba 返回的概率小于 0.5,但样本依旧被标记为正类的情况出现,毕竟支持向量机本身并不依赖于概率来完成自己的分类。如果我们的确需要置信度分数,但不一定非要是概率形式的话,那建议可以将 probability 设置为 False,使用 decision_function 这个接口而不是 predict_proba。


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

发布于: 刚刚阅读数: 4
用户头像

跃入人海 2022-09-14 加入

还未添加个人简介

评论

发布
暂无评论
支持向量机-ROC曲线中的概率和阈值_Python_烧灯续昼2002_InfoQ写作社区