MNIST-3LNN  1.0
A simple 3-layer neural network to recognize handwritten single digit numbers from the MNIST image files.
3lnn.c
Go to the documentation of this file.
1 /**
2  * @file 3lnn.c
3  * @brief Neural network functionality for a 3-layer (INPUT, HIDDEN, OUTPUT) feed-forward, back-prop NN
4  * @author Matt Lind
5  * @date August 2015
6  */
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <math.h>
11 
12 #include "util/mnist-utils.h"
13 #include "3lnn.h"
14 
15 
16 
17 
18 /**
19  * @details Retrieves a node via ID from a layer
20  */
21 
22 Node *getNode(Layer *l, int nodeId) {
23 
24  int nodeSize = sizeof(Node) + (l->nodes[0].wcount * sizeof(double));
25  uint8_t *sbptr = (uint8_t*) l->nodes;
26 
27  sbptr += nodeId * nodeSize;
28 
29  return (Node*) sbptr;
30 }
31 
32 
33 
34 
35 /**
36  * @brief Returns one of the layers of the network
37  * @param nn A pointer to the NN
38  * @param ltype Type of layer to be returned (INPUT, HIDDEN, OUTPUT)
39  */
40 
42 
43  Layer *l;
44 
45  switch (ltype) {
46  case INPUT:{
47  l = nn->layers;
48  break;
49  }
50  case HIDDEN:{
51  uint8_t *sbptr = (uint8_t*) nn->layers;
52  sbptr += nn->inpLayerSize;
53  l = (Layer*)sbptr;
54  break;
55  }
56 
57  default:{ // OUTPUT
58  uint8_t *sbptr = (uint8_t*) nn->layers;
59  sbptr += nn->inpLayerSize + nn->hidLayerSize;
60  l = (Layer*)sbptr;
61  break;
62  }
63  }
64 
65  return l;
66 }
67 
68 
69 
70 
71 /**
72  * @brief Returns the result of applying the given outputValue to the derivate of the activation function
73  * @param nn A pointer to the NN
74  * @param ltype Type of layer (INPUT, HIDDEN, OUTPUT)
75  * @param outVal Output value that is to be back propagated
76  */
77 
78 double getActFctDerivative(Network *nn, LayerType ltype, double outVal){
79 
80  double dVal = 0;
81  ActFctType actFct;
82 
83  if (ltype==HIDDEN) actFct = nn->hidLayerActType;
84  else actFct = nn->outLayerActType;
85 
86  if (actFct==TANH) dVal = 1-pow(tanh(outVal),2);
87  else dVal = outVal * (1-outVal);
88 
89  return dVal;
90 }
91 
92 
93 
94 
95 /**
96  * @brief Updates a node's weights based on given error
97  * @param nn A pointer to the NN
98  * @param ltype Type of layer (INPUT, HIDDEN, OUTPUT)
99  * @param id Sequential id of the node that is to be calculated
100  * @param error The error (difference between desired output and actual output
101  */
102 
103 void updateNodeWeights(Network *nn, LayerType ltype, int id, double error){
104 
105  Layer *updateLayer = getLayer(nn, ltype);
106  Node *updateNode = getNode(updateLayer, id);
107 
108  Layer *prevLayer;
109  int prevLayerNodeSize = 0;
110  if (ltype==HIDDEN) {
111  prevLayer = getLayer(nn, INPUT);
112  prevLayerNodeSize = nn->inpNodeSize;
113  } else {
114  prevLayer = getLayer(nn, HIDDEN);
115  prevLayerNodeSize = nn->hidNodeSize;
116  }
117 
118  uint8_t *sbptr = (uint8_t*) prevLayer->nodes;
119 
120  for (int i=0; i<updateNode->wcount; i++){
121  Node *prevLayerNode = (Node*)sbptr;
122  updateNode->weights[i] += (nn->learningRate * prevLayerNode->output * error);
123  sbptr += prevLayerNodeSize;
124  }
125 
126  // update bias weight
127  updateNode->bias += (nn->learningRate * 1 * error);
128 
129 }
130 
131 
132 
133 
134 /**
135  * @brief Back propagates network error to hidden layer
136  * @param nn A pointer to the NN
137  * @param targetClassification Correct classification (=label) of the input stream
138  */
139 
140 void backPropagateHiddenLayer(Network *nn, int targetClassification){
141 
142  Layer *ol = getLayer(nn, OUTPUT);
143  Layer *hl = getLayer(nn, HIDDEN);
144 
145  for (int h=0;h<hl->ncount;h++){
146  Node *hn = getNode(hl,h);
147 
148  double outputcellerrorsum = 0;
149 
150  for (int o=0;o<ol->ncount;o++){
151 
152  Node *on = getNode(ol,o);
153 
154  int targetOutput = (o==targetClassification)?1:0;
155 
156  double errorDelta = targetOutput - on->output;
157  double errorSignal = errorDelta * getActFctDerivative(nn, OUTPUT, on->output);
158 
159  outputcellerrorsum += errorSignal * on->weights[h];
160  }
161 
162  double hiddenErrorSignal = outputcellerrorsum * getActFctDerivative(nn, HIDDEN, hn->output);
163 
164  updateNodeWeights(nn, HIDDEN, h, hiddenErrorSignal);
165  }
166 
167 }
168 
169 
170 
171 
172 /**
173  * @brief Back propagates network error in output layer
174  * @param nn A pointer to the NN
175  * @param targetClassification Correct classification (=label) of the input stream
176  */
177 
178 void backPropagateOutputLayer(Network *nn, int targetClassification){
179 
180  Layer *ol = getLayer(nn, OUTPUT);
181 
182  for (int o=0;o<ol->ncount;o++){
183 
184  Node *on = getNode(ol,o);
185 
186  int targetOutput = (o==targetClassification)?1:0;
187 
188  double errorDelta = targetOutput - on->output;
189  double errorSignal = errorDelta * getActFctDerivative(nn, OUTPUT, on->output);
190 
191  updateNodeWeights(nn, OUTPUT, o, errorSignal);
192 
193  }
194 
195 }
196 
197 
198 
199 
200 /**
201  * @brief Back propagates network error from output layer to hidden layer
202  * @param nn A pointer to the NN
203  * @param targetClassification Correct classification (=label) of the input stream
204  */
205 
206 void backPropagateNetwork(Network *nn, int targetClassification){
207 
208  backPropagateOutputLayer(nn, targetClassification);
209 
210  backPropagateHiddenLayer(nn, targetClassification);
211 
212 }
213 
214 
215 
216 
217 /**
218  * @brief Performs an activiation function (as defined in the NN's defaults) to a specified node
219  * @param nn A pointer to the NN
220  * @param ltype Type of layer (INPUT, HIDDEN, OUTPUT)
221  * @param id Sequential id of the node that is to be calculated
222  */
223 
224 void activateNode(Network *nn, LayerType ltype, int id){
225 
226  Layer *l = getLayer(nn, ltype);
227  Node *n = getNode(l, id);
228 
229  ActFctType actFct;
230 
231  if (ltype==HIDDEN) actFct = nn->hidLayerActType;
232  else actFct = nn->outLayerActType;
233 
234  if (actFct==TANH) n->output = tanh(n->output);
235  else n->output = 1 / (1 + (exp((double)-n->output)) );
236 
237 }
238 
239 
240 
241 
242 /**
243  * @brief Calculates the output value of a specified node by multiplying all its weights with the previous layer's outputs
244  * @param nn A pointer to the NN
245  * @param ltype Type of layer (INPUT, HIDDEN, OUTPUT)
246  * @param id Sequential id of the node that is to be calculated
247  */
248 
249 void calcNodeOutput(Network *nn, LayerType ltype, int id){
250 
251  Layer *calcLayer = getLayer(nn, ltype);
252  Node *calcNode = getNode(calcLayer, id);
253 
254  Layer *prevLayer;
255  int prevLayerNodeSize = 0;
256 
257  if (ltype==HIDDEN) {
258  prevLayer = getLayer(nn, INPUT);
259  prevLayerNodeSize = nn->inpNodeSize;
260  }
261  else {
262  prevLayer = getLayer(nn, HIDDEN);
263  prevLayerNodeSize = nn->hidNodeSize;
264  }
265 
266  uint8_t *sbptr = (uint8_t*) prevLayer->nodes;
267 
268  // Start by adding the bias
269  calcNode->output = calcNode->bias;
270 
271  for (int i=0; i<prevLayer->ncount;i++){
272  Node *prevLayerNode = (Node*)sbptr;
273  calcNode->output += prevLayerNode->output * calcNode->weights[i];
274  sbptr += prevLayerNodeSize;
275  }
276 
277 }
278 
279 
280 
281 
282 /**
283  * @brief Calculates the output values of a given NN layer
284  * @param nn A pointer to the NN
285  * @param ltype Type of layer (INPUT, HIDDEN, OUTPUT)
286  */
287 
288 void calcLayer(Network *nn, LayerType ltype){
289  Layer *l;
290  l = getLayer(nn, ltype);
291 
292  for (int i=0;i<l->ncount;i++){
293  calcNodeOutput(nn, ltype, i);
294  activateNode(nn,ltype,i);
295  }
296 }
297 
298 
299 
300 
301 /**
302  * @brief Feeds input layer values forward to hidden to output layer (calculation and activation fct)
303  * @param nn A pointer to the NN
304  */
305 
307  calcLayer(nn, HIDDEN);
308  calcLayer(nn, OUTPUT);
309 }
310 
311 
312 
313 
314 /**
315  * @brief Feeds some Vector data into the INPUT layer of the NN
316  * @param nn A pointer to the NN
317  * @param v A pointer to a vector
318  */
319 
320 void feedInput(Network *nn, Vector *v) {
321 
322  Layer *il;
323  il = nn->layers;
324 
325  Node *iln;
326  iln = il->nodes;
327 
328  // Copy the vector content to the "output" field of the input layer nodes
329  for (int i=0; i<v->size;i++){
330  iln->output = v->vals[i];
331  iln++; // @warning This only works because inputNodeSize = sizeof(Node)
332  }
333 
334 }
335 
336 
337 
338 
339 /**
340  * @details Creates an input layer and sets all weights to random values [0-1]
341  * @param inpCount Number of nodes in the input layer
342  */
343 
344 Layer *createInputLayer(int inpCount){
345 
346  int inpNodeSize = sizeof(Node); // Input layer has 0 weights
347  int inpLayerSize = sizeof(Layer) + (inpCount * inpNodeSize);
348 
349  Layer *il = malloc(inpLayerSize);
350  il->ncount = inpCount;
351 
352  // Create a detault input layer node
353  Node iln;
354  iln.bias = 0;
355  iln.output = 0;
356  iln.wcount = 0;
357 
358  // Use a single byte pointer to fill in the network's content
359  uint8_t *sbptr = (uint8_t*) il->nodes;
360 
361  // Copy the default input layer node x times
362  for (int i=0;i<il->ncount;i++){
363  memcpy(sbptr,&iln,inpNodeSize);
364  sbptr += inpNodeSize;
365  }
366 
367  return il;
368 }
369 
370 
371 
372 /**
373  * @details Creates a layer and sets all weights to random values [0-1]
374  * @param nodeCount Number of nodes
375  * @param weightCount Number of weights per node
376  */
377 
378 Layer *createLayer(int nodeCount, int weightCount){
379 
380  int nodeSize = sizeof(Node) + (weightCount * sizeof(double));
381  Layer *l = (Layer*)malloc(sizeof(Layer) + (nodeCount*nodeSize));
382 
383  l->ncount = nodeCount;
384 
385  // create a detault node
386  Node *dn = (Node*)malloc(sizeof(Node) + ((weightCount)*sizeof(double)));
387  dn->bias = 0;
388  dn->output = 0;
389  dn->wcount = weightCount;
390  for (int o=0;o<weightCount;o++) dn->weights[o] = 0; // will be initialized later
391 
392  uint8_t *sbptr = (uint8_t*) l->nodes; // single byte pointer
393 
394  // copy the default cell to all cell positions in the layer
395  for (int i=0;i<nodeCount;i++) memcpy(sbptr+(i*nodeSize),dn,nodeSize);
396 
397  free(dn);
398 
399  return l;
400 }
401 
402 
403 
404 
405 /**
406  * @brief Initializes the NN by creating and copying INTPUT, HIDDEN, OUTPUT data structures into the NN's memory space
407  * @param nn A pointer to the NN
408  * @param inpCount Number of nodes in the INPUT layer
409  * @param hidCount Number of nodes in the HIDDEN layer
410  * @param outCount Number of nodes in the OUTPUT layer
411  */
412 
413 void initNetwork(Network *nn, int inpCount, int hidCount, int outCount){
414 
415  // Copy the input layer into the network's memory block and delete it
416  Layer *il = createInputLayer(inpCount);
417  memcpy(nn->layers,il,nn->inpLayerSize);
418  free(il);
419 
420  // Move pointer to end of input layer = beginning of hidden layer
421  uint8_t *sbptr = (uint8_t*) nn->layers; // single byte pointer
422  sbptr += nn->inpLayerSize;
423 
424  // Copy the hidden layer into the network's memory block and delete it
425  Layer *hl = createLayer(hidCount, inpCount);
426  memcpy(sbptr,hl,nn->hidLayerSize);
427  free(hl);
428 
429  // Move pointer to end of hidden layer = beginning of output layer
430  sbptr += nn->hidLayerSize;
431 
432  // Copy the output layer into the network's memory block and delete it
433  Layer *ol = createLayer(outCount, hidCount);
434  memcpy(sbptr,ol,nn->outLayerSize);
435  free(ol);
436 
437 }
438 
439 
440 
441 
442 /**
443  * @brief Sets the default network parameters (which can be overwritten/changed)
444  * @param nn A pointer to the NN
445  */
446 
448 
449  // Set deffault activation function types
450  nn->hidLayerActType = SIGMOID;
451  nn->outLayerActType = SIGMOID;
452 
453  nn->learningRate = 0.004; // TANH 78.0%
454  nn->learningRate = 0.2; // SIGMOID 91.5%
455 
456 }
457 
458 
459 
460 
461 /**
462  * @brief Initializes a layer's weights with random values
463  * @param nn A pointer to the NN
464  * @param ltype Defining what layer to initialize
465  */
466 
467 void initWeights(Network *nn, LayerType ltype){
468 
469  int nodeSize = 0;
470  if (ltype==HIDDEN) nodeSize=nn->hidNodeSize;
471  else nodeSize=nn->outNodeSize;
472 
473  Layer *l = getLayer(nn, ltype);
474 
475  uint8_t *sbptr = (uint8_t*) l->nodes;
476 
477  for (int o=0; o<l->ncount;o++){
478 
479  Node *n = (Node *)sbptr;
480 
481  for (int i=0; i<n->wcount; i++){
482  n->weights[i] = 0.7*(rand()/(double)(RAND_MAX));
483  if (i%2) n->weights[i] = -n->weights[i]; // make half of the weights negative
484 
485  }
486 
487  // init bias weight
488  n->bias = rand()/(double)(RAND_MAX);
489  if (o%2) n->bias = -n->bias; // make half of the bias weights negative
490 
491  sbptr += nodeSize;
492  }
493 
494 }
495 
496 
497 
498 /**
499  * @brief Creates a dynamically-sized, 3-layer (INTPUT, HIDDEN, OUTPUT) neural network
500  * @param inpCount Number of nodes in the INPUT layer
501  * @param hidCount Number of nodes in the HIDDEN layer
502  * @param outCount Number of nodes in the OUTPUT layer
503  */
504 
505 Network *createNetwork(int inpCount, int hidCount, int outCount){
506 
507  // Calculate size of INPUT Layer
508  int inpNodeSize = sizeof(Node); // Input layer has 0 weights
509  int inpLayerSize = sizeof(Layer) + (inpCount * inpNodeSize);
510 
511  // Calculate size of HIDDEN Layer
512  int hidWeightsCount = inpCount;
513  int hidNodeSize = sizeof(Node) + (hidWeightsCount * sizeof(double));
514  int hidLayerSize = sizeof(Layer) + (hidCount * hidNodeSize);
515 
516  // Calculate size of OUTPUT Layer
517  int outWeightsCount = hidCount;
518  int outNodeSize = sizeof(Node) + (outWeightsCount * sizeof(double));
519  int outLayerSize = sizeof(Layer) + (outCount * outNodeSize);
520 
521  // Allocate memory block for the network
522  Network *nn = (Network*)malloc(sizeof(Network) + inpLayerSize + hidLayerSize + outLayerSize);
523 
524  // Set/remember byte sizes of each component of the network
525  nn->inpNodeSize = inpNodeSize;
526  nn->inpLayerSize = inpLayerSize;
527  nn->hidNodeSize = hidNodeSize;
528  nn->hidLayerSize = hidLayerSize;
529  nn->outNodeSize = outNodeSize;
530  nn->outLayerSize = outLayerSize;
531 
532  // Initialize the network by creating the INPUT, HIDDEN and OUTPUT layer inside of it
533  initNetwork(nn, inpCount, hidCount, outCount);
534 
535  // Setting defaults
536  setNetworkDefaults(nn);
537 
538  // Init connection weights with random values
539  initWeights(nn, HIDDEN);
540  initWeights(nn, OUTPUT);
541 
542  return nn;
543 }
544 
545 
546 
547 
548 /**
549  * @brief Returns the network's classification using the ID of teh node with the hightest output
550  * @param nn A pointer to the NN
551  */
552 
554 
555  Layer *l = getLayer(nn, OUTPUT);
556 
557  double maxOut = 0;
558  int maxInd = 0;
559 
560  for (int i=0; i<l->ncount; i++){
561 
562  Node *on = getNode(l,i);
563 
564  if (on->output > maxOut){
565  maxOut = on->output;
566  maxInd = i;
567  }
568  }
569 
570  return maxInd;
571 }
void backPropagateOutputLayer(Network *nn, int targetClassification)
Back propagates network error in output layer.
Definition: 3lnn.c:178
struct Layer Layer
Definition: 3lnn.h:13
Dynamic data structure containing defined number of values.
Definition: 3lnn.h:27
int inpLayerSize
Definition: 3lnn.h:65
Layer * createLayer(int nodeCount, int weightCount)
Definition: 3lnn.c:378
Node nodes[]
Definition: 3lnn.h:55
int hidLayerSize
Definition: 3lnn.h:67
Definition: 3lnn.h:17
Neural network functionality for a 3-layer (INPUT, HIDDEN, OUTPUT) feed-forward, back-prop NN...
int hidNodeSize
Definition: 3lnn.h:66
ActFctType outLayerActType
Definition: 3lnn.h:72
int outLayerSize
Definition: 3lnn.h:69
Layer layers[]
Definition: 3lnn.h:73
int wcount
Definition: 3lnn.h:42
void feedForwardNetwork(Network *nn)
Feeds input layer values forward to hidden to output layer (calculation and activation fct) ...
Definition: 3lnn.c:306
void backPropagateNetwork(Network *nn, int targetClassification)
Back propagates network error from output layer to hidden layer.
Definition: 3lnn.c:206
Layer * getLayer(Network *nn, LayerType ltype)
Returns one of the layers of the network.
Definition: 3lnn.c:41
int outNodeSize
Definition: 3lnn.h:68
Definition: 3lnn.h:18
void setNetworkDefaults(Network *nn)
Sets the default network parameters (which can be overwritten/changed)
Definition: 3lnn.c:447
double output
Definition: 3lnn.h:41
void updateNodeWeights(Network *nn, LayerType ltype, int id, double error)
Updates a node's weights based on given error.
Definition: 3lnn.c:103
int inpNodeSize
Definition: 3lnn.h:64
void activateNode(Network *nn, LayerType ltype, int id)
Performs an activiation function (as defined in the NN's defaults) to a specified node...
Definition: 3lnn.c:224
Node * getNode(Layer *l, int nodeId)
Definition: 3lnn.c:22
double vals[]
Definition: 3lnn.h:29
int getNetworkClassification(Network *nn)
Returns the network's classification using the ID of teh node with the hightest output.
Definition: 3lnn.c:553
double getActFctDerivative(Network *nn, LayerType ltype, double outVal)
Returns the result of applying the given outputValue to the derivate of the activation function...
Definition: 3lnn.c:78
void feedInput(Network *nn, Vector *v)
Feeds some Vector data into the INPUT layer of the NN.
Definition: 3lnn.c:320
double learningRate
Factor by which connection weight changes are applied.
Definition: 3lnn.h:70
Dynamic data structure holding a definable number of nodes that form a layer.
Definition: 3lnn.h:53
LayerType
Definition: 3lnn.h:17
void backPropagateHiddenLayer(Network *nn, int targetClassification)
Back propagates network error to hidden layer.
Definition: 3lnn.c:140
int ncount
Definition: 3lnn.h:54
int size
Definition: 3lnn.h:28
Definition: 3lnn.h:18
struct Node Node
Definition: 3lnn.h:14
Layer * createInputLayer(int inpCount)
Definition: 3lnn.c:344
ActFctType hidLayerActType
Definition: 3lnn.h:71
Definition: 3lnn.h:17
Network * createNetwork(int inpCount, int hidCount, int outCount)
Creates a dynamically-sized, 3-layer (INTPUT, HIDDEN, OUTPUT) neural network.
Definition: 3lnn.c:505
ActFctType
Definition: 3lnn.h:18
double bias
Definition: 3lnn.h:40
void initWeights(Network *nn, LayerType ltype)
Initializes a layer's weights with random values.
Definition: 3lnn.c:467
void calcLayer(Network *nn, LayerType ltype)
Calculates the output values of a given NN layer.
Definition: 3lnn.c:288
Definition: 3lnn.h:17
Utitlies for handling the MNIST files.
Dynamic data structure modeling a neuron with a variable number of connections/weights.
Definition: 3lnn.h:39
void calcNodeOutput(Network *nn, LayerType ltype, int id)
Calculates the output value of a specified node by multiplying all its weights with the previous laye...
Definition: 3lnn.c:249
double weights[]
Definition: 3lnn.h:43
void initNetwork(Network *nn, int inpCount, int hidCount, int outCount)
Initializes the NN by creating and copying INTPUT, HIDDEN, OUTPUT data structures into the NN's memor...
Definition: 3lnn.c:413
Dynamic data structure holding the whole network.
Definition: 3lnn.h:63