https://mooseframework.inl.gov
Loading...
Searching...
No Matches
SurrogateTrainer.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 "SurrogateTrainer.h"
11#include "SurrogateModel.h"
12#include "Sampler.h"
13#include "StochasticToolsApp.h"
14#include "MooseRandom.h"
15#include "Shuffle.h"
16#include <algorithm>
17
20{
23 params.registerBase("SurrogateTrainer");
24 return params;
25}
26
28 : GeneralUserObject(parameters),
29 RestartableModelInterface(*this, /*read_only=*/false, _type + "_" + name())
30{
31}
32
35{
37 params.addRequiredParam<SamplerName>("sampler",
38 "Sampler used to create predictor and response data.");
39 params.addParam<ReporterName>(
40 "converged_reporter",
41 "Reporter value used to determine if a sample's multiapp solve converged.");
42 params.addParam<bool>("skip_unconverged_samples",
43 false,
44 "True to skip samples where the multiapp did not converge, "
45 "'stochastic_reporter' is required to do this.");
46
47 // Common Training Data
48 MooseEnum data_type("real=0 vector_real=1", "real");
50 "response",
51 "Reporter value of response results, can be vpp with <vpp_name>/<vector_name> or sampler "
52 "column with 'sampler/col_<index>'.");
53 params.addParam<MooseEnum>("response_type", data_type, "Response data type.");
54 params.addParam<std::vector<ReporterName>>(
55 "predictors",
56 std::vector<ReporterName>(),
57 "Reporter values used as the independent random variables, If 'predictors' and "
58 "'predictor_cols' are both empty, all sampler columns are used.");
59 params.addParam<std::vector<unsigned int>>(
60 "predictor_cols",
61 std::vector<unsigned int>(),
62 "Sampler columns used as the independent random variables, If 'predictors' and "
63 "'predictor_cols' are both empty, all sampler columns are used.");
64 // End Common Training Data
65
66 MooseEnum cv_type("none=0 k_fold=1", "none");
67 params.addParam<MooseEnum>(
68 "cv_type",
69 cv_type,
70 "Cross-validation method to use for dataset. Options are 'none' or 'k_fold'.");
71 params.addRangeCheckedParam<unsigned int>(
72 "cv_splits", 10, "cv_splits > 1", "Number of splits (k) to use in k-fold cross-validation.");
73 params.addParam<UserObjectName>("cv_surrogate",
74 "Name of Surrogate object used for model cross-validation.");
75 params.addParam<unsigned int>(
76 "cv_n_trials", 1, "Number of repeated trials of cross-validation to perform.");
77 params.addParam<unsigned int>("cv_seed",
78 std::numeric_limits<unsigned int>::max(),
79 "Seed used to initialize random number generator for data "
80 "splitting during cross validation.");
81
82 return params;
83}
84
86 : SurrogateTrainerBase(parameters),
88 _sampler(getSampler("sampler")),
89 _rval(nullptr),
90 _rvecval(nullptr),
91 _pvals(getParam<std::vector<ReporterName>>("predictors").size()),
92 _pcols(getParam<std::vector<unsigned int>>("predictor_cols")),
93 _n_outputs(declareModelData<unsigned int>("_n_outputs", 1)),
94 _row_data(_sampler.getNumberOfCols()),
95 _skip_unconverged(getParam<bool>("skip_unconverged_samples")),
96 _converged(nullptr),
97 _cv_type(getParam<MooseEnum>("cv_type")),
98 _n_splits(getParam<unsigned int>("cv_splits")),
99 _cv_n_trials(getParam<unsigned int>("cv_n_trials")),
100 _cv_seed(getParam<unsigned int>("cv_seed")),
101 _doing_cv(_cv_type != "none"),
102 _cv_trial_scores(declareModelData<std::vector<std::vector<Real>>>("cv_scores"))
103{
105 {
106 if (!isParamValid("converged_reporter"))
107 paramError("skip_unconverged_samples",
108 "'converged_reporter' needs to be specified to skip unconverged sample.");
109 _converged = &getTrainingData<bool>(getParam<ReporterName>("converged_reporter"));
110 }
111
112 if (_doing_cv)
113 {
114 if (!isParamValid("cv_surrogate"))
115 paramError("cv_type",
116 "To perform cross-validation, the option cv_surrogate needs to be specified",
117 " to provide a Surrogate object for training and evaluation.");
118
120 paramError("cv_splits",
121 "The specified number of splits (cv_splits = ",
122 _n_splits,
123 ")",
124 " exceeds the number of rows in Sampler '",
125 getParam<SamplerName>("sampler"),
126 "'");
127
129 }
130
131 // Get TrainingData for responses and predictors
132 if (getParam<MooseEnum>("response_type") == 0)
133 _rval = &getTrainingData<Real>(getParam<ReporterName>("response"));
134 else if (getParam<MooseEnum>("response_type") == 1)
135 _rvecval = &getTrainingData<std::vector<Real>>(getParam<ReporterName>("response"));
136
137 const auto & pnames = getParam<std::vector<ReporterName>>("predictors");
138 for (unsigned int i = 0; i < pnames.size(); ++i)
139 _pvals[i] = &getTrainingData<Real>(pnames[i]);
140
141 // If predictors and predictor_cols are empty, use all sampler columns
142 if (_pvals.empty() && _pcols.empty())
143 {
145 std::iota(_pcols.begin(), _pcols.end(), 0);
146 }
147 _n_dims = _pvals.size() + _pcols.size();
148
149 _predictor_data.resize(_n_dims);
150}
151
152void
154{
155 // Figure out if data is distributed
156 for (auto & pair : _training_data)
157 {
158 const ReporterName & name = pair.first;
159 TrainingDataBase & data = *pair.second;
160
161 const auto & mode = _fe_problem.getReporterData().getReporterMode(name);
162 if (mode == REPORTER_MODE_DISTRIBUTED || (mode == REPORTER_MODE_ROOT && processor_id() != 0))
163 data.isDistributed() = true;
164 else if (mode == REPORTER_MODE_REPLICATED ||
165 (mode == REPORTER_MODE_ROOT && processor_id() == 0))
166 data.isDistributed() = false;
167 else
168 mooseError("Predictor reporter value ", name, " is not of supported mode.");
169 }
170
171 if (_doing_cv)
172 _cv_surrogate = &getSurrogateModel("cv_surrogate");
173}
174
175void
177{
178 if (_doing_cv)
179 for (const auto & trial : make_range(_cv_n_trials))
180 {
181 std::vector<Real> trial_score = crossValidate();
182
183 // Expand _cv_trial_scores with more columns if necessary, then insert values.
184 for (unsigned int r = _cv_trial_scores.size(); r < trial_score.size(); ++r)
185 _cv_trial_scores.push_back(std::vector<Real>(_cv_n_trials, 0.0));
186 for (auto r : make_range(trial_score.size()))
187 _cv_trial_scores[r][trial] = trial_score[r];
188 }
189
193}
194
195void
197{
198 // Check that the number of sampler columns hasn't changed
199 if (_row_data.size() != _sampler.getNumberOfCols())
200 mooseError("Number of sampler columns has changed.");
201
202 // Check that training data is correctly sized
203 for (auto & pair : _training_data)
204 {
205 dof_id_type rsize = pair.second->size();
206 dof_id_type nrow =
207 pair.second->isDistributed() ? _sampler.getNumberOfLocalRows() : _sampler.getNumberOfRows();
208 if (rsize != nrow)
209 mooseError("Reporter value ",
210 pair.first,
211 " of size ",
212 rsize,
213 " does not match sampler size (",
214 nrow,
215 ").");
216 }
217}
218
219void
221{
224 _local_row = 0;
225
226 preTrain();
227
229 {
230 // Need to do this manually in order to keep the iterators valid
231 const std::vector<Real> data = _sampler.getNextLocalRow();
232 for (unsigned int i = 0; i < _row_data.size(); ++i)
233 _row_data[i] = data[i];
234
235 // Set training data
236 for (auto & pair : _training_data)
237 pair.second->setCurrentIndex((pair.second->isDistributed() ? _local_row : _row));
238
240
241 if ((!_skip_unconverged || *_converged) &&
242 std::find(_skip_indices.begin(), _skip_indices.end(), _row) == _skip_indices.end())
243 train();
244
245 _local_row++;
246 }
247
248 postTrain();
249}
250
251std::vector<Real>
253{
254 std::vector<Real> cv_score(1, 0.0);
255
256 // Get skipped indices for each split
257 dof_id_type n_rows = _sampler.getNumberOfRows();
258 std::vector<std::vector<dof_id_type>> split_indices;
259 if (processor_id() == 0)
260 {
261 std::vector<dof_id_type> indices_flat(n_rows);
262 std::iota(indices_flat.begin(), indices_flat.end(), 0);
263 MooseUtils::shuffle(indices_flat, _cv_generator, 0);
264
265 split_indices.resize(_n_splits);
266 for (const auto & k : make_range(_n_splits))
267 {
268 const dof_id_type num_ind = n_rows / _n_splits + (k < (n_rows % _n_splits) ? 1 : 0);
269 split_indices[k].insert(split_indices[k].begin(),
270 std::make_move_iterator(indices_flat.begin()),
271 std::make_move_iterator(indices_flat.begin() + num_ind));
272 std::sort(split_indices[k].begin(), split_indices[k].end());
273 indices_flat.erase(indices_flat.begin(), indices_flat.begin() + num_ind);
274 }
275 }
276
277 std::vector<dof_id_type> split_ids_buffer;
278 for (const auto & k : make_range(_n_splits))
279 {
280 if (processor_id() == 0)
281 split_ids_buffer = split_indices[k];
282 _communicator.broadcast(split_ids_buffer, 0);
283
284 _current_sample_size = _sampler.getNumberOfRows() - split_ids_buffer.size();
285
286 auto first = std::lower_bound(
287 split_ids_buffer.begin(), split_ids_buffer.end(), _sampler.getLocalRowBegin());
288 auto last = std::upper_bound(
289 split_ids_buffer.begin(), split_ids_buffer.end(), _sampler.getLocalRowEnd());
290 _skip_indices.insert(_skip_indices.begin(), first, last);
291
293
294 // Train the model
296
297 // Evaluate the model
298 std::vector<Real> split_mse(1, 0.0);
299 std::vector<Real> row_mse(1, 0.0);
300
301 auto skipped_row = _skip_indices.begin();
302
303 for (dof_id_type p = _sampler.getLocalRowBegin(); p < _sampler.getLocalRowEnd(); ++p)
304 {
305 const std::vector<Real> row = _sampler.getNextLocalRow();
306 if (skipped_row != _skip_indices.end() && p == *skipped_row)
307 {
308 for (unsigned int i = 0; i < _row_data.size(); ++i)
309 _row_data[i] = row[i];
310
311 for (auto & pair : _training_data)
312 pair.second->setCurrentIndex(
313 (pair.second->isDistributed() ? p - _sampler.getLocalRowBegin() : p));
314
316
318
319 // Expand split_mse if needed.
320 split_mse.resize(row_mse.size(), 0.0);
321
322 // Increment errors
323 for (unsigned int r = 0; r < split_mse.size(); ++r)
324 split_mse[r] += row_mse[r];
325
326 skipped_row++;
327 }
328 }
329 gatherSum(split_mse);
330
331 // Expand cv_score if necessary.
332 cv_score.resize(split_mse.size(), 0.0);
333
334 for (auto r : make_range(split_mse.size()))
335 cv_score[r] += split_mse[r] / n_rows;
336
337 _skip_indices.clear();
338 }
339
340 for (auto r : make_range(cv_score.size()))
341 cv_score[r] = std::sqrt(cv_score[r]);
342
343 return cv_score;
344}
345
346std::vector<Real>
348{
349 std::vector<Real> error(1, 0.0);
350
351 if (_rval)
352 {
353 Real model_eval = surr.evaluate(_predictor_data);
354 error[0] = MathUtils::pow(model_eval - (*_rval), 2);
355 }
356 else if (_rvecval)
357 {
358 error.resize(_rvecval->size());
359
360 // Evaluate for vector response.
361 std::vector<Real> model_eval(error.size());
362 surr.evaluate(_predictor_data, model_eval);
363 for (auto r : make_range(_rvecval->size()))
364 error[r] = MathUtils::pow(model_eval[r] - (*_rvecval)[r], 2);
365 }
366
367 return error;
368}
369
370void
372{
373 unsigned int d = 0;
374 for (const auto & val : _pvals)
375 _predictor_data[d++] = *val;
376 for (const auto & col : _pcols)
377 _predictor_data[d++] = _row_data[col];
378}
const Real p
const ReporterMode REPORTER_MODE_REPLICATED
const ReporterMode REPORTER_MODE_DISTRIBUTED
const ReporterMode REPORTER_MODE_ROOT
const std::string name
Definition Setup.h:21
void ErrorVector unsigned int
const ReporterData & getReporterData() const
static InputParameters validParams()
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void registerBase(const std::string &value)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const std::string & name() const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
bool isParamValid(const std::string &name) const
void seed(std::size_t i, unsigned int seed)
const ReporterProducerEnum & getReporterMode(const ReporterName &reporter_name) const
An interface class which manages the model data save and load functionalities from moose objects (suc...
static InputParameters validParams()
std::vector< Real > getNextLocalRow()
dof_id_type getNumberOfLocalRows() const
dof_id_type getLocalRowEnd() const
dof_id_type getLocalRowBegin() const
dof_id_type getNumberOfRows() const
dof_id_type getNumberOfCols() const
Interface for objects that need to use samplers.
T & getSurrogateModel(const std::string &name) const
Get a SurrogateModel/Trainer with a given name.
virtual Real evaluate(const std::vector< Real > &x) const
Evaluate surrogate model given a row of parameters.
This is the base trainer class whose main functionality is the API for declaring model data.
SurrogateTrainerBase(const InputParameters &parameters)
static InputParameters validParams()
std::vector< Real > crossValidate()
const bool _doing_cv
Set to true if cross validation is being performed, controls behavior in execute().
dof_id_type _local_row
During training loop, this is the local row index of the data.
virtual void train()
std::unordered_map< ReporterName, std::shared_ptr< TrainingDataBase > > _training_data
Vector of reporter names and their corresponding values (to be filled by getTrainingData)
virtual std::vector< Real > evaluateModelError(const SurrogateModel &surr)
virtual void preTrain()
const std::vector< Real > * _rvecval
Vector response value.
const unsigned int & _cv_seed
Seed used for _cv_generator.
std::vector< Real > _row_data
Sampler data for the current row.
void checkIntegrity() const
const Real * _rval
Response value.
dof_id_type _row
During training loop, this is the row index of the data.
virtual void execute() final
std::vector< Real > _predictor_data
Predictor data for current row - can be combination of Sampler and Reporter values.
MooseRandom _cv_generator
Random number generator used for shuffling sampler rows during splitting.
const SurrogateModel * _cv_surrogate
SurrogateModel used to evaluate model error relative to test points.
std::vector< dof_id_type > _skip_indices
std::vector< const Real * > _pvals
Predictor values from reporters.
unsigned int _local_sample_size
Number of samples (locally) used to train the model.
static InputParameters validParams()
const unsigned int & _n_splits
Number of splits (k) to split sampler data into.
std::vector< std::vector< Real > > & _cv_trial_scores
RMSE scores from each CV trial - can be grabbed by VPP or Reporter.
unsigned int _n_dims
Dimension of predictor data - either _sampler.getNumberOfCols() or _pvals.size() + _pcols....
std::vector< unsigned int > _pcols
Columns from sampler for predictors.
const unsigned int & _cv_n_trials
Number of repeated trials of cross validation to perform.
unsigned int _current_sample_size
Number of samples used to train the model.
SurrogateTrainer(const InputParameters &parameters)
virtual void initialize() final
const bool _skip_unconverged
Whether or not we are skipping samples that have unconverged solutions.
const bool * _converged
Whether or not the current sample has a converged solution.
virtual void postTrain()
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
void gatherSum(T &value)
FEProblemBase & _fe_problem
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
T pow(T x, int e)
void shuffle(std::vector< T > &data, MooseRandom &generator, const std::size_t seed_index=0)