朴素贝叶斯(Naive Bayes)算法理论与实践

简介

朴素贝叶斯(naive Bayes)算法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入输出的联合概率分布。然后基于此模型,对给定的输入x,利用贝叶斯定理求出后验概率最大的输出y。不同于其他分类器,朴素贝叶斯是一种基于概率理论的分类算法;总体来说,朴素贝叶斯原理和实现都比较简单,学习和预测效率较高,是一种经典而常用的分类算法。其中的朴素(naive)是指的对于数据集中的各个特征(feature)都有较强的独立性假设,并未将特征之间的相关性考虑其中。

贝叶斯公式

这里首先不加证明地给出贝叶斯定理:
P(c | x) = \frac {P(c)P(x|c)} {P(x)}
其中,P(c | x)称为后验(posterior)概率; P(c)是类先验(prior)概率;P(x|c)是样本x相对于类标记c的类条件概率(class-conditional proability),或称为似然(likelihood)概率;P(x)是用于归一化的证据(evidence)因子。对于给定样本x,证据因子P(x)与类别标记无关,因此估计后验概率P(c|x)的问题就转化为如何基于训练数据D来估计先验概率P(c)和似然P(x|c)

这里补充一下关于先验概率和后验概率的概念:
先验概率: 根据以往经验或经过数据统计得到的概率。例如我们可以通过统计训练数据集来计算得到先验概率P(c)
后验概率:事情已经发生,求这件事情发生的原因是由某个因素引起的可能性的大小。比如P(c | x)称为c的后验概率,即它是在观察到事件x发生之后计算得到的。
可参考:https://zhuanlan.zhihu.com/p/26464206

贝叶斯定义之所以有用,是因为我们通常很难直接得到后验概率P(c|x),但是先验概率P(c)和似然概率P(x|c)反而比较好得到,通过这两者我们可以计算得到后验概率。实际上,机器学习所要实现的就是基于有限的训练样本集合尽可能准确地估计出后验概率P(c|x)。一般来说有两种策略:给定x,直接建模P(c|x)来预测c,这样得到的是“判别式模型”(discriminative models),常见的包括决策树模型、神经网络、支持向量机、逻辑回归模型等等;也可以对联合概率分布P(x,c)建模,然后再由此获取P(c|x),这样得到就是“生成式模型”(generative models),比如本文介绍的朴素贝叶斯模型。

极大似然估计

贝叶斯公式确定了,现在的问题变成了如何从训练样本集合中去估计先验概率P(c)和似然P(x|c)。类先验概率P(x)表达了样本空间中各类样本所占的比例,根据大数定律,当训练集包含充足的独立同分布样本时,P(c)可通过各类样本出现的频率来进行估计。但是对于类条件概率P(x|c)来说,由于它涉及到了关于x所有属性的联合概率,实际上是无法进行估计的。比如每个样本多具有d个属性,每个属性都是二值的,那么样本空间将有2^d种可能的取值,在实际应用中,样本一般都具有多个特征,并且每个特征的取值也各不相同,这样组合下来的取值会远训练样本的数量。这会使得条件概率分布P(x|c)具有指数级数量的参数,造成组合爆炸的问题。
朴素贝叶斯对条件概率分布做了条件独立性的假设,也正是因为这一假设,朴素贝叶斯因此得名。具体来说,条件独立性假设是:
P(x|c) = P(x^{(1)}, x^{(2)},...,x^{(n)} | c) = \prod_{j=1}^n P(x^{j} | c)
朴素贝叶斯实际上学习到生成数据的机制,所以属于生成模型。条件独立假设等于是说用于分类的特征在类别确定的条件下是条件独立的。这一假设使得朴素贝叶斯变得简单,但是特征实际上一般是互相有依赖的,并不完全满足这个假设,因此朴素贝叶斯也牺牲了一定的准确率。
有了上述前提之后,我们现在使用极大似然估计的方法来估计先验概率P(c)和似然P(x|c)

  • 先验概率P(c)的极大似然估计是:
    P(c_k) = \frac {\sum_{i}^N I(y_i = c_k) } {N}, k={1,2, ... ,K}
    其中N是样本集的总数,K是类别的总数,I表示事件。

  • 离散特征下,条件概率P(x|c)的极大似然估计是:
    P(x^{(j)} = a_{jl}|y = c_k) = \frac {\sum_{i=1}^N I(x_i^{(j)} = a_{jl} ,y_i=c_k)} {\sum_{i=1}^N I(y_i=c_k)}
    其中,x_i^{(j)}代表第i个样本的第j个特征;a_{jl}是第j个特征可能取的第l个值。

  • 在连续特征下,这里首先假设概率密度函数P(x|c) \sim N(\mu,\sigma^2),\mu\in\mathbb{R},\sigma^2\in\mathbb{R}^+,则参数\mu_c\sigma_c^2的极大似然估计为:
    \hat \mu_c = \frac {1} {D_c} \sum x \\ \hat \sigma_c = \frac {1} {D_c} \sum (x- \hat \mu_c)(x - \hat \mu_c)^T
    也就是说,通过极大似然估计得到的正态分布均值就是样本均值,方差就是(x- \hat \mu_c)(x - \hat \mu_c)^T的均值,这显然是一个符合直觉的结果。在离散特征属性下,也可以通过类似的方式来估计类条件概率。

需要注意的是,这种参数化的方法虽然能使类条件概率估计变得相对简单,但是估计的准确性严重依赖所假设的概率分布形式是否符合潜在的真实数据分布。在现实应用中,欲做出能较好地接近潜在真实分布的假设,往往需在一定程度上利用关于应用任务本身的经验知识,否则若仅凭“猜测”来假设概率分布形式,很可能会导致错误的结果。

朴素贝叶斯算法步骤

下面给出朴素贝叶斯算法的具体步骤:
输入:训练数据T= \{ (x_1, y_1), (x_2, y_2),..., (x_N, y_N) \}\, 其中x_i = (x_i^{(1)},x_i^{(2)},...x_i^{(n)})^Tx_i^{(j)}是第i个样本的第j个特征,x_i^{(j)} \in \{ a_{j1},a_{j2},...,a_{jS_j} \},a_{jl}是第j个特征可能取的第l个值,j=1,2,...,n, l=1,2,...,S_j,y_i \in ( c_1, c_2,...,c_k)
输出:实例x的分类。

  1. 计算先验概率和条件概率:
    P(y = c_k) = \frac {\sum_{i}^N I(y_i = c_k) } {N}, k=1,2,...,K \\ 对于离散属性而言,条件概率为: P(x^{(j)} = a_{jl}|y = c_k) = \frac {\sum_{i=1}^N I(x_i^{(j)} = a_{jl} ,y_i=c_k)} {\sum_{i=1}^N I(y_i=c_k)} \\ 对于连续属性而言,条件概率为:P(^{(j)} = a_{jl}|y=c_k) = \frac {1}{\sqrt{2 \pi \sigma_{c,j}}} exp(- \frac {(x_i^{(j)} - \mu_{c,j})^2} {2 \sigma_{c,j}^2} )

  2. 对于给定的实例x = (x^{(1)},x^{(2)},...,x^{(j)} ),计算:
    P(y=c_k) \prod_{j=1}^n P(x = x^{(j)}| y=C_k)

  3. 确定x的分类:
    y = argmax_{ck} P(y=c_k) \prod_{j=1}^n P(x = x^{(j)}| y=C_k)

实例解析

下面使用西瓜书中的西瓜数据集3.0来演示一下朴素贝叶斯的整体计算流程。训练样本数据集如下:

import pandas as pd
from io import StringIO

data = '编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜\n\
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.46,是\n\
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是\n\
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是\n\
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是\n\
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是\n\
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是\n\
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是\n\
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是\n\
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否\n\
10,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否\n\
11,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否\n\
12,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否\n\
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否\n\
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否\n\
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.36,0.37,否\n\
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否\n\
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,否'


df = pd.read_csv(StringIO(data))
df
西瓜数据集3.0

我们的测试样本为训练集的第一个样本:

test = df.loc[:0,:]
test["好瓜"] = '?'
test

测试样本

首先计算先验概率P(c),直接观察表格可得:
P(好瓜=是) = 8/17 \approx 0.471 \\ P(好瓜=否) = 9/17 \approx 0.529
其次计算条件概率,分为离散特征和连续特征两步,首先是离散特征。这里出于篇幅原因,只举一个例子,其余类似。以第一个离散属性"色泽"为例,测试样本的色泽为”青绿“,那么分别计算在”是好瓜“和”不是好瓜“这两类事件发生的条件下,并且色泽是”青绿“的条件概率,如下:
P_{青绿|是} = P(色泽=青绿| 好瓜=是) = 3/8 \approx 0.375 \\ P_{青绿|否} = P(色泽=青绿| 好瓜=否) = 3/9 \approx 0.333
其他离散属性同理,这里不再赘述。
接着是连续属性,我们假设其条件概率符合均值为\mu_c且方差为\sigma_c^2的高斯分布,我们首先在数据集中计算出对应的均值和方差。以"密度"为例,将训练集按照是否为好瓜拆成两部分,分别计算两种类别下属性”密度“的均值和方差,如下:

haogua = df['密度'][:8] # 取前8个样本,求密度的均值和标准差
mean, std = haogua.mean(), haogua.std()
print("好瓜的密度均值和标准差:\n", mean, std)

langua = df['密度'][8:] # 取前后9个样本,求密度的均值和标准差
mean, std = langua.mean(), langua.std()
print("烂瓜的密度均值和标准差:\n", mean, std)

结果为:

均值和标准差

则条件概率计算如下:
P_{密度=0.697|是} = P(密度=0.796| 好瓜=是) = \frac {1}{\sqrt {2 \pi } * 0.129 } exp (- \frac { ( 0.697 - 0.574)^2}{2 * 0.129^2}) \approx 1.959

P_{密度=0.697|否} = P(密度=0.796| 好瓜=否) = \frac {1}{\sqrt {2 \pi } * 0.195 } exp (- \frac { ( 0.697 - 0.496)^2}{2 * 0.195^2}) \approx 1.203

接着计算:
P(好瓜=是) \times P_{青绿|是}\times P_{蜷缩|是}\times P_{浊响|是}\times P_{清晰|是}\times P_{凹陷|是}\times P_{硬滑|是}\times P_{密度=0.697|是}\times P_{含糖=0.460|是} \approx 0.052 \\ P(好瓜=否) \times P_{青绿|否}\times P_{蜷缩|否}\times P_{浊响|否}\times P_{清晰|否}\times P_{凹陷|否}\times P_{硬滑|否}\times P_{密度=0.697|否}\times P_{含糖=0.460|否} \approx 6.8 \times 10^{-5}
对比两个概率的大小,由于0.052 > 6.8 \times 10^{-5},所以这个测试样本被朴素贝叶斯分类器判定为”好瓜“。

拉普拉斯平滑

仔细观察上述例子会发现一个问题,若某个属性值在训练集中没有与某个类别同时出现的时候,如果直接使用频率统计来进行概率估计会出现问题。例如,如果某个测试用例中包含了"敲声=清脆"这一属性,那么:
P_{清脆|是} = P(敲声=清脆| 好瓜=是) = 0/8 = 0
由于极大似然估计使用的是连乘,这就会导致估计出的的条件概率为0,这样显然是不合理的。
为了避免其他属性携带的信息被训练集中未出现的属性值给抹去,在估计概率值时通常要进行”平滑“(smoothing)操作,常用的但是拉布拉斯修正(Laplacian correction)。具体来说,条件概率的贝叶斯估计为:
P_{\lambda}(x^{(j)} = a_{jl}|y = c_k) = \frac {\sum_{i=1}^N I(x_i^{(j)} = a_{jl} ,y_i=c_k) + \lambda} {\sum_{i=1}^N I(y_i=c_k) + \lambda S_j}
其中\lambda \ge 0, S_j代表第j个特征的最大取值个数,这等价于在随机变量各个取值的频数上赋予一个正数\lambda > 0,当\lambda = 0时就是极大似然估计。常取\lambda = 1,这时候称为拉普拉斯平滑。显然有:
P_{\lambda}(x^{(j)} = a_{jl}|y = c_k) > 0 \\ \sum_{l=1}^{S_j} P_{\lambda}(x^{(j)} = a_{jl}|y = c_k) = 1
同理,对于先验概率的贝叶斯估计为:
P(c_k) = \frac {\sum_{i}^N I(y_i = c_k) } {N + \lambda K }, k=1,2,...,K \

对于上述的例子,类的先验概率的贝叶斯估计为:
\hat P(好瓜=是) = \frac {8 + 1}{17 + 2} \approx 0.474 \\ \hat P(好瓜=否) = \frac {9 + 1}{17 + 2} \approx 0.526
类似地, 条件概率的贝叶斯估计为:
P_{青绿|是} = P(色泽=青绿| 好瓜=是) = \frac {3 + 1} {8 + 3} \approx 0.364
P_{青绿|否} = P(色泽=青绿| 好瓜=否) = \frac {3 + 1} {9 + 3} \approx 0.333

显然,拉普拉斯平滑避免了因训练集样本不充分而导致的概率估值为0的情况,并且在训练集变大的时候,平滑过程中引入的先验的影响也会逐渐变得可忽略,使得估值趋向于实际概率值。

代码实践

本文不打算自己手写一个朴素贝叶斯的实现,sklean包中包含了各种贝叶斯算法模型,我们依旧是基于西瓜数据集3.0,来使用sklearn中集成的贝叶斯模型来预测上一节的测试样本。

由于原始的离散特征属性是字符串型的,模型无法处理,我们首先需要进行数据预处理,使用LabelEncoder将其转换成数值类型。代码如下:

from sklearn import preprocessing

le = preprocessing.LabelEncoder()
df['色泽'] = le.fit_transform(df['色泽'].values)
df['根蒂'] = le.fit_transform(df['根蒂'].values)
df['敲声'] = le.fit_transform(df['敲声'].values)
df['纹理'] = le.fit_transform(df['纹理'].values)
df['脐部'] = le.fit_transform(df['脐部'].values)
df['触感'] = le.fit_transform(df['触感'].values)
df['好瓜'] = le.fit_transform(df['好瓜'].values)
df
预处理后的数据

接着划分数据集:

# 划分训练集和测试集
train_x, train_y = df[['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率']], df[['好瓜']]
test_x, test_y = df[:1][['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率']], df[:1][['好瓜']]
test_x
测试数据

模型训练和预测:

from sklearn.naive_bayes import GaussianNB

gnb = GaussianNB()
# training
gnb.fit(x, y.values.ravel())
# predicting
pred_y = gnb.predict(test_x)
print(pred_y)

输出为1.
输出

完整代码如下:

import pandas as pd
from io import StringIO

data = '编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜\n\
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.46,是\n\
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是\n\
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是\n\
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是\n\
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是\n\
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是\n\
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是\n\
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是\n\
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否\n\
10,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否\n\
11,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否\n\
12,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否\n\
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否\n\
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否\n\
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.36,0.37,否\n\
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否\n\
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,否'

df = pd.read_csv(StringIO(data), index_col=0)

from sklearn import preprocessing

le = preprocessing.LabelEncoder()
df['色泽'] = le.fit_transform(df['色泽'].values)
df['根蒂'] = le.fit_transform(df['根蒂'].values)
df['敲声'] = le.fit_transform(df['敲声'].values)
df['纹理'] = le.fit_transform(df['纹理'].values)
df['脐部'] = le.fit_transform(df['脐部'].values)
df['触感'] = le.fit_transform(df['触感'].values)
df['好瓜'] = le.fit_transform(df['好瓜'].values)

# 划分训练集和测试集
train_x, train_y = df[['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率']], df[['好瓜']]
test_x, test_y = df[:1][['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率']], df[:1][['好瓜']]

from sklearn.naive_bayes import GaussianNB

gnb = GaussianNB()
# training
gnb.fit(x, y.values.ravel())
# predicting
pred_y = gnb.predict(test_x)
print(pred_y)

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容