一、ClassifyInstance
首先先说一下构造好的分类树是如何对一个新的Instance进行区分。
直观上,会对树进行一个检索,从根节点根据属性的不同,最终走到叶子节点,得到具体的分类。
但Weka在实现上,是遍历了这个Instance属于不同的class的可能性,并从中选出了一个最大的,代码如下:
- public double classifyInstance(Instance instance)
- throws Exception {
- double maxProb = -1;
- double currentProb;
- int maxIndex = 0;
- int j;
- for (j = 0; j < instance.numClasses(); j++) {
- currentProb = getProbs(j, instance, 1);
- if (Utils.gr(currentProb,maxProb)) {
- maxIndex = j;
- maxProb = currentProb;
- }
- }
- return (double)maxIndex;
- }
- if (m_isLeaf) {
- return weight * localModel().classProb(classIndex, instance, -1);
- } else {
- int treeIndex = localModel().whichSubset(instance);
- if (treeIndex == -1) {
- double[] weights = localModel().weights(instance);
- for (int i = 0; i < m_sons.length; i++) {
- if (!son(i).m_isEmpty) {
- prob += son(i).getProbs(classIndex, instance,
- weights[i] * weight);
- }
在分类树中这种情况理论上是不会出现的,weka在这里其实是用treeIndex==-1来处理了相关属性缺失的情况。
whichSubset代码如下:
- public final int whichSubset(Instance instance)
- throws Exception {
- if (instance.isMissing(m_attIndex))
- return -1;
- else{
- if (instance.attribute(m_attIndex).isNominal())
- return (int)instance.value(m_attIndex);
- else
- if (Utils.smOrEq(instance.value(m_attIndex),m_splitPoint))
- return 0;
- else
- return 1;
- }
- }
二、总结
接下来可以回答第二篇博客开头时提到的4个问题了。
1、如何控制分类树的精度。
答:使用minNoObj参数来控制分类树的精度,节点停止分裂的条件有5个:
(1)所有的instances已经属于同一个分类(selectModel里)
(2)instances数量小于2*minNoObj(selectModel里)
(3)一个分裂产生的信息增益石0(selectModel里)
(4)对离散值进行分裂节点的计算时,超过一个的Bag里的instance数量小于minNoObj(spliter里)
(5)对连续值进行分裂计算时,有效instances数量小于2*minNoObj(spliter里)
2、如何处理缺失的值(MissingValue)
答:对缺失值得处理分两种,其一是训练阶段的缺失值处理,方法是直接忽略掉这条记录。在分类阶段对缺失值得处理是,忽略该属性,使其在不同的branch上进行计算,最后将概率结果进行合并。
3、如何对连续值进行离散化。
答:Spliter首先对连续值进行排序,之后从前到后遍历每一个值当做分裂点,对分裂结果计算信息增益率最大的作为最终分裂点构建不同的branch。
4、如何进行分类树的剪枝。
答:具体实现参见第二篇博客的collapse方法和prune方法。剪枝过程是一个单一的自底向上的遍历,一般有三种情况:第一种情况是保持树的原状;第二种情况是仅保留上一决策结点输出最大的那个;第三种情况就是将子树替换为叶节点,其类别选择训练集中的最大类别。这三种情况都需要分别估算对应的错误率。