/**************************************************************************** Neural Network Library Copyright (C) 1998 Daniel Franklin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ****************************************************************************/ #include "nnwork.h" #include #include #include #include #include #include #include #include #include // This class implements a simple three-layer backpropagation network. // Calculate the sigmoid function. This is the transfer function of the nodes. double sigmoid (double data) { return (1. / (1. + exp (-data))); } // Constructors: create the network. // Arguments are the dims of the network nnwork::nnwork (int input, int hidden, int output) { input_size = input; hidden_size = hidden; output_size = output; srand (time (0)); hidden_nodes = new nnlayer (hidden_size, input_size); assert (hidden_nodes); output_nodes = new nnlayer (output_size, hidden_size); assert (output_nodes); } // Empty network nnwork::nnwork () { input_size = 0; hidden_size = 0; output_size = 0; hidden_nodes = 0; output_nodes = 0; } // Destructor: destroys the network. nnwork::~nnwork () { if (output_nodes) delete output_nodes; if (hidden_nodes) delete hidden_nodes; } // The following function is useful if you want to find out the dimensions // of a network loaded from a file, or whatever. ALL adds up everything. int nnwork::get_layersize (int layer) { switch (layer) { case (ALL): return input_size + hidden_size + output_size; case (INPUT): return input_size; case (HIDDEN): return hidden_size; case (OUTPUT): return output_size; default: { fprintf(stderr, "Warning: no such layer: %d\n", layer); return -1; } } } // Training routine for the network. Uses data as input, compares output with // desired output, computes errors, adjusts weights attached to each node, // then repeats until the mean squared error at the output is less than // max_MSE. The learning rate is eta. void nnwork::train (double data [], double desired [], double max_MSE, double eta, int maxiter) { double *output, *output_weight_delta, *hidden_weight_delta; double MSE, MSE_max = max_MSE * 2., sum; // slight speed enhancement int i, j, k; #ifdef WAFFLY long n = 0; #endif if (input_size == 0 || hidden_size == 0 || output_size == 0) { fprintf(stderr, "Warning: supid dimensions. No action taken.\n"); return; } output = new double [output_size]; assert (output); output_weight_delta = new double [output_size]; assert (output_weight_delta); hidden_weight_delta = new double [hidden_size]; assert (hidden_weight_delta); // Keep going while the Mean Square Error is too high. int iter = 0; while (iter < maxiter) { iter++; run (data, output); // Firstly calculate the output layer error terms for (k = 0, MSE = 0; k < output_size; k++) { output_weight_delta [k] = desired [k] - output [k]; MSE += output_weight_delta [k] * output_weight_delta [k]; output_weight_delta [k] *= output [k] * (1 - output [k]); } #ifdef WAFFLY n++; if (!(n % 10)) fprintf(stderr, "2*MSE (every 10th) currently is: %.2f\n", MSE); #endif if (MSE < MSE_max) break; // And the hidden layer error terms for (j = 0; j < hidden_size; j++) { for (k = 0, sum = 0; k < output_size; k++) sum += output_weight_delta [k] * output_nodes -> nodes [k].weights [j]; hidden_weight_delta [j] = sum * hidden_nodes -> nodes [j].output * (1 - hidden_nodes -> nodes [j].output); } // Now update the output weights. for (k = 0; k < output_size; k++) for (j = 0; j < hidden_size; j++) output_nodes -> nodes [k].weights [j] += eta * output_weight_delta [k] * hidden_nodes -> nodes [j].output; // And the hidden weights. for (j = 0; j < hidden_size; j++) for (i = 0; i < input_size; i++) hidden_nodes -> nodes [j].weights [i] += eta * hidden_weight_delta [j] * data [i]; } delete [] output; delete [] output_weight_delta; delete [] hidden_weight_delta; } // Should only be applied after the network has been suitably trained. When // it is ready, data are applied at the input, and the output (basically a // boolean output but could be different) goes into result. Naturally, data // and result are the same size as the input and output vectors respectively. void nnwork::run (double data [], double result []) { int i, j, k; double sum; if (input_size == 0 || hidden_size == 0 || output_size == 0) { fprintf(stderr, "Warning: stupid dimensions. No action taken.\n"); return; } for (j = 0; j < hidden_size; j++) { sum = 0; // Calculate the output value for (i = 0; i < input_size; i++) sum += hidden_nodes -> nodes [j].weights [i] * data [i]; hidden_nodes -> nodes [j].output = sigmoid (sum); } for (k = 0; k < output_size; k++) { sum = 0; // Calculate the output value for (j = 0; j < hidden_size; j++) sum += output_nodes -> nodes [k].weights [j] * hidden_nodes -> nodes [j].output; result [k] = sigmoid (sum); } }