# Back-propagation Neural Net

By , 28 Mar 2006

## Introduction

The class `CBackProp` encapsulates a feed-forward neural network and a back-propagation algorithm to train it. This article is intended for those who already have some idea about neural networks and back-propagation algorithms. If you are not familiar with these, I suggest going through some material first.

## Background

This is part of an academic project which I worked on during my final semester back in college, for which I needed to find the optimal number and size of hidden layers and learning parameters for different data sets. It wasn't easy finalizing the data structure for the neural net and getting the back-propagation algorithm to work. The motivation for this article is to save someone else the same effort.

Here's a little disclaimer... This article describes a simplistic implementation of the algorithm, and does not sufficiently elaborate on the algorithm. There is a lot of room to improve the included code (like adding exception handling :-), and for many steps, there is a lot more reasoning required than I have included, e.g., values that I have chosen for parameters, and the number of layers/the neurons in each layer are for demonstrating the usage and may not be optimal. To know more about these, I suggest going to:

## Using the code

Typically, the usage involves the following steps:

• Create the net using `CBackProp::CBackProp(int nl,int *sz,double b,double a)`
• Apply the back-propagation algorithm - train the net by passing the input and the desired output, to `void CBackProp::bpgt(double *in,double *tgt)` in a loop, until the Mean square error, which is obtained by `CBackProp::double mse(double *tgt)`, gets reduced to an acceptable value.
• Use the trained net to make predictions by feed-forwarding the input data using `void CBackProp::ffwd(double *in)`.

The following is a description of the sample program that I have included.

## One step at a time...

#### Setting the objective:

We will try to teach our net to crack the binary A XOR B XOR C. XOR is an obvious choice, it is not linearly separable hence requires hidden layers and cannot be learned by a single perception.

A training data set consists of multiple records, where each record contains fields which are input to the net, followed by fields consisting of the desired output. In this example, it's three inputs + one desired output.

```// prepare XOR training data
double data[][4]={//    I  XOR  I  XOR  I   =   O
//--------------------------------
0,      0,      0,      0,
0,      0,      1,      1,
0,      1,      0,      1,
0,      1,      1,      0,
1,      0,      0,      1,
1,      0,      1,      0,
1,      1,      0,      0,
1,      1,      1,      1 };```

#### Configuration:

Next, we need to specify a suitable structure for our neural network, i.e., the number of hidden layers it should have and the number of neurons in each layer. Then, we specify suitable values for other parameters: learning rate - `beta`, we may also want to specify momentum - `alpha` (this one is optional), and Threshold - `thresh` (target mean square error, training stops once it is achieved else continues for `num_iter` number of times).

Let's define a net with 4 layers having 3,3,3, and 1 neuron respectively. Since the first layer is the input layer, i.e., simply a placeholder for the input parameters, it has to be the same size as the number of input parameters, and the last layer being the output layer must be same size as the number of outputs - in our example, these are 3 and 1. Those other layers in between are called hidden layers.

```int numLayers = 4, lSz[4] = {3,3,3,1};
double beta = 0.2, alpha = 0.1, thresh = 0.00001;
long num_iter = 500000;```

#### Creating the net:

`CBackProp *bp = new CBackProp(numLayers, lSz, beta, alpha);`

#### Training:

```for (long i=0; i < num_iter ; i++)
{
bp->bpgt(data[i%8], &data[i%8][3]);

if( bp->mse(&data[i%8][3]) < thresh)
break; // mse < threshold - we are done training!!!
}```

#### Let's test its wisdom:

We prepare test data, which here is the same as training data minus the desired output.

```double testData[][3]={ //  I  XOR  I  XOR  I  =  ?
//----------------------
0,      0,      0,
0,      0,      1,
0,      1,      0,
0,      1,      1,
1,      0,      0,
1,      0,      1,
1,      1,      0,
1,      1,      1};```

Now, using the trained network to make predictions on our test data....

```for ( i = 0 ; i < 8 ; i++ )
{
bp->ffwd(testData[i]);
cout << testData[i][0]<< "  "
<< testData[i][1]<< "  "
<< testData[i][2]<< "  "
<< bp->Out(0) << endl;
}```

## Now a peek inside:

#### Storage for the neural net

I think the following code has ample comments and is self-explanatory...

```class CBackProp{

//      output of each neuron
double **out;

//      delta error value for each neuron
double **delta;

//      3-D array to store weights for each neuron
double ***weight;

//      no of layers in net including input layer
int numl;

//      array of numl elements to store size of each layer
int *lsize;

//      learning rate
double beta;

//      momentum
double alpha;

//      storage for weight-change made in previous epoch
double ***prevDwt;

//      sigmoid function
double sigmoid(double in);

public:

~CBackProp();

//      initializes and allocates memory
CBackProp(int nl,int *sz,double b,double a);

//      backpropogates error for one set of input
void bpgt(double *in,double *tgt);

//      feed forwards activations for one set of inputs
void ffwd(double *in);

//      returns mean square error of the net
double mse(double *tgt);

//      returns i'th output of the net
double Out(int i) const;
};```

Some alternative implementations define a separate class for layer / neuron / connection, and then put those together to form a neural network. Although it is definitely a cleaner approach, I decided to use `double ***` and `double **` to store weights and output etc. by allocating the exact amount of memory required, due to:

• The ease it provides while implementing the learning algorithm, for instance, for weight at the connection between (i-1)th layer's jth Neuron and ith layer's kth neuron, I personally prefer `w[i][k][j]` (than something like `net.layer[i].neuron[k].getWeight(j)`). The output of the i`th` neuron of the j`th` layer is `out[i][j]`, and so on.
• Another advantage I felt is the flexibility of choosing any number and size of the layers.
```// initializes and allocates memory
CBackProp::CBackProp(int nl,int *sz,double b,double a):beta(b),alpha(a)
{

// Note that the following are unused,
//
// delta[0]
// weight[0]
// prevDwt[0]

//  I did this intentionally to maintain
//  consistency in numbering the layers.
//  Since for a net having n layers,
//  input layer is referred to as 0th layer,
//  first hidden layer as 1st layer
//  and the nth layer as output layer. And
//  first (0th) layer just stores the inputs
//  hence there is no delta or weight
//  values associated to it.

//    set no of layers and their sizes
numl=nl;
lsize=new int[numl];

for(int i=0;i<numl;i++){
lsize[i]=sz[i];
}

//    allocate memory for output of each neuron
out = new double*[numl];

for( i=0;i<numl;i++){
out[i]=new double[lsize[i]];
}

//    allocate memory for delta
delta = new double*[numl];

for(i=1;i<numl;i++){
delta[i]=new double[lsize[i]];
}

//    allocate memory for weights
weight = new double**[numl];

for(i=1;i<numl;i++){
weight[i]=new double*[lsize[i]];
}
for(i=1;i<numl;i++){
for(int j=0;j<lsize[i];j++){
weight[i][j]=new double[lsize[i-1]+1];
}
}

//    allocate memory for previous weights
prevDwt = new double**[numl];

for(i=1;i<numl;i++){
prevDwt[i]=new double*[lsize[i]];

}
for(i=1;i<numl;i++){
for(int j=0;j<lsize[i];j++){
prevDwt[i][j]=new double[lsize[i-1]+1];
}
}

//    seed and assign random weights
srand((unsigned)(time(NULL)));
for(i=1;i<numl;i++)
for(int j=0;j<lsize[i];j++)
for(int k=0;k<lsize[i-1]+1;k++)
weight[i][j][k]=(double)(rand())/(RAND_MAX/2) - 1;

//    initialize previous weights to 0 for first iteration
for(i=1;i<numl;i++)
for(int j=0;j<lsize[i];j++)
for(int k=0;k<lsize[i-1]+1;k++)
prevDwt[i][j][k]=(double)0.0;
}```

#### Feed-Forward

This function updates the output value for each neuron. Starting with the first hidden layer, it takes the input to each neuron and finds the output (`o`) by first calculating the weighted sum of inputs and then applying the Sigmoid function to it, and passes it forward to the next layer until the output layer is updated:

where:

```// feed forward one set of input
void CBackProp::ffwd(double *in)
{
double sum;

// assign content to input layer

for(int i=0;i < lsize[0];i++)
out[0][i]=in[i];

// assign output(activation) value
// to each neuron usng sigmoid func

// For each layer
for(i=1;i < numl;i++){
// For each neuron in current layer
for(int j=0;j < lsize[i];j++){
sum=0.0;
// For input from each neuron in preceding layer
for(int k=0;k < lsize[i-1];k++){
// Apply weight to inputs and add to sum
sum+= out[i-1][k]*weight[i][j][k];
}
// Apply bias
sum+=weight[i][j][lsize[i-1]];
// Apply sigmoid function
out[i][j]=sigmoid(sum);
}
}
}```

#### Back-propagating...

The algorithm is implemented in the function `void CBackProp::bpgt(double *in,double *tgt)`. Following are the various steps involved in back-propagating the error in the output layer up till the first hidden layer.

```void CBackProp::bpgt(double *in,double *tgt)
{
double sum;```

First, we call `void CBackProp::ffwd(double *in)` to update the output values for each neuron. This function takes the input to the net and finds the output of each neuron:

where:

`ffwd(in);`

The next step is to find the delta for the output layer:

```for(int i=0;i < lsize[numl-1];i++){
delta[numl-1][i]=out[numl-1][i]*
(1-out[numl-1][i])*(tgt[i]-out[numl-1][i]);
}```

then find the delta for the hidden layers...

```for(i=numl-2;i>0;i--){
for(int j=0;j < lsize[i];j++){
sum=0.0;
for(int k=0;k < lsize[i+1];k++){
sum+=delta[i+1][k]*weight[i+1][k][j];
}
delta[i][j]=out[i][j]*(1-out[i][j])*sum;
}
}```

Apply momentum (does nothing if alpha=0):

```for(i=1;i < numl;i++){
for(int j=0;j < lsize[i];j++){
for(int k=0;k < lsize[i-1];k++){
weight[i][j][k]+=alpha*prevDwt[i][j][k];
}
weight[i][j][lsize[i-1]]+=alpha*prevDwt[i][j][lsize[i-1]];
}
}```

Finally, adjust the weights by finding the correction to the weight.

And then apply the correction:

```for(i=1;i < numl;i++){
for(int j=0;j < lsize[i];j++){
for(int k=0;k < lsize[i-1];k++){
prevDwt[i][j][k]=beta*delta[i][j]*out[i-1][k];
weight[i][j][k]+=prevDwt[i][j][k];
}
prevDwt[i][j][lsize[i-1]]=beta*delta[i][j];
weight[i][j][lsize[i-1]]+=prevDwt[i][j][lsize[i-1]];
}
}```

#### How learned is the net?

Mean square error is used as a measure of how well the neural net has learnt.

As shown in the sample XOR program, we apply the above steps until a satisfactorily low error level is achieved. `CBackProp::double mse(double *tgt)` returns just that.

## History

• Created date: 25th Mar'06.

 Tejpal Singh Chhabra Software Developer India Member
I have done Masters in Computer Application. I am working with a product company in Pune, India. I code in c, c++ and java, mostly on unix.

Votes of 3 or less require a comment

 Search this forum Profile popups    Spacing RelaxedCompactTight   Noise Very HighHighMediumLowVery Low   Layout Open AllThread ViewNo JavascriptPreview   Per page 102550
 First PrevNext
 Sigmoid derivative Raphael Gruaz 1 Oct '12 - 23:12
 Thanks a lot for this article. It really helped me to understand how to implement a neural network. However, there is something I still cannot understand from your code: I thought the derivative function of Sigmoid was something like DS(x) = S(x)(1-S(x)), but I cannot see it anywhere in your back-propagation function...   Did I miss something? Or did you use a simplified form? Thanks again,   Raphael Sign In·View Thread·Permalink
 Overflow Miguel Tomas 5 Aug '12 - 6:06
 on the traning code, the data array has only 8 elements how could be on a iteration with 200.000 steps here's the code:   ```for (long i=0; i < num_iter ; i++) { bp->bpgt(data[i%8], &data[i%8][3]);   if( bp->mse(&data[i%8][3]) < thresh) break; // mse < threshold - we are done training!!! }```   and also the second argument of bp->bpgt has to be an array and on the above code it isn't.   Can anyone help me with this?   regards Sign In·View Thread·Permalink
 How to save the network? tooym 20 Jan '10 - 16:05
 Firstly,thanks to the author for providing me a very useful backpropagation neural network in C++. Regarding to the neural network as shown above, how can i save the network 'bp' permanently for the further testing with others input? Thanks. Sign In·View Thread·Permalink
 How to minimize Total Network Error AjayIndian 31 Dec '09 - 22:21
 Dear Sir, I have developed the code for back-propagation algo.,but I m not able to minimize the Total Network Error(Global Error),How it can be done. Actually after learning,when I m presenting the test pattern to the network,the network is giving the result as the last result given by the network for last training pattern.   looking for ur kind cooperation.   ajay Sign In·View Thread·Permalink
 Understanding the basis for code locuaz 16 Oct '09 - 21:34
 Hi dude, your article is interesting and didactic, I'm focused on understanding your backpropagation algorithm, so I need you can tell me which books or sources you used to implement it into C++, please. I'm having serious problems to adopt/understand the equation tp update weigths, 'cause there are some books and papers using different representations for it, including you. For example, someone is using this formula: weight(new) = weight(old) + learning rate * output error * output(neurons i) * output(neurons i+1) * ( 1 - output(neurons i+1) ) And my doubt is, wtf he gets: output(neurons i) * output(neurons i+1) * ( 1 - output(neurons i+1) ) It doesnot appear in my books Reference: Backpropagation Thanks in advance.   I'm in love with Delphi   Billiardo Sign In·View Thread·Permalink
 how can I get the source code xumin1988 16 Oct '09 - 5:03
 Bias value? emrecaglar 12 Oct '09 - 8:30
 I was wondering where the bias weights are set to 1 or -1? I think they are initialized to random values in constructor.Am i right? Many thanks Sign In·View Thread·Permalink
 Out(0) tangsu 1 Sep '09 - 1:10
 telecom billing Sofritom 13 Jul '09 - 0:32
 Hi Tejpal,   I wish to know about the telecom billing part - did you worked with FTS? what does is has to do with Neural network? Sign In·View Thread·Permalink
 Error in the code [modified] gpwr9k95 11 Jun '09 - 12:08
 The code computes MSE for just one training value. Check these lines in main(): ```for (i=0; ibpgt(data[i%8], &data[i%8][3]); if( bp->mse(&data[i%8][3]) < Thresh) { ``` The learning iterations are arranged such that only one training value is checked at each iteration steps. As a result, for a net with one output, like in the article example, MSE simply becomes the squared error between the net output and the training value, i.e. (Net_Out - Target)^2. This is wrong because the iterations stop when MSE is less than Threshold for just one training value. Instead, iterations should be arranged in epochs where one epoch contains the complete scan over all training values and MSE is calculated by summing squared errors between the net outputs and the corresponding training values for each epoch as in the example below (ntr - number of training samples): ```double MSE; int ep; for (ep=0;epbpgt(data[i], &data[i][lSz[0]]); MSE+=bp->mse(&data[i][lSz[0]]); } MSE/=ntr; if(MSE
 Last Visit: 31 Dec '99 - 18:00     Last Update: 24 May '13 - 2:46 Refresh 12345 Next »