parent
a3f0f40d92
commit
d3111c6162
@ -0,0 +1,214 @@ |
|||||||
|
// OneLoneCoder
|
||||||
|
// Simple Neural Network Example with training
|
||||||
|
// May have bugs - is not finished yet
|
||||||
|
// WORK IN PROGRESS
|
||||||
|
// User supplies an input and output vector to determine how many
|
||||||
|
// neurons are in input and output layers
|
||||||
|
// can also specify hidden layers (which are same size as input)
|
||||||
|
|
||||||
|
// Example shows training XOR
|
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
#include <cmath> |
||||||
|
using namespace std; |
||||||
|
|
||||||
|
class NeuralNetwork |
||||||
|
{ |
||||||
|
struct Synapse |
||||||
|
{ |
||||||
|
int nAfferentNeuron; |
||||||
|
int nEfferentNeuron; |
||||||
|
float fWeight; |
||||||
|
}; |
||||||
|
|
||||||
|
struct Neuron |
||||||
|
{ |
||||||
|
vector<int> vecAfferentSynapse; |
||||||
|
vector<int> vecEfferentSynapse; |
||||||
|
float fSummedInput; |
||||||
|
float fOutput; |
||||||
|
float fError; |
||||||
|
bool bIsInput; |
||||||
|
bool bIsOutput; |
||||||
|
}; |
||||||
|
|
||||||
|
vector<float> &vecInput; |
||||||
|
vector<float> &vecOutput; |
||||||
|
vector<Neuron> vecNodes; |
||||||
|
vector<Synapse> vecSynapses; |
||||||
|
|
||||||
|
public: |
||||||
|
NeuralNetwork(vector<float> &in, vector<float> &out, int nHiddenLayers) : vecInput(in), vecOutput(out) |
||||||
|
{ |
||||||
|
// Construct Network (assumes non recurrent, and fully connected, hidden layers same size as input)
|
||||||
|
int nNodeCount = 0; |
||||||
|
|
||||||
|
vector<int> vecPreviousLayerIDs; |
||||||
|
vector<int> vecNewPreviousLayerIDs; |
||||||
|
|
||||||
|
// Input Layer
|
||||||
|
for (auto n : vecInput) |
||||||
|
{ |
||||||
|
Neuron node; |
||||||
|
node.bIsInput = true; |
||||||
|
node.bIsOutput = false; |
||||||
|
vecNodes.push_back(node); |
||||||
|
vecNewPreviousLayerIDs.push_back(vecNodes.size() - 1); |
||||||
|
} |
||||||
|
|
||||||
|
// Hidden Layers (assumes same size as input)
|
||||||
|
for (int h = 0; h < nHiddenLayers; h++) |
||||||
|
{ |
||||||
|
vecPreviousLayerIDs = vecNewPreviousLayerIDs; |
||||||
|
vecNewPreviousLayerIDs.clear(); |
||||||
|
|
||||||
|
for (auto n : vecInput) // For layer size
|
||||||
|
{ |
||||||
|
// Create New Neuron
|
||||||
|
Neuron node; |
||||||
|
node.bIsInput = false; |
||||||
|
node.bIsOutput = false; |
||||||
|
vecNodes.push_back(node); |
||||||
|
vecNewPreviousLayerIDs.push_back(vecNodes.size() - 1); |
||||||
|
|
||||||
|
// Fully connect it to previous layer
|
||||||
|
for (auto p : vecPreviousLayerIDs) |
||||||
|
{ |
||||||
|
// Create synapse
|
||||||
|
Synapse syn; |
||||||
|
syn.nAfferentNeuron = p; |
||||||
|
syn.fWeight = (float)rand() / (float)RAND_MAX; //0.0f;
|
||||||
|
syn.nEfferentNeuron = vecNodes.size() - 1; |
||||||
|
vecSynapses.push_back(syn); |
||||||
|
|
||||||
|
// Connect Afferent Node to synapse
|
||||||
|
vecNodes[p].vecEfferentSynapse.push_back(vecSynapses.size() - 1); |
||||||
|
|
||||||
|
// Connect this node to synapse
|
||||||
|
vecNodes.back().vecAfferentSynapse.push_back(vecSynapses.size() - 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Output layer
|
||||||
|
vecPreviousLayerIDs = vecNewPreviousLayerIDs; |
||||||
|
for (auto n : vecOutput) // For layer size
|
||||||
|
{ |
||||||
|
// Create New Neuron
|
||||||
|
Neuron node; |
||||||
|
node.bIsInput = false; |
||||||
|
node.bIsOutput = true; |
||||||
|
vecNodes.push_back(node); |
||||||
|
vecNewPreviousLayerIDs.push_back(vecNodes.size() - 1); |
||||||
|
|
||||||
|
// Fully connect it to previous layer
|
||||||
|
for (auto p : vecPreviousLayerIDs) |
||||||
|
{ |
||||||
|
// Create synapse
|
||||||
|
Synapse syn; |
||||||
|
syn.nAfferentNeuron = p; |
||||||
|
syn.fWeight = (float)rand() / (float)RAND_MAX; //0.0f;
|
||||||
|
syn.nEfferentNeuron = vecNodes.size() - 1; |
||||||
|
vecSynapses.push_back(syn); |
||||||
|
|
||||||
|
// Connect Afferent Node to synapse
|
||||||
|
vecNodes[p].vecEfferentSynapse.push_back(vecSynapses.size() - 1); |
||||||
|
|
||||||
|
// Connect this node to synapse
|
||||||
|
vecNodes.back().vecAfferentSynapse.push_back(vecSynapses.size() - 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void PropagateForward() |
||||||
|
{ |
||||||
|
int in_count = 0; |
||||||
|
int out_count = 0; |
||||||
|
for (auto &n : vecNodes) |
||||||
|
{ |
||||||
|
n.fSummedInput = 0; |
||||||
|
if (n.bIsInput) |
||||||
|
{ |
||||||
|
// Node is input, so just set output directly
|
||||||
|
n.fOutput = vecInput[in_count]; |
||||||
|
in_count++; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// Accumulate input via weighted synapses
|
||||||
|
for (auto s : n.vecAfferentSynapse) |
||||||
|
n.fSummedInput += vecNodes[vecSynapses[s].nAfferentNeuron].fOutput * vecSynapses[s].fWeight; |
||||||
|
|
||||||
|
// Activation Function
|
||||||
|
n.fOutput = 1.0f / (1.0f + expf(-n.fSummedInput * 2.0f)); |
||||||
|
|
||||||
|
if (n.bIsOutput) |
||||||
|
{ |
||||||
|
vecOutput[out_count] = n.fOutput; |
||||||
|
out_count++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void PropagateBackwards(vector<float> &vecTrain, float fDelta) |
||||||
|
{ |
||||||
|
// Go through neurons backwards, this way first vecTrain.size() are output layer
|
||||||
|
// and layers are propagated backwards in correct order - smart eh? :P
|
||||||
|
for (int n = vecNodes.size() - 1; n >= 0; n--) |
||||||
|
{ |
||||||
|
if (vecNodes[n].bIsOutput) // Output Layer
|
||||||
|
{ |
||||||
|
vecNodes[n].fError = (vecTrain[vecTrain.size() - (vecNodes.size() - n)] - vecNodes[n].fOutput) * (vecNodes[n].fOutput * (1.0f - vecNodes[n].fOutput)); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
vecNodes[n].fError = 0.0f; |
||||||
|
for (auto effsyn : vecNodes[n].vecEfferentSynapse) |
||||||
|
{ |
||||||
|
float fEfferentNeuronError = vecNodes[vecSynapses[effsyn].nEfferentNeuron].fError; |
||||||
|
float fEfferentSynapseWeight = vecSynapses[effsyn].fWeight; |
||||||
|
vecNodes[n].fError += (fEfferentSynapseWeight * fEfferentNeuronError) * (vecNodes[n].fOutput * (1.0f - vecNodes[n].fOutput)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Update Synaptic Weights
|
||||||
|
for (auto &s : vecSynapses) |
||||||
|
s.fWeight += fDelta * vecNodes[s.nEfferentNeuron].fError * vecNodes[s.nAfferentNeuron].fOutput; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
int main() |
||||||
|
{ |
||||||
|
vector<float> input = { 0,0 }; |
||||||
|
vector<float> output = { 0 }; |
||||||
|
vector<float> train = { 0 }; |
||||||
|
|
||||||
|
NeuralNetwork nn(input, output, 1); |
||||||
|
|
||||||
|
// Example XOR Training
|
||||||
|
for (int i = 0; i < 5000; i++) |
||||||
|
{ |
||||||
|
int nA = rand() % 2; |
||||||
|
int nB = rand() % 2; |
||||||
|
int nY = nA ^ nB; |
||||||
|
|
||||||
|
input[0] = (float)nA; |
||||||
|
input[1] = (float)nB; |
||||||
|
train[0] = (float)nY; |
||||||
|
|
||||||
|
nn.PropagateForward(); |
||||||
|
|
||||||
|
int nO = (int)(output[0] + 0.5f); |
||||||
|
cout << "A: " << nA << " B: " << nB << " Y: " << nY << " NN: " << nO << " (" << to_string(output[0]) << ") " << (nO == nY ? "PASS" : "FAIL") << endl; |
||||||
|
nn.PropagateBackwards(train, 0.5f); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
//system("pause");
|
||||||
|
return 0; |
||||||
|
}; |
Loading…
Reference in new issue