Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/modules/ml/src/lr.cpp
16337 views
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:
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 CV_FINAL : public LogisticRegression
85
{
86
public:
87
88
LogisticRegressionImpl() { }
89
virtual ~LogisticRegressionImpl() {}
90
91
inline double getLearningRate() const CV_OVERRIDE { return params.alpha; }
92
inline void setLearningRate(double val) CV_OVERRIDE { params.alpha = val; }
93
inline int getIterations() const CV_OVERRIDE { return params.num_iters; }
94
inline void setIterations(int val) CV_OVERRIDE { params.num_iters = val; }
95
inline int getRegularization() const CV_OVERRIDE { return params.norm; }
96
inline void setRegularization(int val) CV_OVERRIDE { params.norm = val; }
97
inline int getTrainMethod() const CV_OVERRIDE { return params.train_method; }
98
inline void setTrainMethod(int val) CV_OVERRIDE { params.train_method = val; }
99
inline int getMiniBatchSize() const CV_OVERRIDE { return params.mini_batch_size; }
100
inline void setMiniBatchSize(int val) CV_OVERRIDE { params.mini_batch_size = val; }
101
inline TermCriteria getTermCriteria() const CV_OVERRIDE { return params.term_crit; }
102
inline void setTermCriteria(TermCriteria val) CV_OVERRIDE { params.term_crit = val; }
103
104
virtual bool train( const Ptr<TrainData>& trainData, int=0 ) CV_OVERRIDE;
105
virtual float predict(InputArray samples, OutputArray results, int flags=0) const CV_OVERRIDE;
106
virtual void clear() CV_OVERRIDE;
107
virtual void write(FileStorage& fs) const CV_OVERRIDE;
108
virtual void read(const FileNode& fn) CV_OVERRIDE;
109
virtual Mat get_learnt_thetas() const CV_OVERRIDE { return learnt_thetas; }
110
virtual int getVarCount() const CV_OVERRIDE { return learnt_thetas.cols; }
111
virtual bool isTrained() const CV_OVERRIDE { return !learnt_thetas.empty(); }
112
virtual bool isClassifier() const CV_OVERRIDE { return true; }
113
virtual String getDefaultName() const CV_OVERRIDE { return "opencv_ml_lr"; }
114
protected:
115
Mat calc_sigmoid(const Mat& data) const;
116
double compute_cost(const Mat& _data, const Mat& _labels, const Mat& _init_theta);
117
void compute_gradient(const Mat& _data, const Mat& _labels, const Mat &_theta, const double _lambda, Mat & _gradient );
118
Mat batch_gradient_descent(const Mat& _data, const Mat& _labels, const Mat& _init_theta);
119
Mat mini_batch_gradient_descent(const Mat& _data, const Mat& _labels, const Mat& _init_theta);
120
bool set_label_map(const Mat& _labels_i);
121
Mat remap_labels(const Mat& _labels_i, const map<int, int>& lmap) const;
122
protected:
123
LrParams params;
124
Mat learnt_thetas;
125
map<int, int> forward_mapper;
126
map<int, int> reverse_mapper;
127
Mat labels_o;
128
Mat labels_n;
129
};
130
131
Ptr<LogisticRegression> LogisticRegression::create()
132
{
133
return makePtr<LogisticRegressionImpl>();
134
}
135
136
Ptr<LogisticRegression> LogisticRegression::load(const String& filepath, const String& nodeName)
137
{
138
return Algorithm::load<LogisticRegression>(filepath, nodeName);
139
}
140
141
142
bool LogisticRegressionImpl::train(const Ptr<TrainData>& trainData, int)
143
{
144
CV_TRACE_FUNCTION_SKIP_NESTED();
145
// return value
146
bool ok = false;
147
148
if (trainData.empty()) {
149
return false;
150
}
151
clear();
152
Mat _data_i = trainData->getSamples();
153
Mat _labels_i = trainData->getResponses();
154
155
// check size and type of training data
156
CV_Assert( !_labels_i.empty() && !_data_i.empty());
157
if(_labels_i.cols != 1)
158
{
159
CV_Error( CV_StsBadArg, "labels should be a column matrix" );
160
}
161
if(_data_i.type() != CV_32FC1 || _labels_i.type() != CV_32FC1)
162
{
163
CV_Error( CV_StsBadArg, "data and labels must be a floating point matrix" );
164
}
165
if(_labels_i.rows != _data_i.rows)
166
{
167
CV_Error( CV_StsBadArg, "number of rows in data and labels should be equal" );
168
}
169
170
// class labels
171
set_label_map(_labels_i);
172
Mat labels_l = remap_labels(_labels_i, this->forward_mapper);
173
int num_classes = (int) this->forward_mapper.size();
174
if(num_classes < 2)
175
{
176
CV_Error( CV_StsBadArg, "data should have atleast 2 classes" );
177
}
178
179
// add a column of ones to the data (bias/intercept term)
180
Mat data_t;
181
hconcat( cv::Mat::ones( _data_i.rows, 1, CV_32F ), _data_i, data_t );
182
183
// coefficient matrix (zero-initialized)
184
Mat thetas;
185
Mat init_theta = Mat::zeros(data_t.cols, 1, CV_32F);
186
187
// fit the model (handles binary and multiclass cases)
188
Mat new_theta;
189
Mat labels;
190
if(num_classes == 2)
191
{
192
labels_l.convertTo(labels, CV_32F);
193
if(this->params.train_method == LogisticRegression::BATCH)
194
new_theta = batch_gradient_descent(data_t, labels, init_theta);
195
else
196
new_theta = mini_batch_gradient_descent(data_t, labels, init_theta);
197
thetas = new_theta.t();
198
}
199
else
200
{
201
/* take each class and rename classes you will get a theta per class
202
as in multi class class scenario, we will have n thetas for n classes */
203
thetas.create(num_classes, data_t.cols, CV_32F);
204
Mat labels_binary;
205
int ii = 0;
206
for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
207
{
208
// one-vs-rest (OvR) scheme
209
labels_binary = (labels_l == it->second)/255;
210
labels_binary.convertTo(labels, CV_32F);
211
if(this->params.train_method == LogisticRegression::BATCH)
212
new_theta = batch_gradient_descent(data_t, labels, init_theta);
213
else
214
new_theta = mini_batch_gradient_descent(data_t, labels, init_theta);
215
hconcat(new_theta.t(), thetas.row(ii));
216
ii += 1;
217
}
218
}
219
220
// check that the estimates are stable and finite
221
this->learnt_thetas = thetas.clone();
222
if( cvIsNaN( (double)sum(this->learnt_thetas)[0] ) )
223
{
224
CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
225
}
226
227
// success
228
ok = true;
229
return ok;
230
}
231
232
float LogisticRegressionImpl::predict(InputArray samples, OutputArray results, int flags) const
233
{
234
// check if learnt_mats array is populated
235
if(!this->isTrained())
236
{
237
CV_Error( CV_StsBadArg, "classifier should be trained first" );
238
}
239
240
// coefficient matrix
241
Mat thetas;
242
if ( learnt_thetas.type() == CV_32F )
243
{
244
thetas = learnt_thetas;
245
}
246
else
247
{
248
this->learnt_thetas.convertTo( thetas, CV_32F );
249
}
250
CV_Assert(thetas.rows > 0);
251
252
// data samples
253
Mat data = samples.getMat();
254
if(data.type() != CV_32F)
255
{
256
CV_Error( CV_StsBadArg, "data must be of floating type" );
257
}
258
259
// add a column of ones to the data (bias/intercept term)
260
Mat data_t;
261
hconcat( cv::Mat::ones( data.rows, 1, CV_32F ), data, data_t );
262
CV_Assert(data_t.cols == thetas.cols);
263
264
// predict class labels for samples (handles binary and multiclass cases)
265
Mat labels_c;
266
Mat pred_m;
267
Mat temp_pred;
268
if(thetas.rows == 1)
269
{
270
// apply sigmoid function
271
temp_pred = calc_sigmoid(data_t * thetas.t());
272
CV_Assert(temp_pred.cols==1);
273
pred_m = temp_pred.clone();
274
275
// if greater than 0.5, predict class 0 or predict class 1
276
temp_pred = (temp_pred > 0.5f) / 255;
277
temp_pred.convertTo(labels_c, CV_32S);
278
}
279
else
280
{
281
// apply sigmoid function
282
pred_m.create(data_t.rows, thetas.rows, data.type());
283
for(int i = 0; i < thetas.rows; i++)
284
{
285
temp_pred = calc_sigmoid(data_t * thetas.row(i).t());
286
vconcat(temp_pred, pred_m.col(i));
287
}
288
289
// predict class with the maximum output
290
Point max_loc;
291
Mat labels;
292
for(int i = 0; i < pred_m.rows; i++)
293
{
294
temp_pred = pred_m.row(i);
295
minMaxLoc( temp_pred, NULL, NULL, NULL, &max_loc );
296
labels.push_back(max_loc.x);
297
}
298
labels.convertTo(labels_c, CV_32S);
299
}
300
301
// return label of the predicted class. class names can be 1,2,3,...
302
Mat pred_labs = remap_labels(labels_c, this->reverse_mapper);
303
pred_labs.convertTo(pred_labs, CV_32S);
304
305
// return either the labels or the raw output
306
if ( results.needed() )
307
{
308
if ( flags & StatModel::RAW_OUTPUT )
309
{
310
pred_m.copyTo( results );
311
}
312
else
313
{
314
pred_labs.copyTo(results);
315
}
316
}
317
318
return ( pred_labs.empty() ? 0.f : static_cast<float>(pred_labs.at<int>(0)) );
319
}
320
321
Mat LogisticRegressionImpl::calc_sigmoid(const Mat& data) const
322
{
323
CV_TRACE_FUNCTION();
324
Mat dest;
325
exp(-data, dest);
326
return 1.0/(1.0+dest);
327
}
328
329
double LogisticRegressionImpl::compute_cost(const Mat& _data, const Mat& _labels, const Mat& _init_theta)
330
{
331
CV_TRACE_FUNCTION();
332
float llambda = 0; /*changed llambda from int to float to solve issue #7924*/
333
int m;
334
int n;
335
double cost = 0;
336
double rparameter = 0;
337
Mat theta_b;
338
Mat theta_c;
339
Mat d_a;
340
Mat d_b;
341
342
m = _data.rows;
343
n = _data.cols;
344
345
theta_b = _init_theta(Range(1, n), Range::all());
346
347
if (params.norm != REG_DISABLE)
348
{
349
llambda = 1;
350
}
351
352
if(this->params.norm == LogisticRegression::REG_L1)
353
{
354
rparameter = (llambda/(2*m)) * sum(theta_b)[0];
355
}
356
else
357
{
358
// assuming it to be L2 by default
359
multiply(theta_b, theta_b, theta_c, 1);
360
rparameter = (llambda/(2*m)) * sum(theta_c)[0];
361
}
362
363
d_a = calc_sigmoid(_data * _init_theta);
364
log(d_a, d_a);
365
multiply(d_a, _labels, d_a);
366
367
// use the fact that: log(1 - sigmoid(x)) = log(sigmoid(-x))
368
d_b = calc_sigmoid(- _data * _init_theta);
369
log(d_b, d_b);
370
multiply(d_b, 1-_labels, d_b);
371
372
cost = (-1.0/m) * (sum(d_a)[0] + sum(d_b)[0]);
373
cost = cost + rparameter;
374
375
if(cvIsNaN( cost ) == 1)
376
{
377
CV_Error( CV_StsBadArg, "check training parameters. Invalid training classifier" );
378
}
379
380
return cost;
381
}
382
383
struct LogisticRegressionImpl_ComputeDradient_Impl : ParallelLoopBody
384
{
385
const Mat* data;
386
const Mat* theta;
387
const Mat* pcal_a;
388
Mat* gradient;
389
double lambda;
390
391
LogisticRegressionImpl_ComputeDradient_Impl(const Mat& _data, const Mat &_theta, const Mat& _pcal_a, const double _lambda, Mat & _gradient)
392
: data(&_data)
393
, theta(&_theta)
394
, pcal_a(&_pcal_a)
395
, gradient(&_gradient)
396
, lambda(_lambda)
397
{
398
399
}
400
401
void operator()(const cv::Range& r) const CV_OVERRIDE
402
{
403
const Mat& _data = *data;
404
const Mat &_theta = *theta;
405
Mat & _gradient = *gradient;
406
const Mat & _pcal_a = *pcal_a;
407
const int m = _data.rows;
408
Mat pcal_ab;
409
410
for (int ii = r.start; ii<r.end; ii++)
411
{
412
Mat pcal_b = _data(Range::all(), Range(ii,ii+1));
413
multiply(_pcal_a, pcal_b, pcal_ab, 1);
414
415
_gradient.row(ii) = (1.0/m)*sum(pcal_ab)[0] + (lambda/m) * _theta.row(ii);
416
}
417
}
418
};
419
420
void LogisticRegressionImpl::compute_gradient(const Mat& _data, const Mat& _labels, const Mat &_theta, const double _lambda, Mat & _gradient )
421
{
422
CV_TRACE_FUNCTION();
423
const int m = _data.rows;
424
Mat pcal_a, pcal_b, pcal_ab;
425
426
const Mat z = _data * _theta;
427
428
CV_Assert( _gradient.rows == _theta.rows && _gradient.cols == _theta.cols );
429
430
pcal_a = calc_sigmoid(z) - _labels;
431
pcal_b = _data(Range::all(), Range(0,1));
432
multiply(pcal_a, pcal_b, pcal_ab, 1);
433
434
_gradient.row(0) = ((float)1/m) * sum(pcal_ab)[0];
435
436
//cout<<"for each training data entry"<<endl;
437
LogisticRegressionImpl_ComputeDradient_Impl invoker(_data, _theta, pcal_a, _lambda, _gradient);
438
cv::parallel_for_(cv::Range(1, _gradient.rows), invoker);
439
}
440
441
442
Mat LogisticRegressionImpl::batch_gradient_descent(const Mat& _data, const Mat& _labels, const Mat& _init_theta)
443
{
444
CV_TRACE_FUNCTION();
445
// implements batch gradient descent
446
if(this->params.alpha<=0)
447
{
448
CV_Error( CV_StsBadArg, "check training parameters (learning rate) for the classifier" );
449
}
450
451
if(this->params.num_iters <= 0)
452
{
453
CV_Error( CV_StsBadArg, "number of iterations cannot be zero or a negative number" );
454
}
455
456
int llambda = 0;
457
int m;
458
Mat theta_p = _init_theta.clone();
459
Mat gradient( theta_p.rows, theta_p.cols, theta_p.type() );
460
m = _data.rows;
461
462
if (params.norm != REG_DISABLE)
463
{
464
llambda = 1;
465
}
466
467
for(int i = 0;i<this->params.num_iters;i++)
468
{
469
// this seems to only be called to ensure that cost is not NaN
470
compute_cost(_data, _labels, theta_p);
471
472
compute_gradient( _data, _labels, theta_p, llambda, gradient );
473
474
theta_p = theta_p - ( static_cast<double>(this->params.alpha)/m)*gradient;
475
}
476
return theta_p;
477
}
478
479
Mat LogisticRegressionImpl::mini_batch_gradient_descent(const Mat& _data, const Mat& _labels, const Mat& _init_theta)
480
{
481
// implements batch gradient descent
482
int lambda_l = 0;
483
int m;
484
int j = 0;
485
int size_b = this->params.mini_batch_size;
486
487
if(this->params.mini_batch_size <= 0 || this->params.alpha == 0)
488
{
489
CV_Error( CV_StsBadArg, "check training parameters for the classifier" );
490
}
491
492
if(this->params.num_iters <= 0)
493
{
494
CV_Error( CV_StsBadArg, "number of iterations cannot be zero or a negative number" );
495
}
496
497
Mat theta_p = _init_theta.clone();
498
Mat gradient( theta_p.rows, theta_p.cols, theta_p.type() );
499
Mat data_d;
500
Mat labels_l;
501
502
if (params.norm != REG_DISABLE)
503
{
504
lambda_l = 1;
505
}
506
507
for(int i = 0;i<this->params.term_crit.maxCount;i++)
508
{
509
if(j+size_b<=_data.rows)
510
{
511
data_d = _data(Range(j,j+size_b), Range::all());
512
labels_l = _labels(Range(j,j+size_b),Range::all());
513
}
514
else
515
{
516
data_d = _data(Range(j, _data.rows), Range::all());
517
labels_l = _labels(Range(j, _labels.rows),Range::all());
518
}
519
520
m = data_d.rows;
521
522
// this seems to only be called to ensure that cost is not NaN
523
compute_cost(data_d, labels_l, theta_p);
524
525
compute_gradient(data_d, labels_l, theta_p, lambda_l, gradient);
526
527
theta_p = theta_p - ( static_cast<double>(this->params.alpha)/m)*gradient;
528
529
j += this->params.mini_batch_size;
530
531
// if parsed through all data variables
532
if (j >= _data.rows) {
533
j = 0;
534
}
535
}
536
return theta_p;
537
}
538
539
bool LogisticRegressionImpl::set_label_map(const Mat &_labels_i)
540
{
541
// this function creates two maps to map user defined labels to program friendly labels two ways.
542
int ii = 0;
543
Mat labels;
544
545
this->labels_o = Mat(0,1, CV_8U);
546
this->labels_n = Mat(0,1, CV_8U);
547
548
_labels_i.convertTo(labels, CV_32S);
549
550
for(int i = 0;i<labels.rows;i++)
551
{
552
this->forward_mapper[labels.at<int>(i)] += 1;
553
}
554
555
for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
556
{
557
this->forward_mapper[it->first] = ii;
558
this->labels_o.push_back(it->first);
559
this->labels_n.push_back(ii);
560
ii += 1;
561
}
562
563
for(map<int,int>::iterator it = this->forward_mapper.begin(); it != this->forward_mapper.end(); ++it)
564
{
565
this->reverse_mapper[it->second] = it->first;
566
}
567
568
return true;
569
}
570
571
Mat LogisticRegressionImpl::remap_labels(const Mat& _labels_i, const map<int, int>& lmap) const
572
{
573
Mat labels;
574
_labels_i.convertTo(labels, CV_32S);
575
576
Mat new_labels = Mat::zeros(labels.rows, labels.cols, labels.type());
577
578
CV_Assert( !lmap.empty() );
579
580
for(int i =0;i<labels.rows;i++)
581
{
582
map<int, int>::const_iterator val = lmap.find(labels.at<int>(i,0));
583
CV_Assert(val != lmap.end());
584
new_labels.at<int>(i,0) = val->second;
585
}
586
return new_labels;
587
}
588
589
void LogisticRegressionImpl::clear()
590
{
591
this->learnt_thetas.release();
592
this->labels_o.release();
593
this->labels_n.release();
594
}
595
596
void LogisticRegressionImpl::write(FileStorage& fs) const
597
{
598
// check if open
599
if(fs.isOpened() == 0)
600
{
601
CV_Error(CV_StsBadArg,"file can't open. Check file path");
602
}
603
writeFormat(fs);
604
string desc = "Logistic Regression Classifier";
605
fs<<"classifier"<<desc.c_str();
606
fs<<"alpha"<<this->params.alpha;
607
fs<<"iterations"<<this->params.num_iters;
608
fs<<"norm"<<this->params.norm;
609
fs<<"train_method"<<this->params.train_method;
610
if(this->params.train_method == LogisticRegression::MINI_BATCH)
611
{
612
fs<<"mini_batch_size"<<this->params.mini_batch_size;
613
}
614
fs<<"learnt_thetas"<<this->learnt_thetas;
615
fs<<"n_labels"<<this->labels_n;
616
fs<<"o_labels"<<this->labels_o;
617
}
618
619
void LogisticRegressionImpl::read(const FileNode& fn)
620
{
621
// check if empty
622
if(fn.empty())
623
{
624
CV_Error( CV_StsBadArg, "empty FileNode object" );
625
}
626
627
this->params.alpha = (double)fn["alpha"];
628
this->params.num_iters = (int)fn["iterations"];
629
this->params.norm = (int)fn["norm"];
630
this->params.train_method = (int)fn["train_method"];
631
632
if(this->params.train_method == LogisticRegression::MINI_BATCH)
633
{
634
this->params.mini_batch_size = (int)fn["mini_batch_size"];
635
}
636
637
fn["learnt_thetas"] >> this->learnt_thetas;
638
fn["o_labels"] >> this->labels_o;
639
fn["n_labels"] >> this->labels_n;
640
641
for(int ii =0;ii<labels_o.rows;ii++)
642
{
643
this->forward_mapper[labels_o.at<int>(ii,0)] = labels_n.at<int>(ii,0);
644
this->reverse_mapper[labels_n.at<int>(ii,0)] = labels_o.at<int>(ii,0);
645
}
646
}
647
648
}
649
}
650
651
/* End of file. */
652
653