Man wird im letzten Kapitel feststellen, dass der Lernprozess aufgrund des Zufallsanteils bei den Wichtungsänderungen nicht sehr zielgerichtet abläuft und
dadurch mehr Lernschritte als erforderlich benötigt. Allerdings dürfte dies Verfahren den Lernprozessen in der Natur noch am nächsten kommen.
Ein anderes Verfahren ist, die Wichtungsänderung direkt über die Abweichungen von vorgegebenen und berechneten Ausgangswerten durchzuführen. Verfahren
dieser Art sind die Delta-Regel und der Backpropagation-Algorithmus.
Eine Ebene tiefer kann man den Fehler durchkoppeln oder geeignet anpassen: Da die Eingangswerte mit Wichtungen multipliziert werden, muss bei einer kleinen Wichtung der Fehler des Neurons, das den Eingangwert liefert, entsprechend groß sein, um einen Effekt auf den Fehler des betrachteten Ausgangsneurons zu haben. Ist hingegen die Wichtung groß, so kann bereits ein kleiner Fehler des Vorgängerneurons eine große Wirkung auf das Ausgangsneuron haben. Damit erscheint es sinnvoll, nicht für alle Neuronen den gleichen Fehler zu verwerten, sondern ihn von Ebene zu Ebene anzupassen, beginnend mit der Ausgangsebene.
Das bedeutet, dass der Lernprozess nicht von der Eingangsebene bis zur Ausgangsebene abgewickelt wird, sondern in umgekehrter Richtung. Außerdem sollte jedes Neuron "wissen", mit
welchen Neuronen seine Eingänge verbunden sind. Technisch ist es günstiger, die Neuronen in einer Liste abzulegen und zu jedem Neuron eine Liste anzufertigen, die seinen
Eingängen entspricht und die Neuronen enthält, die die Eingangswerte liefern.
Um Rekursionen nicht unnötig kompliziert zu machen, ist es günstig, die vorgegebenen Eingangswerte in "Dummy-Neuronen" zu kopieren, die lediglich die Vorgaben ausgeben, aber keine
Eingänge besitzen und auch keine Berechnungen ausführen. Die Unterscheidung "Dummy-Neuron" und "normales Neuron" geschieht über das Flag inf (true = Dummy-Neuron).
Dazu werden die Klassen neuron und einneur definiert, letztere zur Behandlung der einzelnen Neuroneneingänge. Der Lernprozess gliedert sich auf in entsprechende Methoden für diese Klassen.
|
neuron ne=null; double we=0,fk=1; eineur nxt=null; public double wert() { we=ne.wert(); return we*fk; }
void lernen(double d) { |
Zeiger auf Vorgängerneuron, das einen Wert an den Dendriten liefert Wert vom Vorgängerneuron, Wichtungsfaktor Zeiger awf Nachfolgedendriten Wert vom Vorgängerneuron Rückgabe des gewichteten Wertes Lernschritt bei Fehler d (Differenz berechneter und vorgegebener Ausgangswert) Lernaufruf für das Vorgängerneuron mit gewichtetem Fehler d*fk Anpassung der Wichtung um Lernrate 100 * Fehler * Wert |
|
eineur anfli=null; neuron nxt=null; double dfa=100,gr=0; double we=0; boolean inf=false; double wert() { if(inf) return we; else { double su=gr; for(eineur zwli=anfli;zwli!=null;zwli=zwli.nxt) su=su+zwli.wert(); su=Math.max(su,-1000); return 1/(Math.exp(-su)+1); } }
void lernen(double d) { |
Anfang der Liste der Eingangsneuronen / Dendritenliste Zeiger auf Nachfolger in der Neuronenliste Fehlerwert, Schaltschwelle Ausgangswert Flag Dummy-Neuron, normales Neuron wenn Dummy-Neuron, dann Rückgabe des gespeicherten Wertes we sonst (normales Neuron): Eingangssumme mit Schaltschwelle initialisiert Durchlaufen der Dendritenliste, dabei Summieren der Eingangswerte Berechung und Rückgabe des Ausgangswertes Anpassung der Eingangswichtungen Anpassung der Schaltschwelle mit Lernrate 100 |
Das vollständige Programm kann unter neudel.java geladen und für eigene Experimente genutzt werden: Nach dem Lernprozess kann man weitere Muster eingeben und testen, welche Ausgangsmuster dann erzeugt werden.
Hinweis: Damit die durch den Fehler d verursachten Änderungen nicht zu groß werden und dadurch die Stabilisierung der Wichtungen verhindern, kann man den Fehler d durch den Term d/(a + Math.abs(d)) ersetzen, wobei a eine positive Zahl ist, z.B. 1 .