Sklearn中的樸素貝葉斯分類器`
目錄
3.4,改進多項式樸素貝葉斯:補集樸素貝葉斯ComplementNB
上一篇文章我向大家介紹了樸素貝葉斯工作的理論部分,需要看的小夥伴請移步:貝葉斯分類器,接下來,我們基於Sklearn機器學習庫來使用以下具體的貝葉斯分類器演算法。
1,sklearn中的貝葉斯分類器
Sklearn基於資料分佈以及這些分佈上的概率估計的改進,為我們提供了四個樸素貝葉斯的分類器。
類 | 含義 |
naive_bayes.BernoulliNB | 伯努利分佈下的樸素貝葉斯 |
naive_bayes.GaussianNB | 高斯分佈下的樸素貝葉斯 |
naive_bayes.MultinomialNB | 多項式分佈下的樸素貝葉斯 |
naive_bayes.ComplementNB | 補集樸素貝葉斯 |
linear_model.BayesianRidge | 貝葉斯嶺迴歸,在引數估計過程中使用貝葉斯迴歸技術來包括正則化引數 |
由於貝葉斯是從概率角度進行估計,它所需要的樣本量比較少,極端情況下甚至我們可以使用1%的資料作為訓練集,依然可以得到很好的擬合效果。當然,如果樣本量少於特徵數目,貝葉斯的效果就會被削弱。與SVM和隨機森林相比,樸素貝葉斯執行速度更快,因為求解本質是在每個特徵上單獨對概率進行計算,然後再求乘積,所以每個特徵上的計算可以是獨立並且並行的,因此貝葉斯的計算速度比較快。不過相對的,貝葉斯的執行效果不是那麼好,所以貝葉斯的介面呼叫的predict_proba其實也不是總指向真正的分類結果,這一點需要注
意。
1.1,高斯樸素貝葉斯GaussianNB
1.1.1,認識高斯樸素貝葉斯
class sklearn.naive_bayes.GaussianNB (priors=None, var_smoothing=1e-09)
高斯樸素貝葉斯,通過假設是服從高斯分佈(也就是正態分佈),來估計每個特徵下每個類別上的條件概率。對於每個特徵下的取值,高斯樸素貝葉斯有如下公式:,對於任意一個的取值,貝葉斯都以求解最大化的為目標,這樣我們才能夠比較在不同標籤下我們的樣本究竟更靠近哪一個取值。以最大化為目標,高斯樸素貝葉斯會為我們求解公式中的引數和。求解出引數後,帶入一個的值,就能夠得到一個的概率取值。
1.1.2,引數說明
引數 | 含義 |
prior(先驗概率) | 可輸入任何類陣列結構,形狀為(n_classes,) 表示類的先驗概率。如果指定,則不根據資料調整先驗,如果不指定,則自行根據資料計算先驗概率。 |
var_smoothing | 浮點數,可不填(預設值= 1e-9) 在估計方差時,為了追求估計的穩定性,將所有特徵的方差中最大的方差以某個比例新增 到估計的方差中。這個比例,由var_smoothing引數控制。 |
但在例項化的時候,我們不需要對高斯樸素貝葉斯類輸入任何的引數,呼叫的介面也全部是sklearn中比較標準的一些搭配,可以說是一個非常輕量級的類,操作非常容易。但過於簡單也意味著貝葉斯沒有太多的引數可以調整,因此貝葉斯演算法的成長空間並不是太大,如果貝葉斯演算法的效果不是太理想,我們一般都會考慮換模型。
1.1.3,高斯樸素貝葉斯建模案例
-
檢視開發工具版本
%%cmd
pip install watermark #這是一個魔法命令
# 魔法命令必須寫在一個cell的第一行
#在這裡必須分開cell,魔法命令必須是一個cell的第一部分內容
#注意load_ext這個命令只能夠執行一次,再執行就會報錯,要求用reload命令
%load_ext watermark
%watermark -a "ruirui" -d -v -m -p numpy,pandas,matplotlib,scipy,sklearn
- 建模
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
# 匯入資料集
digits=load_digits()
X=digits.data
y=digits.target
# 劃分測試集和訓練資料集,劃分後,訓練資料1257個樣本,測試資料集540個樣本
xtrain,xtest,ytrain,ytest=train_test_split(X,y,test_size=0.3,random_state=0)
# 檢視標籤種類
np.unique(ytrain)
# 例項化模型並且訓練模型,其中fit()過程就是在計算概率的過程
gnb=GaussianNB().fit(xtrain,ytrain)
# score()介面對於我們的分型別演算法,返回預測的精確性,也就是accuracy,使用測試資料集測試
acc_score=gnb.score(xtest,ytest)
# 返回所有樣本對應的類別,這裡的樣本標籤是用我們下面得到的概率中,
# 選取每一個樣本中概率最大的作為此樣本的標籤
y_pred=gnb.predict(xtest)
# 檢視我們的概率結果
yprob=gnb.predict_proba(xtest)
# 可以看到,返回的結果是540*10的二維矩陣,其中應為分類有10個,所以一共返回10列概率
# 取其中概率最大的哪一個分類作為最後的分類結果,並且每一行的概率之和是1
#注意,ROC曲線是不能用於多分類的。多分類狀況下最佳的模型評估指標是混淆矩陣和整體的準確度
from sklearn.metrics import confusion_matrix as CM
CM(ytest,y_pred)
1.1.4,探索高斯樸素貝葉斯擅長的資料集
我們還是使用常用的三種資料分佈:月亮型,環形資料以及二分型資料來看看高斯樸素貝葉斯的情況。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_moons, make_circles, make_classification
from sklearn.naive_bayes import GaussianNB,MultinomialNB,BernoulliNB,ComplementNB
h = .02
# 模型的名字
names = ["Multinomial","Gaussian","Bernoulli","Complement"]
# 建立我們的模型物件
classifiers = [MultinomialNB(),GaussianNB(),BernoulliNB(),ComplementNB()]
# 建立分類資料集
X, y = make_classification(n_features=2, n_redundant=0, n_informative=2,
random_state=1, n_clusters_per_class=1)
# 月亮刑資料
rng = np.random.RandomState(2)
X += 2 * rng.uniform(size=X.shape)
linearly_separable = (X, y)
datasets = [make_moons(noise=0.3, random_state=0),
make_circles(noise=0.2, factor=0.5, random_state=1),
linearly_separable
]
# 建立畫布
figure = plt.figure(figsize=(6, 9))
i = 1
for ds_index, ds in enumerate(datasets):
X, y = ds
# 標準化資料集
X = StandardScaler().fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4,random_state=42)
# 對畫布畫網格線
x1_min, x1_max = X[:, 0].min() - .5, X[:, 0].max() + .5
x2_min, x2_max = X[:, 1].min() - .5, X[:, 1].max() + .5
array1,array2 = np.meshgrid(np.arange(x1_min, x1_max, 0.2),
np.arange(x2_min, x2_max, 0.2))
cm = plt.cm.RdBu
cm_bright = ListedColormap(['#FF0000', '#0000FF'])
ax = plt.subplot(len(datasets), 2, i)
if ds_index == 0:
ax.set_title("Input data")
ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train,
cmap=cm_bright,edgecolors='k')
ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test,
cmap=cm_bright, alpha=0.6,edgecolors='k')
ax.set_xlim(array1.min(), array1.max())
ax.set_ylim(array2.min(), array2.max())
ax.set_xticks(())
ax.set_yticks(())
i += 1
ax = plt.subplot(len(datasets),2,i)
clf = GaussianNB().fit(X_train, y_train)
score = clf.score(X_test, y_test)
Z = clf.predict_proba(np.c_[array1.ravel(),array2.ravel()])[:, 1]
Z = Z.reshape(array1.shape)
ax.contourf(array1, array2, Z, cmap=cm, alpha=.8)
ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=cm_bright,
edgecolors='k')
ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright,
edgecolors='k', alpha=0.6)
ax.set_xlim(array1.min(), array1.max())
ax.set_ylim(array2.min(), array2.max())
ax.set_xticks(())
ax.set_yticks(())
if ds_index == 0:
ax.set_title("Gaussian Bayes")
ax.text(array1.max() - .3, array2.min() + .3, ('{:.1f}%'.format(score*100)),
size=15, horizontalalignment='right')
i += 1
plt.tight_layout()
plt.show()
從圖上來看,高斯貝葉斯屬於比較特殊的一類分類器,其分類效果在二分資料和月亮型資料上表現優秀,但是環形資料不太擅長。我們之前學過的模型中,許多線性模型比如邏輯迴歸,線性SVM等等,線上性資料集上會繪製直線決策邊界,因此難以對月亮型和環形資料進行區分,但高斯樸素貝葉斯的決策邊界是曲線,可以是環形也可以是弧線,所以儘管貝葉斯本身更加擅長線性可分的二分資料,但樸素貝葉斯在環形資料和月亮型資料上也可以有遠遠勝過其他線性模型的表現。
1.1.5,探索貝葉斯:高斯樸素貝葉斯的擬合效果與運算速度
我們已經瞭解高斯樸素貝葉斯屬於分類效果不算頂尖的模型,但我們依然好奇,這個演算法在擬合的時候還有哪些特性呢?比如說我們瞭解,決策樹是天生過擬合的模型,而支援向量機是不調引數的情況下就非常接近極限的模型。我們希望通過繪製高斯樸素貝葉斯的學習曲線與分類樹,隨機森林和支援向量機的學習曲線的對比,來探索高斯樸素貝葉斯演算法在擬合上的性質。過去繪製學習曲線都是以演算法類的某個引數的取值為橫座標,今天我們來使用sklearn中自帶的繪製學習曲線的類learning_curve,在這個類中執行交叉驗證並從中獲得不同樣本量下的訓練和測試的準確度。
- 匯入需要的模組
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.tree import DecisionTreeClassifier as DTC
from sklearn.linear_model import LogisticRegression as LR
from sklearn.datasets import load_digits
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
from time import time
import datetime
- 交叉驗證的模式
datas=load_digits()
X=datas.data
y=datas.target
clf=GaussianNB()
# 交叉驗證的模式
# 這裡使用的訓練集資料比例是預設的,5次
cv = ShuffleSplit(n_splits=50# 標示把資料分成多少份
, test_size=0.2# 其中有20%資料作為測試集
, random_state=0)# 在交叉驗證的時候進行的隨機抽樣的模式
# 把資料分50分,其中20%作為測試集,50此測試,每一次都用20%的資料集作為測試
learning_curve(clf# 標示分類器
, X, y# 特徵矩陣和標籤
,cv=cv# 設定交叉驗證的模式
,n_jobs=4)# 每一次執行可以使用的執行緒數目
train_sizes, train_scores, test_scores = learning_curve(clf, X, y
,cv=cv,n_jobs=3)
train_sizes
# 訓練集裡面資料的比例,array([ 143, 467, 790, 1113, 1437])
# 分成5次訓練,訓練資料樣本逐漸增加,每一次劃分,都把資料集劃分為50分
train_scores.shape #(5, 50)
test_scores.shape# (5, 50)
# 畫曲線的函式
# 第一步:找出每一個圖的橫縱座標
def plot_learning_curve(estimator,title, X, y,# estimator標示使用的分類器
ax, #選擇子圖
ylim=None, #設定縱座標的取值範圍
cv=None, #交叉驗證
n_jobs=None #設定索要使用的執行緒
):
# 此函式會返回影像的橫縱座標,但是不會畫出影像
train_sizes, train_scores, test_scores = learning_curve(estimator, X, y
,cv=cv,n_jobs=n_jobs)
# train_sizes每一次劃分訓練集和測試集後,訓練集上面樣本的數量
# train_scores得到的是訓練集上面的分數
# test_scores得到最後的測試分數
ax.set_title(title)
if ylim is not None:# 設定y座標的量綱形式一樣,也就是把多個子圖的y座標設定為一樣
ax.set_ylim(*ylim)
ax.set_xlabel("Training examples")
ax.set_ylabel("Score")
ax.grid() #顯示網格作為背景,不是必須
ax.plot(train_sizes, np.mean(train_scores, axis=1), 'o-'
, color="r",label="Training score")# 畫訓練資料集的影像
ax.plot(train_sizes, np.mean(test_scores, axis=1), 'o-'
, color="g",label="Test score")# 畫出測試集影像
ax.legend(loc="best")
return ax
- 繪製曲線
title = ["Naive Bayes","DecisionTree","SVM, RBF kernel","RandomForest","Logistic"]
model = [GaussianNB(),DTC(),SVC(gamma=0.001)
,RFC(n_estimators=50),LR(C=.1,solver="lbfgs")]# 例項化我們的模型演算法
fig, axes = plt.subplots(1,5,figsize=(30,6))# 設定子圖的個數
for ind,title_,estimator in zip(range(len(title)),title,model):
times = time()
plot_learning_curve(estimator, title_, X, y,
ax=axes[ind], ylim = [0.7, 1.05],n_jobs=4, cv=cv)
print("{}:{}".format(title_,datetime.datetime.fromtimestamp(time()-
times).strftime("%M:%S:%f")))
plt.show()
# 影像的橫座標標示訓練樣本的個數
# 如果資料集的維度很高,我們就使用貝葉斯演算法,對於一般的資料集我們首選邏輯迴歸
我們首先返回的結果是各個演算法的執行時間。可以看到,決策樹和貝葉斯不相伯仲(如果你沒有發現這個結果,那麼可以多執行幾次,你會發現貝葉斯和決策樹的執行時間逐漸變得差不多)。決策樹之所以能夠執行非常快速是因為sklearn中的分類樹在選擇特徵時有所“偷懶”,沒有計算全部特徵的資訊熵而是隨機選擇了一部分特徵來進行計算,因此速度快可以理解,但我們知道決策樹的運算效率隨著樣本量逐漸增大會越來越慢,但樸素貝葉斯卻可以在很少的樣本上獲得不錯的結果,因此我們可以預料,隨著樣本量的逐漸增大貝葉斯會逐漸變得比決策樹更快。樸素貝葉斯計算速度遠遠勝過SVM,隨機森林這樣複雜的模型,邏輯迴歸的執行受到最大迭代次數的強烈影響和輸入資料的影響(邏輯迴歸一般線上性資料上執行都比較快,但在這裡應該是受到了稀疏矩陣的影響)。因此在運算時間上,樸素貝葉斯
還是十分有優勢的。
緊接著,我們來看一下每個演算法在訓練集上的擬合。手寫數字資料集是一個較為簡單的資料集,決策樹,森林,SVC和邏輯迴歸都成功擬合了100%的準確率,但貝葉斯的最高訓練準確率都沒有超過95%,這也應證了我們最開始說的,樸素貝葉斯的分類效果其實不如其他分類器,貝葉斯天生學習能力比較弱。並且我們注意到,隨著訓練樣本量的逐漸增大,其他模型的訓練擬合都保持在100%的水平,但貝葉斯的訓練準確率卻逐漸下降,這證明樣本量越大,貝葉斯需要學習的東西越多,對訓練集的擬合程度也越來越差。反而比較少量的樣本可以讓貝葉斯有較高的訓練準確率。
再來看看過擬合問題。首先一眼看到,所有模型在樣本量很少的時候都是出於過擬合狀態的(訓練集上表現好,測試集上表現糟糕),但隨著樣本的逐漸增多,過擬合問題都逐漸消失了,不過每個模型的處理手段不同。比較強大的分類器們,比如SVM,隨機森林和邏輯歸,是依靠快速升高模型在測試集上的表現來減輕過擬合問題。相對的,決策樹雖然也是通過提高模型在測試集上的表現來減輕過擬合,但隨著訓練樣本的增加,模型在測試集上的表現善生卻非常緩慢。樸素貝葉斯獨樹一幟,是依賴訓練集上的準確率下降,測試集上的準確率上升來逐漸解決過擬合問題。
接下來,看看每個演算法在測試集上的擬合結果,即泛化誤差的大小。隨著訓練樣本數量的上升,所有模型的測試表現都上升了,但貝葉斯和決策樹在測試集上的表現遠遠不如SVM,隨機森林和邏輯迴歸。SVM在訓練資料量增大到1500個樣本左右的時候,測試集上的表現已經非常接近100%,而隨機森林和邏輯迴歸的表現也在95%以上,而決策樹和樸素貝葉斯還徘徊在85%左右。但這兩個模型所面臨的情況十分不同:決策樹雖然測試結果不高,但是卻依然具有潛力,因為它的過擬合現象非常嚴重,我們可以通過減枝來讓決策樹的測試結果逼近訓練結果。然而貝葉斯的過擬合現象在訓練樣本達到1500左右的時候已經幾乎不存在了,訓練集上的分數和測試集上的分數非常接近,只有在非常少的時候測試集上的分數才能夠比訓練集上的結果更高,所以我們基本可以判斷,85%左右就是貝葉斯在這個資料集上的極限了。可以預測到,如果我們進行調參,那決策樹最後應該可以達到90%左右的預測準確率,但貝葉斯卻幾乎沒有潛力了。
在這個對比之下,我們可以看出:貝葉斯是速度很快,但分類效果一般,並且初次訓練之後的結果就很接近演算法極限的演算法,幾乎沒有調參的餘地。也就是說,如果我們追求對概率的預測,並且希望越準確越好,那我們應該先選擇邏輯迴歸。如果資料十分複雜,或者是稀疏矩陣,那我們堅定地使用貝葉斯。如果我們分類的目標不是要追求對概率的預測,那我們完全可以先試試看高斯樸素貝葉斯的效果(反正它運算很快速,還不需要太多的樣本),如果效果很不錯,我們就很幸運地得到了一個表現優秀又快速的模型。如果我們沒有得到比較好的結果,那我們完全可以選擇再更換成更加複雜的模型。
2,概率模型的評估指標
混淆矩陣和精確性可以幫助我們瞭解貝葉斯的分類結果。然而,我們選擇貝葉斯進行分類,大多數時候都不是為了單單追求效果,而是希望看到預測的相關概率。這種概率給出預測的可信度,所以對於概率類模型,我們希望能夠由其他的模型評估指標來幫助我們判斷,模型在“概率預測”這項工作上,完成得如何。接下來,我們就來看看概率模型獨有的評估指標。
2.2.1,布里爾分數Brier Score
概率預測的準確程度被稱為“校準程度”,是衡量演算法預測出的概率和真實結果的差異的一種方式。一種比較常用的指標叫做布里爾分數,它被計算為是概率預測相對於測試樣本的均方誤差,表示為:
,其中N是樣本數量,為樸素貝葉斯預測出的概率, 是樣本所對應的真實結果,只能取到0或者1,如果事件發生則為1,如果不發生則為0。這個指標衡量了我們的概率距離真實標籤結果的差異,其實看起來非常像是均方誤差。布里爾分數的範圍是從0到1,分數越高則預測結果越差勁,校準程度越差,因此布里爾分數越接近0越好。由於它的本質也是在衡量一種損失,所以在sklearn當中,布里爾得分被命名為brier_score_loss。我們可以從模組metrics中匯入這個分數來衡量我們的模型評估結果:
from sklearn.metrics import brier_score_loss
#注意,第一個引數是真實標籤,第二個引數是預測出的概率值
#在二分類情況下,介面predict_proba會返回兩列,但SVC的介面decision_function卻只會返回一列
#要隨時注意,使用了怎樣的概率分類器,以辨別查詢置信度的介面,以及這些介面的結構
brier_score_loss(Ytest, prob[:,1], pos_label=1)
#我們的pos_label與prob中的索引一致,就可以檢視這個類別下的布里爾分數是多少
布里爾分數可以用於任何可以使用predict_proba介面呼叫概率的模型,我們來探索一下在我們的手寫數字資料集
上,邏輯迴歸,SVC和我們的高斯樸素貝葉斯的效果如何:
brier_score_loss(Ytest,prob[:,8],pos_label=8)
# 建立模型並且擬合資料
logi = LR(C=1., solver='lbfgs',max_iter=5000,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)
#由於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,svc_prob[:,1],pos_label=1)
# 資料視覺化
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之間,效果也不錯,但比起邏輯迴歸,還是不夠精確和穩定。
2.2.2,對數似然函式Log Loss
另一種常用的概率損失衡量是對數損失(log_loss),又叫做對數似然,邏輯損失或者交叉熵損失,它是多元邏輯迴歸以及一些擴充演算法,比如神經網路中使用的損失函式。它被定義為,對於一個給定的概率分類器,在預測概率為條件的情況下,真實概率發生的可能性的負對數(如何得到這個損失函式的證明過程和推導過程在邏輯迴歸的章節中有完整得呈現)。由於是損失,因此對數似然函式的取值越小,則證明概率估計越準確,模型越理想。值得注意得是,對數損失只能用於評估分型別模型。
對於一個樣本,如果樣本的真實標籤在{0,1}中取值,並且這個樣本在類別1下的概率估計為,則這個樣本所對應的對數損失是:
和我們邏輯迴歸的損失函式一模一樣:
只不過在邏輯迴歸的損失函式中,我們的真實標籤是由表示,預測值(概率估計)是由來表示,僅僅是表示方式的不同。注意,這裡的表示以為底的自然對數。
在sklearn中,我們可以從metrics模組中匯入我們的對數似然函式:
# 似然對數損失
from sklearn.metrics import log_loss
# 似然損失是可以衡量多分類的
log_loss(ytest,yprob)
# 第一個引數是真實的標籤,第二個引數是概率值
log_loss(ytest,logi.predict_proba(xtest))
log_loss(ytest,svc_prob)
第一個引數是真實標籤,第二個引數是我們預測的概率。如果我們使用shift tab來檢視log_loss的引數,會發現第二個引數寫著y_pred,這會讓人誤解為這是我們的預測標籤。由於log_loss是專門用於產出概率的演算法的,因此它假設我們預測出的y就是以概率形式呈現,但在sklearn當中,我們的y_pred往往是已經根據概率歸類後的類別{0,1,2},真正的概率必須要以介面predict_proba來呼叫,千萬避免混淆。
注意到,我們用log_loss得出的結論和我們使用布里爾分數得出的結論不一致:當使用布里爾分數作為評判標準的時候,SVC的估計效果是最差的,邏輯迴歸和貝葉斯的結果相接近。而使用對數似然的時候,雖然依然是邏輯迴歸最強大,但貝葉斯卻沒有SVC的效果好。為什麼會有這樣的不同呢?
因為邏輯迴歸和SVC都是以最優化為目的來求解模型,然後進行分類的演算法。而樸素貝葉斯中,卻沒有最優化的過程。對數似然函式直接指向模型最優化的方向,甚至就是邏輯迴歸的損失函式本身,因此在邏輯迴歸和SVC上表現得更好。
那什麼時候使用對數似然,什麼時候使用布里爾分數?
在現實應用中,對數似然函式是概率類模型評估的黃金指標,往往是我們評估概率類模型的優先選擇。但是它也有一些缺點,首先它沒有界,不像布里爾分數有上限,可以作為模型效果的參考。其次,它的解釋性不如布里爾分數,很難與非技術人員去交流對數似然存在的可靠性和必要性。第三,它在以最優化為目標的模型上明顯表現更好。而且,它還有一些數學上的問題,比如不能接受為0或1的概率,否則的話對數似然就會取到極限值(考慮以為底的自然對數在取到0或1的時候的情況)。所以因此通常來說,我們有以下使用規則:
需求 | 優先使用對數似然 | 優先使用布里爾分數 |
衡量模型 | 要對比多個模型,或者衡量模型的不同變化 | 衡量單一模型的表現 |
可解釋性 | 機器學習和深度學習之間的行家交流,學術論文 | 商業報告,老闆開會,業務模型的衡量 |
最優化指向 | 邏輯迴歸,SVC | 樸素貝葉斯 |
數學問題 | 概率只能無限接近於0或1,無法取到0或1 | 概率可以取到0或1,比如樹,隨機森林 |
回到我們的貝葉斯來看,如果貝葉斯的模型效果不如其他模型,而我們又不想更換模型,那怎麼辦呢?如果以精確度為指標來調整引數,貝葉斯估計是無法拯救了——不同於SVC和邏輯迴歸,貝葉斯的原理簡單,根本沒有什麼可用的引數。但是產出概率的演算法有自己的調節方式,就是調節概率的校準程度。校準程度越高,模型對概率的預測越準確,演算法在做判斷時就越有自信,模型就會更穩定。如果我們追求模型在概率預測上必須儘量貼近真實概率,那我們就可以使用可靠性曲線來調節概率的校準程度。
2.2.3,可靠性曲線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)
xtrain,xtest,ytrain,ytest=train_test_split(X,y,test_size=0.99,random_state=0)
- 建立模型
gns=GaussianNB()
gns=gns.fit(xtrain,ytrain)
# 匯出分類的結果
y_pred=gns.predict(xtest)
# 匯出預測概率
y_prob=gns.predict_proba(xtest)[:,1]
- 繪製影像
y_prob.shape
# 我們的預測概率,作為橫座標
# ytest 我們的真實標籤,作為縱座標
import pandas as pd
#在我們的橫縱表座標上,概率是由順序的(由小到大),為了讓圖形規整一些,我們要先對預測概率和真實標籤按照預測
# 概率進行一個排序,這一點我們通過DataFrame來實現
df=pd.DataFrame({"ytrue":ytest[:500],"probability":y_prob[:500]})
# 對df中的資料進行排序
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.plot(df["probability"],df["ytrue"],"s-",label="%s (%1.3f)" % ("Bayes", 3))
ax1.set_ylabel("True label")
ax1.set_xlabel("predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()
這個影像看起來非常可怕,完全不止所云!為什麼存在這麼多上下穿梭的直線?反應快的小夥伴可能很快就發現了,我們是按照預測概率的順序進行排序的,而預測概率從0開始到1的過程中,真實取值不斷在0和1之間變化,而我們是繪製折線圖,因此無數個縱座標分佈在0和1的被連結起來了,所以看起來如此混亂。
可以看到,由於真實標籤是0和1,所以所有的點都在y=1和y=0這兩條直線上分佈,這完全不是我們希望看到的影像。回想一下我們的可靠性曲線的橫縱座標:橫座標是預測概率,而縱座標是真實值,我們希望預測概率很靠近真實值,那我們的真實取值必然也需要是一個概率才可以,如果使用真實標籤,那我們繪製出來的影像完全是沒有意義的。但是,我們去哪裡尋找真實值的概率呢?這是不可能找到的——如果我們能夠找到真實的概率,那我們何必還用演算法來估計概率呢,直接去獲取真實的概率不就好了麼?所以真實概率在現實中是不可獲得的。但是,我們可以獲得類概率的指標來幫助我們進行校準。一個簡單的做法是,將資料進行分箱,然後規定每個箱子中真實的少數類所佔的比例為這個箱上的真實概率trueproba,這個箱子中預測概率的均值為這個箱子的預測概率predproba,然後以trueproba為縱座標,predproba為橫座標,來繪製我們的可靠性曲線。
來看下面這張表,這是一組資料不分箱時表現出來的影像:
再來看看分箱之後的影像:
可見,分箱之後樣本點的特徵被聚合到了一起,曲線明顯變得單調且平滑。這種分箱操作本質相當於是一種平滑,在sklearn中,這樣的做法可以通過繪製可靠性曲線的類calibration_curve來實現。和ROC曲線類似,類calibration_curve可以幫助我們獲取我們的橫縱座標,然後使用matplotlib來繪製影像。該類有如下引數:
引數 | 含義 |
y_true | 真實標籤 |
y_prob | 預測返回的,正類別下的概率值或置信度 |
normalize | 布林值,預設False 是否將y_prob中輸入的內容歸一化到[0,1]之間,比如說,當y_prob並不是真正的概率的時候可使用。如果這是為True,則會將y_prob中最小的值歸一化為0,最大值歸一化為1。 |
n_bins | 整數值,表示分箱的個數。如果箱數很大,則需要更多的資料 |
返回 | 含義 |
trueproba | 可靠性曲線的縱座標,結構為(n_bins, ),是每個箱子中少數類(Y=1)的佔比 |
predproba | 可靠性曲線的橫座標,結構為(n_bins, ),是每個箱子中概率的均值 |
- 重新繪製上面的可靠性曲線
from sklearn.calibration import calibration_curve
# 分成10箱,ytest代表是真實標籤,y_prob標示返回的概率
trueproba,predproba=calibration_curve(ytest,y_prob,n_bins=10)
# predproba代表每一個箱子中概率的均值
# trueproba代表每一個箱子中少數類的佔比,均值
trueproba
# 我們要求分10個箱子,所以會返回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", 10))# 10代表是分10個箱子
ax1.set_ylabel("True label")
ax1.set_xlabel("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]):
ax1=axes[ind]
trueproba,predproba=calibration_curve(ytest,y_prob,n_bins=i)
ax1.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated") #得做一條對角線來對比呀
ax1.plot(predproba,trueproba,"s-",label="%s (%1.3f)" % ("Bayes", i))
ax1.set_ylabel("True label")
ax1.set_xlabel("predcited probability")
ax1.set_ylim([-0.05, 1.05])
ax1.legend()
plt.show()
# 當n=100的時候,明顯是過擬合
# 我們繪製出的影像,越接近y=x這條直線,越好
很明顯可以看出,n_bins越大,箱子越多,概率校準曲線就越精確,但是太過精確的曲線不夠平滑,無法和我們希望的完美概率密度曲線相比較。n_bins越小,箱子越少,概率校準曲線就越粗糙,雖然靠近完美概率密度曲線,但是無法真實地展現模型概率預測地結果。因此我們需要取一個既不是太大,也不是太小的箱子個數,讓概率校準曲線既不是太精確,也不是太粗糙,而是一條相對平滑,又可以反應出模型對概率預測的趨勢的曲線。通常來說,建議先試試看箱子數等於10的情況。箱子的數目越大,所需要的樣本量也越多,否則曲線就會太過精確。
邏輯迴歸的概率估計是最接近完美的概率校準曲線,所以邏輯虎歸的效果最完美。相對的,高斯樸素貝葉斯和支援向量機分類器的結果都比較糟糕。支援向量機呈現類似於sigmoid函式的形狀,而高斯樸素貝葉斯呈現和Sigmoid函式相反的形狀。
對於貝葉斯,如果概率校準曲線呈現sigmoid函式的映象的情況,則說明資料集中的特徵不是相互條件獨立的。貝葉斯原理中的”樸素“原則:特徵相互條件獨立原則被違反了(這其實是我們自己的設定,我們設定了10個冗餘特徵,這些特徵就是噪音,他們之間不可能完全獨立),因此貝葉斯的表現不夠好。
而支援向量機的概率校準曲線效果其實是典型的置信度不足的分類器(under-confident classifier)的表現:大量的樣本點集中在決策邊界的附近,因此許多樣本點的置信度靠近0.5左右,即便決策邊界能夠將樣本點判斷正確,模型本身對這個結果也不是非常確信的。相對的,離決策邊界很遠的點的置信度就會很高,因為它很大可能性上不會被判斷錯誤。支援向量機在面對混合度較高的資料的時候,有著天生的置信度不足的缺點。
2.2.4,預測概率的直方圖
我們可以通過繪製直方圖來檢視模型的預測概率的分佈。直方圖是以樣本的預測概率分箱後的結果為橫座標,每個箱中的樣本數量為縱座標的一個影像。注意,這裡的分箱和我們在可靠性曲線中的分箱不同,這裡的分箱是將預測概率均勻分為一個個的區間,與之前可靠性曲線中為了平滑的分箱完全是兩碼事。我們來繪製一下我們的直方圖:
# 直方圖的操作
fig,ax2=plt.subplots(figsize=(8,6))
name = ["GaussianBayes","Logistic","SVC"]
gnb = GaussianNB()
logi = LR(C=1., solver='lbfgs',max_iter=3000,multi_class="auto")
svc = SVC(kernel = "linear",gamma=1)
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.set_xticks([0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1])
ax2.legend(loc=9)
plt.show()
高斯貝葉斯的概率分佈是兩邊非常高,中間非常低,幾乎90%以上的樣本都在0和1的附近,可以說是置信度最高的演算法,但是貝葉斯的布里爾分數卻不如邏輯迴歸,這證明貝葉斯中在0和1附近的樣本中有一部分是被分錯的。支援向量貝葉斯完全相反,明顯是中間高,兩邊低,類似於正態分佈的狀況,證明了我們剛才所說的,大部分樣本都在決策邊界附近,置信度都徘徊在0.5左右的情況。而邏輯迴歸位於高斯樸素貝葉斯和支援向量機的中間,即沒有太多的樣本過度靠近0和1,也沒有形成像支援向量機那樣的正態分佈。一個比較健康的正樣本的概率分佈,就是邏輯迴歸的直方圖顯示出來的樣子。
概率密度曲線和概率分佈直方圖
大家也許還記得我們說過,我們是假設樣本的概率分佈為高斯分佈,然後使用高斯的方程來估計連續型變數的概率。怎麼現在我們繪製出的概率分佈結果中,高斯普斯貝葉斯的概率分佈反而完全不是高斯分佈了呢?注意,千萬不要把概率密度曲線和概率分佈直方圖混淆。
在稱重漢堡的時候所繪製的曲線,是概率密度曲線,橫座標是樣本的取值,縱座標是落在這個樣本取值區間中的樣本個數,衡量的是每個X的取值區間之內有多少樣本。服從高斯分佈的是X的取值上的樣本分佈。
現在我們的概率分佈直方圖,橫座標是概率的取值[0,1],縱座標是落在這個概率取值範圍中的樣本的個數,衡量的是每個概率取值區間之內有多少樣本。這個分佈,是沒有任何假設的。
我們已經得知了樸素貝葉斯和SVC預測概率的效果各方面都不如邏輯迴歸,那在這種情況下,我們如何來幫助模型或者演算法,讓他們對自己的預測更有信心,置信度更高呢?我們可以使用等近似迴歸來矯正概率演算法。
2.2.5,校準可靠性曲線
等近似迴歸有兩種迴歸可以使用,一種是基於Platt的Sigmoid模型的引數校準方法,一種是基於等滲迴歸(isotoniccalibration)的非引數的校準方法。概率校準應該發生在測試集上,必須是模型未曾見過的資料。在數學上,使用這兩種方式來對概率進行校準的原理十分複雜,而此過程我們在sklearn中無法進行干涉,大家不必過於去深究。
class sklearn.calibration.CalibratedClassifierCV (base_estimator=None, method=’sigmoid’, cv=’warn’)
這是一個帶交叉驗證的概率校準類,它使用交叉驗證生成器,對交叉驗證中的每一份資料,它都在訓練樣本上進行模型引數估計,在測試樣本上進行概率校準,然後為我們返回最佳的一組引數估計和校準結果。每一份資料的預測概率會被求解平均。注意,類CalibratedClassifierCV沒有介面decision_function,要檢視這個類下校準過後的模型生成的概率,必須呼叫predict_proba介面。
引數說明:
- base_estimator:需要校準其輸出決策功能的分類器,必須存在predict_proba或decision_function介面。 如果引數cv = prefit,分類
器必須已經擬合資料完畢。 - cv:整數,確定交叉驗證的策略。可能輸入是:
- None,表示使用預設的3折交叉驗證
- 任意整數,指定折數,對於輸入整數和None的情況下來說,如果是二分類,則自動使用類sklearn.model_selection.StratifiedKFold進行折數分割。如果y是連續型變數,則使用sklearn.model_selection.KFold進行分割。
- 已經使用其他類建好的交叉驗證模式或生成器cv
- 可迭代的,已經分割完畢的測試集和訓練集索引陣列
- 輸入"prefit",則假設已經在分類器上擬合完畢資料。在這種模式下,使用者必須手動確定用來擬合分類器的資料與即將倍校準的資料沒有交集
- method:進行概率校準的方法,可輸入"sigmoid"或者"isotonic"
- 輸入'sigmoid',使用基於Platt的Sigmoid模型來進行校準
- 輸入'isotonic',使用等滲迴歸來進行校準
當校準的樣本量太少(比如,小於等於1000個測試樣本)的時候,不建議使用等滲迴歸,因為它傾向於過擬合。樣本量過少時請使用sigmoids,即Platt校準。
- 函式包裝
首先,我們將之前繪製可靠性曲線和直方圖的程式碼包裝成函式。考慮函式的引數為:模型,模型的名字,資料,和需要分箱的個數。我們在這裡將直方圖和可靠性曲線打包在同一個函式中,讓他們並排顯示。
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")# 畫出y=x的直線
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: # 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=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)
gnb.score(Xtest,Ytest)
brier_score_loss(Ytest,gnb.predict_proba(Xtest)[:,1],pos_label = 1)
gnbisotonic = CalibratedClassifierCV(gnb, cv=2, method='isotonic').fit(Xtrain,Ytrain)
gnbisotonic.score(Xtest,Ytest)
brier_score_loss(Ytest,gnbisotonic.predict_proba(Xtest)[:,1],pos_label = 1)
可以看出,校準概率後,布里爾分數明顯變小了,但整體的準確率卻略有下降,這證明演算法在校準之後,儘管對概率的預測更準確了,但模型的判斷力略有降低。來思考一下:布里爾分數衡量模型概率預測的準確率,布里爾分數越低,代表模型的概率越接近真實概率,當進行概率校準後,本來標籤是1的樣本的概率應該會更接近1,而標籤本來是0的樣本應該會更接近0,沒有理由布里爾分數提升了,模型的判斷準確率居然下降了。但從我們的結果來看,模型的準確率和概率預測的正確性並不是完全一致的,為什麼會這樣呢?
對於不同的概率類模型,原因是不同的。對於SVC,決策樹這樣的模型來說,概率不是真正的概率,而更偏向於是一個“置信度”,這些模型也不是依賴於概率預測來進行分類(決策樹依賴於樹杈而SVC依賴於決策邊界),因此對於這些模型,可能存在著類別1下的概率為0.4但樣本依然被分類為1的情況,這種情況代表著——模型很沒有信心認為這個樣本是1,但是還是堅持把這個樣本的標籤分類為1了。這種時候,概率校準可能會向著更加錯誤的方向調整(比如把概率為0.4的點調節得更接近0,導致模型最終判斷錯誤),因此出現布里爾分數可能會顯示和精確性相反的趨勢。
而對於樸素貝葉斯這樣的模型,卻是另一種情況。注意在樸素貝葉斯中,我們有各種各樣的假設,除了我們的“樸素”假設,還有我們對概率分佈的假設(比如說高斯),這些假設使得我們的貝葉斯得出的概率估計其實是有偏估計,也就是說,這種概率估計其實不是那麼準確和嚴肅。我們通過校準,讓模型的預測概率更貼近於真實概率,本質是在統計學上讓演算法更加貼近我們對整體樣本狀況的估計,這樣的一種校準在一組資料集上可能表現出讓準確率上升,也可能表現出讓準確率下降,這取決於我們的測試集有多貼近我們估計的真實樣本的面貌。這一系列有偏估計使得我們在概率校準中可能出現布里爾分數和準確度的趨勢相反的情況。
當然,可能還有更多更深層的原因,比如概率校準過程中的數學細節如何影響了我們的校準,類calibration_curve中是如何分箱,如何通過真實標籤和預測值來生成校準曲線使用的橫縱座標的,這些過程中也可能有著讓布里爾分數和準確率向兩個方向移動的過程。
在現實中,當兩者相悖的時候,請務必以準確率為標準。但是這不代表說布里爾分數和概率校準曲線就無效了。概率類模型幾乎沒有引數可以調整,除了換模型之外,鮮有更好的方式幫助我們提升模型的表現,概率校準是難得的可以幫助我們針對概率提升模型的方法。
- 試試看對於SVC,哪種校準更有效呢?
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來說,兩種校正都改善了準確率和布里爾分數。可見,概率校正對於SVC非常有效。這也說明,概率校正對於原本的可靠性曲線是形容Sigmoid形狀的曲線的演算法比較有效。
在現實中,我們可以選擇調節模型的方向,我們不一定要追求最高的準確率或者追求概率擬合最好,我們可以根據自己的需求來調整模型。當然,對於概率類模型來說,由於可以調節的引數甚少,所以我們更傾向於追求概率擬合,並使用概率校準的方式來調節模型。如果你的確希望追求更高的準確率和Recall,可以考慮使用天生就非常準確的概率類模型邏輯迴歸,也可以考慮使用除了概率校準之外還有很多其他引數可調的支援向量機分類器。
3,多項式樸素貝葉斯以及其變化
3.1,多項式樸素貝葉斯MultinomialNB
多項式貝葉斯可能是除了高斯之外,最為人所知的貝葉斯演算法了。它也是基於原始的貝葉斯理論,但假設概率分佈是服從一個簡單多項式分佈。多項式分佈來源於統計學中的多項式實驗,這種實驗可以具體解釋為:實驗包括n次重複試驗,每項試驗都有不同的可能結果。在任何給定的試驗中,特定結果發生的概率是不變的
- 多項式分佈擅長的是分型別變數,在其原理假設中,的概率是離散的,並且不同下的相互獨立,互不影響。雖然sklearn中的多項式分佈也可以處理連續型變數,但現實中,如果我們真的想要處理連續型變數,我們應當使用高斯樸素貝葉斯。
- 多項式實驗中的實驗結果都很具體,它所涉及的特徵往往是次數,頻率,計數,出現與否這樣的概念,這些概念都是離散的正整數,因此sklearn中的多項式樸素貝葉斯不接受負值的輸入。
由於這樣的特性,多項式樸素貝葉斯的特徵矩陣經常是稀疏矩陣(不一定總是稀疏矩陣),並且它經常被用於文字分類。我們可以使用著名的TF-IDF向量技術,也可以使用常見並且簡單的單詞計數向量手段與貝葉斯配合使用。這兩種手段都屬於常見的文字特徵提取的方法,可以很簡單地通過sklearn來實現,
從數學的角度來看,在一種標籤類別下,我們有一組分別對應特徵的引數向量,其中n表示特徵的總數。一個表示這個標籤類別下的第i個特徵所對應的引數。這個引數被我們定義為:
記作,表示當Y=c這個條件固定的時候,一組樣本在這個特徵上的取值被取到的概率。注意,我們在高斯樸素貝葉斯中求解的概率是對於一個樣本來說,而我們現在求解的是對於一個特徵來說的概率。
對於一個在標籤類別下,結構為(m, n)的特徵矩陣來說,我們有:
其中每個都是特徵發生的次數。基於這些理解,我們通過平滑後的最大似然估計來求解引數:
對於每個特徵,是特徵下所有標籤為c的樣本的特徵取值之和,其實就是特徵矩陣中每一列的和。是所有標籤類別為c的樣本上,所有特徵的取值之和,其實就是特徵矩陣中所有元素的和。被稱為平滑係數,我們令來防止訓練資料中出現過的一些詞彙沒有出現在測試集中導致的0概率,以避免讓引數為0的情況。如果我們將設定為1,則這個平滑叫做拉普拉斯平滑,如果小於1,則我們把它叫做利德斯通平滑。兩種平滑都屬於自然語言處理中比較常用的用來平滑分類資料的統計手段。
在sklearn中,用來執行多項式樸素貝葉斯的類MultinomialNB包含如下的引數和屬性:
class sklearn.naive_bayes.MultinomialNB (alpha=1.0, fit_prior=True, class_prior=None)
引數 |
alpha : 浮點數, 可不填 (預設為1.0) 拉普拉斯或利德斯通平滑的引數,如果設定為0則表示完全沒有平滑選項。但是需要注意的是,平滑相當於人 為給概率加上一些噪音,因此設定得越大,多項式樸素貝葉斯的精確性會越低(雖然影響不是非常大),布里 爾分數也會逐漸升高。 |
fit_prior : 布林值, 可不填 (預設為True) 是否學習先驗概率。如果設定為false,則不使用先驗概率,而使用統一先驗概率(uniform prior),即認為每個標籤類出現的概率是。 |
class_prior:形似陣列的結構,結構為(n_classes, ),可不填(預設為None) 類的先驗概率。如果沒有給出具體的先驗概率則自動根據資料來進行計算。 |
通常我們在例項化多項式樸素貝葉斯的時候,我們會讓所有的引數保持預設。
from sklearn.preprocessing import MinMaxScaler
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs
from sklearn.metrics import brier_score_loss
class_1=500
class_2=500
centers=[[0,0],[2,2]]# 設定資料集的中心
clusters_std=[0.5,0.5]# 設定兩個類別的均值和方差
X,y=make_blobs(n_samples=[class_1,class_2],centers=centers,cluster_std=clusters_std
,random_state=0,shuffle=False)
xtrain,xtest,ytrain,ytest=train_test_split(X,y,random_state=0,test_size=0.3)
# 對資料進行歸一化處理,防止出現負數
mms=MinMaxScaler().fit(xtrain)
xtrain_=mms.transform(xtrain)
xtest_=mms.transform(xtest)
# 現在例項化模型,進行擬合
# 訓練的時候使用歸一化之後的特徵矩陣
mnb=MultinomialNB().fit(xtrain_,ytrain)
# 獲取每一個特徵的先驗概率
# 返回每一個標籤類對應的先驗概率
mnb.class_log_prior_
mnb.class_log_prior_.shape
# 形狀永遠等於標籤中所帶的類別數量
# 返回每一個固定類別標籤下的每一個特徵的對數概率log(p(xi|y))
mnb.feature_log_prob_
# 矩陣形狀是2*2,因為有2個特徵,2個類別
# 在fit時每一個標籤類別下包含的樣本數
# 當fit時候介面中sample_weight被設定的時候,該介面返回的值也會受到加權的影響
mnb.class_count_
# 返回和我們標籤類別一樣的結構
mnb.predict(xtest)
# 返回我們的預測結果
# 返回每一個樣本在每一個標籤下面的取值概率
mnb.predict_proba(xtest)
# 返回我們的準確度
mnb.score(xtest,ytest)
brier_score_loss(ytest,mnb.predict_proba(xtest)[:,1],pos_label=1)
# 返回我們的布里爾分數
- 把連續性變數轉換為離散型變數
#來試試看把Xtiain轉換成分型別資料吧
#注意我們的Xtrain沒有經過歸一化,因為做啞變數之後自然所有的資料就不會又負數了
from sklearn.preprocessing import KBinsDiscretizer
kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain)
Xtrain_ = kbs.transform(Xtrain)
Xtest_ = kbs.transform(Xtest)
mnb = MultinomialNB().fit(Xtrain_, Ytrain)
mnb.score(Xtest_,Ytest)
brier_score_loss(Ytest,mnb.predict_proba(Xtest_)[:,1],pos_label=1)
可以看出,多項式樸素貝葉斯的基本操作和程式碼都非常簡單。同樣的資料,如果採用啞變數方式的分箱處理,多項式貝葉斯的效果會突飛猛進。
3.2,伯努利樸素貝葉斯BernoulliNB
多項式樸素貝葉斯可同時處理二項分佈(拋硬幣)和多項分佈(擲骰子),其中二項分佈又叫做伯努利分佈,它是一種現實中常見,並且擁有很多優越數學性質的分佈。因此,既然有著多項式樸素貝葉斯,我們自然也就又專門用來處理二項分佈的樸素貝葉斯:伯努利樸素貝葉斯。
伯努利貝葉斯類BernoulliN假設資料服從多元伯努利分佈,並在此基礎上應用樸素貝葉斯的訓練和分類過程。多元伯努利分佈簡單來說,就是資料集中可以存在多個特徵,但每個特徵都是二分類的,可以以布林變數表示,也可以表示為{0,1}或者{-1,1}等任意二分類組合。因此,這個類要求將樣本轉換為二分類特徵向量,如果資料本身不是二分類的,那可以使用類中專門用來二值化的引數binarize來改變資料。
伯努利樸素貝葉斯與多項式樸素貝葉斯非常相似,都常用於處理文字分類資料。但由於伯努利樸素貝葉斯是處理二項分佈,所以它更加在意的是“存在與否”,而不是“出現多少次”這樣的次數或頻率,這是伯努利貝葉斯與多項式貝葉斯的根本性不同。
class sklearn.naive_bayes.BernoulliNB (alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None)
引數說明:
alpha : 浮點數, 可不填 (預設為1.0) 拉普拉斯或利德斯通平滑的引數,如果設定為0則表示完全沒有平滑選項。但是需要注意的是,平滑相當於人 為給概率加上一些噪音,因此設定得越大,多項式樸素貝葉斯的精確性會越低(雖然影響不是非常大),布里 爾分數也會逐漸升高。 |
binarize : 浮點數或None,可不填,預設為0 將特徵二值化的閾值,如果設定為None,則會假定說特徵已經被二值化完畢 |
fit_prior : 布林值, 可不填 (預設為True) 是否學習先驗概率。如果設定為false,則不使用先驗概率,而使用統一先驗概率(uniform prior),即認為每個標籤類出現的概率 |
class_prior:形似陣列的結構,結構為(n_classes, ),可不填(預設為None) 類的先驗概率。如果沒有給出具體的先驗概率則自動根據資料來進行計算。 |
# 使用伯努利貝葉斯或者多項式貝葉斯,一定要注意我們的資料結構
from sklearn.naive_bayes import MultinomialNB, GaussianNB, BernoulliNB
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs
from sklearn.preprocessing import KBinsDiscretizer
from sklearn.metrics import brier_score_loss as BS,recall_score,roc_auc_score as AUC
# 處理二項分佈的樸素貝葉斯----伯努利樸素貝葉斯
from sklearn.naive_bayes import BernoulliNB
# 接下來進行資料的歸一化
mms=MinMaxScaler().fit(xtrain)
xtrain__=mms.transform(xtrain)
xtest__=mms.transform(xtest)
# 先將數值全部進行歸一化後,然後設定一個閾值,將數值進行二值化
# 先不設定二值化
bnl=BernoulliNB().fit(xtrain__,ytrain)
bnl.score(xtest__,ytest)
brier_score_loss(ytest,bnl.predict_proba(xtest__)[:,1],pos_label=1)
# 現在設定二值化的閾值
bnl=BernoulliNB(binarize=0.5).fit(xtrain__,ytrain)
bnl.score(xtest__,ytest)
和多項式貝葉斯一樣,伯努利貝葉斯的結果也受到資料結構非常大的影響。因此,根據資料的模樣選擇貝葉斯,是貝葉斯模型選擇中十分重要的一點。
3.3,探索貝葉斯:貝葉斯的樣本不均衡問題
接下來,我們來探討一個分類演算法永遠都逃不過的核心問題:樣本不平衡。貝葉斯由於分類效力不算太好,因此對樣本不平衡極為敏感,我們接下來就來看一看樣本不平衡如何影響了貝葉斯。
# 檢視所有貝葉斯樣本不均衡的表現
name = ["Multinomial","Gaussian","Bernoulli"]
models = [MultinomialNB(),GaussianNB(),BernoulliNB()]
for name,clf in zip(name,models):
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y
,test_size=0.3
,random_state=420)
if name != "Gaussian":
kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain)
Xtrain = kbs.transform(Xtrain)
Xtest = kbs.transform(Xtest)
clf.fit(Xtrain,Ytrain)
y_pred = clf.predict(Xtest)
proba = clf.predict_proba(Xtest)[:,1]
score = clf.score(Xtest,Ytest)
print(name)
print("\tBrier:{:.3f}".format(BS(Ytest,proba,pos_label=1)))
print("\tAccuracy:{:.3f}".format(score))
print("\tRecall:{:.3f}".format(recall_score(Ytest,y_pred)))
print("\tAUC:{:.3f}".format(AUC(Ytest,proba)))
# 在這裡解釋一下:recall返傭的是少數類判斷正確的比例
從結果上來看,多項式樸素貝葉斯判斷出了所有的多數類樣本,但放棄了全部的少數類樣本,受到樣本不均衡問題影響最嚴重。高斯比多項式在少數類的判斷上更加成功一些,至少得到了43.8%的recall。伯努利貝葉斯雖然整體的準確度和布里爾分數不如多項式和高斯樸素貝葉斯和,但至少成功捕捉出了77.1%的少數類。可見,伯努利貝葉斯最能夠忍受樣本不均衡問題。
可是,伯努利貝葉斯只能用於處理二項分佈資料,在現實中,強行將所有的資料都二值化不會永遠得到好結果,在我們有多個特徵的時候,我們更需要一個個去判斷究竟二值化的閾值該取多少才能夠讓演算法的效果優秀。這樣做無疑是非常低效的。那如果我們的目標是捕捉少數類,我們應該怎麼辦呢?高斯樸素貝葉斯的效果雖然比多項式好,但是也沒有好到可以用來幫助我們捕捉少數類的程度——43.8%,還不如拋硬幣的結果。因此,孜孜不倦的統計學家們改進了樸素貝葉斯演算法,修正了包括無法處理樣本不平衡在內的傳統樸素貝葉斯的眾多缺點,得到了新興貝葉斯演算法:補集樸素貝葉斯。
3.4,改進多項式樸素貝葉斯:補集樸素貝葉斯ComplementNB
補集樸素貝葉斯(complement naive Bayes,CNB)演算法是標準多項式樸素貝葉斯演算法的改進。CNB的發明小組創造出CNB的初衷是為了解決貝葉斯中的“樸素”假設帶來的各種問題,他們希望能夠創造出數學方法以逃避樸素貝葉斯中的樸素假設,讓演算法能夠不去關心所有特徵之間是否是條件獨立的。以此為基礎,他們創造出了能夠解決樣本不平衡問題,並且能夠一定程度上忽略樸素假設的補集樸素貝葉斯。在實驗中,CNB的引數估計已經被證明比普通多項式樸素貝葉斯更穩定,並且它特別適合於樣本不平衡的資料集。有時候,CNB在文字分類任務上的表現有時能夠優於多項式樸素貝葉斯,因此現在補集樸素貝葉斯也開始逐漸流行。
簡單來說,CNB使用來自每個標籤類別的補集的概率,並以此來計算每個特徵的權重。
其中表示每個樣本, 表示在樣本上對於特徵的下的取值,在文字分類中通常是計數的值或者是TF-IDF值。是像標準多項式樸素貝葉斯中一樣的平滑係數。可以看出,這個看似複雜的公式其實很簡單, 其實指的就是,一個特徵下,所有標籤類別不等於c值的樣本的特徵取值之和。而其實就是,所有特徵下,素有標籤類別不等於c值得樣本的特徵取值之和。其實就是多項式分佈的逆向思路。
對於這個概率,我們對它取對數後得到權重。我們還可以選擇除以它的L2正規化,以解決了在多項式分佈中,特徵取值比較多的樣本(比如說比較長的文件)支配引數估計的情況。很多時候我們的特徵矩陣是稀疏矩陣,但也不排除在有一些隨機事件中,可以一次在兩個特徵中取值的情況。如果一個樣本下的很多個隨機事件可以同時發生,並且互不干涉,那麼這個樣本上可能有很多個特徵下都有取值——文字分類中就有很多這樣的情況。
基於這個權重,補充樸素貝葉斯中一個樣本的預測規則為:
即我們求解出的最小補集概率所對應的標籤就是樣本的標籤,因為的概率越小,則意味著的概率越大,所以樣本屬於標籤類別c。
在sklearn中,補集樸素貝葉斯由類ComplementNB完成,它包含的引數和多項式貝葉斯也非常相似:
class sklearn.naive_bayes.ComplementNB (alpha=1.0, fit_prior=True, class_prior=None, norm=False)
alpha : 浮點數, 可不填 (預設為1.0) 拉普拉斯或利德斯通平滑的引數,如果設定為0則表示完全沒有平滑選項。但是需要注意的是,平滑相當於人 為給概率加上一些噪音,因此設定得越大,多項式樸素貝葉斯的精確性會越低(雖然影響不是非常大),布里 爾分數也會逐漸升高。 |
norm : 布林值,可不填,預設False 在計算權重的時候是否適用L2正規化來規範權重的大小。預設不進行規範,即不跟從補集樸素貝葉斯演算法的全部 內容,如果希望進行規範,請設定為True。 |
fit_prior : 布林值, 可不填 (預設為True) 是否學習先驗概率。如果設定為false,則不使用先驗概率,而使用統一先驗概率(uniform prior),即認為每個標籤類出現的概率是。 |
class_prior:形似陣列的結構,結構為(n_classes, ),可不填(預設為None) 類的先驗概率。如果沒有給出具體的先驗概率則自動根據資料來進行計算。 |
from sklearn.naive_bayes import ComplementNB
from time import time
import datetime
name = ["Multinomial","Gaussian","Bernoulli","Complement"]
models = [MultinomialNB(),GaussianNB(),BernoulliNB(),ComplementNB()]
for name,clf in zip(name,models):
times = time()
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y
,test_size=0.3
,random_state=420)
#預處理
if name != "Gaussian":
kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain)
Xtrain = kbs.transform(Xtrain)
Xtest = kbs.transform(Xtest)
clf.fit(Xtrain,Ytrain)
y_pred = clf.predict(Xtest)
proba = clf.predict_proba(Xtest)[:,1]
score = clf.score(Xtest,Ytest)
print(name)
print("\tBrier:{:.3f}".format(BS(Ytest,proba,pos_label=1)))
print("\tAccuracy:{:.3f}".format(score))
print("\tRecall:{:.3f}".format(recall_score(Ytest,y_pred)))
print("\tAUC:{:.3f}".format(AUC(Ytest,proba)))
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))
可以發現,補集樸素貝葉斯犧牲了部分整體的精確度和布里爾指數,但是得到了十分高的召回率Recall,捕捉出了98.7%的少數類,並且在此基礎上維持了和原本的多項式樸素貝葉斯一致的AUC分數。和其他的貝葉斯演算法比起來,我們的補集樸素貝葉斯的執行速度也十分優秀。如果我們的目標是捕捉少數類,那我們毫無疑問會希望選擇補集樸素貝葉斯作為我們的演算法。
參考資料:
[1] https://www.bilibili.com/video/BV1WJ411k7L3?p=228
相關文章
- [譯] Sklearn 中的樸素貝葉斯分類器
- 樸素貝葉斯分類
- 樸素貝葉斯和半樸素貝葉斯(AODE)分類器Python實現Python
- 樸素貝葉斯/SVM文字分類文字分類
- 機器學習Sklearn系列:(四)樸素貝葉斯機器學習
- 機器學習之樸素貝葉斯分類機器學習
- 分類演算法-樸素貝葉斯演算法
- 樸素貝葉斯實現文件分類
- 樸素貝葉斯分類流程圖介紹流程圖
- HanLP-樸素貝葉斯分類預測缺陷HanLP
- 樸素貝葉斯--新浪新聞分類例項
- 樸素貝葉斯模型模型
- (實戰)樸素貝葉斯實現垃圾分類_201121
- 簡單易懂的樸素貝葉斯分類演算法演算法
- 樸素貝葉斯分類-實戰篇-如何進行文字分類文字分類
- ML-樸素貝葉斯
- 概率分類之樸素貝葉斯分類(垃圾郵件分類python實現)Python
- 機器學習經典演算法之樸素貝葉斯分類機器學習演算法
- 樸素貝葉斯與Laplace平滑
- 樸素貝葉斯演算法演算法
- 樸素貝葉斯—印第安人
- 第7章 基於樸素貝葉斯的垃圾郵件分類
- 有監督學習——支援向量機、樸素貝葉斯分類
- 高階人工智慧系列(一)——貝葉斯網路、機率推理和樸素貝葉斯網路分類器人工智慧
- 樸素貝葉斯分類和預測演算法的原理及實現演算法
- 監督學習之樸素貝葉斯
- 04_樸素貝葉斯演算法演算法
- 【機器學習基礎】樸素貝葉斯對B站彈幕分類(是否永久封禁)機器學習
- 使用樸素貝葉斯過濾垃圾郵件
- 【火爐煉AI】機器學習010-用樸素貝葉斯分類器解決多分類問題AI機器學習
- Python實現 利用樸素貝葉斯模型(NBC)進行問句意圖分類Python模型
- 樸素貝葉斯演算法的實現與推理演算法
- 100天搞定機器學習|Day15 樸素貝葉斯機器學習
- Python機器學習筆記:樸素貝葉斯演算法Python機器學習筆記演算法
- 機器學習演算法(二): 樸素貝葉斯(Naive Bayes)機器學習演算法AI
- Python機器學習 — 樸素貝葉斯演算法(Naive Bayes)Python機器學習演算法AI
- 【火爐煉AI】機器學習013-用樸素貝葉斯分類器估算個人收入階層AI機器學習
- ML-樸素貝葉斯-先驗分佈/後驗分佈/似然估計