这两天复习了一下生成学习算法,顺便实现了朴素贝叶斯垃圾邮件分类,这里谈一下自己的理解.$ $

理论推导

其实理论都是来源于Andrew Ng的课程,这里不做详细的解释,只是说一下自己的理解.
第一:特征,在垃圾邮件分类中就是词典中的单词
第二:特征相互独立,这也是朴素贝叶斯的由来,就是说字典中的单词是否在邮件中出现这是相互独立的(虽然显示不是这样的,但是实际应用中朴素贝叶斯是很有用处的)
第三:垃圾邮件中用到的是离散数值的贝叶斯
我们还是回到概率二分类这个问题上:

\[ p(y=1|x)=\frac{p(x,y=1)}{p(x)}\\ \\ p(y=0|x)=\frac{p(x,y=0)}{p(x)} \]

我们应该就是比较这两个概率,谁大我们就认为是哪一类,等同于比较:

\[ p(x,y=1)=p(x|y=1)p(y=1)\\ \\ p(x,y=0)=p(x|y=0)p(y=0) \]

由于

\[ x=[x_{1},x_{2},\cdots,x_{n}] \]

那么

\[ p(x,y=1)=p(x_{1}|y=1)^{c_{1}}p(x_{2}|y=1)^{c_{2}}\cdots p(x_{n}|y=1)^{c_{n}}p(y=1)\\ \\ p(x,y=0)=p(x_{1}|y=0)^{c_{1}^{'}}p(x_{2}|y=0)^{c_{2}^{'}}\cdots p(x_{n}|y=0)^{c_{n}^{'}}(1-p(y=1))\\ 其中c_{i}和c_{i}^{'}都表示各个特征在不同类别中出现的次数 \]

所以我们的问题就是要求出:

\[ p(x_{i}|y=1)\\ \\ p(x_{i}|y=0)\\ \\ p(y=1) \]

这其实对应的就是Andrew ng课程中的

\[ \phi_{i|y=1}\\ \\ \phi_{i|y=0}\\ \\ \phi_{y} \]

至于Andrew ng说的最大似然,求出上面三个值,我觉得就是利用离散最大似然估计(也有可能他是通过求导求取来的),最后都是求出每个特征出现次数占总特征出现次数的比例

第四:平滑处理

第五:对最终的概率取log

代码

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
# encoding=utf8
from __future__ import division
import numpy as np
import scipy.sparse
def load_data():
"""加载训练和测试数据并处理
Returns:
train_features:ndarray类型,(num_sample*2500);每行词典中的单词分别在一封邮件中出现的次数
train_lables:ndarray类型,(num_sample,);每个元素表示对应的邮件是否为垃圾邮件,用0和1表示.
test_features:同上
test_lables:同上
"""
# 加载数据
train_data=np.loadtxt('./data/email_train_features.txt')
train_lables=np.loadtxt('./data/email_train_labels.txt')
test_data=np.loadtxt('./data/email_test_features.txt')
test_lables=np.loadtxt('./data/email_test_labels.txt')
# 获取行和列以及相应的值
train_row=train_data[:,0]-1
train_col=train_data[:,1]-1
train_frequency=train_data[:,2]
# 利用行,列的位置和值构建矩阵
train_features=scipy.sparse.csr_matrix((train_frequency,(train_row,train_col)))
test_row=test_data[:,0]-1
test_col=test_data[:,1]-1
test_frequency=test_data[:,2]
test_features=scipy.sparse.csr_matrix((test_frequency,(test_row,test_col)))
# 将构建的矩阵稀疏化
train_features=np.array(train_features.todense())
test_features=np.array(test_features.todense())
return train_features,train_lables,test_features,test_lables
def navie_bayes(X,y):
"""
Args:
X:ndarray类型(num_sample*num_feature);样本特征矩阵
y:ndarray类型(num_sample,);样本标签
Returns:
prob_tokens_spam:ndarray类型,(num_feature,);垃圾邮件中,每个特征所占的比例
prob_tokens_nonspam:ndarray类型,(num_feature,);非垃圾邮件中,每个特征所占的比例
prob_spam:flaot类型;所有邮件中,垃圾邮件所占比例
"""
# 获得样本个数和特征个数
num_sample=X.shape[0]
num_feature=X.shape[1]
# 垃圾邮件所在行数
spam_index=np.array(np.where(y==1))[0]
nonspam_index=np.array(np.where(y==0))[0]
# 垃圾邮件占总邮件的概率
prob_spam=len(spam_index)/num_sample
# 在垃圾邮件中,计算训练样本各个特征所出现次数的总和
wc_spam=np.sum(X[spam_index,:],axis=0)
wc_nonspam=np.sum(X[nonspam_index,:],axis=0)
# 求概率以及平滑处理
# 这里加入平滑其实不是为了解决0/0这种情况,因为分母其实不用考虑到计算中(它们都相同) 而是为了解决log0问题.
prob_tokens_spam = (wc_spam + 1) / (sum(wc_spam) + num_feature)
prob_tokens_nonspam = (wc_nonspam + 1) / (sum(wc_nonspam) + num_feature)
return prob_tokens_spam,prob_tokens_nonspam,prob_spam
if __name__ == '__main__':
train_features,train_lables,test_features,test_lables=load_data()
prob_tokens_spam,prob_tokens_nonspam,prob_spam=navie_bayes(train_features,train_lables)
# 求概率,进行了log处理
test_spam_proc = np.sum(test_features*np.log(prob_tokens_spam),axis=1)+ np.log(prob_spam)
test_nonspam_proc = np.sum(test_features*np.log(prob_tokens_nonspam),axis=1) + np.log(1-prob_spam)
# 预测
test_spam=test_spam_proc>test_nonspam_proc
accuracy = np.sum(test_spam==test_lables) / len(test_lables)
print accuracy

代码写得很详细,不过我还是要解释下面一句:

1
test_features*np.log(prob_tokens_spam)

我刚开始就很疑惑为什么log不是先乘然后在整体取了?
如果你注意到理论部分公式中的\(c_{i}\),那么就好解释了,我们其实是对下面的公式取的

\[ log(p(x,y=1)) \]

推荐:
Naive Bayes