DMP_BBO library
FunctionApproximatorLWR.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 
38 
41 
42 #include <iostream>
43 #include <eigen3/Eigen/SVD>
44 #include <eigen3/Eigen/LU>
45 
46 using namespace std;
47 using namespace Eigen;
48 
49 namespace DmpBbo {
50 
51 FunctionApproximatorLWR::FunctionApproximatorLWR(const MetaParametersLWR *const meta_parameters, const ModelParametersLWR *const model_parameters)
52 :
53  FunctionApproximator(meta_parameters,model_parameters)
54 {
55  if (model_parameters!=NULL)
56  preallocateMemory(model_parameters->getNumberOfBasisFunctions());
57 }
58 
59 FunctionApproximatorLWR::FunctionApproximatorLWR(const ModelParametersLWR *const model_parameters)
60 :
61  FunctionApproximator(model_parameters)
62 {
63  preallocateMemory(model_parameters->getNumberOfBasisFunctions());
64 }
65 
66 void FunctionApproximatorLWR::preallocateMemory(int n_basis_functions)
67 {
68  lines_one_prealloc_ = MatrixXd(1,n_basis_functions);
69  activations_one_prealloc_ = MatrixXd(1,n_basis_functions);
70 
71  lines_prealloc_ = MatrixXd(1,n_basis_functions);
72  activations_prealloc_ = MatrixXd(1,n_basis_functions);
73 }
74 
75 
77  // All error checking and cloning is left to the FunctionApproximator constructor.
78  return new FunctionApproximatorLWR(
79  dynamic_cast<const MetaParametersLWR*>(getMetaParameters()),
80  dynamic_cast<const ModelParametersLWR*>(getModelParameters())
81  );
82 };
83 
84 
85 
87 // * Taken from: http://eigen.tuxfamily.org/bz/show_bug.cgi?id=257
88 // * \param[in] a The matrix to be inversed.
89 // * \param[out] result The pseudo-inverse of the matrix.
90 // * \param[in] epsilon Don't know, not my code ;-)
91 // * \return true if pseudo-inverse possible, false otherwise
92 //template<typename _Matrix_Type_>
93 //bool pseudoInverse(const _Matrix_Type_ &a, _Matrix_Type_ &result, double
94 //epsilon = std::numeric_limits<typename _Matrix_Type_::Scalar>::epsilon())
95 //{
96 // if(a.rows()<a.cols())
97 // return false;
98 //
99 // Eigen::JacobiSVD< _Matrix_Type_ > svd = a.jacobiSvd(Eigen::ComputeThinU |
100 //Eigen::ComputeThinV);
101 //
102 // typename _Matrix_Type_::Scalar tolerance = epsilon * std::max(a.cols(),
103 //a.rows()) * svd.singularValues().array().abs().maxCoeff();
104 //
105 // result = svd.matrixV() * _Matrix_Type_( (svd.singularValues().array().abs() >
106 //tolerance).select(svd.singularValues().
107 // array().inverse(), 0) ).asDiagonal() * svd.matrixU().adjoint();
108 //
109 // return true;
110 //}
111 
112 void FunctionApproximatorLWR::train(const Eigen::Ref<const Eigen::MatrixXd>& inputs, const Eigen::Ref<const Eigen::MatrixXd>& targets)
113 {
114  if (isTrained())
115  {
116  cerr << "WARNING: You may not call FunctionApproximatorLWR::train more than once. Doing nothing." << endl;
117  cerr << " (if you really want to retrain, call reTrain function instead)" << endl;
118  return;
119  }
120 
121  assert(inputs.rows() == targets.rows());
122  assert(inputs.cols()==getExpectedInputDim());
123 
124  const MetaParametersLWR* meta_parameters_lwr =
125  dynamic_cast<const MetaParametersLWR*>(getMetaParameters());
126 
127  // Determine the centers and widths of the basis functions, given the range of the input data
128  VectorXd min = inputs.colwise().minCoeff();
129  VectorXd max = inputs.colwise().maxCoeff();
130  MatrixXd centers, widths;
131  meta_parameters_lwr->getCentersAndWidths(min,max,centers,widths);
132  bool normalize_activations = true;
133  bool asym_kernels = meta_parameters_lwr->asymmetric_kernels();
134 
135  // Get the activations of the basis functions
136  int n_samples = inputs.rows();
137  int n_kernels = centers.rows();
138  MatrixXd activations(n_samples,n_kernels);
139  BasisFunction::Gaussian::activations(centers,widths,inputs,activations,normalize_activations,asym_kernels);
140 
141  // Parameters for the weighted least squares regressions
142  bool use_offset = true;
143  double regularization = meta_parameters_lwr->regularization();
144  double min_weight = 0.000001*activations.maxCoeff();
145 
146  // Prepare matrices
147  int n_betas = inputs.cols();
148  if (use_offset)
149  n_betas++;
150  MatrixXd beta(n_kernels,n_betas);
151  VectorXd cur_beta(n_betas);
152  VectorXd weights(inputs.rows());
153 
154  // Perform one weighted least squares regression for each kernel
155  for (int i_kernel=0; i_kernel<n_kernels; i_kernel++)
156  {
157  weights = activations.col(i_kernel);
158  cur_beta = weightedLeastSquares(inputs,targets,weights,use_offset,regularization,min_weight);
159  beta.row(i_kernel) = cur_beta;
160  }
161 
162  MatrixXd offsets = beta.rightCols(1);
163  MatrixXd slopes = beta.leftCols(n_betas-1);
164 
165  setModelParameters(new ModelParametersLWR(centers,widths,slopes,offsets,asym_kernels));
166 
167  preallocateMemory(n_kernels);
168 }
169 
170 void FunctionApproximatorLWR::predict(const Eigen::Ref<const Eigen::MatrixXd>& inputs, MatrixXd& outputs)
171 {
172 
173  if (!isTrained())
174  {
175  cerr << "WARNING: You may not call FunctionApproximatorLWPR::predict if you have not trained yet. Doing nothing." << endl;
176  return;
177  }
178 
179  // The following line of code took a long time to decide on.
180  // The member FunctionApproximator::model_parameters_ (which we access through
181  // getModelParameters()) is of class ModelParameters, not ModelParametersLWR.
182  // So within this function, we need to cast it to ModelParametersLWR in order to make predictions.
183  // There are three options to do this:
184  //
185  // 1) use a dynamic_cast. This is really the best way to do it, but the execution of dynamic_cast
186  // can take relatively long, so we should avoid calling it in this time-critical function
187  // predict() function. (note: because it doesn't matter so much for the non time-critical
188  // train() function above, we use the preferred dynamic_cast<MetaParametersLWR*> as we should)
189  //
190  // 2) move the model_parameters_ member from FunctionApproximator to FunctionApproximatorLWR, and
191  // make it ModelParametersLWR instead of ModelParameters. This, however, will lead to lots of
192  // code duplication, because each derived function approximator class will have to do this.
193  //
194  // 3) Do a static_cast. The static cast does not do checking like dynamic_cast, so we have to be
195  // really sure that getModelParameters returns a ModelParametersLWR. The only way in which this
196  // could wrong is if someone calls setModelParameters() with a different derived class. And
197  // this is near-impossible, because setModelParameters is protected within
198  // FunctionApproximator, and a derived class would be really dumb to set ModelParametersAAA
199  // with setModelParameters and expect getModelParameters to return ModelParametersBBB.
200  //
201  // So I decided to go with 3) because it is fast and does not lead to code duplication,
202  // and only real dumb derived classes can cause trouble ;-)
203  //
204  // Note: The execution time difference between 2) and 3) is negligible:
205  // No cast : 8.90 microseconds/prediction of 1 input sample
206  // Static cast: 8.91 microseconds/prediction of 1 input sample
207  //
208  // There, ~30 lines of comment for one line of code ;-)
209  // (mostly for me to remember why it is like this)
210  const ModelParametersLWR* model_parameters_lwr = static_cast<const ModelParametersLWR*>(getModelParameters());
211 
212  bool only_one_sample = (inputs.rows()==1);
213  if (only_one_sample)
214  {
215  ENTERING_REAL_TIME_CRITICAL_CODE
216 
217  // Only 1 sample, so real-time execution is possible. No need to allocate memory.
218  model_parameters_lwr->getLines(inputs, lines_one_prealloc_);
219 
220  // Weight the values for each line with the normalized basis function activations
221  model_parameters_lwr->kernelActivations(inputs,activations_one_prealloc_);
222 
223  outputs = (lines_one_prealloc_.array()*activations_one_prealloc_.array()).rowwise().sum();
224 
225  EXITING_REAL_TIME_CRITICAL_CODE
226 
227  }
228  else
229  {
230 
231  int n_time_steps = inputs.rows();
232  int n_basis_functions = model_parameters_lwr->getNumberOfBasisFunctions();
233 
234  // The next two lines are not be real-time, as they allocate memory
235  lines_prealloc_.resize(n_time_steps,n_basis_functions);
236  activations_prealloc_.resize(n_time_steps,n_basis_functions);
237  outputs.resize(n_time_steps,getExpectedOutputDim());
238 
239  model_parameters_lwr->getLines(inputs, lines_prealloc_);
240 
241  // Weight the values for each line with the normalized basis function activations
242  model_parameters_lwr->kernelActivations(inputs,activations_prealloc_);
243 
244  outputs = (lines_prealloc_.array()*activations_prealloc_.array()).rowwise().sum();
245 
246  }
247 
248 }
249 
250 bool FunctionApproximatorLWR::saveGridData(const VectorXd& min, const VectorXd& max, const VectorXi& n_samples_per_dim, string save_directory, bool overwrite) const
251 {
252  if (save_directory.empty())
253  return true;
254 
255  MatrixXd inputs;
256  FunctionApproximator::generateInputsGrid(min, max, n_samples_per_dim, inputs);
257 
258  const ModelParametersLWR* model_parameters_lwr = static_cast<const ModelParametersLWR*>(getModelParameters());
259 
260  int n_samples = inputs.rows();
261  int n_basis_functions = model_parameters_lwr->getNumberOfBasisFunctions();
262 
263  MatrixXd lines(n_samples,n_basis_functions);
264  model_parameters_lwr->getLines(inputs, lines);
265 
266  MatrixXd unnormalized_activations(n_samples,n_basis_functions);
267  model_parameters_lwr->unnormalizedKernelActivations(inputs, unnormalized_activations);
268 
269  MatrixXd activations(n_samples,n_basis_functions);
270  model_parameters_lwr->kernelActivations(inputs, activations);
271 
272  MatrixXd predictions = (lines.array()*activations.array()).rowwise().sum();
273 
274  saveMatrix(save_directory,"n_samples_per_dim.txt",n_samples_per_dim,overwrite);
275  saveMatrix(save_directory,"inputs_grid.txt",inputs,overwrite);
276  saveMatrix(save_directory,"lines_grid.txt",lines,overwrite);
277  saveMatrix(save_directory,"activations_unnormalized_grid.txt",unnormalized_activations,overwrite);
278  saveMatrix(save_directory,"activations_grid.txt",activations,overwrite);
279  saveMatrix(save_directory,"predictions_grid.txt",predictions,overwrite);
280 
281 
282  return true;
283 
284 }
285 
286 template<class Archive>
287 void FunctionApproximatorLWR::serialize(Archive & ar, const unsigned int version)
288 {
289  // serialize base class information
290  ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(FunctionApproximator);
291 }
292 
293 }
Meta-parameters for the Locally Weighted Regression (LWR) function approximator.
Header file for input/output of Eigen matrices to ASCII files.
void kernelActivations(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &kernel_activations) const
Get the normalized kernel activations for given inputs.
Header file for various least squares functions.
Eigen::MatrixXd weightedLeastSquares(const Eigen::Ref< const Eigen::MatrixXd > &inputs, const Eigen::Ref< const Eigen::MatrixXd > &targets, const Eigen::Ref< const Eigen::VectorXd > &weights, bool use_offset, double regularization, double min_weight)
(Regularized) weighted least squares with bias
static void generateInputsGrid(const Eigen::VectorXd &min, const Eigen::VectorXd &max, const Eigen::VectorXi &n_samples_per_dim, Eigen::MatrixXd &inputs_grid)
Generate a input samples that lie on a grid (much like Matlab&#39;s meshgrid) For instance, if min = [2 6], and max = [3 8], and n_samples_per_dim = [3 5] then this function first makes linearly spaces samples along each dimension, e.g.
Model parameters for the Locally Weighted Regression (LWR) function approximator. ...
bool saveMatrix(std::string filename, Eigen::Matrix< Scalar, RowsAtCompileTime, ColsAtCompileTime > matrix, bool overwrite=false)
Save an Eigen matrix to an ASCII file.
bool isTrained(void) const
Determine whether the function approximator has already been trained with data or not...
MetaParametersLWR class header file.
void predict(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &outputs)
Query the function approximator to make a prediction.
BasisFunction header file.
int getExpectedOutputDim(void) const
The expected dimensionality of the output data.
Base class for all function approximators.
FunctionApproximator * clone(void) const
Return a pointer to a deep copy of the FunctionApproximator object.
void getCentersAndWidths(const Eigen::VectorXd &min, const Eigen::VectorXd &max, Eigen::MatrixXd &centers, Eigen::MatrixXd &widths) const
Get the centers and widths of the basis functions.
void getLines(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &lines) const
Get the output of each linear model (unweighted) for the given inputs.
bool saveGridData(const Eigen::VectorXd &min, const Eigen::VectorXd &max, const Eigen::VectorXi &n_samples_per_dim, std::string directory, bool overwrite=false) const
Generate a grid of inputs, and output the response of the basis functions and line segments for these...
ModelParametersLWR class header file.
LWR (Locally Weighted Regression) function approximator.
const MetaParameters * getMetaParameters(void) const
Accessor for FunctionApproximator::meta_parameters_.
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.
const ModelParameters * getModelParameters(void) const
Accessor for FunctionApproximator::model_parameters_.
BOOST_CLASS_EXPORT_IMPLEMENT(DmpBbo::FunctionApproximatorLWR)
For boost::serialization.
void setModelParameters(ModelParameters *model_parameters)
Accessor for FunctionApproximator::model_parameters_.
unsigned int getNumberOfBasisFunctions() const
Get the number of basis functions in this model.
FunctionApproximatorLWR class header file.
int getExpectedInputDim(void) const
The expected dimensionality of the input data.
void unnormalizedKernelActivations(const Eigen::Ref< const Eigen::MatrixXd > &inputs, Eigen::MatrixXd &kernel_activations) const
Get the unnormalized kernel activations for given inputs.
Header file for serialization of Eigen matrices.