From 0effd03303ca1fee96cb153856883206720d7af7 Mon Sep 17 00:00:00 2001 From: Dario Izzo Date: Tue, 31 Oct 2023 16:28:05 +0100 Subject: [PATCH] WIP --- CMakeLists.txt | 1 + include/heyoka/model/ffnn.hpp | 1 + src/model/ffnn.cpp | 55 ++++++++++++++++++++++++++++++----- test/CMakeLists.txt | 1 + 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56e649501..352d0a478 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,6 +261,7 @@ set(HEYOKA_SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/model/mascon.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/model/vsop2013.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/model/cr3bp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/model/ffnn.cpp" # Math functions. "${CMAKE_CURRENT_SOURCE_DIR}/src/math/kepE.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/math/kepF.cpp" diff --git a/include/heyoka/model/ffnn.hpp b/include/heyoka/model/ffnn.hpp index 0c2cc38da..e1c0b456a 100644 --- a/include/heyoka/model/ffnn.hpp +++ b/include/heyoka/model/ffnn.hpp @@ -28,6 +28,7 @@ namespace model // The expression will contain the weights and biases of the neural network flattened into `pars` with the following conventions: // // from the left to right layer of parameters: [flattened weights1, flattened weights2, ... , biases1, bises2, ...] +// HEYOKA_DLL_PUBLIC std::vector ffnn_impl(const std::vector &, std::uint32_t, const std::vector &, const std::vector> &, diff --git a/src/model/ffnn.cpp b/src/model/ffnn.cpp index 261cf08e6..4ef1c60da 100644 --- a/src/model/ffnn.cpp +++ b/src/model/ffnn.cpp @@ -6,9 +6,14 @@ // Public License v. 2.0. If a copy of the MPL was not distributed // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +#include #include +#include #include +#include +#include + #include #include #include @@ -65,8 +70,13 @@ std::vector compute_layer(std::uint32_t layer_id, const std::vector< auto n_neurons_curr_layer = n_neurons[layer_id]; std::vector retval(n_neurons_curr_layer, 0_dbl); + fmt::print("nneurons: {}", n_neurons_prev_layer); + std::cout << std::endl; + for (std::uint32_t i = 0u; i < n_neurons_curr_layer; ++i) { for (std::uint32_t j = 0u; j < n_neurons_prev_layer; ++j) { + fmt::print("layer, i, j: {}, {}, {}", layer_id, i, j); + std::cout << std::endl; retval[i] += net_wb[flattenw(i, j, n_neurons, layer_id)] * inputs[j]; } retval[i] += net_wb[flattenb(i, n_neurons, layer_id, n_net_w)]; @@ -76,27 +86,46 @@ std::vector compute_layer(std::uint32_t layer_id, const std::vector< } } // namespace detail -HEYOKA_DLL_PUBLIC std::vector ffnn_impl( +std::vector ffnn_impl( // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) const std::vector &in, std::uint32_t n_out, const std::vector &n_neurons_per_hidden_layer, const std::vector> &activations, const std::vector &net_wb) { - // Sanity check (should be a throw check?) - assert(n_neurons_per_hidden_layer.size() + 1 == activations.size()); + // Sanity checks + if (n_neurons_per_hidden_layer.size() + 1 != activations.size()) { + throw std::invalid_argument(fmt::format( + "The number of hidden layers, as detected from the inputs, was {}, while" + "the number of activation function supplied was {}. A FFNN needs exactly one more activation function " + "than the number of hidden layers.", + n_neurons_per_hidden_layer.size(), activations.size())); + } + if (in.empty()) { + throw std::invalid_argument("The inputs provided to the ffnn seem to be an empty vector."); + } + if (n_out == 0) { + throw std::invalid_argument("The number of network outputs cannot be zero."); + } + if (!std::all_of(n_neurons_per_hidden_layer.begin(), n_neurons_per_hidden_layer.end(), + [](std::uint32_t item) { return item > 0; })) { + throw std::invalid_argument("The number of neurons for each hidden layer must be greater than zero!"); + } + if (n_neurons_per_hidden_layer.empty()) { // TODO(darioizzo): maybe this is actually a wanted corner case, remove? + throw std::invalid_argument("The number of hidden layers cannot be zero."); + } // Number of hidden layers (defined as all neuronal columns that are nor input nor output neurons) auto n_hidden_layers = boost::numeric_cast(n_neurons_per_hidden_layer.size()); // Number of neuronal layers (counting input and output) auto n_layers = n_hidden_layers + 2; - // Number o + // Number of inputs auto n_in = boost::numeric_cast(in.size()); // Number of neurons per neuronal layer std::vector n_neurons = n_neurons_per_hidden_layer; n_neurons.insert(n_neurons.begin(), n_in); n_neurons.insert(n_neurons.end(), n_out); - // Number of network parameters + // Number of network parameters (wb: weights and biases, w: only weights) std::uint32_t n_net_wb = 0u; std::uint32_t n_net_w = 0u; for (std::uint32_t i = 1u; i < n_layers; ++i) { @@ -104,10 +133,20 @@ HEYOKA_DLL_PUBLIC std::vector ffnn_impl( n_net_w += n_neurons[i - 1] * n_neurons[i]; n_net_wb += n_neurons[i]; } - // Sanity check (should be a throw check?) - assert(net_wb.size() == n_net_wb); - std::vector retval{}; + // Sanity check + if (net_wb.size() != n_net_wb) { + throw std::invalid_argument(fmt::format( + "The number of network parameters, detected from its structure to be {}, does not match the size of" + "the corresponding expressions {} ", + n_net_wb, net_wb.size())); + } + + // Now we build the expressions recursively going from layer to layer (L = f(Wx+b))) + + std::vector retval = in; for (std::uint32_t i = 1u; i < n_layers; ++i) { + fmt::print("{},{}", i, n_neurons[i]); + std::cout << std::endl; retval = detail::compute_layer(i, retval, n_neurons, activations[i], net_wb, n_net_w); } return retval; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7463f3ab1..2a3874e3e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -140,6 +140,7 @@ ADD_HEYOKA_TESTCASE(model_fixed_centres) ADD_HEYOKA_TESTCASE(model_rotating) ADD_HEYOKA_TESTCASE(model_mascon) ADD_HEYOKA_TESTCASE(model_cr3bp) +ADD_HEYOKA_TESTCASE(model_ffnn) ADD_HEYOKA_TESTCASE(step_callback) ADD_HEYOKA_TESTCASE(llvm_state_mem_cache)