trainIn trainOut对应于输入该树的输入输出集,Nodes表示的是节点序列,在这里我们的树的构造使用的是数组,且树的节点间的索引是通过索引值维护的,这颗树非常紧密(如果只看NODES是看不出节点间的层级关系的)。 它有如下成员函数:
setDecisionTree用于给trainIn 和 trainOut 赋值
getNodeSequence(node1[])本来是用来输出节点参数的,这里不做详述 initialize用于初始化决策树。
getNodeAttr用于得到某一节点的备选属性分割值
computePerNodeGini用于计算某一节点的GINI值,这在停止节点分割时有用 computeNodeValue是用于计算某一叶子节点的拟合值的。
我们再说一下Nodes节点,它的结构如下
Attrbutes[selectedColumns]是用于存放候选的分割值的容器 其余变量的功能见图片中的文字注释
这里我们用dataIndex存放对应记录所在索引的方法取代了直接存放记录,这里是一个巨大的改进,将程序的执行速度提高了至少10倍。
在构造一棵决策树时,当train函数对应的trace栈的栈顶非空时,我们会不断的取出栈顶元素,对其进行
操作,Index指的是节点所在的索引值,container用于存放这个节点的左右叶子索引,由于树的构建是由外部栈维护的,所以这个container是必不可少的,在当前节点分割完成后,我们会将这个节点的索引值出栈,如果container[0]的值不是-1,我们会将container[0],container[1]入栈。建树的对应模块在main.cpp下的train函数中的
下面再重点说一下函数:
这个函数是单棵决策树构造的核心,调用这个函数,如果当前节点的Gini值已经为0,那么这个函数会计算当前节点的拟合值:
结束条件是gini == 0 || 层数等于10 如果当前节点不满足结束分割条件,那么函数将对属性进行抽样,抽样的方法是打乱后取前selectedColumns 列。然后调用getNodeAttr(s,index)获取当前节点的备选分割值,这里的s是抽取的属性的列号的集合。
在得到备选的属性分割值后,将进入循环,寻找最优分割点
6.最终结果计算 在main函数中,我们将四个线程所得的transformOutT相加,最后遍历取每一行最大值的下标,即可得到最终结果。 六.算法优化
1.应用了数组+栈建树取代了普通的函数递归建树,加快了建树速度。
2.在传递每个节点的节点数据集时,使用了传递数据集的索引而非数据本身,这样做的好处是,原来如果传递一条数据需要复制617 个double类型的数量,而现在只需要传递一个Int型的索引,这种快了617倍的数据集传递方式使程序运行效率提高了10倍以上。
3.在每个属性中选择备选分割值的时候,采用了一种下采样的策略。即:如果该节点的数据集大小小于某一数值,则将这个数据集的这个属性的所有值都纳入候选分割值列表。但是如果大于了这个阈值,则将属性所对应的列进行排序后再进行等间距采样得到样本数等于阈值的子集作为候选分割集。代码详见getPartition().这样做的好处是需要计算的分割gini值大大减少了(本人取的采样阈值时100,相比原数据集,样本空间缩小了尽30倍),这里也再一次加速了程序运行。但是这个优化随机而来的一个问题是:有可能每次分割都不是最佳分割。 4.使用了C++11的
C++11
最后将4个线程得到的结果累加再做转换即可得到最终结果。
八.测试结果 树的数量 260 2600 26000 每轮Train样本 3129 3129 3129 决策树最大层数 7 7 7 分割备选属性数 200 200 200 每个属性采样数 100 100 100 运行时间 1.7min 17min 170min 准确率 0.9 0.943 九.并行效率对比
十.总结
本次实验,我们构造了决策树以及随机森林,构造基本模型我用了一天时间,但是构造出来的模型面临着执行速度很慢的瓶颈。其原因在于(1)没有对属性列进行采样(2)没有在选取每一列的时候对这一列的值进行采样(3)在构造决策树子节点的时候传递的是数据集而不是索引,这导致我的决策树虽然是正确的,但是几乎一分钟才能构造一棵CART,这样的CART要用来构造随机森林几乎是不可能的事情(时间上存在很大瓶颈),然后我逐一针对这些问题进行了优化,例如传递数据集采用的是索引,对属性及属性集采样等等。 另外,在构造决策树时,我发现虽然属性列很多,但往往取很少的列,很少的层,较少的分割节点一样能取到比较好的效果,这对于速度提升的方面是有良好的指导意义的。另外决策树本质上是做折线函数拟合,因此,过拟合是存在的,这就意味着决策树单棵如果层数过高效果将会非常不好,这一点也在KAGGLE的结果上体现了,过拟合仍然是随机森林面临的挑战。并且粗略的了解了一下C++的多线程,多线程可以充分调用起CPU资源,速度加快了很多,是以后实现算法时需要考虑的方向。