1 ///////////////////////////////////////////////////////////////////////////////////////
2 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
3 
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.
7 
8 // This is a implementation of the Logistic Regression algorithm in C++ in OpenCV.
9 
10 // AUTHOR:
11 // Rahul Kavi rahulkavi[at]live[at]com
12 
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.
16 
17 // #
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.
21 
22 // # Logistic Regression ALGORITHM
23 
24 
25 //                           License Agreement
26 //                For Open Source Computer Vision Library
27 
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.
31 
32 // Redistribution and use in source and binary forms, with or without modification,
33 // are permitted provided that the following conditions are met:
34 
35 //   * Redistributions of source code must retain the above copyright notice,
36 //     this list of conditions and the following disclaimer.
37 
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.
41 
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.
44 
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.
55 
56 #include "precomp.hpp"
57 
58 using namespace std;
59 
60 namespace cv {
61 namespace ml {
62 
63 class LrParams
64 {
65 public:
LrParams()66     LrParams()
67     {
68         alpha = 0.001;
69         num_iters = 1000;
70         norm = LogisticRegression::REG_L2;
71         train_method = LogisticRegression::BATCH;
72         mini_batch_size = 1;
73         term_crit = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, num_iters, alpha);
74     }
75 
76     double alpha; //!< learning rate.
77     int num_iters; //!< number of iterations.
78     int norm;
79     int train_method;
80     int mini_batch_size;
81     TermCriteria term_crit;
82 };
83 
84 class LogisticRegressionImpl : public LogisticRegression
85 {
86 public:
87 
LogisticRegressionImpl()88     LogisticRegressionImpl() { }
~LogisticRegressionImpl()89     virtual ~LogisticRegressionImpl() {}
90 
91     CV_IMPL_PROPERTY(double, LearningRate, params.alpha)
92     CV_IMPL_PROPERTY(int, Iterations, params.num_iters)
93     CV_IMPL_PROPERTY(int, Regularization, params.norm)
94     CV_IMPL_PROPERTY(int, TrainMethod, params.train_method)
95     CV_IMPL_PROPERTY(int, MiniBatchSize, params.mini_batch_size)
96     CV_IMPL_PROPERTY(TermCriteria, TermCriteria, params.term_crit)
97 
98     virtual bool train( const Ptr<TrainData>& trainData, int=0 );
99     virtual float predict(InputArray samples, OutputArray results, int) const;
100     virtual void clear();
101     virtual void write(FileStorage& fs) const;
102     virtual void read(const FileNode& fn);
103     virtual Mat get_learnt_thetas() const;
getVarCount() const104     virtual int getVarCount() const { return learnt_thetas.cols; }
isTrained() const105     virtual bool isTrained() const { return !learnt_thetas.empty(); }
isClassifier() const106     virtual bool isClassifier() const { return true; }
getDefaultName() const107     virtual String getDefaultName() const { return "opencv_ml_lr"; }
108 protected:
109     Mat calc_sigmoid(const Mat& data) const;
110     double compute_cost(const Mat& _data, const Mat& _labels, const Mat& _init_theta);
111     Mat compute_batch_gradient(const Mat& _data, const Mat& _labels, const Mat& _init_theta);
112     Mat compute_mini_batch_gradient(const Mat& _data, const Mat& _labels, const Mat& _init_theta);
113     bool set_label_map(const Mat& _labels_i);
114     Mat remap_labels(const Mat& _labels_i, const map<int, int>& lmap) const;
115 protected:
116     LrParams params;
117     Mat learnt_thetas;
118     map<int, int> forward_mapper;
119     map<int, int> reverse_mapper;
120     Mat labels_o;
121     Mat labels_n;
122 };
123 
create()124 Ptr<LogisticRegression> LogisticRegression::create()
125 {
126     return makePtr<LogisticRegressionImpl>();
127 }
128 
train(const Ptr<TrainData> & trainData,int)129 bool LogisticRegressionImpl::train(const Ptr<TrainData>& trainData, int)
130 {
131     clear();
132     Mat _data_i = trainData->getSamples();
133     Mat _labels_i = trainData->getResponses();
134 
135     CV_Assert( !_labels_i.empty() && !_data_i.empty());
136 
137     // check the number of columns
138     if(_labels_i.cols != 1)
139     {
140         CV_Error( CV_StsBadArg, "_labels_i should be a column matrix" );
141     }
142 
143     // check data type.
144     // data should be of floating type CV_32FC1
145 
146     if((_data_i.type() != CV_32FC1) || (_labels_i.type() != CV_32FC1))
147     {
148         CV_Error( CV_StsBadArg, "data and labels must be a floating point matrix" );
149     }
150 
151     bool ok = false;
152 
153     Mat labels;
154 
155     set_label_map(_labels_i);
156     int num_classes = (int) this->forward_mapper.size();
157 
158     // add a column of ones
159     Mat data_t = Mat::zeros(_data_i.rows, _data_i.cols+1, CV_32F);
160     vconcat(Mat(_data_i.rows, 1, _data_i.type(), Scalar::all(1.0)), data_t.col(0));
161 
162     for (int i=1;i<data_t.cols;i++)
163     {
164         vconcat(_data_i.col(i-1), data_t.col(i));
165     }
166 
167     if(num_classes < 2)
168     {
169         CV_Error( CV_StsBadArg, "data should have atleast 2 classes" );
170     }
171 
172     if(_labels_i.rows != _data_i.rows)
173     {
174         CV_Error( CV_StsBadArg, "number of rows in data and labels should be the equal" );
175     }
176 
177 
178     Mat thetas = Mat::zeros(num_classes, data_t.cols, CV_32F);
179     Mat init_theta = Mat::zeros(data_t.cols, 1, CV_32F);
180 
181     Mat labels_l = remap_labels(_labels_i, this->forward_mapper);
182     Mat new_local_labels;
183 
184     int ii=0;
185     Mat new_theta;
186 
187     if(num_classes == 2)
188     {
189         labels_l.convertTo(labels, CV_32F);
190         if(this->params.train_method == LogisticRegression::BATCH)
191             new_theta = compute_batch_gradient(data_t, labels, init_theta);
192         else
193             new_theta = compute_mini_batch_gradient(data_t, labels, init_theta);
194         thetas = new_theta.t();
195     }
196     else
197     {
198         /* take each class and rename classes you will get a theta per class
199         as in multi class class scenario, we will have n thetas for n classes */
200         ii = 0;
201 
202         for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
203         {
204             new_local_labels = (labels_l == it->second)/255;
205             new_local_labels.convertTo(labels, CV_32F);
206             if(this->params.train_method == LogisticRegression::BATCH)
207                 new_theta = compute_batch_gradient(data_t, labels, init_theta);
208             else
209                 new_theta = compute_mini_batch_gradient(data_t, labels, init_theta);
210             hconcat(new_theta.t(), thetas.row(ii));
211             ii += 1;
212         }
213     }
214 
215     this->learnt_thetas = thetas.clone();
216     if( cvIsNaN( (double)sum(this->learnt_thetas)[0] ) )
217     {
218         CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
219     }
220     ok = true;
221     return ok;
222 }
223 
predict(InputArray samples,OutputArray results,int) const224 float LogisticRegressionImpl::predict(InputArray samples, OutputArray results, int) const
225 {
226     /* returns a class of the predicted class
227     class names can be 1,2,3,4, .... etc */
228     Mat thetas, data, pred_labs;
229     data = samples.getMat();
230 
231     // check if learnt_mats array is populated
232     if(this->learnt_thetas.total()<=0)
233     {
234         CV_Error( CV_StsBadArg, "classifier should be trained first" );
235     }
236     if(data.type() != CV_32F)
237     {
238         CV_Error( CV_StsBadArg, "data must be of floating type" );
239     }
240 
241     // add a column of ones
242     Mat data_t = Mat::zeros(data.rows, data.cols+1, CV_32F);
243     for (int i=0;i<data_t.cols;i++)
244     {
245         if(i==0)
246         {
247             vconcat(Mat(data.rows, 1, data.type(), Scalar::all(1.0)), data_t.col(i));
248             continue;
249         }
250         vconcat(data.col(i-1), data_t.col(i));
251     }
252 
253     this->learnt_thetas.convertTo(thetas, CV_32F);
254 
255     CV_Assert(thetas.rows > 0);
256 
257     double min_val;
258     double max_val;
259 
260     Point min_loc;
261     Point max_loc;
262 
263     Mat labels;
264     Mat labels_c;
265     Mat temp_pred;
266     Mat pred_m = Mat::zeros(data_t.rows, thetas.rows, data.type());
267 
268     if(thetas.rows == 1)
269     {
270         temp_pred = calc_sigmoid(data_t*thetas.t());
271         CV_Assert(temp_pred.cols==1);
272 
273         // if greater than 0.5, predict class 0 or predict class 1
274         temp_pred = (temp_pred>0.5)/255;
275         temp_pred.convertTo(labels_c, CV_32S);
276     }
277     else
278     {
279         for(int i = 0;i<thetas.rows;i++)
280         {
281             temp_pred = calc_sigmoid(data_t * thetas.row(i).t());
282             vconcat(temp_pred, pred_m.col(i));
283         }
284         for(int i = 0;i<pred_m.rows;i++)
285         {
286             temp_pred = pred_m.row(i);
287             minMaxLoc( temp_pred, &min_val, &max_val, &min_loc, &max_loc, Mat() );
288             labels.push_back(max_loc.x);
289         }
290         labels.convertTo(labels_c, CV_32S);
291     }
292     pred_labs = remap_labels(labels_c, this->reverse_mapper);
293     // convert pred_labs to integer type
294     pred_labs.convertTo(pred_labs, CV_32S);
295     pred_labs.copyTo(results);
296     // TODO: determine
297     return 0;
298 }
299 
calc_sigmoid(const Mat & data) const300 Mat LogisticRegressionImpl::calc_sigmoid(const Mat& data) const
301 {
302     Mat dest;
303     exp(-data, dest);
304     return 1.0/(1.0+dest);
305 }
306 
compute_cost(const Mat & _data,const Mat & _labels,const Mat & _init_theta)307 double LogisticRegressionImpl::compute_cost(const Mat& _data, const Mat& _labels, const Mat& _init_theta)
308 {
309     int llambda = 0;
310     int m;
311     int n;
312     double cost = 0;
313     double rparameter = 0;
314     Mat theta_b;
315     Mat theta_c;
316     Mat d_a;
317     Mat d_b;
318 
319     m = _data.rows;
320     n = _data.cols;
321 
322     theta_b = _init_theta(Range(1, n), Range::all());
323     multiply(theta_b, theta_b, theta_c, 1);
324 
325     if (params.norm != REG_DISABLE)
326     {
327         llambda = 1;
328     }
329 
330     if(this->params.norm == LogisticRegression::REG_L1)
331     {
332         rparameter = (llambda/(2*m)) * sum(theta_b)[0];
333     }
334     else
335     {
336         // assuming it to be L2 by default
337         rparameter = (llambda/(2*m)) * sum(theta_c)[0];
338     }
339 
340     d_a = calc_sigmoid(_data* _init_theta);
341 
342 
343     log(d_a, d_a);
344     multiply(d_a, _labels, d_a);
345 
346     d_b = 1 - calc_sigmoid(_data * _init_theta);
347     log(d_b, d_b);
348     multiply(d_b, 1-_labels, d_b);
349 
350     cost = (-1.0/m) * (sum(d_a)[0] + sum(d_b)[0]);
351     cost = cost + rparameter;
352 
353     return cost;
354 }
355 
compute_batch_gradient(const Mat & _data,const Mat & _labels,const Mat & _init_theta)356 Mat LogisticRegressionImpl::compute_batch_gradient(const Mat& _data, const Mat& _labels, const Mat& _init_theta)
357 {
358     // implements batch gradient descent
359     if(this->params.alpha<=0)
360     {
361         CV_Error( CV_StsBadArg, "check training parameters for the classifier" );
362     }
363 
364     if(this->params.num_iters <= 0)
365     {
366         CV_Error( CV_StsBadArg, "number of iterations cannot be zero or a negative number" );
367     }
368 
369     int llambda = 0;
370     double ccost;
371     int m, n;
372     Mat pcal_a;
373     Mat pcal_b;
374     Mat pcal_ab;
375     Mat gradient;
376     Mat theta_p = _init_theta.clone();
377     m = _data.rows;
378     n = _data.cols;
379 
380     if (params.norm != REG_DISABLE)
381     {
382         llambda = 1;
383     }
384 
385     for(int i = 0;i<this->params.num_iters;i++)
386     {
387         ccost = compute_cost(_data, _labels, theta_p);
388 
389         if( cvIsNaN( ccost ) )
390         {
391             CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
392         }
393 
394         pcal_b = calc_sigmoid((_data*theta_p) - _labels);
395 
396         pcal_a = (static_cast<double>(1/m)) * _data.t();
397 
398         gradient = pcal_a * pcal_b;
399 
400         pcal_a = calc_sigmoid(_data*theta_p) - _labels;
401 
402         pcal_b = _data(Range::all(), Range(0,1));
403 
404         multiply(pcal_a, pcal_b, pcal_ab, 1);
405 
406         gradient.row(0) = ((float)1/m) * sum(pcal_ab)[0];
407 
408         pcal_b = _data(Range::all(), Range(1,n));
409 
410         //cout<<"for each training data entry"<<endl;
411         for(int ii = 1;ii<gradient.rows;ii++)
412         {
413             pcal_b = _data(Range::all(), Range(ii,ii+1));
414 
415             multiply(pcal_a, pcal_b, pcal_ab, 1);
416 
417             gradient.row(ii) = (1.0/m)*sum(pcal_ab)[0] + (llambda/m) * theta_p.row(ii);
418         }
419 
420         theta_p = theta_p - ( static_cast<double>(this->params.alpha)/m)*gradient;
421     }
422     return theta_p;
423 }
424 
compute_mini_batch_gradient(const Mat & _data,const Mat & _labels,const Mat & _init_theta)425 Mat LogisticRegressionImpl::compute_mini_batch_gradient(const Mat& _data, const Mat& _labels, const Mat& _init_theta)
426 {
427     // implements batch gradient descent
428     int lambda_l = 0;
429     double ccost;
430     int m, n;
431     int j = 0;
432     int size_b = this->params.mini_batch_size;
433 
434     if(this->params.mini_batch_size <= 0 || this->params.alpha == 0)
435     {
436         CV_Error( CV_StsBadArg, "check training parameters for the classifier" );
437     }
438 
439     if(this->params.num_iters <= 0)
440     {
441         CV_Error( CV_StsBadArg, "number of iterations cannot be zero or a negative number" );
442     }
443 
444     Mat pcal_a;
445     Mat pcal_b;
446     Mat pcal_ab;
447     Mat gradient;
448     Mat theta_p = _init_theta.clone();
449     Mat data_d;
450     Mat labels_l;
451 
452     if (params.norm != REG_DISABLE)
453     {
454         lambda_l = 1;
455     }
456 
457     for(int i = 0;i<this->params.term_crit.maxCount;i++)
458     {
459         if(j+size_b<=_data.rows)
460         {
461             data_d = _data(Range(j,j+size_b), Range::all());
462             labels_l = _labels(Range(j,j+size_b),Range::all());
463         }
464         else
465         {
466             data_d = _data(Range(j, _data.rows), Range::all());
467             labels_l = _labels(Range(j, _labels.rows),Range::all());
468         }
469 
470         m = data_d.rows;
471         n = data_d.cols;
472 
473         ccost = compute_cost(data_d, labels_l, theta_p);
474 
475         if( cvIsNaN( ccost ) == 1)
476         {
477             CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
478         }
479 
480         pcal_b = calc_sigmoid((data_d*theta_p) - labels_l);
481 
482         pcal_a = (static_cast<double>(1/m)) * data_d.t();
483 
484         gradient = pcal_a * pcal_b;
485 
486         pcal_a = calc_sigmoid(data_d*theta_p) - labels_l;
487 
488         pcal_b = data_d(Range::all(), Range(0,1));
489 
490         multiply(pcal_a, pcal_b, pcal_ab, 1);
491 
492         gradient.row(0) = ((float)1/m) * sum(pcal_ab)[0];
493 
494         pcal_b = data_d(Range::all(), Range(1,n));
495 
496         for(int k = 1;k<gradient.rows;k++)
497         {
498             pcal_b = data_d(Range::all(), Range(k,k+1));
499             multiply(pcal_a, pcal_b, pcal_ab, 1);
500             gradient.row(k) = (1.0/m)*sum(pcal_ab)[0] + (lambda_l/m) * theta_p.row(k);
501         }
502 
503         theta_p = theta_p - ( static_cast<double>(this->params.alpha)/m)*gradient;
504 
505         j+=this->params.mini_batch_size;
506 
507         if(j+size_b>_data.rows)
508         {
509             // if parsed through all data variables
510             break;
511         }
512     }
513     return theta_p;
514 }
515 
set_label_map(const Mat & _labels_i)516 bool LogisticRegressionImpl::set_label_map(const Mat &_labels_i)
517 {
518     // this function creates two maps to map user defined labels to program friendly labels two ways.
519     int ii = 0;
520     Mat labels;
521 
522     this->labels_o = Mat(0,1, CV_8U);
523     this->labels_n = Mat(0,1, CV_8U);
524 
525     _labels_i.convertTo(labels, CV_32S);
526 
527     for(int i = 0;i<labels.rows;i++)
528     {
529         this->forward_mapper[labels.at<int>(i)] += 1;
530     }
531 
532     for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
533     {
534         this->forward_mapper[it->first] = ii;
535         this->labels_o.push_back(it->first);
536         this->labels_n.push_back(ii);
537         ii += 1;
538     }
539 
540     for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
541     {
542         this->reverse_mapper[it->second] = it->first;
543     }
544 
545     return true;
546 }
547 
remap_labels(const Mat & _labels_i,const map<int,int> & lmap) const548 Mat LogisticRegressionImpl::remap_labels(const Mat& _labels_i, const map<int, int>& lmap) const
549 {
550     Mat labels;
551     _labels_i.convertTo(labels, CV_32S);
552 
553     Mat new_labels = Mat::zeros(labels.rows, labels.cols, labels.type());
554 
555     CV_Assert( !lmap.empty() );
556 
557     for(int i =0;i<labels.rows;i++)
558     {
559         new_labels.at<int>(i,0) = lmap.find(labels.at<int>(i,0))->second;
560     }
561     return new_labels;
562 }
563 
clear()564 void LogisticRegressionImpl::clear()
565 {
566     this->learnt_thetas.release();
567     this->labels_o.release();
568     this->labels_n.release();
569 }
570 
write(FileStorage & fs) const571 void LogisticRegressionImpl::write(FileStorage& fs) const
572 {
573     // check if open
574     if(fs.isOpened() == 0)
575     {
576         CV_Error(CV_StsBadArg,"file can't open. Check file path");
577     }
578     string desc = "Logisitic Regression Classifier";
579     fs<<"classifier"<<desc.c_str();
580     fs<<"alpha"<<this->params.alpha;
581     fs<<"iterations"<<this->params.num_iters;
582     fs<<"norm"<<this->params.norm;
583     fs<<"train_method"<<this->params.train_method;
584     if(this->params.train_method == LogisticRegression::MINI_BATCH)
585     {
586         fs<<"mini_batch_size"<<this->params.mini_batch_size;
587     }
588     fs<<"learnt_thetas"<<this->learnt_thetas;
589     fs<<"n_labels"<<this->labels_n;
590     fs<<"o_labels"<<this->labels_o;
591 }
592 
read(const FileNode & fn)593 void LogisticRegressionImpl::read(const FileNode& fn)
594 {
595     // check if empty
596     if(fn.empty())
597     {
598         CV_Error( CV_StsBadArg, "empty FileNode object" );
599     }
600 
601     this->params.alpha = (double)fn["alpha"];
602     this->params.num_iters = (int)fn["iterations"];
603     this->params.norm = (int)fn["norm"];
604     this->params.train_method = (int)fn["train_method"];
605 
606     if(this->params.train_method == LogisticRegression::MINI_BATCH)
607     {
608         this->params.mini_batch_size = (int)fn["mini_batch_size"];
609     }
610 
611     fn["learnt_thetas"] >> this->learnt_thetas;
612     fn["o_labels"] >> this->labels_o;
613     fn["n_labels"] >> this->labels_n;
614 
615     for(int ii =0;ii<labels_o.rows;ii++)
616     {
617         this->forward_mapper[labels_o.at<int>(ii,0)] = labels_n.at<int>(ii,0);
618         this->reverse_mapper[labels_n.at<int>(ii,0)] = labels_o.at<int>(ii,0);
619     }
620 }
621 
get_learnt_thetas() const622 Mat LogisticRegressionImpl::get_learnt_thetas() const
623 {
624     return this->learnt_thetas;
625 }
626 
627 }
628 }
629 
630 /* End of file. */
631