Feed-Forward und Back-Propagation

Neuronale Netze – Teil 2
Kommentare

In der letzten Ausgabe und dem ersten Teil dieser Artikelserie haben wir uns mit dem einfachen Perzeptron-Modell beschäftigt und damit eine einzelne künstliche Nervenzelle simuliert. Für praktische Anwendungen ist dieses Modell jedoch nur begrenzt tauglich, da künstliche Nervenzellen, gleich ihren natürlichen Vorbildern im Gehirn, nur durch das Zusammenspiel in einem großen Netzwerk ihre tatsächliche Stärke entfalten können. Wir führen daher unser Modell einen Schritt weiter und wenden uns mehrschichtigen neuronalen Netzen zu.

Mehrschichtige neuronale Netze gliedern sich, wie der Name vermuten lässt, in verschiedene Schichten (Layers), die wiederum aus einem oder mehreren künstlichen Neuronen bestehen. Diese Schichten lassen sich im Regelfall in folgende Kategorien unterteilen:

  • Die Eingabeschicht (Input Layer) repräsentiert die Eingabedaten in das neuronale Netz, also die eigentlichen Daten, auf die eine Klassifikation angewendet wird. Bei einem Bilderkennungsproblem könnte beispielsweise jedes Neuron in der Eingabeschicht einem Pixel des Bildes entsprechen. Neuronen der Eingabeschicht sind mit denen der darauffolgenden versteckten Schicht verbunden.
  • Versteckte Schicht (Hidden Layer): Zwischen den Neuronen der Eingabeschicht und denen der Ausgabeschicht befinden sich eine oder auch mehrere versteckte Schichten. Gibt es mehr als eine versteckte Schicht, spricht man von „tiefen neuronalen Netzen“ (Deep Neural Networks). Erst dieses Konzept der zusätzlichen Komplexität, die durch die Entkopplung von Ein- und Ausgabedaten stattfindet, macht neuronale Netze zu dem, was sie sind, und verleiht ihnen die Fähigkeit zu „Lernen“ und sich an neue Aufgabenstellungen anzupassen.
  • Ausgabeschicht (Output Layer): Die Ausgabeschicht ist mit den Neuronen der versteckten Schicht verbunden und repräsentiert das Ergebnis eines Klassifikationsproblems.

Abbildung 1 zeigt schematisch ein mehrschichtiges neuronales Netz, das aus einer Eingabeschicht (Knoten X1X5), einer versteckten Schicht und einer Ausgabeschicht (Knoten Z1 und Z2) besteht. Trotz seiner Einfachheit weist dieses neuronale Netz zwei wichtige Charakteristika auf, die wir für unsere weiteren Betrachtungen als Grundlagen annehmen:

  • Neuronen einer Schicht sind nur mit denen der darauffolgenden Schicht verbunden. Es kann somit keine Schicht „übersprungen“ werden, und ebenso sind Verbindungen innerhalb einer Schicht oder mit zurückliegenden Schichten nicht erlaubt. Sind diese Voraussetzungen erfüllt, spricht man von einem „Feed-Forward“-System, da Informationen nur in eine Richtung „fließen“ können.
  • Gleichzeitig handelt es sich um ein „vollständig verbundenes“ (fully connected) neuronales Netz, da immer sämtliche Neuronen einer Schicht mit allen der darauffolgenden verbunden sind. Jedes Neuron der verstecken Schicht ist also mit allen Neuronen der Eingabeschicht verbunden und ebenso ist jedes Neuron der Ausgabeschicht mit allen der (letzten) versteckten Schicht verbunden.
Abb. 1: Ein dreischichtiges neuronales Netz

Abb. 1: Ein dreischichtiges neuronales Netz

 

Feed-Forward: Von der Eingabe zur Ausgabe

Nachdem wir nun mit der grundsätzlichen Funktionsweise mehrschichtiger neuronaler Netze vertraut sind, sehen wir uns an, wie der eigentliche Datenfluss von der Eingabe zur Ausgabe stattfindet. Dazu rufen wir uns das Modell des einfachen Perzeptrons bzw. eines einzelnen künstlichen Neurons ins Gedächtnis (Abb. 2), das folgende wesentliche Charakteristika aufweist:

  • Die Eingabewerte (X1, X2, … Xn) sind mit Gewichtungen (w1, w2, … wn) versehen. Diese Gewichtungen werden beim „Trainieren“ des neuronalen Netzes angepasst und sind somit unmittelbar für den „Lernvorgang“ verantwortlich.
  • Eine Sonderstellung bei den Eingaben kommt dem so genannten „Bias-Wert“ zu, der als zusätzliche Eingabe mit konstantem Wert interpretiert werden kann. Mithilfe dieses Werts kann ein einzelnes Neuron unabhängig von der tatsächlichen Eingabe das Ergebnis seiner Berechnung um einen konstanten Wert erhöhen oder erniedrigen. Ob dieser Bias-Wert explizit modelliert oder als Gewichtung eines zusätzlichen konstanten Eingabewerts modelliert wird, ist in erster Linie Geschmackssache und in der Literatur oft von Fall zu Fall unterschiedlich.
  • Zusätzlich verfügt ein künstliches Neuron über eine Aktivierungsfunktion. Dabei handelt es sich um eine mathematische Funktion, die auf das Zwischenergebnis (die Summe der Eingaben multipliziert mit deren Gewichtungen) angewendet wird. Damit lässt sich, angelehnt an das natürliche Vorbild einer Nervenzelle, ein Aktivierungspotenzial simulieren. Zusätzlich werden die Ausgabewerte der Neuronen einer Schicht normalisiert. Abhängig von der Schicht, in der sich ein Neuron befindet, verwendet man unterschiedliche Aktivierungsfunktionen. Für Neuronen der versteckten Schicht wird beispielsweise oft der hyperbolische Tangens (Abb. 3) herangezogen. In der Ausgabeschicht hingegen wird gerne auf die Sigmoid-Funktion (Abb. 4) als Aktivierungsfunktion zurückgegriffen. Zusätzlich zu den beiden genannten Aktivierungsfunktionen findet man in der Literatur noch weitere. Allesamt weisen diese Aktivierungsfunktionen aber große mathematische Ähnlichkeiten auf, und die Auswahl hängt letzten Endes oft von persönlichen Vorlieben und lokalen Lehrmeinungen ab.

Bevor wir nun zum Back-Propagation-Algorithmus und somit zum eigentlich Trainings- und Lernvorgang eines neuronalen Netzes übergehen, sehen wir uns den Feed-Forward-Mechanismus noch einmal anhand eines Beispiels mit konkreten Zahlen an. Dazu betrachten wir das erste Neuron in der versteckten Schicht aus Abbildung 5.

Als Zwischenergebnis der gewichteten Eingabewerte ergibt sich: H1‘‘ =  I1 * w1 + I2 * w2 + I3 * w3.

Addieren wir noch den Bias-Wert des Knotens hinzu, erhalten wir H1‘ = H1‘‘ + b1.

Nach Anwendung des hyperbolischen Tangens bekommen wir den endgültigen Wert für das Neuron aus der versteckten Schicht: H1 = tanh(H1‘).

Nahezu dasselbe Schema lässt sich auch auf Neuronen der Ausgabeschicht anwenden. Allein anstelle der Eingabewerte (I1 … In) werden hier die Zwischenergebnisse der versteckten Schicht (H1 … Hn) verwendet und der hyperbolische Tangens wird durch die Sigmoid-Funktion ersetzt. Für den Wert des Ausgabeknotens O1 aus Abbildung 5 ergibt sich somit folgender Ausdruck: O1 = sigm(H1 * wa + H2 * wb + ba).

Eine C#-Implementierung des Feed-Forward-Algorithmus in Form der Klasse Network findet man hier; zudem kann diese Implementierung auch als NuGet-Paket von hier bezogen werden. Die Verwendung veranschaulicht der Unit Test in Listing 1.

Abb. 2: Modell eines künstlichen Neurons

Abb. 2: Modell eines künstlichen Neurons

 

Abb. 3: Aktivierungsfunktion: „Hyperbolischer Tangens“ (Quelle: Wikipedia)

Abb. 3: Aktivierungsfunktion: „Hyperbolischer Tangens“ (Quelle: Wikipedia)

 

Abb. 4: Aktivierungsfunktion: „Sigmoidfunktion“ (Quelle: Wikipedia)

Abb. 4: Aktivierungsfunktion: „Sigmoidfunktion“ (Quelle: Wikipedia)

 

Abb. 5: Ein Feed-Forward-Netz

Abb. 5: Ein Feed-Forward-Netz

 

[TestMethod]
public void Test_SimpleNetwork()
{
  var network = new Network(3, 2, 1);
  // Input nodes.
  network.InputNodes[0].Value = 0.1;
  network.InputNodes[1].Value = 0.2;
  network.InputNodes[2].Value = 0.3;
  // Configure weights and bias for hidden node.
  network.HiddenNodes[0].Bias = 0.2;
  network.HiddenNodes[0].Incoming[0].Weight = 0.4;
  network.HiddenNodes[0].Incoming[1].Weight = 0.5;
  network.HiddenNodes[0].Incoming[2].Weight = 0.6;
  network.HiddenNodes[1].Bias = 0.8;
  network.HiddenNodes[1].Incoming[0].Weight = 0.7;
  network.HiddenNodes[1].Incoming[1].Weight = 0.6;
  network.HiddenNodes[1].Incoming[2].Weight = 0.5;
  // Confgure weights and bias for output node.
  network.OutputNodes[0].Bias = 0.5;
  network.OutputNodes[0].Incoming[0].Weight = 0.3;
  network.OutputNodes[0].Incoming[1].Weight = 0.4;

  Assert.AreEqual(0.72, network.OutputNodes[0].Value, 0.01);
}

Back-Propagation: Aus Fehlern wird man klug

Leider hat man es in realen Anwendungen nicht so einfach wie in Listing 1, in dem das neuronale Netz gleich mit konkreten Zahlen für Gewichtungen und Bias-Werten initialisiert wurde. Vielmehr steht man in der Praxis immer vor dem Problem, dass man genau diese Werte nicht kennt und sie durch „Training“ des neuronalen Netzwerks erst ermitteln muss. Die bekannteste Vorgehensweise zur Ermittlung dieser Werte ist das Back-Propagation-Verfahren, das bereits 1974 von Paul Werbos entwickelt wurde.

Die Idee, die hinter dem Back-Propagation Verfahren steckt, ist prinzipiell einfach und leicht erklärt: Wie alle Trainingsverfahren setzt es voraus, dass man über eine Menge von Eingabedatensätzen verfügt, für die das erwartete Ergebnis bereits bekannt ist. Anschließend „füttert“ man das neuronale Netz mit diesen Datensätzen und korrigiert anhand mathematischer Verfahren so lange dessen Gewichtungen, bis die Abweichung der tatsächlichen und erwarteten Ergebnisse unter eine definierte Fehlerschranke gefallen ist. Ab diesem Zeitpunkt betrachtet man das Training als abgeschlossen und kann das neuronale Netz nun auch auf solche Eingabedaten anwenden, für die die erwarteten Ausgaben nicht bekannt sind. Soweit also ganz einfach, die Details liegen lediglich in den erwähnten mathematischen Verfahren. Genauer gesagt kommt hier ein Gradientenverfahren zur Lösung eines Optimierungsproblems zum Einsatz.

In Pseudocode ausgedrückt funktioniert das Back-Propagation-Verfahren somit folgendermaßen:

REPEAT
  Ausgabe aus Eingabe berechnen
  Gradienten der Ausgabe Neuronen ermitteln
  Gradienten der versteckten Neuronen ermitteln
  Gewichtungen und Bias Werte anpassen
UNTIL Optimum erreicht

Der gesamte Quellcode zur Ermittlung der Gradienten sowie zur Lösung dieses Optimierungsproblems findet sich in den Klassen BackPropagation und BackPropagationTrainer, die ebenfalls hier zu finden sind. Listing 2 demonstriert, wie diese Klassen verwendet werden können:

  • Zuerst wird eine Instanz eines neuronalen Netzes (Klasse Network) angelegt.
  • Die Klasse BackPropagationTrainer erhält im Konstruktor eine Referenz auf die Instanz von Network übergeben und kapselt die Durchführung des Back-Propagation-Verfahrens inklusive dem Anpassen von Gewichtungen und Bias-Werten vollständig.
  • Der Benutzer muss lediglich eine Reihe von BackPropagationTrainingRow-Objekten an die Methode Train der BackPropagationTrainer-Instanz übergeben. Die Objekte repräsentieren Daten der Eingabeschicht samt deren erwünschter Ausgabe.
  • Die weiteren Parameter der Methode Train bestimmen die Lernrate (wie aggressiv werden Gewichtungen und Bias-Werte im Fehlerfall angepasst) sowie die Anzahl der Durchläufe.
var network = new Network(4, 3, 2);
var trainer = new BackPropagationTrainer(network);
var rows = new[]
{
  ...
  new BackPropagationTrainingRow (new [] { 0.2, 0.3, 0.4, 0.5 }, new [] { 0.0, 1.0 }),
  ...
};
trainer.Train(rows, 0.01, 500);

Fazit

Auch wenn neuronale Netzwerke samt ihren Lernverfahren wie Back-Propagation durch verschiedenste Softwarebibliotheken abgedeckt sind, ist es dennoch sinnvoll, deren Funktionsweise prinzipiell zu verstehen. Nichts fördert dieses Verstehen jedoch mehr, als selbst an eine Implementierung eines solchen neuronalen Netzes heranzugehen. Hat man die prinzipielle Funktionsweise von Feed-Forward- und Back-Propagation-Verfahren einmal verstanden, liefern diese ein mächtiges Werkzeug, mit dem sich praktische Probleme des Entwickleralltags einmal mit einer völlig anderen Herangehensweise lösen lassen können. Im dritten und abschließenden Teil dieser Artikelserie werden wir uns dann auch einem konkreten Problem unter Verwendung dieser neu erlernten Techniken widmen und eine App entwickeln, die sich neuronaler Netze bedient.

Artikelserie

Aufmacherbild: Neurons abstract background von Shutterstock/Urheberrecht: Leigh Prather

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -