https://mooseframework.inl.gov
Loading...
Searching...
No Matches
LibtorchActorNeuralNet.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://www.mooseframework.org
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#ifdef MOOSE_LIBTORCH_ENABLED
11
13#include "LibtorchRandomUtils.h"
14#include "MooseError.h"
15#include "libmesh/utility.h"
16
17namespace
18{
19
20template <typename NamedTensorList>
21auto
22captureTensorShapes(const NamedTensorList & tensors)
23{
24 std::vector<std::pair<std::string, std::vector<int64_t>>> shapes;
25 shapes.reserve(tensors.size());
26
27 for (const auto & tensor : tensors)
28 shapes.emplace_back(
29 tensor.key(),
30 std::vector<int64_t>(tensor.value().sizes().begin(), tensor.value().sizes().end()));
31
32 return shapes;
33}
34
35template <typename NamedTensorList>
36void
37verifyTensorShapes(const NamedTensorList & tensors,
38 const std::vector<std::pair<std::string, std::vector<int64_t>>> & expected,
39 const char * tensor_kind)
40{
41 if (tensors.size() != expected.size())
42 mooseError("The loaded DRL actor ", tensor_kind, " count does not match the generated schema.");
43
44 for (const auto tensor_i : make_range(tensors.size()))
45 {
46 const auto actual_shape = std::vector<int64_t>(tensors[tensor_i].value().sizes().begin(),
47 tensors[tensor_i].value().sizes().end());
48
49 if (tensors[tensor_i].key() != expected[tensor_i].first ||
50 actual_shape != expected[tensor_i].second)
51 mooseError("The loaded DRL actor ",
52 tensor_kind,
53 " '",
54 tensors[tensor_i].key(),
55 "' does not match the generated schema.");
56 }
57}
58
59} // namespace
60
61namespace Moose
62{
63
65 const std::string name,
66 const unsigned int num_inputs,
67 const unsigned int num_outputs,
68 const std::vector<unsigned int> & num_neurons_per_layer,
69 const std::vector<std::string> & activation_function,
70 const std::vector<Real> & minimum_values,
71 const std::vector<Real> & maximum_values,
72 const torch::DeviceType device_type,
73 const torch::ScalarType data_type,
74 const bool build_on_construct,
75 const std::vector<Real> & input_shift_factors,
76 const std::vector<Real> & input_scaling_factors,
77 const std::vector<Real> & output_scaling_factors,
78 const bool state_independent_std)
80 num_inputs,
81 num_outputs,
82 num_neurons_per_layer,
83 activation_function,
84 device_type,
85 data_type,
86 false,
87 input_shift_factors,
88 input_scaling_factors,
89 output_scaling_factors),
90 _minimum_values(minimum_values),
91 _maximum_values(maximum_values),
92 _state_independent_std(state_independent_std)
93{
94 const bool has_minimum_values = !_minimum_values.empty();
95 const bool has_maximum_values = !_maximum_values.empty();
96 if (has_minimum_values != has_maximum_values)
97 mooseError("Bounded action distributions require both minimum_values and maximum_values.");
98
99 if (has_minimum_values)
100 {
101 if (_minimum_values.size() != _num_outputs || _maximum_values.size() != _num_outputs)
102 mooseError("The number of minimum_values and maximum_values entries must match the number "
103 "of action outputs.");
104
105 for (const auto i : make_range(_minimum_values.size()))
106 if (!(_maximum_values[i] > _minimum_values[i]))
107 mooseError("maximum_values entries must be strictly greater than minimum_values entries.");
108 }
109
110 if (build_on_construct)
112}
113
115 const bool build_on_construct)
116 : LibtorchArtificialNeuralNet(dynamic_cast<const LibtorchArtificialNeuralNet &>(nn), false),
117 _minimum_values(nn.minValues()),
118 _maximum_values(nn.maxValues()),
119 _state_independent_std(nn.stateIndependentStd())
120{
121 // We construct the NN architecture
122 if (build_on_construct)
123 {
125 // We fill it up with the current parameter values
126 const auto & from_params = nn.named_parameters();
127 auto to_params = this->named_parameters();
128 for (unsigned int param_i : make_range(from_params.size()))
129 to_params[param_i].value().data() = from_params[param_i].value().data().clone();
130
131 const auto & from_buffers = nn.named_buffers();
132 auto to_buffers = this->named_buffers();
133 for (unsigned int buffer_i : make_range(from_buffers.size()))
134 to_buffers[buffer_i].value().data() = from_buffers[buffer_i].value().data().clone();
135 }
136}
137
138void
139LibtorchActorNeuralNet::initializeNeuralNetwork(const c10::optional<at::Generator> generator)
140{
141 for (unsigned int i = 0; i < numHiddenLayers(); ++i)
142 {
143 const auto & activation =
145 const Real gain = determineGain(activation);
146 Moose::orthogonalInitializeTensor(_weights[i]->weight, gain, generator);
147 torch::nn::init::zeros_(_weights[i]->bias);
148 }
149
150 _action_distribution->initialize(generator);
151}
152
153void
155{
156 // Adding hidden layers
157 unsigned int inp_neurons = _num_inputs;
158 for (unsigned int i = 0; i < numHiddenLayers(); ++i)
159 {
160 std::unordered_map<std::string, unsigned int> parameters = {
161 {"inp_neurons", inp_neurons}, {"out_neurons", _num_neurons_per_layer[i]}};
162 addLayer("hidden_layer_" + std::to_string(i + 1), parameters);
163
164 // Necessary to retain double precision (and error-free runs)
166 inp_neurons = _num_neurons_per_layer[i];
167 }
168
169 if (_minimum_values.empty() && _maximum_values.empty())
171 std::make_shared<LibtorchGaussianActionDistribution>("action_distribution",
172 inp_neurons,
176 true,
179 else
181 std::make_shared<LibtorchBetaActionDistribution>("action_distribution",
182 inp_neurons,
188 true,
190
191 // Keep the serialized module name stable so existing checkpoints continue to load.
192 register_module("action_head", _action_distribution);
193}
194
195torch::Tensor
197{
198 return _action_distribution->entropy();
199}
200
206
212
215{
216 const auto * distribution = gaussianActionDistributionPtr();
217 if (!distribution)
218 mooseError("Requested a Gaussian action distribution from a bounded actor.");
219 return *distribution;
220}
221
224{
225 auto * distribution = gaussianActionDistributionPtr();
226 if (!distribution)
227 mooseError("Requested a Gaussian action distribution from a bounded actor.");
228 return *distribution;
229}
230
236
242
245{
246 const auto * distribution = betaActionDistributionPtr();
247 if (!distribution)
248 mooseError("Requested a Beta action distribution from an unbounded actor.");
249 return *distribution;
250}
251
254{
255 auto * distribution = betaActionDistributionPtr();
256 if (!distribution)
257 mooseError("Requested a Beta action distribution from an unbounded actor.");
258 return *distribution;
259}
260
261void
263{
264 _action_distribution->reset(input);
265}
266
267torch::Tensor
268LibtorchActorNeuralNet::forward(const torch::Tensor & x)
269{
270 torch::Tensor output = preprocessInput(x);
271
272 for (unsigned int i = 0; i < _weights.size(); ++i)
273 {
274 std::string activation =
276 if (activation == "relu")
277 output = torch::relu(_weights[i]->forward(output));
278 else if (activation == "sigmoid")
279 output = torch::sigmoid(_weights[i]->forward(output));
280 else if (activation == "tanh")
281 output = torch::tanh(_weights[i]->forward(output));
282 else if (activation == "elu")
283 output = torch::elu(_weights[i]->forward(output));
284 else if (activation == "gelu")
285 output = torch::gelu(_weights[i]->forward(output));
286 else if (activation == "linear")
287 output = _weights[i]->forward(output);
288 }
289
290 return output;
291}
292
293torch::Tensor
295 const bool sampled,
296 const c10::optional<at::Generator> generator)
297{
298 torch::Tensor output = forward(x);
299
301
302 if (sampled)
303 return sample(generator);
304
305 return _action_distribution->deterministicAction();
306}
307
308torch::Tensor
309LibtorchActorNeuralNet::sample(const c10::optional<at::Generator> generator)
310{
311 return _action_distribution->sample(generator);
312}
313
314torch::Tensor
315LibtorchActorNeuralNet::logProbability(const torch::Tensor & action)
316{
317 return _action_distribution->logProbability(action);
318}
319
320void
322{
323 try
324 {
325 const auto expected_parameters = captureTensorShapes(nn.named_parameters());
326 const auto expected_buffers = captureTensorShapes(nn.named_buffers());
327
328 torch::serialize::InputArchive archive;
329 archive.load_from(filename);
330 nn.load(archive);
331
332 verifyTensorShapes(nn.named_parameters(), expected_parameters, "parameter");
333 verifyTensorShapes(nn.named_buffers(), expected_buffers, "buffer");
334
337 }
338 catch (const c10::Error & e)
339 {
340 mooseError("The requested DRL actor checkpoint could not be loaded as a native libtorch "
341 "archive. Make sure the file exists and matches the generated actor schema.\n",
342 e.msg());
343 }
344}
345
346}
347
348#endif
const std::vector< double > x
void mooseError(Args &&... args)
const std::string name
Definition Setup.h:21
void synchronizeScalingFactorsFromBuffer()
Sync cached scaling metadata from the registered buffers after loading state.
Feed-forward actor network coupled to a Gaussian or Beta action distribution.
virtual torch::Tensor sample(c10::optional< at::Generator > generator=c10::nullopt)
Sample an action from the already-reset distribution.
const std::vector< Real > _maximum_values
Upper action bounds used by bounded actor distributions.
const LibtorchGaussianActionDistribution * gaussianActionDistributionPtr() const
Return the Gaussian action distribution pointer, or nullptr for bounded actors.
virtual void constructNeuralNetwork() override
Construct the actor body and action-distribution modules.
const LibtorchGaussianActionDistribution & gaussianActionDistribution() const
Return the Gaussian action distribution reference.
virtual torch::Tensor forward(const torch::Tensor &x) override
Run a forward pass through the actor body.
virtual void initializeNeuralNetwork(c10::optional< at::Generator > generator=c10::nullopt) override
Initialize the actor body and action-distribution parameters.
const std::vector< Real > _minimum_values
Lower action bounds used by bounded actor distributions.
std::shared_ptr< LibtorchActionDistribution > _action_distribution
Action-distribution module attached to the actor output.
const LibtorchBetaActionDistribution * betaActionDistributionPtr() const
Return the Beta action distribution pointer, or nullptr for Gaussian actors.
torch::Tensor logProbability(const torch::Tensor &other)
Evaluate the log-probability of an action under the current actor state.
const LibtorchBetaActionDistribution & betaActionDistribution() const
Return the Beta action distribution reference.
virtual torch::Tensor evaluate(torch::Tensor &input, bool sampled, c10::optional< at::Generator > generator=c10::nullopt)
Evaluate the actor and either sample from it or use its deterministic action.
const bool _state_independent_std
Whether the Gaussian std ignores the current actor features.
LibtorchActorNeuralNet(const std::string name, const unsigned int num_inputs, const unsigned int num_outputs, const std::vector< unsigned int > &num_neurons_per_layer, const std::vector< std::string > &activation_function={"relu"}, const std::vector< Real > &minimum_values={}, const std::vector< Real > &maximum_values={}, const torch::DeviceType device_type=torch::kCPU, const torch::ScalarType scalar_type=torch::kDouble, const bool build_on_construct=true, const std::vector< Real > &input_shift_factors={}, const std::vector< Real > &input_scaling_factors={}, const std::vector< Real > &output_scaling_factors={}, const bool state_independent_std=true)
Build an actor network with either a Gaussian or Beta action distribution.
torch::Tensor entropy()
Compute the entropy of the current action distribution.
const LibtorchActionDistribution & actionDistribution() const
Return the active action distribution as the common base type.
void resetDistributionParams(torch::Tensor input)
Refresh the cached distribution parameters from a fresh input tensor.
Real determineGain(const std::string &activation)
const torch::ScalarType _data_type
std::vector< torch::nn::Linear > _weights
virtual void addLayer(const std::string &layer_name, const std::unordered_map< std::string, unsigned int > &parameters)
const torch::DeviceType _device_type
virtual torch::Tensor preprocessInput(const torch::Tensor &x) const
unsigned int numHiddenLayers() const
const std::vector< unsigned int > _num_neurons_per_layer
std::vector< Real > _output_scaling_factors
Beta action distribution for bounded action spaces.
Gaussian action distribution for unbounded action spaces.
unsigned int size() const
void orthogonalInitializeTensor(torch::Tensor &tensor, Real gain=1.0, c10::optional< at::Generator > generator=c10::nullopt)
void loadLibtorchActorNeuralNetState(Moose::LibtorchActorNeuralNet &nn, const std::string &filename)
Load an actor checkpoint written as a native libtorch state archive.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
IntRange< T > make_range(T beg, T end)