机器学习:NavieBayes练习
实验介绍
1.实验内容
本实验包括:
- 基于朴素贝叶斯算法的言论过滤器
- 基于朴素贝叶斯算法实现垃圾邮件过滤
2.实验目标
通过本实验掌握朴素贝叶斯算法原理,熟悉朴素贝叶斯算法的简单应用。
3.实验知识点
- 朴素贝叶斯算法
4.实验环境
- python 3.6.5
- numpy 1.13.3
- matplotlib 2.2.3
实验准备
点击屏幕右上方的下载实验数据模块,选择下载bayes_email.tgz到指定目录下,然后再依次选择点击上方的File->Open->Upload,上传刚才下载的数据集压缩包,再使用如下命令解压:
!tar -zxvf bayes_email.tgz
bayes_email/
bayes_email/spam/
bayes_email/spam/1.txt
bayes_email/spam/10.txt
bayes_email/spam/11.txt
bayes_email/spam/12.txt
bayes_email/spam/13.txt
bayes_email/spam/14.txt
bayes_email/spam/15.txt
bayes_email/spam/16.txt
bayes_email/spam/17.txt
bayes_email/spam/18.txt
bayes_email/spam/19.txt
bayes_email/spam/2.txt
bayes_email/spam/20.txt
bayes_email/spam/21.txt
bayes_email/spam/22.txt
bayes_email/spam/23.txt
bayes_email/spam/24.txt
bayes_email/spam/25.txt
bayes_email/spam/3.txt
bayes_email/spam/4.txt
bayes_email/spam/5.txt
bayes_email/spam/6.txt
bayes_email/spam/7.txt
bayes_email/spam/8.txt
bayes_email/spam/9.txt
bayes_email/ham/
bayes_email/ham/1.txt
bayes_email/ham/10.txt
bayes_email/ham/11.txt
bayes_email/ham/12.txt
bayes_email/ham/13.txt
bayes_email/ham/14.txt
bayes_email/ham/15.txt
bayes_email/ham/16.txt
bayes_email/ham/17.txt
bayes_email/ham/18.txt
bayes_email/ham/19.txt
bayes_email/ham/2.txt
bayes_email/ham/20.txt
bayes_email/ham/21.txt
bayes_email/ham/22.txt
bayes_email/ham/23.txt
bayes_email/ham/24.txt
bayes_email/ham/25.txt
bayes_email/ham/3.txt
bayes_email/ham/4.txt
bayes_email/ham/5.txt
bayes_email/ham/6.txt
bayes_email/ham/7.txt
bayes_email/ham/8.txt
bayes_email/ham/9.txt
# -*- coding: UTF-8 -*-
import re
import os
import numpy as np
import random
import matplotlib.pyplot as plt
【言论过滤器】
实验步骤:【言论过滤器】- 概述
以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。
实验步骤:【言论过滤器】- 词条切分和词性标注
我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。编写代码如下:
def loadDataSet():
"""
函数说明:创建实验样本
Parameters:
无
Returns:
postingList - 实验样本切分的词条
classVec - 类别标签向量
"""
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['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 setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #创建一个其中所含元素都为0的向量
for word in inputSet: #遍历每个词条
if word in vocabList: #如果词条存在于词汇表中,则置1
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文档向量
def createVocabList(dataSet):
vocabSet = set([]) #创建一个空的不重复列表
for document in dataSet:
vocabSet = vocabSet | set(document) #取并集
return list(vocabSet)
实验步骤:【言论过滤器】-训练朴素贝叶斯分类器
通过词条向量训练朴素贝叶斯分类器。
Tips:
-
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率。如果其中有一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1, 并将分母初始化为2。这种做法就叫做拉普拉斯平滑(Laplace Smoothing),是比较常用的平滑方法。
-
下溢问题。由于太多很小的数相乘会导致下溢问题。两个小数相乘,越乘越小,这样就造成了下溢出。在程序中,许多很小的数相乘,最后四舍五入计算结果可能就变成0。为了解决这个问题,对乘积结果取自然对数。通过求对数可以避免下溢出或者浮点数舍入导致的错误。
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数组,
p0Denom = 0.0; p1Denom = 0.0 #分母初始化为0.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num/p1Denom #相除
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
实验步骤:【言论过滤器】- 使用分类器进行分类
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
朴素贝叶斯分类器分类函数
Parameters:
vec2Classify - 待分类的词条数组
p0Vec - 非侮辱类的条件概率数组
p1Vec - 侮辱类的条件概率数组
pClass1 - 文档属于侮辱类的概率
Returns:
0 - 属于非侮辱类
1 - 属于侮辱类
"""
# 计算文档属于侮辱类的概率
p0 = sum(vec2Classify * p0Vec) + np.log(1 - pClass1) # P(D|0) * P(0)
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # P(D|1) * P(1)
# 比较两个类别的概率
if p1 > p0:
return 1 # 属于侮辱类
else:
return 0 # 属于非侮辱类
def testingNB():
"""
测试朴素贝叶斯分类器
测试样本1:['love', 'my', 'dalmation']
测试样本2:['stupid', 'garbage']
"""
# 加载数据集
postingList, classVec = loadDataSet()
# 创建词汇表
vocabList = createVocabList(postingList)
# 将每篇文档转换为词汇表的向量
trainMat = []
for post in postingList:
trainMat.append(setOfWords2Vec(vocabList, post))
# 训练朴素贝叶斯分类器
p0Vec, p1Vec, pAbusive = trainNB0(np.array(trainMat), np.array(classVec))
# 测试样本1:['love', 'my', 'dalmation']
testEntry1 = ['love', 'my', 'dalmation']
testVec1 = setOfWords2Vec(vocabList, testEntry1)
print("Test sample 1 classified as:", classifyNB(np.array(testVec1), p0Vec, p1Vec, pAbusive))
# 测试样本2:['stupid', 'garbage']
testEntry2 = ['stupid', 'garbage']
testVec2 = setOfWords2Vec(vocabList, testEntry2)
print("Test sample 2 classified as:", classifyNB(np.array(testVec2), p0Vec, p1Vec, pAbusive))
if __name__ == '__main__':
testingNB()
Test sample 1 classified as: 0
Test sample 2 classified as: 1
【垃圾邮件过滤】
实验步骤:【垃圾邮件过滤】- 概述
使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。本实验中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下使用朴素贝叶斯对电子邮件进行分类的步骤:
- 收集数据:提供文本文件。
- 准备数据:将文本文件解析成词条向量。
- 分析数据:检查词条确保解析的正确性。
- 训练算法:使用我们之前建立的trainNB()函数。
- 测试算法:使用classifyNB()函数,并构建一个新的测试函数来计算文档集的错误率。
- 使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
数据集路径为:bayes_email,该目录下有两个文件夹:ham和spam,其中spam文件下的txt文件为垃圾邮件。
数据示例

实验步骤:【垃圾邮件过滤】- 文本切分
对于英文文本,我们可以以非字母、非数字作为符号进行切分,使用split函数即可。编写代码如下:
# 邮件裁词
def textParse(bigString):
regEx = re.compile(r'\W')
listOfTokens = regEx.split(bigString)
var = [tok.lower() for tok in listOfTokens if len(tok) > 2] # 只返回长度大于2的情况
return var
# 创建词汇表
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个无序且不重复的集合对象
for document in dataSet:
vocabSet = vocabSet | set(document) # 创建两个集合的并集
return list(vocabSet)
trainingSet = list(range(50)) # 训练集的索引,0-49(因为ham和spam各有25个,故一共有50个)
trainingSet = trainingSet
testSet = []
# 随机构造训练集
for i in range(10): # 表示只创建10个测试集、剩下的都是训练集
# 函数生成一个0到len(trainingSet)之间的随机浮点数,然后取整,得到一个随机的索引值randIndex。
randIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randIndex]) # 将随机选择的索引值randIndex添加到测试集列表testSet中。
trainingSet.pop(randIndex) # 删除训练集中对应的测试集的索引值,以确保不会重复选择作为测试集。
# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个长度为vocabList的列表returnVec,并将列表中的每个元素都初始化为0
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1 # 在词库里出现次数
return returnVec
# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs) # 侮辱性单词出现的概率
p0Num = ones(numWords) # 为了防止出现 0 * x 的情况
p1Num = ones(numWords)
p0Denom = 2.0 # 为了防止分母为0的情况
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 计算每个词出现的次数
p1Denom += sum(trainMatrix[i]) # 计算总共出现多少个词
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom) # 计算每个词语是侮辱性词汇的概率
p0Vect = log(p0Num / p0Denom) # log是为了防止数据太小导致下溢出
return p0Vect, p1Vect, pAbusive
# 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
实验步骤:【垃圾邮件过滤】- 分类
根据词汇表,将每个文本向量化。将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性。编写代码如下:
import re
def textParse(bigString):
"""
函数说明:接收一个字符串并将其解析为字符串列表
Parameters:
bigString - 字符串
Returns:
字符串列表(除了单个字母,例如大写的I,其它单词变成小写,同时过滤长度小于3的字符串)
"""
# 使用正则表达式只保留字母和数字
listOfTokens = re.split(r'\W+', bigString) # \W+ 匹配非字母和非数字的字符
# 将所有单词转化为小写,并过滤掉长度小于3的单词
return [token.lower() for token in listOfTokens if len(token) > 2]
import numpy as np # Import numpy
def trainNB(trainMatrix, trainCategory):
"""
函数说明: 朴素贝叶斯分类器训练函数
Parameters:
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
p0Vect - 非侮辱类的条件概率数组
p1Vect - 侮辱类的条件概率数组
pAbusive - 文档属于侮辱类的概率
"""
# 文档总数
numDocs = len(trainMatrix)
# 侮辱类的文档数量
numAbusive = sum(trainCategory)
# 计算文档属于侮辱类和非侮辱类的概率
pAbusive = numAbusive / float(numDocs)
# 词汇表大小(trainMatrix 是一个二维数组,每一行表示一个文档,每一列表示一个词汇表中的单词。)
vocabSize = len(trainMatrix[0])
# 初始化条件概率的分子和分母,就是上面提到的卡普拉斯平滑
p0Num = np.ones(vocabSize) # 非侮辱类词汇计数,初始化为1
p1Num = np.ones(vocabSize) # 侮辱类词汇计数,初始化为1
p0Denom = 2.0 # 非侮辱类的总词数,初始化为词汇表大小
p1Denom = 2.0 # 侮辱类的总词数
# 遍历所有的训练数据
for i in range(numDocs):
if trainCategory[i] == 1:
# 侮辱类
p1Num += trainMatrix[i] # 对应单词出现的次数加到侮辱类计数
p1Denom += sum(trainMatrix[i]) # 侮辱类总词数
else:
# 非侮辱类
p0Num += trainMatrix[i] # 对应单词出现的次数加到非侮辱类计数
p0Denom += sum(trainMatrix[i]) # 非侮辱类总词数
# 计算每个词汇的条件概率
p0Vect = np.log(p0Num / p0Denom) # 对数似然
p1Vect = np.log(p1Num / p1Denom) # 对数似然
return p0Vect, p1Vect, pAbusive
import os
from random import shuffle
def loadDataSet():
"""
函数说明: 创建实验样本
返回:postingList(切分的词条)、classVec(类别标签)
"""
postingList = []
classVec = []
# 加载 spam 文件夹的邮件内容
spamFiles = os.listdir('bayes_email/spam')
for fileName in spamFiles:
with open(f'bayes_email/spam/{fileName}', 'r', encoding='ISO-8859-1') as f:
text = f.read()
words = textParse(text)
postingList.append(words)
classVec.append(1) # spam 的标签是 1
# 加载 ham 文件夹的邮件内容
hamFiles = os.listdir('bayes_email/ham')
for fileName in hamFiles:
with open(f'bayes_email/ham/{fileName}', 'r', encoding='ISO-8859-1') as f:
text = f.read()
words = textParse(text)
postingList.append(words)
classVec.append(0) # ham 的标签是 0
return postingList, classVec
def spamTest():
"""
函数说明: 将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性
"""
# 加载数据集
postingList, classVec = loadDataSet()
# 创建词汇表
vocabList = createVocabList(postingList)
# 划分数据集,使用交叉验证
data = list(zip(postingList, classVec))
shuffle(data) # 随机打乱数据集
fold_size = len(data) // 5 # 5折交叉验证
accuracies = []
for i in range(5):
# 分割训练集和测试集
test_set = data[i*fold_size : (i+1)*fold_size]
train_set = data[:i*fold_size] + data[(i+1)*fold_size:]
# 提取训练集和测试集的内容和标签
train_data, train_labels = zip(*train_set)
test_data, test_labels = zip(*test_set)
# 将训练集和测试集转化为词条向量
train_matrix = [setOfWords2Vec(vocabList, doc) for doc in train_data]
test_matrix = [setOfWords2Vec(vocabList, doc) for doc in test_data]
# 训练朴素贝叶斯分类器
p0Vec, p1Vec, pSpam = trainNB(train_matrix, list(train_labels))
# 测试分类器的准确性
correct_count = 0
for i in range(len(test_matrix)):
predicted_label = classifyNB(test_matrix[i], p0Vec, p1Vec, pSpam)
if predicted_label == test_labels[i]:
correct_count += 1
# 计算当前折的准确率
accuracy = correct_count / len(test_matrix)
accuracies.append(accuracy)
# 输出交叉验证的平均准确率
avg_accuracy = sum(accuracies) / 5
print(f'5-fold cross-validation average accuracy: {avg_accuracy:.2f}')
if __name__ == '__main__':
spamTest()
5-fold cross-validation average accuracy: 0.92

浙公网安备 33010602011771号