文本分类是非常重要的机器学习应用,朴素贝叶斯是是用于文本分类的常用算法。
(1)先将文本转换为词向量
【方法1】词集模型(sef-of-words model)
(如下图所示)整个文档是一个样本,根据观察文档中出现的词条,把每个词条的出现或不出现作为一个特征,这样得到的特征数目就跟词汇表中的词目一样多。其中词条是指字符的任意组合,可以是单词,也可以是非单词(比如URL、IP地址或任意字符串)
/词条向量.png)
缺点:在词集中每个词只能出现一次。如果一个词在文本中出现不止一次,那么该模型就无法表达出这一信息,可以用词袋模型来解决。
【方法2】词袋模型(bag-of-words model)
它与词集模型唯一不同的是:每当遇到一个单词时,就会增加词向量中对应的值,而不只是将对应的数值设为1。
(2)计算文本属于某类的概率
先验概率p(ci):看文本内容之前,仅从收集的文本中有多少是某类的文本,推测某类的概率
条件概率p(w|ci):也称为似然函数,表示某类生成我们观测到的词条的可能性
后验概率p(ci|w):看过文本内容后,对先验概率进行修正,能够针对不同的文本推测出它属于某一类别的概率,高概率对应的类别就是预测结果(体现出贝叶斯决策理论的核心思想)
通过计算先验概率、条件概率训练NB分类器,然后用下图的公式计算后验概率,预测类别
/计算概率.png)
朴素贝叶斯分类器有2个假设:①特征之间相互独立(即一个特征或单词出现的可能性与它和其他单词相邻没有关系)②每个特征同等重要
这2个假设都有问题,①比如bacon出现在unhealthy后面,与出现在delicious后面的概率显然不同,因为bacon经常出现在delicious附近 ②比如要判断留言板的留言是否得当,那么可能不需要看完所有的1000个单词,只需要看10~20个特征就能做出判断了。尽管上述述假设存在一些小的瑕疵,但朴素贝叶斯的实际效果却很好。
代码实现
先统一好变量命名:
①一个文本整体的字符串:textstring
②一个文本的单词列表:wordlist
③一个文本的词向量:wordvec
④多个文本的单词列表:wordlists
⑤多个文本的词向量:wordvecs
⑥词汇表:vocablist
⑦每个文本对应的标签向量label
(1)用词袋模型,将文本转换成词向量
1 | #词袋模型 |
词袋模型需要传入两个列表,由以下两个函数生成:1
2
3
4
5
6# 构建词汇表
def createVocabList(wordlists):
vocabset = set() #创建空的集合
for doc in wordlists: #读取整个数据集的唯一单词
vocabset = vocabset | set(doc) #取并集
return list(vocabset)
1 | #构建文本列表,从文本内容中得到字符串列表 |
(2)构建模型:利用训练集计算先验、条件概率
1 | wordvec |
上面的代码解决了计算条件概率时的两个弊端:
①条件概率要计算多个概率的乘积,如果有一个概率值为0,那么最后的结果也是0。解决:将所有词的出现数初始化为1,并将分母初始化为2
②用Python对多个很小的数相乘,最后四舍五入为0。解决:对乘积取自然对数ln(a*b)=ln(a)+ln(b),因为f(x)与lnf(x)在相同的区域内同时增加、同时减少,并且在相同点上取极值,虽然除了极值点的其它点取值不同,但不影响最终结果,所以可以进行对数变换。
(3)计算后验概率,预测新邮件的类型
1 | #输入一个新的词向量 |
(4)评估模型:计算误分类率
电子邮件放在email文件夹,该文件夹又包含2个子文件夹,分别是spam(垃圾邮件)、ham(正常邮件),各有25封邮件,文件名是1到251
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
38import random
def spamTest():
wordlists = [] #之后放入所有邮件(包括ham、spam)
label = []
for i in range(1,26): #遍历25个邮件
wordlist = textParse(open('email/spam/%d.txt' % i).read()) #打开文件,读取整个邮件内容存入一个字符串,然后进行文本解析,得到单词列表
wordlists.append(wordlist)
label.append(1) #是垃圾邮件
wordlist = textParse(open('email/ham/%d.txt' % i).read())
wordlists.append(wordlist)
label.append(0) #是正常邮件
vocablist = createVocabList(wordlists) #构建词汇列表
train_index = list(range(50)) #这一部分是为了生成随机索引,随机选取测试文本
test_index = []
for i in range(10): #随机选择10封邮件,range函数返回一个迭代器
random_index = int(random.uniform(0,len(train_index)))
test_index.append( train_index[random_index] )
del( train_index[random_index] )
X_train = [] #训练集的特征矩阵、类别
y_train = []
for i in train_index: #遍历训练集索引,训练模型
X_train.append( bagOfWords(vocablist, wordlists[i])) #词袋模型,将文本变为词向量
y_train.append( label[i] ) #提取对应的类别
pw0, pw1, p_prior = trainNB(X_train, y_train) #得到先验、条件概率
errorCount = 0 #定义误分类数
for i in test_index: #遍历测试集,做预测
wordvec = bagOfWords(vocablist, wordlists[i]) #生成词向量
y = classifyNB(wordvec, pw0, pw1, p_prior)
if y != label[i]:
errorCount += 1
print("predcit: %s,label: %s" %(y,label[i]))
print('the error rate is: ',errorCount/len(test_index))
代码问题及解决方法:执行open(….)时,出现错误“UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xae in position 199: illegal multib”,说明文字中某个字节不能解码,即文件中包含了非法字符。循环遍历后发现是ham/23.txt中有个问号字符,删除它就可以了。1
2
3
4
5
6
7>> spamTest()
the error rate is: 0.0
>> spamTest() #每次可能会得到不同的结果(因为测试集是随机抽取的)
predcit: 1,label: 0
predcit: 1,label: 0
the error rate is: 0.2
重复运行多次后发现,错误一直都是:将正常邮件误判为垃圾邮件。相比之下,将垃圾邮件误判为正常邮件,比将正常邮件误判为垃圾邮件要好。为了避免错误,有很多方法可以用来修正分类器,之后再讨论。