EUAdvancer

机器学习-朴素贝叶斯

bg
朴素贝叶斯(Naive Bayesian Model)是基于贝叶斯决策的定理,利用概率论知识进行分类,常应用于文本分类等实际应用

贝叶斯定理

在学习朴素贝叶斯之前,我们首先得知道贝叶斯定理,这个定理非常有用,比如我们已知在B发生的情况下A发生的可能性,但往往我们很难求出交换过来的可能性,那么我们可以通过贝叶斯定理求出在A发生的情况下B发生的可能性。公式如下

P(B|A)是已知A发生后B的条件概率 ,也由于得自A的取值而被称作B的后验概率 。
P(A)是A的先验概率
P(A|B)是已知B发生后A的 条件概率 ,也由于得自B的取值而被称作A的后验概率 。
P(B)是B的先验概率

朴素贝叶斯分类的原理

在上面的贝叶斯公式中,我们假设A为所有特征的集合,B为分类,那么我们的目的就是已知特征求得该分类的概率,也就是我们只需要求得公式右边的3个概率就可以了,而对于P(A),因为在判断拥有这些特征的类分类时是相同的,所以我们不需要求出它,所以到现在我们只需要求出P(A | B) x P(B),假设A中的所有特征都是独立的,也就是互不影响的,那么我可以得出P(A | B) = P(A1 | B) x P(A2 | B)…P(An | B),而A1,A2分别代表每个特征。由此,我们得到求得每个分类概率的公式

然后我们取概率值最大的分类作为该分类算法的结果。

伯努利模型(Bernoulli model)

在上述公式中,对于求P(A | B)有不同的求法,首先介绍的是以以文件为粒度的伯努利模型,对于每个特征它只记录特征是否出现而不计出现几次(比如在文本分类中,一个训练文本多次出现某一单词,但它的结果就是出现该单词(True))。而对于没有出现的单词为了防止概率为0,引入Laplace校准即分子加1,同时分母加上分类个数。即

  • 先验概率:P( c )= 类c下文件总数 / 整个训练样本的文件总数
  • 类条件概率P:P(tk|c)= (类 c 下包含单词tk的文件数 + 1) / (类 c 的文档总数 + 2)

例如:

上面我给了3个样本,对应2个分类。这里我们将所有文字都作为一个特征,利用伯努利模型我们可以得到以下概率

P(yes) = 2 / 3
P(happy | yes) = (1 + 2) / (2 + 2) = 3 / 4 (引入Laplace校准即分子加1,同时分母加上2, 即分类个数;在yes分类的样本中一共有2个样本,而每个样本都有happy)
P(sad | yes) = (1 + 1) / (2 + 2) = 1 / 2
P(friend | yes) = (1 + 1) / (2 + 2) = 1 / 2
P(shit | yes) = (0 + 1) / (2 + 2) = 1 / 4
p(bitch | yes) = (0 + 1) / (2 + 2) = 1 / 4

而求得这些先验概率和条件概率后,如果我们需要预测一个样本m,我们是需要将所有特征的条件概率相乘的,对于没有在m中出现的特征,但是在全局单词表中出现的单词,也会参与计算,不过是作为“反方”参与的。 比如假设m为{happy, shit, sad}

P(yes | m) = P(yes) x P(happy | yes) x P(sad | yes) x P(shit | yes) x (1 - P(friend | yes)) x (1 - p(bitch | yes))

类为no的原理类似,这里就不列出了.这里还有个小细节就是由于太多个太小的数相乘会导致得到程序计算结果变成0,所以我们将上述的公式进行一些转变

1
2
3
根据 log(a * b) = log(a) + log(b) 公式, 我们可以得到以下转换  
log(P(yes | m)) = log(P(yes)) + log(P(happy | yes)) + log(P(sad | yes)) ...
这样我们仍然可以通过计算出来的概率最大值作为我们的分类

用python实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import numpy as np
import math


def trainNB(dataset, labels):
"""训练朴素贝叶斯模型(基于伯努利模型)

先验概率:P(c)= 类c下文件总数 / 整个训练样本的文件总数
类条件概率P:P(tk|c)= (类c下包含单词tk的文件数+1) / (类c下单词总数+2) 【该模型以文件为粒度,所以类c下单词总数以文件为单位计算】
:param dataset: 数据集(numpy格式)
:param labels: 标签
:return: P(0), P(1), P(feature | 0), P(feature | 1)
"""

# 将同一类的数据放在一起
dataLabel_1 = dataset[np.where(labels == 1), :]
dataLabel_0 = dataset[np.where(labels == 0), :]
# 计算每个特征的总数
sum_dataLabel_1 = np.sum(dataLabel_1, 0)
sum_dataLabel_0 = np.sum(dataLabel_0, 0)
# 计算每类的个数
total_dataLabel_1 = dataLabel_1.shape[0]
total_dataLabel_0 = dataLabel_0.shape[0]
# 计算各个类别的所有特征的条件概率
pro_feature_1 = math.log((sum_dataLabel_1 + 1) / (total_dataLabel_1 + 2))
pro_feature_0 = math.log((sum_dataLabel_0 + 1) / (total_dataLabel_0 + 2))
# 计算各个类别的先验概率
pro_1 = total_dataLabel_0 / dataset.shape[0]
pro_0 = total_dataLabel_0 / dataset.shape[0]
return pro_0, pro_1, pro_feature_0, pro_feature_1


def classifyNB(pro_0, pro_1, pro_feature_0, pro_feature_1, newdata):
"""根据朴素贝叶斯模型分类

newdata需要根据训练字典转化为仅含1,0的列表
:param pro_0: P(0)
:param pro_1: P(1)
:param pro_feature_0: P(feature | 0)
:param pro_feature_1: P(feature | 1)
:param newdata: 要预测的数据
:return: 预测结果
"""

# 得到预测的文本包含的单词和不包含单词但出现在训练字典中的两部分
newdata_appear = newdata[np.where(newdata == 1)]
new_disappear = newdata[np.where(newdata == 0)]

# 得到新文本含有的单词在训练字典中对应部分的概率
p1_appear = pro_feature_1[np.where(newdata == 1)]
p1_disappear = pro_feature_1[np.where(newdata == 0)]
# 新文本含有的单词使用P(feature | 1),不含有的单词使用1 - P(feature | 1)
p1 = np.sum(p1_appear * newdata_appear) + np.sum((1 - p1_disappear) * new_disappear) + math.log(pro_1)

p0_appear = pro_feature_0[np.where(newdata == 1)]
p0_disappear = pro_feature_0[np.where(newdata == 0)]
p0 = np.sum(p0_appear * newdata_appear) + np.sum((1 - p0_disappear) * new_disappear) + math.log(pro_0)

return 1 if p1 > p0 else 0

多项式模型(Multinomial model)

多项式模型以单词为粒度,特征同样是单词,但值不再是原先的是否出现(True or False),现在的值是单词的出现次数,也就是同一个单词出现几次。对于没有出现的单词,分子同样采用Laplace校准即分子加1,而分母则加上整个训练集共有的不重复单词数

  • 先验概率P( c ) = 类c下单词总数 / 整个训练样本的单词总数
  • 类条件概率P(tk|c) = (类c下单词tk在各个文档中出现过的次数之和 + 1) / (类c下单词总数 + |V|)

同样是上面的例子

利用多项式模型,我们可以得到以下

P(yes) = 2 / 3
P(happy | yes) = (1 + 3) / (5 + 5) = 2 / 5 (因为训练样本里共有5种单词,所以分母加5)
P(sad | yes) = (1 + 1) / (5 + 5) = 1 / 5
P(friend | yes) = (1 + 1) / (5 + 5) = 1 / 5
P(shit | yes) = (1 + 0) / (5 + 5) = 1 / 10 (没有出现的单词,引入Laplace校准即分子加1,同时分母加上5)
p(bitch | yes) = (1 + 0) / (5 + 5) = 1 / 10

而求得这些先验概率和条件概率后,如果我们需要预测一个样本m,在多项式模型中,只有在m中出现过的单词,才会参与后验概率计算。 比如假设m为{happy, happy, shit, sad}

P(yes | m) = P(yes) x P(happy | yes) x P(happy | yes) x P(shit | yes) x P(sad | yes)

用python实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import numpy as np
import math


def trainNB(dataset, labels):
"""训练朴素贝叶斯模型(基于多项式模型)

先验概率P( c ) = 类c下单词总数 / 整个训练样本的单词总数
类条件概率P(tk|c) = (类c下单词tk在各个文档中出现过的次数之和 + 1) / (类c下单词总数 + |V|)
:param dataset: 数据集(numpy格式)
:param labels: 标签
:return: P(0), P(1), P(feature | 0), P(feature | 1)
"""

# 将同一类的数据放在一起
dataLabel_1 = dataset[np.where(labels == 1), :]
dataLabel_0 = dataset[np.where(labels == 0), :]
# 计算每个特征的总数
sum_dataLabel_1 = np.sum(dataLabel_1, 0)
sum_dataLabel_0 = np.sum(dataLabel_0, 0)
# 计算同类特征总数(单词总数)
total_dataLabel_1 = np.sum(dataLabel_1)
total_dataLabel_0 = np.sum(dataLabel_0)
total_all_feature = np.sum(dataset)
# 计算分母(同类的无重复单词个数)
total_feature_0 = dataset.size - len(np.where(dataset == 0)[0]) # 对于矩阵会分别返回行索引和列索引元组,所以取任意一个就可以了
# 计算各个类别的所有特征的条件概率
pro_feature_1 = math.log((sum_dataLabel_1 + 1) / (total_dataLabel_1 + total_feature_0))
pro_feature_0 = math.log((sum_dataLabel_0 + 1) / (total_dataLabel_0 + total_feature_0))
# 计算各个类别的先验概率
pro_1 = total_dataLabel_1 / total_all_feature
pro_0 = total_dataLabel_0 / total_dataLabel_0
return pro_0, pro_1, pro_feature_0, pro_feature_1


def classifyNB(pro_0, pro_1, pro_feature_0, pro_feature_1, newdata):
"""根据朴素贝叶斯模型分类

newdata需要根据训练字典转化为仅含1,0的列表
:param pro_0: P(0)
:param pro_1: P(1)
:param pro_feature_0: P(feature | 0)
:param pro_feature_1: P(feature | 1)
:param newdata: 要预测的数据
:return: 预测结果
"""

# 得到预测的文本包含的单词和不包含单词但出现在字典中的两部分
newdata_appear = newdata[np.where(newdata == 1)]

# 得到新文本含有的单词在字典中对应部分的概率
p1_appear = pro_feature_1[np.where(newdata == 1)]
# 仅使用新文本含有的单词P(feature | 1)
p1 = np.sum(p1_appear * newdata_appear) + math.log(pro_1)

p0_appear = pro_feature_0[np.where(newdata == 1)]
p0 = np.sum(p0_appear * newdata_appear) + math.log(pro_0)

return 1 if p1 > p0 else 0

高斯分布

上述说的两种模型都是针对离散值,关于离散值和连续变量

离散型随机变量与连续型随机变量也是由随机变量取值范围(或说成取值的形式)确定, 变量取值只能取离散型的自然数,就是离散型随机变量, 比如,一次掷20个硬币,k个硬币正面朝上, k是随机变量, k的取值只能是自然数0,1,2,…,20,而不能取小数3.5、无理数√20, 因而k是离散型随机变量。 如果变量可以在某个区间内取任一实数,即变量的取值可以是连续的,这随机变量就称为连续型随机变量

针对连续变量的值,我们无法用上述的模型来计算概率,所以我们用到了高斯分布模型,我们假设其符合高斯分布(正态分布),即

因此只要计算出训练样本中各个类别中此特征项划分的各均值和标准差,代入上述公式即可得到需要的估计值,而对于待预测样本出现却没有在全局单词表中出现的单词,我们同样引入引入Laplace校准,即对全局单词表所有单词数加1,这样就可以避免0的情况,而预测时和多项式模型一样值仅将待预测样本的特征进行计算

结语

朴素贝叶斯确实很“朴素”,它的思想很简单,就是将每个分类可能性取最大的那个作为分类,而每个分类的概率通过贝叶斯定理求出即可