kramann.info
© Guido Kramann

Login: Passwort:










8.3 Umsetzung eines Backpropagation Algorithmus

Als Beispiel für die Umsetzung des Backpropagation-Verfahrens zum Belernen eines Neuronalen Netzes wird ein Netz mit zwei Einangsneuronen, einer Zwischenschicht mit vier Neuronen und einer Ausgangsschicht mit einem Neuron verwendet.

Das Netz soll entsprechend den vorangegangenen Ausführungen die Antivalenz-Logik lernen. Es werden folgende Bezeichnungen verwendet:

  • in0, in1: Eingänge
  • w0..w15: Gewichte der Netzverbindungen
  • u0..u6: Summierte Eingangssignale
  • n10, n11: Ausgangs-Aktivierung der Neuronen der Eingangsschicht
  • n21, n22, n23, n24: Ausgangs-Aktivierung der Neuronen der Zwischenschicht
  • n30: Ausgangs-Aktivierung der Neuronen der Ausgangsschicht
Netz für das der Backpropagation Algorithmus umgesetzt werden soll.

Bild 8.3-1: Netz für das der Backpropagation Algorithmus umgesetzt werden soll.

  • Dass die Zwischenschicht nun vier Neuronen besitzt, sorgt mit dafür, dass die notwendige Information zur Realisierung des Antivalenz-Gatters im Netz repräsentiert werden kann.

Das Backpropagatioverfahren wird für das Netz direkt innerhalb einer Klasse umgesetzt werden, um die Komplexität der Darstellung möglichst gering zu halten. Ebensowenig werden Verknüpfungstabellen verwendet, um die Vernetzung darzustellen, sondern die Verknüpfung wird unmittelbar in der Ausgangsberechnung und beim Backpropagationschritt sichtbar. Der folgende Quellcode stellt also obiges Neuronale Netz dar und einen auf diesem Netz wirkenden Backpropagation-Algorithmus.

import java.util.Random;
public class Backpropagation
{
    public static double[] u = new double[7];
    public static double n10,n11,n20,n21,n22,n23,n30;
    public static double sigmoid(double u)
    {
        return 1.0 / ( 1.0 + Math.exp(-u) );
    }
    public static double ableitung_sigmoid(double u)
    {
        return Math.exp(-u) / ( ( 1.0 + Math.exp(-u) )*( 1.0 + Math.exp(-u) ) );
    }
    public static double netz(double in0, double in1,double[] w)
    {
        u[0] = in0*w[0]+in1*w[1];
        u[1] = in0*w[2]+in1*w[3];
        n10 = sigmoid(u[0]);
        n11 = sigmoid(u[1]);
        u[2] = n10*w[4]+n11*w[5];
        u[3] = n10*w[6]+n11*w[7];
        u[4] = n10*w[8]+n11*w[9];
        u[5] = n10*w[10]+n11*w[11];
        n20 = sigmoid(u[2]);
        n21 = sigmoid(u[3]);
        n22 = sigmoid(u[4]);
        n23 = sigmoid(u[5]);
        u[6] = n20*w[12]+n21*w[13]+n22*w[14]+n23*w[15];
        n30 = sigmoid(u[6]);
        return n30;
    }
    public static double berechneFehler(double in0, double in1, double sollout, double[] w)
    {
        double ausgang = netz(in0,in1,w);
        return (ausgang-sollout)*(ausgang-sollout);
    }
    public static double berechneFehler(double[] w)
    {
        double ausgang;      
        double fehler = 0.0;
        double fehler_aktuell = 0.0;
        fehler_aktuell = berechneFehler(0.0,0.0,0.0,w);
        ausgang = netz(0.0,0.0,w);
        System.out.println("in: 0 0 sollout: 0, istwert:"+ausgang);
        fehler += fehler_aktuell;
        fehler_aktuell = berechneFehler(0.0,1.0,1.0,w);
        ausgang = netz(0.0,1.0,w);
        System.out.println("in: 0 1 sollout: 1, istwert:"+ausgang);
        fehler += fehler_aktuell;
        fehler_aktuell = berechneFehler(1.0,0.0,1.0,w);
        ausgang = netz(1.0,0.0,w);
        System.out.println("in: 1 0 sollout: 1, istwert:"+ausgang);
        fehler += fehler_aktuell;
        fehler_aktuell = berechneFehler(1.0,1.0,0.0,w);
        ausgang = netz(1.0,1.0,w);
        System.out.println("in: 1 1 sollout: 0, istwert:"+ausgang);
        fehler += fehler_aktuell;
        return fehler;
    }
    public static void backpropagation(double in0, double in1, double sollout,
                                       double lernfaktor, double[] w)
    {
//Übung: Korrektheit der Implementierung prüfen:
        double[] Q = new double[w.length];
        double istout = netz(in0,in1,w); //hier wird auch u[0..6] bstimmt!
        //Zuleitungen zu Ausgangsschicht:
        Q[12] = ableitung_sigmoid(u[6])*(sollout - istout);
        w[12] += lernfaktor*Math.abs(n20)*Q[12];
        Q[13] = ableitung_sigmoid(u[6])*(sollout - istout);
        w[13] += lernfaktor*Math.abs(n21)*Q[13];
        Q[14] = ableitung_sigmoid(u[6])*(sollout - istout);
        w[14] += lernfaktor*Math.abs(n22)*Q[14];
        Q[15] = ableitung_sigmoid(u[6])*(sollout - istout);
        w[15] += lernfaktor*Math.abs(n23)*Q[15];
        //Zuleitung zu Zwischenschicht:
        Q[4]  = ableitung_sigmoid(u[2])*(w[12]*Q[12]);
        w[4] += lernfaktor*Math.abs(n10)*Q[4]; //nur die hinführenden, nicht alle
        Q[5]  = ableitung_sigmoid(u[2])*(w[12]*Q[12]);
        w[5] += lernfaktor*Math.abs(n11)*Q[5];
        Q[6]  = ableitung_sigmoid(u[3])*(w[13]*Q[13]);
        w[6] += lernfaktor*Math.abs(n10)*Q[6]; //nur die hinführenden, nicht alle
        Q[7]  = ableitung_sigmoid(u[3])*(w[13]*Q[13]);
        w[7] += lernfaktor*Math.abs(n11)*Q[7];
        Q[8]  = ableitung_sigmoid(u[4])*(w[14]*Q[14]);
        w[8] += lernfaktor*Math.abs(n10)*Q[8]; //nur die hinführenden, nicht alle
        Q[9]  = ableitung_sigmoid(u[4])*(w[14]*Q[14]);
        w[9] += lernfaktor*Math.abs(n11)*Q[9];
        Q[10]  = ableitung_sigmoid(u[5])*(w[15]*Q[15]);
        w[10] += lernfaktor*Math.abs(n10)*Q[10]; //nur die hinführenden, nicht alle
        Q[11]  = ableitung_sigmoid(u[5])*(w[15]*Q[15]);
        w[11] += lernfaktor*Math.abs(n11)*Q[11];
        //Zuleitung zu Eingangsschicht:
        Q[0]  = ableitung_sigmoid(u[0])*(w[4]*Q[4]+w[6]*Q[6]+w[8]*Q[8]+w[10]*Q[10]);
        w[0] += lernfaktor*Math.abs(in0)*Q[0]; //nur die hinführenden, nicht alle
        Q[1]  = ableitung_sigmoid(u[1])*(w[5]*Q[5]+w[7]*Q[7]+w[9]*Q[9]+w[11]*Q[11]);
        w[1] += lernfaktor*Math.abs(in1)*Q[1]; //nur die hinführenden, nicht alle
        Q[2]  = ableitung_sigmoid(u[0])*(w[4]*Q[4]+w[6]*Q[6]+w[8]*Q[8]+w[10]*Q[10]);
        w[2] += lernfaktor*Math.abs(in0)*Q[2]; //nur die hinführenden, nicht alle
        Q[3]  = ableitung_sigmoid(u[1])*(w[5]*Q[5]+w[7]*Q[7]+w[9]*Q[9]+w[11]*Q[11]);
        w[3] += lernfaktor*Math.abs(in1)*Q[3]; //nur die hinführenden, nicht alle
    }
    public static void main(String[] args)
    {
        double[] w = new double[16];
        Random random = new Random(0);
        for(int i=0;i<w.length;i++)
            w[i] = 2.0*random.nextDouble()-1.0;  //[-1,+1]
        for(int i=0;i<2000;i++)
        {
            backpropagation(0.0,0.0,0.0,1.0,w);
            backpropagation(0.0,1.0,1.0,1.0,w);
            backpropagation(1.0,0.0,1.0,1.0,w);
            backpropagation(1.0,1.0,0.0,1.0,w);
            if(i%100==0)
                System.out.println("Fehler = "+berechneFehler(w));
        }
    }
}

Code 8.3-1: Neuronales Netz zur Realisierung eines NAND-Gatters mit Backpropagation Algorithmus.

Übung
  1. Testen Sie obiges Programm.
  2. Vollziehen Sie die Implementierung des Backpropagation-Algorithmus nach. Bilden Sie u.a. selber zur Kontrolle die Ableitung der verwendeten sigmoiden Schwellwert-Funktion.
  3. Variieren Sie die Lernrate: Ab welcher Lernrate konvergiert die Lösung nicht mehr?
  4. Versuchen Sie die Anzahl der Backpropagationschritte dadurch zu verringern, dass Sie eine automatische Anpassung der Lernrate implementieren. Wie kann diese aussehen? Wettbewerb: möglichst wenig Iterationsschritte verbrauchen, um einen maximalen Fehler beim Ausgang von einem Prozent zu erzielen.
  5. Entwerfen Sie möglichst konkret ein Neuronales Netz als Grundlage für ein Fahrzeug mit Linienverfolgung mittels einer USB-Kamera. Bedenken Sie, dass viele Netz-Verknüpfungen auch einen hohen Rechenaufwand bei der Auswertung und beim Belernen bedeutet.