kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Pretraining mit SDA

(EN google-translate)

(PL google-translate)

  • Für das Pre-Training bei tiefen Netzen wurden bisher zwei Verfahren entwickelt:
  • deep belief nets (DBN)
  • Stacked Denoising Autoencoders (SDA)
  • Bei beiden Verfahren wird Verbindungsschicht für Verbindungsschicht so belernt, dass ein Pattern am Eingang erst auf den Ausgang und dann wieder zurück propagiert wird und gefordert wird, dass das Resultat wieder dem Eingangspattern entspricht.
  • Dem SDA-Verfahren liegt eine wesentlich einfachere Theorie zugrunde und es verwendet einen wesentlich einfacheren Lernalgorithmus.
  • Deshalb wird im folgenden das SDA-Verfahren implementiert und getestet.
  • Alle Deep-Learning-Verfahren erlauben das Belernen zusätzlicher Bias-Werte. Das sind Konstanten, die der gewichteten Eingangssumme hinzuaddiert werden, bevor das Resultat der sigmoiden Funktion (oder einer anderen) übergeben wird.
  • Während also sonst gilt: o = sigmoid(u), mit u = summe(wi*xi), gilt mit Bias b: u = summe(wi*xi+b).
  • Für das Belernen von Biaswerten wird eine weitere Extra-Lernfunktion benötigt.
  • Die Verwendung von Biaswerten bedeutet aber neben einer höheren Kompliziertheit des Lernverfahrens auch eine Erhöhung der zu lernenden/festzulegenden Freiheitsgrade.
  • Darum wird im Rahmen dieser LV auf deren Einführung verzichtet, zumindest solange sich keine Probleme stellen, die nicht ohne gelöst werden können.

SDA -- Stacked Denoising Autoencoders

Beim SDA-Verfahren wird das Eingangspattern mit einem zusätzlichen Rauschen überlagert. Dieses Rauschen soll durch das hin- und zurückgespiegelte Propagieren durch das Netz entfernt werden.

Dies bedeutet aber, wenn das Verfahren gelingt, dass nur die besonderen Merkmale der Pattern durch das Netz durchgelassen werden und unnütze Signale unterdrückt werden.

Wieder anders gesagt: Die einzelnen Verbindungsschichten werden so belernt, dass sie die typischen an ihnen anliegenden Pattern sogar bis zu einem gewissen Grad rekonstruieren können, wenn diese fehlerhaft / verrauscht dargeboten werden.

Die Eingangspattern sollten aus den üblichen zu lernenden gelabelten Pattern resultieren. D.h. an der Eingangsschicht liegt das an, was auch sonst dargeboten wird, also in unserem Fall ein TicTacToe-Spielstand. Zuerst wird das Pre-Training auf die Gewichte zwischen Eingangs- und erster versteckten Neuronen-Schicht durchgeführt. Danach wird es auf die Gewichte der ersten und der zweiten verdeckten Schicht angewendet usw. Weil immer die vorhergehende Schicht bereits vortrainiert wurde, müssen nicht Extra-Eingangspattern für das Trainieren der Zwischenschichten konstruiert werden. Es reicht immer die üblichen Pattern durchzupropagieren. Lediglich das Rauschen muß immer bei der gerade aktuellen Eingangsseite hinzugefügt werden.

Und so sieht die Gradienten-basierte Veränderung der Gewichte beim SDA-Verfahren für einen einzelnen Lernschritt aus:

Indices der Neuronen der aktuell betrachteten Eingangsschicht: i
Anzahl der Eingangsneuronen: p
Indices der Neuronen der aktuell betrachteten Ausgangsschicht: k
Anzahl der Ausgangsneuronen: q
Gewicht zwischen einem Eingangsneuron und einem Ausgangsneuron: wik

Das eingespeiste Pattern ohne Rauschen wird:                                 xx
Die Werte am Ausgang der Neuronen der Eingangsschicht werden bezeichnet mit: nn 
...und entsprechen der verrauschten Version von xx.
Der Ausgang nach dem Vorwärtspropagieren wird bezeichnet mit:                zz
Das Pattern, das nach dem zurückpropagieren wieder beim Eingang vorliegt:    yy

Anzahl der Werte / Zugehörigkeit:
xx, nn, yy gehören zum Eingang und haben p Werte mit Index i=0..p-1
zz gehört zum Ausgang und hat q Werte mit Index k=0..q-1

Dann gilt für ein beliebiges Gewicht der gerade betrachteten Verbindungsschicht:


Code 0-1: Bezeichnungen beim SDA-Verfahren

$ wneu_{ik} = walt_{ik} + lernfaktor \cdot \left(Q \cdot zz_k \cdot \left(1-zz_k\right) \cdot nn_i+\left(xx_i-yy_i\right) \cdot zz_k\right) $

Formel 0-1: Lernregel beim SDA-Verfahren.


$ Q= \sum _{i=0}^{p-1} walt_{ik} \cdot \left(xx_i-yy_i\right) $

Formel 0-2: Bedeutung von Q.


            for(int k=0;k<ausgangs_layer.holeAnzahlNeuronen();k++)
            {
                  double Q = 0.0;
                  for(int i=0;i<eingangs_layer.holeAnzahlNeuronen();i++)
                  {
                       //holeGewichtAlt(int index_eingang, int index_ausgang)
                       Q +=  holeGewichtAlt(i,k)*(xx[i]-yy[i]);
                  }
                  for(int i=0;i<eingangs_layer.holeAnzahlNeuronen();i++)
                  {
                       double wneu = holeGewichtAlt(i,k) + lernfaktor*(Q*zz[k]*(1.0-zz[k])*nn[i]+(xx[i]-yy[i])*zz[k]);
                       //setzeGewicht(int index_eingang, int index_ausgang, double wert)
                       setzeGewicht(i, k,wneu);
                  }
            }

Code 0-2: In den nachfolgenden Projekten, findet sich obige Implementierung für die SDA-Lernregel: siehe Zeilen 190-204 in der Klasse "Gewichte".

      public void macheBackpropagation(double[] soll_ausgang, double lernfaktor)
      {
           //1. Gewichte unmittelbar vor Ausgangsschicht belernen:
           //   w_neu = w_alt + lernfaktor*x*Q
           //   Q = f'(u)*(out_soll - out_ist)
           //   u: gewichtete Summe == Eingang der Ausgangsschicht
           //   f'(u): Ableitung der sigmoiden Funktion der aktuellen Ausgangsneuronen 
           //   x: Aktivierung des Neurons, von dem das aktuelle Gewicht ausgeht.
           for(int i=0;i<layer[layer.length-2].holeAnzahlNeuronen();i++) //vorhergehende Schicht
           {
               double x = layer[layer.length-2].holeAusgang(i); //Aktivierung des Neurons, wo die aktuelle Leitung startet
               for(int k=0;k<layer[layer.length-1].holeAnzahlNeuronen();k++)
               {
                   double w_aktuell = w[w.length-1].holeGewicht(i, k);
                   double Q = layer[layer.length-1].holeAusgangAbleitung(k)*(soll_ausgang[k] - layer[layer.length-1].holeAusgang(k));
                   double w_neu = w_aktuell + lernfaktor*x*Q;
                   w[w.length-1].setzeGewicht(i,k,w_neu);
                   //Immer das korrespondierende Q zu jedem Gewicht mit merken, da es für die nachfolgenden Backpropagierungsschritte benötigt wird!
                   w[w.length-1].merkeKorrespondierendesQ(i,k,Q);
               }    
           }    
           
           //2. Gewichte für alle vorangehenden Zwischenschichten belernen 
           //   w_neu = w_alt + lernfaktor*x*Q
           //   Q = f'(u)*Summe(vorangehendesQk*wneuk) //Summe der Produkte aller neuen Qs und neuen ws aller vom Zielneuron abgehenden Leitungen 
           //   u: gewichtete Summe == Eingang der Ausgangsschicht
           //   f'(u): Ableitung der sigmoiden Funktion der aktuellen Ausgangsneuronen 
           //   x: Aktivierung des Neurons, von dem das aktuelle Gewicht ausgeht.
           for(int schicht = layer.length-3;schicht>=0;schicht--) //Hier z.B.: 4 Layer, 3 ws:  schicht=1 0
           {
               for(int i=0;i<layer[schicht].holeAnzahlNeuronen();i++) //vorhergehende Schicht
               {
                   double x = layer[schicht].holeAusgang(i); //Aktivierung des Neurons, wo die aktuelle Leitung startet
                   for(int k=0;k<layer[schicht+1].holeAnzahlNeuronen();k++)
                   {
                       double w_aktuell = w[schicht].holeGewicht(i, k);
                       double summeQw=0.0;
                       for(int p=0;p<layer[schicht+2].holeAnzahlNeuronen();p++)
                            summeQw+=w[schicht+1].holeGewicht(k,p)*w[schicht+1].holeQ(k,p);
                       double Q = layer[schicht+1].holeAusgangAbleitung(k)*summeQw;
                       double w_neu = w_aktuell + lernfaktor*x*Q;
                       w[schicht].setzeGewicht(i,k,w_neu);
                       //Immer das korrespondierende Q zu jedem Gewicht mit merken, da es für die nachfolgenden Backpropagierungsschritte benötigt wird!
                       w[schicht].merkeKorrespondierendesQ(i,k,Q);
                   }    
               }                     
           }
      }

Code 0-3: Der Vollständigkeit halber sei auch die implementierte Backpropagation-Regel für das Fine-Tuning mit angegeben: siehe Zeilen 84-131 in der Klasse "Netz".

Ergänzung aus dem Unterricht:

  • Backpropagation von Hand für ein kleines Netz
Backtracking für Ausgangs-Verbindungsschicht.

Bild 0-1: Backtracking für Ausgangs-Verbindungsschicht.

Backtracking für Zwischen-Verbindungsschicht.

Bild 0-2: Backtracking für Zwischen-Verbindungsschicht.

  • SDA von Hand für ein kleines Netz
Vorwärts-Rückwärts-Propagierung (Encoding / Decoding)

Bild 0-3: Vorwärts-Rückwärts-Propagierung (Encoding / Decoding)

Durchführen des SDA-Lernschritts mit den Ergebnissen aus der Vorwärts-Rückwärts-Propagierung.

Bild 0-4: Durchführen des SDA-Lernschritts mit den Ergebnissen aus der Vorwärts-Rückwärts-Propagierung.

TicTacToe und Deep Learning

  • Die folgenden drei Projekte sind nahezu identisch:
  • Es wird das Projekt TicTacToe32_Neuro_strukturiert, bei dem ein Netz lernt, wo es setzen darf durch ein SDA-Pre-Training ergänzt.
  • Ein einzelner Schritt dafür ist in der Klasse "Gewichte" durch die Methode
    public double endecoding(double lernfaktor, double noise)
    implementiert.
  • Im setup wird mit dem Aufruf for(int i=0;i
  • Der Backpropagation blieb in allen Fällen unverändert und ist nun das Fine-Tuning, das nach dem Pre-Training durchgeführt wird.
  • TicTacToe34_Neuro_DeepLearning führt für das auch zuvor benutzte 9-18-18-9-Netz das Pretraining durch und anschließend normales Backpropagation als Fine-Tuning
  • TicTacToe35_Neuro_DeepLearning_DEEEEEP: Hier wird ein 9-9-9-9-9-9-9-Netz verwendet und einmal mit und einmal ohne Pre-Training belernt.
  • TicTacToe36_Neuro_mehr_Pretraining schließlich führt längeres Pretraining durch und verwendet danach einen 10mal größeren Lernfaktor beim Fine-Tuning, als bei den vorangehenden Beispielen.
TicTacToe34_Neuro_DeepLearning.zip
TicTacToe35_Neuro_DeepLearning_DEEEEEP.zip
TicTacToe36_Neuro_mehr_Pretraining.zip

Ergebnisse:


****************************************
**** TicTacToe34_Neuro_DeepLearning ****
****************************************

MIT Pre-Training:
SDA Fehler zu Beginn: 2.763058507320894  ... am Ende: 0.028231118919433668
SDA Fehler zu Beginn: 1.1856504677026982  ... am Ende: 0.03130818018416652
SDA Fehler zu Beginn: 0.6321134781296878  ... am Ende: 0.039251778755574965
Start Fine-Tuning
fehler = 0.03337222600483036
Echte Fehler: von 900 sind 23.0 falsch.
fehler = 0.027289134062207464
Echte Fehler: von 900 sind 6.0 falsch.
NULL FEHLER ERSTES MAL NACH 219540 SCHRIITEN!

OHNE Pre-Training:
Start Fine-Tuning
fehler = 0.04550619929493047
Echte Fehler: von 900 sind 109.0 falsch.
fehler = 0.03433224544873166
Echte Fehler: von 900 sind 47.0 falsch.
fehler = 0.0298075423863876
Echte Fehler: von 900 sind 17.0 falsch.
NULL FEHLER ERSTES MAL NACH 398014 SCHRIITEN!

************************************************
**** TicTacToe35_Neuro_DeepLearning_DEEEEEP ****
************************************************

9,9,9,9,9,9,9-Netz
MIT Pre-Training:
NULL FEHLER ERSTES MAL NACH 7073877 SCHRIITEN!

9,9,9,9,9,9,9-Netz
OHNE Pre-Training:
NULL FEHLER ERSTES MAL NACH 24941950 SCHRIITEN!



********************************************
**** TicTacToe36_Neuro_mehr_Pretraining ****
********************************************
9,9,9,9,9,9,9-Netz
MIT Pre-Training:
SDA Fehler zu Beginn: 2.517800122000871  ... am Ende: 0.08212722329337914
SDA Fehler zu Beginn: 1.079696242195671  ... am Ende: 0.0642301257378609
SDA Fehler zu Beginn: 0.5633722552154168  ... am Ende: 0.14978189605178832
SDA Fehler zu Beginn: 0.5510060587360196  ... am Ende: 0.04319369263624905
SDA Fehler zu Beginn: 0.5515231637120703  ... am Ende: 0.013878004882203152
SDA Fehler zu Beginn: 0.6505741359085077  ... am Ende: 0.01592400723910906
Start Fine-Tuning
fehler = 0.03799268723454107
Echte Fehler: von 900 sind 68.0 falsch.
fehler = 0.029757944145807045
Echte Fehler: von 900 sind 33.0 falsch.
fehler = 0.027656180706206002
Echte Fehler: von 900 sind 38.0 falsch.
fehler = 0.027996441778316287
Echte Fehler: von 900 sind 26.0 falsch.
lernfaktor = 0.1000200048090007
fehler = 0.027394148139482263
Echte Fehler: von 900 sind 31.0 falsch.
fehler = 0.025325394232452517
Echte Fehler: von 900 sind 20.0 falsch.
fehler = 0.02319232489533295
Echte Fehler: von 900 sind 12.0 falsch.
NULL FEHLER ERSTES MAL NACH 748016 SCHRIITEN!


Code 0-4: Ergebnisse, die mit den obigen Beispielprojekten mit und ohne Pretraining gewonnen wurden.

Fazit

  • Somit wurde das SDA-Verfahren erfolgreich implementiert.
  • Für das gleiche tiefe Netz 5 hidden-Layers konnte gezeigt werden, dass es ohne Pre-Training 25Millionen Lernschritte benötigt, um das gleiche Ergebnis zu erzielen, wie das vortrainierte in 700000 (mit erhöhtem Lernfaktor).
  • Das Netz mit Pretraining ermöglicht hier also ca. 35mal schnelleres Lernen, bzw. ermöglicht überhaupt das Belernen tiefer Netze.

Erstellen eines neuronalen Tic-Tac-Toe-Spielers

  1. Trainer mit SDA-Verfahren und Exportfunktion für fertig belernte Netze
  2. Testlauf mit optimalem Gegner
1.: TicTacToe37_Neuro_TTTspieler.zip
2.: TicTacToe08b_Turnier_P_korrigiert_NEURO.zip

Hinweise:

  • Die Export-Funktion speichert die Gewichte als Textfile, das wie ein zweidiemnsionales double-Java-Array formatiert ist: double[][] ww = { ...
  • Der Restfehler des verwendeten Neuro-Spielers war 0.01421, erscheint mit Faktor 100000 im Filenamen als: w_1421.txt
  • Der Neurospieler (X) spielt die meisten Partien unentschieden gegen den optimalen Spieler (O). Er ist also gut, reicht aber nicht ganz an den optimalen Spieler hern:
Verlauf eines Turniers des Neurospielers (X, rot) gegen den optimalen Spieler (O, grün).

Bild 0-5: Verlauf eines Turniers des Neurospielers (X, rot) gegen den optimalen Spieler (O, grün).

Offene Fragen / TODOs

  1. Trennung der "TicTacToe-Teile" von den allgemeinen Teilen, um ein leicht zu benutzendes Framework für Deep-Learning zur Verfügung zu stellen.
  2. Effizientere Berechnung von Q beim Backpropagation-Algorithmus (Geschachtelte Schleifen tauschen...)
  3. Beschleunigung / Ermöglichung noch größerer Netze mittels RELU
  4. Verwendung von short-Werten statt double, um den Weg zu ebnen, die Netze auf FPGAs zu übertragen.
  5. Anwendung der bisherigen Verfahren auf Bildverarbeitung / Bilderkennung
  6. Ermöglichung auch von Reinforced Learning / Markov-Ketten
  7. Darstellung der Herleitung wenigstens eines Gradient basierten Lernverfahrens
  8. Klären wichtiger Begriffe wie Overfitting und Underfitting.