65
edits
Line 24: | Line 24: | ||
|} | |} | ||
Bei dem unteren Beispiel handelt es sich um eine leicht modifizierte Version eines neuronalen Netzes von Daniel Shiffman <ref>Daniel Shiffman: [http://www.shiffman.net/teaching/nature/nn/ The Nature of Code - Neural Networks]</ref>. Mittels eines [http://en.wikipedia.org/wiki/Multilayer_perceptron Multilayer Perceptrons] wird hier versucht das nichtlinear trennbare [http://en.wikipedia.org/wiki/Xor XOR Problem] zu lösen. Jede Ecke des Würfels entspricht einer 0 oder 1, wobei sich gleiche Werte je Fläche diagonal spiegeln, d.h. benachbarte Eckpunkte sind nie gleich 0 oder 1. Zwischen 2 Eckpunkten wird entlang der Flächen interpoliert, sodass man für Werte zwischen 0 und 1 eine Annährung erhält. Zur Verdeutlichung wurde im Folgenden als mögliches Antwortpaar für das Training via [http://en.wikipedia.org/wiki/Backpropagation Backpropagation] [0.2, 0.8] gewählt (siehe angepasste Wahrheitstabelle | Bei dem unteren Beispiel handelt es sich um eine leicht modifizierte Version eines neuronalen Netzes von Daniel Shiffman <ref>Daniel Shiffman: [http://www.shiffman.net/teaching/nature/nn/ The Nature of Code - Neural Networks]</ref>. Mittels eines [http://en.wikipedia.org/wiki/Multilayer_perceptron Multilayer Perceptrons] wird hier versucht das nichtlinear trennbare [http://en.wikipedia.org/wiki/Xor XOR Problem] zu lösen. Jede Ecke des Würfels entspricht einer 0 oder 1, wobei sich gleiche Werte je Fläche diagonal spiegeln, d.h. benachbarte Eckpunkte sind nie gleich 0 oder 1. Zwischen 2 Eckpunkten wird entlang der Flächen interpoliert, sodass man für Werte zwischen 0 und 1 eine Annährung erhält. Zur Verdeutlichung wurde im Folgenden als mögliches Antwortpaar für das Training via [http://en.wikipedia.org/wiki/Backpropagation Backpropagation] [0.2, 0.8] gewählt (siehe angepasste Wahrheitstabelle links). | ||
[[Image:processing_neural_network_xor_1.png|thumb|left|100px|XOR possible state (1)]] | [[Image:processing_neural_network_xor_1.png|thumb|left|100px|XOR possible state (1)]] | ||
Line 44: | Line 44: | ||
=== Trainieren des neuronalen Netzes === | === Trainieren des neuronalen Netzes === | ||
In der setup() Methode initialisiert man einmalig die Eingabewerte (hier mit den ursprünglichen Werten 0 und 1 für das XOR Problem), welche in der | In der setup() Methode initialisiert man einmalig die Eingabewerte (hier mit den ursprünglichen Werten 0 und 1 für das XOR Problem), welche in der globalen [http://en.wikipedia.org/wiki/Variable_(programming)#Scope_and_extent Scope] zur Verfügung stehen. | ||
<source lang="java"> | <source lang="java"> | ||
Line 58: | Line 58: | ||
</source> | </source> | ||
Das Training wird randomisiert jeden Frame 5 mal wiederholt | Das Training wird randomisiert jeden Frame 5 mal wiederholt. | ||
<source lang="java"> | <source lang="java"> | ||
void draw() { | |||
// One epoch. | |||
int rate = 5; | int rate = 5; | ||
for (int i = 0; i < rate; i++) { | for (int i = 0; i < rate; i++) { | ||
// Randomly select from training set. | |||
int pick = int(random(inputs.size())); | int pick = int(random(inputs.size())); | ||
float[] inp = (float[]) inputs.get(pick); | float[] inp = (float[]) inputs.get(pick); | ||
float known = 1.0; | |||
// Choose the appropriate answer for the training's input. | |||
if ((inp[0] > 0.5 && inp[1] > 0.5) || (inp[0] < 0.5 && inp[1] < 0.5)) known = 0.0; | |||
float result = net.train(inp, known); | |||
} | |||
} | |||
</source> | |||
Um den mittleren quadratischen Fehler zu erhalten, berechnet man zunächst die Ausgabe des gesamten Netzes und anschließend den Fehler wie folgt: | |||
<source lang="java"> | |||
float calcError() { | |||
float mse = 0.0; | |||
for (int i = 0; i < inputs.size(); ++i) { | |||
float[] inp = (float[]) inputs.get(i); | |||
float known = 0.8; | float known = 0.8; | ||
if ((inp[0] > 0.5 && inp[1] > 0.5) || (inp[0] < 0.5 && inp[1] < 0.5)) known = 0.2; | if ((inp[0] > 0.5 && inp[1] > 0.5) || (inp[0] < 0.5 && inp[1] < 0.5)) known = 0.2; | ||
float result = net. | float result = nn.feedForward(inp); | ||
mse += (result - known) * (result - known); | |||
} | |||
// Return root mean squared error. | |||
return sqrt(mse / inputs.size()); | |||
} | |||
</source> | |||
== Erläuterung der Library == | |||
Ein Neuron hat grundlegend folgende Eigenschaften: | |||
* Ausgabewert | |||
* Liste der Verbindungen | |||
* BIAS-Status (0 oder 1) | |||
Für alle nicht-BIAS Neuronen ist der BIAS-Status 0. Ein Neuron ist dann ein BIAS Neuron, wenn es bei der Initialisierung einen beliebigen Integer erhält. Die Ausgabe eines Neurons wird für jedes einzeln berechnet. Dafür stellt die Basisklasse folgende Methode bereit: | |||
<source lang="java"> | |||
public void calcOutput() { | |||
if (!bias) { | |||
float sum = 0; | |||
for (int i = 0; i < connections.size(); ++i) { | |||
Connection c = (Connection) connections.get(i); | |||
Neuron from = c.getFrom(); | |||
Neuron to = c.getTo(); | |||
// Only calculate the incoming connection's weight (input). | |||
if (to == this) { sum += from.getOutput() * c.getWeight(); } | |||
} | |||
output = f(sum); | |||
} | |||
} | |||
</source> | |||
Bei <code>f(float)</code> handelt es sich um die [http://en.wikipedia.org/wiki/Artificial_neuron#Types_of_transfer_functions Aktiviersungsfunktion] (transfer function), hier eine Sigmoid Funktion. | |||
<source lang="java"> | |||
public static float f(float x) { | |||
return 1.0f / (1.0f + (float) Math.exp(-x)); | |||
} | |||
</source> | |||
Bei der initialen Erstellung des Netzes werden die einzelnen Ebenen miteinander verbunden. Eine Verbindung hält die Referenz von einem Neuron zu einem anderen, sowie das dazugehörige Gewicht. Für den Konstruktor und die Gewichtsanpassung ergibt sich: | |||
<source lang="java"> | |||
public Connection(Neuron a_, Neuron b_) { | |||
from = a_; | |||
to = b_; | |||
// A random value between -1 and 1. | |||
weight = (float) Math.random() * 2 - 1; | |||
} | |||
public void adjustWeight(float delta) { | |||
weight += delta; | |||
} | |||
</source> | |||
=== Der Lernprozess - Backpropagation === | |||
Als Eingabe wird ein ein zufällig gewähltes Paar an Werte aus dem Trainingsset und der entsprechende Erwartungswert übergeben. | |||
<source lang="java"> | |||
public float train(float[] inputs, float answer) { | |||
... | |||
} | |||
</source> | |||
Zunächst wird die derzeitige Netzausgabe via eines [http://en.wikipedia.org/wiki/Feed-forward#Feed-forward_systems_in_computing Feed-forward] Systems berechnet - Ebene für Ebene werden die Ausgabewerte einzelner Neuronen berechnet, um am Ende eine Netzausgabe zu erhalten. | |||
<source lang="java"> | |||
float result = feedForward(inputs); | |||
public float feedForward(float[] inputVals) { | |||
// Feed input. | |||
for (int i = 0; i < inputVals.length; i++) { | |||
input[i].input(inputVals[i]); | |||
} | |||
// Calculate hidden layer output. | |||
for (int i = 0; i < hidden.length - 1; i++) { | |||
hidden[i].calcOutput(); | |||
} | |||
// Calculate net output. | |||
output.calcOutput(); | |||
return output.getOutput(); | |||
} | |||
</sourceu> | |||
Daraufhin werden zuerst die äußeren Gewichte, vom Output zum Hidden Layer, angepasst. | |||
<source lang="java"> | |||
ArrayList connections = output.getConnections(); | |||
float deltaOutput = result * (1 - result) * (answer - result); | |||
for (int i = 0; i < connections.size(); i++) { | |||
Connection c = (Connection) connections.get(i); | |||
c.adjustWeight(deltaOutput * c.getFrom().getOutput() * ETA); | |||
} | |||
</source> | |||
Anpassung innerer Gewichte - vom Hidden Layer zum Input Layer. | |||
<source lang="java"> | |||
for (int i = 0; i < hidden.length; i++) { | |||
connections = hidden[i].getConnections(); | |||
float sum = 0; | |||
for (int j = 0; j < connections.size(); j++) { | |||
Connection c = (Connection) connections.get(j); | |||
if (c.getFrom() == hidden[i]) { sum += c.getWeight() * deltaOutput; } | |||
} | |||
for (int j = 0; j < connections.size(); j++) { | |||
Connection c = (Connection) connections.get(j); | |||
if (c.getTo() == hidden[i]) { | |||
float output = hidden[i].getOutput(); | |||
float deltaHidden = output * (1 - output) * sum; | |||
c.adjustWeight(deltaHidden * c.getFrom().getOutput() * ETA); | |||
} | |||
} | } | ||
} | |||
return result; | |||
</source> | </source> | ||
edits