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 LRlogi = 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_splitX, 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()結果:?
可以看到,由于真實標簽是0和1,所以所有的點都在y=1和y=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的接口,存在則返回Trueif hasattr(clf, "predict_proba"):prob_pos = clf.predict_proba(Xtest)[:,1]else: # use decision functionprob_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-con?dent?classi?er)的表現:?大量的樣本點集中在決策邊界的附近,因此許多樣本點的置信度靠近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的接口,存在則返回Trueif hasattr(clf, "predict_proba"):prob_pos = clf.predict_proba(Xtest)[:,1]else: # use decision functionprob_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%以上的樣本都在0和1的附近,可以說是置信度最高的算法,但是貝葉斯的布里爾分數卻不如邏輯回歸,這證明貝葉斯中在0和1附近的樣本中有一部分是被分錯的。支持向量貝葉斯完全相反,明顯是中間高,兩邊低,類似于正態分布的狀況,證明了我們剛才所說的,大部分樣本都在決策邊界附近,置信度都徘徊在0.5左右的情況。而邏輯回歸位于高斯樸素貝葉斯和支持向量機的中間,即沒有太多的樣本過度靠近0和1,也沒有形成像支持向量機那樣的正態分布。一個比較健康的正樣本的概率分布,就是邏輯回歸的直方圖顯示出來的樣子。
?
def plot_calib(models,name,Xtrain,Xtest,Ytrain,Ytest,n_bins=10):import matplotlib.pyplot as pltfrom sklearn.metrics import brier_score_lossfrom sklearn.calibration import calibration_curvefig, (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的接口,存在則返回Trueif 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,??sigmoid和isotonic的校準效果都非常不錯,無論是從校準曲線來看還是從概率分布圖來看,兩?種校準都讓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.1630Accuracy:0.8633 SVC+isotonic:Brier:0.0999Accuracy:0.8639 SVC+sigmoid:Brier:0.0987Accuracy:0.8634可以看到,對于SVC來說,兩種校正都改善了準確率和布里爾分數。可見,概率校正對于SVC非常有效。這也說明,概率校正對于原本的可靠性曲線是形容Sigmoid形狀的曲線的算法比較有效。
在現實中,我們可以選擇調節模型的方向,我們不一定要追求最高的準確率或者追求概率擬合最好,我們可以根據自己的需求來調整模型。當然,對于概率類模型來說,由于可以調節的參數甚少,所以我們更傾向于追求概率擬合,并使用概率校準的方式來調節模型。如果你的確希望追求更高的準確率和Recall,可以考慮使用天生就非常準確的概率類模型邏輯回歸,也可以考慮使用除了概率校準之外還有很多其他參數可調的支持向量機分類器。
總結
以上是生活随笔為你收集整理的Sklearn(v3)——朴素贝叶斯(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sklearn(v3)——朴素贝叶斯(1
- 下一篇: Sklearn(v3)——朴素贝叶斯(3