朴素贝叶斯被认为是最简单的分类算法之一。首先,我们需要了解一些概率论的基本理论。假设有两个随机变量X和Y,他们分别可以取值为x和y。有这两个随机变量,我们可以定义两种概率:
关键概念:联合概率与条件概率
联合概率:“X取值为x”和“Y取值为y”两个事件同时发生的概率,表示为 P ( X = x , Y = y ) P(X = x, Y = y) P(X=x,Y=y)
条件概率:在X取值为x的前提下,Y取值为y的概率,表示为 P ( Y = y ∣ X = x ) P(Y = y | X = x) P(Y=y∣X=x)
举个例子,我们让X为"气温",Y为“七星瓢虫冬眠”,则X和Y可能的取值分为别x和y,其中x = {0,1},0表示没有下降到0度以下,1表示下降到了0度以下。y = {0,1},其中0表示否,1表示是。
这两个事件分别发生的概率就为:
P ( X = 1 ) = 50 % P(X = 1) = 50\% P(X=1)=50%,则是说明,气温下降到0度以下的可能性为50%,则 P ( X = 0 ) = 1 − P ( X = 1 ) = 50 % P(X = 0) = 1 - P(X = 1) = 50\% P(X=0)=1−P(X=1)=50%。
P ( Y = 1 ) = 70 % P(Y = 1) = 70\% P(Y=1)=70%,则是说明,七星瓢虫会冬眠的可能性为70%,则 P ( Y = 0 ) = 1 − P ( Y = 1 ) = 30 % P(Y = 0) = 1 - P(Y = 1) = 30\% P(Y=0)=1−P(Y=1)=30%。
则这两个事件的联合概率为 P ( X = 1 , Y = 1 ) P(X = 1, Y = 1) P(X=1,Y=1), 这个概率代表了气温下降到0度以下和七星瓢虫去冬眠这两件事情同时,独立发生的概率。
而两个事件之间的条件概率为 P ( Y = 1 ∣ X = 1 ) P(Y = 1 | X = 1) P(Y=1∣X=1),这个概率代表了,当气温下降到0度以下这个条件被满足之后,七星瓢虫会去冬眠的概率。也就是说,气温下降到0度以下,一定程度上影响了七星瓢虫去冬眠这个事件。
在概率论中,我们可以证明,两个事件的联合概率等于这两个事件任意条件概率 * 这个条件事件本身的概率。
P ( X = 1 , Y = 1 ) = P ( Y = 1 ∣ X = 1 ) ∗ P ( X = 1 ) = P ( X = 1 ∣ Y = 1 ) ∗ P ( Y = 1 ) P(X = 1, Y = 1) =P(Y = 1|X = 1)*P(X=1)=P(X = 1|Y=1)*P(Y=1) P(X=1,Y=1)=P(Y=1∣X=1)∗P(X=1)=P(X=1∣Y=1)∗P(Y=1)
简单一些,则可以将上面的式子写成:
P ( X , Y ) = P ( Y ∣ X ) ∗ P ( X ) = P ( X ∣ Y ) ∗ P ( Y ) P(X, Y)=P(Y|X)*P(X)=P(X|Y)*P(Y) P(X,Y)=P(Y∣X)∗P(X)=P(X∣Y)∗P(Y)
由上面的式子,我们可以得到贝叶斯理论等式:
P ( Y ∣ X ) = P ( X ∣ Y ) ∗ P ( Y ) P ( X ) P(Y|X) = \frac{P(X|Y)*P(Y)}{P(X)} P(Y∣X)=P(X)P(X∣Y)∗P(Y)
而这个式子,就是我们一切贝叶斯算法的根源理论。我们可以把我们的特征 X X X当成是我们的条件事件,而我们要求解的标签 Y Y Y当成是我们被满足条件后会被影响的结果,而两者之间的概率关系就是 P ( Y ∣ X ) P(Y|X) P(Y∣X),这个概率在机器学习中,被我们称之为是标签的后验概率(posterior probability),即是说我们先知道了条件,再去求解结果。而标签 在没有任何条件限制下取值为某个值的概率,被我们写作 P ( Y ) P(Y) P(Y),与后验概率相反,这是完全没有任何条件限制的,标签的先验概率(prior probability)。而我们的 P ( X ∣ Y ) P(X|Y) P(X∣Y) 被称为“类的条件概率”,表示当Y的取值固定的时候,X为某个值的概率。那现在,有趣的事情就出现了。
假设,我们依然让X是“气温”,这就是我们的特征,Y是“七星瓢虫冬眠”,就是我们的标签。现在,我们建模的目的是,预测七星瓢虫是否会冬眠。在许多教材和博客里,大家会非常自然地开始说,我们现在求的就是我们的 P ( Y ∣ X ) P(Y|X) P(Y∣X),然后根据贝叶斯理论等式开始做各种计算和分析。现在请问大家,我写作 P ( Y ∣ X ) P(Y|X) P(Y∣X) 的这个概率,代表了什么呢?更
具体一点,这个表达,可以代表多少种概率呢?
P ( Y ∣ X ) P(Y|X) P(Y∣X)代表了多少种情况的概率?
P(Y = 1|X = 1)气温0度以下的条件下,七星瓢虫冬眠的概率
P(Y = 1|X = 0)气温0度以上的条件下,七星瓢虫冬眠的概率
P(Y = 0|X = 1)气温0度以下的条件下,七星瓢虫没有冬眠的概率
P(Y = 0|X = 0)气温0度以上的条件下,七星瓢虫没有冬眠的概率
数学中的第一个步骤,也就是最重要的事情,就是定义清晰。其实在数学中, P ( Y ∣ X ) P(Y|X) P(Y∣X) 还真的就代表了全部的可能性,而不是单一的概率本身。现在我们的 Y Y Y有两种取值,而 X X X也有两种取值,就让概率 P ( Y ∣ X ) P(Y|X) P(Y∣X) 的定义变得很模糊,排列组合之后竟然有4种可能。在机器学习当中,一个特征 X X X下取值可能远远不止两种,标签也可能是多分类的,还会有多个特征,排列组合以下,到底求解的 P ( Y ∣ X ) P(Y|X) P(Y∣X)是什么东西,是一个太让人感到混淆的点。同理, P ( Y ) P(Y) P(Y)随着标签中分类的个数,可以有不同的取值。 也是一样。在这里,我们来为大家澄清:
机器学习中的简写 ,通常表示标签取到少数类的概率,少数类往往使用正样本表示,也就是 P ( Y = 1 ) P(Y=1) P(Y=1),本质就是所有样本中标签为1的样本所占的比例。如果没有样本不均衡问题,则必须在求解的时候明确,你的Y的取值是什么。
而 P ( Y ∣ X ) P(Y|X) P(Y∣X)是对于任意一个样本而言,如果这个样本的特征 的取值为1,则表示求解 P ( Y = 1 ∣ X = 1 ) P(Y=1|X=1) P(Y=1∣X=1) 。如果这个样本下的特征 取值为0,则表示求解 P ( Y = 1 ∣ X = 0 ) P(Y=1|X=0) P(Y=1∣X=0) 。也就是说, P ( Y ∣ X ) P(Y|X) P(Y∣X)是具体到每一个样本上的,究竟求什么概率,由样本本身的特征的取值决定。每个样本的 P ( Y ∣ X ) P(Y|X) P(Y∣X)如果大于阈值0.5,则认为样本是少数类(正样本,1),如果这个样本的 P ( Y ∣ X ) P(Y|X) P(Y∣X)小于阈值0.5,则认为样本是多数类(负样本,0或者-1)。如果没有具体的样本,只是说明例子,则必须明确 P ( Y ∣ X ) P(Y|X) P(Y∣X)中 X X X的取值。
在机器学习当中,对每一个样本,我们不可能只有一个特征 x x x,而是会存在着包含n个特征的取值的特征向量 X X X 。因此机器学习中的后验概率,被写作 P ( Y ∣ X ) P(Y|X) P(Y∣X),其中 X X X中包含样本在n个特征 X i X_{i} Xi 上的分别的取值 ,由此可i以表示为 X = { X 1 = x 1 , X 2 = x 2 , . . . , X n = x n } X=\{X_{1}=x_{1}, X_{2}=x_{2},...,X_{n}=x_{n}\} X={X1=x1,X2=x2,...,Xn=xn}。
因此,我们有:
一个 样 本 上 , 所 有 特 征 取 值 下 的 概 率 :
每 一 个 向 量 都 是 一 个 样 本 上 的 特 征 向 量
P ( X ) P(X) P(X) P ( x ) P(x) P(x) P ( X 1 = x 1 , X 2 = x 2 , . . . , X n = x n ) P(X_{1}=x_{1}, X_{2}=x_{2},...,X_{n}=x_{n}) P(X1=x1,X2=x2,...,Xn=xn) P ( X 1 = x 1 , X 2 = x 2 , . . . , X n = x n ) P(X_{1}=x_{1}, X_{2}=x_{2},...,X_{n}=x_{n}) P(X1=x1,X2=x2,...,Xn=xn) P ( X 1 , X 2 , . . . , X n ) P(X_{1}, X_{2},...,X_{n}) P(X1,X2,...,Xn) P ( x 1 , x 2 , . . . , x n ) P(x_{1}, x_{2},...,x_{n}) P(x1,x2,...,xn)
一 个 样 本 上 , 一 个 特 征 所 取 值 下 的 概 率 :
是 对 于 单 独 元 素 来 说 , 没 有 向 量 存 在
P ( X i = x i ) P(X_{i} = x_{i}) P(Xi=xi) P ( X i ) P(X_{i}) P(Xi) P ( x i ) P(x_{i}) P(xi)
一个 特 征 上 , 所 有 样 本 下 所 取 得 的 概 率 :
每 一 个 向 量 都 是 一 个 特 征 下 的 样 本 向 量
P ( X i ) P(X_{i}) P(Xi) P ( i ) P({i}) P(i)
虽然写法不同,但其实都包含折同样的含义。以此为基础,机器学习中,对每一个样本我们有:
P ( Y = 1 ∣ X ) = P ( X ∣ Y = 1 ) ∗ P ( Y = 1 ) P ( X ) = P ( x 1 , x 2 , . . . , x n ∣ Y = 1 ) ∗ P ( Y = 1 ) P ( x 1 , x 2 , . . . , x n ) P(Y=1|X)=\frac{P(X|Y=1) * P(Y=1)}{P(X)}=\frac{P(x_{1}, x_{2}, ...,x_{n}|Y=1) * P(Y=1)}{P(x_{1}, x_{2}, ...,x_{n})} P(Y=1∣X)=P(X)P(X∣Y=1)∗P(Y=1)=P(x1,x2,...,xn)P(x1,x2,...,xn∣Y=1)∗P(Y=1)
对于分子而言 P ( Y = 1 ) P(Y=1) P(Y=1)就是少数类占总样本量的比例, P ( X ∣ Y = 1 ) P(X|Y=1) P(X∣Y=1)则需要稍微复杂一点的过程来求解。假设我们只有两个特征 X 1 X_{1} X1 , X 2 X_{2} X2 ,由联合概率公式,我们可以有如下证明:
P ( X 1 , X 2 ∣ Y = 1 ) = P ( X 1 , X 2 , Y = 1 ) P ( Y = 1 ) = P ( X 1 , X 2 , Y = 1 ) P ( X 2 , Y = 1 ) ∗ P ( X 2 , Y = 1 ) P ( Y = 1 ) = P ( X 1 ∣ X 2 , Y = 1 ) ∗ P ( X 2 , Y = 1 ) P(X_{1},X_{2}|Y=1)=\frac{P(X_{1},X_{2},Y=1)}{P(Y=1)}=\frac{P(X_{1},X_{2},Y=1)}{P(X_{2},Y=1)} * \frac{P(X_{2},Y=1)}{P(Y=1)}=P(X_{1} | X_{2},Y=1) * P( X_{2},Y=1) P(X1,X2∣Y=1)=P(Y=1)P(X1,X2,Y=1)=P(X2,Y=1)P(X1,X2,Y=1)∗P(Y=1)P(X2,Y=1)=P(X1∣X2,Y=1)∗P(X2,Y=1)
假设 X 1 X_{1} X1和 X 2 X_{2} X2是相互独立的。 = P ( X 1 ∣ Y = 1 ) ∗ P ( X 2 ∣ Y = 1 ) =P(X_{1}|Y=1)*P(X_{2}|Y=1) =P(X1∣Y=1)∗P(X2∣Y=1)
是一种若推广到n个 上,则有 P ( X ∣ Y = 1 ) = ∏ i = 1 n P ( X i = x i ∣ Y = 1 ) P(X|Y=1)=\prod_{i=1}^{n}P(X_{i}=x_{i}|Y=1) 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(X_{1}|X_{2},Y=1) = P(X_{1}|Y=1) P(X1∣X2,Y=1)=P(X1∣Y=1),这是在假设 X 2 X_{2} X2是一个对 X 1 X_{1} X1在某个条件下的取值完全无影响的变量。
比如说,温度( X 1 X_{1} X1 )与观察到的瓢虫的数目( X 2 X_{2} X2 )之间的关系。有人可能会说,温度在零下的时候,观察到的瓢虫数目往往很少。这种关系的存在可以通过一个中间因素:瓢虫会冬眠(Y)来解释。冬天瓢虫都冬眠了,自然观察不到很多瓢虫出来活动。也就是说,如果瓢虫冬眠的属性是固定的( Y = 1 Y=1 Y=1),那么观察到的温度和瓢虫出没数目之间关系就会消失,因此无论是否还存在着”观察到的瓢虫的数目“这样的因素,我们都可以判断这只瓢虫到底会不会冬眠。这种情况下,我们就说,温度与观察到的瓢虫的数目,是条件独立的。
假设特征之间是有条件独立的,可以解决众多问题,也简化了很多计算过程,这是朴素贝叶斯被称为”朴素“的理由。因此,贝叶斯在特征之间有较多相关性的数据集上表现不佳,而现实中的数据多多少少都会有一些相关性,所以贝叶斯的分类效力在分类算法中不算特别强大。同时,一些影响特征本身的相关性的降维算法,比如PCA和SVD,和贝叶斯连用效果也会不佳。但无论如何,有了这个式子,我们就可以求解出我们的分子
接下来,来看看我们贝叶斯理论等式的分母 。我们可以使用全概率公式来求解 P ( X ) P(X) P(X):
P ( X ) = ∑ i = 1 m P ( y i ) ∗ P ( X ∣ Y i ) P(X)=\sum \limits_{i=1} ^{m}P(y_{i})*P(X|Y_{i}) P(X)=i=1∑mP(yi)∗P(X∣Yi)
其中 代表标签的种类,也就是说,对于二分类而言我们有
P ( X ) = P ( Y = 1 ) ∗ P ( X ∣ Y = 1 ) + P ( Y = 0 ) ∗ P ( X ∣ Y = 0 ) P(X)=P(Y=1)*P(X|Y=1) + P(Y=0)*P(X|Y=0) P(X)=P(Y=1)∗P(X∣Y=1)+P(Y=0)∗P(X∣Y=0)
在过去的许多个星期内,我们学习的分类算法总是有一个特点:这些算法先从训练集中学习,获取某种信息来建立模型,然后用模型去对测试集进行预测。比如逻辑回归,我们要先从训练集中获取让损失函数最小的参数,然后用参数建立模型,再对测试集进行预测。在比如支持向量机,我们要先从训练集中获取让边际最大的决策边界,然后用决策边界对测试集进行预测。相同的流程在决策树,随机森林中也出现,我们在fit的时候必然已经构造好了能够让对测试集进行判断的模型。而朴素贝叶斯,似乎没有这个过程。
我给了大家一张有标签的表,然后提出说,我要预测零下的时候,年龄为20天的瓢虫,会冬眠的概率,然后我们就顺理成章地算了出来。没有利用训练集求解某个模型的过程,也没有训练完毕了我们来做测试的过程,而是直接对有标签的数据提出要求,就可以得到预测结果了。
这说明,朴素贝叶斯是一个不建模的算法。以往我们学的不建模算法,比如KMeans,比如PCA,都是无监督学习,而朴素贝叶斯是第一个有监督的,不建模的分类算法。在我们刚才举的例子中,有标签的表格就是我们的训练集,而我提出的要求“零下的时候,年龄为20天的瓢虫”就是没有标签的测试集。我们认为,训练集和测试集都来自于同一个不可获得的大样本下,并且这个大样本下的各种属性所表现出来的规律应当是一致的,因此训练集上计算出来的各种概率,可以直接放到测试集上来使用。即便不建模,也可以完成分类。
但实际中,贝叶斯的决策过程并没有我们给出的例子这么简单。
P ( Y = 1 ∣ X ) = P ( Y = 1 ) ∗ ∏ i = 1 n P ( x i ∣ Y = 1 ) P ( X ) P(Y=1|X)=\frac{P(Y=1)*\prod \limits_{i=1}^{n} P(x_{i} | Y=1)}{P(X)} P(Y=1∣X)=P(X)P(Y=1)∗i=1∏nP(xi∣Y=1)
对于这个式子来说,从训练集中求解 P ( Y = 1 ) P(Y=1) P(Y=1)很容易,但 P ( X ) P(X) P(X)和 P ( x i ∣ Y = 1 ) P(x_{i}|Y=1) P(xi∣Y=1)这一部分就没有这么容易了。在我们的例子中,我们通过全概率公式来求解分母,两个特征就求解了四项概率。随着特征数目的逐渐变多,分母上的计算两会成指数级增长,而分子中的 也越来越难计算。
不过幸运的是,对于同一个样本来说,在二分类状况下我们可以有:
P ( Y = 1 ∣ X ) = P ( Y = 1 ) ∗ ∏ i = 1 n P ( x i ∣ Y = 1 ) P ( X ) P(Y=1|X)=\frac{P(Y=1)*\prod \limits_{i=1}^{n} P(x_{i} | Y=1)}{P(X)} P(Y=1∣X)=P(X)P(Y=1)∗i=1∏nP(xi∣Y=1)
P ( Y = 0 ∣ X ) = P ( Y = 0 ) ∗ ∏ i = 1 n P ( x i ∣ Y = 0 ) P ( X ) P(Y=0|X)=\frac{P(Y=0)*\prod \limits_{i=1}^{n} P(x_{i} | Y=0)}{P(X)} P(Y=0∣X)=P(X)P(Y=0)∗i=1∏nP(xi∣Y=0)
并且: P ( Y = 1 ∣ X ) + P ( Y = 0 ∣ X ) = 1 P(Y=1|X)+P(Y=0|X)=1 P(Y=1∣X)+P(Y=0∣X)=1
在分类的时候,我们选择 P ( Y = 1 ∣ X ) P(Y=1|X) P(Y=1∣X)和 P ( Y = 0 ∣ X ) P(Y=0|X) P(Y=0∣X)中较大的一个所对应的Y的取值,作为这个样本的分类。在比较两个类别的时候,两个概率计算的分母是一致的,因此我们可以不用计算分母,只考虑分子的大小。当我们分别计算出分子的大小之后,就可以通过让两个分子相加,来获得分母的值,以此来避免计算一个样本上所有特征下的概率 P ( X ) P(X) P(X)。这个过程,被我们称为“最大后验估计”(MAP)。在最大后验估计中,我们只需要求解分子,主要是求解一个样本下每个特征取值下的概率 P ( x i ∣ Y = y i ) P(x_{i}|Y=y_{i}) P(xi∣Y=yi),再求连乘便能够获得相应的概率。
在现实中,要求解分子也会有各种各样的问题。比如说,测试集中出现的某种概率组合,是训练集中从未出现的状况,这种时候就会出现某一个概率为0的情况,贝叶斯概率的分子就会为0。
class sklearn.naive_bayes.GaussianNB (priors=None, var_smoothing=1e-09)
高斯朴素贝叶斯,通过假设 P ( x i ∣ Y ) P(x_{i}|Y) P(xi∣Y) 是服从高斯分布(也就是正态分布),来估计每个特征下每个类别上的条件概率。对于每个特征下的取值,高斯朴素贝叶斯有如下公式:
P ( x i ∣ Y ) = f ( x i ; μ y , σ y ) ∗ ϵ P(x_{i}|Y) = f(x_{i};\mu_{y},\sigma_{y}) * \epsilon P(xi∣Y)=f(xi;μy,σy)∗ϵ = 1 2 π σ y 2 e x p ( − ( x i − μ y ) 2 2 σ y 2 ) = \frac{1}{\sqrt{2 \pi \sigma_{y}^{2}}} exp(-\frac{(x_i-\mu_{y})^{2}}{2\sigma_{y}^{2}}) =2πσy21exp(−2σy2(xi−μy)2)
对于任意一个Y的取值,贝叶斯都以求解最大化的 P ( x i ∣ Y ) P(x_{i}|Y) P(xi∣Y)为目标,这样我们才能够比较在不同标签下我们的样本究竟更靠近哪一个取值。以最大化 P ( x i ∣ Y ) P(x_{i}|Y) P(xi∣Y)为目标,高斯朴素贝叶斯会为我们求解公式中的参数 σ y \sigma_{y} σy和 μ y \mu_{y} μy。求解出参数后,带入一个 的值,就能够得到一个 的概率取值。
这个类包含两个参数:
参数 | 含义 |
---|---|
prior | 可输入任何类数组结构,形状为(n_classes,)表示类的先验概率。如果指定,则不根据数据调整先验,如果不指定,则自行根据数据计算先验概率P(Y)。 |
var_smoothing | 浮点数,可不填(默认值= 1e-9)在估计方差时,为了追求估计的稳定性,将所有特征的方差中最大的方差以某个比例添加到估计的方差中。这个比例,由var_smoothing参数控制。 |
但在实例化的时候,我们不需要对高斯朴素贝叶斯类输入任何的参数,调用的接口也全部sklearn中比较标准的一些搭配,可以说是一个非常轻量级的类,操作非常容易。但过于简单也意味着贝叶斯没有太多的参数可以调整,因此贝叶斯算法的成长空间并不是太大,如果贝叶斯算法的效果不是太理想,我们一般都会考虑换模型。
无论如何,先来进行一次预测试试看吧:
在这里我们来使用watermask这个便利的模块来帮助我们,这是一个能够帮助我们一行代码查看设备和库的版本的模块。如果没有watermask的你可能需要在cmd中运行pip来安装。也可以直接使用魔法命令%%cmd作为一个cell的开头来帮助我们在jupyter lab中安装你的watermark。
%%cmd
pip install watermark
#在这里必须分开cell,魔法命令必须是一个cell的第一部分内容
#注意load_ext这个命令只能够执行一次,再执行就会报错,要求用reload命令
%load_ext watermark
%watermark -a "TsaiTsai" -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, y = digits.data, digits.target
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
gnb = GaussianNB().fit(Xtrain,Ytrain)
#查看分数
acc_score = gnb.score(Xtest,Ytest)
acc_score
#查看预测结果
Y_pred = gnb.predict(Xtest)
#查看预测的概率结果
prob = gnb.predict_proba(Xtest)
prob.shape
prob.shape #每一列对应一个标签下的概率
prob[1,:].sum() #每一行的和都是一
prob.sum(axis=1)
from sklearn.metrics import confusion_matrix as CM
CM(Ytest,Y_pred)
#注意,ROC曲线是不能用于多分类的。多分类状况下最佳的模型评估指标是混淆矩阵和整体的准确度
那高斯普斯贝叶斯擅长什么样的数据集呢?我们还是使用常用的三种数据分布:月亮型,环形数据以及二分型数据。
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等等,在线性数据集上会绘制直线决策边界,因此难以对月亮型和环形数据进行区分,但高斯朴素贝叶斯的决策边界是曲线,可以是环形也可以是弧线,所以尽管贝叶斯本身更加擅长线性可分的二分数据,但朴素贝叶斯在环形数据和月亮型数据上也可以有远远胜过其他线性模型的表现。
多项式贝叶斯可能是除了高斯之外,最为人所知的贝叶斯算法了。它也是基于原始的贝叶斯理论,但假设概率分布是服从一个简单多项式分布。多项式分布来源于统计学中的多项式实验,这种实验可以具体解释为:实验包括n次重复试验,每项试验都有不同的可能结果。在任何给定的试验中,特定结果发生的概率是不变的。
举个例子,比如说一个特征矩阵 表示投掷硬币的结果,则得到正面的概率为P( X=正面| Y) = 0.5,得到反面的概率为 P( X=反面| Y) = 0.5,只有这两种可能并且两种结果互不干涉,并且两个随机事件的概率加和为1,这就是一个二项分布。这种情况下,适合于多项式朴素贝叶斯的特征矩阵应该长这样:
测试编号 | X 1 : 出 现 正 面 X_{1}:出现正面 X1:出现正面 | X 2 : 出 现 反 面 X_{2}:出现反面 X2:出现反面 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
2 | 1 | 0 |
3 | 0 | 1 |
假设另一个特征 表示投掷骰子的结果,则 就可以在[1,2,3,4,5,6]中取值,六种结果互不干涉,且只要样本量足够大,概率都为 1 6 \frac{1}{6} 61 ,这就是一个多项分布。多项分布的特征矩阵应该长这样:
测试编号 | 出现1 | 出现2 | 出现3 | 出现4 | 出现5 | 出现6 |
---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 1 |
2 | 0 | 0 | 1 | 0 | 0 | 0 |
… | ||||||
m | 0 | 0 | 0 | 0 | 0 | 1 |
可以看出:
多项式分布擅长的是分类型变量,在其原理假设中, P ( x i ∣ Y ) P(x_{i}|Y) P(xi∣Y) 的概率是离散的,并且不同 x i x_{i} xi下的 P ( x i ∣ Y ) P(x_{i}|Y) P(xi∣Y)相互独立,互不影响。虽然sklearn中的多项式分布也可以处理连续型变量,但现实中,如果我们真的想要处理连续型变量,我们应当使用高斯朴素贝叶斯。
多项式实验中的实验结果都很具体,它所涉及的特征往往是次数,频率,计数,出现与否这样的概念,这些概念都是离散的正整数,因此sklearn中的多项式朴素贝叶斯不接受负值的输入。
由于这样的特性,多项式朴素贝叶斯的特征矩阵经常是稀疏矩阵(不一定总是稀疏矩阵),并且它经常被用于文本分类。我们可以使用著名的TF-IDF向量技术,也可以使用常见并且简单的单词计数向量手段与贝叶斯配合使用。这两种手段都属于常见的文本特征提取的方法,可以很简单地通过sklearn来实现。
从数学的角度来看,在一种标签类别 Y = c Y=c Y=c 下,我们有一组分别对应特征的参数向量 θ c = ( θ c 1 , θ c 2 , . . . , θ c n ) \theta_{c}=(\theta_{c_{1}},\theta_{c_{2}}, ...,\theta_{c_{n}}) θc=(θc1,θc2,...,θcn) ,其中n表示特征的总数。 θ c i \theta_{c_{i}} θci一个 表示这个标签类别下的第 个特征所对应的参数。这个参数被我们定义为:
θ c i = 特 征 X i 在 这 个 分 类 下 的 所 有 样 本 的 取 值 总 和 所 有 特 征 Y = c 在 这 个 分 类 下 的 所 有 样 本 的 取 值 总 和 \theta_{c_{i}}=\frac{特 征 X_{i}在 这 个 分 类 下 的 所 有 样 本 的 取 值 总 和}{所 有 特 征Y=c 在 这 个 分 类 下 的 所 有 样 本 的 取 值 总 和} θci=所有特征Y=c在这个分类下的所有样本的取值总和特征Xi在这个分类下的所有样本的取值总和
记作 P ( X i ∣ Y = c ) P(X_{i}|Y=c) P(Xi∣Y=c) ,表示当 Y = c Y=c Y=c这个条件固定的时候,一组样本在 这个特征上的取值被取到的概率。注意,我们在高斯朴素贝叶斯中求解的概率 是对于一个样本来说,而我们现在求解的 是对于一个特征来说的概率。
对于一个在标签类别 下,结构为(m, n)的特征矩阵来说,我们有:
X y = [ x 11 x 12 x 13 . . . x 1 n x 21 x 22 x 23 . . . x 2 n x 31 x 32 x 33 . . . x 3 n . . . . . . . . . . . . x m 1 x m 2 x m 3 . . . x m n ] X_{y} = \left[ \begin{array} { c c } {x_{11}} & {x_{12}} &{x_{13}} &...& {x_{1n} } \\ {x_{21}} & {x_{22}}&{x_{23}}&...& {x_{2n} } \\ {x_{31}} & {x_{32}}&{x_{33}}&...& {x_{3n} } \\ ...& ... & ...& ... \\ {x_{m1}} & {x_{m2}}&{x_{m3}}&...& {x_{mn} } \end{array} \right] Xy=⎣⎢⎢⎢⎢⎡x11x21x31...xm1x12x22x32...xm2x13x23x33...xm3...............x1nx2nx3nxmn⎦⎥⎥⎥⎥⎤
其中每个 x j i x_{ji} xji 都是特征 X i X_{i} Xi发生的次数。基于这些理解,我们通过平滑后的最大似然估计来求解参数 θ y \theta_{y} θy :
θ c i = ∑ y j = c x j i + α ∑ i = 1 n ∑ y j = c x j i + α n \theta_{c_{i}}=\frac{\sum \limits _{y_{j}=c} x_{ji} + \alpha}{\sum \limits_{i=1}^{n}\sum \limits_{y_{j}=c} x_{ji} +\alpha n} θci=i=1∑nyj=c∑xji+αnyj=c∑xji+α
对于每个特征 ∑ y j = c x j i \sum \limits _{y_{j}=c} x_{ji} yj=c∑xji是特征 X i X_{i} Xi下所有标签为c的样本的特征取值之和,其实就是特征矩阵中每一列的和。 ∑ i = 1 n ∑ y j = c x j i \sum \limits_{i=1}^{n}\sum \limits_{y_{j}=c} x_{ji} i=1∑nyj=c∑xji是所有标签类别为c的样本上,所有特征的取值之和,其实就是特征矩阵 X y X_{y} Xy中所有元素的和。 α \alpha α被称为平滑系数,我们令 α > 0 \alpha > 0 α>0来防止训练数据中出现过的一些词汇没有出现在测试集中导致的0概率,以避免让参数 为0的情况。如果我们将 α \alpha α设置为1,则这个平滑叫做拉普拉斯平滑,如果 α \alpha α小于1,则我们把它叫做利德斯通平滑。两种平滑都属于自然语言处理中比较常用的用来平滑分类数据的统计手段。
在sklearn中,用来执行多项式朴素贝叶斯的类MultinomialNB包含如下的参数和属性:
class sklearn.naive_bayes.MultinomialNB (alpha=1.0, fit_prior=True, class_prior=None)。
alpha : 浮点数, 可不填 (默认为1.0)
拉普拉斯或利德斯通平滑的参数 α \alpha α,如果设置为0则表示完全没有平滑选项。但是需要注意的是,平滑相当于人为给概率加上一些噪音,因此 α \alpha α设置得越大,多项式朴素贝叶斯的精确性会越低(虽然影响不是非常大)。
fit_prior : 布尔值, 可不填 (默认为True)是否学习先验概率 P ( Y = c ) P(Y=c) P(Y=c)。如果设置为false,则不使用先验概率,而使用统一先验概率(uniform prior),即认为每个标签类出现的概率是 1 n _ c l a s s e s \frac{1}{n\_classes} n_classes1 。
class_prior:形似数组的结构,结构为(n_classes, ),可不填(默认为None)类的先验概率 P ( Y = c ) P(Y=c) P(Y=c) 。如果没有给出具体的先验概率则自动根据数据来进行计算。
通常我们在实例化多项式朴素贝叶斯的时候,我们会让所有的参数保持默认。先来简单建一个多项式朴素贝叶斯的例子试试看:
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)
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_
np.unique(Ytrain)
(Ytrain == 1).sum()/Ytrain.shape[0]
mnb.class_log_prior_.shape
#可以使用np.exp来查看真正的概率值
np.exp(mnb.class_log_prior_)
#重要属性:返回一个固定标签类别下的每个特征的对数概率log(P(Xi|y))
mnb.feature_log_prob_
mnb.feature_log_prob_.shape
#重要属性:在fit时每个标签类别下包含的样本数。当fit接口中的sample_weight被设置时,该接口返回的值也会受
到加权的影响
mnb.class_count_
mnb.class_count_.shape
#一些传统的接口
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)
可以看出,多项式朴素贝叶斯的基本操作和代码都非常简单。同样的数据,如果采用哑变量方式的分箱处理,多项式贝叶斯的效果会突飞猛进。作为在文本分类中大放异彩的算法,我们将会在案例中来详细讲解多项式贝叶斯的使用,并为大家介绍文本分类的更多细节。
多项式朴素贝叶斯可同时处理二项分布(抛硬币)和多项分布(掷骰子),其中二项分布又叫做伯努利分布,它是一种现实中常见,并且拥有很多优越数学性质的分布。因此,既然有着多项式朴素贝叶斯,我们自然也就又专门用来处理二项分布的朴素贝叶斯:伯努利朴素贝叶斯。
伯努利贝叶斯类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)拉普拉斯或利德斯通平滑的参数 α \alpha α,如果设置为0则表示完全没有平滑选项。但是需要注意的是,平滑相当于人为给概率加上一些噪音,因此 α \alpha α设置得越大,多项式朴素贝叶斯的精确性会越低(虽然影响不是非常大)。
binarize : 浮点数或None,可不填,默认为0,将特征二值化的阈值,如果设定为None,则会假定说特征已经被二值化完毕。
fit_prior : 布尔值, 可不填 (默认为True)是否学习先验概率 P ( Y = c ) P(Y=c) P(Y=c)。如果设置为false,则不使用先验概率,而使用统一先验概率(uniform prior),即认为每个标签类出现的概率是 1 n _ c l a s s e s \frac{1}{n\_classes} n_classes1 。
class_prior:形似数组的结构,结构为(n_classes, ),可不填(默认为None)类的先验概率 P ( Y = c ) P(Y=c) P(Y=c) 。如果没有给出具体的先验概率则自动根据数据来进行计算。
在sklearn中,伯努利朴素贝叶斯的实现也非常简单:
from sklearn.naive_bayes import BernoulliNB
#普通来说我们应该使用二值化的类sklearn.preprocessing.Binarizer来将特征一个个二值化
#然而这样效率过低,因此我们选择归一化之后直接设置一个阈值
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)
#设置二值化阈值为0.5
bnl = BernoulliNB(binarize=0.5).fit(Xtrain_, Ytrain)
bnl.score(Xtest_,Ytest)
brier_score_loss(Ytest,bnl.predict_proba(Xtest_)[:,1],pos_label=1)
和多项式贝叶斯一样,伯努利贝叶斯的结果也受到数据结构非常大的影响。因此,根据数据的模样选择贝叶斯,是贝叶斯模型选择中十分重要的一点。
TF-IDF全称term frequency-inverse document frequency,词频逆文档频率,是通过单词在文档中出现的频率来衡量其权重,也就是说,IDF的大小与一个词的常见程度成反比,这个词越常见,编码后为它设置的权重会倾向于越小,以此来压制频繁出现的一些无意义的词。在sklearn当中,我们使用feature_extraction.text中类TfidfVectorizer来执行这种编码。
from sklearn.datasets import fetch_20newsgroups
import numpy as np
import pandas as pd
#初次使用这个数据集的时候,会在实例化的时候开始下载
data = fetch_20newsgroups()
categories = ["sci.space" #科学技术 - 太空
,"rec.sport.hockey" #运动 - 曲棍球
,"talk.politics.guns" #政治 - 枪支问题
,"talk.politics.mideast"] #政治 - 中东问题
train = fetch_20newsgroups(subset="train",categories = categories)
test = fetch_20newsgroups(subset="test",categories = categories)
from sklearn.feature_extraction.text import TfidfVectorizer as TFIDF
Xtrain = train.data
Xtest = test.data
Ytrain = train.target
Ytest = test.target
tfidf = TFIDF().fit(Xtrain)
Xtrain_ = tfidf.transform(Xtrain)
Xtest_ = tfidf.transform(Xtest)
Xtrain_
tosee = pd.DataFrame(Xtrain_.toarray(),columns=tfidf.get_feature_names())
tosee.head()
tosee.shape
from sklearn.naive_bayes import MultinomialNB, ComplementNB, BernoulliNB
from sklearn.metrics import brier_score_loss as BS
name = ["Multinomial","Complement","Bournulli"]
#注意高斯朴素贝叶斯不接受稀疏矩阵
models = [MultinomialNB(),ComplementNB(),BernoulliNB()]
for name,clf in zip(name,models):
clf.fit(Xtrain_,Ytrain)
y_pred = clf.predict(Xtest_)
proba = clf.predict_proba(Xtest_)
score = clf.score(Xtest_,Ytest)
print(name)
#4个不同的标签取值下的布里尔分数
Bscore = []
for i in range(len(np.unique(Ytrain))):
bs = BS(Ytest,proba[:,i],pos_label=i)
Bscore.append(bs)
print("\tBrier under {}:{:.3f}".format(train.target_names[i],bs))
print("\tAverage Brier:{:.3f}".format(np.mean(Bscore)))
print("\tAccuracy:{:.3f}".format(score))
print("\n")
从结果上来看,两种贝叶斯的效果都很不错。虽然补集贝叶斯的布里尔分数更高,但它的精确度更高。我们可以使用概率校准来试试看能否让模型进一步突破:
from sklearn.calibration import CalibratedClassifierCV
name = ["Multinomial"
,"Multinomial + Isotonic"
,"Multinomial + Sigmoid"
,"Complement"
,"Complement + Isotonic"
,"Complement + Sigmoid"
,"Bernoulli"
,"Bernoulli + Isotonic"
,"Bernoulli + Sigmoid"]
models = [MultinomialNB()
,CalibratedClassifierCV(MultinomialNB(), cv=2, method='isotonic')
,CalibratedClassifierCV(MultinomialNB(), cv=2, method='sigmoid')
,ComplementNB()
,CalibratedClassifierCV(ComplementNB(), cv=2, method='isotonic')
,CalibratedClassifierCV(ComplementNB(), cv=2, method='sigmoid')
,BernoulliNB()
,CalibratedClassifierCV(BernoulliNB(), cv=2, method='isotonic')
,CalibratedClassifierCV(BernoulliNB(), cv=2, method='sigmoid')
]
for name,clf in zip(name,models):
clf.fit(Xtrain_,Ytrain)
y_pred = clf.predict(Xtest_)
proba = clf.predict_proba(Xtest_)
score = clf.score(Xtest_,Ytest)
print(name)
Bscore = []
for i in range(len(np.unique(Ytrain))):
bs = BS(Ytest,proba[:,i],pos_label=i)
Bscore.append(bs)
print("\tBrier under {}:{:.3f}".format(train.target_names[i],bs))
print("\tAverage Brier:{:.3f}".format(np.mean(Bscore)))
print("\tAccuracy:{:.3f}".format(score))
print("\n")
可以观察到,多项式分布下无论如何调整,算法的效果都不如补集朴素贝叶斯来得好。因此我们在分类的时候,应该选择补集朴素贝叶斯。对于补集朴素贝叶斯来说,使用Sigmoid进行概率校准的模型综合最优秀:准确率最高,对数损失和布里尔分数都在0.1以下,可以说是非常理想的模型了。
对于机器学习而言,朴素贝叶斯也许不是最常用的分类算法,但作为概率预测算法中唯一一个真正依赖概率来进行计算,并且简单快捷的算法,朴素贝叶斯还是常常被人们提起。并且,朴素贝叶斯在文本分类上的效果的确非常优秀。由此可见,只要我们能够提供足够的数据,合理利用高维数据进行训练,朴素贝叶斯就可以为我们提供意想不到的效果。