├── Neuroduino.cpp ├── Neuroduino.h ├── README.md └── examples └── Neuroduino_Example └── Neuroduino_Example.pde /Neuroduino.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Neuroduino.c 3 | * 4 | * Arduino Neural Network Library 5 | * Adapted & simplified from: 6 | * http://www.neural-networks-at-your-fingertips.com/bpn.html 7 | * 8 | * Implements a simple feedforward perceptron network 9 | * 10 | * Created by Ted Hayes on 4/15/10. 11 | * Copyright 2010 Limina.Studio. All rights reserved. 12 | * 13 | */ 14 | 15 | #include "WProgram.h" 16 | #include "Neuroduino.h" 17 | 18 | Neuroduino::Neuroduino(){}; 19 | 20 | Neuroduino::Neuroduino(int nodeArray[], int numLayers, double eta = 0.1, double theta = 0.0, boolean debug = false) { 21 | // Constructor 22 | _debug = debug; 23 | trace("Neuroduino::Neuroduino\n"); 24 | 25 | _numLayers = numLayers; 26 | _output = (int*) calloc(nodeArray[_numLayers-1], sizeof(int)); 27 | 28 | // allocate structs & initialize network 29 | 30 | int i, j; 31 | 32 | _net.Layer = (LAYER**) calloc(_numLayers, sizeof(LAYER*)); 33 | 34 | for (i=0; i < _numLayers; i++) { 35 | // 2D array of the network's layers 36 | //_output[i] = 0; 37 | _net.Layer[i] = (LAYER*) malloc(sizeof(LAYER)); 38 | _net.Layer[i]->Units = nodeArray[i]; 39 | _net.Layer[i]->Output = (int*) calloc(nodeArray[i], sizeof(int)); 40 | 41 | // 2D array of weights for each pair of nodes between layers 42 | _net.Layer[i]->Weight = (VAL**) calloc(nodeArray[i]+1, sizeof(VAL*)); 43 | 44 | if (i!=0) { 45 | for (j=0; jWeight[j] = (VAL*) calloc(nodeArray[i-1], sizeof(VAL)); 48 | } 49 | } 50 | } 51 | 52 | _net.InputLayer = _net.Layer[0]; 53 | _net.OutputLayer = _net.Layer[_numLayers-1]; 54 | _net.Eta = eta; // learning rate 55 | _net.Theta = theta; // threshold 56 | } 57 | 58 | /********* UTILITIES *********/ 59 | 60 | //just for testing alternative ways of storing the values... (for now is the same: DOUBLE) 61 | VAL Neuroduino::doubleToVal(double dValue) { 62 | return (VAL) dValue; 63 | } 64 | 65 | double Neuroduino::randomEqualDouble(double Low, double High) { 66 | return ((double) rand() / RAND_MAX) * (High-Low) + Low; 67 | } 68 | 69 | void Neuroduino::printNet(){ 70 | if (_debug) { 71 | int l,i,j; 72 | 73 | //Serial.print("weights of layer 0 test: "); 74 | //Serial.println(_net.Layer[0]->Weight[0][0], DEC); 75 | 76 | for (l=1; l<_numLayers; l++) { 77 | Serial.print("Layer["); 78 | Serial.print(l, DEC); 79 | Serial.println("]:"); 80 | Serial.print("\t"); 81 | for (i=0; i<_net.Layer[l]->Units; i++) { 82 | Serial.print(i,DEC); 83 | Serial.print("\t"); 84 | } 85 | Serial.println(); 86 | for (i=0; i<_net.Layer[l]->Units; i++) { 87 | // cycle through units of current layer 88 | //Serial.print(" Unit "); 89 | Serial.print(i,DEC); 90 | Serial.print(":\t"); 91 | for (j=0; j<_net.Layer[l-1]->Units; j++) { 92 | // cycle through units of "leftmost" layer 93 | Serial.print(_net.Layer[l]->Weight[i][j], 3); 94 | trace("\t"); 95 | } 96 | trace("\n"); 97 | } 98 | trace("\n"); 99 | } 100 | trace("\n"); 101 | } 102 | } 103 | 104 | // free memory check 105 | // from: http://forum.pololu.com/viewtopic.php?f=10&t=989#p4218 106 | int Neuroduino::get_free_memory(){ 107 | int free_memory; 108 | if((int)__brkval == 0) 109 | free_memory = ((int)&free_memory) - ((int)&__bss_end); 110 | else 111 | free_memory = ((int)&free_memory) - ((int)__brkval); 112 | 113 | return free_memory; 114 | } 115 | 116 | /********* PRIVATE *********/ 117 | 118 | void Neuroduino::randomizeWeights() { 119 | int l,i,j; 120 | double temp; 121 | 122 | for (l=1; l<_numLayers; l++) { 123 | for (i=0; i<_net.Layer[l]->Units; i++) { 124 | for (j=0; j<_net.Layer[l-1]->Units; j++) { 125 | _net.Layer[l]->Weight[i][j] = doubleToVal(randomEqualDouble(-0.5, 0.5)); 126 | } 127 | } 128 | } 129 | } 130 | 131 | void Neuroduino::setInput(int inputs[]){ 132 | int i; 133 | for (i=0; i<_net.InputLayer->Units; i++) { 134 | _net.InputLayer->Output[i] = inputs[i]; 135 | } 136 | } 137 | 138 | int* Neuroduino::getOutput(){ 139 | 140 | return _output; 141 | 142 | } 143 | 144 | int Neuroduino::signThreshold(double sum){ 145 | if (sum >= _net.Theta) { 146 | return 1; 147 | } else { 148 | return -1; 149 | } 150 | } 151 | 152 | double Neuroduino::weightedSum(int l, int node){ 153 | // calculates input activation for a particular neuron 154 | int i; 155 | double currentWeight, sum = 0.0; 156 | 157 | for (i=0; i<_net.Layer[l-1]->Units; i++) { 158 | currentWeight = _net.Layer[l]->Weight[node][i]; 159 | sum += currentWeight * _net.Layer[l-1]->Output[i]; 160 | } 161 | 162 | return sum; 163 | } 164 | 165 | void Neuroduino::adjustWeights(int trainArray[]){ 166 | int l,i,j; 167 | int in,out, error; 168 | int activation; // for each "rightmost" node 169 | double delta; 170 | 171 | for (l=1; l<_numLayers; l++) { 172 | // cycle through each pair of nodes 173 | for (i=0; i<_net.Layer[l]->Units; i++) { 174 | // "rightmost" layer 175 | // calculate current activation of this output node 176 | activation = signThreshold(weightedSum(l,i)); 177 | out = trainArray[i]; // correct activation 178 | error = out - activation; // -2, 2, or 0 179 | 180 | for (j=0; j<_net.Layer[l-1]->Units; j++) { 181 | // "leftmost" layer 182 | 183 | in = _net.Layer[l-1]->Output[j]; 184 | 185 | delta = _net.Eta * in * error; 186 | _net.Layer[l]->Weight[i][j] += delta; 187 | } 188 | } 189 | } 190 | } 191 | 192 | void Neuroduino::simulateNetwork(){ 193 | /***** 194 | Calculate activations of each output node 195 | *****/ 196 | int l,j; 197 | 198 | for (l=_numLayers-1; l>0; l--) { 199 | // step backwards through layers 200 | // TODO: this will only work for _numLayers = 2! 201 | for (j=0; j < _net.Layer[l]->Units; j++) { 202 | _output[j] = signThreshold(weightedSum(1, j)); 203 | } 204 | } 205 | } 206 | 207 | /********* PUBLIC *********/ 208 | 209 | void Neuroduino::train(int inputArray[], int trainArray[]) { 210 | trace("Neuroduino::train\n"); 211 | 212 | setInput(inputArray); 213 | adjustWeights(trainArray); 214 | 215 | } 216 | 217 | int* Neuroduino::simulate(int inputArray[]) { 218 | // introduce an input stimulus, simulate the network, 219 | // and return an output array 220 | trace("Neuroduino::simulate\n"); 221 | setInput(inputArray); 222 | simulateNetwork(); 223 | 224 | return _output; 225 | } 226 | 227 | void Neuroduino::trace(char *message) { 228 | if(_debug){ 229 | Serial.print(message); 230 | } 231 | } -------------------------------------------------------------------------------- /Neuroduino.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Neuroduino.h 3 | * 4 | * Arduino Neural Network Library 5 | * Adapted and simplified from: 6 | * http://www.neural-networks-at-your-fingertips.com/bpn.html 7 | * 8 | * Implements a simple feedforward perceptron network 9 | * 10 | * Created by Ted Hayes on 4/15/10. 11 | * Copyright 2010 Limina.Studio. All rights reserved. 12 | * 13 | */ 14 | 15 | #ifndef Neuroduino_h 16 | #define Neuroduino_h 17 | 18 | #include "WProgram.h" 19 | 20 | // used for get_free_memory() 21 | extern int __bss_end; 22 | extern void *__brkval; 23 | 24 | typedef double VAL; // no idea why this has to go here 25 | 26 | class Neuroduino 27 | { 28 | public: 29 | // methods 30 | Neuroduino(); 31 | Neuroduino(int nodeArray[], int numLayers, double eta, double theta, boolean debug); 32 | void train(int inputArray[], int trainArray[]); 33 | void printNet(); 34 | int* getOutput(); 35 | void randomizeWeights(); 36 | int* simulate(int inputArray[]); 37 | 38 | // properties 39 | 40 | private: 41 | // types 42 | //typedef double VAL; // this doesn't work here 43 | typedef struct { /* A LAYER OF A NET: */ 44 | int Units; /* - number of units in this layer */ 45 | //VAL* Output; /* - output of ith unit */ 46 | int* Output; // 1 or -1 47 | //VAL* Error; /* - error term of ith unit */ 48 | VAL** Weight; /* - connection weights to ith unit */ 49 | //VAL** WeightSave; /* - saved weights for stopped training */ 50 | //VAL** dWeight; /* - last weight deltas for momentum */ 51 | } LAYER; 52 | typedef struct { /* A NET: */ 53 | LAYER** Layer; /* - layers of this net */ 54 | LAYER* InputLayer; /* - input layer */ 55 | LAYER* OutputLayer; /* - output layer */ 56 | //double Alpha; /* - momentum factor */ 57 | double Eta; /* - learning rate */ 58 | //double Gain; /* - gain of sigmoid function */ 59 | //double Error; /* - total net error */ 60 | double Theta; 61 | } NET; 62 | 63 | // class methods 64 | 65 | void setInput(int inputs[]); 66 | void adjustWeights(int trainArray[]); 67 | int signThreshold(double sum); 68 | double weightedSum(int layer, int node); 69 | void simulateNetwork(); 70 | 71 | // utility methods 72 | void trace(char *message); 73 | VAL doubleToVal(double dValue); 74 | double randomEqualDouble(double Low, double High); 75 | int get_free_memory(); 76 | 77 | // properties 78 | boolean _debug; 79 | int _numLayers; 80 | NET _net; 81 | int* _output; 82 | }; 83 | 84 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neuroduino 2 | An artificial neural network library for Arduino 3 | 4 | Neuroduino is a two-layer perceptron network with a simplified API suitable for Arduino projects that need to flexibly associate an input pattern with an output pattern. See [Neuroduino Example](https://github.com/t3db0t/Neuroduino/blob/master/examples/Neuroduino_Example/Neuroduino_Example.pde) for a functioning example. This library was developed in 2010 at the Interactive Telecommunications Program for my thesis, [The Dawn Chorus](http://log.liminastudio.com/itp/the-dawn-chorus). 5 | 6 | Example uses: 7 | - Simple clustering and classification (but [not XOR](https://www.quora.com/Why-cant-the-XOR-problem-be-solved-by-a-one-layer-perceptron) until backpropagation is finished!) 8 | - 'Natural' language emergence (as in my thesis) 9 | - Conceivably, simple stabilization of servos or motors 10 | 11 | Pull requests for bug fixes, improvements, documentation, etc are absolutely welcome! 12 | 13 | ## Basic Usage 14 | 15 | ### Setup 16 | ```C++ 17 | Neuroduino myNet; 18 | int netArray[NUM_LAYERS] = {8,8}; 19 | int inputArray[] = {1, -1, 1, -1, -1, 1, -1, 1}; // The input to be trained to 20 | int trainArray[] = {-1, 1, -1, -1, 1, -1, 1, -1}; // What you want the network to output when it gets the inputArray 21 | myNet = Neuroduino(netArray, NUM_LAYERS, ETA, THETA, DEBUG); 22 | ``` 23 | 24 | - `NUM_LAYERS` is currently fixed to 2. If anyone wants to finish implementing backpropagation, I would welcome your pull requests! 25 | - `ETA` is the learning rate: the amount each value changes on each iteration. Making this larger makes learning much faster, but can result in oscillations. Making it smaller does the opposite. 0.1 by default. 26 | - `THETA` is the activation threshold. This is 0.0 by default. 27 | - `DEBUG` turns on extra debugging output. False by default. 28 | 29 | ### Set initial weights 30 | ```C++ 31 | srand(analogRead(0)); (or use a fixed integer for reproducible results) 32 | myNet.randomizeWeights(); 33 | ``` 34 | 35 | ### Run some iterations 36 | `printArray(myNet.simulate(inputArray), netArray[1]);` 37 | 38 | Eventually, the network should output `trainArray` when it gets `inputArray`. Yay! Why go through all this trouble? The association between input and output is flexible, so small 'imperfections' in the input should still produce the desired output. 39 | -------------------------------------------------------------------------------- /examples/Neuroduino_Example/Neuroduino_Example.pde: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define NUM_LAYERS 2 4 | #define ETA 0.01 5 | #define THETA 0.0 6 | #define DEBUG false 7 | 8 | // used for get_free_memory() 9 | extern int __bss_end; 10 | extern void *__brkval; 11 | 12 | int netArray[NUM_LAYERS] = {8,8}; 13 | int inputArray[] = {1, -1, 1, -1, -1, 1, -1, 1}; 14 | int trainArray[] = {-1, 1, -1, -1, 1, -1, 1, -1}; 15 | 16 | int pinState; 17 | int lastRead = HIGH; 18 | 19 | int netMem; 20 | 21 | Neuroduino myNet; 22 | 23 | // free memory check 24 | // from: http://forum.pololu.com/viewtopic.php?f=10&t=989#p4218 25 | int get_free_memory(){ 26 | int free_memory; 27 | if((int)__brkval == 0) 28 | free_memory = ((int)&free_memory) - ((int)&__bss_end); 29 | else 30 | free_memory = ((int)&free_memory) - ((int)__brkval); 31 | 32 | return free_memory; 33 | } 34 | 35 | byte arrayToByte(int arr[], int len){ 36 | // Convert -1 to 0 and pack the array into a byte 37 | int i; 38 | byte result = 0; 39 | 40 | for(i=len-1; i>=0; i--){ 41 | if(arr[i] == -1){ 42 | result &= ~(0 << i); 43 | } else { 44 | result |= (1 << i); 45 | } 46 | } 47 | return result; 48 | } 49 | 50 | void byteToArray(byte in, int out[]){ 51 | int i, temp;//, out[8]; 52 | 53 | for(i=0; i<8; i++){ 54 | temp = (in >> i) & 1; 55 | if(temp == 0) temp = -1; 56 | out[i] = temp; 57 | } 58 | } 59 | 60 | void printArray(int arr[], int len){ 61 | int i; 62 | Serial.print("["); 63 | for(i=0; i