mahout bayes源码分析总结

来源:互联网 发布:淘宝卖家后台登陆网址 编辑:程序博客网 时间:2024/04/30 20:46

最近在学习mahout,首先从最简单的bayes分类入手研究了一下源码,有谬误之处欢迎批评指正。

一、贝叶斯分类

条件概率P(A|B) 表示事件B已经发生的前提下,事件A发生的概率,叫做事件B发生下事件A的条件概率。其基本求解公式为P(A|B)= P(AB)/ P(B)。该公式说明了如何计算已知B发生的前提下A还要发生的概率。贝叶斯定理解决了现实生活里经常遇到的问题:已知某条件概率,如何得到两个事件交换后的概率,也就是在已知P(A|B)的情况下如何求得P(B|A)。贝叶斯定理就为我们打通从P(A|B)获得P(B|A)的道路。所以该定理的用途十分广大,可以用作数据的预测分类等。下面直接给出贝叶斯定理:


贝叶斯分类的正式定义如下:

 1、设

 

为一个待分类项,而每个ax的一个特征属性。

2、有类别集合


3、计算


4、如果

那么现在的关键就是如何计算第3步中的各个条件概率。我们可以这么做:

1、找到一个已知分类的待分类项集合,这个集合叫做训练样本集。

2、统计得到在各类别下各个特征属性的条件概率估计。即

3、如果各个特征属性是条件独立的,则根据贝叶斯定理有如下推导:


因为分母对于所有类别为常数,因为我们只要将分子最大化皆可。又因为各特征属性是条件独立的,所以有:


也就是说我只需要计算出每一个特征向量在某一种分类的累乘然后乘以这个分类的概率。这样算出的最大值所在的分类则为需要的分类。

以上材料参考自http://www.cnblogs.com/leoo2sk/archive/2010/09/17/naive-bayesian-classifier.html,很经典的一篇文章。

二、Mahoutbayes实现分析

Bayes分类主要分为两个阶段:
(1)分类器训练阶段,这个阶段的任务就是生成分类器(model),主要工作是计算每个类别在训练样本中的出现频率及每个特征属性划分对每个类别的条件概率估计,并将结果记录。其输入是特征属性和训练样本,输出是分类器。在mahout中,由mahout trainnb完成。
(2)应用阶段。这个阶段的任务是使用分类器对待分类项进行分类,其输入是分类器和待分类项,输出是待分类项与类别的映射关系。这一阶段由mahout testnb完成。
         实际上,在两个阶段之前,还有一个准备工作阶段,这个阶段的任务是为朴素贝叶斯分类做必要的准备,主要工作是根据具体情况确定特征属性,并对每个特征属性进行适当划分,然后由人工对一部分待分类项进行分类,形成训练样本集合。这一阶段的输入是所有待分类数据,输出是特征属性和训练样本。这一阶段是整个朴素贝叶斯分类中唯一需要人工完成的阶段,其质量对整个过程将有重要影响,分类器的质量很大程度上由特征属性、特征属性划分及训练样本质量决定。下面我们主要分析由程序完成的分类器训练阶段和应用阶段。

1mahout trainnb

训练样本,对应类org.apache.mahout.classifier.naivebayes.training.TrainNaiveBayesJob。输入是经过mahout seqdirectory和mahout seq2sparse向量化的序列化文件,输出是一个model(训练器)。具体流程:
(1)叠加所有相同label的tfidf vector
通过TrainNaiveBayesJob-IndexInstancesMapper-Reducer实现。该mr使用到了缓存文件labelindex,labelindex存储每个label对应的id。生成的文件是放到summedObservations临时目录下面。这一个过程实际上是得到了每个label下的feature的得分。
(2)叠加上一步中所有label和所有feature的向量
通过TrainNaiveBayesJob-WeightsMapper-Reducer实现,输出到tmp的weights目录下面。将(1)中所有vector相加,得到每个Feature的权重向量weightsPerFeature;将(1)中每个label对应的所有vector的所有元素相加,得到每个Label的权重向量weightsPerLabel。这一过程实际上是计算每个label的得分和每个feature在所有label下的得分。
(3)建立model
新建一个Matrix scoresPerLabelAndFeature,这是一个二维的矩阵,行是weightsPerLabel的大小,列是weightsPerFeature的大小,然后将(1)中生成的每个label下的每个feature的得分填充这个scoresPerLabelAndFeature,这样scoresPerLabelAndFeature实际上就是每个feature在每个label下的得分,加上(2)中生成的weightsPerFeature、weightsPerLabel就可以构造一个NaiveBayesModel,写入到hadoop的model/naiveBayesModel.bin中。
2mahout testnb

使用训练器进行分类。org.apache.mahout.classifier.naivebayes.test.TestNaiveBayesDriver。该过程只有一个MR 叫TestNaiveBayesDriver-BayesTestMapper-Reducer。它的输出key是期望的label,而value是在每一个label下的打分。首先将上面生成的model读出来,建立NaiveBayesModel对象,然后就可以计算待分类向量在每个label下的得分了。根据贝叶斯的特征独立性特点,它实际上是将待分类向量的每个feature在这个label下的分值相加了(因为feature在label下的打分使用log)。根据选择的分类器是ComplementaryNaiveBayesClassifier还是StandardNaiveBayesClassifier, feature在label下的打分的计算公式不一样:
(1)StandardNaiveBayesClassifier:For Bayes
Weight = Log [ ( W-N-Tf-Idf + alpha_i ) / ( Sigma_k + N  ) ]
其中
featureLabelWeight即W-N-Tf-Idf
labelWeight即Sigma_k
numFeatures即N
(2)ComplementaryNaiveBayesClassifier:For CBayes
Weight = Log [ ( Sigma_j - W-N-Tf-Idf + alpha_i ) / ( Sigma_jSigma_k - Sigma_k + N  ) ]
其中
featureWeight即Sigma_j
featureLabelWeight即W-N-Tf-Idf
labelWeight即Sigma_k
totalWeight即Sigma_jSigma_k,是labelWeight中各项之和。
numFeatures即N
参考:https://cwiki.apache.org/confluence/display/MAHOUT/Bayesian
计算出待分类向量在每个label下的得分后,就可以决定分到哪一个label下了。

三、Mahout中向量实现分析

不仅是bayes等分类,包括聚类也都需要文本的向量化,所以我们需要了解下Mahout的向量实现。对于文本信息的向量化,Mahout 已经提供了工具类,它基于 Lucene对文本信息进行分析,然后创建文本向量。在Mahout中,向量(Vector)有很多不同的实现,适用于不同的算法场景,主要介绍下面三种(对他们也可以用其他的Vector进行再包装):
(1)DenseVector,它的实现就是一个浮点数数组,对向量里所有域都进行存储,适合用于存储密集向量。
(2)RandomAccessSparseVector 基于浮点数的 HashMap 实现的,key 是整形 (int) 类型,value 是浮点数 (double) 类型,它只存储向量中不为空的值,并提供随机访问。
(3)SequentialAccessVector 实现为整形 (int) 类型和浮点数 (double) 类型的并行数组,它也只存储向量中不为空的值,但只提供顺序访问。下面简单介绍一下Mahout向量化的过程。
1mahout seqdirectory

对应的源文件是org.apache.mahout.text.SequenceFilesFromDirectory。使用mahout seqdirectory命令可以将文本文件转成Hadoop 的SequenceFile文件, SequenceFile文件是一种二制制存储的key-value键值对。以邮件分类为例,mahout seqdirectory的输入是一堆分类目录,每个目录名就是类别名,目录里面包含的是属于此类的一些文档(一个文件是一篇文章);输出是一个SequenceFile文件,key-value类型是(Text,Text),每个key-value键值对的key是目录名(类别名),本目录下所有文件的内容作为value。

2mahout seq2sparse

mahout seq2sparse将上面生成的SequenceFile转成向量文件,对应的源文件是
org.apache.mahout.vectorizer.SparseVectorsFromSequenceFiles。seq2sparse实际上是一个计算文本TF,DF,TFIDF的过程,依次产生的向量文件目录结构是:
(1)tokenized-documents 目录:保存着分词过后的文本信息。
(2)wordcount 目录:保存着全局的词汇出现的次数。
(3)dictionary.file-0 目录:保存着这些文本的词汇表。
(4)tf-vectors 目录:保存着以 TF 作为权值的文本向量。
(5)df-count 目录:保存着文本的频率信息。
(6)frequcency-file-0 目录 : 保存着词汇表对应的频率信息。
(7)tfidf-vectors 目录:保存着以 TFIDF 作为权值的文本向量
下面简单介绍seq2sparse的工作流程:
(1)(Text,Text)变成(text,StringTuple)。
将上面seqdirectory生成的的序列文件的(Text,Text)变成(text,StringTuple),放在tokenized-documents目录。StringTuple是一种适合做hadoop处理的有序字符串列表,本质是一个List<String>。使用DocumentProcessor::DocumentTokenizer MR实现,使用了lucene的org.apache.lucene.analysis.Analyzer来对文本进行分析。
(2)将上面生成的(Text,StringTuple)转成词频TF向量(Text,VectorWritalbe)。
(2.1)使用名为DictionaryVectorizer::WordCount 的MR计算所有单词的数目,放在wordcount目录。
(2.2)读取上一步生成的word count文件,并且为每个单词分配唯一的id。这个函数没有使用mr,直接使用hdfs api操作,并且考虑到了块划分的问题,生成多个dictionary.file-*文件。
(2.3)使用每个dictionary.file-*,对tokenized-documents中的文件进行TF计算,生成多个partial-vectors-*。这个过程使用了DictionaryVectorizer::MakePartialVectors MR,对于每个在当前字典中找到的term,累加次数即可。注意maxNGramSize参数,根据用户设置max.ngrams,如果maxNGramSize >= 2,那么需要首先使用org.apache.lucene.analysis.shingle.ShingleFilter进行可能的单词组合,作为新的单词。
(2.4)PartialVectorMerger.mergePartialVectors将(3)生成的各个partial-vectors-*合并,并删除每个partial-vectors-*。
(3)计算每个term在所有文档中的df。
(3.1)以(2)生成的TF向量文件为输入,输出每个term在所有文档中的频率以及所有的向量个数,生成的文件在df-count中。由VectorTfIdf Document Frequency Count mr完成。
(3.2)直接使用hdfs api读取上一步生成的DF 文件,生成多个frequency.file-*文件。并统计一共有多少个vector和feature。
(3.3)计算tfidf向量。类似上面求tf,计算tfidf也是拆成多个makePartialVectors,每个makePartialVectors使用了名为MakePartialVectors 的MR,输入是上面(2)生成的tf-vectors,和(3.2)中生成的frequency.file-*作为字典文件,生成针对这个字典文件的partial-vectors-*。对于在当前字典中存在的feature 的df,会根据maxDf,minDf的配置做一下调整,然后使用TFIDF类的calculate方法根据tf,df,文档总数目计算tfidf。TFIDF类的calculate方法会使用Similarity接口的的tf()和idf()方法,tf()使用(float)Math.sqrt(freq),idf()使用(float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0)。可以看到,算出的tfidf,与tf成正比,与idf成反比。
(3.4)最后使用PartialVectorMerger::MergePartialVectors MR,对于上一步生成的所有partial-vectors-*合并,并删除每个partial-vectors-*。
0 0
原创粉丝点击