takminの書きっぱなし備忘録 @はてなブログ

主にコンピュータビジョンなど技術について、たまに自分自身のことや思いついたことなど

Viola & Johns版AdaBoostの実装 その2

前回の続き。

本論に入る前に、ここで解説したAdaBoostのアルゴリズムをもうちょい補足しておきます。

  1. 学習データを用意し、それぞれのデータの重みを均等にしておく。
  2. 学習データを識別器へ入力し、エラー率を求める。ただし、エラー率には学習データの重みを反映させる。
  3. 一番エラー率の低かった識別器を選択し、そのエラー率を記録しておく。
  4. 3で正解した学習データの重みは低く、不正解だったデータの重みを重くする。
  5. 更新した重みを元に2〜4の処理を複数回繰り返す。
  6. 最終的に選択した識別器に、エラー率に基づいた重みを割り振り(エラー率が低ければ重く、逆なら軽く)、その線形和を強識別器とする。

もちっと詳しく知りたい人は、この論文の4ページ目の囲いを読んでください。

で、ここから本題。

前回も解説したとおり、OpenCVに実装されているオブジェクト検出"cvHaarDetectObjects()"関数は、CvBoostクラスを使用しておらず、独自にAdaBoostを実装しています。

その関数のソースは、"apps/HaarTraining/src/cvboost.cpp"に実装されていて、実際に学習で使用するのは以下の3つの関数:

  • icvBoostStartTraining()
  • icvBoostNextWeakClassifier()
  • icvBoostEndTraining()

というわけで、いきなりソースをさらすと

tmBoost.h

#include 
#include 
#include "ml.h"

// Viola & Johnsのアルゴリズムで用いたAdaBoostの実装
class tmBoost
{
public:
	tmBoost(void);
public:
	~tmBoost(void);

private:
	CvDTree* weakClassifier;
	int	num_classifier;
	float* alpha;
	int* index;
	int weak_num;

public:
	int train(CvMat* data, CvMat* response, int num_select);  // 学習関数
	float predict(const CvMat* inputMat);   // 予測関数

protected:
	CvMat* preCalcEvalVals(CvMat *data);
};

tmBoost.cpp

/******************qsort用******************/
struct qsort_data{
	float data;
	int	ref;
};

int compare_data(const void *a, const void *b)
{
	if(((struct qsort_data *)a)->data > *1;
			evalVals->data.fl[num_classifier * j + i] = weakClassifier[i].predict(sample)->value;
		}
	}
	cvReleaseMat(&sample);
	return evalVals;
}


/********* Discreet AdaBoostによるトレーニング *********/
// data:学習データ
// res:教師データ
// num_select: 繰り返し計算の数
int tmBoost::train(CvMat* data, CvMat* res, int num_select)
{
	assert(data != NULL);
	assert(res != NULL);
	assert(data->rows == res->rows);
	assert(CV_MAT_TYPE(data->type)==CV_32FC1);
	assert(CV_MAT_TYPE(res->type)==CV_32FC1 || CV_MAT_TYPE(res->type)==CV_32SC1);

	/*** 弱識別器の学習 ここから ***/
	CvMat* res2 = cvCreateMat(res->rows, res->cols, CV_32FC1);
	if(CV_MAT_TYPE(res->type)==CV_32SC1){
		cvConvert(res,res2);
	} else{
		cvCopy(res, res2);
	}

	releaseClassifier();

	int sample_num = data->rows;	// サンプル数
	num_classifier = data->cols;	// データ要素数=識別器の数

	// 弱識別器の割り当て
	weakClassifier = new CvDTree[num_classifier];

	CvDTreeParams param = CvDTreeParams( 1, // max depth
                                 2, // min sample count
                                 0, // regression accuracy: N/A here
                                 false, // compute surrogate split, as we have missing data
                                 2, // max number of categories (use sub-optimal algorithm for larger numbers)
                                 0, // the number of cross-validation folds
                                 false, // use 1SE rule => smaller tree
                                 false, // throw away the pruned tree branches
                                 0 // the array of priors, the bigger p_weight, the more attention
                                 );

	int i;
	CvMat* var_idx = cvCreateMat(1,1,CV_32SC1);
	CvMat* var_type = cvCreateMat( data->cols + 1, 1, CV_8U );
	cvSet( var_type, cvScalarAll(CV_VAR_NUMERICAL) ); // 入力データは連続値
	var_type->data.ptr[num_classifier] = CV_VAR_CATEGORICAL;	// 教師データは離散値(ラベル)

	/*** 弱識別器の学習 ***/
	for(i=0;idata.i[0] = i;	// 学習に使用する要素
		weakClassifier[i].train(data, CV_ROW_SAMPLE, res2, var_idx, 0, var_type, 0, param);
	}

	cvReleaseMat(&var_idx);
	cvReleaseMat(&var_type);

	/*** 弱識別器の学習 ここまで ***/

	CvMat* weakEvalVals = cvCreateMat(sample_num, 1, CV_32FC1);	// 弱識別器の評価データを格納
	CvMat* weakTrainVals = cvCreateMat(sample_num,1, CV_32FC1);	// 教師データが変換されて格納される
	CvMat* weights = cvCreateMat(sample_num, 1, CV_32FC1);		// 入力データへの重み

	/* positiveデータとnegativeデータの数をカウント */
	int p_num = 0;
	int n_num = 0;
	for(i=0;idata.i[i] > 0) ? p_num++ : n_num++;
	}

	/* データに重みを均等に割り当て */
	float p_val = 0.5 / p_num;
	float n_val = 0.5 / n_num;

	for(i=0;idata.fl[i] = (res2->data.i[i] > 0) ? p_val : n_val;
	}

	// Boostingに必要な情報を取得
	CvBoostTrainer *trainer = icvBoostStartTraining(res2, weakTrainVals, weights, NULL, CV_DABCLASS);

	float* tmp_alpha = (float*)malloc(num_select * sizeof(float));
	int* tmp_index = (int*)malloc(num_select*sizeof(int));

	CvMat *tmpEvalVals = preCalcEvalVals(data);

	weak_num = num_select;

	// Boosting
	for(i=0;i 0){
			weak_num++;
			qdatas[i].data /= sum_alpha;
		}
	}
	alpha = (float*)malloc(weak_num * sizeof(float));
	index = (int*)malloc(weak_num * sizeof(int));

	for(i=0;ivalue;
		suma += alpha[i];
	}
	
	return (val >= suma/2) ? 1 : 0;
}

と、こんな感じ。
train関数の引数dataはN行M列の行列で、一行一行が浮動小数点ベクトルであらわすトレーニングサンプルとなっています。ここでは、N個のトレーニングサンプルから、浮動小数点ベクトルの個々の要素を用いてM個の弱識別器を作ることを想定しています。

resは応答データ(教師信号)で、N行1列の行列です。
resはdataのトレーニングサンプル一行一行に対応しており、たとえばdataの1行目のトレーニングサンプルが「正解」データなら、resの1行目は"1"に、dataの4行目のトレーニングサンプルが「非正解」データなら、resの4行目は"0"になります。(イメージとしては、ここで用いたサンプルと同じ)

predict関数の引数sampleは、1行M列のデータです。


以下、トレーニングの流れを簡単に解説

  1. 各N個の入力サンプルを元に、M個の弱識別器を二分木(CvDTree)でトレーニン
  2. icvBoostStartTraining()でAdaBoostのために必要なパラメータを取得
  3. 各入力サンプルをM個の識別器へ入力し、それぞれの誤差を求める。(preCalcEvalVas())
  4. 誤差の重みつき総和を求め、もっとも小さい総和を持つ識別器を選択する。(calcWeakEvalVals())
  5. 選択した識別器とその誤差に基づき、icvBoostNextWeakClassifier()で、重みの更新。その返り値をα(その識別器の重み)として保存。
  6. 4,5を指定した回数(num_select)繰り返す。
  7. icvBoostEndTraining()で終了。

以上、かなりマニアックな解説でした。

*1:struct qsort_data *)b)->data) return 1; else return -1; } /**** Boosting事前計算 ****/ // もっともエラーが低い識別器を選択 int calcWeakEvalVals(CvMat *evalVals, CvMat* res, CvMat *weights, CvMat *weakEvalVals) { int sample_num = evalVals->rows; int num_classifier = evalVals->cols; float min = (float)sample_num; float val, val2, val3; CvMat* sample = cvCreateMat(1,num_classifier,CV_32FC1); int i; int ret_i = -1; for(i=0;idata.fl[num_classifier * j + i]; val3 = (res->data.fl[j]>0) ? (val>0 ? 0 : 1) : (val>0 ? 1 : 0); val2 += (weights->data.fl[j] * val3); } if(val2 < min){ min = val2; ret_i = i; } } for(i=0;idata.fl[i] = 2.0F * (evalVals->data.fl[num_classifier * i + ret_i]) - 1.0F; } cvReleaseMat(&sample); return ret_i; } // それぞれのサンプルデータをそれぞれの弱識別器で計算した結果を返す CvMat* tmBoost::preCalcEvalVals(CvMat *data) { int sample_num = data->rows; CvMat* sample = cvCreateMat(1,num_classifier,CV_32FC1); CvMat* evalVals = cvCreateMat(sample_num,num_classifier, CV_32FC1); for(int i=0;idata.fl,(data->data.fl + j*num_classifier),num_classifier*sizeof(float