本来在此之前还应该有一篇介绍mallet中贝叶斯实现的文章。鉴于已经写过mahout下贝叶斯的博客,算法过程已经十分完整,所以直接进入决策树算法的部分。以cc.mallet.classify.examples包下的DocumentClassifier为驱动来介绍C4.5算法。由于知识浅薄,疏漏错误之处在所难免,所以恳请各位能不吝赐教。
文本预处理
DocumentClassifier
Pipe instancePipe = new SerialPipes ();
InstanceList ilist = new InstanceList (instancePipe);
ilist.addThruPipe (new FileIterator ());
→pipe.newIteratorFrom(ii);
SerialPipes::newIteratorFrom();
→add (pipedInstanceIterator.next());
详细看一下这些过程是如何将目录路径作为输入路径,将文本数据读入并转化为标准的instance吧。
1、instancePipe
instancePipe如其名字,是所有Pipe的容器,ilist以instancePipe作为参数初始化一个用于盛放instance的InstanceList对象。
2、pipe.newIteratorFrom(ii);
这是设计最为微妙的部分,InstanceList调用pipe(即SerialPipes)的newIteratorFrom函数,SerialPipes以Iterator<instance>为参数,为Pipe构造一个Iterator<Instance>类型的迭代器,并作为将其作为参数传递给容器下一个位置的Pipe,直至遍历完成容器中所有的Pipe然后返回ret。用函数式来解释就是:
ret = PrintInputAndTarget //打印输入和标签
(FeatureSequence2FeatureVector
(TokenSequence2FeatureSequence
(TokenSequenceRemoveStopwords //移除停用词
(TokenSequenceLowercase //将所有的字母转化为小写
(CharSequence2TokenSequence
(CharSubsequence //移除UseNet或email header
(Input2CharSequence
(Target2Label()))))))))
3、pipedInstanceIterator.next()
也就是ret.next()的调用了,由上面的公式可以理解,这就是个由内向外的递归调用。每个Pipe负责自己的职责,直至将文档转化为所需要的数据模型Instance。
训练过程
C45Trainer::trainer
root = new C45.Node(); //建立节点
→GainRatio.createGainRatio();
calcGainRatios(); //计算信息增益率。
splitTree(root, 0);
→node.split();
C45 tree = new C45 (trainingList.getPipe(), root);
tree.prune();
→getRoot().computeCostAndPrune();
root.stopGrowth();
训练的过程就是建树的过程:首先创建根节点,计算该节点的信息增益率以及在该节点下的可能的分割的信息增益率。然后对根节点进行分割,分别创建左右子节点,然后递归的进行分割,直至满足停止的三个条件: 1、树高达到上限2、每个节点的熵太小3、最优分割的增益率太小。为避免决策树的过度拟合问题,使用最小描述长度对其进行剪枝。至此建树工作完成,调用stopGrowth()函数释放部分内存。
1、calcGainRatios(ilist, instIndices, minNumInsts);
计算信息增益率与相应的分割点的过程
ilist为包含训练数据的InstanceList。instIndices为属于该节点的所有Instance的索引。MinNumInsts为分割后任一节点包含的Instance的最小数量。
calcGainRatios首先,统计节点下的每个label的数量;然后计算节点的base entropy Info(D),即分割前的信息熵。接下来,
下面是计算每个属性的分割点与其对应的信息增益率的主要代码:
//每个属性的数据存储到hashtable
//key:(split point) value:(info gain, split ratio)
Hashtable[] featureToInfo = new Hashtable[dataDict.size()];
//升序遍历所有属性的分隔点。
for (int fi = 0; fi < dataDict.size(); fi++) {
//遍历已序的Instance
for (int ii = 0; ii < numInsts-1; ii++) {
//在(feature, split-point)上计算Gain(D,T)
gainDT = baseEntropy - passProportion * passEntropy
- (1-passProportion) * failEntropy;
//计算Split(D,T),分割信息
splitDT =
- passProportion * Math.log(passProportion) / log2
- passProportion) * Math.log(1-passProportion) /log2;
//计算信息增益率
gainRatio = gainDT / splitDT;
//将数据放入对应属性的hashtable中。
featureToInfo[fi].put(new Double(splitPoint), new Point2D.Double( gainDT, gainRatio ));
} // End loop through sorted instances
} // End loop through features
最后,使用信息增益作为决断变量,获取每个属性的最大增益率(该店的平均增益大于所有分割分割点的平均增益)和与之关联的分割点。最后返回
new Object[] {gainRatios, splitPoints, new Double(baseEntropy),
baseLabelDistribution, new Integer(numSplitsForBestFeature)};
gainRatios:位置i处的元素为属性i分割的最大增益率
splitPoints:取得上述值的分割点
baseEntropy:该节点的熵
baseLabelDistribution:该节点的类别分布(即每个类别百分比的情况)
numSplitsForBestFeature:所有属性的增益率最大的点拥有的分割点个数。这个东东将在剪枝时用到。
2、node.split();
对节点进行分割的过程
遍历每一个Instance,取具有最大信息增益率的(feature, threshold)对的阈值,与Instance在该分割点的属性的值相比较。若后者小于前者,则将该Instance分配给树的左侧,否则分配到右侧。
3、computeCostAndPrune();
使用MDL对决策树进行剪枝
递归计算每个节点及其子节点的最小描述长度,如果左右节点的MDL值+costSplit的值几乎相等,则使用m_leftChild = m_rightChild = null;剪切掉子节点。其中
costSplit = Math.log(m_gainRatio.getNumSplitPointsForBestFeature()) / GainRatio.log2;
分类过程
分类过程相对简单,同分割过程类似,使用getLeaf函数递归求最终的叶节点,在递归过程中由属性值和该节点的阈值决定选择左子树还是右子树进行递归(同node.split())。叶节点,也就是想求的结果了。
转载请注明原载地址:http://blog.csdn.net/xinhanggebuguake/article/details/8728875