最近看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,则跳出迭代循环,当前强分类器训练完成!!!!

下面上源码和注释(结合代码与原理更容易理解)::::

static
CvIntHaarClassifier* 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_ARRANGEMENTint flags = CV_COL_SAMPLE;
#elseint flags = CV_ROW_SAMPLE;
#endifCvStageHaarClassifier* 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_VERBOSEprintf( "+----+----+-+---------+---------+---------+---------+\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_VERBOSEint 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_VERBOSEv_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_VERBOSEv_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==icvEvalCARTHaarClassifiereval.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必须大于0if( ( 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训练 之 强分类器训练原理相关推荐

  1. adaboost训练 之 弱分类器训练原理

    二叉决策树介绍 二叉决策树由LeoBreiman和他的同事提出.他们称之为"分类和回归树(CART)".OpenCV实现的就是"分类回归树".简单地说,二叉决策 ...

  2. opencv学习笔记——自己训练人脸识别分类器

    在使用opencv自带的分类器haarcascade_frontalface_alt.xml进行人脸识别的基础认识后,决定自己训练一个分类器看一下效果.该过程大致可分为三个阶段:样本采集.分类器训练和 ...

  3. Python口罩识别检测全网最全OpenCV训练分类器具体步骤(以训练口罩检测分类器为例)附分类器和数据集下载地址

    声明 本文仅在CSDN发布,其他均为盗版.请支持正版! 正版链接: https://blog.csdn.net/meenr/article/details/115825671 OpenCV训练口罩检测 ...

  4. 人脸检测(十)--强分类器源码分析

    原文: http://blog.csdn.net/beerbuddys/article/details/40712957 下面的内容很长,倒杯水(有茶或者咖啡更好),带上耳机,准备就绪再往下看.下面我 ...

  5. 迁移学习算法之TrAdaBoost ——本质上是在用不同分布的训练数据,训练出一个分类器...

    迁移学习算法之TrAdaBoost from: https://blog.csdn.net/Augster/article/details/53039489 TradaBoost算法由来已久,具体算法 ...

  6. 人脸检测:经典的VJ人脸检测器(类Harr特征,积分图加速法,级联的Adaboost强分类器)

    著名的VJ人脸检测算法就是一种基于Adaboost分类器的方法.该检测器由Paul Viola和Michael Jones在2001年的 Robust Real-Time Face Detection ...

  7. 《推荐系统笔记(三)》Adaboost算法 —— 弱分类器组合成强分类器的方法

    前言 我们将介绍将弱分类器组合成强分类器的算法,Adaboost算法,以及该算法有效的证明. 对于这种提升方法,我们有 每次迭代加大误分类点的权重,这样下次生成的弱分类器能够更可能将该误分类点分类正确 ...

  8. python训练opencb分类器_opencv 训练自己的分类器汇总

    原地址:http://www.cnblogs.com/zengqs/archive/2009/02/12/1389208.html OpenCV训练分类器 OpenCV训练分类器 一.简介 目标检测方 ...

  9. HOG人体特征提取+SVM分类器训练进行人体检测

    1.HOG特征: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和统计图像局部区域的 ...

最新文章

  1. CMD命令,常驻内存的与外部的介绍 还有CMD格式化
  2. 高铁转地铁不想重复安检?多地已有举措
  3. python pynlpir中科院分词的使用
  4. nginx反向代理+缓存开启+url重写+负载均衡(带健康探测)的部署记录
  5. NET问答: 为什么仅有 getter 的属性,还可以在构造函数中赋值 ?
  6. 背景色透明,里面内容(图片、文字)不透明
  7. 如何控制表格的宽度_利用Word制作表格,这些实用技巧一定要知道!制作表格更加快捷...
  8. Leetcode 34.排序数组中的第一个元素和最后一个元素
  9. nvidia tesla k40
  10. 数据挖掘实战—餐饮行业的数据挖掘之数据预处理
  11. 精讲!!! Web服务器基础与http协议
  12. 【精华贴】一键启动bat脚本——Anaconda Jupyter Notebook
  13. 计算机上机考试自我检查800字,【考试太差 自我反省检讨书800字】_考试成绩差自我反省检讨书范文3篇...
  14. 基于MATLAB的混沌密码与数字图像加密应用、信息隐藏
  15. IT30: IT人创业项目路演!!!
  16. 出现nginx: [emerg] unknown directive “]“ in /usr/local/openresty/nginx/conf/nginx.conf:74
  17. SQL AUTO INCREMENT
  18. 实战 - 学成在线项目
  19. 【智联沙龙活动】混合云云平台PaaS技术分享
  20. 以编程方式使用 Word 中的内置对话框

热门文章

  1. react修改webpack配置,添加别名
  2. 华为c8815手机在开发Android调试时logcat不显示输出信息的解决办法
  3. 虚树-树上动态规划的利器
  4. 介质访问控制MAC以及ALOHA协议
  5. 密码学:分组密码.(块密码:是一种对称密码算法)
  6. X86汇编学习小结----cmp 完整版本
  7. 微服务拆分原则之AKF
  8. memcpy内存拷贝和=直接赋值有什么区别
  9. 同花顺选股python开发_Funcat 将同花顺、通达信等的公式写法移植到了 Python 中
  10. java ref传引用_java引用传递