9 #ifdef MOOSE_LIBTORCH_ENABLED 15 #include <petscdmda.h> 17 #include "libmesh/petsc_vector.h" 18 #include "libmesh/petsc_matrix.h" 25 #include <torch/optim/adam.h> 36 flattenOutputData(
const torch::Tensor & output_data)
38 mooseAssert(output_data.dim() == 2,
"GaussianProcess output data must be rank-2.");
39 return torch::reshape(torch::transpose(output_data, 0, 1),
40 {output_data.size(0) * output_data.size(1), 1});
44 doubleOptionsLike(
const torch::Tensor & tensor)
46 return tensor.options().dtype(at::kDouble);
50 toOptions(
const torch::Tensor & tensor,
const torch::TensorOptions & options)
52 auto result = tensor.to(options.device());
53 if (result.scalar_type() != at::kDouble)
54 result = result.to(at::kDouble);
59 exportHyperParameter(
const torch::Tensor & tensor)
63 mooseError(
"Unsupported hyperparameter rank ", tensor.dim(),
".");
65 if (cpu_tensor.scalar_type() != at::kDouble)
66 cpu_tensor = cpu_tensor.to(at::kDouble).contiguous();
67 const auto flattened = cpu_tensor.reshape({-1});
68 return {flattened.data_ptr<
Real>(), flattened.data_ptr<Real>() + flattened.numel()};
72 buildVectorHyperParameter(
const std::vector<Real> & values,
const torch::TensorOptions & options)
74 auto tensor = torch::empty({long(values.size())}, torch::TensorOptions().dtype(at::kDouble));
75 auto tensor_accessor = tensor.accessor<
Real, 1>();
77 tensor_accessor[index] = values[index];
78 return toOptions(tensor, options);
82 moveHyperParameters(HyperParameterMap & hyperparameters,
const torch::TensorOptions & options)
84 for (
auto & iter : hyperparameters)
85 iter.second = toOptions(iter.second, options);
89 updateHyperParameter(torch::Tensor & tensor,
90 const std::vector<Real> & values,
91 const std::string &
name)
93 const auto options = doubleOptionsLike(tensor);
96 mooseAssert(values.size() == 1,
"Scalar hyperparameter update requires a single value.");
98 toOptions(torch::tensor(values[0], torch::TensorOptions().dtype(at::kDouble)), options);
101 tensor = buildVectorHyperParameter(values, options);
103 mooseError(
"Unsupported hyperparameter rank ", tensor.dim(),
" for ",
name,
".");
109 const unsigned int num_iter,
110 const unsigned int batch_size,
111 const Real learning_rate,
117 : show_every_nth_iteration(show_every_nth_iteration),
119 batch_size(batch_size),
120 learning_rate(learning_rate),
125 optimizer_type(optimizer_type)
133 const std::vector<std::string> & params_to_tune,
134 const std::vector<Real> & min,
135 const std::vector<Real> & max)
154 const torch::Tensor & training_data,
157 const auto options = doubleOptionsLike(training_params);
158 const auto params = toOptions(training_params, options);
159 const auto data = toOptions(training_data, options);
161 mooseAssert(params.dim() == 2,
"GaussianProcess training parameters must be rank-2.");
162 mooseAssert(data.dim() == 2,
"GaussianProcess training responses must be rank-2.");
164 const auto num_samples = params.size(0);
165 mooseAssert(data.size(0) == num_samples,
166 "Training parameter and response sample counts must match.");
168 "Training response dimension does not match the covariance output dimension.");
182 const auto flattened_tensor = flattenOutputData(data);
197 const std::vector<Real> & min_vector,
198 const std::vector<Real> & max_vector)
202 const bool upper_bounds_specified = min_vector.size();
203 const bool lower_bounds_specified = max_vector.size();
205 for (
const auto param_i :
index_range(params_to_tune))
207 const auto & hp = params_to_tune[param_i];
217 ::mooseError(
"The covariance parameter ", hp,
" could not be found!");
220 min = lower_bounds_specified ? min_vector[param_i] :
min;
221 max = upper_bounds_specified ? max_vector[param_i] :
max;
247 const torch::Tensor & training_data,
250 const auto options = doubleOptionsLike(training_params);
255 auto theta = torch::from_blob(theta_values.data(),
257 torch::TensorOptions().dtype(at::kDouble))
259 .to(options.device());
261 auto adam_options = torch::optim::AdamOptions(opts.
learning_rate);
262 adam_options.betas(std::make_tuple(opts.
b1, opts.
b2));
263 adam_options.eps(opts.
eps);
266 adam_options.weight_decay(0.0);
267 torch::optim::Adam optimizer({theta}, adam_options);
269 Real store_loss = 0.0;
270 std::vector<Real> grad_values;
273 const bool use_full_batch =
_batch_size ==
static_cast<unsigned int>(training_params.size(0));
276 std::vector<unsigned int> v_sequence;
279 v_sequence.resize(training_params.size(0));
280 std::iota(std::begin(v_sequence), std::end(v_sequence), 0);
283 Moose::out <<
"OPTIMIZING GP HYPER-PARAMETERS USING " 284 << (use_legacy_update ?
"legacy-compatible Adam" :
"Adam") << std::endl;
285 for (
unsigned int ss = 0; ss < opts.
num_iter; ++ss)
287 torch::Tensor inputs;
288 torch::Tensor outputs;
291 inputs = training_params;
292 outputs = training_data;
297 generator.
seed(0, 1980);
299 MooseUtils::shuffle<unsigned int>(v_sequence, generator, 0);
301 std::vector<int64_t> batch_indices_vec(v_sequence.begin(), v_sequence.begin() +
_batch_size);
302 auto batch_indices = torch::tensor(
303 batch_indices_vec, torch::TensorOptions().dtype(torch::kLong).device(options.device()));
304 inputs = torch::index_select(training_params, 0, batch_indices);
305 outputs = torch::index_select(training_data, 0, batch_indices);
308 store_loss =
getLoss(inputs, outputs);
310 Moose::out <<
"Iteration: " << ss + 1 <<
" LOSS: " << store_loss << std::endl;
313 auto grad = torch::from_blob(grad_values.data(),
315 torch::TensorOptions().dtype(at::kDouble))
317 .to(options.device());
318 optimizer.zero_grad();
319 theta.mutable_grad() =
grad;
320 torch::Tensor theta_before_step;
321 if (use_legacy_update)
322 theta_before_step = theta.detach().clone();
326 torch::NoGradGuard no_grad;
327 if (use_legacy_update)
328 theta -= opts.
lambda * theta_before_step;
331 const auto first_index = std::get<0>(iter->second);
332 const auto num_entries = std::get<1>(iter->second);
333 const auto min_value = std::get<2>(iter->second);
334 const auto max_value = std::get<3>(iter->second);
335 theta.slice(0, first_index, first_index + num_entries).clamp_(min_value, max_value);
340 const auto * theta_data = theta_export.data_ptr<
Real>();
341 theta_values.assign(theta_data, theta_data + theta_export.numel());
347 Moose::out <<
"OPTIMIZED GP HYPER-PARAMETERS:" << std::endl;
349 Moose::out <<
"FINAL LOSS: " << store_loss << std::endl;
352 if (theta_values.size() > 0)
354 unsigned int count = 1;
365 const auto flattened_data = flattenOutputData(outputs);
369 Real log_likelihood = 0;
372 log_likelihood += -2.0 * torch::sum(torch::log(torch::diagonal(
_K_cho_decomp))).item<
Real>();
373 log_likelihood -= flattened_data.size(0) * std::log(2 * M_PI);
374 log_likelihood = -log_likelihood / 2;
375 return log_likelihood;
382 doubleOptionsLike(inputs));
383 std::vector<Real> grad_vec;
387 std::string hyper_param_name = iter->first;
388 const auto first_index = std::get<0>(iter->second);
389 const auto num_entries = std::get<1>(iter->second);
390 for (
unsigned int ii = 0; ii < num_entries; ++ii)
392 const auto global_index = first_index + ii;
394 const auto quadratic_form =
397 const auto inverse_trace =
399 grad_vec[global_index] = (inverse_trace - quadratic_form) / 2.0;
407 const std::unordered_map<std::string, std::tuple<unsigned int, unsigned int, Real, Real>> &
410 std::vector<Real> & vec)
const 412 for (
auto iter : tuning_data)
414 const std::string & param_name = iter.first;
415 const auto tensor_it = hyperparam_map.find(param_name);
416 if (tensor_it == hyperparam_map.end())
417 mooseError(
"The covariance parameter ", param_name,
" could not be found!");
419 const auto values = exportHyperParameter(tensor_it->second);
420 const auto num_entries = std::get<1>(iter.second);
421 mooseAssert(values.size() == num_entries,
422 "Hyperparameter size does not match tuning metadata.");
423 for (
unsigned int ii = 0; ii < num_entries; ++ii)
424 vec[std::get<0>(iter.second) + ii] = values[ii];
430 const std::unordered_map<std::string, std::tuple<unsigned int, unsigned int, Real, Real>> &
433 const std::vector<Real> & vec)
const 435 for (
auto iter : tuning_data)
437 const std::string & param_name = iter.first;
438 const auto tensor_it = hyperparam_map.find(param_name);
439 if (tensor_it == hyperparam_map.end())
440 mooseError(
"The covariance parameter ", param_name,
" could not be found!");
442 const auto first_index = std::get<0>(iter.second);
443 const auto num_entries = std::get<1>(iter.second);
444 std::vector<Real> values(num_entries);
445 for (
unsigned int ii = 0; ii < num_entries; ++ii)
446 values[ii] = vec[first_index + ii];
448 updateHyperParameter(tensor_it->second, values, param_name);
void dataLoad(std::istream &stream, StochasticTools::GaussianProcess &gp_utils, void *context)
static bool isVectorHyperParameter(const torch::Tensor &tensor)
Return true if a hyperparameter tensor stores a vector of values.
void mooseError(Args &&... args)
void seed(std::size_t i, unsigned int seed)
torch::Tensor toCPUContiguous(const torch::Tensor &tensor)
static bool isScalarHyperParameter(const torch::Tensor &tensor)
Return true if a hyperparameter tensor stores one scalar value.
Base class for covariance functions that are used in Gaussian Processes.
void buildHyperParamMap(HyperParameterMap &map) const
Populates the input maps with the owned hyperparameters.
auto max(const L &left, const R &right)
const std::string & name() const
const std::string & type() const
const std::vector< UserObjectName > & dependentCovarianceNames() const
Get the names of the dependent covariances.
void loadHyperParamMap(const HyperParameterMap &map)
Load some hyperparameters into the local map contained in this object.
std::string stringify(const T &t)
void dataStore(std::ostream &stream, StochasticTools::GaussianProcess &gp_utils, void *context)
std::string grad(const std::string &var)
void dependentCovarianceTypes(std::map< UserObjectName, std::string > &name_type_map) const
Populate a map with the names and types of the dependent covariance functions.
virtual bool computedKdhyper(torch::Tensor &dKdhp, const torch::Tensor &x, const std::string &hyper_param_name, unsigned int ind) const
Redirect dK/dhp for hyperparameter "hp".
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
unsigned int numOutputs() const
Return the number of outputs assumed for this covariance function.
auto min(const L &left, const R &right)
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)
virtual bool isTunable(const std::string &name) const
Check if a given parameter is tunable.
virtual void computeCovarianceMatrix(torch::Tensor &K, const torch::Tensor &x, const torch::Tensor &xp, const bool is_self_covariance) const =0
Generates the Covariance Matrix given two sets of points in the parameter space.