目录
0、前言
阅读本文之前,需要懂得贝叶斯算法的原理,可以参考机器学习算法的另一个分支-贝叶斯算法原理(贝叶斯要解决什么问题)。
本文实现了,贝叶斯算法,包括邮件预处理,将文本转换成词向量,语料库的构建,训练和测试模块实现,即:先验概率的计算,测试邮件在垃圾邮件中和正常邮件的词频统计。
1. 存在问题
1.1 如果测试邮件的某一个词不在正常邮件(垃圾邮件)的语料库里,那么得到的导致,这是要不得的。那么解决的方法就是默认每一词的次数都是1开始。
1.2 在词频统计时每个词出现的次数少,那么得到的概率就变小,那么在进行计算得到值很小。解决方法就是采用求对数的方式,将值变大,将乘法转换成加法。
2、代码实现
2.1 预处理
#预处理
def textParse(input_string):
'''
把文章转换成词,用list存储
:param input_string:
:return:
'''
listofTokens = re.split(r'\w+',input_string) #切分单词
return [tok.lower() for tok in listofTokens if len(listofTokens)>2] #把单词转换成小写
2.2 构建语料库
def creatVocablist(doclist):
'''
生成样本的语料库,把测试的样本生成语料库 用list存储
:param doclist:
:return:
'''
vocabSet = set([])
for document in doclist:
vocabSet = vocabSet|set(document)
return list(vocabSet)
2.3 转换词向量
def setOfWord2Vec(vocablist,inputSet): #词向量
'''
把文本转成向量
:param vocablist:
:param inputSet:
:return:
'''
returnVec = [0]*len(vocablist) #为方便计算 大小为语料库一样大
for word in inputSet:
if word in vocablist:
returnVec[vocablist.index(word)] = 1 #表示出现
return returnVec
2.4 训练模块
根据训练集得到先验概率,测试邮件在垃圾邮件中和正常邮件的词频统计。
def trainNB(trainMat,trainClass):
numTrainDocs = len(trainMat)
numWords = len(trainMat[0])
p1 = sum(trainClass)/float(numTrainDocs)
p0Num = np.ones((numWords)) # 做了一个平滑处理 防止最后结果为0
p1Num = np.ones((numWords)) # 拉普拉斯平滑
p0Denom = 2
p1Denom = 2 #通常情况下都是设置类别个数
for i in range(numTrainDocs):
if trainClass[i] == 1: #垃圾邮件
p1Num += trainMat[i]
p1Denom += sum(trainMat[i])
else:
p0Num += trainMat[i]
p0Denom += sum(trainMat[i])
#贝叶斯公式对数变换
p1Vec = np.log(p1Num/p1Denom)
p0Vec = np.log(p0Num/p0Denom)
return p0Vec,p1Vec,p1
2.5 测试模块
根据贝叶斯公式得出邮件类别
def classifyNB(wordVec,p0Vec,p1Vec,p1_class):
'''
贝叶斯公式转换 化成对数就是加法
:param wordVec:
:param p0Vec:
:param p1Vec:
:param p1_class:
:return:
'''
p1 = np.log(p1_class) + sum(wordVec*p1Vec)
p0 = np.log(1.0 - p1_class) + sum(wordVec*p0Vec)
if p0 > p1:
return 0
else:
return 1
2.6 主模块
def spam():
doclist = []
classlist = []
for i in range(1,26):
wordlist = textParse(open('Spam/%d.txt'%i,'r').read())
doclist.append(wordlist)
classlist.append(1) #1代表垃圾邮件
wordlist = textParse(open('Ham/%d.txt' % i, 'r').read())
doclist.append(wordlist)
classlist.append(0) # 0代表正常邮件
vocablist = creatVocablist(doclist) #语料表
trainSet = list(range(50))
testSet = []
for i in range(10):
randIndex = int(random.uniform(0,len(trainSet)))
testSet.append(trainSet[randIndex])
del (trainSet[randIndex])
#训练的向量和标签
trainMat = []
trainClass = []
#将文本转换成向量
for docIndex in trainSet:
trainMat.append(setOfWord2Vec(vocablist,doclist[docIndex]))
trainClass.append(classlist[docIndex])
#分类别统计词频
p0Vec,p1Vec,p1 = trainNB(np.array(trainMat),np.array(trainClass))
errorCount = 0
for docIndex in testSet:
wordVoc = setOfWord2Vec(vocablist, doclist[docIndex])
if classifyNB(np.array(wordVoc),p0Vec,p1Vec,p1) != classlist[docIndex]:
errorCount += 1
print('当前是{}个测试样本错了'.format(errorCount))
3、完整代码及结果
import numpy as np
import re
import random
#预处理
def textParse(input_string):
'''
把文章转换成词,用list存储
:param input_string:
:return:
'''
listofTokens = re.split(r'\w+',input_string) #切分单词
return [tok.lower() for tok in listofTokens if len(listofTokens)>2] #把单词转换成小写
def creatVocablist(doclist):
'''
生成样本的语料库,把测试的样本生成语料库 用list存储
:param doclist:
:return:
'''
vocabSet = set([])
for document in doclist:
vocabSet = vocabSet|set(document)
return list(vocabSet)
def setOfWord2Vec(vocablist,inputSet): #词向量
'''
把文本转成向量
:param vocablist:
:param inputSet:
:return:
'''
returnVec = [0]*len(vocablist) #为方便计算 大小为语料库一样大
for word in inputSet:
if word in vocablist:
returnVec[vocablist.index(word)] = 1 #表示出现
return returnVec
def trainNB(trainMat,trainClass):
numTrainDocs = len(trainMat)
numWords = len(trainMat[0])
p1 = sum(trainClass)/float(numTrainDocs)
p0Num = np.ones((numWords)) # 做了一个平滑处理 防止最后结果为0
p1Num = np.ones((numWords)) # 拉普拉斯平滑
p0Denom = 2
p1Denom = 2 #通常情况下都是设置类别个数
for i in range(numTrainDocs):
if trainClass[i] == 1: #垃圾邮件
p1Num += trainMat[i]
p1Denom += sum(trainMat[i])
else:
p0Num += trainMat[i]
p0Denom += sum(trainMat[i])
#贝叶斯公式对数变换
p1Vec = np.log(p1Num/p1Denom)
p0Vec = np.log(p0Num/p0Denom)
return p0Vec,p1Vec,p1
def classifyNB(wordVec,p0Vec,p1Vec,p1_class):
'''
贝叶斯公式转换 化成对数就是加法
:param wordVec:
:param p0Vec:
:param p1Vec:
:param p1_class:
:return:
'''
p1 = np.log(p1_class) + sum(wordVec*p1Vec)
p0 = np.log(1.0 - p1_class) + sum(wordVec*p0Vec)
if p0 > p1:
return 0
else:
return 1
def spam():
doclist = []
classlist = []
for i in range(1,26):
wordlist = textParse(open('Spam/%d.txt'%i,'r').read())
doclist.append(wordlist)
classlist.append(1) #1代表垃圾邮件
wordlist = textParse(open('Ham/%d.txt' % i, 'r').read())
doclist.append(wordlist)
classlist.append(0) # 0代表正常邮件
vocablist = creatVocablist(doclist) #语料表
trainSet = list(range(50))
testSet = []
for i in range(10):
randIndex = int(random.uniform(0,len(trainSet)))
testSet.append(trainSet[randIndex])
del (trainSet[randIndex])
#训练的向量和标签
trainMat = []
trainClass = []
#将文本转换成向量
for docIndex in trainSet:
trainMat.append(setOfWord2Vec(vocablist,doclist[docIndex]))
trainClass.append(classlist[docIndex])
#分类别统计词频
p0Vec,p1Vec,p1 = trainNB(np.array(trainMat),np.array(trainClass))
errorCount = 0
for docIndex in testSet:
wordVoc = setOfWord2Vec(vocablist, doclist[docIndex])
if classifyNB(np.array(wordVoc),p0Vec,p1Vec,p1) != classlist[docIndex]:
errorCount += 1
print('当前是{}个测试样本错了'.format(errorCount))
if __name__ == '__main__':
spam()
4、说明
本文所用的数据集包括25封正常邮件,25封垃圾邮件
数据集获取读者可以自行搜索下载。