GMU:Neuronale Netze - eine Einführung für Künstler/Präsentation/Neuronale Netze in Processing: Difference between revisions

From Medien Wiki
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 rechts).
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 global [http://en.wikipedia.org/wiki/Variable_(programming)#Scope_and_extent Scope] zur Verfügung stehen.
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. Zunächst erfolgt...
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.train(inp, known);
     float result = nn.feedForward(inp);
     epoch++;
    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>



Revision as of 00:27, 12 July 2010

Processing

Processing IDE

Processing ist eine Programmiersprache und Entwicklungsumgebung, die 2001 am Massachusetts Institute of Technology von den Erfindern Ben Fry und Casey Reas als quelloffenes Projekt ins Leben gerufen wurde. Die Sprache wurde speziell für die Bereiche Simulation und Animation entworfen und richtet sich daher vorwiegend an Personen aus der Gestaltung und Kunst.

Das sogenannte Sketchbook verkörpert das Prinzip einer stark vereinfachten IDE (Integrated Development Environment). Dem Nutzer steht eine simple Toolbar, ein Editor und Textfeld zur Verfügung. Über das Hauptprogramm hinaus erzeugte Klassen werden in eigenen Tabs untergebracht. Jede dieser Sketches ist tatsächlich nur eine Unterklasse der PApplet Java-Klasse, welche fast alle Features der Sprache implementiert. Alle zusätzlich erstellten Klassen werden beim Kompilieren als innere Klassen behandelt. Wenn man in Java programmieren will, muss dies explizit geschehen.

Weitere Informationen bzgl. Processing und dessen Verwendung lassen sich auf der Hauptseite und im Wiki-Eintrag zu Processing finden.

Neuronale Netze

Da man in Processing direkt in Java programmieren kann, sind einem bzgl. der Komplexität der Programme keine Grenzen gesetzt. Für die einfache Erstellung neuronaler Netze kann man zusätzliche Libraries verwenden, die man dem Sketchbook beifügt. Als Beispiel wird hier die nn-library von Daniel Shiffman verwendet.

i(1) i(2)
0.2 0.2 0.2
0.2 0.2 0.8
0.8 0.2 0.8
0.8 0.8 0.2

Bei dem unteren Beispiel handelt es sich um eine leicht modifizierte Version eines neuronalen Netzes von Daniel Shiffman [1]. Mittels eines Multilayer Perceptrons wird hier versucht das nichtlinear trennbare 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 Backpropagation [0.2, 0.8] gewählt (siehe angepasste Wahrheitstabelle links).

XOR possible state (1)
XOR possible state (2)
XOR possible state (3)
XOR possible state (4)
XOR possible state (5)


Anlegen eines neuronalen Netzes

Multilayer Perceptron

Zunächst erzeugt man ein neues Netzwerk, für welches man vorgibt, wieviele input und hidden Neuronen enthalten sein sollen. Zusätzlich wird je Ebene intern ein BIAS Neuron angelegt (siehe Abbildung rechts). Nach Modifikation der Library wäre es ebenfalls möglich die Anzahl der Output-Neuronen zu bestimmen. Davon wurde hierbei jedoch abgesehen.

Network net = new Network(2,3);

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 globalen Scope zur Verfügung stehen.

ArrayList inputs = new ArrayList();

void setup() {
  size(400, 400, OPENGL);
  inputs.add(new float[] { 0, 0 });
  inputs.add(new float[] { 1, 0 });
  inputs.add(new float[] { 0, 1 });
  inputs.add(new float[] { 1, 1 });
}

Das Training wird randomisiert jeden Frame 5 mal wiederholt.

void draw() {
  // One epoch.
  int rate = 5;
  for (int i = 0; i < rate; i++) {
    // Randomly select from training set.
    int pick = int(random(inputs.size()));
    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);
  }
}

Um den mittleren quadratischen Fehler zu erhalten, berechnet man zunächst die Ausgabe des gesamten Netzes und anschließend den Fehler wie folgt:

float calcError() {
  float mse = 0.0;
  for (int i = 0; i < inputs.size(); ++i) {
    float[] inp = (float[]) inputs.get(i); 
    float known = 0.8;
    if ((inp[0] > 0.5 && inp[1] > 0.5) || (inp[0] < 0.5 && inp[1] < 0.5)) known = 0.2;
    float result = nn.feedForward(inp);
    mse += (result - known) * (result - known);
  }
  // Return root mean squared error.
  return sqrt(mse / inputs.size());
}

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:

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

Bei f(float) handelt es sich um die Aktiviersungsfunktion (transfer function), hier eine Sigmoid Funktion.

public static float f(float x) {
  return 1.0f / (1.0f + (float) Math.exp(-x));
}

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:

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

Der Lernprozess - Backpropagation

Als Eingabe wird ein ein zufällig gewähltes Paar an Werte aus dem Trainingsset und der entsprechende Erwartungswert übergeben.

public float train(float[] inputs, float answer) {
  ...
}

Zunächst wird die derzeitige Netzausgabe via eines Feed-forward Systems berechnet - Ebene für Ebene werden die Ausgabewerte einzelner Neuronen berechnet, um am Ende eine Netzausgabe zu erhalten.

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

Anpassung innerer Gewichte - vom Hidden Layer zum Input Layer.

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;

Referenzen