Weka算法Classifier-trees-RandomTree源码分析

                               Weka算法Classifier-trees-RandomTree源码分析


一、RandomTree算法

在网上搜了一下,并没有找到RandomTree的严格意义上的算法描述,因此我觉得RandomTree充其量只是一种构建树的思路,和普通决策树相比,RandomTree会随机的选择若干属性来进行构建而不是选取所有的属性。

Weka在实现上,对于随机属性的选取、生成分裂点的过程是这样的:

1、设置一个要选取的属性的数量K

2、在全域属性中无放回的对属性进行抽样

3、算出该属性的信息增益(注意不是信息增益率)

4、重复K次,选出信息增益最大的当分裂节点。

5、构建该节点的孩子子树。


二、具体代码实现

(1)buildClassifier

[java]  view plain  copy
  1. public void buildClassifier(Instances data) throws Exception {  
  2.   
  3.    // 如果传入的K不合理,把K放到一个合理的范围里  
  4.    if (m_KValue > data.numAttributes() - 1)  
  5.      m_KValue = data.numAttributes() - 1;  
  6.    if (m_KValue < 1)  
  7.      m_KValue = (int) Utils.log2(data.numAttributes()) + 1;//这个是K的默认值  
  8.   
  9.    // 判断一下该分类器是否有能力处理这个数据集,如果没能力直接就在testWithFail里抛异常退出了  
  10.    getCapabilities().testWithFail(data);  
  11.   
  12.    // 删除掉missClass  
  13.    data = new Instances(data);  
  14.    data.deleteWithMissingClass();  
  15.   
  16.    // 如果只有一列,就build一个ZeroR模型,之后就结束了。ZeroR模型分类是这样的:如果是连续型,总是返回期望,如果离散型,总是返回训练集中出现最多的那个  
  17.    if (data.numAttributes() == 1) {  
  18.      System.err  
  19.          .println("Cannot build model (only class attribute present in data!), "  
  20.              + "using ZeroR model instead!");  
  21.      m_zeroR = new weka.classifiers.rules.ZeroR();  
  22.      m_zeroR.buildClassifier(data);  
  23.      return;  
  24.    } else {  
  25.      m_zeroR = null;  
  26.    }  
  27.   
  28.    // 如果m_NumFlods大于0,则会把数据集分为两部分,一部分用于train,一部分用于test,也就是backfit  
[java]  view plain  copy
  1.   //分的方式和多折交叉验证是一样的,例如m_NumFlods是10的话,则train占90%,backfit占10%  
  2.   Instances train = null;  
  3.   Instances backfit = null;  
  4.   Random rand = data.getRandomNumberGenerator(m_randomSeed);  
  5.   if (m_NumFolds <= 0) {  
  6.     train = data;  
  7.   } else {  
  8.     data.randomize(rand);  
  9.     data.stratify(m_NumFolds);  
  10.     train = data.trainCV(m_NumFolds, 1, rand);  
  11.     backfit = data.testCV(m_NumFolds, 1);  
  12.   }  
  13.   
  14.   // 生成所有的可选属性  
  15.   int[] attIndicesWindow = new int[data.numAttributes() - 1];  
  16.   int j = 0;  
  17.   for (int i = 0; i < attIndicesWindow.length; i++) {  
  18.     if (j == data.classIndex())  
  19.       j++; // 忽略掉classIndex  
  20.     attIndicesWindow[i] = j++;//这段代码有点奇怪,i和j是相等的,为啥不用attIndicesWindow=i?  
  21.   }  
  22.   
  23.   // 算出每个class的频率,也就是每个分类出现的次数(更正确的说法应该是权重,但权重默认都是1)  
  24.   double[] classProbs = new double[train.numClasses()];  
  25.   for (int i = 0; i < train.numInstances(); i++) {  
  26.     Instance inst = train.instance(i);  
  27.     classProbs[(int) inst.classValue()] += inst.weight();  
  28.   }  
  29.   
  30.   // Build tree  
  31.   m_Tree = new Tree();  
  32.   m_Info = new Instances(data, 0);  
  33.   m_Tree.buildTree(train, classProbs, attIndicesWindow, rand, 0);//调用tree的build方法,在后面单独分析  
  34.   
  35.   // Backfit if required  
  36.   if (backfit != null) {  
  37.     m_Tree.backfitData(backfit);//在后面单独分析  
  38.   }  
  39. }  

这个Tree对象是RandomTree的一个子类,之前我还以为会复用其余的决策树模型(比如J48),但weka没这么做,很惊奇的是RandomTree和J48的作者还是同一个,不知道为啥这么设计。


(2)tree.buildTree

[java]  view plain  copy
  1. protected void buildTree(Instances data, double[] classProbs,  
  2.        int[] attIndicesWindow, Random random, int depth) throws Exception {  
  3.   
  4.      //首先判断一下是否有instance,如果没有的话直接就返回  
  5.      if (data.numInstances() == 0) {  
  6.        m_Attribute = -1;  
  7.        m_ClassDistribution = null;  
  8.        m_Prop = null;  
  9.        return;  
  10.      }  
  11.   
  12.      m_ClassDistribution = classProbs.clone();  
  13.   
  14.      if (Utils.sum(m_ClassDistribution) < 2 * m_MinNum  
  15.          || Utils.eq(m_ClassDistribution[Utils.maxIndex(m_ClassDistribution)],  
  16.              Utils.sum(m_ClassDistribution))  
  17.          || ((getMaxDepth() > 0) && (depth >= getMaxDepth()))) {  
  18.        // 递归结束的条件有3个 1、instance数量小于2*m_Minnum  2、instance都已经在同一个类中 3、达到最大的深度  
[java]  view plain  copy
  1.  //前两个条件和j48的递归结束条件很相似,相关内容可参考我之前的几篇博客。  
  2.   m_Attribute = -1;  
  3.   m_Prop = null;  
  4.   return;  
  5. }  
  6.   
  7. double val = -Double.MAX_VALUE;  
  8. double split = -Double.MAX_VALUE;  
  9. double[][] bestDists = null;  
  10. double[] bestProps = null;  
  11. int bestIndex = 0;  
  12.   
  13. double[][] props = new double[1][0];  
  14. double[][][] dists = new double[1][0][0];//这个数组第一列只有下标为0的被用到,不知道为啥这么设计  
  15.   
  16. int attIndex = 0;//存储被选择到的属性  
  17. int windowSize = attIndicesWindow.length;//存储目前可选择的属性的数量  
  18. int k = m_KValue;//k代表还能选择的属性的数量  
  19. boolean gainFound = false;//是否发现了一个有信息增益的节点  
  20. while ((windowSize > 0) && (k-- > 0 || !gainFound)) {//此循环退出条件有2个 1、没有节点可以选了 2、已经选了k个属性了并且找到了一个有用的属性 换句话说,如果K次迭代没有找到可以分裂的随机节点,循环也会继续下去  
[java]  view plain  copy
  1.   int chosenIndex = random.nextInt(windowSize);//随机选一个,生成下标  
  2.   attIndex = attIndicesWindow[chosenIndex];//得到该属性的index  
  3.   
  4.  //下面三行把选择的属性放到attIndicesWindow的末尾,然后把windowSize-1这样下个循环就不会选到了,也就是实现了无放回的抽取  
  5.   attIndicesWindow[chosenIndex] = attIndicesWindow[windowSize - 1];  
  6.   attIndicesWindow[windowSize - 1] = attIndex;  
  7.   windowSize--;  
  8.   
  9.   double currSplit = distribution(props, dists, attIndex, data);//这个函数计算了在使用attIndex进行分裂所产生的分布,如果classIndex是连续值的话,还计算了分裂点,原理和J48的split一样,不在赘述。  
  10.   double currVal = gain(dists[0], priorVal(dists[0]));//这个计算了信息增益  
  11.   
  12.   if (Utils.gr(currVal, 0))  
  13.     gainFound = true;//如果信息增益大于0的话,说明节点有效,设置gainFound  
  14.   
  15.   if ((currVal > val) || ((currVal == val) && (attIndex < bestIndex))) {  
  16.     val = currVal; //如果信息增益大的话,则更新把attIndex更新为bestIndex,这是为了选取最优的节点(ID3)的方法  
  17.     bestIndex = attIndex;  
  18.     split = currSplit;  
  19.     bestProps = props[0];  
  20.     bestDists = dists[0];  
  21.   }  
  22. }  
  23.   
  24. m_Attribute = bestIndex;  
  25.   
  26. // Any useful split found?  
  27. if (Utils.gr(val, 0)) {  
  28.   
  29. pan style="white-space:pre"> </span>//如果找到了一个分裂点,则在该分裂点的基础上构建子树  
[java]  view plain  copy
  1.   m_SplitPoint = split;  
  2.   m_Prop = bestProps;  
  3.   Instances[] subsets = splitData(data);  
  4.   m_Successors = new Tree[bestDists.length];  
  5.   for (int i = 0; i < bestDists.length; i++) {  
  6.     m_Successors[i] = new Tree();  
  7.     m_Successors[i].buildTree(subsets[i], bestDists[i], attIndicesWindow,  
  8.         random, depth + 1);//注意这里传入的attIndicesWindow没有变,换句话说,每次迭代传入的可选属性集合是一样的,因此子节点在进行属性的random选择时,很有可能会选择到父节点已经选过的节点,但因为不产生信息增益,因此不会再次作为bestIndex,但会产生额外的计算量(我感觉还不少),这里还有一定的优化空间,同理j48也是这么实现的。  
  9.   }  
  10.   
  11.   boolean emptySuccessor = false;  
  12.   for (int i = 0; i < subsets.length; i++) {  
  13.     if (m_Successors[i].m_ClassDistribution == null) {  
  14.       emptySuccessor = true;  
  15.       break;  
  16.     }  
  17.   }  
  18.   if (!emptySuccessor) {  
  19.     m_ClassDistribution = null;  
  20.   }  
  21. else {  
  22.   
  23.  //这个else是<span style="font-family: Arial, Helvetica, sans-serif;">Utils.gr(currVal, 0)这个条件的,代表没有选择到合适的分裂节点</span>  
  24.   
  25.   m_Attribute = -1;  
  26. }  

(3)tree.backfit

什么是Backfit?Backfit将改变已有tree节点及其子节点的class分布,而class分布将直接被用于实例的预测。

直接使用RandomTree有时会出现过拟合的现象(通过代码可以看到,和J48相比没有剪枝过程),因此通过传入一个新的数据集来backfit已有节点是一个解决过拟合的方法。

[java]  view plain  copy
  1. protected void backfitData(Instances data, double[] classProbs)  
  2.       throws Exception {  
[java]  view plain  copy
  1. <span style="white-space:pre">    </span>//判断一下是否有数据  
  2.       if (data.numInstances() == 0) {  
  3.         m_Attribute = -1;  
  4.         m_ClassDistribution = null;  
  5.         m_Prop = null;  
  6.         return;  
  7.       }  
  8.   
  9.       m_ClassDistribution = classProbs.clone();  
  10.   
  11.       if (m_Attribute > -1) {  
  12.   
  13.         // m_Attribut>-1代表不是leaf,可以看上面的buildTree得出这个结论  
  14.         m_Prop = new double[m_Successors.length];//子节点数组的length也就是分类的类的数量  
[java]  view plain  copy
  1. <span style="white-space:pre">    </span>//把传入的data用此节点算各类的频率  
  2.         for (int i = 0; i < data.numInstances(); i++) {  
  3.           Instance inst = data.instance(i);  
  4.           if (!inst.isMissing(m_Attribute)) {  
  5.             if (data.attribute(m_Attribute).isNominal()) {  
  6.               m_Prop[(int) inst.value(m_Attribute)] += inst.weight();  
  7.             } else {  
  8.               m_Prop[(inst.value(m_Attribute) < m_SplitPoint) ? 0 : 1] += inst  
  9.                   .weight();//连续型只会分两类,小于splitPoint一类,大于是一类,和J48采用的策略相同  
  10.             }  
  11.           }  
  12.         }  
  13.   
  14.         if (Utils.sum(m_Prop) <= 0) {  
  15.           m_Attribute = -1;//如果data全部都是missingValue,则把此节点变成leaf节点  
  16.           m_Prop = null;  
  17.           return;  
  18.         }  
  19.   
  20.         // 归一化  
  21.         Utils.normalize(m_Prop);  
  22.   
  23.         // 根据本节点算出在data上进行分类的subset  
  24.         Instances[] subsets = splitData(data);  
  25.   
  26.         for (int i = 0; i < subsets.length; i++) {  
  27.   
  28.           // 递归的对孩子节点进行backfit  
  29.           double[] dist = new double[data.numClasses()];  
  30.           for (int j = 0; j < subsets[i].numInstances(); j++) {  
  31.             dist[(int) subsets[i].instance(j).classValue()] += subsets[i]  
  32.                 .instance(j).weight();  
  33.           }  
  34.   
  35.           m_Successors[i].backfitData(subsets[i], dist);  
  36.         }  
  37.   
  38. <span style="white-space:pre">    </span>  
  39.         if (getAllowUnclassifiedInstances()) {  
  40.           m_ClassDistribution = null;  
  41.           return;  
  42.         }  
  43. <span style="white-space:pre">    </span>//如果某个子节点的分布为空的话,则父节点要保存分布,否则不需要持有分布。  
[java]  view plain  copy
  1. <span style="white-space:pre">    </span>//为什么呢?因为使用RandomTree进行预测时会遍历节点的分布并进行累加,得到分布最大的class作为预测class,在J48的那篇博客中有分析  
  2.        boolean emptySuccessor = false;  
  3.        for (int i = 0; i < subsets.length; i++) {  
  4.          if (m_Successors[i].m_ClassDistribution == null) {  
  5.            emptySuccessor = true;  
  6.            return;  
  7.          }  
  8.        }  
  9.        m_ClassDistribution = null;  
  10.      }  

  1.    }  


  2. 三、总结

    对RandomForest的分析到这里就结束了,首先分析了RandomForest,接着分析了Bagging,最后分析了RandomTree。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在现有省、市港口信息化系统进行有效整合基础上,借鉴新 一代的感知-传输-应用技术体系,实现对码头、船舶、货物、重 大危险源、危险货物装卸过程、航管航运等管理要素的全面感知、 有效传输和按需定制服务,为行政管理人员和相关单位及人员提 供高效的管理辅助,并为公众提供便捷、实时的水运信息服务。 建立信息整合、交换和共享机制,建立健全信息化管理支撑 体系,以及相关标准规范和安全保障体系;按照“绿色循环低碳” 交通的要求,搭建高效、弹性、高可扩展性的基于虚拟技术的信 息基础设施,支撑信息平台低成本运行,实现电子政务建设和服务模式的转变。 实现以感知港口、感知船舶、感知货物为手段,以港航智能 分析、科学决策、高效服务为目的和核心理念,构建“智慧港口”的发展体系。 结合“智慧港口”相关业务工作特点及信息化现状的实际情况,本项目具体建设目标为: 一张图(即GIS 地理信息服务平台) 在建设岸线、港口、港区、码头、泊位等港口主要基础资源图层上,建设GIS 地理信息服务平台,在此基础上依次接入和叠加规划建设、经营、安全、航管等相关业务应用专题数据,并叠 加动态数据,如 AIS/GPS/移动平台数据,逐步建成航运管理处 "一张图"。系统支持扩展框架,方便未来更多应用资源的逐步整合。 现场执法监管系统 基于港口(航管)执法基地建设规划,依托统一的执法区域 管理和数字化监控平台,通过加强对辖区内的监控,结合移动平 台,形成完整的多维路径和信息追踪,真正做到问题能发现、事态能控制、突发问题能解决。 运行监测和辅助决策系统 对区域港口与航运业务日常所需填报及监测的数据经过科 学归纳及分析,采用统一平台,消除重复的填报数据,进行企业 输入和自动录入,并进行系统智能判断,避免填入错误的数据, 输入的数据经过智能组合,自动生成各业务部门所需的数据报 表,包括字段、格式,都可以根据需要进行定制,同时满足扩展 性需要,当有新的业务监测数据表需要产生时,系统将分析新的 需求,将所需字段融合进入日常监测和决策辅助平台的统一平台中,并生成新的所需业务数据监测及决策表。 综合指挥调度系统 建设以港航应急指挥中心为枢纽,以各级管理部门和经营港 口企业为节点,快速调度、信息共享的通信网络,满足应急处置中所需要的信息采集、指挥调度和过程监控等通信保障任务。 设计思路 根据项目的建设目标和“智慧港口”信息化平台的总体框架、 设计思路、建设内容及保障措施,围绕业务协同、信息共享,充 分考虑各航运(港政)管理处内部管理的需求,平台采用“全面 整合、重点补充、突出共享、逐步完善”策略,加强重点区域或 运输通道交通基础设施、运载装备、运行环境的监测监控,完善 运行协调、应急处置通信手段,促进跨区域、跨部门信息共享和业务协同。 以“统筹协调、综合监管”为目标,以提供综合、动态、实 时、准确、实用的安全畅通和应急数据共享为核心,围绕“保畅通、抓安全、促应急"等实际需求来建设智慧港口信息化平台。 系统充分整合和利用航运管理处现有相关信息资源,以地理 信息技术、网络视频技术、互联网技术、移动通信技术、云计算 技术为支撑,结合航运管理处专网与行业数据交换平台,构建航 运管理处与各部门之间智慧、畅通、安全、高效、绿色低碳的智 慧港口信息化平台。 系统充分考虑航运管理处安全法规及安全职责今后的变化 与发展趋势,应用目前主流的、成熟的应用技术,内联外引,优势互补,使系统建设具备良好的开放性、扩展性、可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值