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 
166  std::vector<Real> m0(_num_tunable, 0.0);
167  std::vector<Real> v0(_num_tunable, 0.0);
168 
169  Real new_val;
170  Real m_hat;
171  Real v_hat;
172  Real store_loss = 0.0;
173  std::vector<Real> grad1;
174 
175  // Initialize randomizer
176  std::vector<unsigned int> v_sequence(training_params.rows());
177  std::iota(std::begin(v_sequence), std::end(v_sequence), 0);
178  RealEigenMatrix inputs(_batch_size, training_params.cols());
179  RealEigenMatrix outputs(_batch_size, training_data.cols());
180  if (opts.show_every_nth_iteration)
181  Moose::out << "OPTIMIZING GP HYPER-PARAMETERS USING Adam" << std::endl;
182  for (unsigned int ss = 0; ss < opts.num_iter; ++ss)
183  {
184  // Shuffle data
185  MooseRandom generator;
186  generator.seed(0, 1980);
187  generator.saveState();
188  MooseUtils::shuffle<unsigned int>(v_sequence, generator, 0);
189  for (unsigned int ii = 0; ii < _batch_size; ++ii)
190  {
191  for (unsigned int jj = 0; jj < training_params.cols(); ++jj)
192  inputs(ii, jj) = training_params(v_sequence[ii], jj);
193 
194  for (unsigned int jj = 0; jj < training_data.cols(); ++jj)
195  outputs(ii, jj) = training_data(v_sequence[ii], jj);
196  }
197 
198  store_loss = getLoss(inputs, outputs);
199  if (opts.show_every_nth_iteration && ((ss + 1) % opts.show_every_nth_iteration == 0))
200  Moose::out << "Iteration: " << ss + 1 << " LOSS: " << store_loss << std::endl;
201  grad1 = getGradient(inputs);
202  for (auto iter = _tuning_data.begin(); iter != _tuning_data.end(); ++iter)
203  {
204  const auto first_index = std::get<0>(iter->second);
205  const auto num_entries = std::get<1>(iter->second);
206  for (unsigned int ii = 0; ii < num_entries; ++ii)
207  {
208  const auto global_index = first_index + ii;
209  m0[global_index] = b1 * m0[global_index] + (1 - b1) * grad1[global_index];
210  v0[global_index] =
211  b2 * v0[global_index] + (1 - b2) * grad1[global_index] * grad1[global_index];
212  m_hat = m0[global_index] / (1 - std::pow(b1, (ss + 1)));
213  v_hat = v0[global_index] / (1 - std::pow(b2, (ss + 1)));
214  new_val = theta[global_index] - opts.learning_rate * m_hat / (std::sqrt(v_hat) + eps);
215 
216  const auto min_value = std::get<2>(iter->second);
217  const auto max_value = std::get<3>(iter->second);
218 
219  theta[global_index] = std::min(std::max(new_val, min_value), max_value);
220  }
221  }
224  }
225  if (opts.show_every_nth_iteration)
226  {
227  Moose::out << "OPTIMIZED GP HYPER-PARAMETERS:" << std::endl;
228  Moose::out << Moose::stringify(theta) << std::endl;
229  Moose::out << "FINAL LOSS: " << store_loss << std::endl;
230  }
231 }
232 
233 Real
235 {
236  _covariance_function->computeCovarianceMatrix(_K, inputs, inputs, true);
237 
238  RealEigenMatrix flattened_data = outputs.reshaped(outputs.rows() * outputs.cols(), 1);
239 
240  setupStoredMatrices(flattened_data);
241 
242  Real log_likelihood = 0;
243  log_likelihood += -(flattened_data.transpose() * _K_results_solve)(0, 0);
244  log_likelihood += -std::log(_K.determinant());
245  log_likelihood -= _batch_size * std::log(2 * M_PI);
246  log_likelihood = -log_likelihood / 2;
247  return log_likelihood;
248 }
249 
250 std::vector<Real>
252 {
255  std::vector<Real> grad_vec;
256  grad_vec.resize(_num_tunable);
257  for (auto iter = _tuning_data.begin(); iter != _tuning_data.end(); ++iter)
258  {
259  std::string hyper_param_name = iter->first;
260  const auto first_index = std::get<0>(iter->second);
261  const auto num_entries = std::get<1>(iter->second);
262  for (unsigned int ii = 0; ii < num_entries; ++ii)
263  {
264  const auto global_index = first_index + ii;
265  _covariance_function->computedKdhyper(dKdhp, inputs, hyper_param_name, ii);
266  RealEigenMatrix tmp = alpha * dKdhp - _K_cho_decomp.solve(dKdhp);
267  grad_vec[global_index] = -tmp.trace() / 2.0;
268  }
269  }
270  return grad_vec;
271 }
272 
273 void
275  const std::unordered_map<std::string, std::tuple<unsigned int, unsigned int, Real, Real>> &
276  tuning_data,
277  const std::unordered_map<std::string, Real> & scalar_map,
278  const std::unordered_map<std::string, std::vector<Real>> & vector_map,
279  std::vector<Real> & vec) const
280 {
281  for (auto iter : tuning_data)
282  {
283  const std::string & param_name = iter.first;
284  const auto scalar_it = scalar_map.find(param_name);
285  if (scalar_it != scalar_map.end())
286  vec[std::get<0>(iter.second)] = scalar_it->second;
287  else
288  {
289  const auto vector_it = vector_map.find(param_name);
290  if (vector_it != vector_map.end())
291  for (unsigned int ii = 0; ii < std::get<1>(iter.second); ++ii)
292  vec[std::get<0>(iter.second) + ii] = (vector_it->second)[ii];
293  }
294  }
295 }
296 
297 void
299  const std::unordered_map<std::string, std::tuple<unsigned int, unsigned int, Real, Real>> &
300  tuning_data,
301  std::unordered_map<std::string, Real> & scalar_map,
302  std::unordered_map<std::string, std::vector<Real>> & vector_map,
303  const std::vector<Real> & vec) const
304 {
305  for (auto iter : tuning_data)
306  {
307  const std::string & param_name = iter.first;
308  if (scalar_map.find(param_name) != scalar_map.end())
309  scalar_map[param_name] = vec[std::get<0>(iter.second)];
310  else if (vector_map.find(param_name) != vector_map.end())
311  for (unsigned int ii = 0; ii < std::get<1>(iter.second); ++ii)
312  vector_map[param_name][ii] = vec[std::get<0>(iter.second) + ii];
313  }
314 }
315 
316 } // StochasticTools namespace
317 
318 template <>
319 void
320 dataStore(std::ostream & stream, Eigen::LLT<RealEigenMatrix> & decomp, void * context)
321 {
322  // Store the L matrix as opposed to the full matrix to avoid compounding
323  // roundoff error and decomposition error
324  RealEigenMatrix L(decomp.matrixL());
325  dataStore(stream, L, context);
326 }
327 
328 template <>
329 void
330 dataLoad(std::istream & stream, Eigen::LLT<RealEigenMatrix> & decomp, void * context)
331 {
332  RealEigenMatrix L;
333  dataLoad(stream, L, context);
334  decomp.compute(L * L.transpose());
335 }
336 
337 template <>
338 void
339 dataStore(std::ostream & stream, StochasticTools::GaussianProcess & gp_utils, void * context)
340 {
341  dataStore(stream, gp_utils.hyperparamMap(), context);
342  dataStore(stream, gp_utils.hyperparamVectorMap(), context);
343  dataStore(stream, gp_utils.covarType(), context);
344  dataStore(stream, gp_utils.covarName(), context);
345  dataStore(stream, gp_utils.covarNumOutputs(), context);
346  dataStore(stream, gp_utils.dependentCovarNames(), context);
347  dataStore(stream, gp_utils.dependentCovarTypes(), context);
348  dataStore(stream, gp_utils.K(), context);
349  dataStore(stream, gp_utils.KResultsSolve(), context);
350  dataStore(stream, gp_utils.KCholeskyDecomp(), context);
351  dataStore(stream, gp_utils.paramStandardizer(), context);
352  dataStore(stream, gp_utils.dataStandardizer(), context);
353 }
354 
355 template <>
356 void
357 dataLoad(std::istream & stream, StochasticTools::GaussianProcess & gp_utils, void * context)
358 {
359  dataLoad(stream, gp_utils.hyperparamMap(), context);
360  dataLoad(stream, gp_utils.hyperparamVectorMap(), context);
361  dataLoad(stream, gp_utils.covarType(), context);
362  dataLoad(stream, gp_utils.covarName(), context);
363  dataLoad(stream, gp_utils.covarNumOutputs(), context);
364  dataLoad(stream, gp_utils.dependentCovarNames(), context);
365  dataLoad(stream, gp_utils.dependentCovarTypes(), context);
366  dataLoad(stream, gp_utils.K(), context);
367  dataLoad(stream, gp_utils.KResultsSolve(), context);
368  dataLoad(stream, gp_utils.KCholeskyDecomp(), context);
369  dataLoad(stream, gp_utils.paramStandardizer(), context);
370  dataLoad(stream, gp_utils.dataStandardizer(), context);
371 }
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).
virtual const std::string & name() const
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)
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 > 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:134
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.