上篇文章提到过,算法存在一定的问题,需要进行改进。那么需要改进的地方在哪里呢?利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中有一个概率值为0,那么最后的成绩也为0。
可以看到很多0
如果新实例文本,包含这种概率为0的分词,那么最终的文本属于某个类别的概率也就是0了。
为了降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing)又被称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。
另外一个遇到的问题就是下溢出,这是由于太多很小的数相乘造成的。学过数学的人都知道,两个小数相乘,越乘越小,这样就造成了下溢出。为了解决这个问题,对乘积结果取自然对数。通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。
'''
函数说明:朴素贝叶斯分类器训练函数
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec [0,1,0,1,0,1]
'''
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 计算训练文档数目
numWords = len(trainMatrix[0]) # 计算每篇文档词条数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率
# p0Num = np.zeros(numWords)
# p1Num = np.zeros(numWords) # 创建numpy.zeros数组,词条出现数初始化为0
#
# p0Denom = 0.0
# p1Denom = 0.0 # 分母初始化为0
p0Num = np.ones(numWords);
p1Num = np.ones(numWords) # 创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
p0Denom = 2.0;
p1Denom = 2.0 # 分母初始化为2,拉普拉斯平滑
# 其实这里就是一个分类 把trainMatrix的每一行,分为两种,并且将两种分别累加
for i in range(numTrainDocs):
if(trainCategory[i] == 1): # 这个if就相当于在已知
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
print('p1Num:\n', p1Num)
print('p1Denom:\n', p1Denom)
print('p0Num:\n', p0Num)
print('p0Denom:\n', p0Denom)
# p1Vect = p1Num / p1Denom
# p0Vect = p0Num / p0Denom
p1Vect = np.log(p1Num / p1Denom) # 取对数,防止下溢出
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
def classifyNB(wordVec, p0Vect, p1Vect, pAbusive):
p0 = np.log(1.0 - pAbusive) + sum(wordVec * p0Vect)
p1 = np.log(pAbusive) + sum(wordVec * p1Vect)
if p0 > p1:
return 0
else:
return 1
因为取自然对数了。logab = loga + logb。
这样,我们的朴素贝叶斯分类器就改进完毕了。
有两个文件夹ham和spam,spam文件下的txt文件为垃圾邮件。
'''
@Project :MachineLearning
@File :emailBayes.py
@Author :Kyrie Irving
@Date :2022/10/27 10:41
'''
import re
import numpy as np
def textParse(bigString): #将字符串转换为字符列表
listOfTokens = re.split(r'\W', bigString) #将特殊符号作为切分标志进行字符串切分,即非字母、非数字
# listOfTokens = re.findall(r"[\w']+", bigString)
return [li.lower() for li in listOfTokens if len(li) > 2] #除了单个字母,例如大写的I,其它单词变成小写
'''
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
'''
def createVocabList(dataSet):
vocabSet = set([])
for li in dataSet:
vocabSet = vocabSet | set(li) # 每一行去重复,再取交集,确保没重复行
return list(vocabSet)
'''
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
vocabList - createVocabList返回的列表
inputSet - 切分的词条列表,即原始数据的每一行
这里遍历每一行传入的每一个单词,如果单词在词汇表中出现了,在词汇表的相应下标位置置为1
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1 # 如果词条存在于词汇表中,则置1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
'''
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
vocabList - createVocabList返回的列表
inputSet - 切分的词条列表,即原始数据的每一行
这里遍历每一行传入的每一个单词,如果单词在词汇表中出现了,在词汇表的相应下标位置置为1
'''
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1 # 如果词条存在于词汇表中,则置1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
'''
函数说明:朴素贝叶斯分类器训练函数
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec [0,1,0,1,0,1]
'''
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 计算训练文档数目
numWords = len(trainMatrix[0]) # 计算每篇文档词条数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 文档属于侮辱类的概率
p0Num = np.ones(numWords)
p1Num = np.ones(numWords) # 创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑
p0Denom = 2.0
p1Denom = 2.0 # 分母初始化为2,拉普拉斯平滑
# 其实这里就是一个分类 把trainMatrix的每一行,分为两种,并且将两种分别累加
for i in range(numTrainDocs):
if(trainCategory[i] == 1): # 这个if就相当于在已知
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num / p1Denom) # 取对数,防止下溢出
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
'''
wordVec必须是np.array()
因为后边需要其矩阵相乘
'''
def classifyNB(wordVec, p0Vect, p1Vect, pAbusive):
p0 = np.log(1.0 - pAbusive) + sum(wordVec * p0Vect)
p1 = np.log(pAbusive) + sum(wordVec * p1Vect)
if p0 > p1:
return 0
else:
return 1
def spamTest():
docList = []
classList = []
for i in range(1, 26):
fileName = 'spam/' + str(i) + '.txt'
bigString = open(fileName, 'r').read()
# wordList = textParse(open(fileName, 'r').read())
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()) # 读取每个垃圾邮件,并字符串转换成字符串列表
wordList = textParse(open('ham/%d.txt' % i, 'r').read()) # 读取每个垃圾邮件,并字符串转换成字符串列表
docList.append(wordList)
classList.append(0) # 标记垃圾邮件,1表示垃圾文件
vocabList = createVocabList(docList) # 创建词汇表,不重复
print('不重复的词汇表:', vocabList)
trainingSet = list(range(50)) #创建存储训练集的索引值的列表和测试集的索引值的列表
testSet = []
for i in range(10): #从50个邮件中,随机挑选出40个作为训练集,10个做测试集
rangIndex = int(np.random.uniform(0, len(trainingSet))) #随机选取索索引值
testSet.append(trainingSet[rangIndex]) #添加测试集的索引值
del (trainingSet[rangIndex]) #在训练集列表中删除添加到测试集的索引值
trainMat = []
trainClasses = [] #创建训练集矩阵和训练集类别标签系向量
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1V, pSpam = trainNB0(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("分类错误的测试集:", docList[docIndex])
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
if __name__ == '__main__':
spamTest()
既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。
后续链接: https://jackcui.blog.csdn.net/article/details/77500679