一 前言
上一篇文章介绍了朴素贝叶斯的基本原理, 现在就来实践一下吧, 阅读了部分<机器学习实战>上的代码, 自己也敲了一遍, 做了一下验证, 现在就在这里分享一下.
环境:
Ubuntu 16.04
Python 3.5.2
二 使用朴素贝叶斯进行文档分类
2.1 准备数据: 从文本中构建词向量
加载数据
'''
加载训练数据, postingList是所有的训练集, 每一个列表代表一条言论, 一共有8条言论
classVec代表每一条言论的类别, 0是正常, 1是有侮辱性
返回 言论和类别
'''
def loadDataSet():
postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'hime'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
看一下运行结果:
统计文档中的单词, 生成词汇表, 词汇表中每一个单词只出现一次, 没有重复的. (就是把文档中的所有单词放在一块, 然后去重.)
'''
创建词汇表, 就是把这个文档中所有的单词不重复的放在一个列表里面
'''
def createVocabList(dataSet):
vocabSet = set([]) # 新建一个set集合, 保证里面的数据不重复
for document in dataSet: # 获得每一个文档
vocabSet = vocabSet | set(document) # 文档去重之后和词汇表求并集
return list(vocabSet) # 词汇表转换为列表
这样我们就生成了一个词汇表. 看一下词汇表:
文档转换为词向量, 对于每一个文档, 我们都要把他转换为词向量, 也就是由数字组成的一个向量, 此处的转换很简单. 上一步我们已经创建了一个词汇表, 对于一个文档, 首先我们生成一个和该文档长度一致的全0列表returnVec, 然后遍历该文当中的每一个单词, 如果这个单词在词汇表中出现过, 就在returnVec中相应位置变为1, 如果没出现过, 就仍然保持为0. 最后返回这个列表returnVec.
'''
vocabList是由createVocabList产生的词汇表
inputSet是输入新的文档
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 生成一个全0列表, 个数为输入文档的长度
for word in inputSet: # 遍历输入文档中的每一个单词
if word in vocabList: # 如果这个单词在词汇表中
returnVec[vocabList.index(word)] = 1 # 置1
else: # 否则依然为0
print("the word %s is not in my Vocabulary" % word)
return returnVec
看一下第一个文档转换为词向量后是什么样子:
词向量就是由0和1组成的数组.
2.2 计算先验概率
计算先验概率, 接下来就是计算各种先验概率了, 还记得甚麽是先验概率吗? 不记得的同学可以去前面文章里面看看甚麽是先验概率. P(x1|Y=ck),P(x2|Y=ck)...P(Y=ck) , 在此处我们使用了拉普拉斯平滑, 注意看代码里面的初始化.
首先统计一共有多少个文档, 然后统计词向量的长度, 接着计算侮辱性文档的先验概率, 再初始化p0Num, p0Denom, p0Num就是一个array(numpy), 大小是词向量的长度, 它用于记录当前文档的每一个单词是否在词向量中存在, 当然它是初始化为全1, 也就是拉普拉斯平滑. 请看for循环, 遍历每一个文档, 首先判断当前文档的label是否为侮辱性的, 是侮辱性的就执行p1, 不是就执行p0. 我们看
p1Num += trainMatrix[i] , 这句话是两个array之间的相加, 也就是下图这种情况:
p1Denom += 1这个就是统计当前文档中有多少是属于侮辱性的, <机器学习实战>上写的是p1Denom += sum(trainMatrix[i]), 但是按照计算先验概率的公式, 我认为是加一即可. 最后p1Vect = log(p1Num / p1Denom), 取log是为了防止多个小数相乘出现下溢. 这样就计算出了每一个单词的先验概率, 以及每一个类别的先验概率.
'''
计算先验概率
trainMatrix: 词向量矩阵
trainCategory: 每一个词向量的类别
返回每一个单词属于侮辱性和非侮辱性词汇的先验概率, 以及训练集包含侮辱性文档的概率
'''
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 由训练集生成的词向量矩阵
numWords = len(trainMatrix[0]) # 每一个词向量的长度
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算侮辱性文档的先验概率
p0Num = ones(numWords) # 生成全1 array, 长度为词向量的长度, 用于统计每个单词在整个矩阵中出现的次数(分子)
p1Num = ones(numWords)
p0Denom = 2.0 # 初始化为2(分母), 拉普拉斯平滑
p1Denom = 2.0
for i in range(numTrainDocs): # 遍历每一个词向量
if trainCategory[i] == 1: # 如果该词向量的类别为1
p1Num += trainMatrix[i] # 计算P(x0)..P(xn)
p1Denom += 1 # 统计侮辱性文档的个数
else:
p0Num += trainMatrix[i] # 计算P(x0)..P(xn)
p0Denom += 1 # 统计非侮辱性文档个数
p0Vect = log(p0Num / p0Denom) # 计算P(x0|0)P(xn|0)
p1Vect = log(p1Num / p1Denom) # 计算P(x0|1) P(x1|1) P(xn|1) 取对数是防止多个小数相乘出现下溢
return p0Vect, p1Vect, pAbusive
我们看一下计算结果:
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
p0V, p1V, pAb
接下来就是将训练集里面的文档转换为词向量了. 代码很简单:
'''
制作词向量矩阵
将每一个文档转换为词向量, 然后放入矩阵中
'''
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
2.3 制作分类器, 测试
接下来就是根据上面计算出来的每一个单词的先验概率, 来预测一个未知文档是否具有侮辱性了. 代码如下:
'''
制作贝叶斯分类器
vec2Classify: 测试样本的词向量
p0Vec: P(x0|Y=0) P(x1|Y=0) P(xn|Y=0)
p1Vec: P(x0|Y=1) P(x1|Y=1) P(xn|Y=1)
pClass1: P(y)
# log(P(x1|1)*P(x2|1)*P(x3|1)P(1))=log(P(x1|1))+log(P(x2|1))+log(P(1))
'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
注释部分已经解释了为什么要加上log(pClass1)了.
检验效果的时候到了, 读入一个文档, 根据我们计算的先验概率, 分别计算他属于侮辱性文档的概率和属于非侮辱性文档的概率, 比较两个概率的大小, 大的那一类就是该文档所属于的类.
'''
测试贝叶斯分类器
'''
def testingNB():
listOPosts, listClasses = loadDataSet() # 加载数据
myVocabList = createVocabList(listOPosts) # 词汇表
trainMat = [] # 训练集词向量
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses) # 计算先验概率
testEntry = ['love', 'my', 'dalmation'] # 测试文档1
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as :', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage', 'stupid'] # 测试文档2
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb))
看一下结果吧,
可以看出, 第一个属于非侮辱性的文档, 第二个属于侮辱性的文档. 因为文档中存在stupid这样的单词, 也就是骂人是傻子的意思.所以被判定为侮辱性的.
文章主要参考<机器学习实战>和<统计学习方法>这两本书, 自己也是一个初学者, 文中有纰漏或者不当的地方, 欢迎各位朋友指出来, 咱们共同进步. 谢谢