Mahout贝叶斯分类器按照官方的说法,是按照《Tackling the PoorAssumptions of Naive Bayes Text Classiers》实现的。分为三个模块:训练、测试和分类。该文档首先简要介绍朴素贝叶斯的基本原理,然后介绍MapReduce实现的思路。
在训练和分类之前都需要将小文档合并,以及分词处理。大量的小文档会让NameNode占用太多的内存空间存储元数据,另一方面在执行MapReduce时会占用太多的Map槽。
将训练数据组织成下图的结构。子文件夹的名字为类型名,子文件夹下为该类的文档。子文件夹下不能有文件夹。
训练数据目录结构
该过程将训练数据生成model,生成三个目录:trainer-tfidf、trainer-weights和trainer-thetaNormalizer 。有四个Job完成该过程。抽取正则化的特征,计算TFIDF,计算权重和计算正则化因子。
(1)特征抽取
输入数据格式:
clazzlable |
文档内容 |
Map:
对每一篇文档分词后计算该片文档中的词频(wordcount)、、。
输出:
(1)归一化的词频
key:<_WT,lable,token>
value :
(2)词的文档频率
Key: <_DF,lable,token>
Value: 1
(3)特征数量
Key:<_FC,lable,token>
Value:1
(4)TF
Key:<"_FF”,lable,token>
Value:wordcount
(5)类型数
Key:<_LC,lable>
Value:1
Combiner:
在Map计算完之后value值求和。
Reduce:
在reduce计算主要做特征选择。过滤掉小于最小词频数和最小文档数的词。
最终输出:
trainer-wordFreq,存储归一化的词频。
trainer-termDocCount,存储文档词的文档频率。
trainer-featureCount,存储特征数量。
trainer-docCount,存储类型数量,每个类型的文档数。
(2)计算TFIDF
输入:
trainer-termDocCount,trainer-wordFreq和trainer-featureCount文件。
将trainer-docCount添加到configuration中。
Map:
Configure(JobConf job){
从configuration中加载trainer-docCount数据。
}
map(StringTupleDoubleWritable,StringTuple,DoubleWritable){
1.对于TF ,直接输出。
2.对于DF=Log(该词对应的类型的总文档数/df)。输出:Key <_WT,lable, token> value:DF
3.对于特征总数,特征词的数量
输出:key <_FS> , value 1
}
Reduce:
1.对于特征集数量,求和,输出 key: <_FS> value: 特征数
2.对于TF和IDF。由于对同一个类型中的同一个词,hadoop会将其值合并在一起,这样reduce传入的同一key的迭代器中只有两个值,一个是TF值,一个是IDF值。计算tfidf =TF*IDF .
输出:key <_WT,lable,token> value :tfidf
最终输出:
trainer-tfidf 文档,key <_WT,lable,token> value :tfidf
(3)计算权重和
该过程为特征和类表计算权重。
输入:
trainer-tfidf文件。格式为key <_WT,label ,token> ,value <tfidf>
Map:
1.特征和,计算全局词特征权重和
从key中拿到token 输出其tfidf值。
输出:key <_SJ,token> value <tfidf>
2.类型label和,计算全局类型权重和
从key中拿到label,输出其tfidf。
输出: key<_SK,label> value <tfidf>
3.特征及label和,计算所有特征所有类型的和
从key中拿到tfidf,输出。
输出: key <_SJSK> value <tfidf>
Reduce:
对map的结果求和,输出。输出格式和map的一样。
(4)计算正则化因子
输入:
trainer-tfidf文件。格式为key <_WT,label ,token> ,value <tfidf>
将trainer-vocabcount 中的数据写入configuration。
Map:
setup(){
从configuration中读取trainer-vocabcount数据。
}
Map{
对输入的每一个词
W=Log(tfidf/(该词的类型的特征和+总特征数))
输出:key <_LTN,label> , value<W>
}
Reduce:
对Map结果求和。
输出:key<_LTN,label> value <SUM(W)>
输出:
trainer-thetaNormalizer文件.格式Key <_LTN,label> value <W>
训练好模型之后,最终会生成如下图的文档结构:
首先将模型信息存储到内存中,InMemoryBayesDatastore 负责。
分类时传入分好词的词数组document,InMemoryBayesDatastore 的实例,没有分出来时默认的类型名(默认为unknow)。
public ClassifierResult classifyDocument(String[] document, Datastore datastore, String defaultCategory){ maxWeight =Double.MAX_VALUE; ClassifierResult clazz =new ClassifierResult (); For(int i = 0 ;i <类型总数;i++){ Label =datastore.get(i); Frequency=document中计算词频 TFIDF: 该词在当前类型中的TFIDF Sigma_ k 该类型的tfidf和VocabCount 总特征词个数 Weight= frequency*log[(TFIDF +1.0) /(Sigma_ k +VocabCount)] If(Weight<maxWeight){ maxWeight=Weight; clazz.setLable(Label); } } clazz.setScore(maxWeight); return clazz; } |
在map中做分类,输出分类结果。
输入:输入文件格式:文档路径\t分词后的文档内容
Map:
setUp(){
加载模型。
}
Map(Text key,Text value){//key为文档路径 ,value为分词后的文档内容
(1)对分词后的文档内容计算N-Gram
(2)计算词频
(3)类型=classifyDocument(String[] document,
Datastore datastore,
String defaultCategory)
输出分类结果 key: 文档路径,value:类型
}
测试本身就是分类后统计分类的正确率。
测试数据的格式为:文档类型 \t 分词后的文档内容
Map:key 正确的标签,value 分词后的文档内容
分类后的类型标签=classifyDocument();
输出:key <_CT,正确的标签,分类后的标签> value 1
Reduce:对map的value求和。
第一部分叙述了Mahout 朴素贝叶斯分类算法的实现原理。下面叙述API的使用。在说API之前先说说文本预处理,一般来说需要训练或分类的文本都不太可能是TXT格式的,pdf、word ,html网页等等。首先要做的就是从各种格式的文件中抽取出文本。然后要将文本分词。
对于训练数据,需要组织成如图的目录结构。
art248 ,computer200等都是类型信息,每一类中有若干文本。各类中文本的数量保持基本一致。
每一类中都是大量的小文件,而如果需要用MapReduce做训练,需要合并小文件,组成一个或多个大文件。组合后的文件格式为:
类型名 |
\t |
分词后的一个小文件内容 |
参数说明
TrainClassfier.main(args []) |
|
--gramSize (-ng) |
Ngram 个数,默认为1。5以上运算量大,存储空间多,精度提高小,建议不要使用。 |
--input (-i) |
训练数据路径。(注意:是HDFS路径) |
--output (-o) |
模型输出路径。(注意:是HDFS路径) |
--classifierType (-type) |
分类器类型。bayes ,默认。 |
--dataSource (-source) |
模型存储位置,默认是hdfs。实际上Mahout并没有实现其它的存储方式。 |
--alpha (-a) |
平滑因子,默认为1。 |
--minDf (-mf) |
最小df值,默认为1。 |
--minSupport (-ms) |
最小tf值,默认为1。 |
训练后生成的文件:
文类数据和训练数据一样,需要抽取和分词。处理后的待分类数据存放在HDFS的一个目录下。
同样待分类数据再分之前也需要合并,原理同上。
合并后的文件格式(SequenceFile)为:
文件路径 |
\t |
分词后的一个小文件内容 |
参数说明:
ClassifyClassifier.main(args []); |
|
--defaultCat (-default) |
默认的类型,unknown。这种情况出现在模型数据为空。若分类数据出现unknown,需要检查模型。 |
--testDir (-d) |
分类数据存放路径 |
--encoding (-e) |
编码格式 (UTF-8) |
--gramSize (-ng) |
Ngram大小,默认为1。建议和训练的ngram值一样。 |
--model (-m) |
模型路径 |
--classifierType (-type) |
默认bayes. |
--dataSource (-source) |
默认hdfs |
--method (-method) |
默认MapReduce |
--verbose (-v) |
是否显示文档分类正确或者错误。注意:该参数在分类时作用不大,分类数据不知道原始的类型信息。 |
--alpha (-a) |
平滑因子,默认为1。 |
--outputDir (-o) |
分类后输出路径。 |
结果文件是SequenceFile,格式如下:
文件路径(Text) |
类型(Text) |
测试数据和训练数据的组织一样,合并后的文件格式如下:
真实类型 |
\t |
分词后的文档内容 |
参数说明
TestClassifier.main(args); |
|
--defaultCat (-default) |
默认的类型,unknown。这种情况出现在模型数据为空。若分类数据出现unknown,需要检查模型。 |
--testDir (-d) |
分类数据存放路径 |
--encoding (-e) |
编码格式 (UTF-8) |
--gramSize (-ng) |
Ngram大小,默认为1。建议和训练的ngram值一样。 |
--model (-m) |
模型路径 |
--classifierType (-type) |
默认bayes. |
--dataSource (-source) |
默认hdfs |
--method (-method) |
默认MapReduce |
--alpha (-a) |
平滑因子 |
--verbose (-v) |
是否显示分类正确或者错误。 |
测试结果,模糊矩阵
1.《Tackling the Poor Assumptions of Naive Bayes Text Classiers》