Sklearn(v3)——朴素贝叶斯(2)

概率类模型的评估指标

混淆矩阵和精确性可以帮助我们了解贝叶斯的分类结果然而我们选择贝叶斯进行分类大多数时候都不是为了单单追求效果而是希望看到预测的相关概率这种概率给出预测的可信度所以对于概率类模型我们希望能够由其 他的模型评估指标来帮助我们判断模型在概率预测这项工作上完成得如何接下来我们就来看看概率模型独有的评估指标

布里尔分数Brier Score

from sklearn.metrics import brier_score_loss
#注意,第一个参数是真实标签,第二个参数是预测出的概率值
#在二分类情况下,接口predict_proba会返回两列,但SVC的接口decision_function却只会返回一列 #要随时注意,使用了怎样的概率分类器,以辨别查找置信度的接口,以及这些接口的结构
brier_score_loss(Ytest, prob[:,1], pos_label=1)
#我们的pos_label与prob中的索引一致,就可以查看这个类别下的布里尔分数是多少

结果:

0.032
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression as LR

logi = LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto").fit(Xtrain,Ytrain) 
svc = SVC(kernel = "linear",gamma=1).fit(Xtrain,Ytrain)
brier_score_loss(Ytest,logi.predict_proba(Xtest)[:,1],pos_label=1)

结果:

0.011421576466807724
#由于SVC的置信度并不是概率,为了可比性,我们需要将SVC的置信度“距离”归一化,压缩到[0,1]之间
svc_prob = (svc.decision_function(Xtest) - svc.decision_function(Xtest).min())/(svc.decision_function(Xtest).max() - svc.decision_function(Xtest).min())
brier_score_loss(Ytest,logi.svc_prob[:,1],pos_label=1)

结果:

0.23
#如果将每个分类器每个标签类别下的布里尔分数可视化:

import pandas as pd
name = ["Bayes","Logistic","SVC"]
color = ["red","black","orange"]

df = pd.DataFrame(index=range(10),columns=name)
for i in range(10):
    df.loc[i,name[0]] = brier_score_loss(Ytest,prob[:,i],pos_label=i)
    df.loc[i,name[1]] = brier_score_loss(Ytest,logi.predict_proba(Xtest)[:,i],pos_label=i)
    df.loc[i,name[2]] = brier_score_loss(Ytest,svc_prob[:,i],pos_label=i)
for i in range(df.shape[1]):
    plt.plot(range(10),df.iloc[:,i],c=color[i])
plt.legend()
plt.show()
df

可以观察到,逻辑回归的布里尔分数有着压倒性优势,SVC的效果明显弱于贝叶斯和逻辑回归(如同我们之前在SVC的讲解中说明过的一样,SVC是强行利用sigmoid函数来压缩概率因此SVC产出的概率结果并不那么可靠)。贝叶斯位于逻辑回归和SVC之间效果也不错但比起逻辑回归还是不够精确和稳定 

对数似然函数Log Loss

from sklearn.metrics import log_loss
print(log_loss(Ytest,prob))
print(log_loss(Ytest,logi.predict_proba(Xtest)))
log_loss(Ytest,svc_prob)

结果:

2.4725653911460683
0.12753760812517437
1.6074987533411256

使用log _loss评价时,svm要优于贝叶斯,因为svm本身就是朝着最小化损失函数的某个方向进行建模的

可靠性曲线Reliability Curve

可靠性曲线( reliability curve ),又叫做概率校准曲线( probability calibration curve ),可靠性图( reliability diagrams),这是一条以预测概率为横坐标,真实标签为纵坐标的曲线。我们希望预测概率和真实值越接近越好
最好两者相等,因此一个模型 / 算法的概率校准曲线越靠近对角线越好 。校准曲线因此也是我们的模型评估指标之一。和布里尔分数相似,概率校准曲线是对于标签的某一类来说的,因此一类标签就会有一条曲线,或者我们可以使用一个多类标签下的平均来表示一整个模型的概率校准曲线。但通常来说,曲线用于二分类的情况最多 ,大家如果感兴趣可以自行探索多分类的情况。根据这个思路,我们来绘制一条曲线试试看。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification as mc
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression as LR
from sklearn.metrics import brier_score_loss
from sklearn.model_selection import train_test_split

X, y = mc(n_samples=100000,n_features=20 #总共20个特征
            ,n_classes=2 #标签为2分类
            ,n_informative=2 #其中两个代表较多信息
            ,n_redundant=10 #10个都是冗余特征
            ,random_state=42)

#样本量足够大,因此使用1%的样本作为训练集
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.99,random_state=42)

gnb = GaussianNB()
gnb.fit(Xtrain,Ytrain)
y_pred = gnb.predict(Xtest)
prob_pos = gnb.predict_proba(Xtest)[:,1] #我们的预测概率  - 横坐标
#Ytest - 我们的真实标签  - 横坐标
#在我们的横纵表坐标上,概率是由顺序的(由小到大),为了让图形规整一些,我们要先对预测概率和真实标签按照预测 概率进行一个排序,这一点我们通过DataFrame来实现
df = pd.DataFrame({"ytrue":Ytest[:500],"probability":prob_pos[:500]})

df = df.sort_values(by="probability")
df.index = range(df.shape[0])

#紧接着我们就可以画图了
fig = plt.figure()
ax1 = plt.subplot()
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
ax1.scatter(df["probability"],df["ytrue"],s=10)
ax1.set_ylabel("True label")
ax1.set_xlabel("predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()

结果: 

可以看到,由于真实标签01所以所有的点都在y=1y=0这两条直线上分布这完全不是我们希望看到的图回想一下我们的可靠性曲线的横纵坐标横坐标是预测概率而纵坐标是真实值我们希望预测概率很靠近真实那我们的真实取值必然也需要是一个概率才可以如果使用真实标签那我们绘制出来的图像完全是没有意义但是我们去哪里寻找真实值的概率呢?这是不可能找到的——如果我们能够找到真实的概率那我们何必还用算法来估计概率呢直接去获取真实的概率不就好了么?所以真实概率在现实中是不可获得的但是我们可以获得类概率的指标来帮助我们进行校准一个简单的做法是将数据进行分箱然后规定每个箱子中真实的少数类所占的 比例为这个箱上的真实概率trueproba,这个箱子中预测概率的均值为这个箱子的预测概率predproba然后以trueproba为纵坐标,predproba横坐标来绘制我们的可靠性曲线

举个例子,来看下面这张表,这是一组数据不分箱时表现出来的图像

from sklearn.calibration import calibration_curve
#从类calibiration_curve中获取横坐标和纵坐标
trueproba, predproba = calibration_curve(Ytest, prob_pos,n_bins=10) #输入希望分箱的个数
fig = plt.figure()
ax1 = plt.subplot()
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
ax1.plot(predproba, trueproba,"s-",label="%s (%1.3f)" % ("Bayes", clf_score))
ax1.set_ylabel("True probability for class 1")
ax1.set_xlabel("Mean predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()

结果:

 

fig, axes = plt.subplots(1,3,figsize=(18,4))
for ind,i in enumerate([3,10,100]):
    ax = axes[ind]
    ax.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
    trueproba, predproba = calibration_curve(Ytest, prob_pos,n_bins=i)
    ax.plot(predproba, trueproba,"s-",label="n_bins = {}".format(i))
    ax1.set_ylabel("True probability for class 1")
    ax1.set_xlabel("Mean predcited probability")
    ax1.set_ylim([-0.05, 1.05])
    ax.legend()
plt.show()

 结果:

很明显可以看出n_bins越大箱子越多概率校准曲线就越精确但是太过精确的曲线不够平滑无法和我们希望的完美概率密度曲线相比较n_bins越小箱子越少概率校准曲线就越粗糙虽然靠近完美概率密度曲线但是无法真实地展现模型概率预测地结果因此我们需要取一个既不是太大也不是太小的箱子个数让概率校准曲线既是太精确也不是太粗糙而是一条相对平滑又可以反应出模型对概率预测的趋势的曲线通常来说建议先试试看箱子数等10的情况箱子的数目越大所需要的样本量也越多否则曲线就会太过精确

name = ["GaussianBayes" ,"Logistic","SVC"]

gnb = GaussianNB()
logi = LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto")
svc = SVC(kernel = "linear",gamma=1)

fig, ax1 = plt.subplots(figsize=(8,6))
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")

for clf, name_ in zip([gnb,logi,svc],name):
    clf.fit(Xtrain,Ytrain)
    y_pred = clf.predict(Xtest)
    #hasattr(obj,name):查看一个类obj中是否存在名字为name的接口,存在则返回True
    if hasattr(clf, "predict_proba"):
        prob_pos = clf.predict_proba(Xtest)[:,1]
    else:  # use decision function
        prob_pos = clf.decision_function(Xtest)
        prob_pos = (prob_pos - prob_pos.min()) / (prob_pos.max() - prob_pos.min())
    #返回布里尔分数
    clf_score = brier_score_loss(Ytest, prob_pos, pos_label=y.max())   
    trueproba, predproba = calibration_curve(Ytest, prob_pos,n_bins=10)
    ax1.plot(predproba, trueproba,"s-",label="%s (%1.3f)" % (name_, clf_score))

ax1.set_ylabel("True probability for class 1")
ax1.set_xlabel("Mean predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()

结果: 

从图像的结果来看我们可以明显看出逻辑回归的概率估计是最接近完美的概率校准曲线所以逻辑虎归的效果 完美相对的高斯朴素贝叶斯和支持向量机分类器的结果都比较糟糕支持向量机呈现类似于sigmoid函数的形状,而高斯朴素贝叶斯呈现和Sigmoid函数相反的形状

对于贝叶斯,如果概率校准曲线呈现sigmoid函数的镜像的情况则说明数据集中的特征不是相互条件独立的贝叶斯原理中的朴素原则:特征相互条件独立原则被违反了(这其实是我们自己的设定我们设定了10个冗余特征些特征就是噪音他们之间不可能完全独立),因此贝叶斯的表现不够好

支持向量机的概率校准曲线效果其实是典型的置信度不足的分类器(under-condent classier)的表现 大量的样本点集中在决策边界的附近因此许多样本点的置信度靠近0.5左右即便决策边界能够将样本点判断正确模型本身对这个结果也不是非常确信的相对的离决策边界很远的点的置信度就会很高因为它很大可能性上不会被判断错误支持向量机在面对混合度较高的数据的时候有着天生的置信度不足的缺点

支持向量机预测概率大多分布在0.5附近,逻辑回归大多预测概率趋近0或者是趋近1

预测概率的直方图 

fig, ax2 = plt.subplots(figsize=(8,6))
name = ["GaussianBayes" ,"Logistic","SVC"]

for clf, name_ in zip([gnb,logi,svc],name):
    clf.fit(Xtrain,Ytrain)
    y_pred = clf.predict(Xtest)
#hasattr(obj,name):查看一个类obj中是否存在名字为name的接口,存在则返回True
    if hasattr(clf, "predict_proba"):
        prob_pos = clf.predict_proba(Xtest)[:,1]
    else:  # use decision function
        prob_pos = clf.decision_function(Xtest)
        prob_pos = (prob_pos - prob_pos.min()) / (prob_pos.max() - prob_pos.min()) 
    ax2.hist(prob_pos,bins=10,label=name_
    ,histtype="step" #设置直方图为透明
    ,lw=2) #设置直方图每个柱子描边的粗细

ax2.set_ylabel("Distribution of probability")
ax2.set_xlabel("Mean predicted probability")
ax2.set_xlim([-0.05, 1.05])
ax2.legend(loc = 9)
ax2.set_xticks([0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1])

结果:
 

可以看到高斯贝叶斯的概率分布是两边非常高中间非常低几乎90%以上的样本都在01的附可以说是置信度最高的算法,但是贝叶斯的布里尔分数却不如逻辑回归这证明贝叶斯中在01附近的样本中有一部分是被分错支持向量贝叶斯完全相反明显是中间高两边低类似于正态分布的状况证明了我们刚才所说的大部分样本都在决策边界附近置信度都徘徊在0.5左右的情况而逻辑回归位于高斯朴素贝叶斯和支持向量机的中间即没有太多的样本过度靠近01也没有形成像支持向量机那样的正态分布一个比较健康的正样本的概率分布就是逻辑回归的直方图显示出来的样子

 

def plot_calib(models,name,Xtrain,Xtest,Ytrain,Ytest,n_bins=10):
    import matplotlib.pyplot as plt
    from sklearn.metrics import brier_score_loss
    from sklearn.calibration import calibration_curve
    fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(20,6))
    ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
    for clf, name_ in zip(models,name):
        clf.fit(Xtrain,Ytrain)
        y_pred = clf.predict(Xtest)
        #hasattr(obj,name):查看一个类obj中是否存在名字为name的接口,存在则返回True
        if hasattr(clf, "predict_proba"):
            prob_pos = clf.predict_proba(Xtest)[:,1]
        else:
            prob_pos = clf.decision_function(Xtest)
            prob_pos = (prob_pos - prob_pos.min()) / (prob_pos.max() - prob_pos.min())
        #返回布里尔分数
        clf_score = brier_score_loss(Ytest, prob_pos, pos_label=y.max())
        trueproba, predproba = calibration_curve(Ytest, prob_pos,n_bins=n_bins)
        ax1.plot(predproba, trueproba,"s-",label="%s (%1.3f)" % (name_, clf_score))
        ax2.hist(prob_pos, range=(0, 1), bins=n_bins, label=name_,histtype="step",lw=2) 
        
    ax2.set_ylabel("Distribution of probability")
    ax2.set_xlabel("Mean predicted probability")
    ax2.set_xlim([-0.05, 1.05])
    ax2.legend(loc=9)
    ax2.set_title("Distribution of probablity")
    ax1.set_ylabel("True probability for class 1")
    ax1.set_xlabel("Mean predcited probability")
    ax1.set_ylim([-0.05, 1.05])
    ax1.legend()
    ax1.set_title("Calibration plots(reliability curve)")
    plt.show()

from sklearn.calibration import CalibratedClassifierCV
name = ["GaussianBayes" ,"Logistic","Bayes+isotonic" ,"Bayes+sigmoid"]
gnb = GaussianNB()
models = [gnb
        ,LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto")
        #定义两种校准方式
        ,CalibratedClassifierCV(gnb, cv=2, method='isotonic')
        ,CalibratedClassifierCV(gnb, cv=2, method='sigmoid')]
plot_calib(models,name,Xtrain,Xtest,Ytrain,Ytest)

结果:

从校正朴素贝叶斯的结果来看, Isotonic 等渗校正大大改善了曲线的形状,几乎让贝叶斯的效果与逻辑回归持平,并且布里尔分数也下降到了0.098 ,比逻辑回归还低一个点。 Sigmoid 校准的方式也对曲线进行了稍稍的改善,不过效果不明显。从直方图来看,Isotonic 校正让高斯朴素贝叶斯的效果接近逻辑回归,而 Sigmoid 校正后的结果依然和原本的高斯朴素贝叶斯更相近。可见,当数据的特征之间不是相互条件独立的时候,使用Isotonic 方式来校准概率曲线,可以得到不错的结果,让模型在预测上更加谦虚。
gnb = GaussianNB().fit(Xtrain,Ytrain)
print(gnb.score(Xtest,Ytest))
print(brier_score_loss(Ytest,gnb.predict_proba(Xtest)[:,1],pos_label = 1))

gnbisotonic = CalibratedClassifierCV(gnb, cv=2, method='isotonic').fit(Xtrain,Ytrain) 
print(gnbisotonic.score(Xtest,Ytest))
brier_score_loss(Ytest,gnbisotonic .predict_proba(Xtest)[:,1],pos_label = 1)

 结果:

0.8650606060606061
0.11760826355000836
0.8626767676767677
0.09833190251353853

可以看出校准概率后布里尔分数明显变小了但整体的准确率却略有下降这证明算法在校准之后尽管对概率的预测更准确了但模型的判断力略有降低来思考一下布里尔分数衡量模型概率预测的准确率布里尔分数越代表模型的概率越接近真实概率当进行概率校准后本来标签是1的样本的概率应该会更接近1而标签本来是0的样本应该会更接近0没有理由布里尔分数提升了模型的判断准确率居然下降了但从我们的结果来看模型的准确率和概率预测的正确性并不是完全一致的为什么会这样呢?

对于不同的概率类模型原因是不同的对于SVC决策树这样的模型来说概率不是真正的概率而更偏向于是一置信度这些模型也不是依赖于概率预测来进行分类(决策树依赖于树杈而SVC依赖于决策边界),此对于这些模型,可能存在着类别1的概率为0.4但样本依然被分类为1的情况这种情况代表着——模型很没有信心认为这个样本是1但是还是坚持把这个样本的标签分类为1这种时候概率校准可能会向着更加错误的方向调整(比如把概率为0.4的点调节得更接近0导致模型最终判断错误),因此出现布里尔分数可能会显示和精确性相反的趋势

而对于朴素贝叶斯这样的模型却是另一种情况注意在朴素贝叶斯中我们有各种各样的假设除了我们还有我们对概率分布的假设(比如说高斯),这些假设使得我们的贝叶斯得出的概率估计其实是有偏估也就是说这种概率估计其实不是那么准确和严肃我们通过校准让模型的预测概率更贴近于真实概本质在统计学上让算法更加贴近我们对整体样本状况的估计这样的一种校准在一组数据集上可能表现出让准确率上升也可能表现出让准确率下降这取决于我们的测试集有多贴近我们估计的真实样本的面貌这一系列有偏估计使得我们在概率校准中可能出现布里尔分数和准确度的趋势相反的情况

当然,可能还有更多更深层的原因,比如概率校准过程中的数学细节如何影响了我们的校准calibration_curve是如何分箱如何通过真实标签和预测值来生成校准曲线使用的横纵坐标的这些过程中也可能有着让布里尔分数和确率向两个方向移动的过程

在现实中,当两者相悖的时候,请务必以准确率为标准。但是这不代表说布里尔分数和概率校准曲线就无效了概率类模型几乎没有参数可以调整除了换模型之外鲜有更好的方式帮助我们提升模型的表现概率校准是难得的可以帮助我们针对概率提升模型的方法 

name_svc = ["SVC","Logistic","SVC+isotonic","SVC+sigmoid"]
svc = SVC(kernel = "linear",gamma=1)
models_svc = [svc
,LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto")
,CalibratedClassifierCV(svc, cv=2, method='isotonic')
,CalibratedClassifierCV(svc, cv=2, method='sigmoid')]
plot_calib(models_svc,name_svc,Xtrain,Xtest,Ytrain,Ytest)

结果: 

可以看出,对于SVC  sigmoidisotonic的校准效果都非常不错无论是从校准曲线来看还是从概率分布图来看 种校准都让SVC的结果接近逻辑回归,其中sigmoid加有效来看看不同的SVC下的精确度结果(对于这一段代码,大家完全可以把它包括在原有的绘图函数中):

 

name_svc = ["SVC","SVC+isotonic" ,"SVC+sigmoid"]
svc = SVC(kernel = "linear",gamma=1)
models_svc = [svc,CalibratedClassifierCV(svc, cv=2, method='isotonic'),CalibratedClassifierCV(svc, cv=2, method='sigmoid')]

for clf, name in zip(models_svc,name_svc):
    clf.fit(Xtrain,Ytrain)
    y_pred = clf.predict(Xtest)
    if hasattr(clf, "predict_proba"):
        prob_pos = clf.predict_proba(Xtest)[:, 1]
    else:
        prob_pos = clf.decision_function(Xtest)
        prob_pos = (prob_pos - prob_pos.min()) / (prob_pos.max() - prob_pos.min()) 
    clf_score = brier_score_loss(Ytest, prob_pos, pos_label=y.max())
    score = clf.score(Xtest,Ytest)
    print("{}:".format(name))
    print("\tBrier:{:.4f}".format(clf_score))
    print("\tAccuracy:{:.4f}".format(score))

结果: 

SVC:
	Brier:0.1630
	Accuracy:0.8633
SVC+isotonic:
	Brier:0.0999
	Accuracy:0.8639
SVC+sigmoid:
	Brier:0.0987
	Accuracy:0.8634

可以看到对于SVC来说两种校正都改善了准确率和布里尔分数可见概率校正对SVC非常有效这也说明概率校正对于原本的可靠性曲线是形容Sigmoid形状的曲线的算法比较有效

在现实中我们可以选择调节模型的方向我们不一定要追求最高的准确率或者追求概率拟合最好我们可以根据自己的需求来调整模型当然对于概率类模型来说由于可以调节的参数甚少所以我们更倾向于追求概率拟合使用概率校准的方式来调节模型。如果你的确希望追求更高的准确率和Recall可以考虑使用天生就非常准确的概率模型逻辑回归也可以考虑使用除了概率校准之外还有很多其他参数可调的支持向量机分类器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值