Opencv249源码分析Extremely randomized trees.docx
《Opencv249源码分析Extremely randomized trees.docx》由会员分享,可在线阅读,更多相关《Opencv249源码分析Extremely randomized trees.docx(34页珍藏版)》请在冰豆网上搜索。
![Opencv249源码分析Extremely randomized trees.docx](https://file1.bdocx.com/fileroot1/2023-1/28/5e6a9bed-d61f-42b8-8351-b9bfc5cbf7a8/5e6a9bed-d61f-42b8-8351-b9bfc5cbf7a81.gif)
Opencv249源码分析Extremelyrandomizedtrees
Opencv2.4.9源码分析——Extremelyrandomizedtrees
一、原理
ET或Extra-Trees(Extremelyrandomizedtrees,极端随机树)是由PierreGeurts等人于2006年提出。
该算法与随机森林算法十分相似,都是由许多决策树构成。
但该算法与随机森林有两点主要的区别:
1、随机森林应用的是Bagging模型,而ET是使用所有的训练样本得到每棵决策树,也就是每棵决策树应用的是相同的全部训练样本;
2、随机森林是在一个随机子集内得到最佳分叉属性,而ET是完全随机的得到分叉值,从而实现对决策树进行分叉的。
对于第2点的不同,我们再做详细的介绍。
我们仅以二叉树为例,当特征属性是类别的形式时,随机选择具有某些类别的样本为左分支,而把具有其他类别的样本作为右分支;当特征属性是数值的形式时,随机选择一个处于该特征属性的最大值和最小值之间的任意数,当样本的该特征属性值大于该值时,作为左分支,当小于该值时,作为右分支。
这样就实现了在该特征属性下把样本随机分配到两个分支上的目的。
然后计算此时的分叉值(如果特征属性是类别的形式,可以应用基尼指数;如果特征属性是数值的形式,可以应用均方误差)。
遍历节点内的所有特征属性,按上述方法得到所有特征属性的分叉值,我们选择分叉值最大的那种形式实现对该节点的分叉。
从上面的介绍可以看出,这种方法比随机森林的随机性更强。
对于某棵决策树,由于它的最佳分叉属性是随机选择的,因此用它的预测结果往往是不准确的,但多棵决策树组合在一起,就可以达到很好的预测效果。
当ET构建好了以后,我们也可以应用全部的训练样本来得到该ET的预测误差。
这是因为尽管构建决策树和预测应用的是同一个训练样本集,但由于最佳分叉属性是随机选择的,所以我们仍然会得到完全不同的预测结果,用该预测结果就可以与样本的真实响应值比较,从而得到预测误差。
如果与随机森林相类比的话,在ET中,全部训练样本都是OOB样本,所以计算ET的预测误差,也就是计算这个OOB误差。
在这里,我们仅仅介绍了ET算法与随机森林的不同之处,ET算法的其他内容(如预测、OOB误差的计算)与随机森林是完全相同的,具体内容请看关于随机森林的介绍。
二、源码分析
下面是ET算法的类CvERTrees,它继承于CvRTrees类:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classCV_EXPORTS_WCvERTrees:
publicCvRTrees
{
public:
CV_WRAPCvERTrees();
virtual~CvERTrees();
virtualbooltrain(constCvMat*trainData,inttflag,
constCvMat*responses,constCvMat*varIdx=0,
constCvMat*sampleIdx=0,constCvMat*varType=0,
constCvMat*missingDataMask=0,
CvRTParamsparams=CvRTParams());
CV_WRAPvirtualbooltrain(constcv:
:
Mat&trainData,inttflag,
constcv:
:
Mat&responses,constcv:
:
Mat&varIdx=cv:
:
Mat(),
constcv:
:
Mat&sampleIdx=cv:
:
Mat(),constcv:
:
Mat&varType=cv:
:
Mat(),
constcv:
:
Mat&missingDataMask=cv:
:
Mat(),
CvRTParamsparams=CvRTParams());
virtualbooltrain(CvMLData*data,CvRTParamsparams=CvRTParams());
protected:
virtualstd:
:
stringgetName()const;
virtualboolgrow_forest(constCvTermCriteriaterm_crit);
};
我们从CvERTrees类可以看出,它没有预测函数predict,因此,如果要进行ET的预测,调用的是它的父类CvRTrees内的predict函数。
在训练样本时,CvERTrees类与CvRTrees类的训练过程是完全一致的,即在train函数内再调用grow_forest函数,而且两个类的train函数的输入参数的形式也是完全一样的。
但在grow_forest函数内会有一点不同,那就是CvERTrees类中的grow_forest函数把全体训练样本都当成OOB样本,因此它不需要随机样本掩码矩阵变量sample_idx_mask_for_tree,而表示样本索引值变量的sample_idx_for_tree保存的就是正常顺序的训练样本的索引值。
ET算法与随机森林算法最大的不同就在于节点的分叉上,而这一点是体现在CvForestERTree类上的:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classCV_EXPORTSCvForestERTree:
publicCvForestTree
{
protected:
virtualdoublecalc_node_dir(CvDTreeNode*node);
virtualCvDTreeSplit*find_split_ord_class(CvDTreeNode*n,intvi,
floatinit_quality=0,CvDTreeSplit*_split=0,uchar*ext_buf=0);
virtualCvDTreeSplit*find_split_cat_class(CvDTreeNode*n,intvi,
floatinit_quality=0,CvDTreeSplit*_split=0,uchar*ext_buf=0);
virtualCvDTreeSplit*find_split_ord_reg(CvDTreeNode*n,intvi,
floatinit_quality=0,CvDTreeSplit*_split=0,uchar*ext_buf=0);
virtualCvDTreeSplit*find_split_cat_reg(CvDTreeNode*n,intvi,
floatinit_quality=0,CvDTreeSplit*_split=0,uchar*ext_buf=0);
virtualvoidsplit_node_data(CvDTreeNode*n);
};
CvForestERTree类定义了一些专用于ET算法的计算分叉、得到最佳分叉属性的函数,下面我们就逐一介绍这些函数。
按最佳分叉属性标注该节点的所有样本是被分配到左分支还是右分支:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
doubleCvForestERTree:
:
calc_node_dir(CvDTreeNode*node)
{
//表示特征属性的种类是属于左分支还是右分支,-1为左分支,1为右分支,如果该特征属性缺失,则为0
char*dir=(char*)data->direction->data.ptr;
//n表示该节点的样本数量,vi表示分类的最佳特征属性
inti,n=node->sample_count,vi=node->split->var_idx;
doubleL,R;
assert(!
node->split->inversed);//确保分叉不反转
if(data->get_var_type(vi)>=0)//splitoncategoricalvar
//表示该特征属性是种类的形式
{
//开辟一块内存空间
cv:
:
AutoBufferinn_buf(n*sizeof(int)*(!
data->have_priors?
1:
2));
int*labels_buf=(int*)(uchar*)inn_buf;
//labels指向该特征属性中各个样本所对应的种类,get_cat_var_data函数在ER算法中被重新定义
constint*labels=data->get_cat_var_data(node,vi,labels_buf);
//subset数组的每一位表示特征属性的种类,左分支的种类位是1,右分支的是0
constint*subset=node->split->subset;
if(!
data->have_priors)//无先验概率
{
intsum=0,sum_abs=0;
for(i=0;i{
intidx=labels[i];//表示该样本的特征属性的种类
//d为-1表示idx(特征属性的种类)属于左分支,为1表示属于右分支,如果没有该特征属性,则d为0
intd=(((idx>=0)&&(!
data->is_buf_16u))||((idx!
=65535)&&(data->is_buf_16u)))?
CV_DTREE_CAT_DIR(idx,subset):
0;
//sum表示d累加求和,因为d也可能为负值,所以sum的含义为右分支比左分支多出的特征属性种类;sum_abs表示d的绝对值之和,表示的含义为被分叉的特征属性种类
sum+=d;sum_abs+=d&1;
dir[i]=(char)d;//赋值
}
//L和R分别表示左右分支的特征属性的种类数量
R=(sum_abs+sum)>>1;
L=(sum_abs-sum)>>1;
}
else//有先验概率
{
constdouble*priors=data->priors_mult->data.db;//得到先验概率
doublesum=0,sum_abs=0;
int*responses_buf=labels_buf+n;
//responses指向该节点样本的分类,即响应值
constint*responses=data->get_class_labels(node,responses_buf);
for(i=0;i{
intidx=labels[i];//表示该样本的特征属性的种类
doublew=priors[responses[i]];//得到该响应值的先验概率
//d为-1表示idx(特征属性的种类)属于左分支,为1表示属于右分支,如果没有该特征属性,则d为0
intd=idx>=0?
CV_DTREE_CAT_DIR(idx,subset):
0;
sum+=d*w;sum_abs+=(d&1)*w;//增加了先验概率
dir[i]=(char)d;
}
//L和R分别表示左右分支的特征属性的种类数量
R=(sum_abs+sum)*0.5;
L=(sum_abs-sum)*0.5;
}
}
else//splitonorderedvar
//表示该特征属性是数值的形式
{
//得到分叉属性的值split_val,如果样本的分叉属性的值小于该值,则被分叉为左节点,否则为右节点
floatsplit_val=node->split->ord.c;
//为该特征属性开辟一块内存空间
cv:
:
AutoBufferinn_buf(n*(sizeof(int)*(!
data->have_priors?
1:
2)+sizeof(float)));
float*val_buf=(float*)(uchar*)inn_buf;//用于存储各个样本当前特征属性的值
int*missing_buf=(int*)(val_buf+n);//表示各个样本是否缺失当前特征属性
constfloat*val=0;//指向数组val_buf
constint*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,&val,&missing,0);
if(!
data->have_priors)//无先验概率
{
L=R=0;
for(i=0;i{
if(missing[i])//该样本缺失vi这个特征属性
dir[i]=(char)0;//方向信息赋值为0
else
{
if(val[i]{
dir[i]=(char)-1;//方向信息赋值为-1
L++;//左分支计数
}
else//右分支
{
dir[i]=(char)1;//方向信息赋值为1
R++;//右分支计数
}
}
}
}
else//有先验概率
{
constdouble*priors=data->priors_mult->data.db;//得到先验概率
int*responses_buf=missing_buf+n;
//responses指向该节点样本的分类,即响应值
constint*responses=data->get_class_labels(node,responses_buf);
L=R=0;
for(i=0;i{
if(missing[i])//该样本缺失vi这个特征属性
dir[i]=(char)0;
else
{
doublew=priors[responses[i]];//得到先验概率
if(val[i]{
dir[i]=(char)-1;
L+=w;
}
else//右分支
{
dir[i]=(char)1;
R+=w;
}
}
}
}
}
node->maxlr=MAX(L,R);//表示左右分支最大值
returnnode->split->quality/(L+R);//得到规范化系数
}
特征为数值的分类树的分叉方法:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
CvDTreeSplit*CvForestERTree:
:
find_split_ord_class(CvDTreeNode*node,intvi,floatinit_quality,CvDTreeSplit*_split,
uchar*_ext_buf)
{
constfloatepsilon=FLT_EPSILON*2;//定义一个最小常数
constfloatsplit_delta=(1+FLT_EPSILON)*FLT_EPSILON;//定义另一个常数
intn=node->sample_count;//该节点的样本数量
intm=data->get_num_classes();//样本数据的分类数
//为该特征属性vi开辟一块内存空间
cv:
:
AutoBufferinn_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);//表示各个样本是否缺失当前特征属性
constfloat*values=0;//指向数组values_buf
constint*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指向该节点样本的分类,即响应值
constint*responses=data->get_class_labels(node,responses_buf);
doublelbest_val=0,rbest_val=0,best_val=init_quality,split_val=0;
//得到不同分类的先验概率
constdouble*priors=data->have_priors?
data->priors_mult->data.db:
0;
boolis_find_split=false;//表示是否找到了分叉属性
floatpmin,pmax;//分别表示样本的特征属性vi的最小值和最大值
intsmpi=0;//表示缺失特征属性的样本索引值
//得到第一个不缺失vi特征属性的样本
while(missing[smpi]&&(smpismpi++;
assert(smpi//初始化pmin和pmax
pmin=values[smpi];
pmax=pmin;
for(;smpi{
floatptemp=values[smpi];//得到当前样本的vi的值
intms=missing[smpi];//当前样本是否缺失该vi值
if(ms)continue;//缺失则遍历下一个样本
if(ptemppmin=ptemp;
if(ptemp>pmax)//更新pmax值
pmax=ptemp;
}
floatfdiff=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,则split_val为一个定值
if(split_val-pmin<=FLT_EPSILON)
split_val=pmin+split_delta;
if(pmax-split_val<=FLT_EPSILON)
split_val=pmax-split_delta;
//calculateGiniindex
//计算基尼指数
if(!
priors)//样本没有先验概率
{
cv:
:
AutoBufferlrc(m*2);
//lc和rc分别表示分类的左、右分支
int*lc=lrc,*rc=lc+m;
intL=0,R=0;//表示左、右分支的样本数
//initarraysofclassinstancecountersonbothsidesofthesplit
for(inti=0;i{
lc[i]=0;
rc[i]=0;
}
for(intsi=0;si{
intr=responses[si];//该样本的响应值
floatval=values[si];//该样本的特征属性vi的值
intms=missing[si];//当前样本是否缺失该vi值
if(ms)continue;//缺失则遍历下一个样本
if(val{
lc[r]++;
L++;
}
else//右分支
{
rc[r]++;
R++;
}
}
//得到分类后的基尼指数best_val
for(inti=0;i{
lbest_val+=lc[i]*lc[i];
rbe