朴素贝叶斯(Naive Bayes)算法的核心思想是:分别计算给定样本属于每个分类的概率,然后挑选概率最高的作为猜测结果。


假定样本有2个特征x和y,则其属于分类1的概率记作p(C1|x,y),它的值无法直接分析训练样本得出,需要利用公式间接求得。

其中p(Ci)表示训练样本中分类为Ci的概率,它等于Ci样本数除以样本总数。

p(x,y)表示满足2个特征的样本概率,它等于第1特征等于x且第2特征等于y的样本数除以样本总数。可以发现p(x,y)与当前在计算哪个分类概率是无关的,因此实际计算中可以忽略它,不会影响结果。

p(x,y|Ci)表示Ci分类中满足2个特征的样本概率,在朴素贝叶斯算法中,认为x与y是相互独立的,因此p(x,y|Ci)=p(x|Ci)*p(y|Ci),其中p(x|Ci)表示Ci分类的样本中第1个特征等于x的概率。


上面的例子中只给定了2维的情况,实际可以扩展到N维,由于假定各特征相互独立,因此p(w|Ci)总是可以分解求得的。


上C#代码:

usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;namespaceMachineLearning{///<summary>///朴素贝叶斯///</summary>publicclassNaiveBayes{privateList<DataVector<string>>m_TrainingSet;///<summary>///训练///</summary>///<paramname="trainingSet"></param>publicvoidTrain(List<DataVector<string>>trainingSet){m_TrainingSet=trainingSet;}///<summary>///分类///</summary>///<paramname="vector"></param>///<returns></returns>publicstringClassify(DataVector<string>vector){varclassProbDict=newDictionary<string,double>();//得到所有分类vartypeDict=newDictionary<string,int>();foreach(variteminm_TrainingSet)typeDict[item.Label]=0;//为每个分类计算概率foreach(stringtypeintypeDict.Keys)classProbDict[type]=GetClassProb(vector,type);//找最大值doublemax=double.NegativeInfinity;stringlabel=string.Empty;foreach(vartypeinclassProbDict.Keys){if(classProbDict[type]>max){max=classProbDict[type];label=type;}}returnlabel;}///<summary>///分类(快速)///</summary>///<paramname="vector"></param>///<returns></returns>publicstringClassifyFast(DataVector<string>vector){vartypeCount=newDictionary<string,int>();varfeatureCount=newDictionary<string,Dictionary<int,int>>();//首先通过一次遍历,得到所需要的各种数量foreach(variteminm_TrainingSet){if(!typeCount.ContainsKey(item.Label)){typeCount[item.Label]=0;featureCount[item.Label]=newDictionary<int,int>();for(inti=0;i<vector.Dimension;++i)featureCount[item.Label][i]=0;}//累加对应分类的样本数typeCount[item.Label]++;//遍历每个维度(特征),累加对应分类对应特征的计数for(inti=0;i<vector.Dimension;++i){if(string.Equals(vector.Data[i],item.Data[i]))featureCount[item.Label][i]++;}}//然后开始计算概率doublemaxProb=double.NegativeInfinity;stringbestLabel=string.Empty;foreach(stringtypeintypeCount.Keys){//计算p(Ci)doubleclassProb=typeCount[type]*1.0/m_TrainingSet.Count;//计算p(F1|Ci)doublefeatureProb=1.0;for(inti=0;i<vector.Dimension;++i)featureProb=featureProb*(featureCount[type][i]*1.0/typeCount[type]);//计算p(Ci|w),忽略p(w)部分doubletypeProb=featureProb*classProb;//保留最大概率if(typeProb>maxProb){maxProb=typeProb;bestLabel=type;}}returnbestLabel;}///<summary>///获取指定分类的概率///</summary>///<paramname="vector"></param>///<paramname="type"></param>///<returns></returns>privatedoubleGetClassProb(DataVector<string>vector,stringtype){doubleclassProb=0.0;doublefeatureProb=1.0;//统计训练样本中属于此分类的数量,用于计算p(Ci)inttypeCount=m_TrainingSet.Count(p=>string.Equals(p.Label,type));//遍历每个维度(特征)for(inti=0;i<vector.Dimension;++i){//统计此分类下符合本特征的样本数,用于计算p(Fn|Ci)intfeatureCount=m_TrainingSet.Count(p=>string.Equals(p.Data[i],vector.Data[i])&&string.Equals(p.Label,type));//计算p(Fn|Ci)featureProb=featureProb*(featureCount*1.0/typeCount);}//计算p(Ci|w),忽略p(w)部分classProb=featureProb*(typeCount*1.0/m_TrainingSet.Count);returnclassProb;}}}


代码中Classify方法以最直接直观的方式实现算法,易于理解,但是由于遍历次数太多,在训练样本较多时效率不佳。ClassifyFast方法减少了遍历次数,效率有一定提升。两者的基础算法是一致的,结果也一致。


需要注意实际运行中有可能遇到多个分类概率相同或者每种分类概率都是0的情况,此时一般是随便选取一个分类作为结果。但有时要小心对待,比如用贝叶斯识别垃圾邮件时,如果概率相同,甚至是两个概率相差不大时,都要按非垃圾邮件来处理,这是因为没识别出垃圾邮件造成的影响远小于把正常邮件识别成垃圾造成的影响。



还是用上回的毒蘑菇数据进行一下测试,这回减少点数据量,选取2000个样本进行训练,然后选取500个测试错误率。

publicvoidTestBayes(){vartrainingSet=newList<DataVector<string>>();vartestSet=newList<DataVector<string>>();varfile=newStreamReader("agaricus-lepiota.txt",Encoding.Default);//读取数据stringline=string.Empty;for(inti=0;i<2500;++i){line=file.ReadLine();if(line==null)break;varparts=line.Split(',');varp=newDataVector<string>(22);p.Label=parts[0];for(intj=0;j<p.Dimension;++j)p.Data[j]=parts[j+1];if(i<2000)trainingSet.Add(p);elsetestSet.Add(p);}file.Close();//检验varbayes=newNaiveBayes();bayes.Train(trainingSet);interror=0;foreach(varpintestSet){varlabel=bayes.ClassifyFast(p);if(label!=p.Label)++error;}Console.WriteLine("Error={0}/{1},{2}%",error,testSet.Count,(error*100.0/testSet.Count));}


测试结果是错误率是0%,有点出乎意料。改变训练样本与测试样本,错误率会有变化,比如与上一篇相同条件时(7000个训练样本+1124个测试样本),测试出的错误率是4.18%,与随机猜测50%的错误率相比,已经相当精确了。