Neuronale Netze I

Ein neuronales Netz besteht aus autonomen Zellen (Neuronen), die Zahlen empfangen und nach der Multiplikation mit Faktoren (Wichtungen) addieren. Ist die Summe größe als ein Schwellenwert, wird eine neue Zahl erzeugt und an nachfolgenden Neuronen übermittelt. Die Neuronen sind dabei in Ebenen angeordnet, die Zahlen laufen von einer Ebenen zur nächste Ebene. Die letzte Ebene liefert das Ergebnis.

Lernen kann das System durch Verändern der Wichtungen, wobei die Änderungen über entsprechende Funktionen gesteuert werden. Im hier vorgestellten Beispiel wird zu einem Muster von Eingangszahlen ein Ausgabemuster vorgegeben, das dann mit dem vom Netz berechneten Muster verglichen wird. Die Abweichung beider Muster wird durch eine Zahl ausgedrückt, die die Weite der Wichtungsänderungen eingrenzt (überwachtes Lernen).
Es gibt auch die Möglichkeit, das Netz zu optimieren, ohne ein Ausgabemuster vorzugeben (unüberwachtes Lernen).

Die Idee

Die einzelnen Neuronen und ihre Aktionen werden in einer eigenen Klasse beschrieben. Das Neuronennetz wird in einem Array abgelegt, wobei die einzelnen Ebenen nacheinander bis zur Ausgangsebene angeordnet werden. Ferner wird parallel ein Zahlenarray angelegt, das die Eingangswerte und die von den Neuronen berechneten Ausgangswerte enthält.
Die Zahl der Ebenen und der Neuronen in den Ebenen kann frei vorgegeben werden. Identisch sind die Neuronenzahl der ersten Ebene und die Zahl der Eingangswerte, ebenso die der letzten Ebene und die Zahl der Ausgabenwerte.

Das System soll ein binäres Eingangsmuster, bestehend aus den Zahlen 0 und 1, mit einem Ausgabemuster verbinden. Das Ausgabemuster wird aus den Zahlen 0 und 1 vorgegeben. Die Ausgabe zeigt dann das berechnete Muster und, wie viele Schritte das System zum Lernen benötigte und wie groß die Abweichung von berechnetem und vorgegebenem Ausgabemuster ist. Ein- und Ausgaben erfolgen der Einfachheit halber im Textmodus ohne Nutzung der grafischen Oberfläche.

Das Programm

Es sollen hier die wesentlichen Bestandteile des neuronalen Netzes vorgestellt und kommentiert werden:

Berechnung des Ausgangswertes eines Neurons: Ein Neuron bekommt über das Array ein[] Eingangswerte geliefert. ein[] enthält alle vorgegebenen und alle bisher berechneten Eingangswerte. Das Neuron darf aber nur die Ergebnisse der Vorgängerebene auswerten. Diese beginnen bei dem Index anf, während inz deren Anzahl angibt. anf und inz sind Variablen, die bei der Initialisierung des Neurons mit new neuron(..) konkrete Werte erhalten haben (vgl. Gesamtlisting).
Die Eingangswerte werden mit Hilfe entsprechender Faktoren aus dem Array fak[] gewichtet und addiert. Der Ausgangswert könnte über eine Abfrage festgelegt werden, wird aber hier über die Sigmoid-Funktion s(x)=1/(exp(-x)+1) berechnet, um näher am Verhalten natürlicher Systeme zu sein. Dies erfordert, dass x nach unten beschränkt wird, da sonst für zu kleine x exp(-x) nicht mehr berechnet werden kann.

public void wert(double ein[]) {
  double s=0;
  for(int i=0;i   s=Math.max(s,-1000);
  ein[pos]=1/(Math.exp(-s)+1);
};
Methodenkopf, Übergabe des Arrays ein[] mit den Eingangswerten

Berechnung der gewichteten Summe s
Beschränkung von s Berechnung des Sigmoid-Wertes von s


Das Lernen erfolgt über die Methode lernen(kickN), wobei kickN die Abweichung des berechneten vom vorgegebenen Gesamtergebnis ist. Zunächst wird getestet, ob die Abweichung der neu berechneten Ausgangswerte von den Vorgaben sich gegenüber dem bisher kleinsten Wert verkleinert hat. Ist dies der Fall, so wird die Mindestabweichung durch die aktuelle Abweichung ersetzt, desgleichen die alten Faktoren durch die aktuellen. Als Faktoren für eine neue Werteberechnung werden die Faktoren um eine Zufallszahl verändert, die von der Lernrate lrn und der aktuellen Abweichung abhängt. Die Lernrate kann am Anfang des Programmtextes entsprechend eingestellt werden.

public void lernen(double kickN) {
  if(kickN<kickO) /
    for(int i=0;i<inz;i++) fakO[i]=fak[i];
    kickO=kickN;
  }
  for(int i=0;i<inz;i++)
    fak[i]=fakO[i]+lrn*(Math.random()-0.5)*kickN;
};
Methodenkopf, Übergabe der Abweichung der Ausgangswerte
wenn die aktuelle Abweichung kleiner als die bisher kleinste, dann
alte Faktoren fakO[i] ersetzen durch die aktuellen Faktoren fak[i]
Mindestabweichung kickO ersetzen durch aktuelle Abweichung kick

für alle Eingänge Neuberechnung der Faktoren fak[i] durch:
fakO[i]+Zufallsänderung*kickN*Lernrate lrn


Das Netz wird trainiert, indem in einer Schleife wert(ein) für alle Neuronen all[i] aufgerufen, dann die Abweichung kick des errechneten Ausgangsmusters von der Vorgabe bestimmt und anschließend für alle Neuronen lernen(kick) aufgerufen wird. Die Schleife endet, wenn kick eine vorgegebene Zahl unterschreitet oder die Lernschrittzahl lsz eine Höchstzahl erreicht:

for(lsz=0;(lsz<10000)&&(kick>0.02);lsz++) {
  for(int i=0;i<gez;i++) al[i].wert(ein);

  kick=0;
  for(int i=0;i<nez;i++) {
    kickN=kickN+Math.abs(aus[i]-ein[fez+i]);
  };

  for(int i=0;i<gez;i++) al[i].lernen(kick);
};

Schleife, Abbruch, wenn lsz 10000 ist oder kick 0.02 unterschreitet
Berechung der Ausgangswerte aller Neuronen
 
 
Berechnung der Abweichung: fez Anfangsindex der letzten Ebene in all[],
  ein[fez+i] berechneter Ausgang, aus[i] zugehörige Vorgabe
 
 
Lernprozess für alle Neuronen

Das vollständige Programm findet man unter neusto.java. Es ist im Wesentlichen durch Ein- und Ausgaben und Initialisierungen der Klassen neuron und neusto vervollständigt worden. Die Ausgabe zeigt, wie erfolgreich der Lernprozess bei einem vorgegebenen Eingangsmuster war:

Oben findet man die Eingaben zur Organisation des Netzes mit 3 Ebenen, 4 Eingängen, den Neuronenzahlen pro Ebene und 2 Ausgängen.

Es folgen die Vorgaben für das Eingabe- und das Ausgabemuster.

Angezeigt werden dann Werte der einzelnen Lernschritte:
Jeweils einer Zahl des Ausgabemusters folgt nach einem Doppelpunkt der entsprechende, vom Netz berechnete Wert.

Zum Schluss wird die Zahl der Lernschritte und die letzte Abweichung von vorgegeber und berechneter Ausgabe angezeigt.


Das Programm erlaubt, mehrere Muster mit entsprechenden Vorgaben für die Ausgänge einzugeben. Nach dem Lernprozess kann man weitere Muster eingeben und testen, welche Ausgangsmuster dann erzeugt werden. Interessant ist, wie nahe diese an den Ausgangsvorgaben liegen, also welche Ähnlichkeiten das Netz entdeckt.

Der Lernprozess muss nicht über Zufallszahlen ablaufen: Wenn man die Abweichungen der einzelnen Ausgänge nicht in einer Summe zusammenfasst und auch nicht den Betrag bildet, kann man die Abweichungen direkt zur Korrektur der Wichtungen nutzen. Bei mehreren Ebenen wären dann die Abweichungen mittels der Eingangswichtungen zu neuen Werten umzurechnen, bevor man sie als Abweichungen in darunter liegenden Neuronenebenen nutzt (Delta-Regel, Backpropagation).

Ein umfassendes Programm, das vielseitige Möglichkeiten zur Netzorganisation bietet und das in der Lage ist, einfache grafische Muster zu erkennen, findet man in dem Paket neuronnetz.zip. Das Paket enthält ein konfigurierbares Java-Programm (.jar), Beispielmuster und die zugehörige Beschreibung.