1、Opencv249源码分析Extremely randomized treesOpencv2.4.9源码分析Extremely randomized trees一、原理 ET或Extra-Trees(Extremely randomized trees,极端随机树)是由PierreGeurts等人于2006年提出。该算法与随机森林算法十分相似,都是由许多决策树构成。但该算法与随机森林有两点主要的区别:1、随机森林应用的是Bagging模型,而ET是使用所有的训练样本得到每棵决策树,也就是每棵决策树应用的是相同的全部训练样本;2、随机森林是在一个随机子集内得到最佳分叉属性,而ET是完全随机的得到
2、分叉值,从而实现对决策树进行分叉的。对于第2点的不同,我们再做详细的介绍。我们仅以二叉树为例,当特征属性是类别的形式时,随机选择具有某些类别的样本为左分支,而把具有其他类别的样本作为右分支;当特征属性是数值的形式时,随机选择一个处于该特征属性的最大值和最小值之间的任意数,当样本的该特征属性值大于该值时,作为左分支,当小于该值时,作为右分支。这样就实现了在该特征属性下把样本随机分配到两个分支上的目的。然后计算此时的分叉值(如果特征属性是类别的形式,可以应用基尼指数;如果特征属性是数值的形式,可以应用均方误差)。遍历节点内的所有特征属性,按上述方法得到所有特征属性的分叉值,我们选择分叉值最大的那种
3、形式实现对该节点的分叉。从上面的介绍可以看出,这种方法比随机森林的随机性更强。对于某棵决策树,由于它的最佳分叉属性是随机选择的,因此用它的预测结果往往是不准确的,但多棵决策树组合在一起,就可以达到很好的预测效果。当ET构建好了以后,我们也可以应用全部的训练样本来得到该ET的预测误差。这是因为尽管构建决策树和预测应用的是同一个训练样本集,但由于最佳分叉属性是随机选择的,所以我们仍然会得到完全不同的预测结果,用该预测结果就可以与样本的真实响应值比较,从而得到预测误差。如果与随机森林相类比的话,在ET中,全部训练样本都是OOB样本,所以计算ET的预测误差,也就是计算这个OOB误差。在这里,我们仅仅介
4、绍了ET算法与随机森林的不同之处,ET算法的其他内容(如预测、OOB误差的计算)与随机森林是完全相同的,具体内容请看关于随机森林的介绍。 二、源码分析 下面是ET算法的类CvERTrees,它继承于CvRTrees类:cpp view plain copy 在CODE上查看代码片派生到我的代码片class CV_EXPORTS_W CvERTrees : public CvRTrees public: CV_WRAP CvERTrees(); virtual CvERTrees(); virtual bool train( const CvMat* trainData, int tflag,
5、const CvMat* responses, const CvMat* varIdx=0, const CvMat* sampleIdx=0, const CvMat* varType=0, const CvMat* missingDataMask=0, CvRTParams params=CvRTParams(); CV_WRAP virtual bool train( const cv:Mat& trainData, int tflag, const cv:Mat& responses, const cv:Mat& varIdx=cv:Mat(), const cv:Mat& sampl
6、eIdx=cv:Mat(), const cv:Mat& varType=cv:Mat(), const cv:Mat& missingDataMask=cv:Mat(), CvRTParams params=CvRTParams(); virtual bool train( CvMLData* data, CvRTParams params=CvRTParams() ); protected: virtual std:string getName() const; virtual bool grow_forest( const CvTermCriteria term_crit ); ; 我们
7、从CvERTrees类可以看出,它没有预测函数predict,因此,如果要进行ET的预测,调用的是它的父类CvRTrees内的predict函数。在训练样本时,CvERTrees类与CvRTrees类的训练过程是完全一致的,即在train函数内再调用grow_forest函数,而且两个类的train函数的输入参数的形式也是完全一样的。但在grow_forest函数内会有一点不同,那就是CvERTrees类中的grow_forest函数把全体训练样本都当成OOB样本,因此它不需要随机样本掩码矩阵变量sample_idx_mask_for_tree,而表示样本索引值变量的sample_idx_fo
8、r_tree保存的就是正常顺序的训练样本的索引值。ET算法与随机森林算法最大的不同就在于节点的分叉上,而这一点是体现在CvForestERTree类上的:cpp view plain copy 在CODE上查看代码片派生到我的代码片class CV_EXPORTS CvForestERTree : public CvForestTree protected: virtual double calc_node_dir( CvDTreeNode* node ); virtual CvDTreeSplit* find_split_ord_class( CvDTreeNode* n, int vi,
9、float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); virtual CvDTreeSplit* find_split_cat_class( CvDTreeNode* n, int vi, float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); virtual CvDTreeSplit* find_split_ord_reg( CvDTreeNode* n, int vi, float init_quality = 0
10、, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); virtual CvDTreeSplit* find_split_cat_reg( CvDTreeNode* n, int vi, float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); virtual void split_node_data( CvDTreeNode* n ); ; CvForestERTree类定义了一些专用于ET算法的计算分叉、得到最佳分叉属性的函数,下面我们就逐一介绍这些函数。 按最
11、佳分叉属性标注该节点的所有样本是被分配到左分支还是右分支:cpp view plain copy 在CODE上查看代码片派生到我的代码片double CvForestERTree:calc_node_dir( CvDTreeNode* node ) /表示特征属性的种类是属于左分支还是右分支,-1为左分支,1为右分支,如果该特征属性缺失,则为0 char* dir = (char*)data-direction-data.ptr; /n表示该节点的样本数量,vi表示分类的最佳特征属性 int i, n = node-sample_count, vi = node-split-var_idx;
12、double L, R; assert( !node-split-inversed ); /确保分叉不反转 if( data-get_var_type(vi) = 0 ) / split on categorical var /表示该特征属性是种类的形式 /开辟一块内存空间 cv:AutoBuffer inn_buf(n*sizeof(int)*(!data-have_priors ? 1 : 2); int* labels_buf = (int*)(uchar*)inn_buf; /labels指向该特征属性中各个样本所对应的种类,get_cat_var_data函数在ER算法中被重新定义
13、const int* labels = data-get_cat_var_data( node, vi, labels_buf ); / subset数组的每一位表示特征属性的种类,左分支的种类位是1,右分支的是0 const int* subset = node-split-subset; if( !data-have_priors ) /无先验概率 int sum = 0, sum_abs = 0; for( i = 0; i = 0)&(!data-is_buf_16u) | (idx != 65535)&(data-is_buf_16u) ) ? CV_DTREE_CAT_DIR(id
14、x,subset) : 0; /sum表示d累加求和,因为d也可能为负值,所以sum的含义为右分支比左分支多出的特征属性种类;sum_abs表示d的绝对值之和,表示的含义为被分叉的特征属性种类 sum += d; sum_abs += d & 1; diri = (char)d; /赋值 /L和R分别表示左右分支的特征属性的种类数量 R = (sum_abs + sum) 1; L = (sum_abs - sum) 1; else /有先验概率 const double* priors = data-priors_mult-data.db; /得到先验概率 double sum = 0, s
15、um_abs = 0; int *responses_buf = labels_buf + n; /responses指向该节点样本的分类,即响应值 const int* responses = data-get_class_labels(node, responses_buf); for( i = 0; i = 0 ? CV_DTREE_CAT_DIR(idx,subset) : 0; sum += d*w; sum_abs += (d & 1)*w; /增加了先验概率 diri = (char)d; /L和R分别表示左右分支的特征属性的种类数量 R = (sum_abs + sum) *
16、0.5; L = (sum_abs - sum) * 0.5; else / split on ordered var /表示该特征属性是数值的形式 /得到分叉属性的值split_val,如果样本的分叉属性的值小于该值,则被分叉为左节点,否则为右节点 float split_val = node-split-ord.c; /为该特征属性开辟一块内存空间 cv:AutoBuffer inn_buf(n*(sizeof(int)*(!data-have_priors ? 1 : 2) + sizeof(float); float* val_buf = (float*)(uchar*)inn_buf
17、; /用于存储各个样本当前特征属性的值 int* missing_buf = (int*)(val_buf + n); /表示各个样本是否缺失当前特征属性 const float* val = 0; /指向数组val_buf const int* missing = 0; /指向数组missing_buf / get_ord_var_data函数在ER算法中被重新定义,各个样本的vi特征属性的值存储在val_buf数组内,各个样本是否缺失该特征属性用missing_buf数组表示 data-get_ord_var_data( node, vi, val_buf, missing_buf, &v
18、al, &missing, 0 ); if( !data-have_priors ) /无先验概率 L = R = 0; for( i = 0; i n; i+ ) /遍历所有样本 if ( missingi ) /该样本缺失vi这个特征属性 diri = (char)0; /方向信息赋值为0 else if ( vali priors_mult-data.db; /得到先验概率 int* responses_buf = missing_buf + n; /responses指向该节点样本的分类,即响应值 const int* responses = data-get_class_labels
19、(node, responses_buf); L = R = 0; for( i = 0; i n; i+ ) /遍历所有样本 if ( missingi ) /该样本缺失vi这个特征属性 diri = (char)0; else double w = priorsresponsesi; /得到先验概率 if ( vali maxlr = MAX( L, R ); /表示左右分支最大值 return node-split-quality/(L + R); /得到规范化系数 特征为数值的分类树的分叉方法:cpp view plain copy 在CODE上查看代码片派生到我的代码片CvDTree
20、Split* CvForestERTree:find_split_ord_class( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) const float epsilon = FLT_EPSILON*2; /定义一个最小常数 const float split_delta = (1 + FLT_EPSILON) * FLT_EPSILON; /定义另一个常数 int n = node-sample_count; /该节点的样本数量 int m = data-get_
21、num_classes(); /样本数据的分类数 /为该特征属性vi开辟一块内存空间 cv:AutoBuffer inn_buf; if( !_ext_buf ) inn_buf.allocate(n*(2*sizeof(int) + sizeof(float); uchar* ext_buf = _ext_buf ? _ext_buf : (uchar*)inn_buf; float* values_buf = (float*)ext_buf; /用于存储各个样本在特征属性vi的值 int* missing_buf = (int*)(values_buf + n); /表示各个样本是否缺失当
22、前特征属性 const float* values = 0; /指向数组values_buf const int* missing = 0; /指向数组missing_buf /得到数组values_buf和missing_buf data-get_ord_var_data( node, vi, values_buf, missing_buf, &values, &missing, 0 ); int* responses_buf = missing_buf + n; /responses指向该节点样本的分类,即响应值 const int* responses = data-get_class_
23、labels( node, responses_buf ); double lbest_val = 0, rbest_val = 0, best_val = init_quality, split_val = 0; /得到不同分类的先验概率 const double* priors = data-have_priors ? data-priors_mult-data.db : 0; bool is_find_split = false; /表示是否找到了分叉属性 float pmin, pmax; /分别表示样本的特征属性vi的最小值和最大值 int smpi = 0; /表示缺失特征属性的样
24、本索引值 /得到第一个不缺失vi特征属性的样本 while ( missingsmpi & (smpi n) ) smpi+; assert(smpi n); /确保smpi的正确 /初始化pmin和pmax pmin = valuessmpi; pmax = pmin; for (; smpi n; smpi+) /遍历样本,得到pmin和pmax float ptemp = valuessmpi; /得到当前样本的vi的值 int ms = missingsmpi; /当前样本是否缺失该vi值 if (ms) continue; /缺失则遍历下一个样本 if ( ptemp pmax) /
25、更新pmax值 pmax = ptemp; float fdiff = pmax-pmin; /pmax与pmin的差值 /差值大于一个常数,表示前面计算的结果是有意义的,也就是得到了分叉属性 if (fdiff epsilon) is_find_split = true; /表示找到了分叉属性 cv:RNG* rng = data-rng; /表示随机数 /随机数为0和1之间的任意数,split_val为pmax与pmin之间任意一个数 split_val = pmin + rng-uniform(0.f, 1.f) * fdiff ; /如果split_val太接近pmax或pmin,则s
26、plit_val为一个定值 if (split_val - pmin = FLT_EPSILON) split_val = pmin + split_delta; if (pmax - split_val = FLT_EPSILON) split_val = pmax - split_delta; / calculate Gini index /计算基尼指数 if ( !priors ) /样本没有先验概率 cv:AutoBuffer lrc(m*2); /lc和rc分别表示分类的左、右分支 int *lc = lrc, *rc = lc + m; int L = 0, R = 0; /表示左
27、、右分支的样本数 / init arrays of class instance counters on both sides of the split for(int i = 0; i m; i+ ) /数组lc和rc清零 lci = 0; rci = 0; for( int si = 0; si n; si+ ) /遍历所有样本 int r = responsessi; /该样本的响应值 float val = valuessi; /该样本的特征属性vi的值 int ms = missingsi; /当前样本是否缺失该vi值 if (ms) continue; /缺失则遍历下一个样本 if ( val split_val ) /左分支 lcr+; L+; else /右分支 rcr+; R+; /得到分类后的基尼指数best_val for (int i = 0; i m; i+) lbest_val += lci*lci; rbe
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1