diff --git a/examples/input.nn.recommended b/examples/input.nn.recommended
index e53934e0f..a0030bf52 100644
--- a/examples/input.nn.recommended
+++ b/examples/input.nn.recommended
@@ -35,6 +35,7 @@ parallel_mode 0 # Training parallelization used (
jacobian_mode 1 # Jacobian computation mode (0 = Summation to single gradient, 1 = Per-task summed gradient, 2 = Full Jacobian).
update_strategy 0 # Update strategy (0 = Combined, 1 = Per-element).
selection_mode 2 # Update candidate selection mode (0 = Random, 1 = Sort, 2 = Threshold).
+decoupling_type 0 # Kalman filter decoupling type (0 = GEKF, 1 = ED-GEKF, 2 = Layer, 3 = NDEKF, 4 = FDEKF).
task_batch_size_energy 1 # Number of energy update candidates prepared per task for each update (0 = Entire training set).
task_batch_size_force 1 # Number of force update candidates prepared per task for each update (0 = Entire training set).
memorize_symfunc_results # Keep symmetry function results in memory.
diff --git a/examples/nnp-train/Cu2S_PBE/input.nn b/examples/nnp-train/Cu2S_PBE/input.nn
index b4aa9333e..23eccf6db 100644
--- a/examples/nnp-train/Cu2S_PBE/input.nn
+++ b/examples/nnp-train/Cu2S_PBE/input.nn
@@ -44,6 +44,7 @@ parallel_mode 0 # Training parallelization used (
jacobian_mode 1 # Jacobian computation mode (0 = Summation to single gradient, 1 = Per-task summed gradient, 2 = Full Jacobian).
update_strategy 0 # Update strategy (0 = Combined, 1 = Per-element).
selection_mode 2 # Update candidate selection mode (0 = Random, 1 = Sort, 2 = Threshold).
+decoupling_type 0 # Kalman filter decoupling type (0 = GEKF, 1 = ED-GEKF, 2 = Layer, 3 = NDEKF, 4 = FDEKF).
task_batch_size_energy 1 # Number of energy update candidates prepared per task for each update (0 = Entire training set).
task_batch_size_force 1 # Number of force update candidates prepared per task for each update (0 = Entire training set).
memorize_symfunc_results # Keep symmetry function results in memory.
diff --git a/examples/nnp-train/H2O_RPBE-D3/input.nn b/examples/nnp-train/H2O_RPBE-D3/input.nn
index b2666eecd..05b3fcacb 100644
--- a/examples/nnp-train/H2O_RPBE-D3/input.nn
+++ b/examples/nnp-train/H2O_RPBE-D3/input.nn
@@ -44,6 +44,7 @@ parallel_mode 0 # Training parallelization used (
jacobian_mode 1 # Jacobian computation mode (0 = Summation to single gradient, 1 = Per-task summed gradient, 2 = Full Jacobian).
update_strategy 0 # Update strategy (0 = Combined, 1 = Per-element).
selection_mode 2 # Update candidate selection mode (0 = Random, 1 = Sort, 2 = Threshold).
+decoupling_type 0 # Kalman filter decoupling type (0 = GEKF, 1 = ED-GEKF, 2 = Layer, 3 = NDEKF, 4 = FDEKF).
task_batch_size_energy 1 # Number of energy update candidates prepared per task for each update (0 = Entire training set).
task_batch_size_force 1 # Number of force update candidates prepared per task for each update (0 = Entire training set).
memorize_symfunc_results # Keep symmetry function results in memory.
diff --git a/examples/nnp-train/LJ/input.nn b/examples/nnp-train/LJ/input.nn
index d42118f4b..2a1812341 100644
--- a/examples/nnp-train/LJ/input.nn
+++ b/examples/nnp-train/LJ/input.nn
@@ -34,6 +34,7 @@ parallel_mode 0 # Training parallelization used (
jacobian_mode 1 # Jacobian computation mode (0 = Summation to single gradient, 1 = Per-task summed gradient, 2 = Full Jacobian).
update_strategy 0 # Update strategy (0 = Combined, 1 = Per-element).
selection_mode 2 # Update candidate selection mode (0 = Random, 1 = Sort, 2 = Threshold).
+decoupling_type 0 # Kalman filter decoupling type (0 = GEKF, 1 = ED-GEKF, 2 = Layer, 3 = NDEKF, 4 = FDEKF).
task_batch_size_energy 1 # Number of energy update candidates prepared per task for each update (0 = Entire training set).
task_batch_size_force 1 # Number of force update candidates prepared per task for each update (0 = Entire training set).
memorize_symfunc_results # Keep symmetry function results in memory.
diff --git a/src/application/makefile b/src/application/makefile
index c8a2b6529..29b260a37 100644
--- a/src/application/makefile
+++ b/src/application/makefile
@@ -47,7 +47,8 @@ APP_CORE=nnp-convert \
nnp-predict \
nnp-prune \
nnp-select \
- nnp-symfunc
+ nnp-symfunc \
+ nnx
# Applications which require libnnp and libnnptrain:
# (nnp-train itself is actually a special case).
@@ -55,7 +56,8 @@ APP_TRAIN=nnp-comp2 \
nnp-dataset \
nnp-norm \
nnp-scaling \
- nnp-sfclust
+ nnp-sfclust \
+ nnp-winit
# All applications together.
APP=$(APP_CORE) $(APP_TRAIN) nnp-train
@@ -72,7 +74,7 @@ CLEAN_APP=$(patsubst %, clean-%, $(APP))
all: $(APP_CORE) $(APP_TRAIN) nnp-train
# Applications which require only libnnp:
-$(APP_CORE): INCLUDES=-I./ -I$(PROJECT_INCLUDE)/
+$(APP_CORE): INCLUDES=-I./ -I$(PROJECT_INCLUDE)/ -I$(PROJECT_EIGEN)
ifeq ($(MODE), shared)
$(APP_CORE): LDFLAGS=-L$(PROJECT_LIB) -lnnp
else
diff --git a/src/application/nnp-winit.cpp b/src/application/nnp-winit.cpp
new file mode 100644
index 000000000..285bb7612
--- /dev/null
+++ b/src/application/nnp-winit.cpp
@@ -0,0 +1,308 @@
+// n2p2 - A neural network potential package
+// Copyright (C) 2018 Andreas Singraber (University of Vienna)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "Log.h"
+#include "NeuralNetwork.h"
+#include "utility.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+using namespace nnp;
+
+int main(int argc, char* argv[])
+{
+ string activationString = "l";
+ int numNeuronsPerLayer = 10;
+ size_t numBins = 1000;
+ size_t numLayers = 7;
+ vector meanActivation(numLayers, 0.0);
+ vector sigmaActivation(numLayers, 0.0);
+ vector countActivation(numLayers, 0);
+ vector meanGradient(numLayers - 1, 0.0);
+ vector sigmaGradient(numLayers - 1, 0.0);
+ vector countGradient(numLayers - 1, 0);
+
+ mt19937_64 rng;
+ rng.seed(0);
+ uniform_real_distribution<> distUniform(-1.0, 1.0);
+ normal_distribution<> distNormal(0.0, 0.3);
+ auto generatorUniform = [&distUniform, &rng]() {return distUniform(rng);};
+ auto generatorNormal = [&distNormal, &rng]() {return distNormal(rng);};
+
+ if (argc != 2)
+ {
+ cout << "USAGE: " << argv[0] << " \n"
+ << " ... Activation function type.\n";
+ return 1;
+ }
+
+ activationString = argv[1];
+
+ ofstream logFile;
+ logFile.open("nnp-winit.log");
+ Log log;
+ log.registerStreamPointer(&logFile);
+ log << "\n";
+ log << "*** WEIGHT INITIALIZATION TESTING *******"
+ "**************************************\n";
+ log << "\n";
+
+ NeuralNetworkTopology t;
+ t.numLayers = numLayers;
+ t.activationFunctionsPerLayer.resize(t.numLayers);
+ fill(t.activationFunctionsPerLayer.begin(),
+ t.activationFunctionsPerLayer.end() - 1,
+ activationFromString(activationString));
+ t.activationFunctionsPerLayer.back() = activationFromString("l");
+ t.numNeuronsPerLayer.resize(t.numLayers);
+ fill(t.numNeuronsPerLayer.begin(),
+ t.numNeuronsPerLayer.end() - 1,
+ numNeuronsPerLayer);
+ t.numNeuronsPerLayer.back() = 1;
+
+ NeuralNetwork nn(t.numLayers,
+ t.numNeuronsPerLayer.data(),
+ t.activationFunctionsPerLayer.data());
+
+ log << nn.info();
+
+ vector histActivation;
+ vector histGradient;
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+ histActivation.push_back(gsl_histogram_alloc(numBins));
+ gsl_histogram_set_ranges_uniform(histActivation.back(), -1.0, 1.0);
+ if (i < numLayers - 1)
+ {
+ histGradient.push_back(gsl_histogram_alloc(numBins));
+ gsl_histogram_set_ranges_uniform(histGradient.back(), -1.0, 1.0);
+ }
+ }
+
+ for (size_t i = 0; i < 100000; ++i)
+ {
+ vector input(numNeuronsPerLayer);
+ generate(input.begin(), input.end(), generatorNormal);
+ nn.setInput(input.data());
+
+ vector connections(nn.getNumConnections());
+ generate(connections.begin(), connections.end(), generatorNormal);
+ nn.setConnections(connections.data());
+ nn.modifyConnections(NeuralNetwork::MS_GLOROTBENGIO_NORMAL);
+ //nn.modifyConnections(NeuralNetwork::MS_FANIN_NORMAL);
+ nn.modifyConnections(NeuralNetwork::MS_ZEROBIAS);
+
+ nn.propagate();
+
+ auto activations = nn.getNeuronsPerLayer();
+
+ auto gradient = nn.getGradientsPerLayer();
+
+ for (size_t il = 0; il < numLayers; ++il)
+ {
+ auto const& l = activations.at(il);
+ for (auto const& a : l)
+ {
+ gsl_histogram_increment(histActivation.at(il), a);
+ // Welford's algorithm (use sigma as M2 storage).
+ countActivation.at(il)++;
+ double const delta = a - meanActivation.at(il);
+ meanActivation.at(il) += delta / countActivation.at(il);
+ double const delta2 = a - meanActivation.at(il);
+ sigmaActivation.at(il) += delta * delta2;
+ }
+ if (il < numLayers - 1)
+ {
+ auto const& l = gradient.at(il);
+ for (auto const& g : l)
+ {
+ gsl_histogram_increment(histGradient.at(il), g);
+ countGradient.at(il)++;
+ double const delta = g - meanGradient.at(il);
+ meanGradient.at(il) += delta / countGradient.at(il);
+ double const delta2 = g - meanGradient.at(il);
+ sigmaGradient.at(il) += delta * delta2;
+ }
+ }
+ }
+ }
+
+ //ofstream f;
+ //f.open("weights.dat");
+ //nn.writeConnections(f);
+ //f.close();
+
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+ sigmaActivation.at(i) = sqrt(sigmaActivation.at(i)
+ / (countActivation.at(i) - 1));
+ if (i < numLayers - 1)
+ {
+ sigmaGradient.at(i) = sqrt(sigmaGradient.at(i)
+ / (countGradient.at(i) - 1));
+ }
+ }
+
+ log << "-----------------------------------------"
+ "--------------------------------------\n";
+ log << "Layer |";
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+ log << strpr(" %12zu", i + 1);
+ }
+ log << "\n";
+ log << "-----------------------------------------"
+ "--------------------------------------\n";
+ log << "Activation |";
+ log << strpr(" %12s", "G");
+ for (size_t i = 1; i < numLayers; ++i)
+ {
+ log << strpr(" %12s", stringFromActivation(
+ t.activationFunctionsPerLayer[i]).c_str());
+ }
+ log << "\n";
+ log << "Count |";
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+ log << strpr(" %12.3E", static_cast(countActivation.at(i)));
+ }
+ log << "\n";
+ log << "Mean |";
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+ log << strpr(" %12.5f", meanActivation.at(i));
+ }
+ log << "\n";
+ log << "Sigma |";
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+ log << strpr(" %12.5f", sigmaActivation.at(i));
+ }
+ log << "\n";
+ log << "-----------------------------------------"
+ "--------------------------------------\n";
+ log << "Gradient\n";
+ log << "Count |";
+ log << strpr(" %12s", "");
+ for (size_t i = 0; i < numLayers - 1; ++i)
+ {
+ log << strpr(" %12.3E", static_cast(countGradient.at(i)));
+ }
+ log << "\n";
+ log << "Mean |";
+ log << strpr(" %12s", "");
+ for (size_t i = 0; i < numLayers - 1; ++i)
+ {
+ log << strpr(" %12.5f", meanGradient.at(i));
+ }
+ log << "\n";
+ log << "Sigma |";
+ log << strpr(" %12s", "");
+ for (size_t i = 0; i < numLayers - 1; ++i)
+ {
+ log << strpr(" %12.5f", sigmaGradient.at(i));
+ }
+ log << "\n";
+ log << "-----------------------------------------"
+ "--------------------------------------\n";
+
+ log << strpr("Writing activation histograms for %zu layers.\n", numLayers);
+ for (size_t i = 0; i < numLayers; ++i)
+ {
+
+ string fileName = strpr("activation.%03zu.out", i + 1);
+ FILE* fp = 0;
+ fp = fopen(fileName.c_str(), "w");
+
+ // File header.
+ vector title;
+ vector colName;
+ vector colInfo;
+ vector colSize;
+ title.push_back(strpr("Activations histogram for layer %zu with "
+ "activation function %s.",
+ i + 1,
+ stringFromActivation(
+ t.activationFunctionsPerLayer[i]).c_str()));
+ colSize.push_back(16);
+ colName.push_back("activation_l");
+ colInfo.push_back("Activation value, left bin limit.");
+ colSize.push_back(16);
+ colName.push_back("activation_r");
+ colInfo.push_back("Activation value, right bin limit.");
+ colSize.push_back(16);
+ colName.push_back("hist");
+ colInfo.push_back("Histogram count.");
+ appendLinesToFile(fp,
+ createFileHeader(title,
+ colSize,
+ colName,
+ colInfo));
+ gsl_histogram_fprintf(fp, histActivation.at(i), "%16.8E", "%16.8E");
+ fflush(fp);
+ fclose(fp);
+ fp = nullptr;
+ }
+
+ log << strpr("Writing gradient histograms for %zu layers.\n",
+ numLayers - 1);
+ for (size_t i = 0; i < numLayers - 1; ++i)
+ {
+
+ string fileName = strpr("gradient.%03zu.out", i + 2);
+ FILE* fp = 0;
+ fp = fopen(fileName.c_str(), "w");
+
+ // File header.
+ vector title;
+ vector colName;
+ vector colInfo;
+ vector colSize;
+ title.push_back(strpr("Gradient histogram for layer %zu.", i + 2));
+ colSize.push_back(16);
+ colName.push_back("gradient_l");
+ colInfo.push_back("Gradient value, left bin limit.");
+ colSize.push_back(16);
+ colName.push_back("gradient_r");
+ colInfo.push_back("Gradient value, right bin limit.");
+ colSize.push_back(16);
+ colName.push_back("hist");
+ colInfo.push_back("Histogram count.");
+ appendLinesToFile(fp,
+ createFileHeader(title,
+ colSize,
+ colName,
+ colInfo));
+ gsl_histogram_fprintf(fp, histGradient.at(i), "%16.8E", "%16.8E");
+ fflush(fp);
+ fclose(fp);
+ fp = nullptr;
+ }
+
+ log << "Finished.\n";
+ log << "*****************************************"
+ "**************************************\n";
+ logFile.close();
+
+ return 0;
+}
diff --git a/src/application/nnx.cpp b/src/application/nnx.cpp
new file mode 100644
index 000000000..95c765b96
--- /dev/null
+++ b/src/application/nnx.cpp
@@ -0,0 +1,40 @@
+// n2p2 - A neural network potential package
+// Copyright (C) 2018 Andreas Singraber (University of Vienna)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "NeuralNetworx.h"
+#include
+#include
+#include
+#include
+
+using namespace std;
+using namespace nnp;
+
+int main(int argc, char* argv[])
+{
+ vector numNeuronsPerLayer = {10, 15, 15, 3};
+ //vector activationPerLayer = {"G", "t", "s", "l"};
+ //NeuralNetworx nn(numNeuronsPerLayer, activationPerLayer);
+ vector activationPerLayer = {NeuralNetworx::Activation::IDENTITY,
+ NeuralNetworx::Activation::TANH,
+ NeuralNetworx::Activation::LOGISTIC,
+ NeuralNetworx::Activation::IDENTITY};
+ NeuralNetworx nn(numNeuronsPerLayer, activationPerLayer);
+
+ for (auto l : nn.info()) cout << l;
+
+ return 0;
+}
diff --git a/src/libnnp/Mode.cpp b/src/libnnp/Mode.cpp
index 475ec8ff3..b2d0cd548 100644
--- a/src/libnnp/Mode.cpp
+++ b/src/libnnp/Mode.cpp
@@ -758,51 +758,7 @@ void Mode::setupNeuralNetwork()
{
t.numNeuronsPerLayer[i] =
atoi(globalNumNeuronsPerHiddenLayer.at(i-1).c_str());
- if (globalActivationFunctions.at(i-1) == "l")
- {
- a = NeuralNetwork::AF_IDENTITY;
- }
- else if (globalActivationFunctions.at(i-1) == "t")
- {
- a = NeuralNetwork::AF_TANH;
- }
- else if (globalActivationFunctions.at(i-1) == "s")
- {
- a = NeuralNetwork::AF_LOGISTIC;
- }
- else if (globalActivationFunctions.at(i-1) == "p")
- {
- a = NeuralNetwork::AF_SOFTPLUS;
- }
- else if (globalActivationFunctions.at(i-1) == "r")
- {
- a = NeuralNetwork::AF_RELU;
- }
- else if (globalActivationFunctions.at(i-1) == "g")
- {
- a = NeuralNetwork::AF_GAUSSIAN;
- }
- else if (globalActivationFunctions.at(i-1) == "c")
- {
- a = NeuralNetwork::AF_COS;
- }
- else if (globalActivationFunctions.at(i-1) == "S")
- {
- a = NeuralNetwork::AF_REVLOGISTIC;
- }
- else if (globalActivationFunctions.at(i-1) == "e")
- {
- a = NeuralNetwork::AF_EXP;
- }
- else if (globalActivationFunctions.at(i-1) == "h")
- {
- a = NeuralNetwork::AF_HARMONIC;
- }
- else
- {
- throw runtime_error("ERROR: Unknown activation "
- "function.\n");
- }
+ a = activationFromString(globalActivationFunctions.at(i-1));
}
}
}
@@ -884,7 +840,9 @@ void Mode::setupNeuralNetworkWeights(string const& fileNameFormat)
weights.push_back(atof(splitLine.at(0).c_str()));
}
}
- it->neuralNetwork->setConnections(&(weights.front()));
+ // Attention: need alternative (old) ordering scheme here for
+ // backward compatibility!
+ it->neuralNetwork->setConnectionsAO(&(weights.front()));
file.close();
}
diff --git a/src/libnnp/NeuralNetwork.cpp b/src/libnnp/NeuralNetwork.cpp
index e7b7808c6..6271bcf94 100644
--- a/src/libnnp/NeuralNetwork.cpp
+++ b/src/libnnp/NeuralNetwork.cpp
@@ -80,12 +80,26 @@ NeuralNetwork(int numLayers,
weightOffset[i] = weightOffset[i-1] +
(layers[i-1].numNeurons + 1) * layers[i].numNeurons;
}
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ biasOffset = new int*[numLayers-1];
+ for (int i = 0; i < numLayers-1; i++)
+ {
+ biasOffset[i] = new int[layers[i+1].numNeurons];
+ for (int j = 0; j < layers[i+1].numNeurons; j++)
+ {
+ biasOffset[i][j] = weightOffset[i]
+ + (layers[i+1].numNeuronsPrevLayer + 1)
+ * (j + 1) - 1;
+ }
+ }
+#else
biasOffset = new int[numLayers-1];
for (int i = 0; i < numLayers-1; i++)
{
biasOffset[i] = weightOffset[i] +
layers[i+1].numNeurons * layers[i].numNeurons;
}
+#endif
biasOnlyOffset = new int[numLayers-1];
biasOnlyOffset[0] = 0;
for (int i = 1; i < numLayers-1; i++)
@@ -106,7 +120,15 @@ NeuralNetwork::~NeuralNetwork()
}
delete[] layers;
delete[] weightOffset;
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ for (int i = 0; i < numLayers - 1; i++)
+ {
+ delete[] biasOffset[i];
+ }
+ delete[] biasOffset;
+#else
delete[] biasOffset;
+#endif
delete[] biasOnlyOffset;
}
@@ -148,6 +170,27 @@ void NeuralNetwork::setConnections(double const* const& connections)
{
int count = 0;
+ for (int i = 1; i < numLayers; i++)
+ {
+ for (int j = 0; j < layers[i].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ {
+ layers[i].neurons[j].weights[k] = connections[count];
+ count++;
+ }
+ layers[i].neurons[j].bias = connections[count];
+ count++;
+ }
+ }
+
+ return;
+}
+
+void NeuralNetwork::setConnectionsAO(double const* const& connections)
+{
+ int count = 0;
+
for (int i = 1; i < numLayers; i++)
{
for (int j = 0; j < layers[i].numNeuronsPrevLayer; j++)
@@ -172,6 +215,27 @@ void NeuralNetwork::getConnections(double* connections) const
{
int count = 0;
+ for (int i = 1; i < numLayers; i++)
+ {
+ for (int j = 0; j < layers[i].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ {
+ connections[count] = layers[i].neurons[j].weights[k] ;
+ count++;
+ }
+ connections[count] = layers[i].neurons[j].bias;
+ count++;
+ }
+ }
+
+ return;
+}
+
+void NeuralNetwork::getConnectionsAO(double* connections) const
+{
+ int count = 0;
+
for (int i = 1; i < numLayers; i++)
{
for (int j = 0; j < layers[i].numNeuronsPrevLayer; j++)
@@ -231,36 +295,119 @@ void NeuralNetwork::modifyConnections(ModificationScheme modificationScheme)
}
}
}
- else if (modificationScheme == MS_FANIN)
+ else if (modificationScheme == MS_FANIN_UNIFORM)
{
for (int i = 1; i < numLayers; i++)
{
- if(layers[i].activationFunction == AF_TANH)
+ for (int j = 0; j < layers[i].numNeurons; j++)
{
- for (int j = 0; j < layers[i].numNeurons; j++)
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
{
- for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ if(layers[i].activationFunction == AF_TANH)
{
- layers[i].neurons[j].weights[k] /=
- sqrt(layers[i].numNeuronsPrevLayer);
+ layers[i].neurons[j].weights[k] *=
+ sqrt(3.0 / layers[i].numNeuronsPrevLayer);
+ }
+ else if(layers[i].activationFunction == AF_LOGISTIC)
+ {
+ layers[i].neurons[j].weights[k] *=
+ 4.0 * sqrt(3.0 / layers[i].numNeuronsPrevLayer);
+ }
+ else if(layers[i].activationFunction == AF_RELU ||
+ layers[i].activationFunction == AF_SOFTPLUS)
+ {
+ layers[i].neurons[j].weights[k] *=
+ sqrt(6.0 / layers[i].numNeuronsPrevLayer);
}
}
}
}
}
- else if (modificationScheme == MS_GLOROTBENGIO)
+ else if (modificationScheme == MS_FANIN_NORMAL)
{
for (int i = 1; i < numLayers; i++)
{
- if(layers[i].activationFunction == AF_TANH)
+ for (int j = 0; j < layers[i].numNeurons; j++)
{
- for (int j = 0; j < layers[i].numNeurons; j++)
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
{
- for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ if(layers[i].activationFunction == AF_TANH)
+ {
+ layers[i].neurons[j].weights[k] *=
+ sqrt(1.0 / layers[i].numNeuronsPrevLayer);
+ }
+ else if(layers[i].activationFunction == AF_LOGISTIC)
+ {
+ layers[i].neurons[j].weights[k] *=
+ 4.0 * sqrt(1.0 / layers[i].numNeuronsPrevLayer);
+ }
+ else if(layers[i].activationFunction == AF_RELU ||
+ layers[i].activationFunction == AF_SOFTPLUS)
{
- layers[i].neurons[j].weights[k] *= sqrt(6.0 / (
- layers[i].numNeuronsPrevLayer
- + layers[i].numNeurons));
+ layers[i].neurons[j].weights[k] *=
+ sqrt(2.0 / layers[i].numNeuronsPrevLayer);
+ }
+ }
+ }
+ }
+ }
+ else if (modificationScheme == MS_GLOROTBENGIO_UNIFORM)
+ {
+ for (int i = 1; i < numLayers; i++)
+ {
+ for (int j = 0; j < layers[i].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ {
+ if(layers[i].activationFunction == AF_TANH)
+ {
+ layers[i].neurons[j].weights[k] *=
+ sqrt(6.0 / (layers[i].numNeuronsPrevLayer
+ + layers[i].numNeurons));
+ }
+ else if(layers[i].activationFunction == AF_LOGISTIC)
+ {
+ layers[i].neurons[j].weights[k] *=
+ 4.0 * sqrt(6.0 / (layers[i].numNeuronsPrevLayer
+ + layers[i].numNeurons));
+ }
+ else if(layers[i].activationFunction == AF_RELU ||
+ layers[i].activationFunction == AF_SOFTPLUS)
+ {
+ layers[i].neurons[j].weights[k] *=
+ sqrt(12.0 / (layers[i].numNeuronsPrevLayer
+ + layers[i].numNeurons));
+ }
+ }
+ }
+ }
+ }
+ else if (modificationScheme == MS_GLOROTBENGIO_NORMAL)
+ {
+ for (int i = 1; i < numLayers; i++)
+ {
+ for (int j = 0; j < layers[i].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ {
+ if(layers[i].activationFunction == AF_TANH)
+ {
+ layers[i].neurons[j].weights[k] *=
+ sqrt(2.0 / (layers[i].numNeuronsPrevLayer
+ + layers[i].numNeurons));
+ }
+ else if(layers[i].activationFunction == AF_LOGISTIC)
+ {
+ layers[i].neurons[j].weights[k] *=
+ 4.0 * sqrt(2.0 / (layers[i].numNeuronsPrevLayer
+ + layers[i].numNeurons));
+ }
+ else if(layers[i].activationFunction == AF_RELU ||
+ layers[i].activationFunction == AF_SOFTPLUS)
+ {
+ layers[i].neurons[j].weights[k] *=
+ sqrt(4.0 / (layers[i].numNeuronsPrevLayer
+ + layers[i].numNeurons));
}
}
}
@@ -368,6 +515,42 @@ void NeuralNetwork::getOutput(double* output) const
return;
}
+vector> NeuralNetwork::getNeuronsPerLayer() const
+{
+ vector> output;
+
+ for (int i = 0; i < numLayers; ++i)
+ {
+ output.push_back(vector());
+ for (int j = 0; j < layers[i].numNeurons; ++j)
+ {
+ output.back().push_back(layers[i].neurons[j].value);
+ }
+ }
+
+ return output;
+}
+
+vector> NeuralNetwork::getGradientsPerLayer() const
+{
+ vector> output;
+
+ double* dEdc = new double[getNumConnections()];
+ calculateDEdc(dEdc);
+
+ for (int i = 0; i < numLayers - 2; ++i)
+ {
+ output.push_back(vector(dEdc + weightOffset[i],
+ dEdc + weightOffset[i + 1]));
+ }
+ output.push_back(vector(dEdc + weightOffset[numLayers - 2],
+ dEdc + getNumConnections()));
+
+ delete[] dEdc;
+
+ return output;
+}
+
void NeuralNetwork::propagate()
{
for (int i = 1; i < numLayers; i++)
@@ -428,6 +611,52 @@ void NeuralNetwork::calculateDEdG(double *dEdG) const
void NeuralNetwork::calculateDEdc(double* dEdc) const
{
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ int count = 0;
+
+ for (int i = 0; i < numConnections; i++)
+ {
+ dEdc[i] = 0.0;
+ }
+
+ for (int i = 0; i < outputLayer->numNeurons; i++)
+ {
+ dEdc[biasOffset[numLayers-2][i]] = outputLayer->neurons[i].dfdx;
+ if (normalizeNeurons)
+ {
+ dEdc[biasOffset[numLayers-2][i]] /=
+ outputLayer->numNeuronsPrevLayer;
+ }
+ }
+
+ for (int i = numLayers - 2; i >= 0; i--)
+ {
+ count = 0;
+ for (int j = 0; j < layers[i+1].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeurons; k++)
+ {
+ dEdc[weightOffset[i]+count] = dEdc[biasOffset[i][j]]
+ * layers[i].neurons[k].value;
+ count++;
+ if (i >= 1)
+ {
+ dEdc[biasOffset[i-1][k]] += dEdc[biasOffset[i][j]]
+ * layers[i+1].neurons[j].weights[k]
+ * layers[i].neurons[k].dfdx;
+ }
+ }
+ count++;
+ }
+ if (normalizeNeurons && i >= 1)
+ {
+ for (int k = 0; k < layers[i].numNeurons; k++)
+ {
+ dEdc[biasOffset[i-1][k]] /= layers[i].numNeuronsPrevLayer;
+ }
+ }
+ }
+#else
int count = 0;
for (int i = 0; i < numConnections; i++)
@@ -468,6 +697,7 @@ void NeuralNetwork::calculateDEdc(double* dEdc) const
}
}
}
+#endif
return;
}
@@ -512,6 +742,67 @@ void NeuralNetwork::calculateDFdc(double* dFdc,
}
void NeuralNetwork::writeConnections(std::ofstream& file) const
+{
+ // File header.
+ vector title;
+ vector colName;
+ vector colInfo;
+ vector colSize;
+ title.push_back("Neural network connection values (weights and biases).");
+ colSize.push_back(24);
+ colName.push_back("connection");
+ colInfo.push_back("Neural network connection value.");
+ colSize.push_back(1);
+ colName.push_back("t");
+ colInfo.push_back("Connection type (a = weight, b = bias).");
+ colSize.push_back(9);
+ colName.push_back("index");
+ colInfo.push_back("Index enumerating weights.");
+ colSize.push_back(5);
+ colName.push_back("l_s");
+ colInfo.push_back("Starting point layer (end point layer for biases).");
+ colSize.push_back(5);
+ colName.push_back("n_s");
+ colInfo.push_back("Starting point neuron in starting layer (end point "
+ "neuron for biases).");
+ colSize.push_back(5);
+ colName.push_back("l_e");
+ colInfo.push_back("End point layer.");
+ colSize.push_back(5);
+ colName.push_back("n_e");
+ colInfo.push_back("End point neuron in end layer.");
+ appendLinesToFile(file,
+ createFileHeader(title, colSize, colName, colInfo));
+
+ int count = 0;
+ for (int i = 1; i < numLayers; i++)
+ {
+ for (int j = 0; j < layers[i].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeuronsPrevLayer; k++)
+ {
+ count++;
+ file << strpr("%24.16E a %9d %5d %5d %5d %5d\n",
+ layers[i].neurons[j].weights[k],
+ count,
+ i - 1,
+ k + 1,
+ i,
+ j + 1);
+ }
+ count++;
+ file << strpr("%24.16E b %9d %5d %5d\n",
+ layers[i].neurons[j].bias,
+ count,
+ i,
+ j + 1);
+ }
+ }
+
+ return;
+}
+
+void NeuralNetwork::writeConnectionsAO(std::ofstream& file) const
{
// File header.
vector title;
@@ -645,6 +936,67 @@ void NeuralNetwork::calculateD2EdGdc(int index,
double const* const& dEdb,
double* d2EdGdc) const
{
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ int count = 0;
+
+ for (int i = 0; i < outputLayer->numNeurons; i++)
+ {
+ d2EdGdc[biasOffset[numLayers-2][i]] = outputLayer->neurons[i].d2fdx2
+ * outputLayer->neurons[i].dxdG;
+ if (normalizeNeurons)
+ {
+ d2EdGdc[biasOffset[numLayers-2][i]] /=
+ outputLayer->numNeuronsPrevLayer;
+ }
+ }
+
+ for (int i = numLayers-2; i >= 0; i--)
+ {
+ count = 0;
+ for (int j = 0; j < layers[i+1].numNeurons; j++)
+ {
+ for (int k = 0; k < layers[i].numNeurons; k++)
+ {
+ if (i == 0)
+ {
+ d2EdGdc[weightOffset[i]+count] = d2EdGdc[biasOffset[i][j]]
+ * layers[i].neurons[k].value;
+ if (k == index)
+ {
+ d2EdGdc[weightOffset[i]+count] +=
+ dEdb[biasOnlyOffset[i]+j];
+ }
+ }
+ else
+ {
+ d2EdGdc[weightOffset[i]+count] = d2EdGdc[biasOffset[i][j]]
+ * layers[i].neurons[k].value
+ + dEdb[biasOnlyOffset[i]+j] * layers[i].neurons[k].dfdx
+ * layers[i].neurons[k].dxdG;
+ }
+ count++;
+ if (i >= 1)
+ {
+ d2EdGdc[biasOffset[i-1][k]] +=
+ layers[i+1].neurons[j].weights[k]
+ * (d2EdGdc[biasOffset[i][j]]
+ * layers[i].neurons[k].dfdx
+ + dEdb[biasOnlyOffset[i]+j]
+ * layers[i].neurons[k].d2fdx2
+ * layers[i].neurons[k].dxdG);
+ }
+ }
+ count++;
+ }
+ for (int k = 0; k < layers[i].numNeurons; k++)
+ {
+ if (normalizeNeurons && i >= 1)
+ {
+ d2EdGdc[biasOffset[i-1][k]] /= layers[i].numNeuronsPrevLayer;
+ }
+ }
+ }
+#else
int count = 0;
for (int i = 0; i < outputLayer->numNeurons; i++)
@@ -699,6 +1051,7 @@ void NeuralNetwork::calculateD2EdGdc(int index,
}
}
}
+#endif
return;
}
@@ -921,6 +1274,45 @@ void NeuralNetwork::getNeuronStatistics(long* count,
return;
}
+vector> NeuralNetwork::getLayerLimits() const
+{
+ vector> limits;
+
+ for (int i = 0; i < numLayers - 2; ++i)
+ {
+ limits.push_back(make_pair(weightOffset[i],
+ weightOffset[i + 1] - 1));
+ }
+ limits.push_back(make_pair(weightOffset[numLayers - 2],
+ numConnections - 1));
+
+ return limits;
+}
+
+vector> NeuralNetwork::getNeuronLimits() const
+{
+ vector> limits;
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ for (int i = 0; i < numLayers - 1; ++i)
+ {
+ limits.push_back(make_pair(weightOffset[i],
+ biasOffset[i][0]));
+ for (int j = 0; j < layers[i+1].numNeurons - 1; ++j)
+ {
+ limits.push_back(make_pair(biasOffset[i][j] + 1,
+ biasOffset[i][j+1]));
+
+ }
+ }
+#else
+ throw runtime_error("ERROR: Neuron boundaries not available with "
+ "alternative (old) weight memory layout, recompile "
+ "without -DALTERNATIVE_WEIGHT_ORDERING.\n");
+#endif
+
+ return limits;
+}
+
/*
void NeuralNetwork::writeStatus(int element, int epoch)
{
@@ -969,7 +1361,15 @@ long NeuralNetwork::getMemoryUsage()
int numNeurons = getNumNeurons();
mem += (numLayers - 1) * sizeof(int); // weightOffset
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ mem += (numLayers - 1) * sizeof(int*); // biasOffset
+ for (int i = 0; i < numLayers - 1; i++)
+ {
+ mem += layers[i].numNeurons * sizeof(int);
+ }
+#else
mem += (numLayers - 1) * sizeof(int); // biasOffset
+#endif
mem += (numLayers - 1) * sizeof(int); // biasOnlyOffset
mem += numLayers * sizeof(Layer); // layers
mem += numNeurons * sizeof(Neuron); // neurons
@@ -1008,45 +1408,11 @@ vector NeuralNetwork::info() const
{
s += strpr(" %3s", "G");
}
- else if (layers[j].activationFunction == AF_IDENTITY)
- {
- s += strpr(" %3s", "l");
- }
- else if (layers[j].activationFunction == AF_TANH)
- {
- s += strpr(" %3s", "t");
- }
- else if (layers[j].activationFunction == AF_LOGISTIC)
- {
- s += strpr(" %3s", "s");
- }
- else if (layers[j].activationFunction == AF_SOFTPLUS)
- {
- s += strpr(" %3s", "p");
- }
- else if (layers[j].activationFunction == AF_RELU)
- {
- s += strpr(" %3s", "r");
- }
- else if (layers[j].activationFunction == AF_GAUSSIAN)
- {
- s += strpr(" %3s", "g");
- }
- else if (layers[j].activationFunction == AF_COS)
- {
- s += strpr(" %3s", "c");
- }
- else if (layers[j].activationFunction == AF_REVLOGISTIC)
- {
- s += strpr(" %3s", "S");
- }
- else if (layers[j].activationFunction == AF_EXP)
- {
- s += strpr(" %3s", "e");
- }
- else if (layers[j].activationFunction == AF_HARMONIC)
+ else
{
- s += strpr(" %3s", "h");
+ s += strpr(" %3s",
+ stringFromActivation(
+ layers[j].activationFunction).c_str());
}
}
else
@@ -1059,3 +1425,47 @@ vector NeuralNetwork::info() const
return v;
}
+
+string nnp::stringFromActivation(NeuralNetwork::ActivationFunction af)
+{
+ string c = "";
+
+ if (af == NeuralNetwork::AF_IDENTITY) c = "l";
+ else if (af == NeuralNetwork::AF_TANH) c = "t";
+ else if (af == NeuralNetwork::AF_LOGISTIC) c = "s";
+ else if (af == NeuralNetwork::AF_SOFTPLUS) c = "p";
+ else if (af == NeuralNetwork::AF_RELU) c = "r";
+ else if (af == NeuralNetwork::AF_GAUSSIAN) c = "g";
+ else if (af == NeuralNetwork::AF_COS) c = "c";
+ else if (af == NeuralNetwork::AF_REVLOGISTIC) c = "S";
+ else if (af == NeuralNetwork::AF_EXP) c = "e";
+ else if (af == NeuralNetwork::AF_HARMONIC) c = "h";
+ else
+ {
+ throw runtime_error("ERROR: Unknown activation function.\n");
+ }
+
+ return c;
+}
+
+NeuralNetwork::ActivationFunction nnp::activationFromString(string c)
+{
+ NeuralNetwork::ActivationFunction af;
+
+ if (c == "l") af = NeuralNetwork::AF_IDENTITY;
+ else if (c == "t") af = NeuralNetwork::AF_TANH;
+ else if (c == "s") af = NeuralNetwork::AF_LOGISTIC;
+ else if (c == "p") af = NeuralNetwork::AF_SOFTPLUS;
+ else if (c == "r") af = NeuralNetwork::AF_RELU;
+ else if (c == "g") af = NeuralNetwork::AF_GAUSSIAN;
+ else if (c == "c") af = NeuralNetwork::AF_COS;
+ else if (c == "S") af = NeuralNetwork::AF_REVLOGISTIC;
+ else if (c == "e") af = NeuralNetwork::AF_EXP;
+ else if (c == "h") af = NeuralNetwork::AF_HARMONIC;
+ else
+ {
+ throw runtime_error("ERROR: Unknown activation function.\n");
+ }
+
+ return af;
+}
diff --git a/src/libnnp/NeuralNetwork.h b/src/libnnp/NeuralNetwork.h
index c56c58e9f..842a4ff80 100644
--- a/src/libnnp/NeuralNetwork.h
+++ b/src/libnnp/NeuralNetwork.h
@@ -17,8 +17,10 @@
#ifndef NEURALNETWORK_H
#define NEURALNETWORK_H
+#include // std::size_t
#include // std::ofstream
#include // std::string
+#include // std::pair
#include // std::vector
namespace nnp
@@ -60,32 +62,64 @@ class NeuralNetwork
MS_ZEROBIAS,
/// Set all weights connecting to the output layer to zero.
MS_ZEROOUTPUTWEIGHTS,
+ /** Normalize weights via number of neuron inputs (fan-in).
+ *
+ * If initial weights are uniformly distributed in
+ * @f$\left[-1, 1\right]@f$ they will be scaled to be in
+ * @f$\left[-\sqrt{\frac{3}{n_\text{in}}},
+ * \sqrt{\frac{3}{n_\text{in}}}\right]@f$, where @f$n_\text{in}@f$ is
+ * the number of incoming weights of a neuron. The interval is
+ * valid for #AF_TANH and is slightly modified for #AF_LOGISTIC,
+ * #AF_RELU and #AF_SOFTPLUS. Weights are not modified for other
+ * activation functions.
+ */
+ MS_FANIN_UNIFORM,
/** Normalize weights via number of neuron inputs (fan-in).
*
* If initial weights are uniformly distributed in
* @f$\left[-1, 1\right]@f$ they will be scaled to be in
* @f$\left[\frac{-1}{\sqrt{n_\text{in}}},
* \frac{1}{\sqrt{n_\text{in}}}\right]@f$, where @f$n_\text{in}@f$ is
- * the number of incoming weights of a neuron (if activation
- * function is of type #AF_TANH).
+ * the number of incoming weights of a neuron. The interval is
+ * valid for #AF_TANH and is slightly modified for #AF_LOGISTIC,
+ * #AF_RELU and #AF_SOFTPLUS. Weights are not modified for other
+ * activation functions.
*/
- MS_FANIN,
+ MS_FANIN_NORMAL,
/** Normalize connections according to Glorot and Bengio.
*
* If initial weights are uniformly distributed in
* @f$\left[-1, 1\right]@f$ they will be scaled to be in
* @f$\left[-\sqrt{\frac{6}{n_\text{in} + n_\text{out}}},
* \sqrt{\frac{6}{n_\text{in} + n_\text{out}}}\right]@f$, where
- * @f$n_\text{in}@f$ and @f$n_\text{out}@f$ are the number of incoming
- * and outgoing weights of a neuron, respectively (if activation
- * function is of type #AF_TANH).
+ * @f$n_\text{in}@f$ is the number of incoming weihghts and
+ * @f$n_\text{out}@f$ is the number of neurons in this layer.
+ * The interval is valid for #AF_TANH and is slightly modified for
+ * #AF_LOGISTIC, #AF_RELU and #AF_SOFTPLUS.
*
* For details see:
* - X. Glorot and Y. Bengio, "Understanding the difficulty of
* training deep feedforward neural networks", International
* conference on artificial intelligence and statistics. 2010.
*/
- MS_GLOROTBENGIO,
+ MS_GLOROTBENGIO_UNIFORM,
+ /** Normalize connections according to Glorot and Bengio.
+ *
+ * If initial weights are normally distributed in
+ * @f$\left[-1, 1\right]@f$ they will be scaled to be in
+ * @f$\left[-\sqrt{\frac{2}{n_\text{in} + n_\text{out}}},
+ * \sqrt{\frac{2}{n_\text{in} + n_\text{out}}}\right]@f$, where
+ * @f$n_\text{in}@f$ is the number of incoming weihghts and
+ * @f$n_\text{out}@f$ is the number of neurons in this layer.
+ * The interval is valid for #AF_TANH and is slightly modified for
+ * #AF_LOGISTIC, #AF_RELU and #AF_SOFTPLUS.
+ *
+ * For details see:
+ * - X. Glorot and Y. Bengio, "Understanding the difficulty of
+ * training deep feedforward neural networks", International
+ * conference on artificial intelligence and statistics. 2010.
+ */
+ MS_GLOROTBENGIO_NORMAL,
/** Initialize connections according to Nguyen-Widrow scheme.
*
* For details see:
@@ -152,29 +186,70 @@ class NeuralNetwork
* @f[
* \underbrace{
* \overbrace{
- * a^{01}_{00}, \ldots, a^{01}_{0m_1},
- * a^{01}_{10}, \ldots, a^{01}_{1m_1},
+ * a^{01}_{00}, \ldots, a^{01}_{n_00}, b^{1}_{0}
+ * }^{\text{Neuron}^{1}_{0}}
+ * \overbrace{
+ * a^{01}_{01}, \ldots, a^{01}_{n_01}, b^{1}_{1}
+ * }^{\text{Neuron}^{1}_{1}}
+ * \ldots,
+ * \overbrace{
+ * a^{01}_{0n_1}, \ldots, a^{01}_{n_0n_1}, b^{1}_{n_1}
+ * }^{\text{Neuron}^{1}_{n_1}}
+ * }_{\text{Layer } 0 \rightarrow 1},
+ * \underbrace{
+ * a^{12}_{00}, \ldots, b^{2}_{n_2}
+ * }_{\text{Layer } 1 \rightarrow 2},
+ * \ldots,
+ * \underbrace{
+ * a^{p-1,p}_{00}, \ldots, b^{p}_{n_p}
+ * }_{\text{Layer } p-1 \rightarrow p}
+ * @f]
+ * where @f$a^{i-1, i}_{jk}@f$ is the weight connecting neuron
+ * @f$j@f$ in layer @f$i-1@f$ to neuron @f$k@f$ in layer
+ * @f$i@f$ and @f$b^{i}_{k}@f$ is the bias assigned to neuron
+ * @f$k@f$ in layer @f$i@f$.
+ *
+ * This ordering scheme is used internally to store weights in memory.
+ * Because all weights and biases connected to a neuron are aligned in a
+ * continuous block this memory layout simplifies the implementation of
+ * node-decoupled Kalman filter training (NDEKF). However, for backward
+ * compatibility reasons this layout is NOT used for storing weights on
+ * disk (see setConnectionsAO()).
+ */
+ void setConnections(double const* const& connections);
+ /** Set neural network weights and biases (alternative ordering)
+ *
+ * @param[in] connections One-dimensional array with neural network
+ * connections in the following order:
+ * @f[
+ * \underbrace{
+ * \overbrace{
+ * a^{01}_{00}, \ldots, a^{01}_{0n_1},
+ * a^{01}_{10}, \ldots, a^{01}_{1n_1},
* \ldots,
- * a^{01}_{n_00}, \ldots, a^{01}_{n_0m_1},
+ * a^{01}_{n_00}, \ldots, a^{01}_{n_0n_1},
* }^{\text{Weights}}
* \overbrace{
- * b^{1}_{0}, \ldots, b^{1}_{m_1}
+ * b^{1}_{0}, \ldots, b^{1}_{n_1}
* }^{\text{Biases}}
* }_{\text{Layer } 0 \rightarrow 1},
* \underbrace{
- * a^{12}_{00}, \ldots, b^{2}_{m_2}
+ * a^{12}_{00}, \ldots, b^{2}_{n_2}
* }_{\text{Layer } 1 \rightarrow 2},
* \ldots,
* \underbrace{
- * a^{p-1,p}_{00}, \ldots, b^{p}_{m_p}
+ * a^{p-1,p}_{00}, \ldots, b^{p}_{n_p}
* }_{\text{Layer } p-1 \rightarrow p}
* @f]
* where @f$a^{i-1, i}_{jk}@f$ is the weight connecting neuron
* @f$j@f$ in layer @f$i-1@f$ to neuron @f$k@f$ in layer
* @f$i@f$ and @f$b^{i}_{k}@f$ is the bias assigned to neuron
* @f$k@f$ in layer @f$i@f$.
+ *
+ * This memory layout is used when storing weights on disk.
*/
- void setConnections(double const* const& connections);
+ void setConnectionsAO(
+ double const* const& connections);
/** Get neural network weights and biases.
*
* @param[out] connections One-dimensional array with neural network
@@ -182,6 +257,13 @@ class NeuralNetwork
* #setConnections())
*/
void getConnections(double* connections) const;
+ /** Get neural network weights and biases (alternative ordering).
+ *
+ * @param[out] connections One-dimensional array with neural network
+ * connections (same order as described in
+ * #setConnectionsAO())
+ */
+ void getConnectionsAO(double* connections) const;
/** Initialize connections with random numbers.
*
* @param[in] seed Random number generator seed.
@@ -211,16 +293,28 @@ class NeuralNetwork
ModificationScheme modificationScheme,
double parameter1,
double parameter2);
- /** Set neural network input layer node values.
+ /** Set neural network input layer neuron values.
*
- * @param[in] input Input layer node values.
+ * @param[in] input Input layer neuron values.
*/
void setInput(double const* const& input) const;
- /** Get neural network output layer node values.
+ /** Get neural network output layer neuron values.
*
- * @param[out] output Output layer node values.
+ * @param[out] output Output layer neuron values.
*/
void getOutput(double* output) const;
+ /** Get neuron values (activations) for all layers.
+ *
+ * @return Vector of neuron values per layer.
+ */
+ std::vector<
+ std::vector> getNeuronsPerLayer() const;
+ /** Get gradients (derivatives of output w.r.t. weights) for all layers.
+ *
+ * @return Vector of gradients per layer.
+ */
+ std::vector<
+ std::vector> getGradientsPerLayer() const;
/** Propagate input information through all layers.
*
* With the input data set by #setInput() this will calculate all remaining
@@ -295,10 +389,20 @@ class NeuralNetwork
void calculateDFdc(double* dFdc,
double const* const& dGdxyz) const;
/** Write connections to file.
+ *
+ * __CAUTION__: For compatibility reasons this format is NOT used for
+ * storing NN weights to disk!
*
* @param[in,out] file File stream to write to.
*/
void writeConnections(std::ofstream& file) const;
+ /** Write connections to file (alternative ordering).
+ *
+ * This NN weights ordering layout is used when writing weights to disk.
+ *
+ * @param[in,out] file File stream to write to.
+ */
+ void writeConnectionsAO(std::ofstream& file) const;
/** Return gathered neuron statistics.
*
* @param[out] count Number of neuron output value calculations.
@@ -333,6 +437,24 @@ class NeuralNetwork
* Counters and summation variables for neuron statistics are reset.
*/
void resetNeuronStatistics();
+ /** Get layer limits in combined weight vector (for Kalman filter
+ * decoupling setup).
+ *
+ * @return Vector with layer limits as pair (begin, end),
+ * (numLayers - 1) points.
+ */
+ std::vector> getLayerLimits() const;
+ /** Get neuron limits in combined weight vector (for Kalman filter
+ * decoupling setup).
+ *
+ * @return Vector with neuron limits as pair (begin, end),
+ * (numNeurons - inputLayer->numNeurons) points.
+ */
+ std::vector> getNeuronLimits() const;
//void writeStatus(int, int);
long getMemoryUsage();
/** Print neural network architecture.
@@ -404,8 +526,13 @@ class NeuralNetwork
int numHiddenLayers;
/// Offset adress of weights per layer in combined weights+bias array.
int* weightOffset;
+#ifndef ALTERNATIVE_WEIGHT_ORDERING
+ /// Offset adress of each neuron per layer in combined weights+bias array.
+ int** biasOffset;
+#else
/// Offset adress of biases per layer in combined weights+bias array.
int* biasOffset;
+#endif
/// Offset adress of biases per layer in bias only array.
int* biasOnlyOffset;
/// Pointer to input layer.
@@ -480,6 +607,30 @@ class NeuralNetwork
void propagateLayer(Layer& layer, Layer& layerPrev);
};
+/** Convert string to activation function.
+ *
+ * @param[in] letter Single character representing activation function.
+ *
+ * @return ActivationFunction corresponding to string.
+ */
+NeuralNetwork::ActivationFunction activationFromString(std::string c);
+/** Convert activation function to string.
+ *
+ * @param[in] af Input ActivationFunction type.
+ *
+ * @return Single char representing activation function.
+ */
+std::string stringFromActivation(NeuralNetwork::ActivationFunction af);
+
+/// Simple class for managing neural network settings.
+struct NeuralNetworkTopology
+{
+ int numLayers = 0;
+ std::vector numNeuronsPerLayer;
+ std::vector<
+ NeuralNetwork::ActivationFunction> activationFunctionsPerLayer;
+};
+
}
#endif
diff --git a/src/libnnp/NeuralNetworx.cpp b/src/libnnp/NeuralNetworx.cpp
new file mode 100644
index 000000000..2c8d4e539
--- /dev/null
+++ b/src/libnnp/NeuralNetworx.cpp
@@ -0,0 +1,725 @@
+// n2p2 - A neural network potential package
+// Copyright (C) 2018 Andreas Singraber (University of Vienna)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "NeuralNetworx.h"
+#include "utility.h"
+#include // std::min, std::max
+#include
+#include
+
+#define EXP_LIMIT 35.0
+
+using namespace std;
+using namespace Eigen;
+using namespace nnp;
+
+NeuralNetworx::NeuralNetworx(vector numNeuronsPerLayer,
+ vector activationPerLayer) :
+ numLayers (0),
+ numConnections (0),
+ numWeights (0),
+ numBiases (0)
+{
+ initialize(numNeuronsPerLayer, activationPerLayer);
+}
+
+NeuralNetworx::NeuralNetworx(vector numNeuronsPerLayer,
+ vector activationStringPerLayer) :
+ numLayers (0),
+ numConnections (0),
+ numWeights (0),
+ numBiases (0)
+{
+ // Set irrelevant input layer activation to linear.
+ activationStringPerLayer.at(0) = "l";
+ vector activationPerLayer;
+ for (auto a : activationStringPerLayer)
+ {
+ activationPerLayer.push_back(activationFromString(a));
+ }
+
+ initialize(numNeuronsPerLayer, activationPerLayer);
+}
+
+NeuralNetworx::Layer::Layer(size_t numNeurons,
+ size_t numNeuronsPrevLayer,
+ Activation activation) :
+ numNeurons (numNeurons),
+ numNeuronsPrevLayer(numNeuronsPrevLayer),
+ fa (activation)
+{
+ // Input layer has no previous layer.
+ if (numNeuronsPrevLayer > 0)
+ {
+ w.resize(numNeurons, numNeuronsPrevLayer);
+ b.resize(numNeurons);
+ x.resize(numNeurons);
+ dydx.resize(numNeurons);
+ d2ydx2.resize(numNeurons);
+ }
+ y.resize(numNeurons);
+}
+
+void NeuralNetworx::Layer::propagate(VectorXd const& yp)
+{
+ x = w * yp + b;
+
+ if (fa == Activation::IDENTITY)
+ {
+ y = x;
+ dydx.setOnes();
+ d2ydx2.setZero();
+ }
+ else if (fa == Activation::TANH)
+ {
+ y = x.array().tanh();
+ dydx = 1.0 - y.array() * y.array();
+ d2ydx2 = -2.0 * y.array() * dydx.array();
+ }
+ else if (fa == Activation::LOGISTIC)
+ {
+ y = x.array().unaryExpr(
+ [](double xi)
+ {
+ if (xi > EXP_LIMIT) return 1.0;
+ else if (xi < -EXP_LIMIT) return 0.0;
+ else return 1.0 / (1.0 + exp(-xi));
+ });
+ //dydx = (y.array() <= 0.0 || y.array() >= 1.0).select(
+ // 0.0,
+ // y.array() * (1.0 - y.array()));
+ dydx = y.array().unaryExpr(
+ [](double yi)
+ {
+ if (yi <= 0.0 || yi >= 1.0) return 0.0;
+ else return yi * (1.0 - yi);
+ });
+ //d2ydx2 = (y.array() <= 0.0 || y.array() >= 1.0).select(
+ // 0.0,
+ // y.array() * (1.0 - y.array()) * (1.0 - 2.0 * y.array()));
+ d2ydx2 = y.array().unaryExpr(
+ [](double yi)
+ {
+ if (yi <= 0.0 || yi >= 1.0) return 0.0;
+ else return yi * (1.0 - yi) * (1.0 - 2.0 * yi);
+ ;
+ });
+ }
+ else if (fa == Activation::SOFTPLUS)
+ {
+ y = x.array().unaryExpr(
+ [](double xi)
+ {
+ if (xi > EXP_LIMIT) return xi;
+ else if (xi < -EXP_LIMIT) return 0.0;
+ else return log(1.0 + exp(xi));
+ });
+ dydx = x.array().unaryExpr(
+ [](double xi)
+ {
+ if (xi > EXP_LIMIT) return 1.0;
+ else if (xi < -EXP_LIMIT) return 0.0;
+ else return 1.0 / (1.0 + exp(-xi));
+ });
+ d2ydx2 = x.array().unaryExpr(
+ [](double xi)
+ {
+ if (xi > EXP_LIMIT) return 0.0;
+ else if (xi < -EXP_LIMIT) return 0.0;
+ else
+ {
+ double const tmp = 1.0 / (1.0 + exp(-xi));
+ return tmp * (1.0 - tmp);
+ }
+ ;
+ });
+ }
+ else if (fa == Activation::RELU)
+ {
+ //y = (x.array() > 0.0).select(x, 0.0);
+ y = x.array().unaryExpr(
+ [](double xi)
+ {
+ if (xi > 0.0) return xi;
+ else return 0.0;
+ });
+ //dydx = (x.array() > 0.0).select(VectorXd::Ones(dydx.size()) 0.0);
+ dydx = x.array().unaryExpr(
+ [](double xi)
+ {
+ if (xi > 0.0) return 1.0;
+ else return 0.0;
+ });
+ d2ydx2.setZero();
+ }
+ else if (fa == Activation::GAUSSIAN)
+ {
+ y = (-0.5 * x.array() * x.array()).exp();
+ dydx = -x.array() * y.array();
+ d2ydx2 = (x.array() * x.array() - 1.0) * y.array();
+ }
+ else if (fa == Activation::COS)
+ {
+ y = x.array().cos();
+ dydx = x.array().sin();
+ d2ydx2 = -y.array();
+ }
+ else if (fa == Activation::REVLOGISTIC)
+ {
+ VectorXd const tmp = 1.0 / (1.0 + (-x.array()).exp());
+ y = 1.0 - tmp.array();
+ dydx = tmp.array() * (tmp.array() - 1.0);
+ d2ydx2 = tmp.array() * (tmp.array() - 1.0) * (1.0 - 2.0 * tmp.array());
+ }
+ else if (fa == Activation::EXP)
+ {
+ y = (-x.array()).exp();
+ dydx = -y.array();
+ d2ydx2 = y.array();
+ }
+ else if (fa == Activation::HARMONIC)
+ {
+ y = x.array() * x.array();
+ dydx = 2.0 * x.array();
+ d2ydx2.setConstant(2.0);
+ }
+
+ return;
+}
+
+void NeuralNetworx::Layer::initializeDerivInput()
+{
+ // Multiply each row of weights (i.e. all weights connected to a neuron
+ // of this layer) with derivative of the activation function.
+ dydyi = dydx.asDiagonal() * w;
+
+ return;
+}
+
+void NeuralNetworx::Layer::propagateDerivInput(MatrixXd const& dypdyi)
+{
+ dydyi = dydx.asDiagonal() * (w * dypdyi);
+
+ return;
+}
+
+void NeuralNetworx::setInput(vector const& input)
+{
+ if (input.size() != layers.at(0).numNeurons)
+ {
+ throw runtime_error("ERROR: Input vector size does not match number "
+ "of input layer neurons.");
+ }
+ Map y0(input.data(), input.size());
+ layers.at(0).y = y0;
+
+ return;
+}
+
+void NeuralNetworx::propagate(bool deriv)
+{
+ for (size_t i = 1; i < layers.size(); ++i)
+ {
+ layers.at(i).propagate(layers.at(i - 1).y);
+ if (deriv)
+ {
+ if (i == 1) layers.at(i).initializeDerivInput();
+ else layers.at(i).propagateDerivInput(layers.at(i - 1).dydyi);
+ }
+ }
+
+ return;
+}
+
+void NeuralNetworx::propagate(vector const& input, bool deriv)
+{
+ setInput(input);
+ propagate(deriv);
+
+ return;
+}
+
+void NeuralNetworx::getOutput(vector& output) const
+{
+ output.clear();
+ output.insert(output.begin(),
+ layers.back().y.data(),
+ layers.back().y.data() + layers.back().y.size());
+
+ return;
+}
+
+void NeuralNetworx::getDerivInput(vector>& derivInput) const
+{
+ derivInput.clear();
+ derivInput.resize(layers.back().dydyi.rows());
+ for (size_t i = 0; i < derivInput.size(); ++i)
+ {
+ for (size_t j = 0; j < layers.at(0).numNeurons; ++j)
+ {
+ derivInput.at(i).push_back(layers.back().dydyi(i, j));
+ }
+ }
+
+ return;
+}
+
+void NeuralNetworx::setConnections(vector const& connections)
+{
+ if (connections.size() != numConnections)
+ {
+ throw runtime_error("ERROR: Provided vector has incorrect length.\n");
+ }
+
+ Map c(connections.data(), connections.size());
+
+ size_t count = 0;
+ for (vector::iterator l = layers.begin() + 1;
+ l != layers.end(); ++l)
+ {
+ size_t const n = l->w.cols();
+ for (Index i = 0; i < l->y.size(); ++i)
+ {
+ l->w.row(i) = c.segment(count, n);
+ count += n;
+ l->b(i) = c(count);
+ count++;
+ }
+ }
+
+ return;
+}
+
+void NeuralNetworx::setConnectionsAO(vector const& connections)
+{
+ if (connections.size() != numConnections)
+ {
+ throw runtime_error("ERROR: Provided vector has incorrect length.\n");
+ }
+
+ Map c(connections.data(), connections.size());
+
+ size_t count = 0;
+ for (vector::iterator l = layers.begin() + 1;
+ l != layers.end(); ++l)
+ {
+ size_t const n = l->w.rows();
+ for (Index i = 0; i < l->w.cols(); ++i)
+ {
+ l->w.col(i) = c.segment(count, n);
+ count += n;
+ }
+ l->b = c.segment(count, l->b.size());
+ count += l->b.size();
+ }
+
+ return;
+}
+
+void NeuralNetworx::getConnections(vector& connections) const
+{
+ if (connections.size() != numConnections)
+ {
+ throw runtime_error("ERROR: Provided vector has incorrect length.\n");
+ }
+
+ Map c(connections.data(), connections.size());
+
+ size_t count = 0;
+ for (vector::const_iterator l = layers.begin() + 1;
+ l != layers.end(); ++l)
+ {
+ size_t const n = l->w.cols();
+ for (Index i = 0; i < l->y.size(); ++i)
+ {
+ c.segment(count, n) = l->w.row(i);
+ count += n;
+ c(count) = l->b(i);
+ count++;
+ }
+ }
+
+ return;
+}
+
+void NeuralNetworx::getConnectionsAO(vector& connections) const
+{
+ if (connections.size() != numConnections)
+ {
+ throw runtime_error("ERROR: Provided vector has incorrect length.\n");
+ }
+
+ Map c(connections.data(), connections.size());
+
+ size_t count = 0;
+ for (vector::const_iterator l = layers.begin() + 1;
+ l != layers.end(); ++l)
+ {
+ size_t const n = l->w.rows();
+ for (Index i = 0; i < l->w.cols(); ++i)
+ {
+ c.segment(count, n) = l->w.col(i);
+ count += n;
+ }
+ c.segment(count, l->b.size()) = l->b;
+ count += l->b.size();
+ }
+
+ return;
+}
+
+void NeuralNetworx::writeConnections(std::ofstream& file) const
+{
+ // File header.
+ vector title;
+ vector colName;
+ vector colInfo;
+ vector colSize;
+ title.push_back("Neural network connection values (weights and biases).");
+ colSize.push_back(24);
+ colName.push_back("connection");
+ colInfo.push_back("Neural network connection value.");
+ colSize.push_back(1);
+ colName.push_back("t");
+ colInfo.push_back("Connection type (a = weight, b = bias).");
+ colSize.push_back(9);
+ colName.push_back("index");
+ colInfo.push_back("Index enumerating weights.");
+ colSize.push_back(5);
+ colName.push_back("l_s");
+ colInfo.push_back("Starting point layer (end point layer for biases).");
+ colSize.push_back(5);
+ colName.push_back("n_s");
+ colInfo.push_back("Starting point neuron in starting layer (end point "
+ "neuron for biases).");
+ colSize.push_back(5);
+ colName.push_back("l_e");
+ colInfo.push_back("End point layer.");
+ colSize.push_back(5);
+ colName.push_back("n_e");
+ colInfo.push_back("End point neuron in end layer.");
+ appendLinesToFile(file,
+ createFileHeader(title, colSize, colName, colInfo));
+
+ size_t count = 0;
+ for (size_t i = 1; i < layers.size(); ++i)
+ {
+ Layer const& l = layers.at(i);
+ for (size_t j = 0; j < l.numNeurons; j++)
+ {
+ for (size_t k = 0; k < l.numNeuronsPrevLayer; k++)
+ {
+ count++;
+ file << strpr("%24.16E a %9d %5d %5d %5d %5d\n",
+ l.w(j, k),
+ count,
+ i - 1,
+ k + 1,
+ i,
+ j + 1);
+ }
+ count++;
+ file << strpr("%24.16E b %9d %5d %5d\n",
+ l.b(j),
+ count,
+ i,
+ j + 1);
+ }
+ }
+
+ return;
+}
+
+void NeuralNetworx::writeConnectionsAO(std::ofstream& file) const
+{
+ // File header.
+ vector title;
+ vector colName;
+ vector colInfo;
+ vector colSize;
+ title.push_back("Neural network connection values (weights and biases).");
+ colSize.push_back(24);
+ colName.push_back("connection");
+ colInfo.push_back("Neural network connection value.");
+ colSize.push_back(1);
+ colName.push_back("t");
+ colInfo.push_back("Connection type (a = weight, b = bias).");
+ colSize.push_back(9);
+ colName.push_back("index");
+ colInfo.push_back("Index enumerating weights.");
+ colSize.push_back(5);
+ colName.push_back("l_s");
+ colInfo.push_back("Starting point layer (end point layer for biases).");
+ colSize.push_back(5);
+ colName.push_back("n_s");
+ colInfo.push_back("Starting point neuron in starting layer (end point "
+ "neuron for biases).");
+ colSize.push_back(5);
+ colName.push_back("l_e");
+ colInfo.push_back("End point layer.");
+ colSize.push_back(5);
+ colName.push_back("n_e");
+ colInfo.push_back("End point neuron in end layer.");
+ appendLinesToFile(file,
+ createFileHeader(title, colSize, colName, colInfo));
+
+ size_t count = 0;
+ for (size_t i = 1; i < layers.size(); ++i)
+ {
+ Layer const& l = layers.at(i);
+ for (size_t j = 0; j < l.numNeuronsPrevLayer; j++)
+ {
+ for (size_t k = 0; k < l.numNeurons; k++)
+ {
+ count++;
+ file << strpr("%24.16E a %9d %5d %5d %5d %5d\n",
+ l.w(k, j),
+ count,
+ i - 1,
+ j + 1,
+ i,
+ k + 1);
+ }
+ }
+ for (size_t j = 0; j < l.numNeurons; j++)
+ {
+ count++;
+ file << strpr("%24.16E b %9d %5d %5d\n",
+ l.b(j),
+ count,
+ i,
+ j + 1);
+ }
+ }
+
+ return;
+}
+
+map NeuralNetworx::getNeuronProperties(size_t layer,
+ size_t neuron) const
+{
+ map properties;
+
+ properties["y"] = layers.at(layer).y(neuron);
+ if (layers.at(layer).numNeuronsPrevLayer > 0)
+ {
+ properties["x"] = layers.at(layer).x(neuron);
+ properties["dydx"] = layers.at(layer).dydx(neuron);
+ properties["d2ydx2"] = layers.at(layer).d2ydx2(neuron);
+ }
+
+ return properties;
+}
+
+vector> NeuralNetworx::getLayerLimits() const
+{
+ vector> limits;
+
+ for (size_t i = 0; i < numLayers - 2; ++i)
+ {
+ limits.push_back(make_pair(offsetLayer.at(i),
+ offsetLayer.at(i + 1) - 1));
+
+ }
+ limits.push_back(make_pair(offsetLayer.at(numLayers - 2),
+ numConnections - 1));
+
+ return limits;
+}
+
+vector> NeuralNetworx::getNeuronLimits() const
+{
+ vector> limits;
+
+ for (size_t i = 0; i < numLayers - 1; ++i)
+ {
+ size_t const n = layers.at(i + 1).numNeurons;
+ for (size_t j = 0; j < n - 1; ++j)
+ {
+ limits.push_back(make_pair(offsetNeuron.at(i).at(j),
+ offsetNeuron.at(i).at(j + 1) - 1));
+ }
+ if (i == numLayers - 1)
+ {
+ limits.push_back(make_pair(offsetNeuron.at(i).at(n - 1),
+ numConnections - 1));
+ }
+ else
+ {
+ limits.push_back(make_pair(offsetNeuron.at(i).at(n - 1),
+ offsetNeuron.at(i + 1).at(0) - 1));
+ }
+ }
+
+ return limits;
+}
+
+vector NeuralNetworx::info() const
+{
+ vector v;
+ Eigen::Index maxNeurons = 0;
+
+ v.push_back(strpr("Number of layers : %6zu\n", numLayers));
+ v.push_back(strpr("Number of neurons : %6zu\n", numNeurons));
+ v.push_back(strpr("Number of connections: %6zu\n", numConnections));
+ v.push_back(strpr("Number of weights : %6zu\n", numWeights));
+ v.push_back(strpr("Number of biases : %6zu\n", numBiases));
+ v.push_back(strpr("Architecture "));
+ for (size_t i = 0; i < layers.size(); ++i)
+ {
+ maxNeurons = max(layers.at(i).y.size(),
+ maxNeurons);
+ }
+ v.push_back("\n");
+ v.push_back("-----------------------------------------"
+ "--------------------------------------\n");
+
+ for (size_t i = 0; i < static_cast(maxNeurons); ++i)
+ {
+ v.push_back(strpr("%4d", i + 1));
+ string s = "";
+ for (size_t j = 0; j < layers.size(); ++j)
+ {
+ if (i < static_cast(layers.at(j).y.size()))
+ {
+ if (j == 0)
+ {
+ s += strpr(" %3s", "G");
+ }
+ else
+ {
+ s += strpr(" %3s",
+ stringFromActivation(layers.at(j).fa).c_str());
+ }
+ }
+ else
+ {
+ s += " ";
+ }
+ }
+ v.push_back(s += "\n");
+ }
+
+ return v;
+}
+
+void NeuralNetworx::initialize(vector numNeuronsPerLayer,
+ vector activationPerLayer)
+{
+ if (numNeuronsPerLayer.size() < 2)
+ {
+ throw runtime_error(strpr("ERROR: Neural network must have at least "
+ "two layers (intput = %zu)\n.",
+ numNeuronsPerLayer.size()));
+ }
+ if (numNeuronsPerLayer.size() != activationPerLayer.size())
+ {
+ throw runtime_error("ERROR: Number of layers is inconsistent.\n");
+ }
+
+ // Input layer has no neurons from previous layer.
+ numNeuronsPerLayer.insert(numNeuronsPerLayer.begin(), 0);
+ numLayers = 0;
+ layers.clear();
+ for (size_t i = 0; i < activationPerLayer.size(); ++i)
+ {
+ if (numNeuronsPerLayer.at(i + 1) < 1)
+ {
+ throw runtime_error("ERROR: Layer needs at least one neuron.\n");
+ }
+ layers.push_back(Layer(numNeuronsPerLayer.at(i + 1),
+ numNeuronsPerLayer.at(i),
+ activationPerLayer.at(i)));
+ numLayers++;
+ }
+
+ // Compute number of connections, weights and biases.
+ numNeurons = 0;
+ numConnections = 0;
+ numWeights = 0;
+ numBiases = 0;
+ for (auto const& l : layers)
+ {
+ numNeurons += l.y.size();
+ numConnections += l.w.size() + l.b.size();
+ numWeights += l.w.size();
+ numBiases += l.b.size();
+ }
+
+ // Compute layer and neuron offsets for combined connections vector.
+ offsetLayer.resize(numLayers - 1, 0);
+ offsetNeuron.resize(numLayers - 1);
+ size_t count = 0;
+ for (size_t i = 1; i < numLayers; ++i)
+ {
+ offsetLayer.push_back(count);
+ for (size_t j = 0; j < layers.at(i).numNeurons; ++j)
+ {
+ offsetNeuron.at(i - 1).push_back(count);
+ // Weights of neuron j.
+ count += layers.at(i).numNeuronsPrevLayer;
+ // Bias of neuron j.
+ count++;
+ }
+ }
+
+ return;
+}
+
+
+string nnp::stringFromActivation(NeuralNetworx::Activation a)
+{
+ string c = "";
+
+ if (a == NeuralNetworx::Activation::IDENTITY) c = "l";
+ else if (a == NeuralNetworx::Activation::TANH) c = "t";
+ else if (a == NeuralNetworx::Activation::LOGISTIC) c = "s";
+ else if (a == NeuralNetworx::Activation::SOFTPLUS) c = "p";
+ else if (a == NeuralNetworx::Activation::RELU) c = "r";
+ else if (a == NeuralNetworx::Activation::GAUSSIAN) c = "g";
+ else if (a == NeuralNetworx::Activation::COS) c = "c";
+ else if (a == NeuralNetworx::Activation::REVLOGISTIC) c = "S";
+ else if (a == NeuralNetworx::Activation::EXP) c = "e";
+ else if (a == NeuralNetworx::Activation::HARMONIC) c = "h";
+ else
+ {
+ throw runtime_error("ERROR: Unknown activation function.\n");
+ }
+
+ return c;
+}
+
+NeuralNetworx::Activation nnp::activationFromString(string c)
+{
+ NeuralNetworx::Activation a;
+
+ if (c == "l") a = NeuralNetworx::Activation::IDENTITY;
+ else if (c == "t") a = NeuralNetworx::Activation::TANH;
+ else if (c == "s") a = NeuralNetworx::Activation::LOGISTIC;
+ else if (c == "p") a = NeuralNetworx::Activation::SOFTPLUS;
+ else if (c == "r") a = NeuralNetworx::Activation::RELU;
+ else if (c == "g") a = NeuralNetworx::Activation::GAUSSIAN;
+ else if (c == "c") a = NeuralNetworx::Activation::COS;
+ else if (c == "S") a = NeuralNetworx::Activation::REVLOGISTIC;
+ else if (c == "e") a = NeuralNetworx::Activation::EXP;
+ else if (c == "h") a = NeuralNetworx::Activation::HARMONIC;
+ else
+ {
+ throw runtime_error("ERROR: Unknown activation function.\n");
+ }
+
+ return a;
+}
diff --git a/src/libnnp/NeuralNetworx.h b/src/libnnp/NeuralNetworx.h
new file mode 100644
index 000000000..bde12ec9f
--- /dev/null
+++ b/src/libnnp/NeuralNetworx.h
@@ -0,0 +1,380 @@
+// n2p2 - A neural network potential package
+// Copyright (C) 2018 Andreas Singraber (University of Vienna)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#ifndef NEURALNETWORX_H
+#define NEURALNETWORX_H
+
+#include // std::size_t
+#include