The official distribution of olcConsoleGameEngine, a tool used in javidx9's YouTube videos and projects
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
videos/NeuralNetwork.cpp

214 lines
5.5 KiB

// 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;
};