X-Boost  2.3.8
SoftCascade.h
1 /* XBoost: Ada-Boost and Friends on Haar/ICF/HOG Features, Library and ToolBox
2  *
3  * Copyright (c) 2008-2014 Paolo Medici <medici@ce.unipr.it>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 
21 #ifndef _SOFTCASCADE_TRAINER_H
22 #define _SOFTCASCADE_TRAINER_H
23 
27 #ifdef _MULTITHREAD
28 # include "Thread/thread_group.h"
29 # include "Thread/bind.h"
30 # include "Thread/ref.h"
31 #endif
32 
33 #include <vector>
34 #include <iostream>
35 
37 #include "Classifier/SoftCascade.h"
38 
40 void computeRejectionDistribution(std::vector<double> & v, double alpha, double r, int n)
41 {
42  double acc = 0.0;
43  v.clear();
44  v.reserve(n);
45 
46  for(int i =0; i<n; ++i)
47  {
48  double tau = (double)i/(double)n;
49  double y = (alpha>=0.0) ? exp( alpha * tau) : exp ( -alpha * (1.0 - tau) );
50  acc += y;
51  v.push_back( y );
52  }
53 
54  for(int i =0; i<n; ++i)
55  {
56  v[i] *= r / acc;
57  }
58 
59 }
60 
61 // precision of IO
62 // #define eps 0.00001
63 
67 template<class DataSetType>
68 double findBestRejectionThreshold(const DataSetType & data, const std::vector<double> & response, int target)
69 {
70  std::map<double, int> h; // positive hypothesis map
71  double psum = 0.0, nsum = 0.0;
72  int pcnt = 0, ncnt = 0;
73 
74  if(target <= 0)
75  {
76  // NOTE non ha senso: errore
77  }
78 
79  // precompute response and sort it. h contains positive count
80  for(unsigned int i =0; i<data.templates.size(); ++i)
81  {
82  double v = response[i];
83  if(data.templates[i].category == 1)
84  {
85  h[v] += 1;
86  psum += v;
87  pcnt ++;
88  }
89  else
90  {
91  h[v] += 0; // dummy: only for insert the step in the map
92  nsum += v;
93  ncnt ++;
94  }
95  }
96 
97  int accum = 0;
98  int count = 0;
99 
100  std::cout << "\tPositive Response Average = " << psum / (double) pcnt << " | Negative Response Average = " << nsum / (double) ncnt << '\n';
101  std::cout << "\tResponses range from " << h.begin()->first << " to " << h.rbegin()->first << std::endl;
102 
103  // in ordine decrescente (TODO: si poteva cambiare la funzione sort della mappa)
104  for(std::map<double, int>::const_reverse_iterator i = h.rbegin(); i != h.rend(); ++i)
105  {
106  accum += i->second;
107  count++;
108  if(accum >= target)
109  {
110  double th1 = i->first;
111  ++i; // next threshold
112  if(i==h.rend())
113  {
114  return th1 - std::abs(th1) * 0.01; // 1% overhead if reach the end
115  }
116  double th2 = i->first;
117  if(0)
118  std::cout << "\tThreshold found: " << th1 << " | accum: " << accum << " | count: " << count << std::endl;
119  return (th1 + th2)/2.0; // in the middle
120  }
121  }
122 
123  // nessuna reiezione (out of threshold)
124  return h.begin()->first - std::abs(h.begin()->first) * 0.01;
125 }
126 
130 template<class DataSetType>
131 double findMinRejectionThreshold(const DataSetType & data, const std::vector<double> & response)
132 {
133  double vmin = 100000000000000.0;
134 
135  // precompute response and sort it. h contains positive count
136  for(unsigned int i =0; i<data.templates.size(); ++i)
137  {
138  double v = response[i];
139  if(data.templates[i].category == 1)
140  {
141  if(v<vmin)
142  vmin = v;
143  }
144  }
145 
146  return vmin - 0.00001;
147 }
148 
149 
150 // keeps only pattern with "response" over threshold
151 //template<class DataSetType>
152 //static void rejectSamples(DataSetType & data, std::vector<double> & response, double th)
153 //{
154 // DataSetType out;
155 // out.templates.reserve(data.templates.size()); // TODO
156 // // use SetParams
157 // //out.width = data.width;
158 // //out.height = data.height;
159 // //
160 // out.SetParams(data.GetParams());
161 // char buff[10];
162 // data.GetConf(buff);
163 // out.Configure(buff);
164 //
165 // for(int i = 0; i<data.templates.size(); i++)
166 // {
167 // if(response[i]>=th)
168 // out.Insert(data.templates[i]); //
169 // }
170 //
171 // data = out;
172 // cout << data.n_patternP << " "<<data.n_patternN<<endl;
173 // cin.get();
174 //}
175 
177 template<class DataSetType>
178 void rejectSamples(DataSetType & data, std::vector<double> & response, double th)
179 {
180  // I use support vector instead of erase in order to improve performance
181  std::vector<typename DataSetType::PatternType> newList;
182  std::vector<double> newResponse;
183 
184  newResponse.reserve(response.size());
185  newList.reserve(data.templates.size());
186 
187  for(unsigned int i = 0; i<data.templates.size(); i++)
188  {
189  if(response[i]<th)
190  {
191  if(data.templates[i].category==1)
192  data.n_patternP--;
193  else
194  data.n_patternN--;
195  // do not copy
196  }
197  else
198  {
199  // copy in the newlist only elements above threshold
200  newList.push_back(data.templates[i]);
201  newResponse.push_back(response[i]);
202  }
203  }
204 
205  // swap vectors
206  std::swap(data.templates, newList);
207  std::swap(response, newResponse);
208 
209 }
210 
211 
214 template<class DataSetType>
215 void rejectPositiveSamples(DataSetType & data, const std::vector<double> & response, double th)
216 {
217  std::vector<typename DataSetType::PatternType> newList;
218  newList.reserve(data.templates.size()); // TODO
219 
220  for(unsigned int i = 0; i<data.templates.size(); i++)
221  {
222  if((response[i]>=th) || (data.templates[i].category==-1))
223  newList.push_back(data.templates[i]); //
224  }
225 
226  std::swap(data.templates, newList);
227 
228 }
229 
230 
232 template<class DataSetType>
233 double computeEdge(const DataSetType & data, const std::vector<double> & response)
234 {
235  double pos = 0.0;
236  double neg = 0.0;
237  int npos = 0;
238  int nneg = 0;
239  for(unsigned int i = 0; i<data.templates.size(); i++)
240  {
241  if(data.templates[i].category == 1)
242  {
243  pos += response[i];
244  npos++;
245  }
246  else
247  {
248  neg += response[i];
249  nneg++;
250  }
251  }
252 
253  if(npos>0 && nneg>0)
254  return (pos/npos) - (neg/nneg);
255  else
256  {
257  return 0.0;
258  }
259 }
260 
262 inline double quad(double x)
263 {
264  return x*x;
265 }
266 
268 template<class DataSetType>
269 double computeEdge2(const DataSetType & data, const std::vector<double> & response)
270 {
271  double pos = 0.0;
272  double neg = 0.0;
273  double pos2 = 0.0;
274  double neg2 = 0.0;
275  int npos = 0;
276  int nneg = 0;
277  for(unsigned int i = 0; i<data.templates.size(); i++)
278  {
279  if(data.templates[i].category == 1)
280  {
281  pos += response[i];
282  pos2 += response[i]*response[i];
283  npos++;
284  }
285  else
286  {
287  neg += response[i];
288  neg2 += response[i]*response[i];
289  nneg++;
290  }
291  }
292 
293  if(npos>0 && nneg>0)
294  {
295 // double var = 0.0;
296 
297 
298 // for(int i = 0; i<data.templates.size(); i++)
299 // {
300 // if(data.templates[i].category == 1)
301 // {
302 // var += quad(response[i] - pos);
303 // }
304 // else
305 // {
306 // var += quad(response[i] - neg);
307 // }
308 // }
309  pos/=(double)npos;
310  neg/=(double)nneg;
311 
312  // compute the mahalanobis distance
313  double sigma = std::sqrt( (pos2 + neg2 - pos*pos*npos - neg*neg*nneg) / (double) data.templates.size() );
314 
315  return (pos - neg) / sigma;
316  }
317  else
318  {
319  return 0.0;
320  }
321 }
322 
324 template<class Classifier, class DataSetType>
325 void inner_compute_raw_response(const DataSetType & data, double* response, const Classifier & classifier, int s0, int s1)
326 {
327  for(int i = s0; i<s1; i++)
328  {
329  response[i] = classifier.raw(getData1(data.templates[i],data),getData2(data.templates[i],data));
330  }
331 }
332 
333 template<class Classifier, class DataSetType>
334 void inner_compute_response(const DataSetType & data, double * response, const Classifier & classifier, int s0, int s1)
335 {
336  for(int i = s0; i<s1; i++)
337  {
338  response[i] = classifier(getData1(data.templates[i],data),getData2(data.templates[i],data));
339  }
340 }
341 
342 template<class Classifier, class DataSetType>
343 void inner_update_response(const DataSetType & data, double * response, const Classifier & classifier, int s0, int s1)
344 {
345  for(int i = s0; i<s1; i++)
346  {
347  response[i] += classifier(getData1(data.templates[i],data),getData2(data.templates[i],data));
348  }
349 }
350 
351 // TOOD: move in library?
353 // template<class WeakClassifier, class DataSetType>
354 // void inner_compute_response(const DataSetType & data, std::vector<double> & response, const BoostClassifier<WeakClassifier> & classifier, int s0, int s1)
355 // {
356 // for(int i = s0; i< s1; i++)
357 // {
358 // response[i] = classifier(getData1(data.templates[i],data),getData2(data.templates[i],data));
359 // }
360 // }
361 
362 
363 // TOOD: move in library?
365 template<class WeakClassifier, class DataSetType>
366 void computeResponse(const DataSetType & data, std::vector<double> & response, const SoftCascadeClassifier<WeakClassifier> & classifier, int max_concurrent_jobs)
367 {
368  response.resize( data.Size() );
369 
370 #ifdef _MULTITHREAD
371  if(max_concurrent_jobs>1)
372  {
373  sprint::thread_group thread_pool_;
374  int n_jobs = data.Size();
375  for(int ii=0; ii<max_concurrent_jobs; ii++)
376  {
377  int s0 = (ii*n_jobs)/max_concurrent_jobs;
378  int s1 = ((ii+1)*n_jobs)/max_concurrent_jobs;
379 
380  thread_pool_.create_thread(sprint::thread_bind(&inner_compute_raw_response< SoftCascadeClassifier<WeakClassifier>, DataSetType>, sprint::c_ref(data), &response[0], sprint::c_ref(classifier), s0, s1));
381  }
382 
383  thread_pool_.join_all();
384  }
385  else
386 #endif
387  {
388  inner_compute_raw_response(data, &response[0], classifier, 0, data.Size());
389  }
390 }
391 
392 
393 // TOOD: move in library?
395 template<class WeakClassifier, class DataSetType>
396 static void computeResponse(const DataSetType & data, std::vector<double> & response, const BoostClassifier<WeakClassifier> & classifier, int max_concurrent_jobs)
397 {
398  response.resize( data.Size() );
399 
400 #ifdef _MULTITHREAD
401  if(max_concurrent_jobs>1)
402  {
403  sprint::thread_group thread_pool_;
404  int n_jobs = data.Size();
405  for(int ii=0; ii<max_concurrent_jobs; ii++)
406  {
407  int s0 = (ii*n_jobs)/max_concurrent_jobs;
408  int s1 = ((ii+1)*n_jobs)/max_concurrent_jobs;
409 
410  thread_pool_.create_thread(sprint::thread_bind(&inner_compute_response< BoostClassifier<WeakClassifier>, DataSetType>, sprint::c_ref(data), &response[0], sprint::c_ref(classifier), s0, s1));
411  }
412 
413  thread_pool_.join_all();
414  }
415  else
416 #endif
417  {
418  inner_compute_response(data, &response[0], classifier, 0, data.Size());
419  }
420 }
421 
422 
424 template<class DataSetType>
425 static void resetResponse(const DataSetType & data, std::vector<double> & response)
426 {
427  response.resize( data.Size() );
428 
429  for(int i = 0; i<data.templates.size(); i++)
430  {
431  response[i] = 0.0;
432  }
433 }
434 
435 // TOOD: move in library?
437 template<class WeakClassifier, class DataSetType>
438 static void updateResponse(const DataSetType & data, std::vector<double> & response, const WeakClassifier & classifier, int max_concurrent_jobs)
439 {
440 
441 #ifdef _MULTITHREAD
442  if(max_concurrent_jobs>1)
443  {
444  sprint::thread_group thread_pool_;
445  int n_jobs = data.Size();
446  for(int ii=0; ii<max_concurrent_jobs; ii++)
447  {
448  int s0 = (ii*n_jobs)/max_concurrent_jobs;
449  int s1 = ((ii+1)*n_jobs)/max_concurrent_jobs;
450 
451  thread_pool_.create_thread(sprint::thread_bind(&inner_update_response< WeakClassifier, DataSetType>, sprint::c_ref(data), &response[0], sprint::c_ref(classifier), s0, s1));
452  }
453 
454  thread_pool_.join_all();
455  }
456  else
457 #endif
458  {
459  inner_update_response(data, &response[0], classifier, 0, data.Size());
460  }
461 }
462 
463 
465 static const char *str_metric[] = {"none", "weight", "edge", "mahalanobis"};
466 
468 enum SoftCascadeRankingAlgo {
469  Ranking_None,
470  Ranking_Weight,
471  Ranking_Edge,
472  Ranking_Mahalanobis
473 };
474 
483 template<class WeakClassifier, class DataSetType>
484 void TrainSoftCascade(DataSetType & training_set, const BoostClassifier<WeakClassifier> & _source, SoftCascadeClassifier<WeakClassifier> & dest, int max_stages, SoftCascadeRankingAlgo sort_algo, bool dbp, double ratio, double alpha, bool blind_complete, int max_concurrent_jobs, bool verbose = true)
485 {
486  BoostClassifier<WeakClassifier> source = _source; // working on a copy (destructive training)
487  double p = training_set.n_patternP; // numero di positivi (in floating point) richiesti
488  std::vector<double> v;
489 
490  int n = (max_stages < 1) ? (source.size()) : ( std::min<int>(source.size(), max_stages) );
491 
492  int sumNegative = 0;
493  int initialNegative = training_set.n_patternN;
494  int initialPositive = training_set.n_patternP;
495 
496  std::vector<double> response;
497 
498  if(verbose)
499  std::cout << "[+] using " << n << " features of " << source.size() << " | metric = " << sort_algo << " ("<< str_metric[sort_algo] << ")" << std::endl;
500 
501  // Use direct backward pruning scheme
502  if(dbp)
503  {
504  // reject ratio * training_set.n_patternP patterns
505 
506  computeResponse(training_set, response, source, max_concurrent_jobs);
507 
508  int p0 = (1.0-ratio) * training_set.n_patternP;
509  double r = findBestRejectionThreshold(training_set, response, p0);
510  if(verbose)
511  std::cout << "Using threshold " << r << " to remove too difficult patterns (try to keep " << p0 << " patterns)" << std::endl;
512 
513  // reject alla samples
514  rejectPositiveSamples(training_set, response, r);
515  // NOTE response ora e' invalido. va chiamato computeResponse
516 
517  if(verbose)
518  std::cout << training_set.n_patternP <<"/" << initialPositive <<" patterns survived" << std::endl;
519 
520  }
521  else
522  {
523  // calcola il profilo di rejection
524  computeRejectionDistribution(v, alpha, ratio * training_set.n_patternP, n );
525  }
526 
527  // at maximum a chain long <n> classifiers
528  for(int t =0; t<n; ++t)
529  {
530  int curPositive, curNegative;
531 
532  // positive/negative before rejection
533  curPositive = training_set.n_patternP;
534  curNegative = training_set.n_patternN;
535 
536  if(dbp)
537  {
538  if(verbose)
539  std::cout << '#' << t << std::endl;
540  }
541  else
542  {
543  // rimuovo la quantita' da regettare allo stadio t
544  p -= v[t];
545 
546  if(verbose)
547  std::cout << '#' << t << " | Positive requested = " << p << "/" << training_set.n_patternP << std::endl;
548  }
549 
550  // calcolo la risposta fino a questo momento del 'dest' di tutti i pattern del training_set
551  // e' necessario farla a ogni giro piu' che altro perche' la rimozione dei pattern incasina l'ordine dei pattern.
552  // TODO: really slow!
553  computeResponse(training_set, response, dest, max_concurrent_jobs);
554 
555  double bestEdge = 0.0;
556  // without resorting jbest is the first of the list
558  std::vector<double> bestResponse;
559 
560  switch(sort_algo)
561  {
562  case Ranking_Weight:
563  {
564  // ALGO: WEIGHT
565  // prendo il maggiore alpha rimanente (si poteva fare un sorting + algo:none)
566  bestResponse = response;
567 
568  for (typename BoostClassifier<WeakClassifier>::ClassifierListType::iterator j = source.list().begin(); j!=source.list().end(); ++j)
569  if(j->alpha > jbest->alpha) // NOTE TODO: gli alberi di decisione non hanno alpha
570  jbest = j;
571 
572 
573  // update response
574  updateResponse(training_set, bestResponse, *jbest, max_concurrent_jobs);
575  }
576  break;
577 
578  case Ranking_Edge:
579  {
580  // ALGO: EDGE (original softascade)
581 
582  // for each classifier this function sarches the one that maximize a statistic
583  for (typename BoostClassifier<WeakClassifier>::ClassifierListType::iterator j = source.list().begin(); j!=source.list().end(); ++j) {
584  std::vector<double> test = response; // copy the original response
585 
586  // update test with (*j)
587  updateResponse(training_set, test, *j, max_concurrent_jobs);
588  // TODO: multithread
589 // for(int i = 0; i<training_set.templates.size(); i++)
590 // {
591 // test[i] += (*j)( getData1(training_set.templates[i], training_set), getData2(training_set.templates[i], training_set) );
592 // }
593 
594  // compute the edge
595  double edge = computeEdge(training_set, test);
596  if(j == source.list().begin() || (edge > bestEdge))
597  {
598  jbest = j;
599  bestEdge = edge;
600  bestResponse = test;
601  }
602 
603  }
604 
605  if(verbose)
606  std::cout << "\tavg edge = " << bestEdge << '\n';
607  }
608  break;
609  case Ranking_Mahalanobis:
610  {
611  // ALGO: MAHALANOBIS
612 
613  // per ognuno dei weak classifier rimasti
614  // cerco quello che massimizza la separazione statistica
615  for (typename BoostClassifier<WeakClassifier>::ClassifierListType::iterator j = source.list().begin(); j!=source.list().end(); ++j) {
616  std::vector<double> test = response;
617 
618  // update test with (*j)
619  updateResponse(training_set, test, *j, max_concurrent_jobs);
620  /*
621  for(int i = 0; i<training_set.templates.size(); i++)
622  {
623  test[i] += (*j)(getData1( training_set.templates[i], training_set), getData2(training_set.templates[i], training_set) );
624  }
625  */
626 
627  // compute edge
628  double edge = computeEdge2(training_set, test);
629  if(j == source.list().begin() || (edge > bestEdge))
630  {
631  jbest = j;
632  bestEdge = edge;
633  bestResponse = test;
634  }
635 
636  }
637 
638  if(verbose)
639  std::cout << "\tavg edge = " << bestEdge << '\n';
640  }
641  break;
642  case Ranking_None:
643  {
644  // ALGO: NONE
645  // prendo il successivo in lista
646  bestResponse = response;
647 
648  updateResponse(training_set, bestResponse, *jbest, max_concurrent_jobs);
649  // aggiorno test
650  // for(int i = 0; i<training_set.templates.size(); i++)
651  // bestResponse[i] += (*jbest)(training_set.templates[i].data+training_set.width+1+1,training_set.width+1);
652  }
653  break;
654  }
655  /* NOTE: gli alberi di decisione non hanno alpha TODO */
656  if(verbose)
657  std::cout << "\talpha = " << jbest->alpha << std::endl;
658 
659  // no need to compute new response (response is alredy updated in bestResponse)
660 
661  double r;
662  if(dbp)
663  {
664  // find the trehsold r that keep all positive patterns
665  r = findMinRejectionThreshold(training_set, bestResponse);
666 
667  }
668  else
669  {
670  // find the trehsold r that keep at least p patterns
671  r = findBestRejectionThreshold(training_set, bestResponse, (int) p);
672  }
673 
674  if(verbose)
675  std::cout << "\tthreshold = " << r << std::endl;
676 
677  // add jbest to output pipeline, with threshold r
678  dest.insert(*jbest, jbest->alpha, r); // NOTE TODO gli aberi decisione non hanno alpha
679 
680  // rimuovo jbest dai sorgenti
681  source.list().erase(jbest);
682 
683  // rimuovo tutti i sample (positivi o negativi che siano) che non hanno soddisfatto the r threshold
684  rejectSamples(training_set, bestResponse, r);
685  // NOTE bestResponse viene modificata di conseguenza (si potrebbe swap su response ed evitare
686  // cosi' invocare computeResponse continuamente)
687 
688  if(verbose)
689  std::cout << "\tpositive = " << training_set.n_patternP << " (" << (100 * training_set.n_patternP) / initialPositive << "%) " << (int) training_set.n_patternP - curPositive
690  << " | negative = " << training_set.n_patternN << " (" << (100 * training_set.n_patternN) / initialNegative << "%) " << (int) training_set.n_patternN - curNegative << std::endl;
691 
692  // statistiche dei negativi
693  sumNegative += training_set.n_patternN;
694  if(training_set.n_patternN == 0 || training_set.n_patternP == 0)
695  {
696  if(verbose)
697  std::cout << "no pattern lefts. terminated." << std::endl;
698  break;
699  }
700 
701  }
702 
703 
705  if(blind_complete)
706  {
707  if(verbose)
708  std::cout << "Insert additional " << source.list().size() << " features..." << std::endl;
709  // MERGE REMAINING CLASSIFIER in order to use AdaBoost as original in the last part
710  for (typename BoostClassifier<WeakClassifier>::ClassifierListType::iterator j = source.list().begin(); j!=source.list().end(); ++j)
711  dest.insert(*j, j->alpha, -100000.0); // TODO lower bound of worst pattern
712  }
713 
714 // std::cout << "Average negative samples evaluated:" << (double) sumNegative / (double) dest.length() << "/" << initialNegative << std::endl;
715 // std::cout << "[+] Average number of stages evaluated for negative samples = " << (double) initialNegative * (double) dest.length() / (double) sumNegative << std::endl;
716  if(verbose)
717  std::cout << "[+] Average number of stages evaluated for negative samples = " << (double) sumNegative / (double) initialNegative << std::endl;
718 
719 
720 }
721 
722 #endif
ClassifierListType & list()
Return the inner list of classifier.
Definition: BoostClassifier.h:125
reference ref class
Definition: thread_group.h:82
void join_all()
wait all threads terminate
Definition: thread_group.h:114
proposal 1 for thread group
int size() const
return the number of weak classifiers
Definition: BoostClassifier.h:119
method to create function pointer for thread call
bool create_thread(const sprint::thread_function &p)
create an additional thread
Definition: thread_group.h:102
Definition: SoftCascade.h:76
a voting for majority classifier.