前言上一篇《机器学习算法实践:决策树 (Decision Tree)》总结了决策树的实现。在本文中,我将一步步实现一个朴素贝叶斯分类器,并使用短信垃圾短信语料库中的数据进行模型训练,过滤垃圾短信,计算分类错误率在***。文本不同于决策树分类和k-最近邻分类算法。贝叶斯分类主要是利用概率论的知识,比较所提供的每一类数据的条件概率,分别计算,然后预测条件概率最好的条件概率。该类别是最好的类别。当然,我们拥有的样本越多,我们统计的不同类型的特征值分布就越准确,使用这种分布进行预测也会更加准确。贝叶斯准则朴素贝叶斯分类器的核心是贝叶斯准则,用下面的公式表示:这个公式表示两个可互换的条件概率之间的关系,它们之间通过联合概率相关联,这使得我们可以计算p(A|B)当我们知道p(B|A)时,我们的贝叶斯模型利用贝叶斯准则计算一个样本在不同类别条件下的条件概率,并取条件概率最大的类别作为该类别的预测结果分类。使用条件概率进行分类。在这里我将介绍如何按条件概率进行分类。假设我们看到一个人的背影,想通过背影的一些特征(数据)来判断这个人的性别(类别)。假设涉及的特征是:是否留长发,身高是否在170以上,腿细,是否穿裙子。当我们看到一个背图的时候,我们会得到一个特征向量来描述上面的特征(1表示是,0表示否):ω=[0,1,1,0]贝叶斯分类就是比较下面两个条件概率:p(boy|ω),在ω等于[0,1,1,0]p(girl|ω))的条件下这个人是男孩的概率,在ω等于[0,1,1,0]的条件如果一个人是女孩的概率是p(boy|ω)>p(girl|ω),那么就确定这个人是男孩,否则就是女孩,那怎么求p(boy|ω)呢?这是基于贝叶斯准则贝叶斯准则为了更容易理解写成:如果特征是相互独立的(条件独立假设),那么上面的条件概率可以改写为:这样,我们可以计算出是否目前的图属于男生,属于女生的条件概率。实现您自己的贝叶斯分类器贝叶斯分类器实现起来非常简单。接下来,我使用Python实现了一个朴素贝叶斯文本分类器,用于文本分类。为了计算条件概率,我们需要计算不同类别下的条件概率和类型的边际概率需要我们通过大量的训练数据统计得到近似值,也就是训练我们的过程朴素贝叶斯模型。对于不同的文本,我们可以将所有出现的词作为数据特征向量,将每个文本中出现的词条数量(或者某个词条是否出现)统计为一个数据向量。这样的文本可以处理成一个整数列表,长度就是所有条目的个数。这个向量可能很长。本文数据集中使用的短信词条一共3000多字。defget_doc_vector(words,vocabulary):'''根据词汇将文档中的term转换为文档向量:paramwords:文档中的term列表:typewords:listofstr:paramvocabulary:totalvocabularylist:typevocabulary:listofstr:returndoc_vect:DocumentvectorforBayesiananalysis:typedoc_vect:listofint'''doc_vect=[0]*len(vocabulary)forwordinwords:ifwordinvocabulary:idx=vocabulary.index(word)doc_vect[idx]=1returndoc_vect统计训练过程的代码实现是如下:deftrain(self,dataset,classes):'''训练朴素贝叶斯模型:paramdataset:所有文档数据向量:typedataset:MxNmatrixcontainingalldocvectors.:paramclasses:所有文档的类型:typeclasses:1xNlist:returncond_probs:训练得到的条件概率matrix:typecond_probs:dict:returncls_probs:各类概率:typecls_probs:dict'''#根据不同类型的内存分类sub_datasets=defaultdict(lambda:[])cls_cnt=defaultdict(lambda:0)fordoc_vect,clsinzip(dataset,classes):sub_datasets[cls].append(doc_vect)cls_cnt[cls]+=1#计算类型概率cls_probs={k:v/len(classes)fork,vincls_cnt.items()}#计算不同类型条件的概率cond_probs={}dataset=np.array(dataset)forcls,sub_datasetinsub_datasets.items():sub_dataset=np.array(sub_dataset)#Improvetheclassifier.cond_prob_vect=np.log((np.sum(sub_dataset,axis=0)+1)/(np.sum(dataset)+2))cond_probs[cls]=cond_prob_vectreturncond_probs,cls_probs注意这里有基本条件概率的直接相乘两个改进:每个特征的概率初始值为1,分母上统计的某类样本总数初始值为1。这是为了避免如果某个特征统计的概率为0,联合概率也为零。自然没有任何意义。如果训练样本足够大,不会影响比较结果。由于每个独立特征的概率都是一个小于1的数,累积起来必然是一个更小的书,会遇到浮点下溢的问题,所以这里我们对所有的概率都进行了对数处理,从而避免了这个问题下溢,同时确保不会有损失。得到统计概率信息后,我们就可以使用BayeuxAdams准则对我们数据的类型进行预测了。这里,我没有直接计算每种情况的概率,而是通过统计得到的向量与数据向量的内积,得到条件概率的相对值,通过相对比较来做出决策。defclassify(self,doc_vect,cond_probs,cls_probs):'''使用朴素贝叶斯对doc_vect进行分类。'''pred_probs={}forcls,cls_probincls_probs.items():cond_prob_vect=cond_probs[cls]pred_probs[cls]=np.sum(cond_prob_vect*doc_vect)+np.log(cls_prob)returnmax(pred_probs,key=pred_probs.get)为短信分类建立了一个朴素贝叶斯模型,我们可以使用这个模型来统计数据并用它来进行预测。这里我使用垃圾短信语料库中的垃圾短信数据,随机抽取90%的数据作为训练数据,其余10%的数据作为测试数据来测试我们贝叶斯模型预测的准确性。当然,在构建模型之前,我们需要将数据处理成模型可以处理的格式:ENCODING='ISO-8859-1'TRAIN_PERCENTAGE=0.9defget_doc_vector(words,vocabulary):'''转换documentintoDocumentvector:paramwords:文档中的术语列表:typewords:listofstr:paramvocabulary:总词汇表:typevocabulary:listofstr:returndoc_vect:用于贝叶斯分析的文档向量:typedoc_vect:listofint'''doc_vect=[0]*len(vocabulary)forwordinwords:ifwordinvocabulary:idx=vocabulary.index(word)doc_vect[idx]=1returndoc_vectdefparse_line(line):'''解析数据集中的每一行并返回入口向量和短信类型。'''cls=line.split(',')[-1].strip()content=','.join(line.split(',')[:-1])word_vect=[word.lower()forwordinre.split(r'\W+',content)ifword]returnword_vect,clsdefparse_file(filename):'''解析文件中的数据'''vocabulary,word_vects,classes=[],[],[]withopen(filename,'r',encoding=编码)asf:forlineinf:ifline:word_vect,cls=parse_line(line)vocabulary.extend(word_vect)word_vects.append(word_vect)classes.append(cls)vocabulary=list(set(vocabulary))returnvocabulary,word_vects,classeshave有了上面三个函数,我们就可以直接把我们的文本转换成得到模型需要的数据向量,然后我们就可以对数据集进行划分,将训练数据集交给贝叶斯模型进行统计。#训练数据&测试数据ntest=int(len(classes)*(1-TRAIN_PERCENTAGE))test_word_vects=[]test_classes=[]foriinrange(ntest):idx=random.randint(0,len(word_vects)-1)test_word_vects.append(word_vector.pop(idx))test_classes.append(classes.pop(idx))train_word_vects=word_vectstrain_classes=classestrain_dataset=[get_doc_vector(words,vocabulary)forwordsintrain_word_vects]训练模型:cond_probs,cls_probs=clf.train(train_dataset,train_classes)剩下的我们用测试数据来测试我们贝叶斯模型的预测精度:#testmodelerror=0fortest_word_vect,test_clsinzip(test_word_vects,test_classes):test_data=get_doc_vector(test_word_vector,vocabulary)pred_cls=clf.classify(test_data,cond_probs,cls_probs)iftest_cls!=pred_cls:print('Predict:{}--Actual:{}'.format(pred_cls,test_cls))error+=1print('ErrorRate:{}'.format(error/len(test_classes)))随机测试四组,错误率分别为:0、0.037、0.015、0,平均错误率为1.3%。测试结束后,我们试试看不同类型短信的每个词条的概率分布是怎样的:#绘制不同类型的概率分布曲线fig=plt.figure()ax=fig.add_subplot(111)forcls,probsincond_probs.items():ax.scatter(np.arange(0,len(probs)),概率*cls_probs[cls],label=cls,alpha=0.3)ax.legend()plt.show()尝试决策树在上一篇文章中,我们实现了一个基于ID3算法的决策树。这也是一个分类问题,我们也可以使用我们的文本数据来构建一个决策树来对文本消息进行分类。当然,唯一麻烦的地方是,如果用和贝叶斯一样的向量作为数据,可能会有很多属性。我们在构建决策树的时候,树结构的每一层都是递归遍历属性,根据信息增益选择***个属性进行树的分裂,所以很多属性对于构建决策树的过程来说可能是比较耗时的。那我们来试试...#生成决策树'sms_tree.pkl')#testmodelerror=0fortest_word_vect,test_clsinzip(test_word_vects,test_classes):test_data=get_doc_vector(test_word_vect,vocabulary)pred_cls=clf.classify(test_data,feat_names=vocabulary)iftest_cls!=pred_cls:print('预测:{}--Actual:{}'.format(pred_cls,test_cls))error+=1print('ErrorRate:{}'.format(error/len(test_classes)))随机测试了两次,错误率分别为:0.09,0.0.效果还不错。我们还是用Graphviz来可视化看一下选择了那些条目作为准则的决策树(此时决策树的好处就体现出来了)。可以看出决策树的深度不是很深。如果分类类型多了,估计决策树的深度可能会增加。麻烦。小结在本文中,我们使用Python逐步实现了朴素贝叶斯分类器,对垃圾短信进行了过滤。我们简单对比了决策树对相同数据的分类效果。本文相关代码实现:https://github.com/PytLab/MLBox/tree/master/naive_bayes。决策树过滤垃圾邮件的脚本在https://github.com/PytLab/MLBox/tree/master/decision_tree参考《Machine Learning in Action》实例详解贝叶斯推理原理:朴素贝叶斯分类器相关阅读机器学习算法实践-决策树
