https://mooseframework.inl.gov
GaussianProcess.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #include "GaussianProcess.h"
11 #include "FEProblemBase.h"
12 
13 #include <petsctao.h>
14 #include <petscdmda.h>
15 
16 #include "libmesh/petsc_vector.h"
17 #include "libmesh/petsc_matrix.h"
18 
19 #include <cmath>
20 
21 #include "MooseRandom.h"
22 #include "Shuffle.h"
23 
24 namespace StochasticTools
25 {
26 
27 GaussianProcess::GPOptimizerOptions::GPOptimizerOptions(const bool show_every_nth_iteration,
28  const unsigned int num_iter,
29  const unsigned int batch_size,
30  const Real learning_rate,
31  const Real b1,
32  const Real b2,
33  const Real eps,
34  const Real lambda)
35  : show_every_nth_iteration(show_every_nth_iteration),
36  num_iter(num_iter),
37  batch_size(batch_size),
38  learning_rate(learning_rate),
39  b1(b1),
40  b2(b2),
41  eps(eps),
42  lambda(lambda)
43 {
44 }
45 
47 
48 void
50  const std::vector<std::string> & params_to_tune,
51  const std::vector<Real> & min,
52  const std::vector<Real> & max)
53 {
54  linkCovarianceFunction(covariance_function);
55  generateTuningMap(params_to_tune, min, max);
56 }
57 
58 void
60 {
61  _covariance_function = covariance_function;
67 }
68 
69 void
71  const RealEigenMatrix & training_data,
72  const GPOptimizerOptions & opts)
73 {
74  const bool batch_decision = opts.batch_size > 0 && (opts.batch_size <= training_params.rows());
75  _batch_size = batch_decision ? opts.batch_size : training_params.rows();
77 
78  if (_tuning_data.size())
79  tuneHyperParamsAdam(training_params, training_data, opts);
80 
81  _K.resize(training_params.rows() * training_data.cols(),
82  training_params.rows() * training_data.cols());
83  _covariance_function->computeCovarianceMatrix(_K, training_params, training_params, true);
84 
85  RealEigenMatrix flattened_data =
86  training_data.reshaped(training_params.rows() * training_data.cols(), 1);
87 
88  // Compute the Cholesky decomposition and inverse action of the covariance matrix
89  setupStoredMatrices(flattened_data);
90 
92 }
93 
94 void
96 {
97  _K_cho_decomp = _K.llt();
98  _K_results_solve = _K_cho_decomp.solve(input);
99 }
100 
101 void
102 GaussianProcess::generateTuningMap(const std::vector<std::string> & params_to_tune,
103  const std::vector<Real> & min_vector,
104  const std::vector<Real> & max_vector)
105 {
106  _num_tunable = 0;
107 
108  const bool upper_bounds_specified = min_vector.size();
109  const bool lower_bounds_specified = max_vector.size();
110 
111  for (const auto param_i : index_range(params_to_tune))
112  {
113  const auto & hp = params_to_tune[param_i];
115  {
116  unsigned int size;
117  Real min;
118  Real max;
119  // Get size and default min/max
120  const bool found = _covariance_function->getTuningData(hp, size, min, max);
121 
122  if (!found)
123  ::mooseError("The covariance parameter ", hp, " could not be found!");
124 
125  // Check for overridden min/max
126  min = lower_bounds_specified ? min_vector[param_i] : min;
127  max = upper_bounds_specified ? max_vector[param_i] : max;
128  // Save data in tuple
129  _tuning_data[hp] = std::make_tuple(_num_tunable, size, min, max);
130  _num_tunable += size;
131  }
132  }
133 }
134 
135 void
137 {
138  if (!keep_moments)
141 }
142 
143 void
145 {
146  if (!keep_moments)
149 }
150 
151 void
153  const RealEigenMatrix & training_data,
154  const GPOptimizerOptions & opts)
155 {
156  std::vector<Real> theta(_num_tunable, 0.0);
158 
160 
161  // Internal params for Adam; set to the recommended values in the paper
162  Real b1 = opts.b1;
163  Real b2 = opts.b2;
164  Real eps = opts.eps;
165  static constexpr Real lambda = 1e-4;
166 
167  std::vector<Real> m0(_num_tunable, 0.0);
168  std::vector<Real> v0(_num_tunable, 0.0);
169 
170  Real new_val;
171  Real m_hat;
172  Real v_hat;
173  Real store_loss = 0.0;
174  std::vector<Real> grad1;
175 
176  // Initialize randomizer
177  std::vector<unsigned int> v_sequence(training_params.rows());
178  std::iota(std::begin(v_sequence), std::end(v_sequence), 0);
179  RealEigenMatrix inputs(_batch_size, training_params.cols());
180  RealEigenMatrix outputs(_batch_size, training_data.cols());
181  if (opts.show_every_nth_iteration)
182  Moose::out << "OPTIMIZING GP HYPER-PARAMETERS USING Adam" << std::endl;
183  for (unsigned int ss = 0; ss < opts.num_iter; ++ss)
184  {
185  // Shuffle data
186  MooseRandom generator;
187  generator.seed(0, 1980);
188  generator.saveState();
189  MooseUtils::shuffle<unsigned int>(v_sequence, generator, 0);
190  for (unsigned int ii = 0; ii < _batch_size; ++ii)
191  {
192  for (unsigned int jj = 0; jj < training_params.cols(); ++jj)
193  inputs(ii, jj) = training_params(v_sequence[ii], jj);
194 
195  for (unsigned int jj = 0; jj < training_data.cols(); ++jj)
196  outputs(ii, jj) = training_data(v_sequence[ii], jj);
197  }
198 
199  store_loss = getLoss(inputs, outputs);
200  if (opts.show_every_nth_iteration && ((ss + 1) % opts.show_every_nth_iteration == 0))
201  Moose::out << "Iteration: " << ss + 1 << " LOSS: " << store_loss << std::endl;
202  grad1 = getGradient(inputs);
203  for (auto iter = _tuning_data.begin(); iter != _tuning_data.end(); ++iter)
204  {
205  const auto first_index = std::get<0>(iter->second);
206  const auto num_entries = std::get<1>(iter->second);
207  for (unsigned int ii = 0; ii < num_entries; ++ii)
208  {
209  const auto global_index = first_index + ii;
210  m0[global_index] = b1 * m0[global_index] + (1 - b1) * grad1[global_index];
211  v0[global_index] =
212  b2 * v0[global_index] + (1 - b2) * grad1[global_index] * grad1[global_index];
213  m_hat = m0[global_index] / (1 - std::pow(b1, (ss + 1)));
214  v_hat = v0[global_index] / (1 - std::pow(b2, (ss + 1)));
215  new_val =
216  theta[global_index] - 1.0 * (opts.learning_rate * m_hat / (std::sqrt(v_hat) + eps) +
217  lambda * theta[global_index]);
218 
219  const auto min_value = std::get<2>(iter->second);
220  const auto max_value = std::get<3>(iter->second);
221 
222  theta[global_index] = std::min(std::max(new_val, min_value), max_value);
223  }
224  }
227  }
228  if (opts.show_every_nth_iteration)
229  {
230  Moose::out << "OPTIMIZED GP HYPER-PARAMETERS:" << std::endl;
231  Moose::out << Moose::stringify(theta) << std::endl;
232  Moose::out << "FINAL LOSS: " << store_loss << std::endl;
233  }
234 
235  if (theta.size() > 0)
236  {
237  unsigned int count = 1;
238  _length_scales.resize(_num_tunable - count);
239  for (unsigned int i = 0; i < _num_tunable - count; ++i)
240  _length_scales[i] = theta[i + 1];
241  }
242 }
243 
244 Real
246 {
247  _covariance_function->computeCovarianceMatrix(_K, inputs, inputs, true);
248 
249  RealEigenMatrix flattened_data = outputs.reshaped(outputs.rows() * outputs.cols(), 1);
250 
251  setupStoredMatrices(flattened_data);
252 
253  Real log_likelihood = 0;
254  log_likelihood += -(flattened_data.transpose() * _K_results_solve)(0, 0);
255  log_likelihood += -std::log(_K.determinant());
256  log_likelihood -= _batch_size * std::log(2 * M_PI);
257  log_likelihood = -log_likelihood / 2;
258  return log_likelihood;
259 }
260 
261 std::vector<Real>
263 {
266  std::vector<Real> grad_vec;
267  grad_vec.resize(_num_tunable);
268  for (auto iter = _tuning_data.begin(); iter != _tuning_data.end(); ++iter)
269  {
270  std::string hyper_param_name = iter->first;
271  const auto first_index = std::get<0>(iter->second);
272  const auto num_entries = std::get<1>(iter->second);
273  for (unsigned int ii = 0; ii < num_entries; ++ii)
274  {
275  const auto global_index = first_index + ii;
276  _covariance_function->computedKdhyper(dKdhp, inputs, hyper_param_name, ii);
277  RealEigenMatrix tmp = alpha * dKdhp - _K_cho_decomp.solve(dKdhp);
278  grad_vec[global_index] = -tmp.trace() / 2.0;
279  }
280  }
281  return grad_vec;
282 }
283 
284 void
286  const std::unordered_map<std::string, std::tuple<unsigned int, unsigned int, Real, Real>> &
287  tuning_data,
288  const std::unordered_map<std::string, Real> & scalar_map,
289  const std::unordered_map<std::string, std::vector<Real>> & vector_map,
290  std::vector<Real> & vec) const
291 {
292  for (auto iter : tuning_data)
293  {
294  const std::string & param_name = iter.first;
295  const auto scalar_it = scalar_map.find(param_name);
296  if (scalar_it != scalar_map.end())
297  vec[std::get<0>(iter.second)] = scalar_it->second;
298  else
299  {
300  const auto vector_it = vector_map.find(param_name);
301  if (vector_it != vector_map.end())
302  for (unsigned int ii = 0; ii < std::get<1>(iter.second); ++ii)
303  vec[std::get<0>(iter.second) + ii] = (vector_it->second)[ii];
304  }
305  }
306 }
307 
308 void
310  const std::unordered_map<std::string, std::tuple<unsigned int, unsigned int, Real, Real>> &
311  tuning_data,
312  std::unordered_map<std::string, Real> & scalar_map,
313  std::unordered_map<std::string, std::vector<Real>> & vector_map,
314  const std::vector<Real> & vec) const
315 {
316  for (auto iter : tuning_data)
317  {
318  const std::string & param_name = iter.first;
319  if (scalar_map.find(param_name) != scalar_map.end())
320  scalar_map[param_name] = vec[std::get<0>(iter.second)];
321  else if (vector_map.find(param_name) != vector_map.end())
322  for (unsigned int ii = 0; ii < std::get<1>(iter.second); ++ii)
323  vector_map[param_name][ii] = vec[std::get<0>(iter.second) + ii];
324  }
325 }
326 
327 } // StochasticTools namespace
328 
329 template <>
330 void
331 dataStore(std::ostream & stream, Eigen::LLT<RealEigenMatrix> & decomp, void * context)
332 {
333  // Store the L matrix as opposed to the full matrix to avoid compounding
334  // roundoff error and decomposition error
335  RealEigenMatrix L(decomp.matrixL());
336  dataStore(stream, L, context);
337 }
338 
339 template <>
340 void
341 dataLoad(std::istream & stream, Eigen::LLT<RealEigenMatrix> & decomp, void * context)
342 {
343  RealEigenMatrix L;
344  dataLoad(stream, L, context);
345  decomp.compute(L * L.transpose());
346 }
347 
348 template <>
349 void
350 dataStore(std::ostream & stream, StochasticTools::GaussianProcess & gp_utils, void * context)
351 {
352  dataStore(stream, gp_utils.hyperparamMap(), context);
353  dataStore(stream, gp_utils.hyperparamVectorMap(), context);
354  dataStore(stream, gp_utils.covarType(), context);
355  dataStore(stream, gp_utils.covarName(), context);
356  dataStore(stream, gp_utils.covarNumOutputs(), context);
357  dataStore(stream, gp_utils.dependentCovarNames(), context);
358  dataStore(stream, gp_utils.dependentCovarTypes(), context);
359  dataStore(stream, gp_utils.K(), context);
360  dataStore(stream, gp_utils.KResultsSolve(), context);
361  dataStore(stream, gp_utils.KCholeskyDecomp(), context);
362  dataStore(stream, gp_utils.paramStandardizer(), context);
363  dataStore(stream, gp_utils.dataStandardizer(), context);
364 }
365 
366 template <>
367 void
368 dataLoad(std::istream & stream, StochasticTools::GaussianProcess & gp_utils, void * context)
369 {
370  dataLoad(stream, gp_utils.hyperparamMap(), context);
371  dataLoad(stream, gp_utils.hyperparamVectorMap(), context);
372  dataLoad(stream, gp_utils.covarType(), context);
373  dataLoad(stream, gp_utils.covarName(), context);
374  dataLoad(stream, gp_utils.covarNumOutputs(), context);
375  dataLoad(stream, gp_utils.dependentCovarNames(), context);
376  dataLoad(stream, gp_utils.dependentCovarTypes(), context);
377  dataLoad(stream, gp_utils.K(), context);
378  dataLoad(stream, gp_utils.KResultsSolve(), context);
379  dataLoad(stream, gp_utils.KCholeskyDecomp(), context);
380  dataLoad(stream, gp_utils.paramStandardizer(), context);
381  dataLoad(stream, gp_utils.dataStandardizer(), context);
382 }
unsigned int _num_tunable
Number of tunable hyperparameters.
void linkCovarianceFunction(CovarianceFunctionBase *covariance_function)
Finds and links the covariance function to this object.
RealEigenMatrix _K_results_solve
A solve of Ax=b via Cholesky.
void setupCovarianceMatrix(const RealEigenMatrix &training_params, const RealEigenMatrix &training_data, const GPOptimizerOptions &opts)
Sets up the covariance matrix given data and optimization options.
void loadHyperParamMap(const std::unordered_map< std::string, Real > &map, const std::unordered_map< std::string, std::vector< Real >> &vec_map)
Load some hyperparameters into the local maps contained in this object.
CovarianceFunctionBase * _covariance_function
Covariance function object.
std::unordered_map< std::string, std::vector< Real > > _hyperparam_vec_map
Vector hyperparameters. Stored for use in surrogate.
StochasticTools::Standardizer _data_standardizer
Standardizer for use with data (y)
const unsigned int show_every_nth_iteration
Switch to enable verbose output for parameter tuning at every n-th iteration.
std::map< UserObjectName, std::string > & dependentCovarTypes()
const Real eps
void getStandardized(RealEigenMatrix &input) const
Returns the standardized (centered and scaled) of the provided input.
Definition: Standardizer.C:80
void saveState()
std::unordered_map< std::string, Real > _hyperparam_map
Scalar hyperparameters. Stored for use in surrogate.
void seed(std::size_t i, unsigned int seed)
Real getLoss(RealEigenMatrix &inputs, RealEigenMatrix &outputs)
const Real eps
Tuning parameter from the paper.
void generateTuningMap(const std::vector< std::string > &params_to_tune, const std::vector< Real > &min=std::vector< Real >(), const std::vector< Real > &max=std::vector< Real >())
Sets up the tuning map which is used if the user requires parameter tuning.
Base class for covariance functions that are used in Gaussian Processes.
StochasticTools::Standardizer _param_standardizer
Standardizer for use with params (x)
std::string _covar_name
The name of the covariance function used in this GP.
std::map< UserObjectName, std::string > _dependent_covar_types
The types of the covariance functions the used covariance function depends on.
void standardizeData(RealEigenMatrix &data, bool keep_moments=false)
Standardizes the vector of responses (y values).
const unsigned int batch_size
The batch isize for Adam optimizer.
auto max(const L &left, const R &right)
void computeSet(const RealEigenMatrix &input)
Methods for computing and setting mean and standard deviation.
Definition: Standardizer.C:58
Structure containing the optimization options for hyperparameter-tuning.
void dataLoad(std::istream &stream, Eigen::LLT< RealEigenMatrix > &decomp, void *context)
void tuneHyperParamsAdam(const RealEigenMatrix &training_params, const RealEigenMatrix &training_data, const GPOptimizerOptions &opts)
const std::string & name() const
unsigned int _num_outputs
The number of outputs of the GP.
Enum for batch type in stochastic tools MultiApp.
StochasticTools::Standardizer & dataStandardizer()
StochasticTools::Standardizer & paramStandardizer()
Get non-constant reference to the contained structures (if they need to be modified from the utside) ...
void dataStore(std::ostream &stream, Eigen::LLT< RealEigenMatrix > &decomp, void *context)
const Real b2
Tuning parameter from the paper.
std::vector< UserObjectName > _dependent_covar_names
The names of the covariance functions the used covariance function depends on.
const std::string & type() const
std::unordered_map< std::string, std::vector< Real > > & hyperparamVectorMap()
const std::vector< UserObjectName > & dependentCovarianceNames() const
Get the names of the dependent covariances.
void buildHyperParamMap(std::unordered_map< std::string, Real > &map, std::unordered_map< std::string, std::vector< Real >> &vec_map) const
Populates the input maps with the owned hyperparameters.
RealEigenMatrix _K
An _n_sample by _n_sample covariance matrix constructed from the selected kernel function.
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic > RealEigenMatrix
const Real b1
Tuning parameter from the paper.
void initialize(CovarianceFunctionBase *covariance_function, const std::vector< std::string > &params_to_tune, const std::vector< Real > &min=std::vector< Real >(), const std::vector< Real > &max=std::vector< Real >())
Initializes the most important structures in the Gaussian Process: the covariance function and a tuni...
std::string stringify(const T &t)
std::unordered_map< std::string, std::tuple< unsigned int, unsigned int, Real, Real > > _tuning_data
Contains tuning inforation. Index of hyperparam, size, and min/max bounds.
Eigen::LLT< RealEigenMatrix > _K_cho_decomp
Cholesky decomposition Eigen object.
void dependentCovarianceTypes(std::map< UserObjectName, std::string > &name_type_map) const
Populate a map with the names and types of the dependent covariance functions.
unsigned int _batch_size
The batch size for Adam optimization.
std::vector< Real > _length_scales
To return the GP length scales for active learning.
std::vector< Real > getGradient(RealEigenMatrix &inputs) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual void computeCovarianceMatrix(RealEigenMatrix &K, const RealEigenMatrix &x, const RealEigenMatrix &xp, const bool is_self_covariance) const =0
Generates the Covariance Matrix given two sets of points in the parameter space.
static const std::string alpha
Definition: NS.h:138
const unsigned int num_iter
The number of iterations for Adam optimizer.
Eigen::LLT< RealEigenMatrix > & KCholeskyDecomp()
std::string _covar_type
Type of covariance function used for this GP.
unsigned int numOutputs() const
Return the number of outputs assumed for this covariance function.
std::unordered_map< std::string, Real > & hyperparamMap()
void vecToMap(const std::unordered_map< std::string, std::tuple< unsigned int, unsigned int, Real, Real >> &tuning_data, std::unordered_map< std::string, Real > &scalar_map, std::unordered_map< std::string, std::vector< Real >> &vector_map, const std::vector< Real > &vec) const
Function used to convert the vectors back to hyperparameter maps.
Utility class dedicated to hold structures and functions commont to Gaussian Processes.
std::vector< UserObjectName > & dependentCovarNames()
void setupStoredMatrices(const RealEigenMatrix &input)
Sets up the Cholesky decomposition and inverse action of the covariance matrix.
auto min(const L &left, const R &right)
virtual bool computedKdhyper(RealEigenMatrix &dKdhp, const RealEigenMatrix &x, const std::string &hyper_param_name, unsigned int ind) const
Redirect dK/dhp for hyperparameter "hp".
void mapToVec(const std::unordered_map< std::string, std::tuple< unsigned int, unsigned int, Real, Real >> &tuning_data, const std::unordered_map< std::string, Real > &scalar_map, const std::unordered_map< std::string, std::vector< Real >> &vector_map, std::vector< Real > &vec) const
Function used to convert the hyperparameter maps in this object to vectors.
MooseUnits pow(const MooseUnits &, int)
virtual bool getTuningData(const std::string &name, unsigned int &size, Real &min, Real &max) const
Get the default minimum and maximum and size of a hyperparameter.
auto index_range(const T &sizable)
void standardizeParameters(RealEigenMatrix &parameters, bool keep_moments=false)
Standardizes the vector of input parameters (x values).
const Real learning_rate
The learning rate for Adam optimizer.
virtual bool isTunable(const std::string &name) const
Check if a given parameter is tunable.