1、、特征为数值的分类树:由于特征属性是用数值进行表示,我们就按照数值的大小顺序依次带入式12,计算最大值。如一共有14个样本,按照由小至大的顺序为:abcdefghijklmn,第一次分叉为:a|bcdefghijklmn,竖线“|”的左侧被划分到左分支,右侧被划分到右分支,带入式12计算其值,然后第二次分叉:ab|cdefghijklmn,同理带入式12计算其值,以此类推,得到这13次分叉的最大值,该种分叉方式即为最佳的分叉方式,其中阈值为分叉的次数。、特征为类的回归树:计算每种特征属性各个种类的平均样本响应值,按照该值的大小进行排序,然后依次带入式19,计算其最大值。、特征为数值的回归树:该
2、种情况与特征为数值的分类树相同,就按照数值的大小顺序依次带入式19,计算最大值。在训练决策树时,还有三个技术问题需要解决。第一个问题是,对于分类树,我们还需要考虑一种情况,当用户想要检测一些非常罕见的异常现象的时候,这是非常难办到的,这是因为训练可能包含了比异常多得多的正常情况,那么很可能分类结果就是认为每一个情况都是正常的。为了避免这种情况的出现,我们需要设置先验概率,这样异常情况发生的概率就被人为的增加(可以增加到0.5甚至更高),这样被误分类的异常情况的权重就会变大,决策树也能够得到适当的调整。先验概率需要根据各自情况人为设置,但还需要考虑各个分类的样本率,因此这个先验值还不能直接应用,
3、还需要处理。设Qj为我们设置的第j个分类的先验概率,Nj为该分类的样本数,则考虑了样本率并进行归一化处理的先验概率qj为:第二个需要解决的问题是,某些样本缺失了某个特征属性,但该特征属性又是最佳分叉属性,那么如何对该样本进行分叉呢?目前有几种方法可以解决该问题,一种是直接把该样本删除掉;另一种方法是用各种算法估计该样本的缺失属性值。还有一种方法就是用另一个特征属性来替代最佳分叉属性,该特征属性被称为替代分叉属性。因此在计算最佳分叉属性的同时,还要计算该特征属性的替代分叉属性,以防止最佳分叉属性缺失的情况。CART算法就是采用的该方法,下面我们就来介绍该方法。寻找替代分叉属性总的原则就是使其分叉
4、的效果与最佳分叉属性相似,即分叉的误差最小。我们仍然根据特征属性是类还是数值的形式,也把替代分叉属性的计算分为两种情况。当特征属性是类的形式的时候,当最佳分叉属性不是该特征属性时,会把该特征属性的每个种类分叉为不同的分支,例如当最佳分叉属性不是风力时,极有可能把5个无风的样本分叉为不同的分支(如3个属于左分支,2个属于右分支),但当最佳分叉属性是风力时,这种情况就不会发生,也就是5个无风的样本要么属于左分支,要么属于右分支。因此我们把被最佳分叉属性分叉的特征属性种类的分支最大样本数量作为该种类的分叉值,计算该特征属性所有种类的这些分叉值,最终这些分叉值之和就作为该替代分叉属性的分叉值。我们还看
5、前面的例子,无风的分叉值为3,再计算小风、中风、大风的分叉值,假如它们的值分别为4、4、3,则把风力作为替代分叉属性的分叉值为14。按照该方法再计算其他特征属性是类形式的替代分叉值,则替代性由替代分叉值按从大到小进行排序。在用替代分叉属性分叉时那些左分支大于右分支样本数的种类被分叉为左分支,反之为右分支,如上面的例子,无风的样本被分叉为左分支。当特征属性是数值的形式的时候,样本被分割成了四个部分:LL、LR、RL和RR,前一个字母表示被最佳分叉属性分叉为左右分支,后一个字母表示被替代分叉属性分叉为左右分支,如LR表示被最佳分叉属性分叉为左分支,但被替代分叉属性分叉为右分支的样本,因此LL和RR
6、表示的是被替代分叉属性分叉正确的样本,而LR和RL是被替代分叉属性分叉错误的样本,在该特征属性下,选取阈值对样本进行分割,使LL+RR或LR+RL达到最大值,则最终maxLL+RR,LR+RL作为该特征属性的替代分叉属性的分叉值。按照该方法再计算其他特征属性是数值形式的替代分叉值,则替代性也由替代分叉值按从大到小进行排序。最终我们选取替代分叉值最大的那个特征属性作为该最佳分叉属性的替代分叉属性。为了让替代分叉属性与最佳分叉属性相比较,我们还需要对替代分叉值进行规范化处理,如果替代分叉属性是类的形式,则替代分叉值需要乘以式12再除以最佳分叉属性中的种类数量,如果替代分叉属性是数值的形式,则替代分
7、叉值需要乘以式19再除以所有样本的数量。规范化后的替代分叉属性如果就是最佳分叉属性时,两者的值是相等的。第三个问题就是过拟合。由于决策树的建立完全是依赖于训练样本,因此该决策树对该样本能够产生完全一致的拟合效果。但这样的决策树对于预测样本来说过于复杂,对预测样本的分类效果也不够精确。这种现象就称为过拟合。将复杂的决策树进行简化的过程称为剪枝,它的目的是去掉一些节点,包括叶节点和中间节点。剪枝常用的方法有预剪枝和后剪枝两种。预剪枝是在构建决策树的过程中,提前终止决策树的生长,从而避免过多的节点的产生。该方法虽然简单但实用性不强,因为我们很难精确的判断何时终止树的生长。后剪枝就是在决策树构建完后再
8、去掉一些节点。常见后剪枝方法有四种:悲观错误剪枝(PEP)、最小错误剪枝(MEP)、代价复杂度剪枝(CCP)和基于错误的剪枝(EBP)。CCP算法能够应用于CART算法中,它的本质是度量每减少一个叶节点所得到的平均错误,在这里我们重点介绍CCP算法。CCP算法会产生一系列树的序列T0,T1,Tm,其中T0是由训练得到的最初的决策树,而Tm只含有一个根节点。序列中的树是嵌套的,也就是序列中的Ti+1是由Ti通过剪枝得到的,即实现用Ti+1中一个叶节点来替代Ti中以该节点为根的子树。这种被替代的原则就是使误差的增加率最小,即二、源码 OpenCV中,决策树应用的是CART算法,并且分类树和回归树都
9、实现了。而剪枝是应用的CCP算法。我们先给出用于构建决策树的参数:cpp view plain copy 在CODE上查看代码片派生到我的代码片CvDTreeParams:CvDTreeParams() : max_categories(10), max_depth(INT_MAX), min_sample_count(10), cv_folds(10), use_surrogates(true), use_1se_rule(true), truncate_pruned_tree(true), regression_accuracy(0.01f), priors(0) CvDTreeParam
10、s( int _max_depth, int _min_sample_count, float _regression_accuracy, bool _use_surrogates, int _max_categories, int _cv_folds, bool _use_1se_rule, bool _truncate_pruned_tree, const float* _priors ) : max_categories(_max_categories), max_depth(_max_depth), min_sample_count(_min_sample_count), cv_fol
11、ds (_cv_folds), use_surrogates(_use_surrogates), use_1se_rule(_use_1se_rule), truncate_pruned_tree(_truncate_pruned_tree), regression_accuracy(_regression_accuracy), priors(_priors) 其中参数的含义为:max_depth:该参数指定决策树的最大可能深度。但由于其他终止条件或者是被剪枝的缘故,最终的决策树的深度可能要比max_depth小。该值不能小于0,否则报错。如果用户把该值设置为大于25的数值,则系统会重新把该值
12、设为25,即该值不能超过25min_sample_count:如果某个节点的样本数量小于该值,则该节点将不再被分叉regression_accuracy:该参数是终止构建回归树的一个条件,表示回归树的响应值的精度如果达到了该值,则无需再分叉。该值不能小于0,否则报错use_surrogates:如果该参数为真,则替代分叉节点需要产生,替代分叉节点用在解决缺失特征属性和进行变量重要性的估计max_categories:表示特征属性为类的形式的最大的类的数量,如果在训练的过程中,类的数量大于该值,则采用聚类的方法会更高效,该参数只应用于非两类的分类树问题。该值一定要大于或等于2,否则报错cv_fo
13、lds:如果该参数大于1,则应该交叉验证来进行剪枝,该值表示交叉验证的子集数量。该值一定不能小于0,否则报错。如果用户把该值设置为1,则系统会重新把该值设为0use_1se_rule:如果为真,表示在剪枝的过程中应用1SE规则,该规则的应用使决策树更简单,对训练数据的噪声更有抵抗力,但是精度会下降了truncate_pruned_tree:如果为真,要被剪掉的节点将被从树上移除,否则它们被保留priors:表示分类树的类别标签的先验概率,按类别标签值排序。该参数可以用于针对某个特定的类别。例如,如果用户希望探测到某个比较少见的异常变化,但训练可能包含了比异常多得多的正常情况,那么很可能分类结果
14、就是认为每个情况都是正常的。为了避免这一点,先验就必须被指定,异常情况发生的概率需要人为的增加(增加到0.5甚至更高),这样误分类的异常情况的权重就会变大,树也能够得到适当的调整。CvDTree构造函数:CvDTree:CvDTree() data = 0; /样本数据 var_importance = 0; /重要数据 default_model_name = my_tree; /表明该类型为决策树 clear(); /清空一些变量 训练样本,构建决策树函数CvDTree:train:bool CvDTree:train( const CvMat* _train_data, int _tfl
15、ag, const CvMat* _responses, const CvMat* _var_idx, const CvMat* _sample_idx, const CvMat* _var_type, const CvMat* _missing_mask, CvDTreeParams _params ) /_train_data训练样本数据,必须为32FC1类型的矩阵形式 /_tflag训练数据的特征属性类型,如果为CV_ROW_SAMPLE,表示样本是以行的形式储存的,即_train_data矩阵的每一行为一个样本(或特征矢量);如果为CV_COL_SAMPLE,表示样本是以列的形式储存的
16、 /_responses决策树的结果,即响应值,该值必须是32SC1或32FC1类型的一维矩阵(即矢量)的形式,并且元素的数量必须与训练样本数据_train_data的样本数一致 /_var_idx标识感兴趣的特征属性,即真正用于训练的那些特征属性,该值的形式与_sample_idx变量相似 /_sample_idx标识感兴趣的样本,即真正用于训练的样本,该值必须是一维矩阵的形式,即矢量的形式,并且类型必须是8UC1、8SU1或者32SC1。如果为8UC1或8SU1,则该值的含义是用掩码的形式表示对应的样本,即0表示不感兴趣的样本,其他数为感兴趣的样本,因此矢量的元素数量必须与训练样本数据_t
17、rain_data的样本数一致;如果为32SC1,则该值的含义是那些感兴趣的样本的索引,而不感兴趣的样本的索引不在该矢量中出现,因此该矢量的元素数量可以小于或等于_train_data的样本数 /_var_type特征属性的形式,是类的形式还是数值的形式,用掩码的形式表现对应特征属性的形式,0表示为数值的形式,1表示为类的形式。该值必须是一维矩阵,并且元素的数量必须是真正用于训练的那些特征属性的数量加1,多余的一个元素表示的是响应值的形式,即是分类树还是回归树 /_missing_mask缺失的特征属性,用掩码的形式表现对应的特征属性,0表示没有缺失,而且必须与_train_data的矩阵尺寸
18、大小一致 /_params为构建决策树的参数 bool result = false; /标识变量 CV_FUNCNAME( train ); _BEGIN_; /实例化CvDTreeTrainData类,并通过set_data函数设置训练样本数据,如果需要交叉验证(cv_folds 1),则还要随机的把样本均等分cv_folds个子集。最后一个参数_share赋值为false,表示重新构建一个决策树 data = new CvDTreeTrainData( _train_data, _tflag, _responses, _var_idx, _sample_idx, _var_type, _
19、missing_mask, _params, false ); /调用do_train函数,对全体样本数据进行训练 CV_CALL( result = do_train(0) ); _END_; return result;下面的train函数与上面的train函数不同的地方就是矩阵的类型不同,一个是Mat,另一个是CvMat。train( const Mat& _train_data, int _tflag, const Mat& _responses, const Mat& _var_idx, _sample_idx, const Mat& _var_type, _missing_mask
20、, CvDTreeParams _params ) CvMat tdata = _train_data, responses = _responses, vidx=_var_idx, sidx=_sample_idx, vtype=_var_type, mmask=_missing_mask; return train(&tdata, _tflag, &responses, vidx.data.ptr ? &vidx : 0, sidx.data.ptr ?sidx : 0, vtype.data.ptr ?vtype : 0, mmask.data.ptr ?mmask : 0, _para
21、ms);在该train函数中,用于决策树的样本数据的一些参数保存在了_data中:train( CvMLData* _data, CvDTreeParams _params ) /提取出训练样本数据的一些参数 const CvMat* values = _data-get_values(); const CvMat* response = _data-get_responses(); const CvMat* missing = _data-get_missing(); const CvMat* var_types = _data-get_var_types(); const CvMat* t
22、rain_sidx = _data-get_train_sample_idx(); const CvMat* var_idx = _data-get_var_idx(); CV_CALL( result = train( values, CV_ROW_SAMPLE, response, var_idx, train_sidx, var_types, missing, _params ) );该train函数多了一个_subsample_idx参数train( CvDTreeTrainData* _data, const CvMat* _subsample_idx ) data = _data;
23、 data-shared = true; /表示共享决策树 /调用do_train函数,只对_subsample_idx指定的由_sample_idx参数确定的训练样本集的索引进行训练 CV_CALL( result = do_train(_subsample_idx);do_train函数,参数_subsample_idx表示训练样本集合的索引do_train( const CvMat* _subsample_idx ) do_train /提取出_subsample_idx指定的训练样本集合,如果_subsample_idx=0,则取所有样本数据 /root表示根节点 root = dat
24、a-subsample_data( _subsample_idx ); CV_CALL( try_split_node(root); /递归调用try_split_node函数,构造决策树 if( root-split ) /如果得到了决策树 /分别确保左分支和右分支的正确性 CV_Assert( root-left );right ); /如果用户所设置的交叉验证的子集大于1,则表示需要对决策树进行剪枝。在前面已经提到,在实例化CvDTreeTrainData时,会把cv_folds = 1重新设置为cv_folds = 0 if( data-params.cv_folds 0 ) CV_C
25、ALL( prune_cv() ); /剪枝 /如果决策树的数据不需要共享,则清空训练所需的数据 if( !data-shared ) free_train_data(); result = true; /正确返回标识 /返回 try_split_node函数的作用是在真正分叉之前,先预处理一下节点,试探一下该节点是否能够被分叉,能则调用split_node_data函数开始分叉,不能则退出,然后对得到的左分支和右分支再分别递归。void CvDTree:try_split_node( CvDTreeNode* node ) CvDTreeSplit* best_split = 0; /n表示该节点的样本数,vi表示特征属性 int i, n = node-sample_count, vi; bool can_split = true; /标识该节点是否能够被分叉 double quality_scale; / calc_node_value函数见后面的分析 calc_node_value( node ); /如果该节点的样本数量小于所设定的阈值,或者目前决策树的深度超过了所设定的阈值,则该节点不能再被分叉 if( node-sample_coun
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1