0%

朴素贝叶斯

首先要明确的一点是朴素贝叶斯属于生成式模型,指导思想是贝叶斯公式。

文本分类

假设现在有一些评论数据,需要识别出这篇文本属于正向评论还是负面评论,也就是对文本进行分类。用数学语言描述就是:
假设已经有分好类的 N 篇文档:(d1,c1)、(d2,c2)、(d3,c3)……(dn,cn),di 表示第 i 篇文档,ci 表示第 i 个类别。目标是:寻找一个分类器,这个分类器能够:当丢给它一篇新文档 d,它就输出 d(最有可能)属于哪个类别 c。

词袋模型

文本分类需要寻找文本的特征。而词袋模型就是表示文本特征的一种方式。词袋模型只考虑一篇文档中单词出现的频率 (次数),用每个单词出现的频率作为文档的特征。
eCyJtP.png

朴素贝叶斯分类器

朴素贝叶斯分类器是一个概率分类器。假设现有的类别 C={c1,c2,……cm}。给定一篇文档 d,文档 d 最有可能属于哪个类呢?这个问题用数学公式表示如下:

c^ 就是:在所有的类别 C={c1,c2,……cm} 中,使得:条件概率 P (c|d) 取最大值的类别。使用贝叶斯公式,将上式转换成如下形式:

对类别 C 中的每个类型,计算 (p (d|c)* p (c))/p (d) 的值,然后选取最大值对应的那个类型 ci ,该 ci 就是最优解 c^,因此,可以忽略掉分母 p (d),可以变成如下形式:

这个公式由两部分组成,前面那部分 P (d|c) 称为似然函数,后面那部分 P (c) 称为先验概率。
前面提到使用词袋模型来表示 文档 d,文档 d 的每个特征表示为:d={f1,f2,f3……fn},那么这里的特征 fi 其实就是单词 wi 出现的频率(次数),因此可以转化成如下形式:

对文档 d 做个假设:假设各个特征之间是相互独立的。那么 p (f1,f2……fn|c)=p (f1|c) p(f2|c) …… * p (fn|c),转化成如下形式:

由于每个概率值很小(比如 0.0001)若干个很小的概率值直接相乘,得到的结果会越来越小。为了避免计算过程出现下溢 (underflower),引入对数函数 Log,在 log space 中进行计算。然后使用词袋模型的每个单词 wi 出现频率作为特征,得到如下公式:

训练朴素贝叶斯分类器

训练朴素贝叶斯的过程其实就是计算先验概率和似然函数的过程。
①先验概率 P (c) 的计算
P (c) 的意思是:在所有的文档中,类别为 c 的文档出现的概率有多大?假设训练数据中一共有 Ndoc 篇文档,只要数一下类别 c 的文档有多少个就能计算 p (c) 了,类别 c 的文档共有 Nc 篇,先验概率的计算公式如下:

先验概率其实就是准备干一件事情时,目前已经掌握了哪些信息了。
②似然函数 P (wi|c) 的计算
由于是用词袋模型表示一篇文档 d,对于文档 d 中的每个单词 wi,找到训练数据集中所有类别为 c 的文档,数一数 单词 wi 在这些文档(类别为 c)中出现的次数:count (wi,c),
然后,再数一数训练数据集中类别为 c 的文档一共有多少个单词,计算二者之间的比值,就是似然函数的值。似然函数计算公式如下:

其中 V,就是词库。(有些单词在词库中,但是不属于类别 C,那么 count (w,c)=0)

unknow words 的情形

假设只考虑文本二分类:将文档分成 positve 类别,或者 negative 类别,C={positive, negative}。
在训练数据集中,类别为 positive 的所有文档 都没有 包含 单词 wi = fantastic(fantastic 可能出现在类别为 negative 的文档中)那么 count (wi=fantastic,ci=positive)=0 。那么:

而注意到前面公式五中的累乘,整篇文档的似然函数值为 0,也就是说:如果文档 d 中有个单词 fantastic 在类别为 c 的训练数据集文档中从未出现过,那文档 d 被分类到类别 c 的概率为 0,尽管文档 d 中还有一些其他单词(特征),而这些单词所代表的特征认为文档 d 应该被分类到类别 c 中。
解决方案就是 add-one smoothing。似然函数公式变成如下形式:

朴素贝叶斯分类示例

假设训练数据集有五篇文档,其中 Negative 类别的文档有三篇,用符号 ‘-‘ 标识;Positive 类别的文档有二篇,用符号 ‘+’ 标识,它们的内容如下:

1
2
3
4
5
-  just plain boring
- entirely predictable and lacks energy
- no surprises and very few laughs
+ very powerful
+ the most fun film of the summer

测试数据集T 有一篇文档dt,内容如下:
1
predictable with no fun

朴素贝叶斯分类器会把“predictable with no fun”归为哪个类呢?根据训练朴素贝叶斯分类器的过程,需要计算先验概率和似然函数。

由于训练数据集中一共有 5 篇文档,其中类别 ‘+’ 的文档有 2 篇,类别为 ‘-‘ 的文档有 3 篇,因此先验概率:P (c)=P (‘-‘)=Nc/Ndoc=3/5=0.6

类别为’+’ 的文档有 2 篇,故 P (c)=P (‘+’)=Nc/Ndoc=2/5=0.4

对测试数据集文档 dt 中的每个单词,似然函数采用 “add-one smoothing” 处理,计算相应的似然概率:

首先单词 predictable 在训练数据集中 类别为’-‘的文档中只出现了 1 次,类别为’-‘的文档一共有 14 个单词,训练数据集中两种类型的文档加起来一共有 23 个单词,但是有三个单词 (and、

very、the) 重复出现了两次,故词库 V 的大小为 20。因此单词 predictable 对应的似然概率如下:

p(predictable|’-‘)=(1+1)/(14+20)=2/34

同理:p (predictable|’+’)=(0+1)/(9+20)=1/29 (predictable 没有在类别为’+’的训练数据集中出现过)

类似地:p (no|’-‘)=(1+1)/(14+20) p (no|’+’)=(0+1)/(9+20)

p(fun|’-‘)=(0+1)/(14+20) p(fun|’+’)=(1+1)/(9+20)

因此,测试集中的文档 d 归类为 ‘-‘ 的概率为:0.6 (221)/343 = 6.110-5

测试集中的文档 d 归类为 ‘+’ 的概率为:0.4(112)/293 =3.210-5

比较上面两个概率的大小,就可以知道将 “predictable with no fun” 归为 ‘-‘ 类别。

代码实现

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import numpy as np

def loaddata():
"""
:return: 文本数据集 和 对应的 label
"""
text=[['just','plain','boring'],

['entirely','predictable','and','lacks','energy'],

['no','surprises','and','very','few','laughs'],

['very','powerful'],

['the' ,'most', 'fun' ,'film' ,'of' ,'the', 'summer']]

label=[0,0,0,1,1]

return text,label

def createVocabList(text):
"""
:param text: 文本数据集
:return: 词语表
"""
vocabSet=set([])
for document in text:
vocabSet=vocabSet|set(document)
return list(vocabSet)

def bag_words_vec(vocab, text):
"""
:param vocab: 词表
:param text: 文本数据集
:return: 通过词袋模型转换后的向量
"""
data = []
for t in text:
vec = [0]*len(vocab)
for word in t:
if word in vocab:
vec[vocab.index(word)]+=1
data.append(vec)
return data

class NB():
def __init__(self,vocab):
self.data = None
self.label = None
self.vocab = vocab
self.vocab_len = len(self.vocab)

def fit(self,data,label):
self.data = data
self.label = label

# 计算每个类别的先验概率
self.pc0 = label.count(0)/len(label)
self.pc1 = label.count(1)/len(label)

# 分出不同类别的数据
self.data0 = [data[i] for i,c in enumerate(label) if c==0]
self.data1 = [data[i] for i,c in enumerate(label) if c==1]

# 计算每个类别中单词的数量,注意如果是len(),就不考虑重复元素,sum()考虑重复元素
self.word_num_0 = sum([i for t in self.data0 for i in t if i!=0])
self.word_num_1 = sum([i for t in self.data1 for i in t if i!=0])

#打印不同类别的单词个数和词表长度
print("类别0的单词个数:{},类别1的单词个数:{},总词表长度:{}".format(self.word_num_0,self.word_num_1,self.vocab_len))

self.word_freq_0 = np.sum(self.data0, axis = 0) #计算每个类别中单词的频率
self.word_freq_1 = np.sum(self.data1, axis = 0)

def pridict(self, text):
# 预测过程

pred = []

# 对于预测集的每一个文本
for t in text:

# 计算属于class 0的概率
p0 = []
for w in t:
# 不在词表中的不计算
if w in vocab:
p0.append((self.word_freq_0[self.vocab.index(w)] + 1)/(self.word_num_0+ self.vocab_len))

# 计算属于class 0的概率
p1 = []
for w in t:
# 不在词表中的不计算
if w in vocab:
p1.append((self.word_freq_1[self.vocab.index(w)] + 1)/(self.word_num_1+self.vocab_len))

print(p0)
print(p1)

print(self.pc0*np.prod(p0))
print(self.pc1*np.prod(p1))

if self.pc0*np.prod(p0)> self.pc1*np.prod(p1):
pred.append(0)
else:
pred.append(1)

return pred


if __name__ == '__main__':

text,label = loaddata()
vocab = createVocabList(text)
data = bag_words_vec(vocab, text)


pred_text = [["predictable","with","no","fun"]]

model = NB(vocab)
model.fit(data,label)
result = model.pridict(pred_text)
print(result)

输出结果:

1
2
3
4
5
6
类别0的单词个数:14,类别1的单词个数:9,总词表长度:20
[0.058823529411764705, 0.058823529411764705, 0.029411764705882353]
[0.034482758620689655, 0.034482758620689655, 0.06896551724137931]
6.106248727864848e-05
3.280167288531715e-05
[0]

支持一根棒棒糖!