https://mooseframework.inl.gov
LibtorchArtificialNeuralNet.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 #ifdef LIBTORCH_ENABLED
11 
13 #include "MooseError.h"
14 
15 namespace Moose
16 {
17 
19  const std::string name,
20  const unsigned int num_inputs,
21  const unsigned int num_outputs,
22  const std::vector<unsigned int> & num_neurons_per_layer,
23  const std::vector<std::string> & activation_function,
24  const torch::DeviceType device_type,
25  const torch::ScalarType data_type)
26  : _name(name),
27  _num_inputs(num_inputs),
28  _num_outputs(num_outputs),
29  _num_neurons_per_layer(num_neurons_per_layer),
30  _activation_function(MultiMooseEnum("relu sigmoid elu gelu linear", "relu")),
31  _device_type(device_type),
32  _data_type(data_type)
33 {
34  _activation_function = activation_function;
35 
36  // Check if the number of activation functions matches the number of hidden layers
37  if ((_activation_function.size() != 1) &&
39  mooseError("The number of activation functions should be either one or the same as the number "
40  "of hidden layers");
42 }
43 
46  : torch::nn::Module(),
47  _name(nn.name()),
48  _num_inputs(nn.numInputs()),
49  _num_outputs(nn.numOutputs()),
50  _num_neurons_per_layer(nn.numNeuronsPerLayer()),
51  _activation_function(nn.activationFunctions()),
52  _device_type(nn.deviceType()),
53  _data_type(nn.dataType())
54 {
55 
56  // We construct the NN architecture
58  // We fill it up with the current parameter values
59  const auto & from_params = nn.named_parameters();
60  auto to_params = this->named_parameters();
61  for (unsigned int param_i : make_range(from_params.size()))
62  to_params[param_i].value().data() = from_params[param_i].value().data().clone();
63 }
64 
65 void
67 {
68  // Adding hidden layers
69  unsigned int inp_neurons = _num_inputs;
70  for (unsigned int i = 0; i < numHiddenLayers(); ++i)
71  {
72  std::unordered_map<std::string, unsigned int> parameters = {
73  {"inp_neurons", inp_neurons}, {"out_neurons", _num_neurons_per_layer[i]}};
74  addLayer("hidden_layer_" + std::to_string(i + 1), parameters);
75 
76  // Necessary to retain double precision (and error-free runs)
78  inp_neurons = _num_neurons_per_layer[i];
79  }
80  // Adding output layer
81  std::unordered_map<std::string, unsigned int> parameters = {{"inp_neurons", inp_neurons},
82  {"out_neurons", _num_outputs}};
83  addLayer("output_layer_", parameters);
84  _weights.back()->to(_device_type, _data_type);
85 }
86 
87 torch::Tensor
88 LibtorchArtificialNeuralNet::forward(const torch::Tensor & x)
89 {
90  torch::Tensor output(x);
91  if (_data_type != output.scalar_type())
92  output.to(_data_type);
93  if (_device_type != output.device().type())
94  output.to(_device_type);
95 
96  for (unsigned int i = 0; i < _weights.size() - 1; ++i)
97  {
98  std::string activation =
100  if (activation == "relu")
101  output = torch::relu(_weights[i]->forward(output));
102  else if (activation == "sigmoid")
103  output = torch::sigmoid(_weights[i]->forward(output));
104  else if (activation == "elu")
105  output = torch::elu(_weights[i]->forward(output));
106  else if (activation == "gelu")
107  output = torch::gelu(_weights[i]->forward(output));
108  else if (activation == "linear")
109  output = _weights[i]->forward(output);
110  }
111 
112  output = _weights[_weights.size() - 1]->forward(output);
113 
114  return output;
115 }
116 
117 void
119  const std::string & layer_name,
120  const std::unordered_map<std::string, unsigned int> & parameters)
121 {
122  auto it = parameters.find("inp_neurons");
123  if (it == parameters.end())
124  ::mooseError("Number of input neurons not found during the construction of "
125  "LibtorchArtificialNeuralNet!");
126  unsigned int inp_neurons = it->second;
127 
128  it = parameters.find("out_neurons");
129  if (it == parameters.end())
130  ::mooseError("Number of output neurons not found during the construction of "
131  "LibtorchArtificialNeuralNet!");
132  unsigned int out_neurons = it->second;
133 
134  _weights.push_back(register_module(layer_name, torch::nn::Linear(inp_neurons, out_neurons)));
135 }
136 
137 void
138 LibtorchArtificialNeuralNet::store(nlohmann::json & json) const
139 {
140  const auto & named_params = this->named_parameters();
141  for (const auto & param_i : make_range(named_params.size()))
142  {
143  // We cast the parameters into a 1D vector
144  json[named_params[param_i].key()] = std::vector<Real>(
145  named_params[param_i].value().data_ptr<Real>(),
146  named_params[param_i].value().data_ptr<Real>() + named_params[param_i].value().numel());
147  }
148 }
149 
150 void
151 to_json(nlohmann::json & json, const Moose::LibtorchArtificialNeuralNet * const & network)
152 {
153  if (network)
154  network->store(json);
155 }
156 
157 }
158 
159 template <>
160 void
161 dataStore<Moose::LibtorchArtificialNeuralNet>(
162  std::ostream & stream, std::shared_ptr<Moose::LibtorchArtificialNeuralNet> & nn, void * context)
163 {
164  std::string n(nn->name());
165  dataStore(stream, n, context);
166 
167  unsigned int ni(nn->numInputs());
168  dataStore(stream, ni, context);
169 
170  unsigned int no(nn->numOutputs());
171  dataStore(stream, no, context);
172 
173  unsigned int nhl(nn->numHiddenLayers());
174  dataStore(stream, nhl, context);
175 
176  std::vector<unsigned int> nnpl(nn->numNeuronsPerLayer());
177  dataStore(stream, nnpl, context);
178 
179  unsigned int afs(nn->activationFunctions().size());
180  dataStore(stream, afs, context);
181 
182  std::vector<std::string> items(afs);
183  for (unsigned int i = 0; i < afs; ++i)
184  items[i] = nn->activationFunctions()[i];
185 
186  dataStore(stream, items, context);
187 
188  auto device_type = static_cast<std::underlying_type<torch::DeviceType>::type>(nn->deviceType());
189  dataStore(stream, device_type, context);
190 
191  auto data_type = static_cast<std::underlying_type<torch::ScalarType>::type>(nn->dataType());
192  dataStore(stream, data_type, context);
193 
194  torch::save(nn, nn->name());
195 }
196 
197 template <>
198 void
199 dataLoad<Moose::LibtorchArtificialNeuralNet>(
200  std::istream & stream, std::shared_ptr<Moose::LibtorchArtificialNeuralNet> & nn, void * context)
201 {
202  std::string name;
203  dataLoad(stream, name, context);
204 
205  unsigned int num_inputs;
206  dataLoad(stream, num_inputs, context);
207 
208  unsigned int num_outputs;
209  dataLoad(stream, num_outputs, context);
210 
211  unsigned int num_hidden_layers;
212  dataLoad(stream, num_hidden_layers, context);
213 
214  std::vector<unsigned int> num_neurons_per_layer;
215  num_neurons_per_layer.resize(num_hidden_layers);
216  dataLoad(stream, num_neurons_per_layer, context);
217 
218  unsigned int num_activation_items;
219  dataLoad(stream, num_activation_items, context);
220 
221  std::vector<std::string> activation_functions;
222  activation_functions.resize(num_activation_items);
223  dataLoad(stream, activation_functions, context);
224 
225  std::underlying_type<torch::DeviceType>::type device_type;
226  dataLoad(stream, device_type, context);
227  const torch::DeviceType divt(static_cast<torch::DeviceType>(device_type));
228 
229  std::underlying_type<torch::ScalarType>::type data_type;
230  dataLoad(stream, data_type, context);
231  const torch::ScalarType datt(static_cast<torch::ScalarType>(data_type));
232 
233  nn = std::make_shared<Moose::LibtorchArtificialNeuralNet>(
234  name, num_inputs, num_outputs, num_neurons_per_layer, activation_functions, divt, datt);
235 
236  torch::load(nn, name);
237 }
238 
239 template <>
240 void
241 dataStore<Moose::LibtorchArtificialNeuralNet const>(
242  std::ostream & /*stream*/,
243  Moose::LibtorchArtificialNeuralNet const *& /*nn*/,
244  void * /*context*/)
245 {
246 }
247 
248 template <>
249 void
250 dataLoad<Moose::LibtorchArtificialNeuralNet const>(
251  std::istream & /*stream*/,
252  Moose::LibtorchArtificialNeuralNet const *& /*nn*/,
253  void * /*context*/)
254 {
255 }
256 
257 #endif
std::string name(const ElemQuality q)
MPI_Datatype data_type
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:302
unsigned int size() const
Return the number of active items in the MultiMooseEnum.
void dataLoad(std::istream &stream, PenetrationInfo *&pinfo, void *context)
MultiMooseEnum _activation_function
Activation functions (either one for all hidden layers or one for every layer separately) ...
const torch::DeviceType _device_type
The device type used for this neural network.
unsigned int numHiddenLayers() const
Return the number of hidden layers.
const std::vector< unsigned int > _num_neurons_per_layer
Hidden layer architecture.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
std::vector< torch::nn::Linear > _weights
Submodules that hold linear operations and the corresponding weights and biases (y = W * x + b) ...
void constructNeuralNetwork()
Construct the neural network.
virtual void addLayer(const std::string &layer_name, const std::unordered_map< std::string, unsigned int > &parameters)
Add layers to the neural network.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
void store(nlohmann::json &json) const
Store the network architecture in a json file (for debugging, visualization)
void to_json(nlohmann::json &json, const Moose::LibtorchArtificialNeuralNet *const &network)
const unsigned int _num_outputs
Number of neurons on the output layer.
const torch::ScalarType _data_type
The data type used in this neural network.
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type...
void dataStore(std::ostream &stream, PenetrationInfo *&pinfo, void *context)
LibtorchArtificialNeuralNet(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 torch::DeviceType device_type=torch::kCPU, const torch::ScalarType scalar_type=torch::kDouble)
Construct using input parameters.
virtual torch::Tensor forward(const torch::Tensor &x) override
Overriding the forward substitution function for the neural network, unfortunately this cannot be con...