草庐IT

随机森林的训练过程

TwcatL_tree 2023-03-28 原文
    随机森林顾名思义,是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一棵决策树之间是没有关联的。在得到森林之后,当有一个新的输入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本为那一类。


     在建立每一棵决策树的过程中,有两点需要注意 - 采样与完全分裂。首先是两个随机采样的过程,random forest对输入的数据要进行行、列的采样。对于行采样,采用有放回的方式,也就是在采样得到的样本集合中,可能有重复的样本。假设输入样本为N个,那么采样的样本也为N个。这样使得在训练的时候,每一棵树的输入样本都不是全部的样本,使得相对不容易出现over-fitting。然后进行列采样,从M 个feature中,选择m个(m << M)。推荐m的值为M的平方根。之后就是对采样之后的数据使用完全分裂的方式建立出决策树,这样决策树的某一个叶子节点要么是无法继续分裂的,要么里面的所有样本的都是指向的同一个分类。一般很多的决策树算法都一个重要的步骤 - 剪枝,但是这里不这样干,由于之前的两个随机采样的过程保证了随机性,所以就算不剪枝,也不会出现over-fitting。随机森林的核心是随机选取样本特征和随机选取样本,每次在选取的训练集上训练决策树。


       随机森林由决策树组成,决策树实际上是将空间用超平面进行划分的一种方法,每次分割的时候,都将当前的空间一分为二,比如说下面的决策树(其属性的值都是连续的实数):


随机深林的优点:比较适合做多分类问题;训练和预测速度快;对训练数据的容错能力,是一种有效地估计缺失数据的一种方法,当数据集中有大比例的数据缺失时仍然可以保持精度不变;能够有效地处理大的数据集;可以处理没有删减的成千上万的变量;能够在分类的过程中可以生成一个泛化误差的内部无偏估计;能够检测到特征之间的相互影响以及重要性程度;不过出现过度拟合;实现简单容易并行化。



下面是随机森林的构造过程:


1. 假如有N个样本,则有放回的随机选择N个样本(每次随机选择一个样本,然后返回继续选择)。这选择好了的N个样本用来训练一个决策树,作为决策树根节点处的样本。


2. 当每个样本有M个属性时,在决策树的每个节点需要分裂时,随机从这M个属性中选取出m个属性,满足条件m << M。然后从这m个属性中采用某种策略(比如说信息增益)来选择1个属性作为该节点的分裂属性。


3. 决策树形成过程中每个节点都要按照步骤2来分裂(很容易理解,如果下一次该节点选出来的那一个属性是刚刚其父节点分裂时用过的属性,则该节点已经达到了叶子节点,无须继续分裂了)。一直到不能够再分裂为止。注意整个决策树形成过程中没有进行剪枝。


4. 按照步骤1~3建立大量的决策树,这样就构成了随机森林了。


从上面的步骤可以看出,随机森林的随机性体现在每颗数的训练样本是随机的,树中每个节点的分类属性也是随机选择的。有了这2个随机的保证,随机森林就不会产生过拟合的现象了。


随机森林有2个参数需要人为控制,一个是森林中树的数量,一般建议取很大。另一个是m的大小,推荐m的值为M的均方根。



bool CvRTrees::train( const CvMat* _train_data, int _tflag,


                       const CvMat* _responses, const CvMat* _var_idx,


                       const CvMat* _sample_idx, const CvMat* _var_type,


                       const CvMat* _missing_mask, CvRTParams params )


{


   。。。。


  // Create mask of active variables at the tree nodes


   active_var_mask = cvCreateMat( 1, var_count, CV_8UC1 );


   if( params.calc_var_importance )


   {


       var_importance  = cvCreateMat( 1, var_count, CV_32FC1 );


       cvZero(var_importance);


   }


{ // initialize active variables mask


//active_var_mask保存是选取的哪些特征,选中的特征对应1,未选中的对应0


//active_var_mask初始化为前面的m个特征


//即将active_var_mask前m个值置为1,后面的值置为0


       CvMat submask1, submask2;


       CV_Assert( (active_var_mask->cols >= 1) && (params.nactive_vars > 0) && (params.nactive_vars <= active_var_mask->cols) );


       cvGetCols( active_var_mask, &submask1, 0, params.nactive_vars );


       cvSet( &submask1, cvScalar(1) );


       if( params.nactive_vars < active_var_mask->cols )


       {


           cvGetCols( active_var_mask, &submask2, params.nactive_vars, var_count );


           cvZero( &submask2 );


       }


   }



   return grow_forest( params.term_crit );


}




随机森林中决策树增长的终止条件:


1.训练得到的决策树的数目达到森林里树的最大数目。最大数目由term_crit.max_iter指定。(term_crit.type == CV_TERMCRIT_ITER)


2. OOB错误小于term_crit.epsilon时,树停止增长。


(term_crit.type != CV_TERMCRIT_ITER)



grow_forest函数的流程:


1.  随机生成创建决策树的训练样本标识,决定哪些样本参加决策树的训练


2.  选择的子样本进行训练,生成一颗决策树


3.  判断决策树的终止类型,如果决策树的类型不是数目限制,则计算OOB错误,并判断该错误是否小于指定的精度,小于则退出,不小于则继续


4.  将已有决策树的棵树加1,并判断其数目是否达到随机森林的最大数目,小于则返回1继续,不小于则退出。



bool CvRTrees::grow_forest( const CvTermCriteria term_crit )


{


。。。。。。


const int max_ntrees = term_crit.max_iter;//最多树的的个数


   trees = (CvForestTree**)cvAlloc( sizeof(trees[0])*max_ntrees );


   memset( trees, 0, sizeof(trees[0])*max_ntrees );



   sample_idx_mask_for_tree = cvCreateMat( 1, nsamples, CV_8UC1 );


   sample_idx_for_tree      = cvCreateMat( 1, nsamples, CV_32SC1 );



   ntrees = 0;


   while( ntrees < max_ntrees )


   {


       int i, oob_samples_count = 0;


       double ncorrect_responses = 0; // used for estimation of variable importance


       CvForestTree* tree = 0;



       cvZero( sample_idx_mask_for_tree );


       for(i = 0; i < nsamples; i++ ) //form sample for creation one tree


       {


//随机生成创建决策树的训练样本标识,决定哪些样本参加决策树的训练


           int idx = (*rng)(nsamples);


           sample_idx_for_tree->data.i[i] = idx;//创建决策树的训练样本标识


           sample_idx_mask_for_tree->data.ptr[idx] = 0xFF;


       }



       //trees是CvRTrees的成员变量,指向随机森林的所有树


       trees[ntrees] = new CvForestTree();      


tree = trees[ntrees];


       tree->train( data, sample_idx_for_tree, this );//训练随机森林中的一棵树



       if ( is_oob_or_vimportance )


       {


// 计算OOB错误率


           。。。。。。


       }


       ntrees++;


       if( term_crit.type != CV_TERMCRIT_ITER && oob_error < max_oob_err )


           break;


   }



   if( var_importance )


   {


       for ( int vi = 0; vi < var_importance->cols; vi++ )


               var_importance->data.fl[vi] = ( var_importance->data.fl[vi] > 0 ) ?                    var_importance->data.fl[vi] : 0;


       cvNormalize( var_importance, var_importance, 1., 0, CV_L1 );


   }



   。。。//



   return true;


}





bool CvForestTree::train( CvDTreeTrainData* _data,


                         const CvMat* _subsample_idx,


                         CvRTrees* _forest )


{


   clear();


   forest = _forest;



   data = _data;


   data->shared = true;


   return do_train(_subsample_idx);


}



grow_forest函数不断调用上面的train函数,随机森林中每棵树都是CvForestTree,这个类继承于CvDTree。CvForestTree重载了train函数,find_best_split函数,因此会调用了CvDTree类中的do_train函数后,do_train函数依旧调用CvDTree类中的try_split_node函数,try_split_node函数就会调用CvForestTree的find_best_split函数。下面介绍CvForestTree的find_best_split函数。




CvDTreeSplit* CvForestTree::find_best_split( CvDTreeNode* node )


{


   CvMat* active_var_mask = 0;


   if( forest )


   {


       int var_count;


       CvRNG* rng = forest->get_rng();



       active_var_mask = forest->get_active_var_mask();


       var_count = active_var_mask->cols;



       CV_Assert( var_count == data->var_count );



       //随机交换active_var_mask中不同位置的值,即可达到随机选择m个特征的目的


       for( int vi = 0; vi < var_count; vi++ )


       {


           uchar temp;


           int i1 = cvRandInt(rng) % var_count;


           int i2 = cvRandInt(rng) % var_count;


           CV_SWAP( active_var_mask->data.ptr[i1],


               active_var_mask->data.ptr[i2], temp );


       }


   }



   cv::ForestTreeBestSplitFinder finder( this, node );


   cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder);



   CvDTreeSplit *bestSplit = 0;


   if( finder.bestSplit->quality > 0 )


   {


       bestSplit = data->new_split_cat( 0, -1.0f );


       memcpy( bestSplit, finder.bestSplit, finder.splitSize );


   }


   return bestSplit;


}



和决策树的find_best_split函数不同的是上面的标黄部分,该部分实现了随机选择样本特征的作用。即只有随机选中的那些才参与后面的最优特征分裂的选择中。(active_var_mask矩阵代表选取的那些特征,选中的特征对应1,未选中的对应0)


【注】active_var_mask在最初训练函数中(CvRTrees::train)已经初始化。该矩阵初始化为:前m个值置为1,后面的值置为0。(m是指定的特征个数,一般为总特征个数的平方根)。



find_best_split函数通过下面这句命令,实现此过程。


cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder);


实际上,parallel_reduce是调用body函数,其中重载了操作符()。即实际上是执行下面的函数。


void ForestTreeBestSplitFinder::operator()(const BlockedRange& range)


{


。。。。。。



   CvForestTree* ftree = (CvForestTree*)tree;


   const CvMat* active_var_mask = ftree->forest->get_active_var_mask();



   for( vi = vi1; vi < vi2; vi++ )


   {


       CvDTreeSplit *res;


       int ci = data->var_type->data.i[vi];


       if( node->num_valid[vi] <= 1


           || (active_var_mask && !active_var_mask->data.ptr[vi]))


           continue;



       if( data->is_classifier )


       {


           if( ci >= 0 )


               res = ftree->find_split_cat_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );


           else


               res = ftree->find_split_ord_class( node, vi, bestSplit->quality, split, (uchar*)inn_buf );


       }


          。。。。。。


     


       if( res && bestSplit->quality < split->quality )


               memcpy( (CvDTreeSplit*)bestSplit, (CvDTreeSplit*)split, splitSize );


   }


}



该操作符重载函数和决策树中的函数的区别是标黄的部分,该部分判断当前的特征是否在active_var_mask中为0,如果为0,代表不选用该特征,则循环继续。也就是当前特征不参与最优分裂的选择。


find_split_cat_class函数并没有重载,依旧使用决策树中的该函数。


有关随机森林的训练过程的更多相关文章

  1. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  2. ruby - 如何在 Ruby 中生成一个非常大的随机整数? - 2

    我想在ruby​​中生成一个64位整数。我知道在Java中你有很多渴望,但我不确定你会如何在Ruby中做到这一点。另外,64位数字中有多少个字符?这是我正在谈论的示例......123456789999。@num=Random.rand(9000)+Random.rand(9000)+Random.rand(9000)但我认为这是非常低效的,必须有一种更简单、更简洁的方法来做到这一点。谢谢! 最佳答案 rand可以将范围作为参数:pa=rand(2**32..2**64-1)#=>11093913376345012184putsa.

  3. ruby-on-rails - 多次选择一个随机数,但绝不会两次选择相同的随机数 - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:HowdoIgeneratealistofnuniquerandomnumbersinRuby?我想做的事:Random.rand(0..10).timesdoputsRandom.rand(0..10)end但如果随机数已经显示过,则无法再次显示。如何最轻松地做到这一点?

  4. ruby - 以随机顺序将数组拆分为多个数组 - Ruby - 2

    我试图在每次运行时以随机顺序将一个名称数组拆分为多个数组。我知道如何拆分它们:name_array=["bob","john","rob","nate","nelly","michael"]array=name_array.each_slice(2).to_a=>[["bob","john"],["rob","nate"],["nelly","michael"]]但是,如果我希望它每次都以随机顺序吐出它们怎么办? 最佳答案 在做同样的事情之前,打乱数组。(Array#shuffle)name_array.shuffle.each_s

  5. ruby - Ruby 中的 block 和过程 - 2

    我已经开始学习Ruby,我已经阅读了一些教程,甚至还买了一本书(“ProgrammingRuby1.9-ThePragmaticProgrammers'Guide”),我遇到了一些以前从未见过的新东西使用我知道的任何其他语言(我是一名PHP网络开发人员)。block和过程。我想我明白它们是什么,但我不明白的是为什么它们如此伟大,以及我应该在何时何地使用它们。我到处都看到他们说block和过程是Ruby中的一个很棒的特性,但我不理解它们。这里有人能给像我这样的Ruby新手一些解释吗? 最佳答案 block有很多好处。电梯演讲:bloc

  6. ruby - 生成X和Y之间的随机数,不包括某些数字 - 2

    有没有办法在ruby​​中生成介于1-100但不包括20、30和40之间的随机数?我可以做类似的事情defrandom_numberrandom_number=rand(100)whilerandom_number==20||30||40random_number=rand(100)endreturnrandom_numberend...但这似乎不是很有效(再加上那个特定的例子可能根本行不通)。有没有更简单的方法?任何帮助深表感谢! 最佳答案 创建一个1到100的数组。从该数组中删除不需要的元素。然后从数组中选择一个随机数。([*1

  7. ruby-on-rails - 给定长度的完全随机标识符 - 2

    我想生成一个包含数字、字母和特殊字符的给定(长度可能不同)长度的完全随机的“唯一”(我将确保使用我的模型)标识符例如:161551960578281|2.AQAIPhEcKsDLOVJZ.3600.1310065200.0-514191032|有人可以建议在RubyonRails中最有效的方法吗?编辑:重要:如果可能,请评论您提出的解决方案的效率,因为每次用户进入网站时都会使用它!谢谢 最佳答案 将其用于访问token与UUID不同。您不仅需要伪随机性,而且还需要加密安全PRNG.如果您真的不关心您使用的是什么字符(它们不会增加任何

  8. ruby - 如何获得随机的 0 和 1 数字 - 2

    所以基本上是为了好玩,我试图生成一列数字(7位数字只有0和1)我的代码很短:a=rand(0000000-1111111)b=220a1=rand(0000000-1111111)a2=rand(0000000-1111111)a3=rand(0000000-1111111)a4=rand(0000000-1111111)a5=rand(0000000-1111111)whileb!=0putsaputsa2putsa3putsa4putsa5end我的问题是,不是生成随机的0和1列,而是所有,而是使用了数字。 最佳答案 这是惯用的

  9. ruby-on-rails - Rails 中 View 的解析过程 - 2

    rails中View的解析过程是怎样的?我对View中erb标记中原始html与ruby​​代码的解析顺序部分感兴趣。我认为这是View代码被解析并最终发送给请求者的顺序:Controller调用ViewView代码从上到下解析当Rails在解析过程中遇到erb标记时:rails解析它并将结果附加到解析的html(这包括erb标签引用助手)一旦整个View被解析,整体结果将发送给请求者这似乎并非如此。看来View代码会扫描任何erb片段并首先解析那些片段(包括对助手的引用)。之后,rails然后从上到下解析所有View代码并将结果发送给请求者。以这个View为例:#_form.html

  10. ruby - 在 Ruby 中训练神经网络 - 2

    在神经网络方面,我完全是个初学者。我整天都在与ruby​​-fann和ai4r搏斗,不幸的是我没有任何东西可以展示,所以我想我会来到StackOverflow并询问这里的知识渊博的人。我有一组样本——每天都有一个数据点,但它们不符合我能够找出的任何明确模式(我尝试了几次回归)。不过,我认为看看是否有任何方法可以仅从日期预测future的数据会很好,而且我认为神经网络将是生成希望表达这种关系的函数的好方法.日期是DateTime对象,数据点是十进制数,例如7.68。我一直在将DateTime对象转换为float,然后除以10,000,000,000得到一个介于0和1之间的数字,我一直在将

随机推荐