ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
trainer.h
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #pragma once
9 
10 // system
11 #include <vector>
12 
13 // dlib
14 #include <dlib/matrix.h>
15 #include <dlib/svm.h>
16 
17 class LDATrainer {
18 public:
19  typedef dlib::matrix<float, 0, 1> sample_type;
20  typedef dlib::linear_kernel<sample_type> kernel_type;
21  typedef dlib::decision_function<kernel_type> trained_function_type;
22  // typedef trained_function_type::mem_manager_type mem_manager_type;
23 
24  trained_function_type train(const std::vector<sample_type>& samplesvec,
25  const std::vector<float>& labels) const {
26  size_t fdim = samplesvec[0].size();
27  size_t nsamples = samplesvec.size();
28 
29  long ndata_class1 = 0, ndata_class2 = 0;
30  for (size_t i = 0; i < nsamples; ++i) {
31  if (labels[i] > 0)
32  ++ndata_class1;
33  else
34  ++ndata_class2;
35  }
36 
37  dlib::matrix<sample_type, 0, 1> samples1, samples2;
38  samples1.set_size(ndata_class1);
39  samples2.set_size(ndata_class2);
40  sample_type mu1;
41  mu1.set_size(fdim);
42  sample_type mu2;
43  mu2.set_size(fdim);
44  for (size_t i = 0; i < fdim; ++i) {
45  mu1(i) = 0;
46  mu2(i) = 0;
47  }
48 
49  ndata_class1 = 0;
50  ndata_class2 = 0;
51  for (size_t i = 0; i < nsamples; ++i) {
52  if (labels[i] > 0) {
53  samples1(ndata_class1) = samplesvec[i];
54  ++ndata_class1;
55  mu1 += samplesvec[i];
56  } else {
57  samples2(ndata_class2) = samplesvec[i];
58  ++ndata_class2;
59  mu2 += samplesvec[i];
60  }
61  }
62  mu1 /= ndata_class1;
63  mu2 /= ndata_class2;
64 
65  // if you get a compilation error coming from here (with templates
66  // and a 'visual_studio_sucks_cov_helper' structure involved) then
67  // you may have to patch the dlib's file 'matrix_utilities.h":
68  //
69  // line 1611, replace
70  // const matrix<double,EXP::type::NR,EXP::type::NC, typename
71  // EXP::mem_manager_type> avg = mean(m);
72  // by
73  // const typename EXP::type avg = mean(m);
74  //
75  dlib::matrix<float> sigma1 = covariance(samples1);
76  dlib::matrix<float> sigma2 = covariance(samples2);
77 
78  sample_type w_vect = pinv(sigma1 + sigma2) * (mu2 - mu1);
79 
81  // ret.alpha.set_size(fdim);
82  // for (int i=0; i<fdim; ++i) ret.alpha(i) = w_vect(i);
83  ret.alpha = w_vect;
84  ret.b = dot(w_vect, (mu1 + mu2) * 0.5);
85  // linear kernel idiocy
86  ret.basis_vectors.set_size(fdim);
87  for (size_t i = 0; i < fdim; ++i) {
88  ret.basis_vectors(i).set_size(fdim);
89  for (size_t j = 0; j < fdim; ++j) ret.basis_vectors(i)(j) = 0;
90  ret.basis_vectors(i)(i) = 1;
91  }
92  return ret;
93  }
94 
95 #if 0
96  trained_function_type train(const trained_function_type::sample_vector_type& samplesvec, const trained_function_type::scalar_vector_type& labels) const
97  {
98  int fdim = samplesvec(0).size();
99  int nsamples = samplesvec.size();
100 
101  int ndata_class1 = 0, ndata_class2 = 0;
102  for (int i=0; i<nsamples; ++i)
103  {
104  if (labels(i)>0)
105  ++ndata_class1;
106  else
107  ++ndata_class2;
108  }
109 
110  dlib::matrix<sample_type,0,1> samples1, samples2;
111  samples1.set_size(ndata_class1);
112  samples2.set_size(ndata_class2);
113  sample_type mu1; mu1.set_size(fdim);
114  sample_type mu2; mu2.set_size(fdim);
115  for (int i=0; i<fdim; ++i)
116  {
117  mu1(i)=0;
118  mu2(i)=0;
119  }
120 
121  ndata_class1 = 0; ndata_class2 = 0;
122  for (int i=0; i<nsamples; ++i)
123  {
124  if (labels(i)>0)
125  {
126  samples1(ndata_class1) = samplesvec(i);
127  ++ndata_class1;
128  mu1 += samplesvec(i);
129  }
130  else
131  {
132  samples2(ndata_class2) = samplesvec(i);
133  ++ndata_class2;
134  mu2 += samplesvec(i);
135  }
136  }
137  mu1 /= ndata_class1;
138  mu2 /= ndata_class2;
139 
140  // if you get a compilation error coming from here (with templates
141  // and a 'visual_studio_sucks_cov_helper' structure involved) then
142  // you may have to patch the dlib's file 'matrix_utilities.h":
143  //
144  // line 1611, replace
145  // const matrix<double,EXP::type::NR,EXP::type::NC, typename EXP::mem_manager_type> avg = mean(m);
146  // by
147  // const typename EXP::type avg = mean(m);
148  //
149  dlib::matrix<float> sigma1 = covariance(samples1);
150  dlib::matrix<float> sigma2 = covariance(samples2);
151 
152  sample_type w_vect = pinv(sigma1+sigma2) * (mu2 - mu1);
153 
155  //ret.alpha.set_size(fdim);
156  //for (int i=0; i<fdim; ++i) ret.alpha(i) = w_vect(i);
157  ret.alpha = w_vect;
158  ret.b = dot(w_vect,(mu1+mu2)*0.5);
159  // linear kernel idiocy
160  ret.basis_vectors.set_size(fdim);
161  for (int i=0; i<fdim; ++i)
162  {
163  ret.basis_vectors(i).set_size(fdim);
164  for (int j=0; j<fdim; ++j)
165  ret.basis_vectors(i)(j)=0;
166  ret.basis_vectors(i)(i) = 1;
167  }
168  return ret;
169  /*LinearPredictor classifier;
170  classifier.weights.resize(fdim+1);
171  for (int i=0; i<fdim; ++i) classifier.weights[i] = w_vect(i);
172  classifier.weights[fdim] = -dot(w_vect,(mu1+mu2)*0.5);
173  return classifier;*/
174  }
175 #endif
176 
177  void train(int nfolds,
178  const std::vector<sample_type>& samples,
179  const std::vector<float>& labels) {
180  dlib::probabilistic_decision_function<kernel_type> pdecfun =
181  dlib::train_probabilistic_decision_function(*this, samples,
182  labels, nfolds);
183  dlib::decision_function<kernel_type>& decfun = pdecfun.decision_funct;
184  int dim = samples.back().size();
185  // see comments in linearSVM.hpp
186  m_weights.clear();
187  m_weights.resize(dim + 1, 0);
188  dlib::matrix<float> w(dim, 1);
189  w = 0;
190  for (int i = 0; i < decfun.alpha.nr(); ++i) {
191  w += decfun.alpha(i) * decfun.basis_vectors(i);
192  }
193  for (int i = 0; i < dim; ++i) m_weights[i] = w(i);
194  m_weights[dim] = -decfun.b;
195  for (int i = 0; i <= dim; ++i) m_weights[i] *= pdecfun.alpha;
196  m_weights[dim] += pdecfun.beta;
197 
198  // TODO: check if necessary here
199  for (int i = 0; i <= dim; ++i) m_weights[i] = -m_weights[i];
200  }
201 
202  double predict(const sample_type& data) const {
203  assert(!m_weights.empty());
204  double ret = m_weights.back();
205  for (size_t d = 0; d < m_weights.size() - 1; ++d)
206  ret += static_cast<double>(m_weights[d]) * data(d);
207  return ret;
208  }
209 
211  std::vector<float> m_weights;
212 };
213 
215 static void GramSchmidt(dlib::matrix<LDATrainer::sample_type, 0, 1>& basis,
216  LDATrainer::sample_type& newX) {
217  // goal: find a basis so that the given vector is the new X
218  // principle: at least one basis vector is not orthogonal with newX (except
219  // if newX is null but we suppose this is not the case)
220  // => use the max dot product vector, and replace it by newX. this forms a
221  // set of linearly independent vectors. then apply the Gram-Schmidt process
222  long dim = basis.size();
223  double maxabsdp = -1.0;
224  long selectedCoord = 0;
225  for (long i = 0; i < dim; ++i) {
226  double absdp = fabs(dot(basis(i), newX));
227  if (absdp > maxabsdp) {
228  absdp = maxabsdp;
229  selectedCoord = i;
230  }
231  }
232  // swap basis vectors to use the selected coord as the X vector, then
233  // replaced by newX
234  basis(selectedCoord) = basis(0);
235  basis(0) = newX;
236  // Gram-Schmidt process to re-orthonormalise the basis.
237  // Thanks Wikipedia for the stabilized version
238  for (long j = 0; j < dim; ++j) {
239  for (long i = 0; i < j; ++i)
240  basis(j) -= (dot(basis(j), basis(i)) / dot(basis(i), basis(i))) *
241  basis(i);
242 
243  basis(j) /= sqrt(dot(basis(j), basis(j)));
244  }
245 }
246 
249  Classifier::Point2D& refpt_neg,
250  const std::vector<float>& proj1,
251  const std::vector<float>& proj2,
252  const std::vector<float>& labels,
253  unsigned* _npos = 0,
254  unsigned* _nneg = 0) {
255  assert(proj1.size() == proj2.size() && proj1.size() == labels.size());
256 
257  refpt_neg = refpt_pos = Classifier::Point2D(0, 0);
258 
259  size_t npos = 0;
260  size_t nneg = 0;
261  for (size_t i = 0; i < labels.size(); ++i) {
262  if (labels[i] < 0) {
263  refpt_neg += Classifier::Point2D(proj1[i], proj2[i]);
264  ++nneg;
265  } else {
266  refpt_pos += Classifier::Point2D(proj1[i], proj2[i]);
267  ++npos;
268  }
269  }
270  if (npos) refpt_pos /= static_cast<float>(npos);
271  if (nneg) refpt_neg /= static_cast<float>(nneg);
272 
273  if (_npos) *_npos = npos;
274  if (_nneg) *_nneg = nneg;
275 }
276 
279 static bool DilateClassifier(
280  Classifier& classifier,
281  std::vector<float>& proj1,
282  std::vector<float>& proj2,
283  const std::vector<float>& labels,
284  const std::vector<LDATrainer::sample_type>& samples,
285  LDATrainer& trainer,
286  LDATrainer& orthoTrainer) {
287  // m_app->dispToConsole("[Cloud dilatation]");
288  Classifier::Point2D e1 = classifier.refPointPos - classifier.refPointNeg;
289  e1.normalize();
290  Classifier::Point2D e2(-e1.y, e1.x);
291  Classifier::Point2D ori =
292  (classifier.refPointPos + classifier.refPointNeg) / 2;
293 
294  float m11 = 0, m21 = 0, m12 = 0, m22 = 0; // m12, m22 null by construction
295  float v11 = 0, v12 = 0, v21 = 0, v22 = 0;
296  size_t nsamples1 = 0;
297  size_t nsamples2 = 0;
298  size_t nsamples = proj1.size();
299  assert(proj1.size() == proj2.size());
300  for (size_t i = 0; i < nsamples; ++i) {
301  Classifier::Point2D p(proj1[i], proj2[i]);
302  p -= ori;
303  float p1 = p.dot(e1);
304  float p2 = p.dot(e2);
305  if (labels[i] < 0) {
306  m11 += p1;
307  v11 += p1 * p1;
308  m12 += p2;
309  v12 += p2 * p2;
310  ++nsamples1;
311  } else {
312  m21 += p1;
313  v21 += p1 * p1;
314  m22 += p2;
315  v22 += p2 * p2;
316  ++nsamples2;
317  }
318  }
319  m11 /= nsamples1;
320  v11 = (v11 - m11 * m11 * nsamples1) / (nsamples1 - 1);
321  m21 /= nsamples2;
322  v21 = (v21 - m21 * m21 * nsamples2) / (nsamples2 - 1);
323  m12 /= nsamples1;
324  v12 = (v12 - m12 * m12 * nsamples1) / (nsamples1 - 1);
325  m22 /= nsamples2;
326  v22 = (v22 - m22 * m22 * nsamples2) / (nsamples2 - 1);
327 
328  float d1 = sqrt(v11 / v12);
329  float d2 = sqrt(v21 / v22);
330  classifier.axisScaleRatio = sqrt(d1 * d2);
331 
332  float bdValues[4] = {e1.x, e1.y, e2.x / classifier.axisScaleRatio,
333  e2.y / classifier.axisScaleRatio};
334  dlib::matrix<float, 2, 2> bd(bdValues);
335 
336  float biValues[4] = {e1.x, e2.x, e1.y, e2.y};
337  dlib::matrix<float, 2, 2> bi(biValues);
338  dlib::matrix<float, 2, 2> c = inv(trans(bd)) /* bi * bd */;
339 
340  std::vector<float>& w1 = trainer.m_weights;
341  std::vector<float>& w2 = orthoTrainer.m_weights;
342  assert(w1.size() == w2.size());
343 
344  std::vector<float> wn1, wn2;
345  try {
346  wn1.resize(w1.size());
347  wn2.resize(w2.size());
348  } catch (const std::bad_alloc&) {
349  // not enough memory
350  return false;
351  }
352 
353  // first shift so the center of the figure is at the midpoint
354  w1.back() -= ori.x;
355  w2.back() -= ori.y;
356  // now transform / scale along e2
357  {
358  for (size_t i = 0; i < w1.size(); ++i) {
359  wn1[i] = c(0, 0) * w1[i] + c(0, 1) * w2[i];
360  wn2[i] = c(1, 0) * w1[i] + c(1, 1) * w2[i];
361  }
362  }
363 
364  trainer.m_weights = wn1;
365  orthoTrainer.m_weights = wn2;
366 
367  // reset projections
368  {
369  for (size_t i = 0; i < nsamples; ++i) {
370  proj1[i] = trainer.predict(samples[i]);
371  proj2[i] = orthoTrainer.predict(samples[i]);
372  }
373  }
374 
375  classifier.weightsAxis1 = wn1;
376  classifier.weightsAxis2 = wn2;
377 
378  // update reference points
379  ComputeReferencePoints(classifier.refPointPos, classifier.refPointNeg,
380  proj1, proj2, labels);
381 
382  return true;
383 }
Classifier.
Definition: classifier.h:23
std::vector< float > weightsAxis2
Definition: classifier.h:71
float axisScaleRatio
Definition: classifier.h:73
Vector2Tpl< float > Point2D
2D point
Definition: classifier.h:26
std::vector< float > weightsAxis1
Definition: classifier.h:71
Point2D refPointPos
Definition: classifier.h:74
Point2D refPointNeg
Definition: classifier.h:74
double predict(const sample_type &data) const
Definition: trainer.h:202
std::vector< float > m_weights
Classifier weights.
Definition: trainer.h:211
trained_function_type train(const std::vector< sample_type > &samplesvec, const std::vector< float > &labels) const
Definition: trainer.h:24
dlib::decision_function< kernel_type > trained_function_type
Definition: trainer.h:21
dlib::linear_kernel< sample_type > kernel_type
Definition: trainer.h:20
void train(int nfolds, const std::vector< sample_type > &samples, const std::vector< float > &labels)
Definition: trainer.h:177
dlib::matrix< float, 0, 1 > sample_type
Definition: trainer.h:19
Type dot(const Vector2Tpl &v) const
Dot product.
Definition: CVGeom.h:71
Type x
Definition: CVGeom.h:36
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:65
Type y
Definition: CVGeom.h:36
__host__ __device__ float dot(float2 a, float2 b)
Definition: cutil_math.h:1119
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
static void GramSchmidt(dlib::matrix< LDATrainer::sample_type, 0, 1 > &basis, LDATrainer::sample_type &newX)
Gram-Schmidt process to re-orthonormalise the basis.
Definition: trainer.h:215
static bool DilateClassifier(Classifier &classifier, std::vector< float > &proj1, std::vector< float > &proj2, const std::vector< float > &labels, const std::vector< LDATrainer::sample_type > &samples, LDATrainer &trainer, LDATrainer &orthoTrainer)
Definition: trainer.h:279
static void ComputeReferencePoints(Classifier::Point2D &refpt_pos, Classifier::Point2D &refpt_neg, const std::vector< float > &proj1, const std::vector< float > &proj2, const std::vector< float > &labels, unsigned *_npos=0, unsigned *_nneg=0)
Compute pos. and neg. reference points.
Definition: trainer.h:248