贝叶斯之朴素解读

贝叶斯分类器本不是一个复杂的东西,但是博主在网上几翻查找,并未找到有哪一篇博文将其写得易懂。硬着头皮去看书《模式分类》,而书上公式一大堆,实在让人头疼。几番痛苦的学习下,终于明白其中原理。现写出此文,献给各位同志。如果大家觉得这文章写得还不错,日后我可以将此文的pdf共享给大家。
这篇博文总共有4节内容,如果你对贝叶斯分类已经熟悉,只想看看它在图像分类中的应用,请直接跳到第4节。

1、概率论的一些基础知识

考虑到一些小伙伴的概率论的知识有点忘了,这是有必要贴心地帮助大家简单地回顾一下一些基本的概率论的知识。

  • 我们记一个事件\(A\)发的概率为\(P(A)\),那么它是\(0 \le P(A) \le 1\)

  • 我们记事件\((A,\ B)\)发生的概率为\(P(A,\ B)\),有时也记为\(P(AB)\),这表示\(A, \ B\)同时发生的概率;或者记成\(P(A\cap B)\),这几种写法是等价的(如果你觉得不对,可以在下面留言讨论,欢迎拍砖😃)。

  • 我们记事件A或者B发生的概率为\(P(A\cup B)\),这里需要注意一下的是,如果\(A,\ B\)不是相互独立的,那么\(P(A\cup B)\ne P(A)+P(B)\)

说明:\(A, \ B\)独立的意思是:它们两个互不相干,就像"博主长得帅"跟“你一看就有钱"这俩件事没有任何关系一样。

  • 如果\(A,\ B,\ C\)是相互独立的,那么有\(P(ABC)=P(A)P(B)P(C)\)。这可推广到\(n\)种情况,如果事件\(A_1,\ A_2,\cdots,\ A_n\)是相互独立的,有如下关系(这个公式我们下面会用到,记住哦😎)

\[P(A_1A_2\cdots A_n)=P(A_1)P(A_2)\cdots P(A_n) \tag{1-1} \]

  • 条件概率: 已知事件\(B\)发生的情况下,事件\(A\)发生的概率记为\(P(A|B)\)

\[P(A|B)=\frac{P(A,B)}{P(B)}=\frac{P(AB)}{P(B)} \tag{1-2} \]

:条件概率也是一个概率,它只是不同于前的记号,所以概率有的性质,条件概率也有。比如,已知事件\(C\)发生的情况下,\(A, \ B\) 发生的概率记为\(P((A,B)|C)\),如果事件\(A,\ B\)是相互独立的,那么有\(P((A,B)|C)=P(A|C)P(B|C)\)

  • 高斯分布,或者叫作正态分布:假设随机变量\(X\)的均值(更专业一点,称之为期望值)和方差分别为\((\mu,\ \sigma^2)\),且服从高斯分布,那么它的概率密度为

\[f(x)=\frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}}=\frac{1}{\sqrt{2\pi}\sigma}\exp{\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)} \]

好了,我们需要回忆的基本的概率论的知识点就是这么多了。

2、贝叶斯公式

在前面的条件概率中我们回忆到,已知事件\(B\)发生的情况下,事件\(A\)发生的概率为

\[P(A|B)=\frac{P(A,B)}{P(B)}=\frac{P(AB)}{P(B)} \]

根据这个公式,我们可以得一个概率的乘法公式

\[P(AB)=P(A|B)P(B)=P(B|A)P(A) \]

如果我们想求\(P(B|A)\)怎么办呢,同样利用条件概率和乘法公式,有如下推论:

\[P(B|A)=\frac{P(AB)}{P(A)}=\frac{P(A|B)P(B)}{P(A)} \tag{2-1} \]

式(2-1)就是我们大名鼎鼎的贝叶斯公式了,这来得非常容易,并不困难😉。

接下来看看在这个贝叶斯问题是如何应用要我们的分类问题中的。我们现在有\(n\)类别,记为\(c_1,c_2,\cdots,c_n\),每个类别都有\(m\)特征\(x_1.x_2,\cdots,x_m\),我们可以把这些特征记为\(\boldsymbol{x}=(x_1.x_2,\cdots,x_m)\),就是把这些特征用一个向量\(\boldsymbol{x}\)来表示。分类问题可以描述为:已知特征\(\boldsymbol{x}=(x_1.x_2,\cdots,x_m)\)的情况下,判断出它是属于哪个类别\(c_i\)。很明显,这里要求\(c_i \in (c_1,c_2,\cdots,c_n)\)

说明:为了更好地描述上面的分类问题,多哆嗦几句,我在这里举个例子。假设我们现在有一群人,我们要把他们分为3个类:老人(\(c_1\))、年轻人(\(c_2\))、小孩(\(c_3\));用这样4个特征来描述出这些人:身高(\(x_1\))、体重(\(x_2\))、力量(\(x_3\))、是否有白头发(\(x_4\))。需要做的分类问题就是,如果已知一个人身高180cm、体重160kg、力量200kg、无白发,即已知\(\boldsymbol{x}=({x_1=180, \ x_2=160,\ x_3 = 200, \ x_4 = 0})\),求\(P(c_1|\boldsymbol{x}),\ P(c_2|\boldsymbol{x}),\ P(c_3|\boldsymbol{x})\)。很明显,求出哪个概率大,我们就可以判断出这个人是老人,年轻人还是小孩。

据式(2-1)我们可以将上述问题描述为如下贝叶斯公式:

\[P(c_i|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_i)P(c_i)}{P(\boldsymbol{x})}\quad \text{or}\quad posterior=\frac{likelihood\times prior}{evidence} \tag{2-2} \]

其中\(\boldsymbol{x}\)就是我们上面所讲特征的向量记法,表示为\(\boldsymbol{x}=(x_1.x_2,\cdots,x_m)\),其中\(m\)表示特征个数。为了显示专业性,我们还得给上面的几个概率取几个听起来很厉害的名字。\(P(c_i|\boldsymbol{x})\)称为后验概率(posterior),\(P(\boldsymbol{x}|c_{i})\)称为似然概率(likelihood),\(P(c_i)\)称为先验概率(prior),\(P(\boldsymbol{x})\)称为证据因子(evidence)。嗯,我们把它们记下来,以后听到别的大佬讨论的时候,或许还可以弱弱地插上几句话,我们不装X,我们只是知识的搬运工😜。

接下来,我们的工作就是要求解出上面的式子,其中\(P(c_i)\)\(P(\boldsymbol{x})\),并不难求(为什么不难求,我们记着,后面再来解释),而求\(P(\boldsymbol{x}|c_i)\)是件比较麻烦的事情。在这里面,为了简化它的计算,我们假设特征向量\(\boldsymbol{x}\)中的所有特征是相互独立的。如果你还记得式(1-1)的话,我们可得到

\[P(\boldsymbol{x}|c_i)=P{(x_1,x_2,\cdots,x_n|c_i)}=P(x_1|c_i)P(x_2|c_i)\cdots P(x_n|c_i) \tag{2-3} \]

这样的话,我们只需计算出已知为类别\(c_i\)的情况下,每一个特征\(x_k\)的条件概率,然后将它们乘起来。式(2-3)也是我们常说的朴素贝叶斯(Naive Bayes,NB)了,为何朴素,因为简单!!。至于\(P(x_k|c_i)\)如何求,这成了我们分类问题的关键,这也是我们后面要继续探讨。去泡杯茶,我们继续💪!!

3、贝叶斯的两个简单的例子

为了说明如何利用上面的贝叶斯公式,有必要举2个典型的例子,应用我们的贝叶斯公式实战一下,这样才有点成就感。

3.1 据天气情况,我们是否出去玩

如下是一张根据天气情况是否出去玩的表格,据这张表,判断当有如下天气情况时,我们是否出去玩。现在提出这样一个问题,当天气状况为:<Outlook = sunny, Temperature = cool, Humidity = high, Wind = strong>,我们应出去玩吗?。

Day Outlook Temperature Humidity Wind Play?
D1 Sunny Hot High Weak No
D2 Sunny Hot High Strong No
D3 Overcast Hot High Weak Yes
D4 Rain Mild High Weak Yes
D5 Rain Cool Normal Weak Yes
D6 Rain Cool Normal Strong No
D7 Overcast Cool Normal Strong Yes
D8 Sunny Mild High Weak No
D9 Sunny Cool Normal Weak Yes
D10 Rain Mild Normal Weak Yes
D11 Sunny Mild Normal Strong Yes
D12 Overcast Mild High Strong Yes
D13 Overcast Hot Normal Weak Yes
D14 Rain Mild High Strong No

根据第2节讨论的贝叶斯,可以将上面的问题抽象成贝叶斯问题,列出如下表3.1。将各种天气的情况写成一个特征向量;是否出去玩,仅有两类,可以记为:\(c_1=\text{Yes},\ c_2=\text{No}​\)

表3.1 特征表
Day Outlook Temperature Humidity Wind Play?
特征:\(\boldsymbol{x}\) \(x_1\) \(x_2\) \(x_3\) \(x_4\) 类别:\(\boldsymbol{c}=(c_1,\ c_2)\)
D1 Sunny Hot High Weak No
D2 Sunny Hot High Strong No
D3 Overcast Hot High Weak Yes
D4 Rain Mild High Weak Yes
D5 Rain Cool Normal Weak Yes
D6 Rain Cool Normal Strong No
D7 Overcast Cool Normal Strong Yes
D8 Sunny Mild High Weak No
D9 Sunny Cool Normal Weak Yes
D10 Rain Mild Normal Weak Yes
D11 Sunny Mild Normal Strong Yes
D12 Overcast Mild High Strong Yes
D13 Overcast Hot Normal Weak Yes
D14 Rain Mild High Strong No

因此,我们需要求解的问题是两个概率问题:

\[P(c_1|x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong})=P(c_1|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_1)P(c_1)}{P(\boldsymbol{x})} \tag{3-1} \]

\[P(c_2|x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong})=P(c_2|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_2)P(c_2)}{P(\boldsymbol{x})} \tag{3-2} \]

前面我提到过\(P(c_i)\)的计算很简单,但是没有给出理由,在这个例子里面,我们可以先计算\(P(c_1)\)\(P(c_2)\),看看我是否骗了你?

\[P(c_1)=P(\text{Yes})=\frac{9}{14}\\ P(c_2)=P(\text{No})=\frac{5}{14} \]

注:看到了吧, 就是这么容易,我们从上面的表中,数出Yes的个数,然后除以总的情况,这就是“出去玩的概率(Play)”,求No的概率同样是如此,不必我废话了🤐。

我们再求\(P(\boldsymbol{x})=P(x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong})\)。同样我在前面也说过\(P(\boldsymbol{x})\)也不难求,但是这个是比上面的难一点点。首先我们作出第一个假设:各个天气特征是相互独立的,因此我们有

\[P(x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong})=P(\text{sunny})P(\text{cool})P(\text{high})P(\text{strong}) \tag{3-3} \]

那么,\(P(\text{sunny})\)又如何求呢,查看上面的表3.1,我们知道Outlook总共才3种天气情况。天晴(Sunny)出现的次数为5次,因此有

\[P(\text{sunny})=\frac{5}{14} \]

同理,我们得到其他概率为\(P(\text{cool})=\dfrac{4}{14},\quad P(\text{high})=\dfrac{7}{14}, \quad P(\text{strong})=\dfrac{9}{14}\)

好了,我们只剩下最后\(P(\boldsymbol{x}|c_{1}),\ P(\boldsymbol{x}|c_{2})\)概率需要求了。我们先求\(P(\boldsymbol{x}|c_1)\),从数学的字面上理解它:已知\(c_1\)发生的情况下,\(\boldsymbol{x}\)发生的概率,这里,把它翻译成人话就是:我们想要去玩(\(c_1=\text{Yes}\)),而天气情况为\(x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong}\),这时候我们出去玩的可能性多大呢(概率多大)?我们得用到前面的假设了:\(x_1.x_2.x_3,x_4\)是相互独立的。也就是说我们认为,天气晴不晴,温度低不低,温度高不高…,这些因素都是相互独立(你肯定会觉得,温度跟天气晴不晴肯定相关,但为了简单起见,就得认为它们不相关,否则你可能就算不出来了)。据这个假设,我们有

\[P(x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong}|c_1)\\= P(\text{sunny}|c_1)P(\text{cool}|c_1)P(\text{high}|c_1)P(\text{strong}|c_1) \tag{3-4} \]

接下来,我们把已知为\(c_1=\text{Yes}\)的情况表列出来,如下表,共有9条出去玩的记录。

表3.2 出去玩的情况表
Day Outlook Temperature Humidity Wind Play?
特征:\(\boldsymbol{x}\) \(x_1\) \(x_2\) \(x_3\) \(x_4\) 类别:\(\boldsymbol{c}=(c_1)\)
D3 Overcast Hot High Weak Yes
D4 Rain Mild High Weak Yes
D5 Rain Cool Normal Weak Yes
D7 Overcast Cool Normal Strong Yes
D9 Sunny Cool Normal Weak Yes
D10 Rain Mild Normal Weak Yes
D11 Sunny Mild Normal Strong Yes
D12 Overcast Mild High Strong Yes
D13 Overcast Hot Normal Weak Yes

这时候,计算式(3-4)就很简单了,就是数数,这个我们应该很拿手吧。据数数的结果,我们计算出式(3-4)的概率如下:

\[P(\text{sunny}|c_1)=\frac{2}{9}\quad P(\text{cool}|c_1)=\frac{3}{9}\quad P(\text{high}|c_1)=\frac{3}{9} \quad P{(\text{strong}|c_1)}=\frac{3}{9} \]

式(3-1)里的该求的终于都求完了,接下来把它计算出来如下:

\[\begin{eqnarray} P(c_1|\boldsymbol{x})&=&P(c_1|x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong})\\ &=&\frac{P(x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong}|c_1)P(c_1)}{P(x_1=\text{sunny},x_2=\text{cool},x_3=\text{high},x_4=\text{strong})}\\ &=&\frac{P(\text{sunny}|c_1)P(\text{cool}|c_1)P(\text{high}|c_1)P(\text{strong}|c_1)P(c_1)}{P(\text{sunny})P(\text{cool})P(\text{high})P(\text{strong})}\\ &=&\frac{\frac{2}{9}\times\frac{3}{9}\times\frac{3}{9}\times\frac{3}{9}\times\frac{9}{14}}{\frac{5}{14}\times\frac{4}{14}\times\frac{7}{14}\times\frac{9}{14}}=0.484 \end{eqnarray} \]

\(P(c_2|\boldsymbol{x})\)可以根据\(P(c_1|\boldsymbol{x})\)的求法求出来,当然,我们也有\(P(c_2|\boldsymbol{x})=1-P(c_1|\boldsymbol{x})=0.516\),所以有\(P(c_2|\boldsymbol{x})>P(c_1|\boldsymbol{x})\),这个时候我们应该选择不出去玩

注:建议你可以试一试利用求\(P(c_1|\boldsymbol{x})\)的方法来求一下\(P(c_2|\boldsymbol{x})\),纸上得来终觉浅,绝知此事要躬行,动动手,印象会更加深刻的。

我们在这里,思考一个问题,我们是不是不需将具体的\(P(c_1|\boldsymbol{x}),\ P(c_2|\boldsymbol{x})\)计算出来,因为我们只需比较它们的大小,就决定哪个选择。我们回过头来看看式(3-1)和(3-2)

\[P(c_1|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_1)P(c_1)}{P(\boldsymbol{x})} \quad P(c_2|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_2)P(c_2)}{P(\boldsymbol{x})} \]

相信你不难看出,它们俩分母是相同的,所以我们只需要计算出哪个分子大,我们就选哪个。因此我们可以把它记成如下形式

\[C_{NB}=\max_{c_i\in c}\{P(\boldsymbol{x}|c_i)P(c_i)\} \]

其中\(C_{NB}\)为我们最终的决策。更一般地,我们可以将上式推广一下,假设现在有\(m\)个类\(c=\{c_1,c_2,\cdots,c_m\}\),这些类有\(n\)个特征\(\boldsymbol{x}=\{x_1,x_2,\cdots,x_n\}\),因此朴素贝叶斯分类器(Naïve Bayes Classifier)可以记为如下公式

\[C_{NB}=\max_{c_i\in c}\{P(\boldsymbol{x}|c_i)P(c_i)\}=\max_{c_i\in c}{\left(P(c_i)\prod_{j=1}^{n}P(x_j|c_i)\right)} \tag{3-5} \]

注:通过上面的例子,我们推论出了朴素贝叶斯分类器的公式,是不是有点小成就感😎,但是不急,这里面还有不少问题需要我们去解决,且下一个例子。

3.2 这是一朵什么样的花

我们现在有一些鸢尾花的数据,其数据如下表3.3所示,共有15条数据,它有4个属性,萼片长(septal length)、萼片宽(sepal width)、花瓣长(petal length)、花瓣宽(petal width),它共有3个种类,分别标号为0、1、2(对应实际的类名为:'setosa' 、'versicolor'、 'virginica'),每个类别有5条件数据。为了方便说明,我们还是像上面一样,将这些花的特征(属性)记为\(\boldsymbol{x}=(x_1,x_2,x_3,x_4)\),类别记为\(\boldsymbol{c}=(c_1,c_2,c_3)\)

注:你有可能会问,'setosa' 、'versicolor'、 'virginica'这都是些什么花,额…,我也不知道,但这并不影响我们做下面的分类问题,它们具体的介绍可以参考IRIS数据集介绍。这个数据集共有150条数据,为了把我们的问题简化,我只取了其中15条。

表3.3 鸢尾花数据
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) class_no
特征:x x_1 x_2 x_3 x_4 类别:c
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0
5 7.0 3.2 4.7 1.4 1
6 6.4 3.2 4.5 1.5 1
7 6.9 3.1 4.9 1.5 1
8 5.5 2.3 4.0 1.3 1
9 6.5 2.8 4.6 1.5 1
10 6.3 3.3 6.0 2.5 2
11 5.8 2.7 5.1 1.9 2
12 7.1 3.0 5.9 2.1 2
13 6.3 2.9 5.6 1.8 2
14 6.5 3.0 5.8 2.2 2

接下来,我提出一个分类问题,我们有下面这样一组数据,需要判断它是属于哪种鸢尾花。

sepal length (cm)    5.4
sepal width (cm)     3.7
petal length (cm)    1.5
petal width (cm)     0.2

这个问题我觉得肯定难不倒你,因为我们在上面的3.1小节中详细地讲述了这类问题如何用贝叶斯求解,按照上面的方法我们需要求出如下条件概率

\[P(c_1|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_1)P(c_1)}{P(\boldsymbol{x})} \quad P(c_2|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_2)P(c_2)}{P(\boldsymbol{x})} \quad P(c_3|\boldsymbol{x})=\frac{P(\boldsymbol{x}|c_3)P(c_3)}{P(\boldsymbol{x})} \quad \]

同样有在上面的3.1小节中说过,我们只需要比较这几个概率的大小,因此无需计算出\(P(\boldsymbol{x})\)\(P(c_1),P(c_2),P(c_3)\)的计算相信也难不倒机智的我们,因为每个类有5条数据,因此有

\[P(c_1)=P(c_2)=P(c_3)=\frac{5}{15}=\frac{1}{3} \]

到这里一切都OK,因为\(P(c_1)=P(c_2)=P(c_3)\),所以我们只需计算出上面的似然概率。即,我们需要分别计算\(P(\boldsymbol{x}|c_1),\ P(\boldsymbol{x}|c_2),\ P(\boldsymbol{x}|c_3)\),然后比较它们的大小,即可判断出这条数据是属于哪种类别的鸢尾花。好,接下我们就按此思路,我们同样认为这是一个朴素贝叶斯问题,即花的各个属性不相关。我们先计算第一个概率

\[P(\boldsymbol{x}|c_1)=P(x_1=5.4|c_1)P(x_2=3.7|c_1)P(x_3=1.5|c_1)P(x_4=0.2|c_1) \]

你可以去试一试,努力把它计算出来……停在此处,拿起你的纸和笔开始计算吧……,一分钟……5分钟……1小时……过去了,你可能还是没有算出来,也许你在第一分钟的时候就放弃了。因为你发现上面式子中的第一个概率\(P(x_1=5.4|c_1)\)你就没法算,其中\(x_1=5.4\)根本没有在表3.3中出现过,可能你会觉得,没有出现过,那么它的概率不就是0?但是你觉得这样子合理吗?\(x_1\)它是一个连续的值,假如我们的\(x_1=5.099\),你也会觉得\(P(x_1=5.099|c_1)=0\ ?\),它只和\(x_1=5.1\)相差了\(0.001\),你就说它为0,这样是不是有点不公平。这就是我们这个例子与上面3.1小节的例子不同的地方,在上面的3.1小节中,天气状况的属性只有那几个确定的值(在数学上,称之为离散的),而在本节的例子中,这些属性都是一个连续的值。

在此,我们碰了问题,那我们该怎么解决?我们可以去请教一下大佬——高斯(Gauss),高斯大佬说,你可以将\(P(x_1|c_1)\)的概率分布看成一个高斯分布(也叫正态分布,参考我们第一小节中的高斯分布),高斯分布有2个参数\((\mu,\sigma^2)\),分别叫均值和方差。Okay,我们就听高斯大佬的,求出\(P(x_1|c_1)\)的均值和方差。我们将类别0的数据单独拿出来,如下表3.4所示。

表3.4 鸢尾花类别0数据
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) class_no
特征:x x_1 x_2 x_3 x_4 类别:c
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0

根据表3.4,求出\(P(x_1|c_1)\)的均值\(\mu = 4.86\),方差\(\sigma ^2= 0.21^2\),因此我们可以认为\(P(x_1|c_1)\)的概率分布为

\[P(x_1|c_1)=\frac{1}{\sqrt{2\pi\cdot0.21^2}}\exp{\left(-\frac{(x-0.49)^2}{2\cdot 0.21^2}\right)} \]

注:学过概率论的同学有可能会发现这里存一个问题,我们平常所说的高斯分布是一个概率密度分布,但在这里,我们却把它当成了概率分布。其实这是有一定的合理性的,在此我也不必深究了,要是继续讨论下去,博主都可以出书了。把它当一个概率分布的好处是我们可以直接将已知的值代入上面式子,得到概率值,而不必像概率密度那样需要求积分。

我们有了上面的概率分布,求\(P(x_1=5.4|c_1)\)那就是小意思了

\[P(x_1=5.4|c_1)=\frac{1}{\sqrt{2\pi\cdot0.21^2}}\exp{\left(-\frac{(5.4-0.49)^2}{2\cdot 0.21^2}\right)} =0.0995 \]

同理,我们可以计算其它几个概率\(P(x_2=3.7|c_1)=0.41,\ P(x_3=1.5|c_1)=2.08,\ P(x_4=0.2|c_1)=39.89\),最后我们再计算出\(P(x_1=5.4|c_1)P(x_2=3.7|c_1)P(x_3=1.5|c_1)P(x_4=0.2|c_1)=3.41\)

关于\(P(\boldsymbol{x}|c_2),\ P(\boldsymbol{x}|c_3),\ P(\boldsymbol{x}|c_4)\)计算都是同样的道理,在此不赘述了。为了方便大家的计算,我将它们的均值和方差列出如下表3.5

表3.5 鸢尾花各属性均值、方差表
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) class_no
mean 4.860000 3.280000 1.400000 0.200000 0
std 0.207364 0.258844 0.070711 0.000000 0
mean 6.460000 2.920000 4.540000 1.440000 1
std 0.594138 0.383406 0.336155 0.089443 1
mean 6.400000 2.980000 5.680000 2.100000 2
std 0.469042 0.216795 0.356371 0.273861 2

最终我们计算出

\[\begin{eqnarray} P(\boldsymbol{x}|c_1)&=&P(x_1=5.4|c_1)P(x_2=3.7|c_1)P(x_3=1.5|c_1)P(x_4=0.2|c_1)\approx 1\\ P(\boldsymbol{x}|c_2)&=&P(x_1=5.4|c_2)P(x_2=3.7|c_2)P(x_3=1.5|c_2)P(x_4=0.2|c_2)\approx 0\\ P(\boldsymbol{x}|c_3)&=&P(x_1=5.4|c_3)P(x_2=3.7|c_3)P(x_3=1.5|c_3)P(x_4=0.2|c_3)\approx 0\\ \end{eqnarray} \]

注:上面的结果,是我把它们归一化后的结果,即将每个求出的值除以这3个值的和,这样可以得到它们真实的概率值。在表3.5中还存在一个问题,class_no=0的标准差(方差的平方根)是0,这是不能被除的,因此在计算的时候,我们需要将它变成一个比较小的数,如0.01。

因此我们认为有下如下属性的花

sepal length (cm)    5.4
sepal width (cm)     3.7
petal length (cm)    1.5
petal width (cm)     0.2

它属于类别0,即setosa(山尾鸢),我查看它的数据标签,它确实是setosa(你们可能没有数据,查不到它的标签,这里我告诉你就行了,我不会骗你们的),而且我们计算出为类别0的概率值几乎为1,所以它非常可能是类别0。

我在这里将表3.5的高斯概率分布画出来,直观感受一下它们的分布,如图1.1所示。

fig1.1
图1.1 鸢尾花各属性的高斯概率分布

从图中明显可以看出来,"petal length"属性的分布间隔很大,基本上靠这个属性就能判断出类别,再加上其它几个属性共同判别,可以更加准确地分类出来。

4、贝叶斯之图像分类

在这节,我们来用Python语言实现一个贝叶斯图像分类器。在这个Python大火的时候,不会点Python,都不好意思说自已在搞大数据的。

注:这一节里面有较多的代码,大部分代码我在下面基本上都已经写出来了,但为叙述的流畅性,并未全部写出,如果大家有需要,可以在下面留言,我会将代码以百度云的方式共享给大家

4.1 准备工作

工欲善其事,必先利其器。我们先准备好我们的工作环境:jupyter+python3.6,这是我目前用的环境,如果大家没有用过jupyter,我建议大家用一下,相信你会爱上它的。关于jupyter的安装和下载以及使用,我在这里就不说了,聪明的你自会百度或google。其次,我们再准备一下数据集:CIFAR-10图像数据,我将其放入了我的百度网盘,链接: https://pan.baidu.com/s/1yIkiL7xXHsqlXS53gxMkEg 提取码: wcc4。原始的CIFAR-10图像是一个用于普世物体识别的数据集,分为airplane、automobile、bird、cat、deer、dog、frog、horse、ship、truck共10类,但是这里为了简单起见,我用了其中5类。

读取数据

将数据下载好后,把它放在当前目录的data文件夹中。利用如下代码,读取数据,并查看一下数据的信息。一般来讲,数据都是一个字典类型。

from scipy.io import loadmat
import numpy as np

train_data_mat = loadmat("./data/train_data.mat")
test_data_mat = loadmat("./data/test_data.mat")
labels_name_mat = loadmat("./data/labels_name.mat")

经过上面的步骤,我们读取到了数据,分为训练集测试集标签名字。在jupyter的cell输入我们的数据变量,可以查一下它信息,让们知道如何进行一下处理。

train_data_mat
{'__header__': b'MATLAB 5.0 MAT-file, Platform: PCWIN64, Created on: Fri Nov 02 10:09:15 2018',
 '__version__': '1.0',
 '__globals__': [],
 'Data': array([[170, 168, 177, ...,  82,  78,  80],
        [159, 150, 153, ...,  14,  17,  19],
        [ 50,  51,  42, ..., 166, 166, 162],
        ...,
        [195, 151, 160, ..., 133, 135, 133],
        [137, 139, 141, ...,  24,  24,  19],
        [177, 174, 182, ..., 119, 127, 136]], dtype=uint8),
 'Label': array([[1],
        [1],
        [1],
        ...,
        [5],
        [5],
        [5]], dtype=uint8)}

从训练集输出的信息来看,它的标签是从1~5,即有5个类,我们看到了在"Data"里面有一堆数据,但是不知道它的结构,而且这都是一些字典类型的数据,我们需从这些字典中把数据提取出然后看看它们的结构。

# 训练数据和标签
train_data = train_data_mat["Data"]
train_data_label = train_data_mat["Label"]
# 测试数据和标签
test_data = test_data_mat["Data"]
test_data_label = test_data_mat["Label"]
# 标答的实际名字
label_names = labels_name_mat["label_names"]
# 因为原始的标签名字有误,我这里把它手动改一下
label_names[:,0]=['automobile','bird','cat','deer','dog']

然后利用shape属性查看一下它们大概的数据结构

print(train_data.shape, train_data_label.shape,test_data.shape, test_data_label.shape)
(9968, 3072) (9968, 1) (5000, 3072) (5000, 1)

从上面的的输出可以看出来,它共有9968个训练集,5000个测试集,每一个样本的特征个数为\(3072 = 32 \times 32 \times 3\),这是把一个\(32\times32\)的RGB图像平铺成了一个行向量(就是把一个3维数组变成一个1维数组)

从开始的类别中我们知道,这个数据共有5个类,下面我们来看看训练集中,每个类分别有多少样本数量。

注:下面代码中用到了一些Python的库,如果你运行的时候报错了,那么你需要先安装这些包,安装的方法,还是请你自行百度或google哦。

import pandas as pd
pd.value_counts(train_data_label[:,0])
2    2042
3    2011
4    2009
1    1981
5    1925
dtype: int64

从上面输出可以看出,这个数据集的中每个类的数量不是相同的。

显示图像

下面我选几幅图,把测试集和训练集中的图片显示出来给大家看看,直观感受一下这些数据图片是不是有点难以分辨。

fig4.1
图4.1 训练集部分图像
fig4.1
图4.2 测试集部分图像

看了上面的图片,不知道你是什么感受。反正我是觉得,有的图片,人都很难分辨出来。

4.2 开始分类

回顾我们上面所提到的训练数据集,每一个样本的特征个数为\(3072 = 32 \times 32 \times 3\),因此它有3072个特征,可以记为\(\boldsymbol{x}=(x_1,x_2,\cdots,x_{3072})\);且数据集分成5个类\(\boldsymbol{c}=(c_1,c_2,c_3,c_4,c_5)\),分别对应'automobile','bird','cat','deer','dog'。因此我们需求出分布

\[P({x_j}|{c_i}){\rm{ = }}{1 \over {\sqrt {2\pi \sigma _{ji}^2} }}\exp \left( { - {{{{\left( {{x_j} - {\mu _{ji}}} \right)}^2}} \over {2\sigma _{ji}^2}}} \right),\;j = 1,2, \cdots ,3072,i = 1,2,3,4,5 \tag{4-1} \]

其中\(\mu_{ij}\)表示第\(i\)类的第\(j\)个属性的均值,\(\sigma_{ij}^2\)表示第\(i\)类的第\(j\)个属性的方差。因为朴素贝叶斯,各特征间,相互独立,那么由式(4-1)有

\[P({\boldsymbol{x}}|{c_i}) = \mathop \prod \limits_{j = 1}^{3072} P({x_j}|{c_i}) = {1 \over {{{\left( {\sqrt {2\pi } } \right)}^{3072}}\mathop \prod \limits_{j = 1}^{3072} \sigma _{ji}}}\exp \left( {\mathop \sum \limits_{j = 1}^{3072} - {{{{\left( {{x_j} - {\mu _{ji}}} \right)}^2}} \over {2\sigma _{ji}^2}}} \right),\;j = 1,2, \cdots ,3072,i = 1,2,3,4,5 \tag{4-2} \]

上面计算有一定的复杂性,大量的乘法运算,为了简化此运算,我们可以将式(4-2)两边同时取自然对数(\(\ln{x},\ \log{x}\))。因为对数运算是一个单调递增的,所以并不会改变我们对\(P({\boldsymbol{x}}|{c_i}),\ i=1,2,3,4,5\)大小的判定。对式(4-2)两边同时取对数,可以得到如下结果

\[\begin{eqnarray} \log{P(\boldsymbol{x}|c_i)} &=&-\frac{3072\log{\sqrt{2\pi}}}{2} - \sum_{j=1}^{3072}\log{\sigma_{ji}}+{\mathop \sum \limits_{j = 1}^{3072} - {{{{\left( {{x_j} - {\mu _{ji}}} \right)}^2}} \over {2\sigma _{ji}^2}}}\\ &=&-\frac{3072\log{\sqrt{2\pi}}}{2} - \sum_{j=1}^{3072}\left(\log{\sigma_{ji}}+{{{{\left( {{x_j} - {\mu _{ji}}} \right)}^2}} \over {2\sigma _{ji}^2}}\right) \tag{4-3} \end{eqnarray} \]

式(4-3)中的常数项同样不影响我们的判决,所以可去将其去掉,最终我们得到如下一个结果

\[p(\boldsymbol{x}|c_i)=- \sum_{j=1}^{3072}\left(\log{\sigma_{ji}}+{{{{\left( {{x_j} - {\mu _{ji}}} \right)}^2}} \over {2\sigma _{ji}^2}}\right),\ j = 1,2, \cdots ,3072,i = 1,2,3,4,5 \tag{4-4} \]

嗯,看着清爽多了,而且计算量看着也不是很大,我们将式(4-4)称为我们的判决式

注:使用式(4-4)的前的提是\(P(\boldsymbol{x}|c_i)\)是相等的。

根据上面我们辛辛苦苦推导出来的判别式来写我们的代码。首先,我将我们的训练集数据组织成pandasDataFrame数据的格式,这样子更方便我们后面的操作。

import pandas as pd
col_name_lst = [0]*3072

for i in range(1, 3073):
    col_name_lst[i-1] = "x" + str(i)

train_data = pd.DataFrame(train_data,columns=col_name_lst)
train_data_label = pd.DataFrame(train_data_label, columns=['class_no'])
train_dataFrm = train_data.join(train_data_label)
train_data.head()#查看前5行数据
表4.1 训练数据集前5行
x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 ... x3067 x3068 x3069 x3070 x3071 x3072
0 170 168 177 183 181 177 181 184 189 189 ... 83 79 78 82 78 80
1 159 150 153 154 138 184 154 77 61 64 ... 13 16 14 14 17 19
2 50 51 42 62 100 66 29 20 18 11 ... 165 167 165 166 166 162
3 139 144 146 148 145 138 139 136 177 195 ... 60 61 66 80 75 59
4 54 59 56 49 41 50 55 42 38 35 ... 119 110 102 111 106 101
5 rows × 3072 columns

我们同样假设每个特征(即每个像素点服从正态分布),因此据式(4-4),我们最关键的是要将均值方差求出来。我同样把均值和方差做成了DataFrame数据的格式。

train_data_mean = pd.DataFrame(dtype=np.float16)
train_data_std = pd.DataFrame(dtype=np.float16)

for i in range(0,5):
    mean_temp = train_data[train_dataFrm["class_no"]==i+1].mean()
    std_temp = train_data[train_dataFrm["class_no"]==i+1].std()
    
    train_data_mean = train_data_mean.append([mean_temp], ignore_index=True)
    train_data_std = train_data_std.append([std_temp], ignore_index=True)

我们在测试集中,随机取1条数据计算每个类别下的似然概率(即,式4-3)值。

some_img_index = 10
ftr_data = test_data[some_img_index]
determine_clf = [0]*5
prior_series = pd.value_counts(train_data_label.iloc[:,0])

for i in range(0,5):
    ftr_mean = train_data_mean.iloc[i]
    ftr_std = train_data_std.iloc[i]
    prob_temp = -(np.log(ftr_std)+0.5*((ftr_data-ftr_mean)/ftr_std)**2).sum()
    prob_temp = (prob_temp + (-3072*np.log(2*np.pi)/2))*prior_series[i+1]
    determine_clf[i]=prob_temp
    
# 输出各个类下的似然概率值,下面是归一化后的结果
print((determine_clf-min(determine_clf))/(max(determine_clf)-min(determine_clf)))
# 输出测试集的标签值
print(test_data_label[some_img_index])
[1.         0.35530925 0.50774247 0.         0.86720707]
[1]

从输出的结果可以看出,最大值在第1个数,因此它最有可能是类别1,而且输出的标签值正好是1,说明我们预测准确了。你可以将上面代码中的some_img_index换个值,看看是否还是准确的。

最后一步,计算出所有的测试集的预测值,并与测试集的标签对比,得出我们所得的模型在测试集上的准确率。

# 算出测试集中的每一条记录的类别,作出预测
prior_series = pd.value_counts(train_data_label.iloc[:,0])
# 存放预测的标签值
pred_label = [0]*test_data.shape[0]
for img_index in range(0,test_data.shape[0]):
    determine_clf = [0]*5
    ftr_data = test_data[img_index]

    for i in range(0,5):
        ftr_mean = train_data_mean.iloc[i]
        ftr_std = train_data_std.iloc[i]
        prob_temp = -(np.log(ftr_std)+0.5*((ftr_data-ftr_mean)/ftr_std)**2).sum()
        prob_temp = (prob_temp + (-3072*np.log(2*np.pi)/2))#这一行注释也不会影响结果
        determine_clf[i]=prob_temp
    pred_label[img_index] = determine_clf.index(max(determine_clf)) + 1

# 统计预测准确率
sum(pred_label == test_data_label[:,0])/test_data.shape[0]
0.4028

上面输出的准确率为0.4028,可以看到,这并不高,因为图像本身也很模糊。

注:此时,你可会觉得,在图像分类中,贝叶斯的表现并不如人意,相对于卷积神经网络来讲,它确实不如,但是我们有提高其准确率的方法,利用LDA(Linear Discriminant Analysis,线性判别分析)。这里我也不对它多作讨论了,这文章写得有点长了,相信你也看累了,我也累了。

4.3 使用Scikit-Learn的贝叶斯库

上面我们自己动手用贝叶斯算出来分类准确率并不高,下面我们来看看Scikit-Learn的库的贝叶斯函数模型准确率如何。

拟合数据

from sklearn.naive_bayes import GaussianNB
nbbayes_clf = GaussianNB()
#拟合数据
nbbayes_clf.fit(train_data, train_data_label)

利用Predict函数进行预测

print("==Predict result by predict==")
pred_label1 = nbbayes_clf.predict(test_data)
print(pred_label1)
# print "==Predict result by predict_proba=="
# print(clf.predict_proba([[-0.8, -1]]))
# print "==Predict result by predict_log_proba=="
# print(clf.predict_log_proba([[-0.8, -1]]))

sum(pred_label1 == test_data_label[:,0])/test_data.shape[0]
==Predict result by predict==
[3 1 1 ... 3 5 5]

0.403

其输出的准确率为0.403,跟我们的结果非常接近。

总结

在理解贝叶斯之前,我本着朴素理解贝叶斯的初心,然后开始了这篇文章,所以我回忆概率论的一些基本知识,接着顺便推了一个贝叶斯公式,然后又举了两个贝叶斯的例子,最后还把贝叶斯在图像分类中应用了一下,写到最后,我才发现我已经忘了初心,实在是对不起大家,文章写得有点长了,但是自认为已经把贝叶斯这个东西说得很明白了,希望能给大家带来一些帮助。

最后感谢一下我的女朋友,要不是20多年来她从未出现,我也不会在此双11之季,奋笔疾书,写下此篇博客,供诸君品读。日后若有他人问君贝叶斯分类,请君甩出此文,侃侃而谈,而后佛袖离去,深藏功与名。

posted @ 2018-11-11 21:43  EndlessCoding  阅读(1476)  评论(3编辑  收藏  举报