Last active
July 4, 2017 14:56
-
-
Save FlorianCassayre/3f4e6f5c1497a2e64f3ac6493d97daba to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
final int HIDDEN_LAYERS = 1; | |
final int INPUTS = 4 * 2; | |
final int OUTPUTS = 4; | |
final int NEURONS_PER_LAYER = 16; | |
final float LEARNING_RATE = 0.1; | |
final float[][] neurons = new float[2 + HIDDEN_LAYERS][]; // [layer][neuron] | |
final float[][][] axons = new float[1 + HIDDEN_LAYERS][][]; // [layer][previous_neuron][next_neuron] | |
final int MARGIN = 15, NEURON_DIAMETER = 50, AXON_LENGTH = 200; | |
final int INITIAL_ITERATIONS = 1000000; | |
int iterations = 0; | |
float error; | |
int exp; | |
int actual; | |
void setup() | |
{ | |
neurons[0] = new float[INPUTS]; | |
for(int i = 1; i < neurons.length; i++) | |
neurons[i] = new float[NEURONS_PER_LAYER]; | |
neurons[neurons.length - 1] = new float[OUTPUTS]; | |
axons[0] = new float[INPUTS][NEURONS_PER_LAYER]; | |
for(int i = 1; i < axons.length - 1; i++) | |
axons[i] = new float[NEURONS_PER_LAYER][NEURONS_PER_LAYER]; | |
axons[axons.length - 1] = new float[NEURONS_PER_LAYER][OUTPUTS]; | |
surface.setSize( | |
2 * MARGIN + neurons.length * NEURON_DIAMETER + axons.length * AXON_LENGTH, | |
2 * MARGIN + max(max(INPUTS, OUTPUTS), NEURONS_PER_LAYER) * (MARGIN + NEURON_DIAMETER) - MARGIN | |
); | |
for(int i = 0; i < axons.length; i++) | |
{ | |
for(int j = 0; j < axons[i].length; j++) | |
{ | |
for(int k = 0; k < axons[i][j].length; k++) | |
{ | |
axons[i][j][k] = random(-1, 1); | |
} | |
} | |
} | |
for(int i = 0; i < INPUTS; i++) // 0 and 1 representing the bits | |
neurons[0][i] = random(0, 1); | |
computeResult(); | |
frameRate(60); | |
for(int i = 0; i < INITIAL_ITERATIONS; i++) // Initial training | |
update(); | |
} | |
void draw() | |
{ | |
background(255); | |
for(int layer = 0; layer < axons.length; layer++) | |
{ | |
for(int neuron = 0; neuron < neurons[layer].length; neuron++) | |
{ | |
final float x = getNeuronX(layer), y = getNeuronY(layer, neuron); | |
for(int axon = 0; axon < axons[layer][neuron].length; axon++) | |
{ | |
final float x1 = x + NEURON_DIAMETER + AXON_LENGTH; | |
final float y1 = getNeuronY(layer + 1, axon); | |
strokeWeight(3.0); | |
stroke(getColor(sigmoid(axons[layer][neuron][axon]) * 2 - 1)); | |
line(x, y, x1, y1); | |
} | |
} | |
} | |
for(int layer = 0; layer < neurons.length; layer++) // No other way to iterate again in order to avoid overlapping | |
{ | |
for(int neuron = 0; neuron < neurons[layer].length; neuron++) | |
{ | |
final float x = getNeuronX(layer), y = getNeuronY(layer, neuron); | |
final float value = neurons[layer][neuron]; | |
strokeWeight(3.0); | |
stroke(0); | |
fill(getColor(value * 2 - 1)); | |
ellipse(x, y, NEURON_DIAMETER, NEURON_DIAMETER); | |
fill(255); | |
textAlign(CENTER, CENTER); | |
textSize(15.0); | |
text(numberFormat(value), x - NEURON_DIAMETER / 2.0, y - NEURON_DIAMETER / 2.0, NEURON_DIAMETER, NEURON_DIAMETER); | |
} | |
} | |
fill(0); | |
textAlign(LEFT); | |
text(iterations + " iterations", 3, 15); | |
if(exp == actual) | |
fill(0, 255, 0); | |
else | |
fill(255, 0, 0); | |
text(((exp >> 3) & 1) + "" + ((exp >> 2) & 1) + "" + ((exp >> 1) & 1) + "" + (exp & 1), 3, 30); | |
} | |
void keyPressed() | |
{ | |
update(); | |
} | |
void mouseClicked() | |
{ | |
update(); | |
} | |
void update() | |
{ | |
for(int i = 0; i < INPUTS; i++) | |
neurons[0][i] = random(0, 1) >= 0.5 ? 1.0 : 0.0; | |
computeResult(); | |
backpropagate(); | |
} | |
void computeResult() | |
{ | |
for(int i = 1; i < neurons.length; i++) | |
{ | |
for(int j = 0; j < neurons[i].length; j++) | |
{ | |
float sum = 0; | |
for(int k = 0; k < neurons[i - 1].length; k++) | |
{ | |
sum += neurons[i - 1][k] * axons[i - 1][k][j]; | |
} | |
neurons[i][j] = sigmoid(sum); | |
} | |
} | |
} | |
int input(int i) | |
{ | |
return round(neurons[0][i]); | |
} | |
int output(int i) | |
{ | |
return round(neurons[neurons.length - 1][i]); | |
} | |
void backpropagate() | |
{ | |
final int i1 = (input(4) << 3) | (input(5) << 2) | (input(6) << 1) | input(7), i2 = (input(0) << 3) | (input(1) << 2) | (input(2) << 1) | input(3); | |
exp = (i1 + i2) & 0xf; | |
final float[] expected = {(exp >> 3) & 1, (exp >> 2) & 1, (exp >> 1) & 1, exp & 1}; | |
actual = (output(0) << 3) | (output(1) << 2) | (output(2) << 1) | output(3); | |
final float[][] errors = new float[neurons.length - 1][]; | |
for(int i = 0; i < errors.length - 1; i++) | |
errors[i] = new float[NEURONS_PER_LAYER]; | |
errors[errors.length - 1] = new float[OUTPUTS]; | |
for(int axon = 0; axon < OUTPUTS; axon++) | |
{ | |
final float neuronError = (neurons[neurons.length - 1][axon] - expected[axon]) * neurons[neurons.length - 1][axon] * (1 - neurons[neurons.length - 1][axon]); | |
errors[errors.length - 1][axon] = neuronError; | |
for(int neuron = 0; neuron < neurons[neurons.length - 2].length; neuron++) | |
{ | |
axons[axons.length - 1][neuron][axon] -= neuronError * neurons[neurons.length - 2][neuron] * LEARNING_RATE; | |
} | |
} | |
for(int layer = errors.length - 2; layer >= 0; layer--) | |
{ | |
for(int neuron = 0; neuron < NEURONS_PER_LAYER; neuron++) | |
{ | |
float d = 0; | |
for(int axon = 0; axon < axons[layer + 1][neuron].length; axon++) | |
{ | |
d += errors[layer + 1][axon] * axons[layer + 1][neuron][axon]; | |
} | |
d *= neurons[layer + 1][neuron] * (1 - neurons[layer + 1][neuron]); | |
errors[layer][neuron] = d; | |
for(int axon = 0; axon < neurons[layer].length; axon++) | |
{ | |
axons[layer][axon][neuron] -= d * neurons[layer][axon] * LEARNING_RATE; | |
} | |
} | |
} | |
error = abs(expected[0] - neurons[2][0]); | |
iterations++; | |
} | |
String numberFormat(float f) | |
{ | |
return nf(f, 1, 2).replace(",", "."); | |
} | |
float sigmoid(float x) | |
{ | |
return 1.0 / (1.0 + exp(-x)); | |
} | |
color getColor(float x) | |
{ | |
return color(255 * (1 + x) / 1.5, 0, 255 * (1 - x) / 1.5); // Blue => -1.0, Red => 1.0 (overflow allowed) | |
} | |
float getNeuronX(int layer) | |
{ | |
return MARGIN + NEURON_DIAMETER / 2.0 + layer * (AXON_LENGTH + NEURON_DIAMETER); | |
} | |
float getNeuronY(int layer, int neuron) | |
{ | |
final float border = (height - neurons[layer].length * (NEURON_DIAMETER + MARGIN) - MARGIN) / 2.0; | |
return border + MARGIN + NEURON_DIAMETER / 2.0 + (NEURON_DIAMETER + MARGIN) * neuron; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment