Based on a C++ tutorial
- Connection.pde -> Class for handling the connections between the neurons
- Layer.pde -> Class with neuron array in it
- Net.pde -> Class works as neural net
- Neuron.pde -> Class for handling a neuron
- getData.pde -> Contains the handling with inputdata and outputdata
- main.pde -> Handles all the other files and contains setup() and draw()
Topology is the basic array for building the neural net. Every item is a layer and its value is the number of neurons in it.
//Create topology -> Item = Neurons in this layer
int[] topology = new int[] { 4, 5, 6, 5};
//So basicly
int totalLayerNumber = topology.length; //4 layers
int neuronNumber = topology[2]; //6 neurons
The next step is to feed the network with trainingdata
void draw() {
background(51);
inputVals = getNextInput(); //Get the next inputValues
targetVals = calcOutput(inputVals); //Targetvalues get calculated here for this example
//could be already set preprocessing like
//targetVals = getNextTargets();
myNet.feedForward(inputVals); //Feed the neural net
resultVals = myNet.getResults(resultVals); //get the values calculated by the neural net
myNet.backProp(targetVals); //Check the result against the target and readjust the weights
}
There are a few functions we need to take a look at:
Here we calculate our input data. In this case it´s a random array with four items. Each item can be wether a one or a zero.
The function returns an double array. The item count should fit to the neuron count in the first layer, the input layer.
//get random inputData
double[] getNextInput() {
double[] inputVals = new double[] { round(random(0, 1)), round(random(0, 1)), round(random(0, 1)), round(random(0, 1)) };
return inputVals;
}
//In this example we have a input layer with 4 neurons + 1 bias neuron
//That´s why inputVals has 4 items
And here we calculate the target output. We want to know how many zeros in the array are. There are five possible outputs. For example if there are two zeros in the input the output array looks like { 0, 0, 1, 0, 0 }
The 1 is at the location where the corresponding number is. So the 1 in the example above means that there are two zeros(rendered as black squares/rectangles)
//Calculate the output
double[] calcOutput(double[] inputData) {
int count = 0;
double[] data = new double[] {};
//Count the dark rectangles
for(int i = 0; i < inputData.length; i++) {
if(inputData[i] == 0) {
count++;
}
}
//set the targetValues
for(int c = 0; c < inputData.length + 1; c++) {
if(c == count) {
data = (double[])append(data, 1);
} else {
data = (double[])append(data, 0);
}
}
return data;
}
This function triggers the feedForward function for each neuron
void feedForward(double[] inputVals) {
assert(inputVals.length == m_layers[0].neuron.length - 1); //Assert that the inputValues are as many as neurons in the first layer
for (int i = 0; i < inputVals.length; ++i) { //assign (latch) the input values into input neurons
m_layers[0].neuron[i].setOutputVal(inputVals[i]);
}
for (int layerNum = 1; layerNum < m_layers.length; ++layerNum) { //Forward propagate
Layer prevLayer = m_layers[layerNum - 1];
for (int n = 0; n < m_layers[layerNum].neuron.length - 1; ++n) {
m_layers[layerNum].neuron[n].feedForward(prevLayer);
}
}
}
So here is the feedForward in a single neuron
void feedForward(Layer prevLayer) {
double sum = 0.0;
//Sum the previous layer´s outputs (which are our inputs)
//Include the bias node from the previous layer.
for (int n = 0; n < prevLayer.neuron.length; ++n) {
sum += prevLayer.neuron[n].getOutputVal()
* prevLayer.neuron[n].m_outputWeights[m_myIndex].weight;
}
m_outputVal = transferFunction(sum); //This is a function to limit the value between -1 and 1 and sets the outputvalue to it
}
Here we get the resultvalues that are calculated by the neural network. We get them here to render them in the window. This is not necessary for the further process.
double[] getResults(double[] resultVals) {
resultVals = new double[] {};
for (int n = 0; n < m_layers[m_layers.length - 1].neuron.length - 1; ++n) {
resultVals = (double[])append(resultVals, m_layers[m_layers.length - 1].neuron[n].getOutputVal());
//Here we get the outputvalues of the neurons in the last layer, the output layer
}
return resultVals;
}
Here we readjust the weights of the single connections to get better results
void backProp(double[] targetVals) {
//Calculate overall net error (RMS of output neuron errors)
Layer outputLayer = m_layers[m_layers.length - 1];
m_error = 0.0;
for (int n = 0; n < outputLayer.neuron.length - 1; ++n) {
double delta = targetVals[n] - outputLayer.neuron[n].getOutputVal();
m_error += delta * delta;
}
m_error /= outputLayer.neuron.length - 1; //get average error squared
m_error = Math.sqrt(m_error); //rms
//Implement a recent average measurment:
m_recentAverageError = (m_recentAverageError * m_recentAverageSmoothingFactor + m_error) / (m_recentAverageSmoothingFactor + 1.0);
//Claculate output layer gradients
for (int n = 0; n < outputLayer.neuron.length - 1; ++n) {
outputLayer.neuron[n].calcOutputGradients(targetVals[n]);
}
//Calculate gradients on hidden layers
for (int layerNum = m_layers.length - 2; layerNum > 0; --layerNum) {
Layer hiddenLayer = m_layers[layerNum];
Layer nextLayer = m_layers[layerNum + 1];
for (int n = 0; n < hiddenLayer.neuron.length; ++n) {
hiddenLayer.neuron[n].calcHiddenGradients(nextLayer);
}
}
//For all layers from outputs to first hidden layer
//update connection weights
for (int layerNum = m_layers.length - 1; layerNum > 0; --layerNum) {
Layer layer = m_layers[layerNum];
Layer prevLayer = m_layers[layerNum - 1];
for (int n = 0; n < layer.neuron.length - 1; ++n) {
layer.neuron[n].updateInputWights(prevLayer);
}
}
}
For a deeper explanation please watch the C++ tutorial linked here