2025年机器学习实战随机森林算法深度解析与应用_第1页
2025年机器学习实战随机森林算法深度解析与应用_第2页
2025年机器学习实战随机森林算法深度解析与应用_第3页
2025年机器学习实战随机森林算法深度解析与应用_第4页
2025年机器学习实战随机森林算法深度解析与应用_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

随机森林试验汇报

试验目的

实现随机森林模型并测试。

试验问题

Kaggle第二次作业Non-linearclassification

算法分析与设计

一.算法设计背景:

1.随机森林的原子分类器一般使用决策树,决策树又分为拟合树和分类树。这两者的区别在于代

价估值函数的不一样。

2.根据经验,用拟合树做分类的效果比分类树略好。

3.对•于一种N分类问题,它总是可以被分解为N个2分类问题,这样分解的好处是其决策树愈加

以便构造,愈加简朴,且愈加有助于用拟合树来构建分类树。对于每一种2分类问题,构造的树又叫

CART树,它是一颗二叉树。

4.将N个2分类树的成果进行汇总即可以得到多分类的成果。

5.CART树构造:

输入:训练数据集D,停止训练条件

输出:CART决策树

根据训练数据集,从根节点开始,递归地对每个节点进行以下操作,递归构造二叉决策树

(1)设节点的训练数据集为D.计算现有特征对该数据集的基尼系数,此时,对每一个特征

A,对于每一个可能的划分值a,根据大于a及小于等于a将训练数据集分成D1,D2两

部分。计律gini系数公式如下:

Gin、wx…^*Gini(Dl)+号*Gini(D2);

Gini(D)=1-ZLiPi

(2)在所有可能特征A以及其所有可能划分点a中,选择gini系数最小的特征及切分值,将D1

和D2分配到左右两个子节点去。

(3)对左右节点分别递归的调用(1),(2),直到满足一定条件为止。

6.随机森林构造:

(1)随机取样,降低树与树之间的关联性

RF在每次构造一•棵决策树时,先随机的有放回的从训练集中抽取(60%)或n(sizeof

trainingset)个样本D.在每个节点分裂前,先随机从总共M个特征中选取m个(m

«M,一般取根号M)作为分裂用的候选特征,这有效降低了树与树之间的关联性。

二.算法思绪:

将一种N分类问题转化为N个二分类问题。转化措施是:构造N棵二叉拟合树,这里假设N为

26,然后我们给N棵二叉树依次标号为1,2,3...26。I号树的成果对应于该条记录是不是属于第一

类,是则输出L否则输出0.2号树的成果对应于该条记录是不是属干笫二类,是则I否则0.依此

类推。这样,我们的26棵二叉械的成果就对应了26个下标。

例如对于某条记录,这26个二叉树的成果按序号排列为{0,0,0,0-0,0,0,0,0,0,0,0,0,

0,0,…1,()),那么这条记录的分类应当为25。要将一种26维的0,1序列变回

一种索引,我们只需要找出这个序列中值最大的元素的索引,这个索引即是序列号。

我们将上面的26棵分别对26个索引做与否判断的二分类树视为一种整体,在多线程的环境下,

构造多种这样的整体,然后进行求和运算,最终取出每个成果序列中值最大的元素的下标作为分类值,

那么久得到了我们想要的成果,懒机森林完毕。

三.算法流程:

I.读入训练集trainset,测试集testset

2.将训练集分割为输入trainin,输出trainOut

3.这里假设类别数N为26,将trainOut[记录条数]映射为transformT:ainOut|训练记录数][26]

4.初始化transformTestOul[测试记录数][26]所有为0

5.Fori=1:ForestSize:

〃对训练集采样,这里要注意输入和输出一致

Isampleln.transformSanipleOut]=TakeSample(trainln.transformTrainOut)

Forcategory=1:26:

//CartTree数组寄存着26棵二分类树

CartTree[category]=TrainCartTree(sanipleIn,transforniSampleOut);

end

“iransformTestOuU测试记录数][26]为承接二分类树输出的容器

fbril=I:testSetNum:

Forcategory=1:26:

transform'Icsl()ut|iIJlcatcgory]+=prcdict(CartTrcclca(egory],lcslsct(i1J)

end

End

End

6.遍历iransformTrainOull],将其每一行的最大值的下标作为该行记录的索引值。

四.决策树及随机森林的配置

L决策树

在这里,我们每一次26分类是由26棵CART共同完毕的,CART的costfunction采用的是gini

系数,CART的最大层数为7,分裂停止条件为目前节点GINI为。或者目前节点所在层数抵达了7.

2.随机森林

a.随机森林每次循环的训练建采样为原训练集的05

b.对于森林中每一棵决策树每一次分割点的选用,对属性进行了打乱抽样,抽样数为25,即每次

分割只在25个属性中寻找最合适的值。并且对于每个选用的属性,我们进行了行采样。即假如这个

属性所拥有的属性值数不小于30,我们选用其中30个作为分割候选,假如不不小于■30,则所有纳入

分割候选。

五.代码详解

I.训练集/测试集的读入

a.在dataDefine.h中定义了:

/definenumparametres619//619中的字®

*definetrainsetNum6239//100〃晒绥的记录条数

♦definetestsetNum1560//100数

"definetypesNum26//26

,definenodesNum200//200/网的总结艇fe

训练集记录列数numparametres(ID(I)+参数数量(617)+输出(1)=619)

训练集记录条数transetNum

测试集记录条数testsetNum

分类类型数lypesNum

而在main.cpp中,我们申明了全局变量

doubletrainIn(trainsetNum][numparametres-2];〃训练集输入

doubletrainOut|:rainsetNum][l];//训您集输出

।doubletestin[testsetNurr)[numparemetres-2];|〃…

IetestOut[:

)inttrainID[trainsetNum];

.inttestIDhestsetNum];〃测试集每一行记录的ID

trainin用于装载训练集输入,IrainOul用于装载训练集的输出(这里IrainOut是二维数组是出于模型

假如泛化,那么输出值不一定只有一种的状况,在本次试验中并未派上什么真正用场,可以将trainOut

看作一种一般一维数组)。irainlD用于装载训练集中每一行的第一列【D号。【esUn.tesiID则对应测试

集的输入和ID号。这里注意,没有icsiOul的原因是测试集的成果理论上应当是不存在的。

然后通过自己编写的读入函数

readData('train.csv","test.csv");

读入测试集合训练集,这个函数珞分别装载我们在前面提到的irainln、【rainOut、irainlD、

testin.testiDn这个函数使用的fstrcam逐行读入的措施,这里不做详述。

2.训练集输出转化为对应的26维01数组transformOul[typesNumJ

在dalaDefine.li中,我们定义了分类类别数typesNuin:

二5

在main.cpp中,我们定义了全局变量iransfonnOut[typesNum]

inttransformOut[trainsetNum][typesNum+1]={0};//类别数

这里的transformOut是用于储存将trainOut每行的值映射为•行对应的26维01序列后所

产生的成果。

这里面的对应关系是:例如trainOutUOJ中的值是13那么transformOut[10][13]=l.transfbrmOut[10][^

13外其他列]=0;假如值是14,那么14列为1,其他列为0,行号代表的是它们对应的是第几条记

录;irainOut[10]和lran$formOul[】OJ都表达的是第10行的分类值为某个值,只是体现方式不一样。

前者用数字表达,后者将对应下标的值置.1表达。

转换接口由main.cpp中的函数

voidindexTransform(inttransformresU[typesNum+1J,doubleogres川1J);//Wfilfe,

定义,它的输入参数依次为转换输出的承接容器transformres,盛放原始输出的容器orges()

它所做的事情是将transformres[i][orges[i)]的值置1

3.并行构建随机森林

在main.cpp中,我们构建了

doubletrainInPerTime[perTimeNum][numparametres-2];

doubletrainInPerTimeTl(perTimeNum][numparametres-2];

doubletrainInPerTimeT2lperTimeNum]lnumparametres-2j;

doubletrainInPerTimeT3[perTimeNum][numparametres-2];

doubletrainInPerTimeT4[perTimeNum][numparametres-2];

inttransformOutPerTime[perTimeNum][typesNum+1]={0};

inttransformOutPerTimeTl[perTimeNum][typesNum+1]={0};

inttransformOutPerTimeT2[perTimeNum][typesNum+1]={0};

inttransformOutPerTimeT3[perTimeNum][typesNum+1]={0};

inttransformOutPerTimeT4[perTimeNum][typesNum+1]={0};

doubletransformTestOutTl[testsetNum][typesNum+1]={0

doubletransformTestOutT2[testsetNumHtypesNum+1]={0};

doubletransformTestOutTS[testsetNum][typesNum+1]={0};

doubletransformTestOutT4[testsetNum][typesNum+1]={0};

trainInpeffime代表的是随机森林算法中通过采样环节后选用的训练输入,

TransformOutPeiTime代表的是与trainlnperTime对应的转换输出

transformte.stOut是承接本支线程的所有CART树的决策值之和的构造,这与算法思绪是对应的,我们

将所有CART树的预测成果在意个转换输出容器上累加,然后对于每行取该行最大列的下标,即可得

到由随机森林得到的分类成果。

我们可以看出,这几种变量都是只有最终的TX有区别,实际上,反豆的创立相似的变量只是为了以

便多线程操作不会冲突。

多线程入口:

decisionTree&tasd=Treel;

threadthreadl(mainInThread,transformOutPerTimeTl,trainlnPerTimeTl,transformTestOutTl,&Treel);

threadthread2(mainInThread,transformOutPerTimeT2,trainInPerTimeT2,transformTestOutT2,&Tree2);

threadthread3(mainInThread,transformOutPerTimeT3,trainInPerTimeT3,transformTestOutT3,&Tree3);

threadthread4(mainInThread,transformOutPerTimeT4,trainInPerTimeT4,transformTestOutT4,&Tree4);

这里使用的是C++I1的<lhread>庠,简朴好用

每一种线程的随机森林框架定义在inain.cpp的

这个函数采用循环的方式,每次循环,对训练集及对应转换输出进行打乱后采样,然后输入

TRAIN(lrdinInPerTiine_,trdnbformOutPerTirne^trdnbforfnTet>tOut_,Tree),

中进行一轮决策树的训练,这一轮训练将会生成26棵CART树,对应26个分类值。这里输入的参数

Tree就是我们所用的决策树容器,这里注意,我们一种线程中只需要公用一种决策树构造即足够了.

transformTestOut[i][j]

在训练完毕后,我们用I累加训练成果。

4.一轮训练26棵树

由于26棵CART树才能完整的等价于一棵26分类树,因此我们将构建这26棵CART树的过程当作

是一种整体。这个过程由函数

TRAIN(trainInPerTime_,transformOutPerTime_,transformTestOut_,Tree);

实现。它的输入依次是本轮的训练输入(通过/下采样,随机森林规定的),对应的转换训练输出,

以及一种决策树容器Tree。决策树的定义我们将在下文中描述。

这个函数有一种栈

stack<int>trace;〃用于追踪树的遍历过程,这里我们假定用的是先根遍历;

并且有一种从1:26的循环

for(inttypesN=1;typesN<=typesNum;typesN++){

每次循环会建立一棵有关对应的分类值得CART树,CART树的构造是由栈trace维护的,irace维护

的是一种先序的遍历次序。

当循环完毕后,将会计算本轮的转换输出成果的变更:

for(inti=0;i<testsetNum;++i){

//testresPerTime[i][typesN]=TputeRes(testIn[i]);

transformTestOut[i][typesN]+=(*Tree).computeRes(testIn[i]);

)

}

5.每科CART树的构造

CART树的数据构造如下:

structdecisionTree{

doubletrainIn[perTimeNum][numparametres-2J;

inttrainOutfperTimeNumJ;

nodeNodes(nodesNum);

建捌时离要使用的可用节点索引记录

intusableNode;

trainintrainOut对应于输入该树的输入输出集,Nodes表达的是节点序列,在这里我们的树的构造使

用的是数组,且树的节点间的索引处通过索引值维护的,这颗树非常紧密(假如只看NODES是看不

出节点间的层级关系的〉。

它有如下组员函数:

decisionTree(){);

voidsetDeci$ionTree(doubletrainln_Mnumpa-ametres2],inttrainOut_[|);

boolgetPartition(intindex,intcontainer!]);〃用于做恻的节点分割

doublecomputeGini(intindex,intlabel,doublevalue);

//doublecomputeNodeGini(intindex);〃计算臬一节^09Gini

doublecomputeRes(double[numpar3metres-2J);//ill随完成后,用于痴8的函数,喻入JBfl僦输入向・

voidgetNodesSequence(nodel[]);

/倒始化树

voidinitialize(nodeele);

〃对于每个节点的一些操作

voidgetNodeAttr(vector<int>selectedCols,intindex);

voidcomputePerNodeGini(intindex);

voidcomputeNodeValueiintindex);

};

setDecisionTree用于给trainin和trainOut赋值

gctNodcScqucncc(nodcI[])本来足用来输出节点参数的,这里不做详述

initialize用于■初始化决策树。

getNodeAttr用于得到某一节点的备选属性分割值

computePerNodeGini用于计算某一节点的GINI值,这在停止节点分割时有用

computcNodcValuc是用于计算某一叶子节点的拟合值的。

我们再说一下Nodes节点,它的沟造如下

vector<int>datalndex;4点

〃用于记录该节点的分割点

vector<double>attributes[SelectedColumns];

intleftChild,rightChild;〃子节点的Index

intisLeaf;//0表示内部节点,1袤示外部节点

intsplitLabel;〃记录当前的分割属性的回B

doublelabelvalue;/感节点分割值

doublevalue;〃该节点的平均值,用于泣的的运算

intlayer;〃用于记录所在层数

doublegini;〃当前节点的GINI值

Aiirbutes[selectedColumn$]是用于寄存候选的分割值的容器

其他变量的功能见图片中的文字注释

这里我们用datalndex寄存对应记录所在索引的措施取代了直接寄存记录,这里是一种巨大的改善,

将程序的执行速度提高了至少10倍。

在构造一棵决策树时,当Tain函数对应的trace栈的栈顶非空时,我们会不停的取出栈顶元素,对其

进行

boolgetPartition(intindex,intcontainer1]);〃用升姗断5点分割

操作,Index指的是节点所在的索引值,container用于寄存这个节点E勺左右叶子索引,由于树的构建

是由外部栈维护的,因此这个container是必不可少的,在目前节点分割完毕后,我们会将这个节点

的索引值出栈,假如container[0]的值不是-I,我们会将container[0]<ontainer[l]入栈。建树的对应模

块在inain.cpp卜.的train函数中的

trace.pusn(O);

〃树的初始化(插入头节点)完成后,正式开始决策树的构建

while(!trace.emptyO){

intcurrent_node=trace.topO;

trace.popQ;

intcontainer[2]={0};

(*Tree).getPartition(current_node,container);

〃判断当前节点是否成功分割"

if(container!。]!=-1){

trace.push(container[l]);

trace.push(container[0]);

)

)

〃训练完成,计算输出

下面再重点说•下函数:

booldecisionTree::getPartition(intindex,intcontainer[2]){

这个函数是单棵决策树构造的关键,调用这个函数,假如目前节点的Gini值己经为0,那么这个函数

会计算目前节点的拟合值:

if(Nodes(inde:<].gini~~0||Node$[index].layer>=MaxLayerNum/41|/aNodes(index].dataSet.sizeO<10*/)(

Nodes(index)isLeaf=1;

〃计算叶子节点的拟合伯

doublesum=0;

for(inti0;i<Nodeslndex].datalndex.size();♦♦讥

sum♦=trainOut[Nodeslndex].d3ta!ndex[i|];//Nodesndex]res(i]-

}

Nodes(index].value=sum/Nodes(ndexl.datalndex.sizeO;

container!。]=containerfl]=-1;

returnfalse;

结束条件是gini==0||层数等于1()

假如目前节点不满足结束分割条件,那么函数将对属性进行抽样,抽样的措施是打乱后取前

selectedColumns列。然后调用geiNodeAilr(s,index)获取目前节点的备选分割值,这里的s是抽取的属

性的列号的集合。

/献御用后的索引,并选取前面的25个作为选取的列的索引,传递给getAttr,获得所需要的分割伯

vector<pairl>sequence(lumparametres-2);

srand((unsigned)time(NULL));

for(i=0;i<sequence.sizeQ;++i)(

sequence[i|.index-i;

sequence[i].value=randQ;

}

std::sort(sequencebeginO,sequence.end(),cmp);

vector<int>s(SelectedColumns);

for(inti=0;i<SelectedColumns;++i){

s[i]=sequence[i].index;

}

在得到备选的属性分割值后,将进入循环,寻找最优分割点

for(i=0;i<SelectedColumns;+*i)(

vector<double>::iteratorcursor

for(cursor=Nodes(index].attributes[i].beginO;cursor!=Nodes[indexJ.attributes[i].end();++cursor){

temp_gini=computeGini(index,s[i],*cursor);

if(min_gini>temp_gini){

min_gini=temp_gini;

parjabel=s[i];

par_value=*cursor;

}

)

}

6.最终止果计算

在main函数中,我们将四个线程所得的UansformOutT相加,最终遍历取每-行最大值的下标,即可

得到最终止果。

六.算法优化

1.应用了数组+栈建树取代了一般的函数递归建树,加紧了建树速度。

2.在传递每个节点的节点数据集时,使用了传递数据集的索引而非数据自身,这样做的好处是,本来

假如传递一条数据需要第制617个double类型的数量,而FI前只需要传递一种Int型的索引,这种快

了617倍的数据集传递方式使程序运行效率提高了10倍以上。

3.在每个属性中选择备选分割值的时候,采用了一种下采样的方略.即:假如该节点的数据集大小不

不小于某一数值,则将这个数据集的这个属性的所有•值都纳入候选分别值列表。不过假如不小于/这

个阈值,则将屈性所对应的列进行排序后再进行等间距采样得到样本数等于阈值的子集作为候选分割

集。代码详见gclPartitionO.这样做的好处是需要计算的分割gini值大大减少了(本人取的采样阈值时

100,相比原数据集,样本空间缩小了尽30倍),这里也再一次加速了程序运行。不过这个优化随机

而来的一种问题是:有也许每次分割都不是最佳分割。

4.使用了C++I1的〈thread〉库进行了并行实现,开出4个线程,程序相比单线程加速了4倍。

七.并行实现

C++ll<lhread>库创立线程,为每个线程赋予独立的数据容器,并将随机森林提成等量的4部分(由

于我使用的是4个线程)。即,每个线程中执行的函数承担1/4规模的随机森林的构造,实现代码如

下:

threadthreadl(mainInThread,transformOutPerTimeTl,trainlnPerTimeTl,transformTestOutTl.&Treel);

threatthread2(mainInThread,transformOutPerTimeT2,trainlnPerTimeT2,transformTestOutT2,&Tree2);

threa;thread3(mainInThread,transformOutPerTimeT3,trainInPerTimeT3,transformTestOutT3,&Tree3);

threadthread4(mainInThread,transformOutPerTimeT4,trainInPerTimeT4,transformTes

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论