123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- #include "opencv2/core.hpp"
- #include "opencv2/ml.hpp"
- #include <cstdio>
- #include <vector>
- #include <iostream>
- using namespace std;
- using namespace cv;
- using namespace cv::ml;
- static void help(char** argv)
- {
- printf("\nThe sample demonstrates how to train Random Trees classifier\n"
- "(or Boosting classifier, or MLP, or Knearest, or Nbayes, or Support Vector Machines - see main()) using the provided dataset.\n"
- "\n"
- "We use the sample database letter-recognition.data\n"
- "from UCI Repository, here is the link:\n"
- "\n"
- "Newman, D.J. & Hettich, S. & Blake, C.L. & Merz, C.J. (1998).\n"
- "UCI Repository of machine learning databases\n"
- "[http://www.ics.uci.edu/~mlearn/MLRepository.html].\n"
- "Irvine, CA: University of California, Department of Information and Computer Science.\n"
- "\n"
- "The dataset consists of 20000 feature vectors along with the\n"
- "responses - capital latin letters A..Z.\n"
- "The first 16000 (10000 for boosting)) samples are used for training\n"
- "and the remaining 4000 (10000 for boosting) - to test the classifier.\n"
- "======================================================\n");
- printf("\nThis is letter recognition sample.\n"
- "The usage: %s [-data=<path to letter-recognition.data>] \\\n"
- " [-save=<output XML file for the classifier>] \\\n"
- " [-load=<XML file with the pre-trained classifier>] \\\n"
- " [-boost|-mlp|-knearest|-nbayes|-svm] # to use boost/mlp/knearest/SVM classifier instead of default Random Trees\n", argv[0] );
- }
- // This function reads data and responses from the file <filename>
- static bool
- read_num_class_data( const string& filename, int var_count,
- Mat* _data, Mat* _responses )
- {
- const int M = 1024;
- char buf[M+2];
- Mat el_ptr(1, var_count, CV_32F);
- int i;
- vector<int> responses;
- _data->release();
- _responses->release();
- FILE* f = fopen( filename.c_str(), "rt" );
- if( !f )
- {
- cout << "Could not read the database " << filename << endl;
- return false;
- }
- for(;;)
- {
- char* ptr;
- if( !fgets( buf, M, f ) || !strchr( buf, ',' ) )
- break;
- responses.push_back((int)buf[0]);
- ptr = buf+2;
- for( i = 0; i < var_count; i++ )
- {
- int n = 0;
- sscanf( ptr, "%f%n", &el_ptr.at<float>(i), &n );
- ptr += n + 1;
- }
- if( i < var_count )
- break;
- _data->push_back(el_ptr);
- }
- fclose(f);
- Mat(responses).copyTo(*_responses);
- cout << "The database " << filename << " is loaded.\n";
- return true;
- }
- template<typename T>
- static Ptr<T> load_classifier(const string& filename_to_load)
- {
- // load classifier from the specified file
- Ptr<T> model = StatModel::load<T>( filename_to_load );
- if( model.empty() )
- cout << "Could not read the classifier " << filename_to_load << endl;
- else
- cout << "The classifier " << filename_to_load << " is loaded.\n";
- return model;
- }
- static Ptr<TrainData>
- prepare_train_data(const Mat& data, const Mat& responses, int ntrain_samples)
- {
- Mat sample_idx = Mat::zeros( 1, data.rows, CV_8U );
- Mat train_samples = sample_idx.colRange(0, ntrain_samples);
- train_samples.setTo(Scalar::all(1));
- int nvars = data.cols;
- Mat var_type( nvars + 1, 1, CV_8U );
- var_type.setTo(Scalar::all(VAR_ORDERED));
- var_type.at<uchar>(nvars) = VAR_CATEGORICAL;
- return TrainData::create(data, ROW_SAMPLE, responses,
- noArray(), sample_idx, noArray(), var_type);
- }
- inline TermCriteria TC(int iters, double eps)
- {
- return TermCriteria(TermCriteria::MAX_ITER + (eps > 0 ? TermCriteria::EPS : 0), iters, eps);
- }
- static void test_and_save_classifier(const Ptr<StatModel>& model,
- const Mat& data, const Mat& responses,
- int ntrain_samples, int rdelta,
- const string& filename_to_save)
- {
- int i, nsamples_all = data.rows;
- double train_hr = 0, test_hr = 0;
- // compute prediction error on train and test data
- for( i = 0; i < nsamples_all; i++ )
- {
- Mat sample = data.row(i);
- float r = model->predict( sample );
- r = std::abs(r + rdelta - responses.at<int>(i)) <= FLT_EPSILON ? 1.f : 0.f;
- if( i < ntrain_samples )
- train_hr += r;
- else
- test_hr += r;
- }
- test_hr /= nsamples_all - ntrain_samples;
- train_hr = ntrain_samples > 0 ? train_hr/ntrain_samples : 1.;
- printf( "Recognition rate: train = %.1f%%, test = %.1f%%\n",
- train_hr*100., test_hr*100. );
- if( !filename_to_save.empty() )
- {
- model->save( filename_to_save );
- }
- }
- static bool
- build_rtrees_classifier( const string& data_filename,
- const string& filename_to_save,
- const string& filename_to_load )
- {
- Mat data;
- Mat responses;
- bool ok = read_num_class_data( data_filename, 16, &data, &responses );
- if( !ok )
- return ok;
- Ptr<RTrees> model;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.8);
- // Create or load Random Trees classifier
- if( !filename_to_load.empty() )
- {
- model = load_classifier<RTrees>(filename_to_load);
- if( model.empty() )
- return false;
- ntrain_samples = 0;
- }
- else
- {
- // create classifier by using <data> and <responses>
- cout << "Training the classifier ...\n";
- // Params( int maxDepth, int minSampleCount,
- // double regressionAccuracy, bool useSurrogates,
- // int maxCategories, const Mat& priors,
- // bool calcVarImportance, int nactiveVars,
- // TermCriteria termCrit );
- Ptr<TrainData> tdata = prepare_train_data(data, responses, ntrain_samples);
- model = RTrees::create();
- model->setMaxDepth(10);
- model->setMinSampleCount(10);
- model->setRegressionAccuracy(0);
- model->setUseSurrogates(false);
- model->setMaxCategories(15);
- model->setPriors(Mat());
- model->setCalculateVarImportance(true);
- model->setActiveVarCount(4);
- model->setTermCriteria(TC(100,0.01f));
- model->train(tdata);
- cout << endl;
- }
- test_and_save_classifier(model, data, responses, ntrain_samples, 0, filename_to_save);
- cout << "Number of trees: " << model->getRoots().size() << endl;
- // Print variable importance
- Mat var_importance = model->getVarImportance();
- if( !var_importance.empty() )
- {
- double rt_imp_sum = sum( var_importance )[0];
- printf("var#\timportance (in %%):\n");
- int i, n = (int)var_importance.total();
- for( i = 0; i < n; i++ )
- printf( "%-2d\t%-4.1f\n", i, 100.f*var_importance.at<float>(i)/rt_imp_sum);
- }
- return true;
- }
- static bool
- build_boost_classifier( const string& data_filename,
- const string& filename_to_save,
- const string& filename_to_load )
- {
- const int class_count = 26;
- Mat data;
- Mat responses;
- Mat weak_responses;
- bool ok = read_num_class_data( data_filename, 16, &data, &responses );
- if( !ok )
- return ok;
- int i, j, k;
- Ptr<Boost> model;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.5);
- int var_count = data.cols;
- // Create or load Boosted Tree classifier
- if( !filename_to_load.empty() )
- {
- model = load_classifier<Boost>(filename_to_load);
- if( model.empty() )
- return false;
- ntrain_samples = 0;
- }
- else
- {
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- //
- // As currently boosted tree classifier in MLL can only be trained
- // for 2-class problems, we transform the training database by
- // "unrolling" each training sample as many times as the number of
- // classes (26) that we have.
- //
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- Mat new_data( ntrain_samples*class_count, var_count + 1, CV_32F );
- Mat new_responses( ntrain_samples*class_count, 1, CV_32S );
- // 1. unroll the database type mask
- printf( "Unrolling the database...\n");
- for( i = 0; i < ntrain_samples; i++ )
- {
- const float* data_row = data.ptr<float>(i);
- for( j = 0; j < class_count; j++ )
- {
- float* new_data_row = (float*)new_data.ptr<float>(i*class_count+j);
- memcpy(new_data_row, data_row, var_count*sizeof(data_row[0]));
- new_data_row[var_count] = (float)j;
- new_responses.at<int>(i*class_count + j) = responses.at<int>(i) == j+'A';
- }
- }
- Mat var_type( 1, var_count + 2, CV_8U );
- var_type.setTo(Scalar::all(VAR_ORDERED));
- var_type.at<uchar>(var_count) = var_type.at<uchar>(var_count+1) = VAR_CATEGORICAL;
- Ptr<TrainData> tdata = TrainData::create(new_data, ROW_SAMPLE, new_responses,
- noArray(), noArray(), noArray(), var_type);
- vector<double> priors(2);
- priors[0] = 1;
- priors[1] = 26;
- cout << "Training the classifier (may take a few minutes)...\n";
- model = Boost::create();
- model->setBoostType(Boost::GENTLE);
- model->setWeakCount(100);
- model->setWeightTrimRate(0.95);
- model->setMaxDepth(5);
- model->setUseSurrogates(false);
- model->setPriors(Mat(priors));
- model->train(tdata);
- cout << endl;
- }
- Mat temp_sample( 1, var_count + 1, CV_32F );
- float* tptr = temp_sample.ptr<float>();
- // compute prediction error on train and test data
- double train_hr = 0, test_hr = 0;
- for( i = 0; i < nsamples_all; i++ )
- {
- int best_class = 0;
- double max_sum = -DBL_MAX;
- const float* ptr = data.ptr<float>(i);
- for( k = 0; k < var_count; k++ )
- tptr[k] = ptr[k];
- for( j = 0; j < class_count; j++ )
- {
- tptr[var_count] = (float)j;
- float s = model->predict( temp_sample, noArray(), StatModel::RAW_OUTPUT );
- if( max_sum < s )
- {
- max_sum = s;
- best_class = j + 'A';
- }
- }
- double r = std::abs(best_class - responses.at<int>(i)) < FLT_EPSILON ? 1 : 0;
- if( i < ntrain_samples )
- train_hr += r;
- else
- test_hr += r;
- }
- test_hr /= nsamples_all-ntrain_samples;
- train_hr = ntrain_samples > 0 ? train_hr/ntrain_samples : 1.;
- printf( "Recognition rate: train = %.1f%%, test = %.1f%%\n",
- train_hr*100., test_hr*100. );
- cout << "Number of trees: " << model->getRoots().size() << endl;
- // Save classifier to file if needed
- if( !filename_to_save.empty() )
- model->save( filename_to_save );
- return true;
- }
- static bool
- build_mlp_classifier( const string& data_filename,
- const string& filename_to_save,
- const string& filename_to_load )
- {
- const int class_count = 26;
- Mat data;
- Mat responses;
- bool ok = read_num_class_data( data_filename, 16, &data, &responses );
- if( !ok )
- return ok;
- Ptr<ANN_MLP> model;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.8);
- // Create or load MLP classifier
- if( !filename_to_load.empty() )
- {
- model = load_classifier<ANN_MLP>(filename_to_load);
- if( model.empty() )
- return false;
- ntrain_samples = 0;
- }
- else
- {
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- //
- // MLP does not support categorical variables by explicitly.
- // So, instead of the output class label, we will use
- // a binary vector of <class_count> components for training and,
- // therefore, MLP will give us a vector of "probabilities" at the
- // prediction stage
- //
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- Mat train_data = data.rowRange(0, ntrain_samples);
- Mat train_responses = Mat::zeros( ntrain_samples, class_count, CV_32F );
- // 1. unroll the responses
- cout << "Unrolling the responses...\n";
- for( int i = 0; i < ntrain_samples; i++ )
- {
- int cls_label = responses.at<int>(i) - 'A';
- train_responses.at<float>(i, cls_label) = 1.f;
- }
- // 2. train classifier
- int layer_sz[] = { data.cols, 100, 100, class_count };
- int nlayers = (int)(sizeof(layer_sz)/sizeof(layer_sz[0]));
- Mat layer_sizes( 1, nlayers, CV_32S, layer_sz );
- #if 1
- int method = ANN_MLP::BACKPROP;
- double method_param = 0.001;
- int max_iter = 300;
- #else
- int method = ANN_MLP::RPROP;
- double method_param = 0.1;
- int max_iter = 1000;
- #endif
- Ptr<TrainData> tdata = TrainData::create(train_data, ROW_SAMPLE, train_responses);
- cout << "Training the classifier (may take a few minutes)...\n";
- model = ANN_MLP::create();
- model->setLayerSizes(layer_sizes);
- model->setActivationFunction(ANN_MLP::SIGMOID_SYM, 0, 0);
- model->setTermCriteria(TC(max_iter,0));
- model->setTrainMethod(method, method_param);
- model->train(tdata);
- cout << endl;
- }
- test_and_save_classifier(model, data, responses, ntrain_samples, 'A', filename_to_save);
- return true;
- }
- static bool
- build_knearest_classifier( const string& data_filename, int K )
- {
- Mat data;
- Mat responses;
- bool ok = read_num_class_data( data_filename, 16, &data, &responses );
- if( !ok )
- return ok;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.8);
- // create classifier by using <data> and <responses>
- cout << "Training the classifier ...\n";
- Ptr<TrainData> tdata = prepare_train_data(data, responses, ntrain_samples);
- Ptr<KNearest> model = KNearest::create();
- model->setDefaultK(K);
- model->setIsClassifier(true);
- model->train(tdata);
- cout << endl;
- test_and_save_classifier(model, data, responses, ntrain_samples, 0, string());
- return true;
- }
- static bool
- build_nbayes_classifier( const string& data_filename )
- {
- Mat data;
- Mat responses;
- bool ok = read_num_class_data( data_filename, 16, &data, &responses );
- if( !ok )
- return ok;
- Ptr<NormalBayesClassifier> model;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.8);
- // create classifier by using <data> and <responses>
- cout << "Training the classifier ...\n";
- Ptr<TrainData> tdata = prepare_train_data(data, responses, ntrain_samples);
- model = NormalBayesClassifier::create();
- model->train(tdata);
- cout << endl;
- test_and_save_classifier(model, data, responses, ntrain_samples, 0, string());
- return true;
- }
- static bool
- build_svm_classifier( const string& data_filename,
- const string& filename_to_save,
- const string& filename_to_load )
- {
- Mat data;
- Mat responses;
- bool ok = read_num_class_data( data_filename, 16, &data, &responses );
- if( !ok )
- return ok;
- Ptr<SVM> model;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.8);
- // Create or load Random Trees classifier
- if( !filename_to_load.empty() )
- {
- model = load_classifier<SVM>(filename_to_load);
- if( model.empty() )
- return false;
- ntrain_samples = 0;
- }
- else
- {
- // create classifier by using <data> and <responses>
- cout << "Training the classifier ...\n";
- Ptr<TrainData> tdata = prepare_train_data(data, responses, ntrain_samples);
- model = SVM::create();
- model->setType(SVM::C_SVC);
- model->setKernel(SVM::LINEAR);
- model->setC(1);
- model->train(tdata);
- cout << endl;
- }
- test_and_save_classifier(model, data, responses, ntrain_samples, 0, filename_to_save);
- return true;
- }
- int main( int argc, char *argv[] )
- {
- string filename_to_save = "";
- string filename_to_load = "";
- string data_filename;
- int method = 0;
- cv::CommandLineParser parser(argc, argv, "{data|letter-recognition.data|}{save||}{load||}{boost||}"
- "{mlp||}{knn knearest||}{nbayes||}{svm||}");
- data_filename = samples::findFile(parser.get<string>("data"));
- if (parser.has("save"))
- filename_to_save = parser.get<string>("save");
- if (parser.has("load"))
- filename_to_load = samples::findFile(parser.get<string>("load"));
- if (parser.has("boost"))
- method = 1;
- else if (parser.has("mlp"))
- method = 2;
- else if (parser.has("knearest"))
- method = 3;
- else if (parser.has("nbayes"))
- method = 4;
- else if (parser.has("svm"))
- method = 5;
- help(argv);
- if( (method == 0 ?
- build_rtrees_classifier( data_filename, filename_to_save, filename_to_load ) :
- method == 1 ?
- build_boost_classifier( data_filename, filename_to_save, filename_to_load ) :
- method == 2 ?
- build_mlp_classifier( data_filename, filename_to_save, filename_to_load ) :
- method == 3 ?
- build_knearest_classifier( data_filename, 10 ) :
- method == 4 ?
- build_nbayes_classifier( data_filename) :
- method == 5 ?
- build_svm_classifier( data_filename, filename_to_save, filename_to_load ):
- -1) < 0)
- return 0;
- }
|