UFJF - Machine Learning Toolkit  0.51.8
SMO.hpp
1 //
2 // Created by Mateus Coutinho Marim on 7/28/2018.
3 //
4 /*****************************************************
5  * SMO classifier lib *
6  * *
7  * Saul Leite <lsaul@lncc.br> *
8  * sep 23, 2004 *
9  *****************************************************/
10 
11 /*****************************************************
12  * SMO classifier lib *
13  *****************************************************/
14 
15 #ifndef UFJF_MLTK_SMO_H
16 #define UFJF_MLTK_SMO_H
17 #pragma once
18 
19 #include "DualClassifier.hpp"
20 #include "int_dll.h"
21 #include <list>
22 
23 namespace mltk{
24  namespace classifier {
25 
26  template<typename T>
27  class SMO : public DualClassifier<T> {
28  private:
29  struct smo_learning_data {
30  double error;
31  char done;
32  int_dll *sv = nullptr;
33 
34  smo_learning_data() {
35  error = 0.0;
36  done = 0;
37  sv = nullptr;
38  }
39  };
40  double param = 0;
41  const int C = 9999; //0.05
42  const double TOL = 0.0001;
43  std::vector<smo_learning_data> l_data;
44  int_dll *head = nullptr;
45 
46  bool examine_example(int i1);
47 
48  bool max_errors(int i1, double e1);
49 
50  bool iterate_non_bound(int i1);
51 
52  bool iterate_all_set(int i1);
53 
54  int take_step(int i1, int i2);
55 
56  double function(int index);
57 
58  bool training_routine();
59 
60  void test_learning();
61 
62  int train_matrix(Kernel<T> *matrix);
63 
64  public:
65  SMO() = default;
66  explicit SMO(const Data<T>& samples, KernelType kernel_type = KernelType::INNER_PRODUCT,
67  double param = 0, int verbose = 0);
68 
69  bool train() override;
70 
71  ~SMO();
72  };
73 
74  template<typename T>
75  SMO<T>::SMO(const Data<T>& samples, KernelType kernel_type, double param, int verbose) {
76  this->samples = mltk::make_data<T>(samples);
77  this->verbose = verbose;
78  this->kernel_type = kernel_type;
79  this->kernel_param = param;
80  this->head = nullptr;
81  }
82 
83  template<typename T>
84  SMO<T>::~SMO() {
85  if (this->head != nullptr) {
86  int_dll::free(&this->head);
87  this->head = nullptr;
88  }
89  if(this->kernel) delete this->kernel;
90  }
91 
92  template<typename T>
93  bool SMO<T>::train() {
94  size_t i = 0, size = this->samples->size(), dim = this->samples->dim();
95  bool ret = true;
96  dMatrix matrix;
97  double norm = 1;
98  std::vector<double> w_saved;
99  this->solution = Solution();
100  this->head = new int_dll;
101  this->l_data.resize(size);
102 
103  /*clear data*/
104  this->solution.bias = 0;
105  for (i = 0; i < size; i++)
106  (*this->samples)[i]->Alpha() = 0;
107  this->alpha.assign(size, 0.0);
108  if(this->kernel) delete this->kernel;
109  this->kernel = new mltk::Kernel<T>(this->kernel_type, this->kernel_param);
110  this->kernel->compute(this->samples);
111 
112  this->timer.reset();
113  /*run training algorithm*/
114  ret = training_routine();
115 
116  norm = this->kernel->featureSpaceNorm(this->samples);
117  if (this->kernel->getType() == 0)
118  w_saved = this->getWeight().X();
119  else {
120  if (this->kernel->getType() == 1 && this->kernel->getParam() == 1)
121  w_saved = this->getDualWeightProdInt().X();
122  else
123  w_saved = this->getDualWeight().X();
124  }
125 
126  this->solution.w = w_saved;
127  this->solution.margin = 1.0 / norm;
128  this->solution.alpha.assign(size, 0.0);
129  this->solution.svs = 0;
130 
131  for (i = 0; i < size; ++i) {
132  this->solution.alpha[i] = (*this->samples)[i]->Alpha();
133  if ((*this->samples)[i]->Alpha() > 0) ++this->solution.svs;
134  if ((*this->samples)[i]->Alpha() > this->C) ret = false;
135  }
136 
137  if (this->verbose) {
138  std::cout << "Number of Support Vectors: " << this->solution.svs << std::endl;
139  std::cout << "Margin found: " << this->solution.margin << "\n\n";
140 
141  if (this->verbose > 1) {
142  std::vector<int> fnames = this->samples->getFeaturesNames();
143  for (i = 0; i < dim; i++)
144  std::cout << "W[" << i << "]: " << this->solution.w[i] << std::endl;
145  std::cout << "Bias: " << this->solution.bias << "\n\n";
146  }
147  }
148 
149  int_dll::free(&this->head);
150  this->l_data.clear();
151 
152  return ret;
153  }
154 
155 
156  template<typename T>
157  bool SMO<T>::examine_example(int i1) {
158  size_t i = 0, size = this->samples->size();
159  double y1 = 0;
160  double e1 = 0;
161  double r1 = 0;
162  double alpha1 = 0;
163 
164  /*cleaning up done list*/
165  for (i = 0; i < size; ++i) this->l_data[i].done = 0;
166  this->l_data[i1].done = 1;
167 
168  /*reading stuff from array*/
169  auto p = (*this->samples)[i1];
170  y1 = p->Y();
171  alpha1 = p->Alpha();
172  if (alpha1 > 0 && alpha1 < this->C) e1 = this->l_data[i1].error;
173  else e1 = function(i1) - y1;
174 
175  /*calculating r1*/
176  r1 = y1 * e1;
177 
178  /*try to find next example by 3 different ways*/
179  if ((r1 < -this->TOL && alpha1 < this->C) || (r1 > this->TOL && alpha1 > 0)) {
180  if (max_errors(i1, e1)) return true;
181  else if (iterate_non_bound(i1)) return true;
182  else if (iterate_all_set(i1)) return true;
183  } else if (this->verbose > 2) std::cout << "Return0 -1\n";
184 
185  return false;
186  }
187 
188  template<typename T>
189  bool SMO<T>::max_errors(int i1, double e1) {
190  int k = 0;
191  int i2 = -1;
192  double tmax = 0;
193  double e2 = 0;
194  double temp = 0;
195  int_dll *list = nullptr;
196 
197  if (this->verbose > 2) std::cout << " Max errors iterations\n";
198 
199  /*iterate through the non-bond examples*/
200  list = this->head->next;
201  while (list != nullptr) {
202  k = list->index;
203  if (this->l_data[k].done == 0 && (*this->samples)[k]->Alpha() < this->C) {
204  e2 = this->l_data[k].error;
205  temp = fabs(e1 - e2);
206 
207  if (temp > tmax) {
208  tmax = temp;
209  i2 = k;
210  }
211  }
212  list = list->next;
213  }
214 
215  return (i2 >= 0 && take_step(i1, i2));
216  }
217 
218  template<typename T>
219  bool SMO<T>::iterate_non_bound(int i1) {
220  int k = 0;
221  int_dll *list = nullptr;
222 
223  if (this->verbose > 2) printf(" Non-bound iteration\n");
224 
225  /* look through all non-bound examples*/
226  list = this->head->next;
227  while (list != nullptr) {
228  k = list->index;
229  if (this->l_data[k].done == 0 && (*this->samples)[k]->Alpha() < this->C)
230  if (take_step(i1, k)) return true;
231  list = list->next;
232  }
233 
234  return false;
235  }
236 
237  template<typename T>
238  bool SMO<T>::iterate_all_set(int i1) {
239  int k0 = 0;
240  int k = 0;
241  int i2 = 0;
242  size_t size = this->samples->size();
243 
244  if (this->verbose > 2) std::cout << " All-set iteration\n";
245 
246  srand(0);
247  /*random starting point*/
248  //k0 = 0;
249  k0 = rand() % size;
250 
251  for (k = k0; k < size + k0; ++k) {
252  i2 = k % size;
253  if (this->l_data[i2].done == 0 && take_step(i1, i2))
254  return true;
255  }
256  return false;
257  }
258 
259  template<typename T>
260  int SMO<T>::take_step(int i1, int i2) {
261  int i = 0, y1 = 0, y2 = 0, s = 0; //, size=0;
262  double alpha1 = 0, alpha2 = 0, new_alpha1 = 0, new_alpha2 = 0;
263  double e1 = 0, e2 = 0, min_val = 0, max_val = 0, eta = 0;
264  double max_val_f = 0, min_val_f = 0;
265  double bnew = 0, b = 0; //delta_b=0 , b=0;
266  double t1 = 0, t2 = 0, error_tot = 0;
267  int_dll *itr = nullptr;
268  dMatrix *matrix = this->kernel->getKernelMatrixPointer();
269 
270  /*this sample is done*/
271  this->l_data[i2].done = 1;
272 
273  /*get info from sample struct*/
274  b = -this->solution.bias;
275  y1 = (*this->samples)[i1]->Y();
276  y2 = (*this->samples)[i2]->Y();
277  //size = sample->size;
278  alpha1 = (*this->samples)[i1]->Alpha();
279  alpha2 = (*this->samples)[i2]->Alpha();
280 
281  /*get error values for i1*/
282  if (alpha1 > 0 && alpha1 < this->C) e1 = this->l_data[i1].error;
283  else e1 = function(i1) - y1;
284 
285  /*get error values for i2*/
286  if (alpha2 > 0 && alpha2 < this->C) e2 = this->l_data[i2].error;
287  else e2 = function(i2) - y2;
288 
289  /*calculate s*/
290  s = y1 * y2;
291 
292  /*compute min and max*/
293  if (s == -1) {
294  min_val = std::max(0.0, alpha2 - alpha1);
295  max_val = std::min(double(this->C), this->C + alpha2 - alpha1);
296  } else {
297  min_val = std::max(0.0, alpha2 + alpha1 - this->C);
298  max_val = std::min(double(this->C), alpha1 + alpha2);
299  }
300  if (min_val == max_val) {
301  if (this->verbose > 2) std::cout << "return0 2\n";
302  return false;
303  }
304 
305  /*compute eta*/
306  eta = 2.0 * (*matrix)[i1][i2] - (*matrix)[i1][i1] - (*matrix)[i2][i2];
307 
308  /*compute new alpha2*/
309  if (eta < 0) {
310  new_alpha2 = alpha2 + y2 * (e2 - e1) / eta;
311 
312  if (new_alpha2 < min_val) new_alpha2 = min_val;
313  else if (new_alpha2 > max_val) new_alpha2 = max_val;
314  } else {
315  /*computing min and max functions*/
316  double c1 = eta / 2.0;
317  double c2 = y2 * (e1 - e2) - eta * alpha2;
318  min_val_f = c1 * min_val * min_val + c2 * min_val;
319  max_val_f = c1 * max_val * max_val + c2 * min_val;
320 
321  if (min_val_f > max_val_f + this->EPS) new_alpha2 = min_val;
322  else if (min_val_f < max_val_f - this->EPS) new_alpha2 = max_val;
323  else new_alpha2 = alpha2;
324  }
325 
326  /*exit if no change made*/
327  if (fabs(new_alpha2 - alpha2) < this->EPS * (new_alpha2 + alpha2 + this->EPS)) {
328  if (this->verbose > 2)std::cout << "return0 3\n";
329  return false;
330  }
331 
332  /*calculate new alpha1*/
333  new_alpha1 = alpha1 - s * (new_alpha2 - alpha2);
334  if (new_alpha1 < 0) {
335  new_alpha2 += s * new_alpha1;
336  new_alpha1 = 0;
337  } else if (new_alpha1 > this->C) {
338  new_alpha2 += s * (new_alpha1 - this->C);
339  new_alpha1 = this->C;
340  }
341  /*saving new alphas*/
342  (*this->samples)[i1]->Alpha() = new_alpha1;
343  (*this->samples)[i2]->Alpha() = new_alpha2;
344 
345  /*saving new stuff into sv list*/
346  if (new_alpha1 > 0 && this->l_data[i1].sv == nullptr) {
347  int_dll *list = int_dll::append(this->head);
348 
349  list->index = i1;
350  this->l_data[i1].sv = list;
351  } else if (new_alpha1 == 0 && this->l_data[i1].sv != nullptr) {
352  int_dll::remove(&(this->l_data[i1].sv));
353  }
354 
355  if (new_alpha2 > 0 && this->l_data[i2].sv == nullptr) {
356  int_dll *list = int_dll::append(this->head);
357  list->index = i2;
358  this->l_data[i2].sv = list;
359  } else if (new_alpha2 == 0 && this->l_data[i2].sv != nullptr) {
360  int_dll::remove(&(this->l_data[i2].sv));
361  }
362 
363  /*update bias*/
364  t1 = y1 * (new_alpha1 - alpha1);
365  t2 = y2 * (new_alpha2 - alpha2);
366 
367  if (new_alpha1 > 0 && new_alpha1 < this->C)
368  bnew = b + e1 + t1 * (*matrix)[i1][i1] + t2 * (*matrix)[i1][i2];
369  else {
370  if (new_alpha2 > 0 && new_alpha2 < this->C)
371  bnew = b + e2 + t1 * (*matrix)[i1][i2] + t2 * (*matrix)[i2][i2];
372  else {
373  double b1 = 0, b2 = 0;
374  b2 = b + e1 + t1 * (*matrix)[i1][i1] + t2 * (*matrix)[i1][i2];
375  b1 = b + e2 + t1 * (*matrix)[i1][i2] + t2 * (*matrix)[i2][i2];
376  bnew = (b1 + b2) / 2.0;
377  }
378  }
379  //delta_b = bnew - b;
380  b = bnew;
381  this->solution.bias = -b;
382 
383  /*updating error cache*/
384  error_tot = 0;
385  itr = this->head->next;
386  while (itr != nullptr) {
387  i = itr->index;
388  if ((i != i1 && i != i2) && (*this->samples)[i]->Alpha() < C) {
389  this->l_data[i].error = function(i) - (*this->samples)[i]->Y();
390  error_tot += this->l_data[i].error;
391  }
392  itr = itr->next;
393  }
394 
395  this->l_data[i1].error = 0.0;
396  this->l_data[i2].error = 0.0;
397 
398  if (this->verbose > 1)
399  std::cout << "Total error= " << error_tot << ", alpha(" << i1 << ")= " << new_alpha1 << ", alpha(" << i2
400  << ")= " << new_alpha2 << std::endl;
401 
402  return 1;
403  }
404 
405  template<typename T>
406  double SMO<T>::function(int index) {
407  int i = 0;
408  double sum = 0;
409  dMatrix *matrix = this->kernel->getKernelMatrixPointer();
410  int_dll *list = this->head->next;
411 
412  while (list != nullptr) {
413  i = list->index;
414  if ((*this->samples)[i]->Alpha() > 0)
415  sum += (*this->samples)[i]->Alpha() * (*this->samples)[i]->Y() * (*matrix)[i][index];
416  list = list->next;
417  }
418  sum += this->solution.bias;
419 
420  return sum;
421  }
422 
423  template<typename T>
424  bool SMO<T>::training_routine() {
425  size_t size = this->samples->size();
426  size_t epoch = 0;
427  int k = 0;
428  int num_changed = 0;
429  int tot_changed = 0;
430  int examine_all = 1;
431 
432  /*initialize variables*/
433  this->solution.bias = 0;
434  for (k = 0; k < size; ++k) {
435  (*this->samples)[k]->Alpha() = 0;
436  this->l_data[k].error = 0;
437  this->l_data[k].done = 0;
438  }
439 
440  /*training*/
441  while (num_changed > 0 || examine_all) {
442  /*stop if iterated too much!*/
443  if (epoch > this->MAX_EPOCH) return false;
444 
445  num_changed = 0;
446  if (examine_all)
447  for (k = 0; k < size; ++k) {
448  num_changed += examine_example(k);
449  }
450  else
451  for (k = 0; k < size; ++k)
452  if ((*this->samples)[k]->Alpha() > 0 && (*this->samples)[k]->Alpha() < C)
453  num_changed += examine_example(k);
454 
455  if (examine_all == 1) examine_all = 0;
456  else if (num_changed == 0) examine_all = 1;
457  tot_changed += num_changed;
458  ++epoch;
459  }
460 
461  /*final verbose*/
462  if (this->verbose) {
463  test_learning();
464  //printf("Margin = %lf, number of changes %d\n",1.0/norm,tot_changed);
465  //free(w);
466  }
467  return true;
468  }
469 
470  template<typename T>
471  void SMO<T>::test_learning() {
472  size_t i = 0, size = this->samples->size();
473  for (i = 0; i < size; ++i)
474  std::cout << i + 1 << " -> " << function(i) << " (error=" << this->l_data[i].error << ") (alpha="
475  << (*this->samples)[i]->Alpha() << ")\n";
476  }
477 
478  template<typename T>
479  int SMO<T>::train_matrix(Kernel<T> *matrix) {
480  size_t i = 0, size = this->samples->size();
481  bool ret = true;
482  double norm = 1;
483  std::vector<smo_learning_data> l_data(size);
484 
485  //srand(0);
486 
487  /*clear data*/
488  this->solution.bias = 0;
489  for (i = 0; i < size; i++)
490  (*this->samples)[i]->Alpha() = 0;
491 
492  /*run training algorithm*/
493  ret = training_routine();
494 
495  norm = matrix->featureSpaceNorm(this->samples);
496  this->solution.margin = 1.0 / norm;
497 
498  this->solution.svs = 0;
499  for (i = 0; i < size; ++i) {
500  if ((*this->samples)[i]->Alpha() > 0) ++this->solution.svs;
501  if ((*this->samples)[i]->Alpha() > this->C) ret = false;
502  }
503 
504  int_dll::free(&this->head);
505 
506  return ret;
507  }
508  }
509 }
510 #endif //UFJF_MLTK_SMO_H
std::shared_ptr< Data< T > > samples
Samples used in the model training.
Definition: Learner.hpp:21
int verbose
Verbose level of the output.
Definition: Learner.hpp:42
Definition: Solution.hpp:13
Definition: DualClassifier.hpp:16
Definition: SMO.hpp:27
bool train() override
Function that execute the training phase of a Learner.
Definition: SMO.hpp:93
A helper class to measure execution time for benchmarking purposes.
Definition: ThreadPool.hpp:503
UFJF-MLTK main namespace for core functionalities.
Definition: classifier/Classifier.hpp:11
T min(const Point< T, R > &p)
Returns the min value of the point.
Definition: Point.hpp:557
T max(const Point< T, R > &p)
Returns the max value of the point.
Definition: Point.hpp:544
Definition: int_dll.h:10