adaboost训练 之 强分类器训练原理
来源:互联网 发布:c语言打印等腰杨辉三角 编辑:程序博客网 时间:2024/06/05 05:00
最近看opencv中adaboost训练强分类器源码,记录下自己对adaboost训练强分类器的原理理解。
adaboost训练强分类器的基本流程:
1、初始化训练样本的类别与权重分布。
2、迭代循环训练弱分类器。
3、将每次循环训练成的弱分类器与已经存在的弱分类器组成的强分类器。
4、根据当前强分类器计算正样本置信度,根据传入参数minhitrate来取得强分类器阈值。
5、用当前强分类器与上步计算得到阈值,分类负样本,如果分类的最大检率小于maxfalsealarm。跳出循环,强分类器训练完成。
下面结合一般的adaboost的算法原理、opencv源码、记录下我对adaboost的算法流程的详细理解:
给定一个训练数据集 ,其中属于标记样本是正样本还是负样本的标记集合{-1,+1},一般-1表示负样本,+1表示正样本。在人脸分类器的训练中,是计算出来的正样本人脸图片与负样本非人脸图片的某个haar特征的集合。Adaboost的目的就是从训练数据集T中学习一系列弱分类器(特征表述+特征阈值),然后将这些弱分类器组合成一个强分类器(弱分类器+阈值)来尽可能准确的分类xi以达到能够分类一个新的样本是正样本还是负样本。
在opencv中函数训练强分类器的函数是:icvCreateCARTStageClassifier。这里要特别说明下,在opencv中为训练强分类器提供了四种方法分别是Discrete AdaBoost(DAB)、Real AdaBoost(RAB)、Logit Boost(LB)和Gentle AdaBoost(GAB)算法。我们通常使用的较多的都是GAB训练算法,也是最简单的,这里是几种训练算法的区别介绍::http://www.cnblogs.com/jcchen1987/p/4581651.html
这里就DAB算法逻辑做一个介绍::::::::::::::::::
1、首先,初始化训练数据的权值分布。每一个训练样本最开始时都被赋予相同的权值:1/N。
opencv源码是在函数cvBooWstStartTraining中对训练的样本数据集进行权重的初始化,以及标识每个样本来初始化数据集
2、进入循环,进行多轮迭代,把每次迭代记做m
A、通过函数 cvTrimWeights来剔除小权值的样本:对实际存在的样本按权重的大小排序,找到权重高于总权重的weightfraction倍的样本保留下来,用来训练接下来的弱分类器。
B、使用前面一步保留下来的权值分布为Dm样本集学习训练,得到一个弱分类器,在函数cvCreateCARTClassifier中实现,记公式如下:
具体如何训练弱分类,见前面几个博客笔记:cvCreateCARTClassifier
C、用上步训练出来的弱分类器Gm(x)计算每个样本的分类置信度(具体调用的是函数icvEvalCARTHaarClassifier这里源码中用的是函数指针有点难找到,具体可以看下面我贴出的代码中的注释),将计算出来的每个样本的置信度传入cvBoostNextWeakClassifier函数计算该弱分类器Gm(X)在数据集上的分类误差em,这里四种不同的分类器训练方法有不同的计算方式,其中DAB的计算方法如下,在函数icvBoostNextWeakClassifierDAB中实现:
D、在上述函数icvBoostNextWeakClassifierDAB中紧接着用公式
计算了当前弱分类器Gm(X)在当前强分类器中的重要程序,这个公式意味着分类误差率越小的弱分类器在强分类器中的权重越高,作用越大。
E、在函数icvBoostNextWeakClassifierDAB中接着遍历所有样本,更新训练样本的数据集的权值分布得到Dm+1用于下轮迭代来训练弱分类器,公式如下:
其中:Zm是一个规一化因子:
通俗的说就是:遍历每个样本更新其权重Wm+1,i=Wm,i*exp(…) , 同时累加每个样本计算得到的权重Wm+1,i得到Zm,然后规一化下。通过公式可以看出当Gm(xi)分类这个样本正确时,则计算得到的Wm+1,i比Wm,i大,反之,Wm+1,i比Wm,i小,这样就实现了将本次分类正确的样本权值减小,本次分类错误的样本权值增大,这样在下次训练弱分类器里更多的聚焦于本次被分错的样本。
F、从函数icvBoostNextWeakClassifierDAB返回后,得到了当前弱分类器的权重。将该弱分类器以如下公式的方式加入到当前强分类器中:
G、紧接着,在函数icvCreateCARTStageClassifier中分开处理正负样本,遍历所有正样本,用当前强分类器中已经训练出来的弱分类器来计算每个正样本的置信度,累加得到置信度累加和,将这些每个样本的置信度累加和排序,根据minhitrate来计算当前强分类器的阈值threshold。
H、再遍历所有负样本,用当前强分类器中已经训练出来的弱分类器来计算每个负样本的置信度累加和,再用前面计算得到的threshold来判断每个负样本的类别,统计负样本的分类总数,得到负样本的误检率falsealarm,如果误检率小于输入的参数maxfalsealarm,则跳出迭代循环,当前强分类器训练完成!!!!
下面上源码和注释(结合代码与原理更容易理解)::::
staticCvIntHaarClassifier* icvCreateCARTStageClassifier( CvHaarTrainingData* data, // 全部训练样本 CvMat* sampleIdx, // 实际训练样本序列 CvIntHaarFeatures* haarFeatures,// 全部HAAR特征 float minhitrate,// 最小正检率(用于确定强分类器阈值) float maxfalsealarm,// 最大误检率(用于确定是否收敛) int symmetric,// HAAR是否对称 float weightfraction,// 样本剔除比例(用于剔除小权值样本) int numsplits,// 每个弱分类器特征个数(一般为1) CvBoostType boosttype,// adaboost类型 CvStumpError stumperror,// Discrete AdaBoost中的阈值计算方式 int maxsplits ) // 弱分类器最大个数 {#ifdef CV_COL_ARRANGEMENT int flags = CV_COL_SAMPLE;#else int flags = CV_ROW_SAMPLE;#endif CvStageHaarClassifier* stage = NULL;// 强分类器 CvBoostTrainer* trainer;// 临时训练器,用于更新样本权值 CvCARTClassifier* cart = NULL;// 弱分类器 CvCARTTrainParams trainParams;// 训练参数 CvMTStumpTrainParams stumpTrainParams;// 弱分类器参数 //CvMat* trainData = NULL; //CvMat* sortedIdx = NULL; CvMat eval;// 临时矩阵 int n = 0;// 特征总数 int m = 0;// 总样本个数 int numpos = 0;// 正样本个数 int numneg = 0; // 负样本个数 int numfalse = 0; // 误检样本个数 float sum_stage = 0.0F;// 置信度累积和 float threshold = 0.0F;// 强分类器阈值 float falsealarm = 0.0F; // 误检率 //CvMat* sampleIdx = NULL; CvMat* trimmedIdx; // 剔除小权值之后的样本序列 //float* idxdata = NULL; //float* tempweights = NULL; //int idxcount = 0; CvUserdata userdata;// 训练数据 int i = 0; int j = 0; int idx; int numsamples;// 实际样本个数 int numtrimmed;// 剔除小权值之后的样本个数 CvCARTHaarClassifier* classifier; CvSeq* seq = NULL; CvMemStorage* storage = NULL; CvMat* weakTrainVals; float alpha; float sumalpha; int num_splits; /* total number of splits in all weak classifiers */#ifdef CV_VERBOSE printf( "+----+----+-+---------+---------+---------+---------+\n" ); printf( "| N |%%SMP|F| ST.THR | HR | FA | EXP. ERR|\n" ); printf( "+----+----+-+---------+---------+---------+---------+\n" );#endif /* CV_VERBOSE */ n = haarFeatures->count; m = data->sum.rows; numsamples = (sampleIdx) ? MAX( sampleIdx->rows, sampleIdx->cols ) : m; //样本与HAAR特征 userdata = cvUserdata( data, haarFeatures ); /* 弱分类参数设置 */ stumpTrainParams.type = ( boosttype == CV_DABCLASS ) ? CV_CLASSIFICATION_CLASS : CV_REGRESSION; // 分类或者回归 stumpTrainParams.error = ( boosttype == CV_LBCLASS || boosttype == CV_GABCLASS ) ? CV_SQUARE : stumperror;// 弱分类器阈值计算方式 stumpTrainParams.portion = CV_STUMP_TRAIN_PORTION;// 每组特征个数 stumpTrainParams.getTrainData = icvGetTrainingDataCallback;// 计算样本的haar值,函数指针 stumpTrainParams.numcomp = n; // 特征个数 stumpTrainParams.userdata = &userdata; stumpTrainParams.sortedIdx = data->idxcache;// 特征-样本序号矩阵(排序之后) // 由于参数众多,所以创建参数结构体 trainParams.count = numsplits;// 弱分类器特征树 trainParams.stumpTrainParams = (CvClassifierTrainParams*) &stumpTrainParams;// 弱分类参数 trainParams.stumpConstructor = cvCreateMTStumpClassifier;// 筛选最优弱分类器,函数指针 trainParams.splitIdx = icvSplitIndicesCallback; // CART节点分裂函数 trainParams.userdata = &userdata; //临时向量,用于存放样本haar特征值 eval = cvMat( 1, m, CV_32FC1, cvAlloc( sizeof( float ) * m ) ); storage = cvCreateMemStorage(); //最优弱分类器存储序列 seq = cvCreateSeq( 0, sizeof( *seq ), sizeof( classifier ), storage ); // 样本类别,只有logitboost才会用到 weakTrainVals = cvCreateMat( 1, m, CV_32FC1 ); // 初始化样本类别与权重,weakTrainVals为{-1, 1},权重都一样,LB的权重初值与其他不一样 trainer = cvBoostStartTraining( &data->cls, weakTrainVals, &data->weights, sampleIdx, boosttype ); num_splits = 0; sumalpha = 0.0F; do /*这里每次循环创建一个弱分类器,一个弱分类器可以凶括几个特征,但是一般只有一个特征*/ { #ifdef CV_VERBOSE int v_wt = 0; int v_flipped = 0; #endif /* CV_VERBOSE */ //剔除小权值样本 trimmedIdx = cvTrimWeights( &data->weights, sampleIdx, weightfraction ); numtrimmed = (trimmedIdx) ? MAX( trimmedIdx->rows, trimmedIdx->cols ) : m; // 实际样本总数 #ifdef CV_VERBOSE v_wt = 100 * numtrimmed / numsamples; v_flipped = 0; #endif /* CV_VERBOSE */ //重要函数,创建CART树的同时,当前最优弱分类器出炉,一般只有根节点~因为一般一个弱分类器只有一个特征由numsplits决定 cart = (CvCARTClassifier*) cvCreateCARTClassifier( data->valcache, flags, weakTrainVals, 0, 0, 0, trimmedIdx, &(data->weights), (CvClassifierTrainParams*) &trainParams ); //创建弱分类器,按分类器的结构体分配一个内存 这个函数指定了eval/save/release三个变量调用的实际函数 classifier = (CvCARTHaarClassifier*) icvCreateCARTHaarClassifier( numsplits ); // 将CART树转化为弱分类器 icvInitCARTHaarClassifier( classifier, cart, haarFeatures ); num_splits += classifier->count; /*这里又把释放了*/ cart->release( (CvClassifier**) &cart ); /*必需要在对称的前提下*/ if( symmetric && (seq->total % 2) ) { float normfactor = 0.0F; CvStumpClassifier* stump; /* flip haar features */ for( i = 0; i < classifier->count; i++ ) { if( classifier->feature[i].desc[0] == 'h' ) { for( j = 0; j < CV_HAAR_FEATURE_MAX && classifier->feature[i].rect[j].weight != 0.0F; j++ ) { classifier->feature[i].rect[j].r.x = data->winsize.width - classifier->feature[i].rect[j].r.x - classifier->feature[i].rect[j].r.width; } } else { int tmp = 0; /* (x,y) -> (24-x,y) */ /* w -> h; h -> w */ for( j = 0; j < CV_HAAR_FEATURE_MAX && classifier->feature[i].rect[j].weight != 0.0F; j++ ) { classifier->feature[i].rect[j].r.x = data->winsize.width - classifier->feature[i].rect[j].r.x; CV_SWAP( classifier->feature[i].rect[j].r.width, classifier->feature[i].rect[j].r.height, tmp ); } } } icvConvertToFastHaarFeature( classifier->feature, classifier->fastfeature, classifier->count, data->winsize.width + 1 ); stumpTrainParams.getTrainData = NULL; stumpTrainParams.numcomp = 1; stumpTrainParams.userdata = NULL; stumpTrainParams.sortedIdx = NULL; for( i = 0; i < classifier->count; i++ ) /*按特征模版循环*/ { for( j = 0; j < numtrimmed; j++ ) /*计算每个样本在这个特征模版下的具体特征值*/ { idx = icvGetIdxAt( trimmedIdx, j ); eval.data.fl[idx] = cvEvalFastHaarFeature( &classifier->fastfeature[i], (sum_type*) (data->sum.data.ptr + idx * data->sum.step), (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step) ); normfactor = data->normfactor.data.fl[idx]; /*缩放因子*/ eval.data.fl[idx] = ( normfactor == 0.0F ) ? 0.0F : (eval.data.fl[idx] / normfactor); } /*cvCreateMTStumpClassifier*/ stump = (CvStumpClassifier*) trainParams.stumpConstructor( &eval, CV_COL_SAMPLE, weakTrainVals, 0, 0, 0, trimmedIdx, &(data->weights), trainParams.stumpTrainParams ); classifier->threshold[i] = stump->threshold; if( classifier->left[i] <= 0 ) { classifier->val[-classifier->left[i]] = stump->left; } if( classifier->right[i] <= 0 ) { classifier->val[-classifier->right[i]] = stump->right; } stump->release( (CvClassifier**) &stump ); } stumpTrainParams.getTrainData = icvGetTrainingDataCallback; stumpTrainParams.numcomp = n; stumpTrainParams.userdata = &userdata; stumpTrainParams.sortedIdx = data->idxcache; #ifdef CV_VERBOSE v_flipped = 1; #endif /* CV_VERBOSE */ } /* if symmetric */ if( trimmedIdx != sampleIdx ) { cvReleaseMat( &trimmedIdx ); trimmedIdx = NULL; } //调用icvEvalCARTHaarClassifier函数,计算每个样本的当前最优弱分类器置信度 for( i = 0; i < numsamples; i++ ) { idx = icvGetIdxAt( sampleIdx, i ); eval.data.fl[idx] = classifier->eval( (CvIntHaarClassifier*) classifier, /*这是本级强分类器的弱分类器列表,每次新得到一个弱分类器都加进来 */ (sum_type*) (data->sum.data.ptr + idx * data->sum.step), (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step), data->normfactor.data.fl[idx] ); } //更新样本权重,如果是LogitBoost,也会更新weakTrainVals,函数返回的是弱分类器权重 /*这里四种不同的adaboost提升方法*/ alpha = cvBoostNextWeakClassifier( &eval, &data->cls, weakTrainVals, &data->weights, trainer ); //这个变量没什么用 sumalpha += alpha; for( i = 0; i <= classifier->count; i++ ) { if( boosttype == CV_RABCLASS ) /*RAB额外再log一次*/ { classifier->val[i] = cvLogRatio( classifier->val[i] ); } classifier->val[i] *= alpha; /*更新分类器权重DAB是依照常见公式,GAB始终是1也就是每个弱分类器的权重相同*/ } //添加弱分类器,classifier是弱分类器 cvSeqPush( seq, (void*) &classifier ); //遍历sampleIdx中所有样本,计算每个样本的弱分类器置信度和 numpos = 0; for( i = 0; i < numsamples; i++ ) { // 获得样本序号 idx = icvGetIdxAt( sampleIdx, i ); // 如果样本为正样本 if( data->cls.data.fl[idx] == 1.0F ) { //初始化置信度值 eval.data.fl[numpos] = 0.0F; // 遍历seq中所有弱分类器 for( j = 0; j < seq->total; j++ ) { // 获取弱分类器 classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j )); // 累积当前正样本的弱分类器置信度和 //eval == CV_INT_HAAR_CLASSIFIER_FIELDS==icvEvalCARTHaarClassifier eval.data.fl[numpos] += classifier->eval((CvIntHaarClassifier*) classifier, (sum_type*) (data->sum.data.ptr + idx * data->sum.step), (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step), data->normfactor.data.fl[idx] ); } /* eval.data.fl[numpos] = 2.0F * eval.data.fl[numpos] - seq->total; */ numpos++; } } // 对弱分类器输出置信度和进行排序 icvSort_32f( eval.data.fl, numpos, 0 ); // 计算阈值,应该是大于threshold则为正类,小于threshold则为负类 threshold = eval.data.fl[(int) ((1.0F - minhitrate) * numpos)]; // 遍历所有样本,统计错分负样本个数 numneg = 0; numfalse = 0; for( i = 0; i < numsamples; i++ ) { idx = icvGetIdxAt( sampleIdx, i ); // 如果样本为负样本 if( data->cls.data.fl[idx] == 0.0F ) { // 遍历seq中所有弱分类器 numneg++; sum_stage = 0.0F; for( j = 0; j < seq->total; j++ ) { classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j )); // 累积当前负样本的分类器输出结果 sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier, (sum_type*) (data->sum.data.ptr + idx * data->sum.step), (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step), data->normfactor.data.fl[idx] ); } // 因为小于threshold为负类,所以下面是分类错误的情况 /* sum_stage = 2.0F * sum_stage - seq->total; */ if( sum_stage >= (threshold - CV_THRESHOLD_EPS) ) { numfalse++; } } } // 因为小于threshold为负类,所以下面是分类错误的情况 falsealarm = ((float) numfalse) / ((float) numneg); #ifdef CV_VERBOSE { // 正样本检出率 float v_hitrate = 0.0F; // 负样本误检率 float v_falsealarm = 0.0F; /* expected error of stage classifier regardless threshold */ float v_experr = 0.0F; // 遍历所有样本 for( i = 0; i < numsamples; i++ ) { idx = icvGetIdxAt( sampleIdx, i ); sum_stage = 0.0F; // 遍历seq中所有弱分类器 for( j = 0; j < seq->total; j++ ) { classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j )); sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier, (sum_type*) (data->sum.data.ptr + idx * data->sum.step), (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step), data->normfactor.data.fl[idx] ); } /* sum_stage = 2.0F * sum_stage - seq->total; */ // 只需要判断单一分支即可 if( sum_stage >= (threshold - CV_THRESHOLD_EPS) ) { if( data->cls.data.fl[idx] == 1.0F ) { v_hitrate += 1.0F; } else { v_falsealarm += 1.0F; } } // 正类样本的sum_stage必须大于0 if( ( sum_stage >= 0.0F ) != (data->cls.data.fl[idx] == 1.0F) ) { v_experr += 1.0F; } } v_experr /= numsamples; printf( "|%4d|%3d%%|%c|%9f|%9f|%9f|%9f|\n", seq->total, v_wt, ( (v_flipped) ? '+' : '-' ), threshold, v_hitrate / numpos, v_falsealarm / numneg, v_experr ); printf( "+----+----+-+---------+---------+---------+---------+\n" ); fflush( stdout ); } #endif /* CV_VERBOSE */ } while( falsealarm > maxfalsealarm && (!maxsplits || (num_splits < maxsplits) ) ); cvBoostEndTraining( &trainer ); if( falsealarm > maxfalsealarm ) { stage = NULL; } else { /*这里创建输出的强分类器*/ stage = (CvStageHaarClassifier*) icvCreateStageHaarClassifier( seq->total, threshold ); cvCvtSeqToArray( seq, (CvArr*) stage->classifier ); } /* CLEANUP */ cvReleaseMemStorage( &storage ); cvReleaseMat( &weakTrainVals ); cvFree( &(eval.data.ptr) ); return (CvIntHaarClassifier*) stage;}
感谢:::http://www.cnblogs.com/harvey888/p/5505511.html
- adaboost训练 之 强分类器训练原理
- adaboost训练——强分类器训练原理
- adaboost训练 之 弱分类器训练原理
- 关于Adaboost强分类器的训练
- adaboost训练——弱分类器训练原理
- adaboost算法分类器的训练
- 基于Adaboost检测的分类器训练
- Cascade Adaboost级联分类器的训练
- Adaboost算法分类器级联结构、训练
- adaboost训练 之 弱分类器训练的opencv源码详解 1
- adaboost训练 之 弱分类器训练的opencv源码详解 2
- adaboost训练之经验总结
- adaboost训练之经验总结
- icvCreateCARTStageClassifier训练强分类器源代码框架
- Adaboost应用系列之二:Opencv2.0中利用Adaboost训练LBP特征产生xml分类器
- Adaboost应用系列之三:Opencv2.4.X中利用Adaboost训练HOG特征产生xml分类器
- Adaboost应用系列之二:Opencv2.0中利用Adaboost训练LBP特征产生xml分类器
- Adaboost应用系列之三:Opencv2.4.X中利用Adaboost训练HOG特征产生xml分类器
- maven利用Profile构建不同环境的部署包
- C语言的条件编译
- 穿越回过去,妹纸怎么活(分布式架构演化)
- error: undefined reference to `_imp__glShadeModel@4
- 珠海鼎芯(D-Chip)IMX6读取CPU的UID的方法
- adaboost训练 之 强分类器训练原理
- hadoop安装 (二)
- Messenger进程间通信及其原理
- Android热修复学习(三)微信热修复 tinker
- C++ 变量类型
- netty启程之路(一):Linux的五种网络I/O模型
- Redis jedis API注释
- APK反编译
- LUA,C#中float型和double型的隐式舍入操作