一、算法概述
算法原理:
1.贝叶斯理论:
朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。
假设现在我们有一个数据集,它由两类数据组成,数据分布如图所示。
我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
如果p1(x,y) > p2(x,y),那么类别为1
如果p1(x,y) < p2(x,y),那么类别为2
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。已经了解了贝叶斯决策理论的核心思想,那么接下来,就是学习如何计算p1和p2概率。
2.条件概率:
p ( A ) : A 发 生 的 概 率 p(A):A发生的概率 p(A):A发生的概率
p ( B ) : B 发 生 的 概 率 p(B):B发生的概率 p(B):B发生的概率
p ( A ∣ B ) : B 条 件 下 , A 发 生 的 概 率 p(A|B):B条件下,A发生的概率 p(A∣B):B条件下,A发生的概率
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B)={\frac{P(A\cap{B})}{P(B)}} P(A∣B)=P(B)P(A∩B)
因此:
P ( A ∩ B ) = P ( A ∣ B ) P ( B ) P(A\cap{B})=P(A|B)P(B) P(A∩B)=P(A∣B)P(B)
同理可得:
P ( A ∩ B ) = P ( B ∣ A ) P ( A ) P(A\cap{B})=P(B|A)P(A) P(A∩B)=P(B∣A)P(A)
所以:
P ( A ∣ B ) P ( B ) = P ( B ∣ A ) P ( A ) P(A|B)P(B)=P(B|A)P(A) P(A∣B)P(B)=P(B∣A)P(A)
即:
P
(
A
∣
B
)
=
P
(
B
∣
A
)
P
(
A
)
P
(
B
)
P(A|B)={\frac{P(B|A)P(A)}{P(B)}}
P(A∣B)=P(B)P(B∣A)P(A)
或:
P
(
A
i
∣
B
)
=
P
(
B
∣
A
i
)
P
(
A
i
)
P
(
B
)
P(A_i|B)={\frac{P(B|A_i)P(A_i)}{P(B)}}
P(Ai∣B)=P(B)P(B∣Ai)P(Ai)
这就是条件概率的计算公式。
3.全概率公式:
P ( B ) = ∑ i = 1 N P ( B ∣ A i ) P ( A i ) P(B)=\sum_{i=1}^NP(B|A_i)P(A_i) P(B)=i=1∑NP(B∣Ai)P(Ai)
故,贝叶斯公式也可写成如下形式:
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) ∑ i = 1 N P ( B ∣ A i ) P ( A i ) P(A|B)={\frac{P(B|A)P(A)}{\sum_{i=1}^NP(B|A_i)P(A_i)}} P(A∣B)=∑i=1NP(B∣Ai)P(Ai)P(B∣A)P(A)
4.贝叶斯推断:
对条件概率公式进行变形,可以得到如下形式:
P ( A ∣ B ) = P ( A ) P ( B ∣ A ) P ( B ) P(A|B)=P(A){\frac{P(B|A)}{P(B)}} P(A∣B)=P(A)P(B)P(B∣A)
我们把P(A)称为"先验概率"(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为"后验概率"(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
所以,条件概率可以理解成下面的式子:
- 后验概率 = 先验概率 x 调整因子
这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增
强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。
二、朴素贝叶斯算法简介
我们称之为“朴素”,是因为整个形式化过程只做最原始、最简单的假设。
优缺点:
- 优点:在数据较少的情况下仍然有效,可以处理多类别问题。
- 缺点:对于输入数据的准备方式较为敏感;由于朴素贝叶斯的“朴素”特点,所以会带来一些准确率上的损失。
适用数据类型:
- 标称型数据
朴素贝叶斯的一般过程:
- 收集数据:可以使用任何方法。本章使用RSS源。
- 准备数据:需要数值型或者布尔型数据。
- 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
- 训练算法:计算不同的独立特征的条件概率。
- 测试算法:计算错误率。
- 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
Step1:收集数据
从文本中构建词向量
需要先拆分文本,然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0
表示词条未出现。
建立两个类别:侮辱类和非侮辱类,使用1和0分别表示
def loadDataSet():
"""
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']]
# #类别标签向量,代表侮辱性文字,0代表正常言论
classVec = [0, 1, 0, 1, 0, 1]
return postingList,classVec
输出结果:
Step2:准备数据
def createVocabList(dataSet):
"""
将切分的实验样本词条整理成不重复的词条列表
- - - -
dataSet - 样本数据集
"""
# 创建一个空集
vocabSet=set([])
for document in dataSet:
# 取并集
vocabSet=vocabSet | set(document)
return list(vocabSet)
def setOfWords2Vec(vocabList,inputSet):
"""
根据词汇表,将inputSet向量化(变为0和1组成的向量)
- - - -
vocabList - 词汇表
inputSet - 切分的词条列表
"""
returnVec=[0]*len(vocabList)
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
Step3:分析数据
if __name__=="__main__":
postingList, classVec=loadDataSet()
myVocabList=createVocabList(postingList)
print("myVocabList:\n",myVocabList)
print(setOfWords2Vec(myVocabList,postingList[0]))
print(setOfWords2Vec(myVocabList,postingList[1]))
print(setOfWords2Vec(myVocabList,postingList[2]))
print(setOfWords2Vec(myVocabList,postingList[3]))
输出结果:
Step4:训练算法
从词向量计算概率
- 利用上述贝叶斯公式:
P ( A i ∣ B ) = P ( B ∣ A i ) P ( A i ) P ( B ) P(A_i|B)={\frac{P(B|A_i)P(A_i)}{P(B)}} P(Ai∣B)=P(B)P(B∣Ai)P(Ai)
若朴素的假设所有词都相互独立:
P ( B 0 , B 1 , B 1 , B 2 , . . . , B N ∣ A i ) = P ( B 0 ∣ A i ) P ( B 1 ∣ A i ) P ( B 2 ∣ A i ) . . . P ( B n ∣ A i ) P(B_0,B_1,B_1,B_2,...,B_N | A_i)=P(B_0|A_i)P(B_1|A_i)P(B_2|A_i)...P(B_n|A_i) P(B0,B1,B1,B2,...,BN∣Ai)=P(B0∣Ai)P(B1∣Ai)P(B2∣Ai)...P(Bn∣Ai)
首先,计算文档属于侮辱性文档(class=1)的概率,即P(1)。
因为这是一个二类分类问题,所以可以通过1-P(1)得到P(0)。
然后,在for循环中,要遍历训练集trainMatrix中的所有文档。一旦某个词语(侮辱性或正常词语)在某一文档中出现,则该词对应的个数(p1Num或者p0Num)就加1,而且在所有的文档中,该文档的总词数也相应加1。
最后,对每个元素除以该类别中的总词数。
def trainNB0(trainMatrix,trainCategory):
# 计算训练的文档数目
numTrainDocs = len(trainMatrix)
# 计算每篇文档的词条数
numWords = len(trainMatrix[0])
# 文档属于侮辱类的概率
pAbusive = sum(trainCategory)/float(numTrainDocs)
# 创建numpy.zeros数组,词条出现数初始化为0
p0Num = np.zeros(numWords);
p1Num = np.zeros(numWords)
# 分母初始化为0
p0Denom = 0.0;
p1Denom = 0.0
for i in range(numTrainDocs):
# 统计属于侮辱类的条件概率所需的数据,即P(B0|1),P(B1|1),P(B2|1)···
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
# 统计属于非侮辱类的条件概率所需的数据,即P(B0|0),P(B1|0),P(B2|0)···
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num/p1Denom
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive
if __name__=="__main__":
postingList, classVec=loadDataSet()
myVocabList=createVocabList(postingList)
trainMat=[]
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB0(trainMat,classVec)
print('p0V:\n',p0V)
print('p1V:\n',p1V)
print('classVec:\n',classVec)
print('pAb:\n',pAb)
输出结果:
Step5:测试算法
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,如果其中一个概率值为0,那么最后的乘积也为0。
为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2
将trainNB0()对应部分修改为:
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
# 分母初始化为0
p0Denom = 2.0
p1Denom = 2.0
另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。
一种解决办法是对乘积取自然对数。在代数中有ln(a*b) = ln(a)+ln(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。
将trainNB0()对应部分修改为:
p1Vect = np.log(p1Num/p1Denom)
p0Vect = np.log(p0Num/p0Denom)
def testingNB():
# 创建实验样本
listOPosts,listClass=loadDataSet()
# 创建词汇表
myVocabList=createVocabList(listOPosts)
trainMat=[]
# 将实验样本向量化
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
# 训练朴素贝叶斯分类器
p0V,p1V,pAb=trainNB0(np.array(trainMat),np.array(listClass))
# 测试样本1
testEntry=['love','my','dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb))
# 测试样本2
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0V, p1V, pAb))
输出结果:
文档词袋模型:
目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-wordsmodel)。
如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。
下面给出基于词袋模型的朴素贝叶斯代码。它与函数setOfWords2Vec()几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。
#朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList,inputSet):
"""
根据词汇表,将inputSet向量化(词袋化)
- - - -
vocabList - 词汇表
inputSet - 切分的词条列表
"""
returnVec = [0] * len(vocabList)
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
三、项目案例
项目概述
在上篇文章那个简单的例子中,我们引入了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。
开发流程
使用朴素贝叶斯对电子邮件进行分类的步骤:
- 收集数据:提供文本文件。
- 准备数据:将文本文件解析成词条向量。
- 分析数据:检查词条确保解析的正确性。
- 训练算法:使用我们之前建立的trainNB0()函数。
- 测试算法:使用classifyNB(),并构建一个新的测试函数来计算文档集的错误率。
- 使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
1.准备数据:切分文本
import re
def textParse(bigString):
"""
将字符串转换为字符列表
- - - -
bigString - 一个含大写字母的字符串
"""
# 将特殊符(非字母数字)作为切分标志进行字符串切分
listOfTokens=re.split(r'\w*',bigString)
# 除了单个字母,例如大写的I,其它单词变成小写
return [tok.lower() for tok in listOfTokens if len(tok)>2]
2.测试算法:使用朴素贝叶斯进行交叉验证
本例中共有50封电子邮件,其中的10封电子邮件被随机选择为测试集。分类器所需要的概率计算只利用训练集中的文档来完成。
import random
def spamTest():
"""
垃圾邮件分类测试函数
"""
docList=[];classList=[];fullText=[]
#遍历25个txt文件
for i in range(1,26):
# 读取每个垃圾邮件,并字符串转换成字符串列表
worfList=textParse(open('C:/Users/answer/Desktop/机器学习/数据集/4.NaiveBayes/email/ham/%d.txt' % i, encoding='ISO-8859-1').read())
docList.append(worfList)
fullText.append(worfList)
# 标记垃圾邮件,1表示垃圾文件
classList.append(1)
worfList = textParse(open('C:/Users/answer/Desktop/机器学习/数据集/4.NaiveBayes/email/spam/%d.txt' % i,encoding='ISO-8859-1').read())
docList.append(worfList)
fullText.append(worfList)
# 用0标记非垃圾邮件
classList.append(0)
# 词汇表
vocabList=createVocabList(docList)
# 存储训练集的索引值的列表和测试集的索引值的列表
trainingSet=list(range(50));testSet=[]
# 从50个邮件中,随机挑选出10个做测试集
for i in range(10):
# 随机选取索索引值
randIndex=int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
# 训练集矩阵和训练集类别标签系向量
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.0
for index in testSet:
wordVector=setOfWords2Vec(vocabList,docList[docIndex])
if classifyNB(np.array(wordVector),p0v,p1v,pSpam)!=classList[docIndex]:
errorCount+=1
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
输出结果:
四、朴素贝叶斯之新浪新闻分类(Sklearn)
1.中文语句切分
考虑一个问题,英文的语句可以通过非字母和非数字进行切分,但是汉语句子呢?就比如我打的这一堆字,该如何进行切分呢?我们自己写个规则?
幸运地是,这部分的工作不需要我们自己做了,可以直接使用第三方分词组件,即jieba,没错就是”结巴”。
jieba已经兼容Python2和Python3,使用如下指令直接安装即可:
pip install jieba
准备完数据集,切分中文语句
# author:answer time:2021/3/29
import numpy as mp
import os
import jieba
def TextProcessing(folder_path):
folder_list=os.listdir(folder_path) #查看folder_path下的文件
data_list=[] #训练集
class_list=[]
#遍历每个子文件夹
for folder in folder_list:
new_folder_path=os.path.join(folder_path,folder) #根据子文件夹生成新的路径
files=os.listdir(new_folder_path) #存放子文件夹下的txt文件列表
j=1
#遍历每个txt文件
for file in files:
if j>100: #每类最多100个样本
break
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f: #打开txt文件
raw=f.read()
word_out=jieba.cut(raw,cut_all=False) #精简模式,返回一个可迭代的generator
word_list=list(word_out) #generator转换为list
data_list.append(word_list)
class_list.append(folder)
j+=1
print(data_list)
print(class_list)
if __name__=='__main__':
#文本预处理
folder_path='C:\\Users\\answer\\Desktop\\机器学习\\machine-learning-master\\Naive Bayes\\SogouC\\Sample'
TextProcessing(folder_path)
执行结果:
2.文本特征选择
将所欲文本分成训练集和测试集,并按词频降序排序
# author:answer time:2021/3/29
import numpy as mp
import os
import jieba
import random
def TextProcessing(folder_path,test_size=0.2):
folder_list=os.listdir(folder_path) #查看folder_path下的文件
data_list=[] #训练集
class_list=[] #标签集
#遍历每个子文件夹
for folder in folder_list:
new_folder_path=os.path.join(folder_path,folder) #根据子文件夹生成新的路径
files=os.listdir(new_folder_path) #存放子文件夹下的txt文件列表
j=1
#遍历每个txt文件
for file in files:
if j>100: #每类最多100个样本
break
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f: #打开txt文件
raw=f.read()
word_out=jieba.cut(raw,cut_all=False) #精简模式,返回一个可迭代的generator
word_list=list(word_out) #generator转换为list
data_list.append(word_list)
class_list.append(folder)
j+=1
data_class_list=list(zip(data_list,class_list)) #zip压缩合并,将数据与标签对应压缩
random.shuffle(data_class_list) #将data_clsaa_list乱序
index=int(len(data_class_list)*test_size)+1 #训练集和测试集切分的索引值
train_list=data_class_list[index:] #训练集
test_list=data_class_list[:index] #测试集
train_data_list,train_class_list=zip(*train_list) #训练集压缩
test_data_list,test_class_list=zip(*test_list) #测试集压缩
all_words_dict={}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word]+=1
else:
all_words_dict[word]=1
#根据键的值倒序排序
all_words_tuple_list=sorted(all_words_dict.items(),key=lambda f:f[1],reverse=True)
all_words_list,all_words_nums=zip(*all_words_tuple_list) #解压缩
all_words_list=list(all_words_list) #转换成列表
return all_words_list,train_data_list,test_data_list,train_class_list,test_class_list
if __name__=='__main__':
#文本预处理
folder_path='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes./SogouC/Sample' #训练集存放地址
TextProcessing(folder_path)
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list=TextProcessing(folder_path,test_size=0.2)
print(all_words_list)
执行结果:
检测结果存在大量无关且高频字词,例如:“的”,“在”,“了”,“是”…然后对这些字词进行去重。
# author:answer time:2021/3/29
import numpy as mp
import os
import jieba
import random
def TextProcessing(folder_path,test_size=0.2):
folder_list=os.listdir(folder_path) #查看folder_path下的文件
data_list=[] #训练集
class_list=[] #标签集
#遍历每个子文件夹
for folder in folder_list:
new_folder_path=os.path.join(folder_path,folder) #根据子文件夹生成新的路径
files=os.listdir(new_folder_path) #存放子文件夹下的txt文件列表
j=1
#遍历每个txt文件
for file in files:
if j>100: #每类最多100个样本
break
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f: #打开txt文件
raw=f.read()
word_out=jieba.cut(raw,cut_all=False) #精简模式,返回一个可迭代的generator
word_list=list(word_out) #generator转换为list
data_list.append(word_list)
class_list.append(folder)
j+=1
data_class_list=list(zip(data_list,class_list)) #zip压缩合并,将数据与标签对应压缩
random.shuffle(data_class_list) #将data_clsaa_list乱序
index=int(len(data_class_list)*test_size)+1 #训练集和测试集切分的索引值
train_list=data_class_list[index:] #训练集
test_list=data_class_list[:index] #测试集
train_data_list,train_class_list=zip(*train_list) #训练集压缩
test_data_list,test_class_list=zip(*test_list) #测试集压缩
all_words_dict={}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word]+=1
else:
all_words_dict[word]=1
#根据键的值倒序排序
all_words_tuple_list=sorted(all_words_dict.items(),key=lambda f:f[1],reverse=True)
all_words_list,all_words_nums=zip(*all_words_tuple_list) #解压缩
all_words_list=list(all_words_list) #转换成列表
return all_words_list,train_data_list,test_data_list,train_class_list,test_class_list
def MakeWordSet(word_file):
"""
读取文件里的内容,并去重
:param word_file: 文件路径
:return: words_set 读取的内容的set集合
"""
words_set=set() #创建set集合
with open(word_file,'r',encoding='utf-8') as f: #打开文件
for line in f.readlines(): #一行一行读取
word=line.strip() #去回车
if len(word)>0: #有文本,添加到words_set中
words_set.add(word)
return words_set #返回处理结果
def words_dict(all_words_list,deleteN,stopwords_set=set()):
"""
文本特征提取
:param all_words_list: 训练集所有文本列表
:param deleteN: 删除词频最高的deleteN个词
:param stopwords_set: 指定结束语
:return feature_words:特征集
"""
feature_words=[] #特征列表
n=1
for t in range(deleteN,len(all_words_list),1):
if n>1000: #feature_word的维度为1000
break
#如果这个词不是数字,并且不是指定的结束语,并且单词长度大于1小于5,那么这个词可以作为特征词
if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1<len(all_words_list[t])<5:
feature_words.append(all_words_list[t])
n+=1
return feature_words
if __name__=='__main__':
#文本预处理
folder_path='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes./SogouC/Sample' #训练集存放地址
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list=TextProcessing(folder_path,test_size=0.2)
#生成stopwords_set
stopwords_file='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes/stopwords_cn.txt'
stopwords_set=MakeWordSet(stopwords_file)
feature_words=words_dict(all_words_list,100,stopwords_set)
print(feature_words)
执行结果:
从结果可以看出,已经过滤了大量高频且无关的字词,筛选出来的feature_words就是用于新闻分类的特征词,接下来将特征文本向量化,然后用于训练朴素贝叶斯分类器。
3使用Sklearn构建朴素贝叶斯分类器
在scikit-learn中,一共有三个朴素贝叶斯的分类算法类:
- GaussianNB(先验为高斯分布的朴素贝叶斯)
- MultinomialNB(先验为多项式分布的朴素贝叶斯)
- BernoulliNB(先验为伯努利分布的朴素贝叶斯)
对于新闻分类,我们使用MultinomialNB()完成我们的新闻分类问题,MultinomialNB假设特征的先验概率为多项式分布,如下式:
p ( X j = x j l ∣ Y = C k ) = x j l + λ m k + n λ p(X_{j}=x_{jl}|Y=C_k)=\frac{x_{jl}+λ}{m_k+nλ}\qquad p(Xj=xjl∣Y=Ck)=mk+nλxjl+λ
其中, P ( X j = X j l ∣ Y = C k ) P(X_j = X_{jl} | Y = C_k) P(Xj=Xjl∣Y=Ck)是第 k k k个类别的第j维特征的第 l l l个取值条件概率。 m k m_k mk是训练集中输出为第 k k k类的样本个数。 λ λ λ为一个大于0的常数,尝尝取值为1,即拉普拉斯平滑,也可以取其他值。
通过观察取不同的去掉前deleteN个高频词的个数与最终检测准确率的关系,确定deleteN的取值:
# author:answer time:2021/3/29
import numpy as mp
import os
import jieba
import random
from sklearn.naive_bayes import MultinomialNB
import matplotlib.pyplot as plt
def TextProcessing(folder_path,test_size=0.2):
"""
中文文本处理
:param folder_path: 文本存放路径
:param test_size: 测试集占比,默认展所有数据的百分之20
:return:
all_words_list - 按词频降序排序的训练集列表
train_data_list - 训练集列表
test_data_list - 测试集列表
train_class_list - 训练集标签列表
test_class_list - 测试集标签列表
"""
folder_list=os.listdir(folder_path) #查看folder_path下的文件
data_list=[] #训练集
class_list=[] #标签集
#遍历每个子文件夹
for folder in folder_list:
new_folder_path=os.path.join(folder_path,folder) #根据子文件夹生成新的路径
files=os.listdir(new_folder_path) #存放子文件夹下的txt文件列表
j=1
#遍历每个txt文件
for file in files:
if j>100: #每类最多100个样本
break
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f: #打开txt文件
raw=f.read()
word_out=jieba.cut(raw,cut_all=False) #精简模式,返回一个可迭代的generator
word_list=list(word_out) #generator转换为list
data_list.append(word_list)
class_list.append(folder)
j+=1
data_class_list=list(zip(data_list,class_list)) #zip压缩合并,将数据与标签对应压缩
random.shuffle(data_class_list) #将data_clsaa_list乱序
index=int(len(data_class_list)*test_size)+1 #训练集和测试集切分的索引值
train_list=data_class_list[index:] #训练集
test_list=data_class_list[:index] #测试集
train_data_list,train_class_list=zip(*train_list) #训练集压缩
test_data_list,test_class_list=zip(*test_list) #测试集压缩
all_words_dict={}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word]+=1
else:
all_words_dict[word]=1
#根据键的值倒序排序
all_words_tuple_list=sorted(all_words_dict.items(),key=lambda f:f[1],reverse=True)
all_words_list,all_words_nums=zip(*all_words_tuple_list) #解压缩
all_words_list=list(all_words_list) #转换成列表
return all_words_list,train_data_list,test_data_list,train_class_list,test_class_list
def MakeWordSet(word_file):
"""
读取文件里的内容,并去重
:param word_file: 文件路径
:return: words_set 读取的内容的set集合
"""
words_set=set() #创建set集合
with open(word_file,'r',encoding='utf-8') as f: #打开文件
for line in f.readlines(): #一行一行读取
word=line.strip() #去回车
if len(word)>0: #有文本,添加到words_set中
words_set.add(word)
return words_set #返回处理结果
def TextFeatures(train_data_list,test_data_list,feature_words):
"""
根据feature_words将文本向量化
:param train_data_list: 训练集
:param test_data_list: 测试集
:param feature_words: 特征集
:return:
train_feature_list 训练集向量化列表
test_feature_list 测试集向量化列表
"""
def text_feature(text,feature_words):
text_words=set(text) #出现在特征集中则置1
features=[1 if word in text_words else 0 for word in feature_words]
return features
train_feature_list=[text_feature(text,feature_words) for text in train_data_list]
test_feature_list=[text_feature(text,feature_words) for text in test_data_list]
return train_feature_list,test_feature_list
def words_dict(all_words_list,deleteN,stopwords_set=set()):
"""
文本特征提取
:param all_words_list: 训练集所有文本列表
:param deleteN: 删除词频最高的deleteN个词
:param stopwords_set: 指定结束语
:return feature_words:特征集
"""
feature_words=[] #特征列表
n=1
for t in range(deleteN,len(all_words_list),1):
if n>1000: #feature_word的维度为1000
break
#如果这个词不是数字,并且不是指定的结束语,并且单词长度大于1小于5,那么这个词可以作为特征词
if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1<len(all_words_list[t])<5:
feature_words.append(all_words_list[t])
n+=1
return feature_words
def TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list):
"""
新闻分类器
:param train_feature_list: 训练集向量化的特征文本
:param test_feature_list: 测试集向量化的特征文本
:param train_class_list: 训练集分类标签
:param test_class_list: 测试集分类标签
:return: test_accuracy:分类器精度
"""
classifier=MultinomialNB().fit(train_feature_list,train_class_list)
test_accuracy=classifier.score(test_feature_list,test_class_list)
return test_accuracy
if __name__=='__main__':
#文本预处理
folder_path='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes./SogouC/Sample' #训练集存放地址
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list=TextProcessing(folder_path,test_size=0.2)
#生成stopwords_set
stopwords_file='C:/Users/answer/Desktop/机器学习/machine-learning-master/Naive Bayes/stopwords_cn.txt'
stopwords_set=MakeWordSet(stopwords_file)
test_accuracy_list=[]
deleteNs=range(0,1000,20)
for deleteN in deleteNs:
feature_words=words_dict(all_words_list,deleteN,stopwords_set)
train_feature_list,test_feature_list=TextFeatures(train_data_list,test_data_list,feature_words)
test_accuracy=TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list)
test_accuracy_list.append(test_accuracy_list)
plt.figure()
plt.plot(deleteNs,test_accuracy_list)
plt.title('Relationship of deleteNs and test_accuracy')
plt.xlabel('delateNs')
plt.ylabel('test_accuracy')
plt.show()
五、总结
- 在训练朴素贝叶斯分类器之前,要处理好训练集,文本的清洗还是有很多需要学习的东西。
- 根据提取的分类特征将文本向量化,然后训练朴素贝叶斯分类器。
- 可以通过特征之间的条件独立性假设,降低对数据量的需求
- 利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。
https://jackcui.blog.csdn.net/article/details/77500679
https://github.com/Jack-Cherish/Machine-Learning