与决策树分类和k近邻分类算法不同,贝叶斯分类主要借助概率论的知识来通过比较提供的数据属于每个类型的条件概率, 将他们分别计算出来然后预测具有最大条件概率的那个类别是最后的类别。当然样本越多我们统计的不同类型的特征值分布就越准确,使用此分布进行预测则会更加准确。
贝叶斯准则
朴素贝叶斯分类器中最核心的便是贝叶斯准则,他用如下的公式表示:
此公式表示两个互换的条件概率之间的关系,他们通过联合概率关联起来,这样使得我们知道p(B|A) 的情况下去计算p(A|B) 成为了可能,而我们的贝叶斯模型便是通过贝叶斯准则去计算某个样本在不同类别条件下的条件概率并取具有最大条件概率的那个类型作为分类的预测结果。
使用条件概率来进行分类
这里我通俗的介绍下如何通过条件概率来进行分类,假设我们看到了一个人的背影,想通过他背影的一些特征(数据)来判断这个人的性别(类别),假设其中涉及到的特征有: 是否是长发, 身高是否在170以上,腿细,是否穿裙子。当我们看到一个背影便会得到一个特征向量用来描述上述特征(1表示是,0表示否): ω=[0,1,1,0]
贝叶斯分类便是比较如下两个条件概率:
- p(男生|ω) ,ω 等于 [0,1,1,0 的条件下此人是男生的概率
- p(女生|ω)) ,ω 等于 [0,1,1,0] 的条件下此人是女生的概率
若p(男生|ω)>p(女生|ω) , 则判定此人为男生, 否则为女生
那么p(男生|ω) 怎么求呢? 这就要上贝叶斯准则了
根据贝叶斯准则,
写成好理解些的便是:
如果特征之间都是相互独立的(条件独立性假设),那么便可以将上述条件概率改写成:
这样我们就能计算当前这个背影属于男生和属于女生的条件概率了。
实现自己的贝叶斯分类器
贝叶斯分类器实现起来非常的简单, 下面我以进行文本分类为目的使用Python实现一个朴素贝叶斯文本分类器.
为了计算条件概率,我们需要计算各个特征的在不同类别下的条件概率以及类型的边际概率,这就需要我们通过大量的训练数据进行统计获取近似值了,这也就是我们训练我们朴素贝叶斯模型的过程.
针对不同的文本,我们可以将所有出现的单词作为数据特征向量,统计每个文本中出现词条的数目(或者是否出现某个词条)作为数据向量。这样一个文本就可以处理成一个整数列表,并且长度是所有词条的数目,这个向量也许会很长,用于本文的数据集中的短信词条大概一共3000多个单词。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
def
get_doc_vector
(
words
,
vocabulary
)
:
''' 根据词汇表将文档中的词条转换成文档向量
:param words: 文档中的词条列表
:type words: list of str
:param vocabulary: 总的词汇列表
:type vocabulary: list of str
:return doc_vect: 用于贝叶斯分析的文档向量
:type doc_vect: list of int
'''
doc_vect
=
[
0
]
*
len
(
vocabulary
)
for
word
in
words
:
if
word
in
vocabulary
:
idx
=
vocabulary
.
index
(
word
)
doc_vect
[
idx
]
=
1
return
doc_vect
|
统计训练的过程的代码实现如下:
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
|
def
train
(
self
,
dataset
,
classes
)
:
''' 训练朴素贝叶斯模型
:param dataset: 所有的文档数据向量
:type dataset: MxN matrix containing all doc vectors.
:param classes: 所有文档的类型
:type classes: 1xN list
:return cond_probs: 训练得到的条件概率矩阵
:type cond_probs: dict
:return cls_probs: 各种类型的概率
:type cls_probs: dict
'''
# 按照不同类型记性分类
sub_datasets
=
defaultdict
(
lambda
:
[
]
)
cls_cnt
=
defaultdict
(
lambda
:
0
)
for
doc_vect
,
cls
in
zip
(
dataset
,
classes
)
:
sub_datasets
[
cls
]
.
append
(
doc_vect
)
cls_cnt
[
cls
]
+=
1
# 计算类型概率
cls_probs
=
{
k
:
v
/
len
(
classes
)
for
k
,
v
in
cls_cnt
.
items
(
)
}
# 计算不同类型的条件概率
cond_probs
=
{
}
dataset
=
np
.
array
(
dataset
)
for
cls
,
sub_dataset
in
sub_datasets
.
items
(
)
:
sub_dataset
=
np
.
array
(
sub_dataset
)
# Improve the classifier.
cond_prob_vect
=
np
.
log
(
(
np
.
sum
(
sub_dataset
,
axis
=
0
)
+
1
)
/
(
np
.
sum
(
dataset
)
+
2
)
)
cond_probs
[
cls
]
=
cond_prob_vect
return
cond_probs
,
cls_probs
|
注意这里对于基本的条件概率直接相乘有两处改进:
- 各个特征的概率初始值为1,分母上统计的某一类型的样本总数的初始值是1,这是为了避免如果有一个特征统计的概率为0,则联合概率也为零那自然没有什么意义了, 如果训练样本足够大时,并不会对比较结果产生影响.
- 由于各个独立特征的概率都是小于1的数,累积起来必然会是个更小的书,这会遇到浮点数下溢的问题,因此在这里我们对所有的概率都取了对数处理,这样在保证不会有损失的情况下避免了下溢的问题。
获取了统计概率信息后,我们便可以通过贝叶斯准则预测我们数据的类型了,这里我并没有直接计算每种情况的概率,而是通过统计得到的向量与数据向量进行内积获取条件概率的相对值并进行相对比较做出决策的。
1
2
3
4
5
6
7
8
|
def
classify
(
self
,
doc_vect
,
cond_probs
,
cls_probs
)
:
''' 使用朴素贝叶斯将doc_vect进行分类.
'''
pred_probs
=
{
}
for
cls
,
cls_prob
in
cls_probs
.
items
(
)
:
cond_prob_vect
=
cond_probs
[
cls
]
pred_probs
[
cls
]
=
np
.
sum
(
cond_prob_vect
*
doc_vect
)
+
np
.
log
(
cls_prob
)
return
max
(
pred_probs
,
key
=
pred_probs
.
get
)
|
进行短信分类
已经构建好了朴素贝叶斯模型,我们就可以使用此模型来统计数据并用来预测了。这里我使用了SMS垃圾短信语料库中的垃圾短信数据, 并随机抽取90%的数据作为训练数据,剩下10%的数据作为测试数据来测试我们的贝叶斯模型预测的准确性。
当然在建立模型前我们需要将数据处理成模型能够处理的格式:
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
|
ENCODING
=
'ISO-8859-1'
TRAIN_PERCENTAGE
=
0.9
def
get_doc_vector
(
words
,
vocabulary
)
:
''' 根据词汇表将文档中的词条转换成文档向量
:param words: 文档中的词条列表
:type words: list of str
:param vocabulary: 总的词汇列表
:type vocabulary: list of str
:return doc_vect: 用于贝叶斯分析的文档向量
:type doc_vect: list of int
'''
doc_vect
=
[
0
]
*
len
(
vocabulary
)
for
word
in
words
:
if
word
in
vocabulary
:
idx
=
vocabulary
.
index
(
word
)
doc_vect
[
idx
]
=
1
return
doc_vect
def
parse_line
(
line
)
:
''' 解析数据集中的每一行返回词条向量和短信类型.
'''
cls
=
line
.
split
(
','
)
[
-
1
]
.
strip
(
)
content
=
','
.
join
(
line
.
split
(
','
)
[
:
-
1
]
)
word_vect
=
[
word
.
lower
(
)
for
word
in
re
.
split
(
r
'\W+'
,
content
)
if
word
]
return
word_vect
,
cls
def
parse_file
(
filename
)
:
''' 解析文件中的数据
'''
vocabulary
,
word_vects
,
classes
=
[
]
,
[
]
,
[
]
with
open
(
filename
,
'r'
,
encoding
=
ENCODING
)
as
f
:
for
line
in
f
:
if
line
:
word_vect
,
cls
=
parse_line
(
line
)
vocabulary
.
extend
(
word_vect
)
word_vects
.
append
(
word_vect
)
classes
.
append
(
cls
)
vocabulary
=
list
(
set
(
vocabulary
)
)
return
vocabulary
,
word_vects
,
classes
|
有了上面三个函数我们就可以直接将我们的文本转换成模型需要的数据向量,之后我们就可以划分数据集并将训练数据集给贝叶斯模型进行统计。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 训练数据 & 测试数据
ntest
=
int
(
len
(
classes
)
*
(
1
-
TRAIN_PERCENTAGE
)
)
test_word_vects
=
[
]
test_classes
=
[
]
for
i
in
range
(
ntest
)
:
idx
=
random
.
randint
(
0
,
len
(
word_vects
)
-
1
)
test_word_vects
.
append
(
word_vects
.
pop
(
idx
)
)
test_classes
.
append
(
classes
.
pop
(
idx
)
)
train_word_vects
=
word_vects
train_classes
=
classes
train_dataset
=
[
get_doc_vector
(
words
,
vocabulary
)
for
words
in
train_word_vects
]
|
训练模型:
1
|
cond_probs
,
cls_probs
=
clf
.
train
(
train_dataset
,
train_classes
)
|
剩下我们用测试数据来测试我们贝叶斯模型的预测准确度:
1
2
3
4
5
6
7
8
9
10
|
# 测试模型
error
=
0
for
test_word_vect
,
test_cls
in
zip
(
test_word_vects
,
test_classes
)
:
test_data
=
get_doc_vector
(
test_word_vect
,
vocabulary
)
pred_cls
=
clf
.
classify
(
test_data
,
cond_probs
,
cls_probs
)
if
test_cls
!=
pred_cls
:
print
(
'Predict: {} -- Actual: {}'
.
format
(
pred_cls
,
test_cls
)
)
error
+=
1
print
(
'Error Rate: {}'
.
format
(
error
/
len
(
test_classes
)
)
)
|
随机测了四组,错误率分别为:0, 0.037, 0.015, 0. 平均错误率为1.3%
测完了我们尝试下看看不同类型短信各个词条的概率分布是怎样的吧:
1
2
3
4
5
6
7
8
9
10
11
|
# 绘制不同类型的概率分布曲线
fig
=
plt
.
figure
(
)
ax
=
fig
.
add_subplot
(
111
)
for
cls
,
probs
in
cond_probs
.
items
(
)
:
ax
.
scatter
(
np
.
arange
(
0
,
len
(
probs
)
)
,
probs
*
cls_probs
[
cls
]
,
label
=
cls
,
alpha
=
0.3
)
ax
.
legend
(
)
plt
.
show
(
)
|