1 ///////////////////////////////////////////////////////////////////////////////////////
2 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 // By downloading, copying, installing or using the software you agree to this license.
5 // If you do not agree to this license, do not download, install,
6 // copy or use the software.
8 // This is a implementation of the Logistic Regression algorithm in C++ in OpenCV.
11 // Rahul Kavi rahulkavi[at]live[at]com
13 // # You are free to use, change, or redistribute the code in any way you wish for
14 // # non-commercial purposes, but please maintain the name of the original author.
15 // # This code comes with no warranty of any kind.
18 // # You are free to use, change, or redistribute the code in any way you wish for
19 // # non-commercial purposes, but please maintain the name of the original author.
20 // # This code comes with no warranty of any kind.
22 // # Logistic Regression ALGORITHM
26 // For Open Source Computer Vision Library
28 // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
29 // Copyright (C) 2008-2011, Willow Garage Inc., all rights reserved.
30 // Third party copyrights are property of their respective owners.
32 // Redistribution and use in source and binary forms, with or without modification,
33 // are permitted provided that the following conditions are met:
35 // * Redistributions of source code must retain the above copyright notice,
36 // this list of conditions and the following disclaimer.
38 // * Redistributions in binary form must reproduce the above copyright notice,
39 // this list of conditions and the following disclaimer in the documentation
40 // and/or other materials provided with the distribution.
42 // * The name of the copyright holders may not be used to endorse or promote products
43 // derived from this software without specific prior written permission.
45 // This software is provided by the copyright holders and contributors "as is" and
46 // any express or implied warranties, including, but not limited to, the implied
47 // warranties of merchantability and fitness for a particular purpose are disclaimed.
48 // In no event shall the Intel Corporation or contributors be liable for any direct,
49 // indirect, incidental, special, exemplary, or consequential damages
50 // (including, but not limited to, procurement of substitute goods or services;
51 // loss of use, data, or profits; or business interruption) however caused
52 // and on any theory of liability, whether in contract, strict liability,
53 // or tort (including negligence or otherwise) arising in any way out of
54 // the use of this software, even if advised of the possibility of such damage.
56 #include "precomp.hpp"
63 LogisticRegression::Params::Params(double learning_rate,
70 alpha = learning_rate;
74 train_method = method;
75 mini_batch_size = batch_size;
76 term_crit = cv::TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, num_iters, alpha);
79 class LogisticRegressionImpl : public LogisticRegression
82 LogisticRegressionImpl(const Params& pms)
86 virtual ~LogisticRegressionImpl() {}
88 virtual bool train( const Ptr<TrainData>& trainData, int=0 );
89 virtual float predict(InputArray samples, OutputArray results, int) const;
91 virtual void write(FileStorage& fs) const;
92 virtual void read(const FileNode& fn);
93 virtual cv::Mat get_learnt_thetas() const;
94 virtual int getVarCount() const { return learnt_thetas.cols; }
95 virtual bool isTrained() const { return !learnt_thetas.empty(); }
96 virtual bool isClassifier() const { return true; }
97 virtual String getDefaultModelName() const { return "opencv_ml_lr"; }
99 cv::Mat calc_sigmoid(const cv::Mat& data) const;
100 double compute_cost(const cv::Mat& _data, const cv::Mat& _labels, const cv::Mat& _init_theta);
101 cv::Mat compute_batch_gradient(const cv::Mat& _data, const cv::Mat& _labels, const cv::Mat& _init_theta);
102 cv::Mat compute_mini_batch_gradient(const cv::Mat& _data, const cv::Mat& _labels, const cv::Mat& _init_theta);
103 bool set_label_map(const cv::Mat& _labels_i);
104 cv::Mat remap_labels(const cv::Mat& _labels_i, const map<int, int>& lmap) const;
107 cv::Mat learnt_thetas;
108 map<int, int> forward_mapper;
109 map<int, int> reverse_mapper;
114 Ptr<LogisticRegression> LogisticRegression::create(const Params& params)
116 return makePtr<LogisticRegressionImpl>(params);
119 bool LogisticRegressionImpl::train(const Ptr<TrainData>& trainData, int)
122 cv::Mat _data_i = trainData->getSamples();
123 cv::Mat _labels_i = trainData->getResponses();
125 CV_Assert( !_labels_i.empty() && !_data_i.empty());
127 // check the number of columns
128 if(_labels_i.cols != 1)
130 CV_Error( CV_StsBadArg, "_labels_i should be a column matrix" );
134 // data should be of floating type CV_32FC1
136 if((_data_i.type() != CV_32FC1) || (_labels_i.type() != CV_32FC1))
138 CV_Error( CV_StsBadArg, "data and labels must be a floating point matrix" );
145 set_label_map(_labels_i);
146 int num_classes = (int) this->forward_mapper.size();
148 // add a column of ones
149 cv::Mat data_t = cv::Mat::zeros(_data_i.rows, _data_i.cols+1, CV_32F);
150 vconcat(cv::Mat(_data_i.rows, 1, _data_i.type(), Scalar::all(1.0)), data_t.col(0));
152 for (int i=1;i<data_t.cols;i++)
154 vconcat(_data_i.col(i-1), data_t.col(i));
159 CV_Error( CV_StsBadArg, "data should have atleast 2 classes" );
162 if(_labels_i.rows != _data_i.rows)
164 CV_Error( CV_StsBadArg, "number of rows in data and labels should be the equal" );
168 cv::Mat thetas = cv::Mat::zeros(num_classes, data_t.cols, CV_32F);
169 cv::Mat init_theta = cv::Mat::zeros(data_t.cols, 1, CV_32F);
171 cv::Mat labels_l = remap_labels(_labels_i, this->forward_mapper);
172 cv::Mat new_local_labels;
179 labels_l.convertTo(labels, CV_32F);
180 if(this->params.train_method == LogisticRegression::BATCH)
181 new_theta = compute_batch_gradient(data_t, labels, init_theta);
183 new_theta = compute_mini_batch_gradient(data_t, labels, init_theta);
184 thetas = new_theta.t();
188 /* take each class and rename classes you will get a theta per class
189 as in multi class class scenario, we will have n thetas for n classes */
192 for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
194 new_local_labels = (labels_l == it->second)/255;
195 new_local_labels.convertTo(labels, CV_32F);
196 if(this->params.train_method == LogisticRegression::BATCH)
197 new_theta = compute_batch_gradient(data_t, labels, init_theta);
199 new_theta = compute_mini_batch_gradient(data_t, labels, init_theta);
200 hconcat(new_theta.t(), thetas.row(ii));
205 this->learnt_thetas = thetas.clone();
206 if( cvIsNaN( (double)cv::sum(this->learnt_thetas)[0] ) )
208 CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
214 float LogisticRegressionImpl::predict(InputArray samples, OutputArray results, int) const
216 /* returns a class of the predicted class
217 class names can be 1,2,3,4, .... etc */
218 cv::Mat thetas, data, pred_labs;
219 data = samples.getMat();
221 // check if learnt_mats array is populated
222 if(this->learnt_thetas.total()<=0)
224 CV_Error( CV_StsBadArg, "classifier should be trained first" );
226 if(data.type() != CV_32F)
228 CV_Error( CV_StsBadArg, "data must be of floating type" );
231 // add a column of ones
232 cv::Mat data_t = cv::Mat::zeros(data.rows, data.cols+1, CV_32F);
233 for (int i=0;i<data_t.cols;i++)
237 vconcat(cv::Mat(data.rows, 1, data.type(), Scalar::all(1.0)), data_t.col(i));
240 vconcat(data.col(i-1), data_t.col(i));
243 this->learnt_thetas.convertTo(thetas, CV_32F);
245 CV_Assert(thetas.rows > 0);
256 cv::Mat pred_m = cv::Mat::zeros(data_t.rows, thetas.rows, data.type());
260 temp_pred = calc_sigmoid(data_t*thetas.t());
261 CV_Assert(temp_pred.cols==1);
263 // if greater than 0.5, predict class 0 or predict class 1
264 temp_pred = (temp_pred>0.5)/255;
265 temp_pred.convertTo(labels_c, CV_32S);
269 for(int i = 0;i<thetas.rows;i++)
271 temp_pred = calc_sigmoid(data_t * thetas.row(i).t());
272 cv::vconcat(temp_pred, pred_m.col(i));
274 for(int i = 0;i<pred_m.rows;i++)
276 temp_pred = pred_m.row(i);
277 minMaxLoc( temp_pred, &min_val, &max_val, &min_loc, &max_loc, Mat() );
278 labels.push_back(max_loc.x);
280 labels.convertTo(labels_c, CV_32S);
282 pred_labs = remap_labels(labels_c, this->reverse_mapper);
283 // convert pred_labs to integer type
284 pred_labs.convertTo(pred_labs, CV_32S);
285 pred_labs.copyTo(results);
290 cv::Mat LogisticRegressionImpl::calc_sigmoid(const cv::Mat& data) const
293 cv::exp(-data, dest);
294 return 1.0/(1.0+dest);
297 double LogisticRegressionImpl::compute_cost(const cv::Mat& _data, const cv::Mat& _labels, const cv::Mat& _init_theta)
303 double rparameter = 0;
313 gradient = cv::Mat::zeros( _init_theta.rows, _init_theta.cols, _init_theta.type());
314 theta_b = _init_theta(Range(1, n), Range::all());
315 cv::multiply(theta_b, theta_b, theta_c, 1);
317 if(this->params.regularized > 0)
322 if(this->params.norm == LogisticRegression::REG_L1)
324 rparameter = (llambda/(2*m)) * cv::sum(theta_b)[0];
328 // assuming it to be L2 by default
329 rparameter = (llambda/(2*m)) * cv::sum(theta_c)[0];
332 d_a = calc_sigmoid(_data* _init_theta);
336 cv::multiply(d_a, _labels, d_a);
338 d_b = 1 - calc_sigmoid(_data * _init_theta);
340 cv::multiply(d_b, 1-_labels, d_b);
342 cost = (-1.0/m) * (cv::sum(d_a)[0] + cv::sum(d_b)[0]);
343 cost = cost + rparameter;
348 cv::Mat LogisticRegressionImpl::compute_batch_gradient(const cv::Mat& _data, const cv::Mat& _labels, const cv::Mat& _init_theta)
350 // implements batch gradient descent
351 if(this->params.alpha<=0)
353 CV_Error( CV_StsBadArg, "check training parameters for the classifier" );
356 if(this->params.num_iters <= 0)
358 CV_Error( CV_StsBadArg, "number of iterations cannot be zero or a negative number" );
368 cv::Mat theta_p = _init_theta.clone();
372 if(this->params.regularized > 0)
377 for(int i = 0;i<this->params.num_iters;i++)
379 ccost = compute_cost(_data, _labels, theta_p);
381 if( cvIsNaN( ccost ) )
383 CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
386 pcal_b = calc_sigmoid((_data*theta_p) - _labels);
388 pcal_a = (static_cast<double>(1/m)) * _data.t();
390 gradient = pcal_a * pcal_b;
392 pcal_a = calc_sigmoid(_data*theta_p) - _labels;
394 pcal_b = _data(Range::all(), Range(0,1));
396 cv::multiply(pcal_a, pcal_b, pcal_ab, 1);
398 gradient.row(0) = ((float)1/m) * sum(pcal_ab)[0];
400 pcal_b = _data(Range::all(), Range(1,n));
402 //cout<<"for each training data entry"<<endl;
403 for(int ii = 1;ii<gradient.rows;ii++)
405 pcal_b = _data(Range::all(), Range(ii,ii+1));
407 cv::multiply(pcal_a, pcal_b, pcal_ab, 1);
409 gradient.row(ii) = (1.0/m)*cv::sum(pcal_ab)[0] + (llambda/m) * theta_p.row(ii);
412 theta_p = theta_p - ( static_cast<double>(this->params.alpha)/m)*gradient;
417 cv::Mat LogisticRegressionImpl::compute_mini_batch_gradient(const cv::Mat& _data, const cv::Mat& _labels, const cv::Mat& _init_theta)
419 // implements batch gradient descent
424 int size_b = this->params.mini_batch_size;
426 if(this->params.mini_batch_size <= 0 || this->params.alpha == 0)
428 CV_Error( CV_StsBadArg, "check training parameters for the classifier" );
431 if(this->params.num_iters <= 0)
433 CV_Error( CV_StsBadArg, "number of iterations cannot be zero or a negative number" );
440 cv::Mat theta_p = _init_theta.clone();
444 if(this->params.regularized > 0)
449 for(int i = 0;this->params.term_crit.maxCount;i++)
451 if(j+size_b<=_data.rows)
453 data_d = _data(Range(j,j+size_b), Range::all());
454 labels_l = _labels(Range(j,j+size_b),Range::all());
458 data_d = _data(Range(j, _data.rows), Range::all());
459 labels_l = _labels(Range(j, _labels.rows),Range::all());
465 ccost = compute_cost(data_d, labels_l, theta_p);
467 if( cvIsNaN( ccost ) == 1)
469 CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
472 pcal_b = calc_sigmoid((data_d*theta_p) - labels_l);
474 pcal_a = (static_cast<double>(1/m)) * data_d.t();
476 gradient = pcal_a * pcal_b;
478 pcal_a = calc_sigmoid(data_d*theta_p) - labels_l;
480 pcal_b = data_d(Range::all(), Range(0,1));
482 cv::multiply(pcal_a, pcal_b, pcal_ab, 1);
484 gradient.row(0) = ((float)1/m) * sum(pcal_ab)[0];
486 pcal_b = data_d(Range::all(), Range(1,n));
488 for(int k = 1;k<gradient.rows;k++)
490 pcal_b = data_d(Range::all(), Range(k,k+1));
491 cv::multiply(pcal_a, pcal_b, pcal_ab, 1);
492 gradient.row(k) = (1.0/m)*cv::sum(pcal_ab)[0] + (lambda_l/m) * theta_p.row(k);
495 theta_p = theta_p - ( static_cast<double>(this->params.alpha)/m)*gradient;
497 j+=this->params.mini_batch_size;
499 if(j+size_b>_data.rows)
501 // if parsed through all data variables
508 bool LogisticRegressionImpl::set_label_map(const cv::Mat &_labels_i)
510 // this function creates two maps to map user defined labels to program friendly labels two ways.
515 this->labels_o = cv::Mat(0,1, CV_8U);
516 this->labels_n = cv::Mat(0,1, CV_8U);
518 _labels_i.convertTo(labels, CV_32S);
520 for(int i = 0;i<labels.rows;i++)
522 this->forward_mapper[labels.at<int>(i)] += 1;
525 for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
527 this->forward_mapper[it->first] = ii;
528 this->labels_o.push_back(it->first);
529 this->labels_n.push_back(ii);
533 for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
535 this->reverse_mapper[it->second] = it->first;
542 cv::Mat LogisticRegressionImpl::remap_labels(const cv::Mat& _labels_i, const map<int, int>& lmap) const
545 _labels_i.convertTo(labels, CV_32S);
547 cv::Mat new_labels = cv::Mat::zeros(labels.rows, labels.cols, labels.type());
549 CV_Assert( lmap.size() > 0 );
551 for(int i =0;i<labels.rows;i++)
553 new_labels.at<int>(i,0) = lmap.find(labels.at<int>(i,0))->second;
558 void LogisticRegressionImpl::clear()
560 this->learnt_thetas.release();
561 this->labels_o.release();
562 this->labels_n.release();
565 void LogisticRegressionImpl::write(FileStorage& fs) const
568 if(fs.isOpened() == 0)
570 CV_Error(CV_StsBadArg,"file can't open. Check file path");
572 string desc = "Logisitic Regression Classifier";
573 fs<<"classifier"<<desc.c_str();
574 fs<<"alpha"<<this->params.alpha;
575 fs<<"iterations"<<this->params.num_iters;
576 fs<<"norm"<<this->params.norm;
577 fs<<"regularized"<<this->params.regularized;
578 fs<<"train_method"<<this->params.train_method;
579 if(this->params.train_method == LogisticRegression::MINI_BATCH)
581 fs<<"mini_batch_size"<<this->params.mini_batch_size;
583 fs<<"learnt_thetas"<<this->learnt_thetas;
584 fs<<"n_labels"<<this->labels_n;
585 fs<<"o_labels"<<this->labels_o;
588 void LogisticRegressionImpl::read(const FileNode& fn)
593 CV_Error( CV_StsBadArg, "empty FileNode object" );
596 this->params.alpha = (double)fn["alpha"];
597 this->params.num_iters = (int)fn["iterations"];
598 this->params.norm = (int)fn["norm"];
599 this->params.regularized = (int)fn["regularized"];
600 this->params.train_method = (int)fn["train_method"];
602 if(this->params.train_method == LogisticRegression::MINI_BATCH)
604 this->params.mini_batch_size = (int)fn["mini_batch_size"];
607 fn["learnt_thetas"] >> this->learnt_thetas;
608 fn["o_labels"] >> this->labels_o;
609 fn["n_labels"] >> this->labels_n;
611 for(int ii =0;ii<labels_o.rows;ii++)
613 this->forward_mapper[labels_o.at<int>(ii,0)] = labels_n.at<int>(ii,0);
614 this->reverse_mapper[labels_n.at<int>(ii,0)] = labels_o.at<int>(ii,0);
618 cv::Mat LogisticRegressionImpl::get_learnt_thetas() const
620 return this->learnt_thetas;