上一篇回顾上一篇介绍了朴素贝叶斯算法的相关知识,包括以下几个方面:朴素贝叶斯朴素贝叶斯的基本原理算法公式推导贝叶斯准则(条件概率公式)构建训练,测试简单文本分类算法拉普拉斯平滑校正公式推导部分比较重要,利用条件概率求解问题也是朴素贝叶斯思想的基础,所以非常重要理解贝叶斯准则是如何得到的,如何应用它,也是后面构建算法的基础。朴素贝叶斯算法在现实生活中应用广泛,如文本分类、垃圾邮件分类、信用评价、钓鱼网站检测等;就文本分类而言,在众多分类算法中,朴素贝叶斯分类算法也是学习效率和分类效果较好的分类器之一,因为朴素贝叶斯原理简单,构造算法也比较容易,而且它具有很好的可解释性。但是,朴素贝叶斯算法的特点是假设所有特征的出现是相互独立的,互不影响,每个特征同等重要。但实际上这个假设在现实世界中并不成立:首先,两个相邻词之间的必然联系不可能是独立的;其次,对于一篇文章来说,一些有代表性的词决定了它的主题,没必要把整篇文章通读一遍,把所有的词都查一遍。因此,需要采用合适的方法进行特征选择,使朴素贝叶斯分类器获得更高的分类效率。本文背景本文采用朴素贝叶斯方法构建了一个情感分类器,用于判断未知句子表达的是积极情绪还是消极情绪,通过将预测结果与真实结果进行比较,得出分类器的准确率。速度。最近在抖音看到一部电影的片段——饥饿站,背景是未来的反乌托邦国度,犯人被关押在垂直堆叠的牢房里,饿着肚子看着楼上掉下来的食物,靠近顶层的人很好——吃饱了,而底层的人则因饥饿而变得激进,讲述了人性黑暗和口渴的一面的故事。个人觉得这部电影还不错,所以选择了豆瓣上这部电影的短评作为本文的数据,可惜爬取的数据不多,主要是思路。豆瓣爬虫比较容易,所以爬虫部分就不过多概括了。我这里使用的是requests和BeautifulSoup的组合,但是需要注意的是模拟登录部分,如果不进行模拟登录,只能获取前10页的短评论,模拟登录后,可以获取一个总共24页的简短评论。小提示:热门短评和最新短评没有冲突。您可以获得100条最新的短评,因此您可以拥有更多的数据样本。最终的数据集有580个样本和三个属性。截图如下:Textpreprocessing在这个搭建情感分类器的小实践中,算法部分并不是很复杂,上面讲了很大一部分。更多的操作是预处理数据集。如果是从公共数据源获取的数据集,可能只需要简单处理一下,因为大部分问题数据集作者都已经解决了,但是个人爬虫获取的数据集问题比较多.我们希望将所有的短评论文本转换成由词汇组成的列表格式,下面对文本进行预处理。在原始数据集中,评分列由评分+推荐指数组成,格式不是我们需要的,所以这里使用自定义函数将其分为1-5的五个等级,我们可以将评分等级视为其相应的短评情感分类。#将分数分为五个等级1-5defrating(e):if'50'ine:return5elif'40'ine:return4elif'30'ine:return3elif'20'ine:return2elif'10'ine:return1else:return'none'#使用map方法后根据评分功能,我们需要标记每一条短评论的情绪。这里我们选择删除评分为3的短评论,因为无法确定短评论情感的类别。然后将评分为4和5的短评标记为1,视为正面情绪;评分为1和2的短评用0标记,被视为负面情绪。#删除评分为3的短评论,判断评分为3的情感为中性data=data[data['new_rating']!=3]#将4和5的评分标记为1,即被视为积极情绪;设1,2的分数标记为0,视为负面情绪data['sentiment']=data['new_rating'].apply(lambdax:+1ifx>3else0)下图展示五个评分等级的比例饼图,可以看出3分的比例比较大,所以删除评分为3的操作会导致数据集丢失大量数据样本;4分和5分的比例远高于1分和2分,因此集中的积极情绪所占比例较大。电影可能真的不错,但也凸显了数据比例不平衡的问题。爬虫获取到的短评论可能包含很多英文符号、单词和字母,对中文情感分析没有帮助,所以在分词之前,使用两个自定义函数删除短评论中的符号和英文字母。这里没有对数字进行操作是因为后面的停用词包含了删除数字的操作。jieba分词模式选择默认的精度模式。精准模式可以精准切句,更适合文本分析。#删除短评论中的符号和英文字母punc='~`!#$%^&*()_+-=|\';":/.,?><~·!@#¥%……&*()-+-=":';,.,?"{}'defremove_fuhao(e):returnre.sub(r[%s]+"%punc,"",e)defremove_letter(new_short):returnre.sub(r'[a-zA-Z]+','',new_short)#使用jieba截取文本defcut_word(text):text=jieba.cut(str(text))return''.join(text)#同apply方法基于以上三个customizations根据函数新建一列data['new_short']=data['short'].apply(remove_fuhao).apply(remove_letter).apply(cut_word)shortreview分完后会产生很多不相关的词,例如one、This、people等,因此停用词功能的作用是从简短评论中过滤掉这些词。这个功能的主要思想是把短评论按照空格分词,然后遍历词表。如果一个词没有出现在停用词列表中,该词的长度大于1,并且该词不是Tab,则连接到字符串outstr;如果一个词已经存在于outstr中,则不会添加,以达到去重的效果。文末提供获取中文停用词列表的方法#读取停用词列表函数defstopwordslist(filepath):stopwords=[line.strip()forlineinopen(filepath,'r',encoding='utf-8').readlines()]returnstopwords#删除短评论中的停用词defsentence_div(text):#将短评论按空格分割成一个列表sentence=text.strip().split()#Loadstopwords路径stopwords=stopwordslist(r'Chinesestopwordslist.txt')#创建一个空字符串outstr=''#遍历短评论列表中的每个单词forwordinsentence:ifwordnotinstopwords:#判断是否词汇表在停用词列表中iflen(word)>1:#单词的长度必须大于1ifword!='\t':#单词不能是制表符ifwordnotinoutstr:#去重:如果单词在outstr中,则不要添加outstr+=''#splitoutstr+=word#将词汇添加到outstr中#返回字符串returnoutstrdata['the_short']=data['new_short'].apply(sentence_div)可能有一个短评说很多都是废话,正好被停用词功能过滤掉了,剩下的几个词对这个短评的情感分析帮助不大,所以此处删除少于4个字的短评;因为上面是在自定义函数的基础上创建了很多新的属性,内容过于复杂,所以选择了情感分析需要的两列(处理后的短评论和注释)合并到一个新的DataFrame中。data['split']=data['the_short'].apply(lambdax:1iflen(x.split())>3else0)data=data[~data['split'].isin(['0'])]#索引需要的两列数据并合并成一个新的DataFramenew_data1=data.iloc[:,3]new_data2=data.iloc[:,5]new_data=pd.DataFrame({'short':new_data2,'sentiment':new_data1})预处理后的数据集中只剩下280个样本。截图如下:如前所述,短评中正面情绪的比例远大于负面情绪。为了避免测试数据集中的样本都是正面情绪,所以数据集采用随机选择的方式进行划分。使用random库中的sample方法,随机选取10%数据的索引作为测试数据集的索引,其余作为训练数据集的索引;然后根据两类索引将数据集分成两部分,分别保存。defsplitDataSet(new_data):#随机获取数据集的10%作为测试集,得到测试数据集的indextest_index=random.sample(new_data.index.tolist(),int(len(new_data.index.tolist())*0.10))#剩下的部分作为训练集,得到训练数据集的索引train_index=[iforiinnew_data.index.tolist()ifinotintest_index]#分别对训练集和测试集进行索引test_data=new_data.iloc[test_index]train_data=new_data.iloc[train_index]#分别保存为csv文件train_data.to_csv('bayes_train.csv',encoding='utf_8_sig',index=False)test_data.to_csv('bayes_test.csv',encoding='utf_8_sig',index=False)构建分类器构建分类器的部分会和上篇文章的代码冲突,所以后面的算法部分不再描述其原则太多;如果你刚接触朴素贝叶斯或者想了解它的原理,建议先阅读上一篇:机器学习笔记(五)——轻松看穿朴素贝叶斯;如果你对朴素贝叶斯的原理有足够的了解,如果你只对源码和数据感兴趣,你可以直接跳过这部分,移到文末。构建词向量loadDataSet函数的作用是将短评论转化为需要的词条向量格式,即每条短评论的词表形成一个列表,然后将所有列表添加到一个列表中,形成词条集合。classVec是由短评论组成的对应情感标注的列表。defloadDataSet(filename):data=pd.read_csv(filename)postingList=[]#textsentencesegmentationforsentenceindata['short']:word=sentence.strip().split()#split方法返回一个listpostingList.append(word)#将每个词汇表添加到一个列表中#类别标签的向量classVec=data['sentiment'].values.tolist()returnpostingList,classVeccreateVocabList函数的作用是通过set方法Set取并合并返回包含文本中所有出现的唯一单词的集合。#CreatevocabularydefcreateVocabList(dataSet):#Createanemptynon-repetitivelistvocabSet=set([])fordocumentindataSet:#取两者的并集vocabSet=vocabSet|set(document)returnlist(vocabSet)setOfWords2Vec函数的作用是对短评论进行向量化处理。输入参数是通用词汇和某个简短的评论。输出是一个文本向量。向量的元素包括1或0,分别表示词汇表中的词是否出现在输入文本中。思路是先创建一个与词汇表长度相同的向量,并将其元素设置为0,然后遍历输入文本的单词。如果词汇表中出现了这篇文章的词,则将其对应位置的0替换为1。#入口向量化函数defsetOfWords2Vec(vocabList,inputSet):#创建一个元素全为0的向量returnVec=[0]*len(vocabList)forwordininputSet:ifwordinvocabList:#如果词汇表中包含这个词,则将位置的0改为1returnVec[vocabList.index(word)]=1returnreturnVecgetMat函数的作用是将所有处理后的词向量组合成一个词向量矩阵,方便测试算法时调用。#入口向量汇总defgetMat(inputSet):trainMat=[]vocabList=createVocabList(inputSet)forSetininputSet:returnVec=setOfWords2Vec(vocabList,Set)trainMat.append(returnVec)returntrainMat训练算法trainNB函数的输入参数包括由短评论矩阵trainMatrix和每个词条的情感标签组成的向量trainCategory。首先,短评论属于正面情绪的概率只需要用正面情绪短评论数除以词条总数即可;在计算P(W|C1)和P(W|C0)时,需要对分子和分母进行初始化,在遍历输入文本时,一旦某个词(积极情绪或消极情绪)出现在某个文档中,对应的数totheword(p1Numorp0Num)会加1,在totaltext中,entry的总次数也相应加1。deftrainNB(trainMatrix,trainCategory):#训练文本的数量numTrainDocs=len(trainMatrix)#每个文本的条目数numWords=len(trainMatrix[0])#文档属于积极情绪的概率(1)pAbusive=sum(trainCategory)/float(numTrainDocs)#创建两个长度为numWords的零数组p0Num=np.ones(numWords)p1Num=np.ones(numWords)#分母初始化p0Denom=2.0p1Denom=2.0foriinrange(numTrainDocs):iftrainCategory[i]==1:#积极情绪的条件概率需要统计,即P(w0|1),P(w1|1),P(w2|1)···p1Num+=trainMatrix[i]#print(p1Num)p1Denom+=sum(trainMatrix[i])#print(p1Denom)else:#统计负面情绪条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Num+=trainMatrix[i]p0Denom+=sum(trainMatrix[i])#计算进入概率p1Vect=np.log(p1Num/p1Denom)p0Vect=np.log(p0Num/p0Denom)#print("\n",p0Vect,"\n\n",p1Vect,"\n\n",pAbusive)returnp1Vect,p0Vect,pAbusive测试算法classifyNB函数是判断类别的函数,输入参数向量格式的测试数据和训练函数trainNB的三个返回值,比如p1如果概率大于p0,说明测试数据是积极情绪,返回值为1;否则为负面情绪,返回值为0defclassifyNB(ClassifyVec,p1V,p0V,pAb):#将对应元素相乘print(pAb)p1=sum(ClassifyVec*p1V)+np.log(pAb)p0=sum(ClassifyVec*p0V)+np.log(1.0-pAb)print('p1:',p1)print('p0:',p0)ifp1>p0:return1else:return0testNB是一个测试函数,通过调用上述函数对测试集进行预测,并将真实结果与测试结果进行比较,得到分类器的准确率。deftestNB():#加载训练集数据train_postingList,train_classVec=loadDataSet('bayes_train4.csv')#创建词汇表vocabSet=createVocabList(train_postingList)#总结训练样本入口向量trainMat=getMat(train_postingList)#训练算法p1V,P0V,PAb=trainNB(trainMat,train_classVec)#加载测试集数据test_postingList,test_classVec=loadDataSet('bayes_test4.csv')#向量化测试文本predict=[]foreach_testintest_postingList:testVec=setOfWords2Vec(vocabSet,each_test)#判断类别ifclassifyNB(testVec,p1V,P0V,PAb):print(each_test,"positiveemotion")predict.append(1)else:print(each_test,"negativeemotion")预测.append(0)corr=0.0foriinrange(len(predict)):ifpredict[i]==test_classVec[i]:corr+=1print("朴素贝叶斯分类器准确率:"+str(round((corr/len(predict)*100),2))+"%")最终程序运行截图如下:因为我们采用随机选择的方式划分训练集和测试集,所以每次我们运行程序时,朴素贝叶斯分类器的准确率都会发生变化,你可以多运行几次,取平均值作为模型的准确率。最后附上根据该数据集绘制的词云图。不知道这部电影的类型能不能引起你的兴趣呢?总结在使用朴素贝叶斯算法进行类似的情感分析或文本分类时,需要尽可能保持原始数据的充足。像上面的580条原始数据,经过文本预处理后只剩下280条。只有数据充足,模型才有实用性。数据太少会导致模型精度波动较大,而且偶然性也很大。关注公众号【奶糖猫】后台回复“饿了么平台”获取源码和数据供参考,感谢阅读。
