LCOV - code coverage report
Current view: top level - annfunc - ANN.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 137 141 97.2 %
Date: 2026-11-05 10:19:04 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             : MIT License
       3             : 
       4             : Copyright (c) 2019 Wei Chen and Andrew Ferguson
       5             : 
       6             : Permission is hereby granted, free of charge, to any person obtaining a copy
       7             : of this software and associated documentation files (the "Software"), to deal
       8             : in the Software without restriction, including without limitation the rights
       9             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      10             : copies of the Software, and to permit persons to whom the Software is
      11             : furnished to do so, subject to the following conditions:
      12             : 
      13             : The above copyright notice and this permission notice shall be included in all
      14             : copies or substantial portions of the Software.
      15             : 
      16             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      17             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      18             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      19             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      20             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      21             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      22             : SOFTWARE.
      23             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      24             : #include "function/Function.h"
      25             : #include "core/ActionRegister.h"
      26             : #include "cassert"
      27             : 
      28             : #include <string>
      29             : #include <cmath>
      30             : #include <iostream>
      31             : 
      32             : using namespace std;
      33             : 
      34             : 
      35             : namespace PLMD {
      36             : namespace function {
      37             : namespace annfunc {
      38             : 
      39             : //+PLUMEDOC ANNMOD_Function ANN
      40             : /*
      41             : Calculates the ANN-function.
      42             : 
      43             : This module implements ANN class, which is a subclass of Function class.
      44             : ANN class takes multi-dimensional arrays as inputs for a fully-connected feedforward neural network
      45             : with specified neural network weights and generates corresponding outputs.
      46             : The ANN outputs can be used as collective variables, inputs for other collective variables,
      47             : or inputs for data analysis tools.
      48             : 
      49             : ## Examples
      50             : 
      51             : Assume we have an ANN with numbers of nodes being [2, 3, 1], and weights connecting layer 0 and 1 are
      52             : 
      53             : [[1,2], [3,4], [5,6]]
      54             : 
      55             : weights connecting layer 1 and 2 are
      56             : 
      57             : [[7,8,9]]
      58             : 
      59             : Bias for layer 1 and 2 are [10, 11, 12] and [13], respectively.
      60             : 
      61             : All activation functions are Tanh.
      62             : 
      63             : Then if input variables are l_0_out_0, l_0_out_1, the corresponding ANN function object can be defined using
      64             : following plumed script:
      65             : 
      66             : ```plumed
      67             : ann: ANN ...
      68             : ARG=l_0_out_0,l_0_out_1
      69             : NUM_LAYERS=3
      70             : NUM_NODES=2,3,1
      71             : ACTIVATIONS=Tanh,Tanh
      72             : WEIGHTS0=1,2,3,4,5,6
      73             : WEIGHTS1=7,8,9
      74             : BIASES0=10,11,12
      75             : BIASES1=13
      76             : ...
      77             : ```
      78             : 
      79             : To access its components, we use "ann.node-0", "ann.node-1", ..., which represents the components of neural network outputs.
      80             : 
      81             : 
      82             : */
      83             : //+ENDPLUMEDOC
      84             : 
      85             : class ANN : public Function {
      86             : private:
      87             :   int num_layers;
      88             :   vector<int> num_nodes;
      89             :   vector<string> activations;   // activation functions
      90             :   vector<vector<double> > weights;  // flattened weight arrays
      91             :   vector<vector<double> > biases;
      92             :   vector<vector<double> > output_of_each_layer;
      93             :   vector<vector<double> > input_of_each_layer;
      94             :   vector<double** > coeff;  // weight matrix arrays, reshaped from "weights"
      95             : 
      96             : public:
      97             :   static void registerKeywords( Keywords& keys );
      98             :   explicit ANN(const ActionOptions&);
      99             :   virtual void calculate();
     100             :   void calculate_output_of_each_layer(const vector<double>& input);
     101             :   void back_prop(vector<vector<double> >& derivatives_of_each_layer, int index_of_output_component);
     102             : };
     103             : 
     104             : PLUMED_REGISTER_ACTION(ANN,"ANN")
     105             : 
     106           9 : void ANN::registerKeywords( Keywords& keys ) {
     107           9 :   Function::registerKeywords(keys);
     108           9 :   keys.use("PERIODIC");
     109           9 :   keys.add("compulsory", "NUM_LAYERS", "number of layers of the neural network");
     110           9 :   keys.add("compulsory", "NUM_NODES", "numbers of nodes in each layer of the neural network");
     111           9 :   keys.add("compulsory", "ACTIVATIONS", "activation functions for the neural network");
     112           9 :   keys.add("numbered", "WEIGHTS", "flattened weight arrays connecting adjacent layers, "
     113             :            "WEIGHTS0 represents flattened weight array connecting layer 0 and layer 1, "
     114             :            "WEIGHTS1 represents flattened weight array connecting layer 1 and layer 2, ...");
     115           9 :   keys.add("numbered", "BIASES", "bias array for each layer of the neural network, "
     116             :            "BIASES0 represents bias array for layer 1, BIASES1 represents bias array for layer 2, ...");
     117             :   // since v2.2 plumed requires all components be registered
     118          18 :   keys.addOutputComponent("node", "default", "scalar", "components of ANN outputs");
     119           9 : }
     120             : 
     121           5 : ANN::ANN(const ActionOptions&ao):
     122             :   Action(ao),
     123           5 :   Function(ao) {
     124           5 :   parse("NUM_LAYERS", num_layers);
     125           5 :   num_nodes = vector<int>(num_layers);
     126           5 :   activations = vector<string>(num_layers - 1);
     127           5 :   output_of_each_layer = vector<vector<double> >(num_layers);
     128           5 :   input_of_each_layer = vector<vector<double> >(num_layers);
     129           5 :   coeff = vector<double** >(num_layers - 1);
     130           5 :   parseVector("NUM_NODES", num_nodes);
     131           5 :   parseVector("ACTIVATIONS", activations);
     132           5 :   log.printf("\nactivations = ");
     133          15 :   for (const auto & ss: activations) {
     134          10 :     log.printf("%s, ", ss.c_str());
     135             :   }
     136           5 :   log.printf("\nnum_nodes = ");
     137          20 :   for (auto ss: num_nodes) {
     138          15 :     log.printf("%d, ", ss);
     139             :   }
     140           5 :   vector<double> temp_single_coeff, temp_single_bias;
     141          10 :   for (int ii = 0; ; ii ++) {
     142             :     // parse coeff
     143          30 :     if( !parseNumberedVector("WEIGHTS", ii, temp_single_coeff) ) {
     144           5 :       temp_single_coeff=weights[ii-1];
     145             :       break;
     146             :     }
     147          10 :     weights.push_back(temp_single_coeff);
     148          10 :     log.printf("size of temp_single_coeff = %lu\n", temp_single_coeff.size());
     149          10 :     log.printf("size of weights = %lu\n", weights.size());
     150             :     // parse bias
     151          20 :     if( !parseNumberedVector("BIASES", ii, temp_single_bias) ) {
     152           0 :       temp_single_bias=biases[ii-1];
     153             :     }
     154          10 :     biases.push_back(temp_single_bias);
     155          10 :     log.printf("size of temp_single_bias = %lu\n", temp_single_bias.size());
     156          10 :     log.printf("size of biases = %lu\n", biases.size());
     157             :   }
     158             : 
     159           5 :   if(static_cast<int>(getNumberOfArguments()) != num_nodes[0]) {
     160           0 :     error("Number of arguments is wrong");
     161             :   }
     162             : 
     163           5 :   auto temp_coeff = weights;
     164          15 :   for (int ii = 0; ii < num_layers - 1; ii ++) {
     165             :     int num_of_rows, num_of_cols; // num of rows/cols for the coeff matrix of this connection
     166          10 :     num_of_rows = num_nodes[ii + 1];
     167          10 :     num_of_cols = num_nodes[ii];
     168             :     assert (num_of_rows * num_of_cols == static_cast<int>(temp_coeff[ii].size())); // check whether the size matches
     169             :     // create a 2d array to hold coefficients
     170          10 :     coeff[ii] = new double*[num_of_rows];
     171          32 :     for (int kk = 0; kk < num_of_rows; kk ++) {
     172          22 :       coeff[ii][kk] = new double[num_of_cols];
     173             :     }
     174          61 :     for (int jj = 0; jj < static_cast<int>(temp_coeff[ii].size()); jj ++) {
     175          51 :       coeff[ii][jj / num_of_cols][jj % num_of_cols] = temp_coeff[ii][jj];
     176             :     }
     177             :   }
     178             :   // check coeff
     179          15 :   for (int ii = 0; ii < num_layers - 1; ii ++) {
     180          10 :     log.printf("coeff %d = \n", ii);
     181          32 :     for (int jj = 0; jj < num_nodes[ii + 1]; jj ++) {
     182          73 :       for (int kk = 0; kk < num_nodes[ii]; kk ++) {
     183          51 :         log.printf("%f ", coeff[ii][jj][kk]);
     184             :       }
     185          22 :       log.printf("\n");
     186             :     }
     187             :   }
     188             :   // check bias
     189          15 :   for (int ii = 0; ii < num_layers - 1; ii ++) {
     190          10 :     log.printf("bias %d = \n", ii);
     191          32 :     for (int jj = 0; jj < num_nodes[ii + 1]; jj ++) {
     192          22 :       log.printf("%f ", biases[ii][jj]);
     193             :     }
     194          10 :     log.printf("\n");
     195             :   }
     196           5 :   log.printf("initialization ended\n");
     197             :   // create components
     198          12 :   for (int ii = 0; ii < num_nodes[num_layers - 1]; ii ++) {
     199          14 :     string name_of_this_component = "node-" + to_string(ii);
     200           7 :     addComponentWithDerivatives(name_of_this_component);
     201           7 :     componentIsNotPeriodic(name_of_this_component);
     202             :   }
     203           5 :   checkRead();
     204           5 : }
     205             : 
     206        1638 : void ANN::calculate_output_of_each_layer(const vector<double>& input) {
     207             :   // first layer
     208        1638 :   output_of_each_layer[0] = input;
     209             :   // following layers
     210        4914 :   for(int ii = 1; ii < static_cast<int>(num_nodes.size()); ii ++) {
     211        3276 :     output_of_each_layer[ii].resize(num_nodes[ii]);
     212        3276 :     input_of_each_layer[ii].resize(num_nodes[ii]);
     213             :     // first calculate input
     214       10374 :     for (int jj = 0; jj < num_nodes[ii]; jj ++) {
     215        7098 :       input_of_each_layer[ii][jj] = biases[ii - 1][jj];  // add bias term
     216       23478 :       for (int kk = 0; kk < num_nodes[ii - 1]; kk ++) {
     217       16380 :         input_of_each_layer[ii][jj] += coeff[ii - 1][jj][kk] * output_of_each_layer[ii - 1][kk];
     218             :       }
     219             :     }
     220             :     // then get output
     221        3276 :     if (activations[ii - 1] == string("Linear")) {
     222        1092 :       for(int jj = 0; jj < num_nodes[ii]; jj ++) {
     223         546 :         output_of_each_layer[ii][jj] = input_of_each_layer[ii][jj];
     224             :       }
     225        2730 :     } else if (activations[ii - 1] == string("Tanh")) {
     226        7644 :       for(int jj = 0; jj < num_nodes[ii]; jj ++) {
     227        5460 :         output_of_each_layer[ii][jj] = tanh(input_of_each_layer[ii][jj]);
     228             :       }
     229         546 :     } else if (activations[ii - 1] == string("Circular")) {
     230             :       assert (num_nodes[ii] % 2 == 0);
     231        1092 :       for(int jj = 0; jj < num_nodes[ii] / 2; jj ++) {
     232         546 :         double radius = sqrt(input_of_each_layer[ii][2 * jj] * input_of_each_layer[ii][2 * jj]
     233         546 :                              +input_of_each_layer[ii][2 * jj + 1] * input_of_each_layer[ii][2 * jj + 1]);
     234         546 :         output_of_each_layer[ii][2 * jj] = input_of_each_layer[ii][2 * jj] / radius;
     235         546 :         output_of_each_layer[ii][2 * jj + 1] = input_of_each_layer[ii][2 * jj + 1] / radius;
     236             : 
     237             :       }
     238             :     } else {
     239             :       printf("layer type not found!\n\n");
     240           0 :       return;
     241             :     }
     242             :   }
     243             : #ifdef DEBUG_2
     244             :   // print out the result for debugging
     245             :   printf("output_of_each_layer = \n");
     246             :   for (int ii = 0; ii < num_layers; ii ++) {
     247             :     printf("layer[%d]: ", ii);
     248             :     if (ii != 0) {
     249             :       cout << activations[ii - 1] << "\t";
     250             :     } else {
     251             :       cout << "input \t" ;
     252             :     }
     253             :     for (int jj = 0; jj < num_nodes[ii]; jj ++) {
     254             :       printf("%lf\t", output_of_each_layer[ii][jj]);
     255             :     }
     256             :     printf("\n");
     257             :   }
     258             :   printf("\n");
     259             : #endif
     260             :   return;
     261             : }
     262             : 
     263        2184 : void ANN::back_prop(vector<vector<double> >& derivatives_of_each_layer, int index_of_output_component) {
     264        2184 :   derivatives_of_each_layer = output_of_each_layer;  // the data structure and size should be the same, so I simply deep copy it
     265             :   // first calculate derivatives for bottleneck layer
     266        5460 :   for (int ii = 0; ii < num_nodes[num_nodes.size() - 1]; ii ++ ) {
     267        3276 :     if (ii == index_of_output_component) {
     268        2184 :       derivatives_of_each_layer[num_nodes.size() - 1][ii] = 1;
     269             :     } else {
     270        1092 :       derivatives_of_each_layer[num_nodes.size() - 1][ii] = 0;
     271             :     }
     272             :   }
     273             :   // the use back propagation to calculate derivatives for previous layers
     274        6552 :   for (int jj = num_nodes.size() - 2; jj >= 0; jj --) {
     275        4368 :     if (activations[jj] == string("Circular")) {
     276        1092 :       vector<double> temp_derivative_of_input_for_this_layer;
     277        1092 :       temp_derivative_of_input_for_this_layer.resize(num_nodes[jj + 1]);
     278             : #ifdef DEBUG
     279             :       assert (num_nodes[jj + 1] % 2 == 0);
     280             : #endif
     281             :       // first calculate the derivative of input from derivative of output of this circular layer
     282        2184 :       for(int ii = 0; ii < num_nodes[jj + 1] / 2; ii ++) {
     283        1092 :         double x_p = input_of_each_layer[jj + 1][2 * ii];
     284        1092 :         double x_q = input_of_each_layer[jj + 1][2 * ii + 1];
     285        1092 :         double radius = sqrt(x_p * x_p + x_q * x_q);
     286        1092 :         temp_derivative_of_input_for_this_layer[2 * ii] = x_q / (radius * radius * radius)
     287        1092 :             * (x_q * derivatives_of_each_layer[jj + 1][2 * ii]
     288        1092 :                - x_p * derivatives_of_each_layer[jj + 1][2 * ii + 1]);
     289        1092 :         temp_derivative_of_input_for_this_layer[2 * ii + 1] = x_p / (radius * radius * radius)
     290        1092 :             * (x_p * derivatives_of_each_layer[jj + 1][2 * ii + 1]
     291        1092 :                - x_q * derivatives_of_each_layer[jj + 1][2 * ii]);
     292             :       }
     293             : #ifdef DEBUG
     294             :       for (int mm = 0; mm < num_nodes[jj + 1]; mm ++) {
     295             :         printf("temp_derivative_of_input_for_this_layer[%d] = %lf\n", mm, temp_derivative_of_input_for_this_layer[mm]);
     296             :         printf("derivatives_of_each_layer[%d + 1][%d] = %lf\n", jj, mm, derivatives_of_each_layer[jj + 1][mm]);
     297             :       }
     298             : #endif
     299             :       // the calculate the derivative of output of layer jj, from derivative of input of layer (jj + 1)
     300        4368 :       for (int mm = 0; mm < num_nodes[jj]; mm ++) {
     301        3276 :         derivatives_of_each_layer[jj][mm] = 0;
     302        9828 :         for (int kk = 0; kk < num_nodes[jj + 1]; kk ++) {
     303        6552 :           derivatives_of_each_layer[jj][mm] += coeff[jj][kk][mm] \
     304        6552 :                                                * temp_derivative_of_input_for_this_layer[kk];
     305             : #ifdef DEBUG
     306             :           printf("derivatives_of_each_layer[%d][%d] = %lf\n", jj, mm, derivatives_of_each_layer[jj][mm]);
     307             :           printf("coeff[%d][%d][%d] = %lf\n", jj, kk, mm, coeff[jj][kk][mm]);
     308             : #endif
     309             :         }
     310             :       }
     311             :       // TODO: should be fine, pass all tests, although there seems to be some problems here previously
     312        1092 :     } else {
     313       10920 :       for (int mm = 0; mm < num_nodes[jj]; mm ++) {
     314        7644 :         derivatives_of_each_layer[jj][mm] = 0;
     315       24024 :         for (int kk = 0; kk < num_nodes[jj + 1]; kk ++) {
     316       16380 :           if (activations[jj] == string("Tanh")) {
     317       14742 :             derivatives_of_each_layer[jj][mm] += derivatives_of_each_layer[jj + 1][kk] \
     318       14742 :                                                  * coeff[jj][kk][mm] \
     319       14742 :                                                  * (1 - output_of_each_layer[jj + 1][kk] * output_of_each_layer[jj + 1][kk]);
     320        1638 :           } else if (activations[jj] == string("Linear")) {
     321        1638 :             derivatives_of_each_layer[jj][mm] += derivatives_of_each_layer[jj + 1][kk] \
     322        1638 :                                                  * coeff[jj][kk][mm] \
     323        1638 :                                                  * 1;
     324             :           } else {
     325             :             printf("layer type not found!\n\n");
     326           0 :             return;
     327             :           }
     328             :         }
     329             :       }
     330             :     }
     331             :   }
     332             : #ifdef DEBUG
     333             :   // print out the result for debugging
     334             :   printf("derivatives_of_each_layer = \n");
     335             :   for (int ii = 0; ii < num_layers; ii ++) {
     336             :     printf("layer[%d]: ", ii);
     337             :     for (int jj = 0; jj < num_nodes[ii]; jj ++) {
     338             :       printf("%lf\t", derivatives_of_each_layer[ii][jj]);
     339             :     }
     340             :     printf("\n");
     341             :   }
     342             :   printf("\n");
     343             : #endif
     344             :   return;
     345             : }
     346             : 
     347        1638 : void ANN::calculate() {
     348             : 
     349        1638 :   vector<double> input_layer_data(num_nodes[0]);
     350        4914 :   for (int ii = 0; ii < num_nodes[0]; ii ++) {
     351        3276 :     input_layer_data[ii] = getArgument(ii);
     352             :   }
     353             : 
     354        1638 :   calculate_output_of_each_layer(input_layer_data);
     355        1638 :   vector<vector<double> > derivatives_of_each_layer;
     356             : 
     357        3822 :   for (int ii = 0; ii < num_nodes[num_layers - 1]; ii ++) {
     358        2184 :     back_prop(derivatives_of_each_layer, ii);
     359        2184 :     string name_of_this_component = "node-" + to_string(ii);
     360        2184 :     Value* value_new=getPntrToComponent(name_of_this_component);
     361        2184 :     value_new -> set(output_of_each_layer[num_layers - 1][ii]);
     362        6552 :     for (int jj = 0; jj < num_nodes[0]; jj ++) {
     363        4368 :       value_new -> setDerivative(jj, derivatives_of_each_layer[0][jj]);  // TODO: setDerivative or addDerivative?
     364             :     }
     365             : #ifdef DEBUG_3
     366             :     printf("derivatives = ");
     367             :     for (int jj = 0; jj < num_nodes[0]; jj ++) {
     368             :       printf("%f ", value_new -> getDerivative(jj));
     369             :     }
     370             :     printf("\n");
     371             : #endif
     372             :   }
     373             : 
     374        1638 : }
     375             : 
     376             : }
     377             : }
     378             : }

Generated by: LCOV version 1.16