赞
踩
菜菜的scikit-learn课堂——sklearn中的朴素贝叶斯
贝叶斯统计
在贝叶斯统计学与传统的经典统计学的主要区别就是。是否利用了先验信息。
贝叶斯统计学是基于总体信息、样本信息、先验信息这三种信息进行统计推断的方法和理论
先验信息:就是在抽样之前,有关统计推断问题中未知参数的一些信息,一般先验信息来自经验或历史资料
先验概率(prior probability):就是指根据以往经验和分析得到的概率,如全概率公式,它往往作为"由因求果"问题中的"因"出现的概率。
先验分布:就是在抽取样本 X 之前,对参数 θ \theta θ 可能取值的认知,即对先验信息进行加工获得的分布称为先验分布
贝叶斯学派基本观点:对于任意未知量 θ \theta θ 都可看作随机变量,可用一个概率分布去描述,这个分布就是先验分布 π ( θ ) \pi \left( \theta \right) π(θ) ,在获得样本之后,总体分布、样本分布、先验分布通过贝叶斯公式结合起来,得到一个关于未知量 θ \theta θ 的新分布——后验分布。之后关于 θ \theta θ 的统计推断都可以基于 θ \theta θ 的后验分布进行。
算法得出的结论,永远不是100%确定的,更多的是判断出了一种“样本的标签更可能是某类的可能性”,而非一种“确定”。
朴素贝叶斯是一种直接衡量标签和特征之间的概率关系的有监督学习算法,是一种专注分类的算法。朴素贝叶斯的算法根源就是基于概率论和数理统计的贝叶斯理论,因此它是根正苗红的概率模型。
朴素贝叶斯被认为是最简单的分类算法之一
条件概率
若事件 A 满足P(A)>0,对任意事件 B ,称
P
(
B
∣
A
)
=
P
(
A
B
)
P
(
A
)
P\left( B|A \right) =\frac{P\left( AB \right)}{P\left( A \right)}
P(B∣A)=P(A)P(AB)
为在 A 发生的条件下,B 发生的条件概率
全概率公式:
设
A
1
,
A
2
,
⋯
,
A
n
A_1,A_2,\cdots ,A_n
A1,A2,⋯,An (n 为有限或者无穷),是样本空间
Ω
\varOmega
Ω 中的一个完备事件群(或为
Ω
\varOmega
Ω 的一个分划),满足
A
i
∩
A
j
=
⊘
A_i\cap A_j=\oslash
Ai∩Aj=⊘ 且
∑
i
=
1
n
A
i
=
Ω
\sum_{i=1}^n{A_i}=\varOmega
∑i=1nAi=Ω,设 B 为
Ω
\varOmega
Ω 中的一个事件,则全概率公式为:
∣ P ( B ) = ∑ i = 1 n P ( B ∣ A i ) P ( A i ) = P ( B ∣ A 1 ) P ( A 1 ) + P ( B ∣ A 2 ) P ( A 2 ) + ⋯ + P ( B ∣ A n ) P ( A n ) \left| P(B)=∑ni=1P(B|Ai)P(Ai)=P(B|A1)P(A1)+P(B|A2)P(A2)+⋯+P(B|An)P(An) \right. ∣∣∣∣∣∣P(B)=∑i=1nP(B∣Ai)P(Ai)=P(B∣A1)P(A1)+P(B∣A2)P(A2)+⋯+P(B∣An)P(An)
贝叶斯公式:
常见的
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P\left( A|B \right) =\frac{P\left( B|A \right) P\left( A \right)}{P\left( B \right)} P(A∣B)=P(B)P(B∣A)P(A)
具体为
若 A 1 , A 2 , ⋯ , A n A_1,A_2,\cdots ,A_n A1,A2,⋯,An 是 Ω \varOmega Ω 的一个划分,且 P ( A i ) > 0 , i = 1 , 2 , ⋯ , n P\left( A_i \right) >0\text{,}i=1,2,\cdots ,n P(Ai)>0,i=1,2,⋯,n,则对于任何概率不为零的事件 B ,有
P ( A i ∣ B ) = P ( A i ) P ( B ∣ A i ) P ( B ) = P ( A i ) P ( B ∣ A i ) ∑ j P ( A j ) P ( B ∣ A j ) , i = 1 , 2 , ⋯ , n P\left( A_i|B \right) =\frac{P\left( A_i \right) P\left( B|A_i \right)}{P\left( B \right)}=\frac{P\left( A_i \right) P\left( B|A_i \right)}{\sum_j{P\left( A_j \right) P\left( B|A_j \right)}}\,\,\text{,}i=1,2,\cdots ,n P(Ai∣B)=P(B)P(Ai)P(B∣Ai)=∑jP(Aj)P(B∣Aj)P(Ai)P(B∣Ai),i=1,2,⋯,n
可以
把 全概率公式看作由 “原因”推“结果”,已知各个原因,推结果发生的概率
把贝叶斯公式看作由“结果”推“原因”,现在有了结果 B ,在导致 B 发生的众多原因中,到底哪个原因导致了 B 发生(或者到底哪个原因导致 B 发生的可能性最大?)
在机器学习当中,对每一个样本,不可能只有一个特征 X,而是会存在着包含n个特征的取值的特征向量 X \boldsymbol{X} X 。
因此机器学习中的后验概率,被写作 P ( Y ∣ X ) P\left( Y|\boldsymbol{X} \right) P(Y∣X),其中 X \boldsymbol{X} X 中包含样本在n个特征 上的分别的取值 ,由此可i以表示为
X = { X 1 = x 1 , X 2 = x 2 , ⋯ , X n = x n } \boldsymbol{X}=\left\{ X_1=x_1,X_2=x_2,\cdots ,X_n=x_n \right\} X={X1=x1,X2=x2,⋯,Xn=xn}
假设现在有两个特征 X 1 、 X 2 X_1\text{、}X_2 X1、X2
可以证明
若推广到n个 X 上,则有:
P
(
X
∣
Y
=
1
)
=
∏
i
=
1
n
P
(
X
i
=
x
i
∣
Y
=
1
)
P\left( \boldsymbol{X|}Y=1 \right) =\prod_{i=1}^n{P\left( X_i=x_i|Y=1 \right)}
P(X∣Y=1)=i=1∏nP(Xi=xi∣Y=1)
这个式子证明,在Y=1的条件下,多个特征的取值被同时取到的概率,就等于Y=1的条件下,多个特征的取值被分别取到的概率相乘。
其中,假设 X 1 X_1 X1 与 X 2 X_2 X2 是有条件独立,则可以让公式
P ( X 1 ∣ X 2 , Y = 1 ) = P ( X 1 ∣ Y = 1 ) P\left( X_1|X_2,Y=1 \right) =P\left( X_1|Y=1 \right) P(X1∣X2,Y=1)=P(X1∣Y=1)
这是在假设 X 2 X_2 X2 是一个对 X 1 X_1 X1 在某个条件下的取值完全无影响的变量。
假设特征之间是有条件独立的,可以解决众多问题,也简化了很多计算过程,这是朴素贝叶斯被称为”朴素“的理由。
朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法
朴素贝叶斯方法是在贝叶斯算法的基础上进行了相应的简化,即假定给定目标值时属性之间相互条件独立。虽然这个简化方式在一定程度上降低了贝叶斯分类算法的分类效果,但是在实际的应用场景中,极大地简化了贝叶斯方法的复杂性。
因此,贝叶斯在特征之间有较多相关性的数据集上表现不佳,而现实中的数据多多少少都会有一些相关性,所以贝叶斯的分类效力在分类算法中不算特别强大。同时,一些影响特征本身的相关性的降维算法,比如PCA和SVD,和贝叶斯连用效果也会不佳。
对于 P ( X ) P\left( \boldsymbol{X} \right) P(X) 。可以使用全概率公式来求解 :
P ( X ) = ∑ i = 1 m P ( y i ) P ( X ∣ Y i ) P\left( \boldsymbol{X} \right) =\sum_{\boldsymbol{i}=1}^{\boldsymbol{m}}{P\left( y_i \right) P\left( \boldsymbol{X|}Y_i \right)} P(X)=i=1∑mP(yi)P(X∣Yi)
其中 m 代表标签的种类,也就是说,对于二分类而言我们有:
P ( X ) = P ( Y = 1 ) P ( X ∣ Y = 1 ) + P ( Y = 0 ) P ( X ∣ Y = 0 ) P\left( \boldsymbol{X} \right) =P\left( Y=1 \right) P\left( \boldsymbol{X|}Y=1 \right) +P\left( Y=0 \right) P\left( \boldsymbol{X|}Y=0 \right) P(X)=P(Y=1)P(X∣Y=1)+P(Y=0)P(X∣Y=0)
朴素贝叶斯是一个不建模的算法
训练集和测试集都来自于同一个不可获得的大样本下,并且这个大样本下的各种属性所表现出来的规律应当是一致的,因此训练集上计算出来的各种概率,可以直接放到测试集上来使用。即便不建模,也可以完成分类。
对于同一个样本来说,在二分类状况下我们可以有:
∣
P
(
Y
=
1
∣
X
)
=
P
(
Y
=
1
)
∏
i
=
1
n
P
(
x
i
∣
Y
=
1
)
/
P
(
X
)
P
(
Y
=
0
∣
X
)
=
P
(
Y
=
0
)
∏
i
=
1
n
P
(
x
i
∣
Y
=
0
)
/
P
(
X
)
P
(
Y
=
1
∣
X
)
+
P
(
Y
=
0
∣
X
)
=
1
\left| \right.
∣∣∣∣∣∣∣∣∣∣P(Y=1∣X)=P(Y=1)∏i=1nP(xi∣Y=1)/P(X)P(Y=0∣X)=P(Y=0)∏i=1nP(xi∣Y=0)/P(X)P(Y=1∣X)+P(Y=0∣X)=1
在分类的时候,选择 P ( Y = 1 ∣ X ) P\left( Y=1|\boldsymbol{X} \right) P(Y=1∣X) 和 P ( Y = 0 ∣ X ) P\left( Y=0|\boldsymbol{X} \right) P(Y=0∣X) 中较大的一个所对应的Y的取值,作为这个样本的分类。
在比较两个类别的时候,两个概率计算的分母是一致的,因此可以不用计算分母,只考虑分子的大小。当我们分别计算出分子的大小之后,就可以通过让两个分子相加,来获得分母的值,以此来避免计算一个样本上所有特征下的概率 P ( X ) P\left( \boldsymbol{X} \right) P(X)。
这个过程,被称为“最大后验估计”(MAP)。
在最大后验估计中,只需要求解分子,主要是求解一个样本下每个特征取值下的概率 P ( x i ∣ Y = y i ) P\left( x_i|Y=y_i \right) P(xi∣Y=yi),再求连乘便能够获得相应的概率。
在现实中,要求解分子也会有各种各样的问题。比如说,测试集中出现的某种概率组合,是训练集中从未出现的状况,这种时候就会出现某一个概率为0的情况,贝叶斯概率的分子就会为0。还有,现实中的大多数标签还是连续型变量,要处理连续型变量的概率,就不是单纯的数样本个数的占比的问题了。
处理连续型变量,有两种方法。
第一种是把连续型变量分成 j 个箱,把连续型强行变成分类型变量。分箱后,将每个箱中的均值 x ˉ i \bar{x}_i xˉi 当作一个特征 X i X_i Xi 上的取值,然后再计算箱 j 中 Y=1 所占的比例,就是 P ( x i ∣ Y = 1 ) P\left( x_i|Y=1 \right) P(xi∣Y=1)
这个过程的主要问题是,箱子不能太大也不能太小,
如果箱子太大,就失去了分箱的基本意义,
如果箱子太小,可能每个箱子里就没有足够的样本来帮助我们计算
因此必须要适当地衡量分箱效果。
第二种
参考教材(这里符号与上面不同):
对于
θ
\theta
θ 是连续型随机变量时,获得样本 X 后,
θ
\theta
θ 的后验分布就是在给定 X=x 条件下
θ
\theta
θ 的条件分布,记为
π
(
θ
∣
x
)
\pi \left( \theta |x \right)
π(θ∣x) ,贝叶斯公式为
π
(
θ
∣
x
)
=
h
(
x
,
θ
)
m
(
x
)
=
f
(
x
∣
θ
)
π
(
θ
)
∫
Θ
f
(
x
∣
θ
)
π
(
θ
)
d
θ
\pi \left( \theta |x \right) =\frac{h\left( x,\theta \right)}{m\left( x \right)}=\frac{f\left( x|\theta \right) \pi \left( \theta \right)}{\int_{\varTheta}{f\left( x|\theta \right) \pi \left( \theta \right) d\theta}}
π(θ∣x)=m(x)h(x,θ)=∫Θf(x∣θ)π(θ)dθf(x∣θ)π(θ)
其中 ,
π
(
θ
)
\pi \left( \theta \right)
π(θ) 表示随机变量
θ
\theta
θ 的概率函数
θ \theta θ 的分布函数用 F π ( θ ) F^{\pi}\left( \theta \right) Fπ(θ) 表示
对于 θ \theta θ 是离散型随机变量时,贝叶斯公式为
P ( θ = θ i X = ∣ x ) = P ( X = x ∣ θ = θ i ) P ( θ = θ i ) ∑ j P ( X = x ∣ θ = θ j ) P ( θ = θ j ) P\left( \theta =\theta _iX=|x \right) =\frac{P\left( X=x|\theta =\theta _i \right) P\left( \theta =\theta _i \right)}{\sum_j{P\left( X=x|\theta =\theta _j \right) P\left( \theta =\theta _j \right)}} P(θ=θiX=∣x)=∑jP(X=x∣θ=θj)P(θ=θj)P(X=x∣θ=θi)P(θ=θi)
Sklearn基于这些分布以及这些分布上的概率估计的改进,为我们提供了四个朴素贝叶斯的分类器。
虽然朴素贝叶斯使用了过于简化的假设,这个分类器在许多实际情况中都运行良好,著名的是文档分类和垃圾邮件过滤。而且由于贝叶斯是从概率角度进行估计,它所需要的样本量比较少,极端情况下甚至我们可以使用1%的数据作为训练集,依然可以得到很好的拟合效果。当然,如果样本量少于特征数目,贝叶斯的效果就会被削弱。
与SVM和随机森林相比,朴素贝叶斯运行速度更快
不过相对的,贝叶斯的运行效果不是那么好,所以贝叶斯的接口调用的predict_proba其实也不是总指向真正的分类结果
sklearn.naive_bayes.GaussianNB (
priors=None,
var_smoothing=1e-09)
参数 | 含义 |
---|---|
prior | 可输入任何类数组结构,形状为(n_classes,)表示类的先验概率。如果指定,则不根据数据调整先验,如果不指定,则自行根据数据计算先验概率 P ( Y ) P\left( Y \right) P(Y) 。 |
var_smoothing | 浮点数,可不填(默认值= 1e-9)在估计方差时,为了追求估计的稳定性,将所有特征的方差中最大的方差以某个比例添加到估计的方差中。这个比例,由var_smoothing参数控制。 |
高斯朴素贝叶斯,通过假设 P ( x i ∣ Y ) P\left( x_i|Y \right) P(xi∣Y) 是服从高斯分布(也就是正态分布),来估计每个特征下每个类别上的条件概率。对于每个特征下的取值,高斯朴素贝叶斯有如下公式:
P
(
x
i
∣
Y
)
=
1
2
π
σ
y
2
exp
(
−
(
x
i
−
μ
y
)
2
2
σ
y
2
)
P\left( x_i|Y \right) =\frac{1}{\sqrt{2\pi \sigma _y^2}}\exp \left( -\frac{\left( x_i-\mu _y \right) ^2}{2\sigma _y^2} \right)
P(xi∣Y)=2πσy2
1exp(−2σy2(xi−μy)2)
对于任意一个Y的取值,贝叶斯都以求解最大化的
P
(
x
i
∣
Y
)
P\left( x_i|Y \right)
P(xi∣Y) 为目标,这样才能够比较在不同标签下样本究竟更靠近哪一个取值。
以最大化 P ( x i ∣ Y ) P\left( x_i|Y \right) P(xi∣Y) 为目标,高斯朴素贝叶斯会为我们求解公式中的参数 σ y 2 \sigma _y^2 σy2 和 μ y \mu _y μy 。求解出参数后,带入一个 x i x_i xi 的值,就能够得到一个 的概率取值。
在实例化的时候,不需要对高斯朴素贝叶斯类输入任何的参数
但过于简单也意味着贝叶斯没有太多的参数可以调整,因此贝叶斯算法的成长空间并不是太大,如果贝叶斯算法的效果不是太理想,一般都会考虑换模型。
例子
# 导入需要的库和数据 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, y = digits.data, digits.target Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=420) Xtrain.shape # (1257, 64) Xtest.shape # (540, 64) np.unique(Ytrain) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # 建模,探索建模结果 gnb = GaussianNB().fit(Xtrain,Ytrain) #查看分数 acc_score = gnb.score(Xtest,Ytest) acc_score # 0.8592592592592593 #查看预测结果 Y_pred = gnb.predict(Xtest) #查看预测的概率结果 prob = gnb.predict_proba(Xtest) prob.shape #每一列对应一个标签下的概率 # (540, 10) prob[0,:].sum() #每一行的和都是一 1.0 prob.sum(axis=1) #每一行 都是1
使用混淆矩阵来查看贝叶斯的分类结果
from sklearn.metrics import confusion_matrix as CM CM(Ytest,Y_pred) ''' array([[47, 0, 0, 0, 0, 0, 0, 1, 0, 0], [ 0, 46, 2, 0, 0, 0, 0, 3, 6, 2], [ 0, 2, 35, 0, 0, 0, 1, 0, 16, 0], [ 0, 0, 1, 40, 0, 1, 0, 3, 4, 0], [ 0, 0, 1, 0, 39, 0, 1, 4, 0, 0], [ 0, 0, 0, 2, 0, 58, 1, 1, 1, 0], [ 0, 0, 1, 0, 0, 1, 49, 0, 0, 0], [ 0, 0, 0, 0, 0, 0, 0, 54, 0, 0], [ 0, 3, 0, 1, 0, 0, 0, 2, 55, 0], [ 1, 1, 0, 1, 2, 0, 0, 3, 7, 41]], dtype=int64) ''' #注意,ROC曲线是不能用于多分类的。多分类状况下最佳的模型评估指标是混淆矩阵和整体的准确度
常用的三种数据分布:月亮型,环形数据以及二分型数据。
高斯贝叶斯属于比较特殊的一类分类器,其分类效果在二分数据和月亮型数据上表现优秀,但是环形数据不太擅长。
我们已经了解高斯朴素贝叶斯属于分类效果不算顶尖的模型,但我们依然好奇,这个算法在拟合的时候还有哪些特性呢?比如说我们了解,决策树是天生过拟合的模型,而支持向量机是不调参数的情况下就非常接近极限的模型。我们希望通过绘制高斯朴素贝叶斯的学习曲线与分类树,随机森林和支持向量机的学习曲线的对比,来探索高斯朴素贝叶斯算法在拟合上的性质。
过去绘制学习曲线都是以算法类的某个参数的取值为横坐标,现在使用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 def plot_learning_curve( estimator, # 分类器 title, # 标题 X, y, # 特征矩阵和标签 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 5个模型的,训练集上的样本数量 train_scores.shape (5,50) 5个模型50次交叉验证的分数 test_scores (5,50) ''' ax.set_title(title) if ylim is not None: ax.set_ylim(*ylim) ax.set_xlabel("Training examples") ax.set_ylabel("Score") ax.grid() #显示网格作为背景,不是必须 # 训练集分数 纵坐标是 50次交叉验证的均值 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 # 手写数字数据集 digits = load_digits() X, y = digits.data, digits.target X.shape # (1797, 64) X #是一个稀疏矩阵 ''' array([[ 0., 0., 5., ..., 0., 0., 0.], [ 0., 0., 0., ..., 10., 0., 0.], [ 0., 0., 0., ..., 16., 9., 0.], ..., [ 0., 0., 1., ..., 6., 0., 0.], [ 0., 0., 2., ..., 12., 0., 0.], [ 0., 0., 10., ..., 12., 1., 0.]]) ''' 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")] cv = ShuffleSplit( n_splits=50, # 50次交叉验证 test_size=0.2, # 20% * 50 份的数据会被作为测试集、 random_state=0) # 份交叉验证的份数的时候进行的随机抽样模式 # 绘图 # 设置画布(子图) fig, axes = plt.subplots(1,5,figsize=(30,6)) # zip(range(len(title)),title,model) 将三者组成一个元祖 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()
Naive Bayes:00:03:926755
DecisionTree:00:02:863364
SVM, RBF kernel:00:15:116466
RandomForest:00:28:851323
Logistic:00:17:748558
随着样本量的逐渐增大贝叶斯会逐渐变得比决策树更快。朴素贝叶斯计算速度远远胜过SVM,随机森林这样复杂的模型,
逻辑回归的运行受到最大迭代次数的强烈影响和输入数据的影响(逻辑回归一般在线性数据上运行都比较快,但在这里应该是受到了稀疏矩阵的影响)。因此在运算时间上,朴素贝叶斯还是十分有优势的。
每个算法在训练集上的拟合。手写数字数据集是一个较为简单的数据集,决策树,森林,SVC和逻辑回归都成功拟合了100%的准确率,但贝叶斯的最高训练准确率都没有超过95%,这也应证了我们最开始说的,朴素贝叶斯的分类效果其实不如其他分类器,贝叶斯天生学习能力比较弱。并且我们注意到,随着训练样本量的逐渐增大,其他模型的训练拟合都保持在100%的水平,但贝叶斯的训练准确率却逐渐下降,这证明样本量越大,贝
叶斯需要学习的东西越多,对训练集的拟合程度也越来越差。反而比较少量的样本可以让贝叶斯有较高的训练准确率。
过拟合问题。首先一眼看到,所有模型在样本量很少的时候都是出于过拟合状态的(训练集上表现好,测试逐渐增多,过拟合问题都逐渐消失了,不过每个模型的处理手段不同。
比较强大的分类器们,比如SVM,随机森林和逻辑回归,是依靠快速升高模型在测试集上的表现来减轻过拟合问题。相对的,决策树虽然也是通过提高模型在测试集上的表现来减轻过拟合,但随着训练样本的增加,模型在测试集上的表现善生却非常缓慢。朴素贝叶斯独树一帜,是依赖训练集上的准确率下降,测试集上的准确率上升来逐渐解决过拟合问题。
每个算法在测试集上的拟合结果,即泛化误差的大小。随着训练样本数量的上升,所有模型的测试表现都上升了,但贝叶斯和决策树在测试集上的表现远远不如SVM,随机森林和逻辑回归。SVM在训练数据量增大到1500个样本左右的时候,测试集上的表现已经非常接近100%,而随机森林和逻辑回归的表现也在95%以上,而决策树和朴素贝叶斯还徘徊在85%左右。但这两个模型所面临的情况十分不同:决策树虽然测试结果不高,但是却依然具有潜力,因为它的过拟合现象非常严重,我们可以通过减枝来让决策树的测试结果逼近训练结果。然而贝叶斯的过拟合现象在训练样本达到1500左右的时候已经几乎不存在了,训练集上的分数和测试集上的分数非常接近,只有在非常少的时候测试集上的分数才能够比训练集上的结果更高,所以我们基本可以判断,85%左右就是贝叶斯在这个数据
集上的极限了。可以预测到,如果我们进行调参,那决策树最后应该可以达到90%左右的预测准确率,但贝叶斯却几乎没有潜力了。
贝叶斯是速度很快,但分类效果一般,并且初次训练之后的结果就很接近算法极限的算法,几乎没有调参的余地。也就是说,
如果我们追求对概率的预测,并且希望越准确越好,那我们应该先选择逻辑回归。
如果数据十分复杂,或者是稀疏矩阵,那我们坚定地使用贝叶斯。
如果分类的目标不是要追求对概率的预测,那完全可以先试试看高斯朴素贝叶斯的效果(反正它运算很快速,还不需要太多的样本)
如果效果很不错,我们就很幸运地得到了一个表现优秀又快速的模型。如果我们没有得到比较好的结果,那我们完全可以选择再更换成更加复杂的模型。
选择贝叶斯进行分类,大多数时候都不是为了单单追求效果,而是希望看到预测的相关概率。
这种概率给出预测的可信度,所以对于概率类模型,我们希望能够由其他的模型评估指标来帮助我们判断,模型在“概率预测”这项工作上,完成得如何。
概率预测的准确程度被称为“校准程度”,是衡量算法预测出的概率和真实结果的差异的一种方式。一种比较常用的指标叫做布里尔分数,它被计算为是概率预测相对于测试样本的均方误差,表示为:
B r i e r S c o r e = 1 N ∑ i = 1 n ( p i − o i ) 2 Brier\ Score=\frac{1}{N}\sum_{i=1}^n{\left( p_i-o_i \right) ^2} Brier Score=N1i=1∑n(pi−oi)2
其中
N是样本数量;
p
i
p_i
pi 为朴素贝叶斯预测出的概率;
o
i
o_i
oi 是样本所对应的真实结果,只能取到0或者1,如果事件发生则为1,如果不发生则为0。
这个指标衡量了概率距离真实标签结果的差异,其实看起来非常像是均方误差。布里尔分数的范围是从0到1,分数越高则预测结果越差劲,校准程度越差,因此布里尔分数越接近0越好。
由于它的本质也是在衡量一种损失,所以在sklearn当中,布里尔得分被命名为brier_score_loss。可以从模块metrics中导入这个分数来衡量模型评估结果:
from sklearn.metrics import brier_score_loss
brier_score_loss(Ytest, prob[:,1], pos_label=1)
第一个参数是真实标签,第二个参数是预测出的概率值
在二分类情况下,接口predict_proba会返回两列,但SVC的接口decision_function却只会返回一列。要随时注意,使用了怎样的概率分类器,以辨别查找置信度的接口,以及这些接口的结构
pos_label与prob中的索引一致,就可以查看这个类别下的布里尔分数是多少
布里尔分数可以用于任何可以使用predict_proba接口调用概率的模型
另一种常用的概率损失衡量是对数损失(log_loss),又叫做对数似然,逻辑损失或者交叉熵损失,它是多元逻辑回归以及一些拓展算法,比如神经网络中使用的损失函数。
它被定义为,对于一个给定的概率分类器,在预测概率为条件的情况下,真实概率发生的可能性的负对数。
由于是损失,因此对数似然函数的取值越小,则证明概率估计越准确,模型越理想。值得注意得是,对数损失只能用于评估分类型模型。
对于一个样本,如果样本的真实标签 y t r u e y_{true} ytrue 在 {0,1} 中取值,并且这个样本在类别1下的概率估计为 y p r e d y_{pred} ypred ,则这个样本所对应的对数损失是:
− log P ( y t r u e ∣ y p r e d ) = − ( y t r u e ∗ log ( y p r e d ) ) + ( 1 − y t r u e ) ∗ log ( 1 − y p r e d ) -\log P\left( y_{true}|y_{pred} \right) =-\left( y_{true}*\log \left( y_{pred} \right) \right) +\left( 1-y_{true} \right) *\log \left( 1-y_{pred} \right) −logP(ytrue∣ypred)=−(ytrue∗log(ypred))+(1−ytrue)∗log(1−ypred)
和逻辑回归的损失函数差不多:
J ( θ ) = − ∑ i = 1 m ( y i ∗ log ( y θ ( x i ) ) + ( 1 − y i ) ∗ log ( 1 − y θ ( x i ) ) ) J\left( \theta \right) =-\sum_{i=1}^m{\left( y_i*\log \left( y_{\theta}\left( x_i \right) \right) +\left( 1-y_i \right) *\log \left( 1-y_{\theta}\left( x_i \right) \right) \right)} J(θ)=−i=1∑m(yi∗log(yθ(xi))+(1−yi)∗log(1−yθ(xi)))
注意,这里的 log 表示以 e 为底的自然对数。
在sklearn中,可以从metrics模块中导入对数似然函数:
from sklearn.metrics import log_loss
log_loss(Ytest,prob) # 朴素贝叶斯
log_loss(Ytest,logi.predict_proba(Xtest)) # 逻辑回归
log_loss(Ytest,svc_prob) # 支持向量机
第一个参数是真实标签,第二个参数是预测的概率。
用 log_loss 得出的结论和使用布里尔分数得出的结论不一致:
这是由于逻辑回归和SVC都是以最优化为目的来求解模型,然后进行分类的算法。而朴素贝叶斯中,却没有最优化的过程。对数似然函数直接指向模型最优化的方向,甚至就是逻辑回归的损失函数本身,因此在逻辑回归和SVC上表现得更好。
那什么时候使用对数似然,什么时候使用布里尔分数?
在现实应用中,对数似然函数是概率类模型评估的黄金指标,往往是评估概率类模型的优先选择。但是它也有一些缺点,
所以因此通常来说,有以下使用规则:
使用可靠性曲线来调节概率的校准程度。
可靠性曲线(reliability curve),又叫做概率校准曲线(probability calibration curve),可靠性图(reliability diagrams),这是一条以预测概率为横坐标,真实标签为纵坐标的曲线。
我们希望预测概率和真实值越接近越好,最好两者相等,因此一个模型/算法的概率校准曲线越靠近对角线越好。
校准曲线因此也是我们的模型评估指标之一。和布里尔分数相似,概率校准曲线是对于标签的某一类来说的,因此一类标签就会有一条曲线,或者可以使用一个多类标签下的平均来表示一整个模型的概率校准曲线。但通常来说,曲线用于二分类的情况最多,大家如果感兴趣可以自行探索多分类的情况。
# 导入需要的库和模块 import numpy as np import pandas as pd 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%的样本作为训练集 # 99%作为测试集 Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, y,test_size=0.99,random_state=42) Xtrain.shape # (1000, 20) np.unique(Ytrain) # array([0, 1]) 建立模型,绘制图像 gnb = GaussianNB() #实例化 clf = gnb.fit(Xtrain,Ytrain) #训练 clf_score = clf.score(Xtest,Ytest) y_pred = gnb.predict(Xtest) #预测 y预测值 prob_pos = gnb.predict_proba(Xtest)[:,1] #预测概率 - 横坐标 #Ytest - 真实标签 - 纵坐标 Ytest.shape # (99000,) prob_pos.shape # (99000,) #在横纵表坐标上,概率是由顺序的(由小到大),为了让图形规整一些,我们要先对预测概率和真实标签按照预测 # 概率进行一个排序,这一点我们通过DataFrame来实现 # 分别取前500个 df = pd.DataFrame({"ytrue":Ytest[:500],"probability":prob_pos[:500]}) df = df.sort_values(by="probability") df.index = range(df.shape[0]) # 重置索引 df ''' ytrue probability 0 0 1.824809e-21 1 0 6.610435e-21 2 0 4.171566e-19 3 0 8.441773e-18 4 0 1.463199e-17 .. ... ... 495 1 1.000000e+00 496 1 1.000000e+00 497 1 1.000000e+00 498 1 1.000000e+00 499 1 1.000000e+00 [500 rows x 2 columns] '''
画图
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", clf_score))
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来绘制图像。该类有如下参数:
使用可靠性曲线的类在贝叶斯上绘制一条校准曲线
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()
不同的 n_bins 取值下曲线
fig, axes = plt.subplots(1,3,figsize=(18,4)) # 1行3列
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) # y 预测值 #hasattr(obj,name):查看一个类obj中是否存在名字为name的接口,存在则返回True # 在分类器(clf)中查看是否有 predict_proba 这个接口 if hasattr(clf, "predict_proba"): prob_pos = clf.predict_proba(Xtest)[:,1] # use decision function (置信度) else: prob_pos = clf.decision_function(Xtest) # 对置信度进行归一化(0/1归一化) prob_pos = (prob_pos - prob_pos.min()) / (prob_pos.max() - prob_pos.min()) #返回布里尔分数 y在{0,1}中取 # clf_score = brier_score_loss(Ytest, prob_pos, pos_label=1) clf_score = brier_score_loss(Ytest, prob_pos, pos_label=y.max()) # 调用 可靠性曲线 ,分10箱,得到可靠性曲线纵坐标和横坐标 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() ax1.set_title('Calibration plots (reliability curve)') plt.show()
可以明显看出,逻辑回归的概率估计是最接近完美的概率校准曲线,所以逻辑虎归的效果最完美。相对的,高斯朴素贝叶斯和支持向量机分类器的结果都比较糟糕。支持向量机呈现类似于sigmoid函数的形状,而高斯朴素贝叶斯呈现和Sigmoid函数相反的形状。
对于贝叶斯,如果概率校准曲线呈现sigmoid函数的镜像的情况,则说明数据集中的特征不是相互条件独立的。贝叶斯原理中的”朴素“原则:特征相互条件独立原则被违反了
(这其实是我们自己的设定,我们设定了10个冗余特征,这些特征就是噪音,他们之间不可能完全独立),因此贝叶斯的表现不够好。
而支持向量机的概率校准曲线效果其实是典型的置信度不足的分类器(under-confident classifier)的表现:大量的样本点集中在决策边界的附近,因此许多样本点的置信度靠近0.5左右,即便决策边界能够将样本点判断正确,模型本身对这个结果也不是非常确信的。相对的,离决策边界很远的点的置信度就会很高,因为它很大可能性上不会被判断错误。支持向量机在面对混合度较高的数据的时候,有着天生的置信度不足的缺点。
可以通过绘制直方图来查看模型的预测概率的分布。
直方图是以样本的预测概率分箱后的结果为横坐标,每个箱中的样本数量为纵坐标的一个图像。
注意,这里的分箱和可靠性曲线中的分箱不同,这里的分箱是将预测概率均匀分为一个个的区间,与之前可靠性曲线中为了平滑的分箱完全是两码事。
下面段代码,与 建立循环,绘制多个模型的概率校准曲线 类似
fig, ax2 = plt.subplots(figsize=(8,6)) 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] # use decision function else: 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,也没有形成像支持向量机那样的正态分布。一个比较健康的正样本的概率分布,就是逻辑回归的直方图显示出来的样子。
避免混淆:概率密度曲线和概率分布直方图
等近似回归有两种回归可以使用,
概率校准应该发生在测试集上,必须是模型未曾见过的数据。
使用sklearn中的概率校正类CalibratedClassifierCV
来对二分类情况下的数据集进行概率校正。
sklearn.calibration.CalibratedClassifierCV (
base_estimator=None,
method=’sigmoid’,
cv=’warn’)
这是一个带交叉验证的概率校准类,它使用交叉验证生成器,对交叉验证中的每一份数据,它都在训练样本上进行模型参数估计,在测试样本上进行概率校准,然后为我们返回最佳的一组参数估计和校准结果。每一份数据的预测概率会被求解平均。
注意,类CalibratedClassifierCV
没有接口 decision_function,要查看这个类下校准过后的模型生成的概率,必须调用 predict_proba 接口。
base_estimator
需要校准其输出决策功能的分类器,必须存在predict_proba或decision_function接口。 如果参数cv = prefit,分类器必须已经拟合数据完毕。
method
进行概率校准的方法,可输入"sigmoid"或者"isotonic"
当校准的样本量太少(比如,小于等于1000个测试样本)的时候,不建议使用等渗回归,因为它倾向于过拟合。
样本量过少时请使用sigmoids,即Platt校准。
cv
整数,确定交叉验证的策略。可能输入是:
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] # use decision function 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)
准确率
gnb.score(Xtest,Ytest) # 0.8650606060606061
布里尔分数
brier_score_loss(Ytest,gnb.predict_proba(Xtest)[:,1],pos_label = 1)
# 0.11760826355000835
校准概率
gnbisotonic = CalibratedClassifierCV(gnb, cv=2, method='isotonic').fit(Xtrain,Ytrain)
准确率
gnbisotonic.score(Xtest,Ytest) # 0.8626767676767677
布里尔分数
brier_score_loss(Ytest,gnbisotonic.predict_proba(Xtest)[:,1],pos_label = 1)
# 0.09833190251353853
可以看出,校准概率后,布里尔分数明显变小了,但整体的准确率却略有下降,这证明算法在校准之后,尽管对概率的预测更准确了,但模型的判断力略有降低。
布里尔分数衡量模型概率预测的准确率,布里尔分数越低,代表模型的概率越接近真实概率,当进行概率校准后,本来标签是1的样本的概率应该会更接近1,而标签本来是0的样本应该会更接近0,没有理由布里尔分数提升了,模型的判断准确率居然下降了。
但从结果来看,模型的准确率和概率预测的正确性并不是完全一致的
在现实中,当两者相悖的时候,以准确率为标准。但是这不代表说布里尔分数和概率校准曲线就无效了。概率类模型几乎没有参数可以调整,除了换模型之外,鲜有更好的方式帮助我们提升模型的表现,概率校准是难得的可以帮助我们针对概率提升模型的方法。
对于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更加有效。
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) 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
Logistic:
Brier:0.0989
Accuracy:0.8632
SVC+isotonic:
Brier:0.0999
Accuracy:0.8639
SVC+sigmoid:
Brier:0.0987
Accuracy:0.8634
可以看到,对于SVC来说,两种校正都改善了准确率和布里尔分数。可见,概率校正对于SVC非常有效。这也说明,概率校正对于原本的可靠性曲线是形容Sigmoid形状的曲线的算法比较有效。
在现实中,可以选择调节模型的方向,不一定要追求最高的准确率或者追求概率拟合最好,可以根据自己的需求来调整模型。
多项式朴素贝叶斯也是基于原始的贝叶斯理论,但假设概率分布是服从一个简单多项式分布。
多项式分布来源于统计学中的多项式实验,这种实验可以具体解释为:实验包括n次重复
试验,每项试验都有不同的可能结果。在任何给定的试验中,特定结果发生的概率是不变的。
由于这样的特性,多项式朴素贝叶斯的特征矩阵经常是稀疏矩阵(不一定总是稀疏矩阵),并且它经常被用于文本分类。
可以使用著名的TF-IDF向量技术,也可以使用常见并且简单的单词计数向量手段与贝叶斯配合使用。这两种手段都属于常见的文本特征提取的方法,可以很简单地通过sklearn来实现
在sklearn中,用来执行多项式朴素贝叶斯的类MultinomialNB
包含如下的参数和属性:
sklearn.naive_bayes.MultinomialNB (alpha=1.0, fit_prior=True, class_prior=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 #两个类别分别设定500个样本 centers = [[0.0, 0.0], [2.0, 2.0]] #设定两个类别的中心 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) X ''' array([[ 0.88202617, 0.2000786 ], [ 0.48936899, 1.1204466 ], [ 0.933779 , -0.48863894], ..., [ 2.09891408, 2.0488754 ], [ 2.70076171, 2.07921692], [ 1.42904929, 1.34451481]]) ''' np.unique(y) # array([0, 1]) Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
归一化,确保输入的矩阵不带有负数
#先归一化,保证输入多项式朴素贝叶斯的特征矩阵中不带有负数
mms = MinMaxScaler().fit(Xtrain)
Xtrain_ = mms.transform(Xtrain)
Xtest_ = mms.transform(Xtest)
建立多项式朴素贝叶斯分类器
mnb = MultinomialNB().fit(Xtrain_, Ytrain) #重要属性:调用根据数据获取的,每个标签类的对数先验概率log(P(Y)) #由于概率永远是在[0,1]之间,因此对数先验概率返回的永远是负值 mnb.class_log_prior_ # 每个标签类的对数先验概率 # array([-0.69029411, -0.69600841]) mnb.class_log_prior_.shape # (2,) 等于标签中所带的类别数量 # 二分类就返回2个。多分类就返回分类数量 np.unique(Ytrain) # array([0, 1]) (Ytrain == 1).sum()/Ytrain.shape[0] # 0.49857142857142855 #可以使用np.exp来查看真正的概率值 np.exp(mnb.class_log_prior_) # array([0.50142857, 0.49857143]) #重要属性:返回一个固定标签类别下的每个特征的对数概率log(P(Xi|y)) mnb.feature_log_prob_ ''' array([[-0.76164788, -0.62903951], [-0.72500918, -0.6622691 ]]) ''' mnb.feature_log_prob_.shape # (2, 2) 两个特征 两个标签 返回的是每个特征下对应的每个标签的对数概率 #重要属性:在fit时每个标签类别下包含的样本数。 #当fit接口中的sample_weight被设置时,该接口返回的值也会受到加权的影响 Xtrain_.shape # (700, 2) mnb.class_count_ # array([351., 349.]) 351+349=700 mnb.class_count_.shape # 返回和标签类别一行的结构 # (2,)
接口
mnb.predict(Xtest_) # y预测值
mnb.predict_proba(Xtest_) # 预测概率
mnb.score(Xtest_,Ytest) # 准确率 0.5433333333333333
# 布里尔分数 (标签1)
brier_score_loss(Ytest,mnb.predict_proba(Xtest_)[:,1],pos_label=1)
# 0.24977828412546027
把Xtiain转换成分类型数据
注意Xtrain没有经过归一化,因为做哑变量之后自然所有的数据就不会又负数了
以使用分箱,也就是离散化连续型变量的方法来处理原始数据,以此来模型表现。
from sklearn.preprocessing import KBinsDiscretizer # 分箱类 对连续型变量进行分箱 # 分十箱 独热编码 kbs = KBinsDiscretizer(n_bins=10, encode='onehot').fit(Xtrain) Xtrain ''' array([[1.61285246, 1.86530511], [0.75999743, 0.85979465], [0.47045881, 0.20260204], ..., [0.74467798, 0.26065187], [1.19157239, 0.47223974], [1.13838587, 2.89876969]]) ''' Xtrain_ = kbs.transform(Xtrain) # 两个特征特征 ,每个特征分了10箱 所分出来的哑变量 Xtrain.shape # (700, 2) Xtrain_.shape # (700, 20) Xtest ''' array([[ 2.33298357, 0.73272277], [ 0.46822286, -0.01754759], [ 1.60473849, 1.54634994], ... ''' Xtest_ = kbs.transform(Xtest) # 在进行建模 拟合 mnb = MultinomialNB().fit(Xtrain_, Ytrain) mnb.score(Xtest_,Ytest) # 0.9966666666666667 brier_score_loss(Ytest,mnb.predict_proba(Xtest_)[:,1],pos_label=1) # 0.0014593932778211862
同样的数据,如果采用哑变量方式的分箱处理,多项式贝叶斯的效果会突飞猛进。
多项式朴素贝叶斯可同时处理二项分布(抛硬币)和多项分布(掷骰子),其中二项分布又叫做伯努利分布,它是一种现实中常见,并且拥有很多优越数学性质的分布。
伯努利朴素贝叶斯:专门用来处理二项分布的朴素贝叶斯
伯努利贝叶斯类 BernoulliNB 假设数据服从多元伯努利分布,并在此基础上应用朴素贝叶斯的训练和分类过程。
多元伯努利分布简单来说,就是数据集中可以存在多个特征,但每个特征都是二分类的,可以以布尔变量表示,也可以表示为{0,1}或者{-1,1}等任意二分类组合。因此,这个类要求将样本转换为二分类特征向量,如果数据本身不是二分类的,那可以使用类中专门用来二值化的参数binarize来改变数据。
伯努利朴素贝叶斯与多项式朴素贝叶斯非常相似,都常用于处理文本分类数据。但由于伯努利朴素贝叶斯是处理二项分布,
所以
sklearn.naive_bayes.BernoulliNB (
alpha=1.0,
binarize=0.0,
fit_prior=True,
class_prior=None)
一般来说我们应该使用二值化的类sklearn.preprocessing.Binarizer
来将特征一个个二值化,然而这样效率过低,因此我们选择归一化之后直接设置一个阈值
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) # 0.49666666666 # 布里尔分数 brier_score_loss(Ytest,bnl_.predict_proba(Xtest_)[:,1],pos_label=1) # 0.25 #设置二值化阈值为0.5 bnl = BernoulliNB(binarize=0.5).fit(Xtrain_, Ytrain) bnl.score(Xtest_,Ytest)# 0.9833333333 brier_score_loss(Ytest,bnl.predict_proba(Xtest_)[:,1],pos_label=1) # 0.0101
和多项式贝叶斯一样,伯努利贝叶斯的结果也受到数据结构非常大的影响
探讨样本不平衡问题。
这里引用:如何解决机器学习中样本不均衡问题?
在一个极度不平衡的样本中,由于机器学习会每个数据进行学习,那么多数数据样本带有的信息量比少数样本信息量大,会对分类器学习过程中造成困扰。假设正样本为少数类,负样本为多数类,一般而言,当正负样本比例超过1:3,分类器就已经会倾向于负样本的判断(表现在负样本Recall过高,而正样本 Recall 低,而整体的 Accuracy依然会有很好的表现)。在这种情况下,我们可以说这个分类器是失败的,因为它没法实现我们对少数类(正样本)的定位。
举例说明:“一个数据集一共有100个样本,分为A、B两种类型,A类别有95个,B类别有5个。”这就是典型的样本不均衡问题,对于机器学习分类算法来说,如果把全部的样本都归为A类,准确率( Accuracy)也可以达到95%,而这个准确率对于我们判别少数类来说是没有意义的,没有任何价值,这个分类器的决策很明显并非是我们想要的判定标准。
贝叶斯由于分类效力不算太好,因此对样本不平衡极为敏感,
样本不平衡如何影响了贝叶斯
导入需要的模块,建立样本不平衡的数据集
import numpy as np import pandas as pd 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 # 人造数据集 class_1 = 50000 #多数类为50000个样本 class_2 = 500 #少数类为500个样本 centers = [[0.0, 0.0], [5.0, 5.0]] #设定两个类别的中心 clusters_std = [3, 1] #设定两个类别的方差 X, y = make_blobs(n_samples=[class_1, class_2], centers=centers, cluster_std=clusters_std, random_state=0, shuffle=False) X ''' array([[ 5.29215704, 1.20047163], [ 2.93621395, 6.7226796 ], [ 5.60267397, -2.93183364], ..., [ 6.10874291, 4.71717025], [ 4.98419297, 6.54137808], [ 4.11866167, 4.55616481]]) ''' X.shape # (50500, 2) # 50000行 是一个类 # 500行 又是一个类 np.unique(y) #array([0, 1]) 二分类
查看所有贝叶斯在样本不平衡数据集上的表现
# 三种朴素贝叶斯 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)))
Multinomial
Brier:0.007 # 布里尔分数
Accuracy:0.990 # 准确率
Recall:0.000 # 召唤率 # 一个少数类都没有捕捉到 完全放弃少数类
AUC:0.991 # AUC分数
Gaussian
Brier:0.006
Accuracy:0.990
Recall:0.438 # 判断对一小部分的少数类
AUC:0.993
Bernoulli
Brier:0.009
Accuracy:0.987
Recall:0.771 # 能捕捉大部分少数类
AUC:0.987
从结果上来看,
多项式朴素贝叶斯判断出了所有的多数类样本,但放弃了全部的少数类样本,受到样本不均衡问题影响最严重。
高斯比多项式在少数类的判断上更加成功一些,至少得到了43.8%的recall。
伯努利贝叶斯虽然整体的准确度和布里尔分数不如多项式和高斯朴素贝叶斯和,但至少成功捕捉出了77.1%的少数类。可见,伯努利贝叶斯最能够忍受样本不均衡问题。
补集朴素贝叶斯(complement naive Bayes,CNB)算法是标准多项式朴素贝叶斯算法的改进。
补集朴素贝叶斯能够解决样本不平衡问题,并且能够一定程度上忽略朴素假设。
CNB的参数估计已经被证明比普通多项式朴素贝叶斯更稳定,并且它特别适合于样本不平衡的数据集。
sklearn.naive_bayes.ComplementNB (
alpha=1.0,
fit_prior=True,
class_prior=None,
norm=False)
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")) Multinomial Brier:0.007 Accuracy:0.990 Recall:0.000 AUC:0.991 00:00:055970 Gaussian Brier:0.006 Accuracy:0.990 Recall:0.438 AUC:0.993 00:00:065963 Bernoulli Brier:0.009 Accuracy:0.987 Recall:0.771 AUC:0.987 00:00:053969 Complement Brier:0.038 Accuracy:0.953 Recall:0.987 AUC:0.991 00:00:049971
可以发现,补集朴素贝叶斯牺牲了部分整体的精确度和布里尔指数,但是得到了十分高的召回率Recall,捕捉出了98.7%的少数类,并且在此基础上维持了和原本的多项式朴素贝叶斯一致的AUC分数。
和其他的贝叶斯算法比起来, 补集朴素贝叶斯的运行速度也十分优秀。如果我们的目标是捕捉少数类,那我们毫无疑问会希望选择补集朴素贝叶斯作为我们的算法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。