DMP_BBO library
FunctionApproximatorGMR.cpp
Go to the documentation of this file.
1 
24 #include <boost/serialization/export.hpp>
25 #include <boost/archive/text_iarchive.hpp>
26 #include <boost/archive/text_oarchive.hpp>
27 #include <boost/archive/xml_iarchive.hpp>
28 #include <boost/archive/xml_oarchive.hpp>
30 
33 
34 #include <iostream>
35 #include <eigen3/Eigen/LU>
36 #include <eigen3/Eigen/Cholesky>
37 #include <ctime>
38 #include <cstdlib>
39 
40 
44 
45 
46 using namespace std;
47 using namespace Eigen;
48 
49 namespace DmpBbo {
50 
51 FunctionApproximatorGMR::FunctionApproximatorGMR(const MetaParametersGMR *const meta_parameters, const ModelParametersGMR *const model_parameters)
52 :
53  FunctionApproximator(meta_parameters,model_parameters)
54 {
55  // TODO : find a more appropriate place for rand initialization
56  //srand(unsigned(time(0)));
57  if (model_parameters!=NULL)
58  preallocateMatrices(
59  model_parameters->getNumberOfGaussians(),
60  model_parameters->getExpectedInputDim(),
61  model_parameters->getExpectedOutputDim()
62  );
63 }
64 
65 FunctionApproximatorGMR::FunctionApproximatorGMR(const ModelParametersGMR *const model_parameters)
66 :
67  FunctionApproximator(model_parameters)
68 {
69  preallocateMatrices(
70  model_parameters->getNumberOfGaussians(),
71  model_parameters->getExpectedInputDim(),
72  model_parameters->getExpectedOutputDim()
73  );
74 }
75 
76 void FunctionApproximatorGMR::preallocateMatrices(int n_gaussians, int n_input_dims, int n_output_dims)
77 {
78  probabilities_prealloc_ = VectorXd::Zero(n_gaussians);
79  probabilities_dot_prealloc_ = VectorXd::Zero(n_gaussians);
80  diff_prealloc_ = VectorXd::Zero(n_input_dims);
81  covar_times_diff_prealloc_ = VectorXd::Zero(n_input_dims);
82  mean_output_prealloc_ = VectorXd::Zero(n_output_dims);
83 
84  covar_input_times_output_ = MatrixXd::Zero(n_input_dims,n_output_dims);
85  covar_output_times_input_ = MatrixXd::Zero(n_output_dims,n_input_dims);
86  covar_output_prealloc_ = MatrixXd::Zero(n_output_dims,n_output_dims);
87 
88  empty_prealloc_ = MatrixXd::Zero(0,0);
89 
90 }
91 
92 
94  // All error checking and cloning is left to the FunctionApproximator constructor.
95  return new FunctionApproximatorGMR(
96  dynamic_cast<const MetaParametersGMR*>(getMetaParameters()),
97  dynamic_cast<const ModelParametersGMR*>(getModelParameters())
98  );
99 };
100 
101 
102 void FunctionApproximatorGMR::train(const Eigen::Ref<const Eigen::MatrixXd>& inputs, const Eigen::Ref<const Eigen::MatrixXd>& targets)
103 {
104  if (isTrained())
105  {
106  cerr << "WARNING: You may not call FunctionApproximatorGMR::train more than once. Doing nothing." << endl;
107  cerr << " (if you really want to retrain, call reTrain function instead)" << endl;
108  return;
109  }
110 
111  assert(inputs.rows() == targets.rows()); // Must have same number of examples
112  assert(inputs.cols() == getExpectedInputDim());
113 
114  const MetaParametersGMR* meta_parameters_GMR =
115  static_cast<const MetaParametersGMR*>(getMetaParameters());
116 
117  const ModelParametersGMR* model_parameters_GMR =
118  static_cast<const ModelParametersGMR*>(getModelParameters());
119 
120  int n_gaussians;
121  if(meta_parameters_GMR!=NULL)
122  n_gaussians = meta_parameters_GMR->number_of_gaussians_;
123  else if(model_parameters_GMR!=NULL)
124  n_gaussians = model_parameters_GMR->priors_.size();
125  else
126  cerr << "FunctionApproximatorGMR::train Something wrong happened, both ModelParameters and MetaParameters are not initialized." << endl;
127 
128  int n_dims_in = inputs.cols();
129  int n_dims_out = targets.cols();
130  int n_dims_gmm = n_dims_in + n_dims_out;
131 
132  // Initialize the means, priors and covars
133  std::vector<VectorXd> means(n_gaussians);
134  std::vector<MatrixXd> covars(n_gaussians);
135  std::vector<double> priors(n_gaussians);
136  int n_observations = 0;
137 
138  for (int i = 0; i < n_gaussians; i++)
139  {
140  means[i] = VectorXd(n_dims_gmm);
141  priors[i] = 0.0;
142  covars[i] = MatrixXd(n_dims_gmm, n_dims_gmm);
143  }
144 
145  // Put the input/output data in one big matrix
146  MatrixXd data = MatrixXd(inputs.rows(), n_dims_gmm);
147  data << inputs, targets;
148  n_observations = data.rows();
149 
150  // Initialization
151  if (inputs.cols() == 1)
152  firstDimSlicingInit(data, means, priors, covars);
153  else
154  kMeansInit(data, means, priors, covars);
155 
156  // Expectation-Maximization
157  expectationMaximization(data, means, priors, covars);
158 
159  // Extract the different input/output components from the means/covars which contain both
160  std::vector<Eigen::VectorXd> means_x(n_gaussians);
161  std::vector<Eigen::VectorXd> means_y(n_gaussians);
162  std::vector<Eigen::MatrixXd> covars_x(n_gaussians);
163  std::vector<Eigen::MatrixXd> covars_y(n_gaussians);
164  std::vector<Eigen::MatrixXd> covars_y_x(n_gaussians);
165  for (int i_gau = 0; i_gau < n_gaussians; i_gau++)
166  {
167  means_x[i_gau] = means[i_gau].segment(0, n_dims_in);
168  means_y[i_gau] = means[i_gau].segment(n_dims_in, n_dims_out);
169 
170  covars_x[i_gau] = covars[i_gau].block(0, 0, n_dims_in, n_dims_in);
171  covars_y[i_gau] = covars[i_gau].block(n_dims_in, n_dims_in, n_dims_out, n_dims_out);
172  covars_y_x[i_gau] = covars[i_gau].block(n_dims_in, 0, n_dims_out, n_dims_in);
173  }
174 
175  setModelParameters(new ModelParametersGMR(n_observations, priors, means_x, means_y, covars_x, covars_y, covars_y_x));
176 
177  // After training, we know the sizes of the matrices that should be cached
178  preallocateMatrices(n_gaussians,n_dims_in,n_dims_out);
179 
180  // std::vector<VectorXd> centers;
181  // std::vector<MatrixXd> slopes;
182  // std::vector<VectorXd> biases;
183  // std::vector<MatrixXd> inverseCovarsL;
184 
185  // // int n_dims_in = inputs.cols();
186  // // int n_dims_out = targets.cols();
187 
188  // for (int i_gau = 0; i_gau < n_gaussians; i_gau++)
189  // {
190  // centers.push_back(VectorXd(means[i_gau].segment(0, n_dims_in)));
191 
192  // slopes.push_back(MatrixXd(covars[i_gau].block(n_dims_in, 0, n_dims_out, n_dims_in) * covars[i_gau].block(0, 0, n_dims_in, n_dims_in).inverse()));
193 
194  // biases.push_back(VectorXd(means[i_gau].segment(n_dims_in, n_dims_out) -
195  // slopes[i_gau]*means[i_gau].segment(0, n_dims_in)));
196 
197  // MatrixXd L = covars[i_gau].block(0, 0, n_dims_in, n_dims_in).inverse().llt().matrixL();
198  // inverseCovarsL.push_back(MatrixXd(L));
199  // }
200 
201  // setModelParameters(new ModelParametersGMR(centers, priors, slopes, biases, inverseCovarsL));
202 
203  //for (size_t i = 0; i < means.size(); i++)
204  // delete means[i];
205  //for (size_t i = 0; i < covars.size(); i++)
206  //delete covars[i];
207 }
208 
211 void FunctionApproximatorGMR::trainIncremental(const Eigen::Ref<const Eigen::MatrixXd>& inputs, const Eigen::Ref<const Eigen::MatrixXd>& targets)
212 {
213  if (!isTrained())
214  {
215  //cout << " Training for the first time... " << endl;
216  train(inputs,targets);
217  return;
218  }
219 
220  const ModelParametersGMR* model_parameters_GMR = static_cast<const ModelParametersGMR*>(getModelParameters());
221 
222 
223  int n_gaussians = model_parameters_GMR->priors_.size();
224  int n_dims_in = inputs.cols();
225  int n_dims_out = targets.cols();
226  int n_dims_gmm = n_dims_in + n_dims_out;
227 
228  // Initialize the means, priors and covars
229  std::vector<VectorXd> means(n_gaussians);
230  std::vector<MatrixXd> covars(n_gaussians);
231  std::vector<double> priors(n_gaussians);
232  int n_observations = 0;
233  for (int i = 0; i < n_gaussians; i++)
234  {
235  means[i] = VectorXd(n_dims_gmm);
236  priors[i] = 0.0;
237  covars[i] = MatrixXd(n_dims_gmm, n_dims_gmm);
238  }
239 
240  // Extract the model parameters
241  for (int i = 0; i < n_gaussians; i++)
242  {
243  means[i].segment(0, n_dims_in) = model_parameters_GMR->means_x_[i];
244  means[i].segment(n_dims_in, n_dims_out) = model_parameters_GMR->means_y_[i];
245 
246  covars[i].block(0, 0, n_dims_in, n_dims_in) = model_parameters_GMR->covars_x_[i];
247  covars[i].block(n_dims_in, n_dims_in, n_dims_out, n_dims_out) = model_parameters_GMR->covars_y_[i];
248  covars[i].block(n_dims_in, 0, n_dims_out, n_dims_in) = model_parameters_GMR->covars_y_x_[i];
249 
250  priors[i] = model_parameters_GMR->priors_[i];
251  }
252  n_observations = model_parameters_GMR->n_observations_;
253 
254  // Put the input/output data in one big matrix
255  MatrixXd data = MatrixXd(inputs.rows(), n_dims_gmm);
256  data << inputs, targets;
257 
258  // Expectation-Maximization Incremental
259  expectationMaximizationIncremental(data, means, priors, covars, n_observations);
260 
261  // Extract the different input/output components from the means/covars which contain both
262  std::vector<Eigen::VectorXd> means_x(n_gaussians);
263  std::vector<Eigen::VectorXd> means_y(n_gaussians);
264  std::vector<Eigen::MatrixXd> covars_x(n_gaussians);
265  std::vector<Eigen::MatrixXd> covars_y(n_gaussians);
266  std::vector<Eigen::MatrixXd> covars_y_x(n_gaussians);
267  for (int i_gau = 0; i_gau < n_gaussians; i_gau++)
268  {
269  means_x[i_gau] = means[i_gau].segment(0, n_dims_in);
270  means_y[i_gau] = means[i_gau].segment(n_dims_in, n_dims_out);
271 
272  covars_x[i_gau] = covars[i_gau].block(0, 0, n_dims_in, n_dims_in);
273  covars_y[i_gau] = covars[i_gau].block(n_dims_in, n_dims_in, n_dims_out, n_dims_out);
274  covars_y_x[i_gau] = covars[i_gau].block(n_dims_in, 0, n_dims_out, n_dims_in);
275  }
276 
277  setModelParameters(new ModelParametersGMR(n_observations, priors, means_x, means_y, covars_x, covars_y, covars_y_x));
278 
279  // After training, we know the sizes of the matrices that should be cached
280  preallocateMatrices(n_gaussians,n_dims_in,n_dims_out);
281 }
282 
283 double FunctionApproximatorGMR::normalPDF(const VectorXd& mu, const MatrixXd& covar, const VectorXd& input)
284 {
285  MatrixXd covar_inverse = covar.inverse();
286  double output = exp(-0.5*(input-mu).transpose()*covar_inverse*(input-mu));
287  // For invertible matrices (which covar apparently was), det(A^-1) = 1/det(A)
288  // Hence the 1.0/covar_inverse.determinant() below
289  // ( (2*pi)^N*|\Sigma| )^(-1/2)
290  output *= pow(pow(2*M_PI,mu.size())/covar_inverse.determinant(),-0.5);
291  return output;
292 }
293 
294 
297 double FunctionApproximatorGMR::normalPDFDamped(const VectorXd& mu, const MatrixXd& covar, const VectorXd& input)
298 {
299  if(covar.determinant() > 0) // It is invertible
300  {
301  MatrixXd covar_inverse = covar.inverse();
302 
303  double output = exp(-0.5*(input-mu).transpose()*covar_inverse*(input-mu));
304 
305  // Check that:
306  // if output == 0.0
307  // return 0.0;
308 
309  // For invertible matrices (which covar apparently was), det(A^-1) = 1/det(A)
310  // Hence the 1.0/covar_inverse.determinant() below
311  // ( (2\pi)^N*|\Sigma| )^(-1/2)
312  output *= pow(pow(2*M_PI,mu.size())/(covar_inverse.determinant()),-0.5);
313  return output;
314  }
315  else
316  {
317  //cerr << "WARNING: FunctionApproximatorGMR::normalPDFDamped output close to singularity..." << endl;
318  return std::numeric_limits<double>::min();
319  }
320 }
321 
322 
323 void FunctionApproximatorGMR::predict(const Eigen::Ref<const Eigen::MatrixXd>& inputs, Eigen::MatrixXd& outputs)
324 {
325  ENTERING_REAL_TIME_CRITICAL_CODE
326  outputs.resize(inputs.rows(),getExpectedOutputDim());
327  predict(inputs,outputs,empty_prealloc_);
328  EXITING_REAL_TIME_CRITICAL_CODE
329 }
330 
331 void FunctionApproximatorGMR::predictVariance(const Eigen::Ref<const Eigen::MatrixXd>& inputs, Eigen::MatrixXd& variances)
332 {
333  ENTERING_REAL_TIME_CRITICAL_CODE
334  variances.resize(inputs.rows(),getExpectedOutputDim());
335  predict(inputs,empty_prealloc_,variances);
336  EXITING_REAL_TIME_CRITICAL_CODE
337 }
338 
339 void FunctionApproximatorGMR::predict(const Eigen::Ref<const Eigen::MatrixXd>& inputs, Eigen::MatrixXd& outputs, Eigen::MatrixXd& variances)
340 {
341  ENTERING_REAL_TIME_CRITICAL_CODE
342 
343  // The reason this function is not pretty and so long (which I usually try to avoid) is to
344  // avoid Eigen making dynamic memory allocations. This would cause trouble in real-time critical
345  // code. Therefore, I
346  // * use preallocated matrices (member variables) for intermediate results
347  // * use noalias() whenever needed (http://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html)
348  // * try to avoid calling other functions (a bit tricky when using Eigen matrices as parameters)
349  // I hope the documentation makes up for the ugliness...
350 
351  if (!isTrained())
352  {
353  cerr << "WARNING: You may not call FunctionApproximatorGMR::predict if you have not trained yet. Doing nothing." << endl;
354  return;
355  }
356 
357  const ModelParametersGMR* gmm = static_cast<const ModelParametersGMR*>(getModelParameters());
358 
359  // Number of Gaussians must be at least one
360  assert(gmm->getNumberOfGaussians()>0);
361  // Dimensionality of input must be same as of the gmm inputs
362  assert(gmm->getExpectedInputDim()==inputs.cols());
363 
364  // Only compute the means if the outputs matrix is not empty
365  bool compute_means = false;
366  if (outputs.rows()>0)
367  {
368  outputs.resize(inputs.rows(),gmm->getExpectedOutputDim());
369  outputs.fill(0);
370  compute_means = true;
371  }
372 
373  // Only compute the variances if the variances matrix is not empty
374  bool compute_variance = false;
375  if (variances.rows()>0)
376  {
377  compute_variance = true;
378  variances.resize(inputs.rows(),gmm->getExpectedOutputDim());
379  variances.fill(0);
380  }
381 
382  // For each input, compute the output
383  for (int i_input=0; i_input<inputs.rows(); i_input++)
384  {
385 
386  // Three main steps
387  // A: compute probability: prior * pdf of the multivariate Gaussian
388  // B: compute estimated mean of y: (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
389  // C: weight the estimated mean with the probability
390 
391 
392  // A: compute probability: prior * pdf of the multivariate Gaussian
393  // Compute probalities that each Gaussian would generate this input in 4 steps
394  // A1. Compute the unnormalized pdf of the multi-variate Gaussian distribution
395  // A2. Normalize the unnormalized pdf (scale factor has been precomputed in the GMM)
396  // A3. Multiply the normalized pdf with the priors
397  // A4. Normalize the probabilities by dividing by their sum
398 
399  for (unsigned int i_gau=0; i_gau<gmm->getNumberOfGaussians(); i_gau++)
400  {
401  // A1. Compute the unnormalized pdf of the multi-variate Gaussian distribution
402  // formula: exp( -2 * (x-mu)^T * Sigma^-1 * (x-mu) )
403  // (we use cached variables and noalias to avoid dynamic allocation)
404  // (x-mu)
405  diff_prealloc_ = inputs.row(i_input).transpose() - gmm->means_x_[i_gau];
406  // Sigma^-1 * (x-mu)
407  covar_times_diff_prealloc_.noalias() = gmm->covars_x_inv_[i_gau]*diff_prealloc_;
408  // exp( -2 * (x-mu)^T * Sigma^-1 * (x-mu) )
409  probabilities_prealloc_[i_gau] = exp(-0.5*diff_prealloc_.dot(covar_times_diff_prealloc_));
410 
411  // A2. Normalize the unnormalized pdf (scale factor has been precomputed in the GMM)
412  // formula for scale factor: 1/sqrt( (2\pi)^N*|\Sigma| )
413  probabilities_prealloc_[i_gau] *= gmm->mvgd_scale_[i_gau];
414 
415  // A3. Multiply the normalized pdf with the priors
416  probabilities_prealloc_[i_gau] *= gmm->priors_[i_gau];
417 
418  }
419 
420  // A4. Normalize the probabilities by dividing by their sum
421  probabilities_prealloc_ /= probabilities_prealloc_.sum();
422 
423 
424  if (compute_means)
425  {
426  for (unsigned int i_gau=0; i_gau<gmm->getNumberOfGaussians(); i_gau++)
427  {
428 
429  // B: compute estimated mean of y: (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
430  // We will compute it bit by bit (with preallocated matrices) to avoid dynamic allocations.
431 
432  // (input-mu_x)
433  diff_prealloc_ = inputs.row(i_input).transpose() - gmm->means_x_[i_gau];
434  // inv(C_x) * (input-mu_x)
435  covar_times_diff_prealloc_.noalias() = gmm->covars_x_inv_[i_gau]*diff_prealloc_;
436  // ( C_y_x * inv(C_x) * (input-mu_x) )
437  mean_output_prealloc_.noalias() = gmm->covars_y_x_[i_gau]*covar_times_diff_prealloc_;
438  // (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
439  mean_output_prealloc_ += gmm->means_y_[i_gau];
440 
441  // C: weight the estimated mean with the probability
442  // probability * (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
443  outputs.row(i_input) += probabilities_prealloc_[i_gau] * mean_output_prealloc_;
444  }
445  }
446 
447  if (compute_variance)
448  {
449  for (unsigned int i_gau=0; i_gau<gmm->getNumberOfGaussians(); i_gau++)
450  {
451  // Here comes the formula: h^2 * (C_y - C_y_x * inv(C_x) * C_y_x^T)
452 
453  // inv(C_x) * C_y_x^T
454  covar_input_times_output_.noalias() = gmm->covars_x_inv_[i_gau]*gmm->covars_y_x_[i_gau].transpose();
455  // - C_y_x * inv(C_x) * C_y_x^T
456  covar_output_prealloc_.noalias() = gmm->covars_y_x_[i_gau] * covar_input_times_output_;
457  // NOTE: covar_output_prealloc_.noalias() = - gmm->covars_y_x_[i_gau] * covar_input_times_output_; causes memory allocation
458  // so we split it in two operations
459  covar_output_prealloc_.noalias() = - covar_output_prealloc_;
460  // (C_y - C_y_x * inv(C_x) * C_y_x^T)
461  covar_output_prealloc_ += gmm->covars_y_[i_gau];
462  // h^2 * (C_y - C_y_x * inv(C_x) * C_y_x^T)
463  variances.row(i_input) += probabilities_prealloc_[i_gau]*probabilities_prealloc_[i_gau] * ( covar_output_prealloc_ ).diagonal();
464 
465  // There are cases where we may get slightly negative variances due to numerical issues
466  // Avoid them here by setting negative variances to 0.
467  for (int i_output_dim=0; i_output_dim<gmm->getExpectedOutputDim(); i_output_dim++)
468  if (variances(i_input,i_output_dim)<0.0)
469  variances(i_input,i_output_dim) = 0.0;
470 
471 
472  }
473  }
474  }
475 
476  EXITING_REAL_TIME_CRITICAL_CODE
477 }
478 
479 void FunctionApproximatorGMR::predictDot(const Eigen::Ref<const Eigen::MatrixXd>& inputs, Eigen::MatrixXd& outputs, Eigen::MatrixXd& outputs_dot)
480 {
481  ENTERING_REAL_TIME_CRITICAL_CODE
482  outputs.resize(inputs.rows(),getExpectedOutputDim());
483  predictDot(inputs,outputs,outputs_dot,empty_prealloc_);
484  EXITING_REAL_TIME_CRITICAL_CODE
485 }
486 
487 void FunctionApproximatorGMR::predictDot(const Eigen::Ref<const Eigen::MatrixXd>& inputs, Eigen::MatrixXd& outputs, Eigen::MatrixXd& outputs_dot, Eigen::MatrixXd& variances)
488 {
489  ENTERING_REAL_TIME_CRITICAL_CODE
490 
491  // The reason this function is not pretty and so long (which I usually try to avoid) is to
492  // avoid Eigen making dynamic memory allocations. This would cause trouble in real-time critical
493  // code. Therefore, I
494  // * use preallocated matrices (member variables) for intermediate results
495  // * use noalias() whenever needed (http://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html)
496  // * try to avoid calling other functions (a bit tricky when using Eigen matrices as parameters)
497  // I hope the documentation makes up for the ugliness...
498 
499  if (!isTrained())
500  {
501  cerr << "WARNING: You may not call FunctionApproximatorGMR::predict if you have not trained yet. Doing nothing." << endl;
502  return;
503  }
504 
505  const ModelParametersGMR* gmm = static_cast<const ModelParametersGMR*>(getModelParameters());
506 
507  // Dimensionality of input must be 1 in order to compute the derivative
508  assert(inputs.cols() == 1);
509  // Number of Gaussians must be at least one
510  assert(gmm->getNumberOfGaussians()>0);
511  // Dimensionality of input must be same as of the gmm inputs
512  assert(gmm->getExpectedInputDim()==inputs.cols());
513 
514  // Only compute the means if the outputs matrix is not empty
515  bool compute_means = false;
516  if (outputs.rows()>0)
517  {
518  outputs.resize(inputs.rows(),gmm->getExpectedOutputDim());
519  outputs_dot.resize(inputs.rows(),gmm->getExpectedOutputDim());
520  outputs.fill(0);
521  outputs_dot.fill(0);
522  compute_means = true;
523  }
524 
525  // Only compute the variances if the variances matrix is not empty
526  bool compute_variance = false;
527  if (variances.rows()>0)
528  {
529  compute_variance = true;
530  variances.resize(inputs.rows(),gmm->getExpectedOutputDim());
531  variances.fill(0);
532  }
533 
534  // For each input, compute the output
535  for (int i_input=0; i_input<inputs.rows(); i_input++)
536  {
537 
538  // Three main steps
539  // A: compute probability: prior * pdf of the multivariate Gaussian
540  // B: compute estimated mean of y: (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
541  // C: weight the estimated mean with the probability
542 
543 
544  // A: compute probability: prior * pdf of the multivariate Gaussian
545  // Compute probalities that each Gaussian would generate this input in 4 steps
546  // A1. Compute the unnormalized pdf of the multi-variate Gaussian distribution
547  // A2. Normalize the unnormalized pdf (scale factor has been precomputed in the GMM)
548  // A3. Multiply the normalized pdf with the priors
549  // A4. Normalize the probabilities by dividing by their sum
550 
551  for (unsigned int i_gau=0; i_gau<gmm->getNumberOfGaussians(); i_gau++)
552  {
553  // A1. Compute the unnormalized pdf of the multi-variate Gaussian distribution
554  // formula: exp( -2 * (x-mu)^T * Sigma^-1 * (x-mu) )
555  // (we use cached variables and noalias to avoid dynamic allocation)
556  // (x-mu)
557  diff_prealloc_ = inputs.row(i_input).transpose() - gmm->means_x_[i_gau];
558  // Sigma^-1 * (x-mu)
559  covar_times_diff_prealloc_.noalias() = gmm->covars_x_inv_[i_gau]*diff_prealloc_;
560  // exp( -2 * (x-mu)^T * Sigma^-1 * (x-mu) )
561  probabilities_prealloc_[i_gau] = exp(-0.5*diff_prealloc_.dot(covar_times_diff_prealloc_));
562 
563  // A2. Normalize the unnormalized pdf (scale factor has been precomputed in the GMM)
564  // formula for scale factor: 1/sqrt( (2\pi)^N*|\Sigma| )
565  probabilities_prealloc_[i_gau] *= gmm->mvgd_scale_[i_gau];
566 
567  // A3. This is the derivate of an exponential function, NOTE that we are assuming input dimension equal to one!
568  probabilities_dot_prealloc_[i_gau] *= - probabilities_prealloc_[i_gau] * covar_times_diff_prealloc_(0,0);
569 
570  // A4. Multiply the normalized pdf with the priors
571  probabilities_prealloc_[i_gau] *= gmm->priors_[i_gau];
572 
573  // A5. Repeat the same with the prob. dot
574  probabilities_dot_prealloc_[i_gau] *= gmm->priors_[i_gau];
575 
576  }
577 
578  // A5. Normalize the probabilities by dividing by their sum
579  probabilities_prealloc_sum_ = probabilities_prealloc_.sum();
580  probabilities_prealloc_ /= probabilities_prealloc_sum_;
581 
582  // A6. Compute the derivative of the probability
583  probabilities_dot_prealloc_sum_ = probabilities_dot_prealloc_.sum();
584  probabilities_dot_prealloc_ = (probabilities_dot_prealloc_ * probabilities_prealloc_sum_ - probabilities_prealloc_ * probabilities_dot_prealloc_sum_)/pow(probabilities_prealloc_sum_,2);
585 
586  if (compute_means)
587  {
588  for (unsigned int i_gau=0; i_gau<gmm->getNumberOfGaussians(); i_gau++)
589  {
590 
591  // B: compute estimated mean of y: (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
592  // We will compute it bit by bit (with preallocated matrices) to avoid dynamic allocations.
593  // (input-mu_x)
594  diff_prealloc_ = inputs.row(i_input).transpose() - gmm->means_x_[i_gau];
595  // inv(C_x) * (input-mu_x)
596  covar_times_diff_prealloc_.noalias() = gmm->covars_x_inv_[i_gau]*diff_prealloc_;
597  // ( C_y_x * inv(C_x) * (input-mu_x) )
598  mean_output_prealloc_.noalias() = gmm->covars_y_x_[i_gau]*covar_times_diff_prealloc_;
599  // (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
600  mean_output_prealloc_ += gmm->means_y_[i_gau];
601 
602  // C: weight the estimated mean with the probability
603  // probability * (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) )
604  outputs.row(i_input) += probabilities_prealloc_[i_gau] * mean_output_prealloc_;
605 
606  // D: compute the derivate of the output
607  // (C_y_x * inv(C_x))
608  covar_output_times_input_.noalias() = gmm->covars_y_x_[i_gau] * gmm->covars_x_inv_[i_gau];
609  // probability_dot * (mu_y + ( C_y_x * inv(C_x) * (input-mu_x) ) ) + probability * (C_y_x * inv(C_x))
610  outputs_dot.row(i_input) += probabilities_dot_prealloc_[i_gau] * mean_output_prealloc_ + probabilities_prealloc_[i_gau] * covar_output_times_input_;
611 
612  }
613  }
614 
615  if (compute_variance)
616  {
617  for (unsigned int i_gau=0; i_gau<gmm->getNumberOfGaussians(); i_gau++)
618  {
619  // Here comes the formula: h^2 * (C_y - C_y_x * inv(C_x) * C_y_x^T)
620 
621  // inv(C_x) * C_y_x^T
622  covar_input_times_output_.noalias() = gmm->covars_x_inv_[i_gau]*gmm->covars_y_x_[i_gau].transpose();
623  // - C_y_x * inv(C_x) * C_y_x^T
624  covar_output_prealloc_.noalias() = gmm->covars_y_x_[i_gau] * covar_input_times_output_;
625  // NOTE: covar_output_prealloc_.noalias() = - gmm->covars_y_x_[i_gau] * covar_input_times_output_; causes memory allocation
626  // so we split it in two operations
627  covar_output_prealloc_.noalias() = - covar_output_prealloc_;
628  // (C_y - C_y_x * inv(C_x) * C_y_x^T)
629  covar_output_prealloc_ += gmm->covars_y_[i_gau];
630  // h^2 * (C_y - C_y_x * inv(C_x) * C_y_x^T)
631  variances.row(i_input) += probabilities_prealloc_[i_gau]*probabilities_prealloc_[i_gau] * ( covar_output_prealloc_ ).diagonal();
632 
633  // There are cases where we may get slightly negative variances due to numerical issues
634  // Avoid them here by setting negative variances to 0.
635  for (int i_output_dim=0; i_output_dim<gmm->getExpectedOutputDim(); i_output_dim++)
636  if (variances(i_input,i_output_dim)<0.0)
637  variances(i_input,i_output_dim) = 0.0;
638 
639 
640  }
641  }
642  }
643 
644  EXITING_REAL_TIME_CRITICAL_CODE
645 }
646 
647 void FunctionApproximatorGMR::firstDimSlicingInit(const MatrixXd& data, std::vector<VectorXd>& centers, std::vector<double>& priors,
648  std::vector<MatrixXd>& covars)
649 {
650 
651  VectorXd first_dim = data.col(0);
652 
653  VectorXi assign(data.rows());
654  assign.setZero();
655 
656  double min_val = first_dim.minCoeff();
657  double max_val = first_dim.maxCoeff();
658 
659  for (int i_first_dim = 0; i_first_dim < first_dim.size(); i_first_dim++)
660  {
661  unsigned int center = int((first_dim[i_first_dim]-min_val)/(max_val-min_val)*centers.size());
662  if (center==centers.size())
663  center--;
664  assign[i_first_dim] = center;
665  }
666 
667  // Init means
668  VectorXi nbPoints = VectorXi::Zero(centers.size());
669  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
670  centers[i_gau].setZero();
671  for (int iData = 0; iData < data.rows(); iData++)
672  {
673  centers[assign[iData]] += data.row(iData).transpose();
674  nbPoints[assign[iData]]++;
675  }
676  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
677  centers[i_gau] /= nbPoints[i_gau];
678 
679  // Init covars
680  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
681  covars[i_gau].setZero();
682  for (int iData = 0; iData < data.rows(); iData++)
683  covars[assign[iData]] += (data.row(iData).transpose() - centers[assign[iData]]) * (data.row(iData).transpose() - centers[assign[iData]]).transpose();
684  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
685  covars[i_gau] /= nbPoints[i_gau];
686 
687  // Be sure that covar is invertible
688  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
689  covars[i_gau] += MatrixXd::Identity(covars[i_gau].rows(), covars[i_gau].cols()) * 1e-5;
690 
691  // Init priors
692  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
693  priors[i_gau] = 1. / centers.size();
694 }
695 
696 void FunctionApproximatorGMR::kMeansInit(const MatrixXd& data, std::vector<VectorXd>& centers, std::vector<double>& priors,
697  std::vector<MatrixXd>& covars, int n_max_iter)
698 {
699 
700  MatrixXd dataCentered = data.rowwise() - data.colwise().mean();
701  MatrixXd dataCov = dataCentered.transpose() * dataCentered / data.rows();
702  MatrixXd dataCovInverse = dataCov.inverse();
703 
704  std::vector<int> dataIndex;
705  for (int i = 0; i < data.rows(); i++)
706  dataIndex.push_back(i);
707  std::random_shuffle (dataIndex.begin(), dataIndex.end());
708 
709  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
710  centers[i_gau] = data.row(dataIndex[i_gau]);
711 
712  VectorXi assign(data.rows());
713  assign.setZero();
714 
715  bool converged = false;
716  for (int iIter = 0; iIter < n_max_iter && !converged; iIter++)
717  {
718  //cout << " iIter=" << iIter << endl;
719 
720  // E step
721  converged = true;
722  for (int iData = 0; iData < data.rows(); iData++)
723  {
724  VectorXd v = (centers[assign[iData]] - data.row(iData).transpose());
725 
726  double minDist = v.transpose() * dataCovInverse * v;
727 
728  for (int i_gau = 0; i_gau < (int)centers.size(); i_gau++)
729  {
730  if (i_gau == assign[iData])
731  continue;
732 
733  v = (centers[i_gau] - data.row(iData).transpose());
734  double dist = v.transpose() * dataCovInverse * v;
735  if (dist < minDist)
736  {
737  converged = false;
738  minDist = dist;
739  assign[iData] = i_gau;
740  }
741  }
742  }
743 
744  // M step
745  VectorXi nbPoints = VectorXi::Zero(centers.size());
746  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
747  centers[i_gau].setZero();
748  for (int iData = 0; iData < data.rows(); iData++)
749  {
750  centers[assign[iData]] += data.row(iData).transpose();
751  nbPoints[assign[iData]]++;
752  }
753  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
754  centers[i_gau] /= nbPoints[i_gau];
755  }
756 
757  // Init covars
758  VectorXi nbPoints = VectorXi::Zero(centers.size());
759  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
760  covars[i_gau].setZero();
761  for (int iData = 0; iData < data.rows(); iData++)
762  {
763  covars[assign[iData]] += (data.row(iData).transpose() - centers[assign[iData]]) * (data.row(iData).transpose() - centers[assign[iData]]).transpose();
764  nbPoints[assign[iData]]++;
765  }
766  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
767  covars[i_gau] /= nbPoints[i_gau];
768 
769  // Be sure that covar is invertible
770  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
771  covars[i_gau] += MatrixXd::Identity(covars[i_gau].rows(), covars[i_gau].cols()) * 1e-5f;
772 
773  // Init priors
774  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
775  priors[i_gau] = 1. / centers.size();
776 }
777 
778 void FunctionApproximatorGMR::expectationMaximization(const MatrixXd& data, std::vector<VectorXd>& centers, std::vector<double>& priors,
779  std::vector<MatrixXd>& covars, int n_max_iter)
780 {
781  MatrixXd assign(centers.size(), data.rows());
782  assign.setZero();
783 
784  std::vector<double> E(centers.size());
785 
786  double oldLoglik = -1e10f;
787  double loglik = 0;
788 
789  for (int iIter = 0; iIter < n_max_iter; iIter++)
790  {
791  //cout << " iIter=" << iIter << endl;
792  // For debugging only
793  //ModelParametersGMR::saveGMM("/tmp/demoTrainFunctionApproximators/GMR",centers,covars,iIter);
794 
795  // E step
796  for (int iData = 0; iData < data.rows(); iData++)
797  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
798  assign(i_gau, iData) = priors[i_gau] * FunctionApproximatorGMR::normalPDF(centers[i_gau], covars[i_gau],data.row(iData).transpose());
799 
800  oldLoglik = loglik;
801  loglik = 0;
802  double sum_tmp = 0.0;
803  for (int iData = 0; iData < data.rows(); iData++)
804  {
805  sum_tmp = assign.col(iData).sum();
806  loglik += log(sum_tmp);
807  }
808  loglik /= data.rows();
809 
810  if (fabs(loglik / oldLoglik - 1) < 1e-8f)
811  break;
812 
813  for (int iData = 0; iData < data.rows(); iData++)
814  assign.col(iData) /= assign.col(iData).sum();
815 
816  if (fabs(loglik / oldLoglik - 1) < 1e-8f)
817  break;
818 
819  // M step
820  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
821  {
822  centers[i_gau].setZero();
823  covars[i_gau].setZero();
824  priors[i_gau] = 0;
825  E[i_gau] = assign.row(i_gau).sum();
826  }
827 
828  for (int iData = 0; iData < data.rows(); iData++)
829  {
830  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
831  {
832  centers[i_gau] += assign(i_gau, iData) * data.row(iData).transpose();
833  priors[i_gau] += assign(i_gau, iData);
834  }
835  }
836 
837  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
838  {
839  centers[i_gau] /= assign.row(i_gau).sum();
840  priors[i_gau] /= assign.cols();
841  }
842 
843  for (int iData = 0; iData < data.rows(); iData++)
844  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
845  covars[i_gau] += assign(i_gau, iData) * (data.row(iData).transpose() - centers[i_gau]) * (data.row(iData).transpose() - centers[i_gau]).transpose();
846 
847  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
848  covars[i_gau] /= assign.row(i_gau).sum();
849 
850  // Be sure that covar is invertible
851  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
852  covars[i_gau] += MatrixXd::Identity(covars[i_gau].rows(), covars[i_gau].cols()) * 1e-5f;
853  }
854 
855  /*
856  Here's a hacky Matlab script for plotting the EM procedure above (if you cann saveGMM)
857  directory = '/tmp/demoTrainFunctionApproximators/GMR/';
858  inputs = load([directory '/inputs.txt']);
859  targets = load([directory '/targets.txt']);
860  outputs = load([directory '/outputs.txt']);
861 
862 
863  plot(inputs,targets,'.k')
864  hold on
865  plot(inputs,outputs,'.r')
866 
867  max_iter = 5;
868  for iter=0:max_iter
869  color = 0.2+(0.8-iter/max_iter)*[1 1 1];
870  for bfs=0:2
871  center = load(sprintf('%s/gmm_iter%02d_mu%03d.txt',directory,iter,bfs));
872  %plot(center(1),center(2))
873  covar = load(sprintf('%s/gmm_iter%02d_covar%03d.txt',directory,iter,bfs));
874  h = error_ellipse(covar,center,'conf',0.95);
875  set(h,'Color',color,'LineWidth',1+iter/max_iter)
876  end
877  end
878  hold off
879  */
880 
881 }
882 
883 void FunctionApproximatorGMR::expectationMaximizationIncremental(const MatrixXd& data, std::vector<VectorXd>& centers, std::vector<double>& priors,
884  std::vector<MatrixXd>& covars, int& n_observations, int n_max_iter)
885 {
886 
887  std::vector<VectorXd> centers_prev = centers;
888  std::vector<double> priors_prev = priors;
889  std::vector<MatrixXd> covars_prev = covars;
890  int n_observations_prev = n_observations;
891  std::vector<double> E_prev;
892  std::vector<double> E(centers.size());
893  n_observations = data.rows();
894 
895  double oldLoglik = -1e10f;
896  double loglik = 0;
897 
898  MatrixXd assign(centers.size(), data.rows());
899  assign.setZero();
900 
901  // Compute the old E
902  E_prev.resize(centers.size());
903  for (size_t i_gau = 0; i_gau<centers.size(); i_gau++)
904  E_prev[i_gau] = priors_prev[i_gau] * n_observations_prev;
905 
906  for (int iIter = 0; iIter < n_max_iter; iIter++)
907  {
908  //cout << " iIter=" << iIter << endl;
909  // For debugging only
910  //ModelParametersGMR::saveGMM("/tmp/demoTrainFunctionApproximators/GMR",centers,covars,iIter);
911 
912  // E step
913  for (int iData = 0; iData < data.rows(); iData++)
914  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
915  assign(i_gau, iData) = priors[i_gau] * FunctionApproximatorGMR::normalPDFDamped(centers[i_gau], covars[i_gau],data.row(iData).transpose());
916 
917  oldLoglik = loglik;
918  loglik = 0;
919  double sum_tmp = 0.0;
920  for (int iData = 0; iData < data.rows(); iData++)
921  {
922  sum_tmp = assign.col(iData).sum();
923  loglik += log(sum_tmp);
924  }
925  loglik /= data.rows();
926 
927  for (int iData = 0; iData < data.rows(); iData++)
928  assign.col(iData) /= assign.col(iData).sum();
929 
930  if (fabs(loglik / oldLoglik - 1) < 1e-8f)
931  break;
932 
933  // M step
934  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
935  {
936  centers[i_gau].setZero();
937  covars[i_gau].setZero();
938  priors[i_gau] = 0;
939  E[i_gau] = assign.row(i_gau).sum();
940  //E_prev[i_gau] = assign_prev.row(i_gau).sum();
941  }
942 
943  for (int iData = 0; iData < data.rows(); iData++)
944  {
945  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
946  {
947  centers[i_gau] += assign(i_gau, iData) * data.row(iData).transpose();
948  }
949  }
950 
951  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
952  {
953  centers[i_gau] = (E_prev[i_gau] * centers_prev[i_gau] + centers[i_gau])/(E[i_gau] + E_prev[i_gau]);
954  priors[i_gau] = (E[i_gau] + E_prev[i_gau])/(n_observations + n_observations_prev);
955  }
956 
957  for (int iData = 0; iData < data.rows(); iData++)
958  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
959  covars[i_gau] += assign(i_gau, iData) * (data.row(iData).transpose() - centers[i_gau]) * (data.row(iData).transpose() - centers[i_gau]).transpose();
960 
961  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
962  covars[i_gau] = covars[i_gau] + E_prev[i_gau] * (covars_prev[i_gau] + (centers_prev[i_gau] - centers[i_gau]) * (centers_prev[i_gau] - centers[i_gau]).transpose());
963 
964  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
965  covars[i_gau] /= (E[i_gau] + E_prev[i_gau]);
966 
967  // Be sure that covar is invertible
968  for (size_t i_gau = 0; i_gau < centers.size(); i_gau++)
969  covars[i_gau] += MatrixXd::Identity(covars[i_gau].rows(), covars[i_gau].cols()) * 1e-5f;
970  }
971 
972  // Increase the total number of obs counting the old plus the new
973  n_observations += n_observations_prev;
974 }
975 
976 template<class Archive>
977 void FunctionApproximatorGMR::serialize(Archive & ar, const unsigned int version)
978 {
979  // serialize base class information
980  ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(FunctionApproximator);
981 }
982 
983 
984 
985 }
MetaParametersGMR class header file.
FunctionApproximatorGMR class header file.
void train(const Eigen::Ref< const Eigen::MatrixXd > &inputs, const Eigen::Ref< const Eigen::MatrixXd > &targets)
Train the function approximator with corresponding input and target examples.
bool isTrained(void) const
Determine whether the function approximator has already been trained with data or not...
BOOST_CLASS_EXPORT_IMPLEMENT(DmpBbo::FunctionApproximatorGMR)
For boost::serialization.
void kMeansInit(const Eigen::MatrixXd &data, std::vector< Eigen::VectorXd > &means, std::vector< double > &priors, std::vector< Eigen::MatrixXd > &covars, int n_max_iter=1000)
Initialize Gaussian for EM algorithm using k-means.
static double normalPDF(const Eigen::VectorXd &mu, const Eigen::MatrixXd &covar, const Eigen::VectorXd &input)
The probability density function (PDF) of the multi-variate normal distribution.
GMR (Gaussian Mixture Regression) function approximator.
virtual int getExpectedOutputDim(void) const
The expected dimensionality of the output data.
int getExpectedOutputDim(void) const
The expected dimensionality of the output data.
Base class for all function approximators.
Meta-parameters for the GMR function approximator.
void firstDimSlicingInit(const Eigen::MatrixXd &data, std::vector< Eigen::VectorXd > &means, std::vector< double > &priors, std::vector< Eigen::MatrixXd > &covars)
Initialize Gaussian for EM algorithm using a same-size slicing on the first dimension (method used in...
Model parameters for the GMR function approximator.
ModelParametersGMR class header file.
void predict(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &output)
Query the function approximator to make a prediction.
void expectationMaximization(const Eigen::MatrixXd &data, std::vector< Eigen::VectorXd > &means, std::vector< double > &priors, std::vector< Eigen::MatrixXd > &covars, int n_max_iter=50)
EM algorithm.
void predictVariance(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &variances)
Query the function approximator to get the variance of a prediction This function is not implemented ...
virtual int getExpectedInputDim(void) const
The expected dimensionality of the input data.
const MetaParameters * getMetaParameters(void) const
Accessor for FunctionApproximator::meta_parameters_.
static double normalPDFDamped(const Eigen::VectorXd &mu, const Eigen::MatrixXd &covar, const Eigen::VectorXd &input)
The damped probability density function (PDF) of the multi-variate normal distribution.
const ModelParameters * getModelParameters(void) const
Accessor for FunctionApproximator::model_parameters_.
virtual FunctionApproximator * clone(void) const
Return a pointer to a deep copy of the FunctionApproximator object.
void predictDot(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &outputs, Eigen::MatrixXd &outputs_dot)
Query the function approximator to make a prediction and to compute the derivate of that prediction...
unsigned int getNumberOfGaussians(void) const
Get the number of Gaussians in the GMM.
void setModelParameters(ModelParameters *model_parameters)
Accessor for FunctionApproximator::model_parameters_.
int getExpectedInputDim(void) const
The expected dimensionality of the input data.
void trainIncremental(const Eigen::Ref< const Eigen::MatrixXd > &inputs, const Eigen::Ref< const Eigen::MatrixXd > &targets)
Train the function approximator incrementally with corresponding input and target examples...
void expectationMaximizationIncremental(const Eigen::MatrixXd &data, std::vector< Eigen::VectorXd > &means, std::vector< double > &priors, std::vector< Eigen::MatrixXd > &covars, int &n_observations, int n_max_iter=50)
EM algorithm Incremental.
Header file for serialization of Eigen matrices.