之前的一篇手撕《机器学习实战》4—朴素贝叶斯算法,书中在计算先验概率和类条件概率时,把伯努利模型和多项式模型混用了,先验概率用伯努利模型而类条件概率用的既不是伯努利也不是多项式(有点像多项式模型)。在此,我把所有计算过程统一为运用伯努利模型。
代码更正部分主要在以下两个函数中:def trainNB、def classifyNB
使用朴素贝叶斯过滤垃圾邮件
说明:
将 email
文件夹放在当前目录下。
import numpy as np
import re
定义创建列表函数
"""
createVocabList()函数会创建一个包含在所有文档中出现的不重复词的列表
"""
def createVocabList(dataSet):
#创建一个空集
vocabSet = set([])
for document in dataSet:
#再创建一个空集后,将每篇文档返回的新词集合添加到该集合中,再求两个集合的并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
定义词集模型函数(set-of-words)
"""
该函数输入参数为词汇表及某个文档,输出的是文档向量,向量的每一个元素为1或者0,分别
表示词汇表中的单词在输入文档中是否出现
"""
def setOfWords2Vec(vocabList, inputSet):
#函数首先创建一个和词汇表等长的向量,并将其元素都设置为0
returnVec = [0]*len(vocabList)
#接着,遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1。
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
定义词带模型函数(bag-of-words)
"""
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
"""
定义朴素贝叶斯分类器训练函数
"""
函数说明:朴素贝叶斯分类器训练函数
trainMatrix--训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵;trainCategory--训练类别标签向量
p1Vect--标记为1的类条件概率数组;p0Vect--标记为0的类条件概率数组;pAbusive是标记为1类的先验概率
"""
def trainNB(trainMatrix, trainCategory):
#计算训练的文档数目
numTrainDocs = len(trainMatrix)
#计算每篇文档的词条数
numWords = len(trainMatrix[0])
#标记为1类的先验概率
pAbusive = sum(trainCategory)/float(numTrainDocs)
"""
创建numpy数组初始化为1,拉普拉斯平滑。
创建numpy.zeros数组,词条出现数初始化为0。分母初始化为2
"""
p0Num = np.ones(numWords); p1Num = np.ones(numWords)
p0Denom = 2.0; p1Denom = 2.0
#计算类条件概率
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += 1
# p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += 1
#p0Denom += sum(trainMatrix[i])
#由于大部分因子都非常小,防止数值下溢得不到正确答案。于是加log计算,可以使得答案不会过小。
p1Vect = np.log(p1Num/p1Denom) #change to np.log()
p0Vect = np.log(p0Num/p0Denom) #change to np.log()
return p0Vect, p1Vect, pAbusive
定义朴素贝叶斯分类器预测函数
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) #element-wise mult
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
"""
#函数说明:朴素贝叶斯分类器分类函数
#vec2Classify--待分类的词条数组; p1Vec--标记为类1的类条件概率数组; p0Vec--标记为类0的类条件概率数组; pClass1--标记为1类的先验概率
"""
博客
https://blog.youkuaiyun.com/qq_27009517/article/details/80044431
https://blog.youkuaiyun.com/lming_08/article/details/37542331
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
1.计算待分类词条数组为1类的概率
"""
#寻找vec2Classify测试数组中,元素为0时对应的索引值
index = np.where(vec2Classify==0)
#遍历元素为0时的索引值,并从p1Vec--1类的条件概率数组中取出对应索引的数值,并存储成列表的形式(p1Vec0=[])
p1Vec0=[]
for i in index:
for m in i:
p1Vec0.append(p1Vec[m])
#所有1-P(vec2Classify=0|1)组成的列表
x0=np.ones(len(p1Vec0))-p1Vec0
#寻找vec2Classify测试数组中,元素为1时对应的索引值
index1= np.where(vec2Classify==1)
#遍历元素为1时的索引值,并从p1Vec--1类的条件概率数组中取出对应索引的数值,并存储成列表的形式(p1Vec1=[])
p1Vec1=[]
for i in index1:
for m in i:
p1Vec1.append(p1Vec[m])
#所有P(vec2Classify=0|1)组成的列表
x1=p1Vec1
##对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
p1 = sum(x0)+sum(x1) + np.log(pClass1)
"""
2.计算待分类词条数组为0类的概率
"""
#寻找vec2Classify测试数组中,元素为0时对应的索引值
index2 = np.where(vec2Classify==0)
#遍历元素为0时的索引值,并从p0Vec--0类的条件概率数组中取出对应索引的数值,并存储成列表的形式(p0Vec0=[])
p0Vec0=[]
for i in index2:
for m in i:
p0Vec0.append(p0Vec[m])
#所有1-P(vec2Classify=0|0)组成的列表
w0=np.ones(len(p0Vec0))-p0Vec0
#寻找vec2Classify测试数组中,元素为1时对应的索引值
index3= np.where(vec2Classify==1)
#遍历元素为1时的索引值,并从p0Vec--0类的条件概率数组中取出对应索引的数值,并存储成列表的形式(p0Vec1=[])
p0Vec1=[]
for i in index3:
for m in i:
p0Vec1.append(p0Vec[m])
#所有1-P(vec2Classify=0|0)组成的列表
w1=p0Vec1
##对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)
p0 = sum(w0)+sum(w1) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
使用朴素贝叶斯过滤垃圾邮件
"""
书本中4.6.1节 准备数据,切分文本部分写的很清晰。
"""
#将一个大字符串解析为字符列表。input is big string, #output is word list
def textParse(bigString):
import re
listOfTokens = re.split(r'\W+', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
docList = []; classList = []; fullText = []
#遍历25个txt文件
for i in range(1, 26):
#读取每个垃圾邮件,大字符串转换成字符列表。
wordList = textParse(open('email/spam/%d.txt' % i, encoding="ISO-8859-1").read())
docList.append(wordList)
fullText.extend(wordList)
#标记垃圾邮件,1表示垃圾邮件
classList.append(1)
#读取每个非垃圾邮件,字符串转换为字符列表
wordList = textParse(open('email/ham/%d.txt' % i, encoding="ISO-8859-1").read())
docList.append(wordList)
fullText.extend(wordList)
#标记每个非垃圾邮件,0表示非垃圾邮件
classList.append(0)
#创建词汇表,不重复
vocabList = createVocabList(docList)
#创建存储训练集的索引值的列表
trainingSet =list(range(50));
#创建存储测试集的索引值的列表
testSet= []
#从50个邮件中,随机挑选出40个作为训练集,10个作为测试集
for i in range(10):
#随机选取索引值
randIndex = int(np.random.uniform(0, len(trainingSet)))
#添加测试集的索引值
testSet.append(trainingSet[randIndex])
#在训练集的列表中删除添加到测试集的索引值
del(list(trainingSet)[randIndex])
#创建训练集矩阵和训练集类别标签向量
trainMat = [];
trainClasses = []
#遍历训练集,目前只有40个训练集
for docIndex in trainingSet:
#将生成的词集模型添加到训练矩阵中
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
#将类别标签添加到训练集的类别标签向量中
trainClasses.append(classList[docIndex])
"""
训练朴素贝叶斯模型
"""
#训练朴素贝叶斯模型
p0V, p1V, pSpam = trainNB(np.array(trainMat), np.array(trainClasses))
#错误分类计数
errorCount = 0
#遍历测试集
for docIndex in testSet:
#测试集的词集模型
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("classification error", docList[docIndex])
print('the error rate is: ', float(errorCount)/len(testSet))
spamTest()
classification error ['that', 'cold', 'there', 'going', 'retirement', 'party', 'are', 'the', 'leaves', 'changing', 'color']
classification error ['arvind', 'thirumalai', 'commented', 'your', 'status', 'arvind', 'wrote', 'you', 'know', 'reply', 'this', 'email', 'comment', 'this', 'status']
classification error ['thanks', 'peter', 'definitely', 'check', 'this', 'how', 'your', 'book', 'going', 'heard', 'chapter', 'came', 'and', 'was', 'good', 'shape', 'hope', 'you', 'are', 'doing', 'well', 'cheers', 'troy']
classification error ['jay', 'stepp', 'commented', 'your', 'status', 'jay', 'wrote', 'the', 'reply', 'this', 'email', 'comment', 'this', 'status', 'see', 'the', 'comment', 'thread', 'follow', 'the', 'link', 'below']
classification error ['benoit', 'mandelbrot', '1924', '2010', 'benoit', 'mandelbrot', '1924', '2010', 'wilmott', 'team', 'benoit', 'mandelbrot', 'the', 'mathematician', 'the', 'father', 'fractal', 'mathematics', 'and', 'advocate', 'more', 'sophisticated', 'modelling', 'quantitative', 'finance', 'died', '14th', 'october', '2010', 'aged', 'wilmott', 'magazine', 'has', 'often', 'featured', 'mandelbrot', 'his', 'ideas', 'and', 'the', 'work', 'others', 'inspired', 'his', 'fundamental', 'insights', 'you', 'must', 'logged', 'view', 'these', 'articles', 'from', 'past', 'issues', 'wilmott', 'magazine']
classification error ['will', 'there', 'the', 'latest']
classification error ['linkedin', 'kerry', 'haloney', 'requested', 'add', 'you', 'connection', 'linkedin', 'peter', 'like', 'add', 'you', 'professional', 'network', 'linkedin', 'kerry', 'haloney']
the error rate is: 0.7
参考文档