Deep Learning: Essentials
Die Welt statistischer Lernmodelle gehört den Deep-Learning-Verfahren. Das ist kein Wunder, schließlich übertreffen sie die Leistung einfacher Machine-Learning-Algorithmen in fast allen Bereichen, ganz besonders aber bei der Analyse von Bild- und Textdaten.
Die Vorteile von Deep-Learning-Verfahren gehen dabei insbesondere auf zwei Faktoren zurück: Erstens lassen sich neuronale Netze weitaus besser auf spezifische Lernaufgaben hin zuschneiden. Von Textdaten wissen wir zum Beispiel, dass man aufeinander folgende Wörter nicht nur isoliert voneinander betrachten sollte. Ein Satz entfaltet seine Bedeutung erst durch das zeitliche Ausrollen seiner Bestandteile. Wenn wir die Architektur eines Lernmodells so einstellen, dass es diese Eigenschaften beim Lernen berücksichtigt, werden wir wesentlich bessere Lernergebnisse erzielen, als wenn wir es dem Modell überlassen, selbst auf diese Idee zu kommen. Schließlich ist die Wahrscheinlichkeit, dass eine "theorieneutrale" Lernkonfiguration auf eine bestimmte vorteilhafte Lösung kommt, relativ gering, wenn der Raum möglicher Lösungen sehr groß ist.
Zwar verfügen die neuesten effektiven Deep-Learning-Architekturen durchaus über Millionen oder gar Milliarden trainierbarer Gewichte. Das heißt aber nicht, dass sich mit diesen Gewichten beliebige Beziehungen erlernen ließen. Ein funktionales neuronales Netz ist vielmehr nach theoretischen Vorstellungen konzipiert, die im Detail festlegen, wie die Daten strukturell interpretiert werden sollen: "The secret of the success of any neural architecture lies in tailoring the structure of the network with a semantic understanding of the domain at hand" [1].
Lernen heißt also im Wesentlichen: die Stellschrauben eines Systems justieren, dessen Basiseigenschaften durch die Architektur schon vorgegeben sind. Das mag etwas überraschend klingen. Aber wenn man Maschinen das Lernen beibringt, braucht man zuerst eine Theorie darüber, wie Lernen an der Materie überhaupt gelingen kann. Das ist bei der linearen Regression so und bei neuronalen Netzen nicht anders. Man würde vielleicht zu weit gehen, wenn man das Einstellen der Gewichte (also den eigentlichen Lernvorgang) nur noch als Nebensache betrachtete. Allein schon, weil die Optimierung der Gewichte so viel Zeit und Ressourcen in Anspruch nimmt. Aber ganz falsch ist es eben auch nicht.
Ein zweiter Faktor, der die Überlegenheit neuronaler Netze gegenüber einfachen Machine-Learning-Algorithmen begründet, ist deren Potenzial, komplexe Beziehungen zu lernen. Wie die Bezeichnung Deep Learning schon impliziert, verwenden solche Modelle mehrere hintereinander geschaltete Funktionen bzw. Layer, um Beziehungen zwischen x- und y-Variablen zu modellieren. Damit lassen sich beliebige Funktionen erlernen: "Standard multilayer feedforward networks are capable of approximating any measurable function to any desired degree of accuracy, in a very specific and satisfying sense. We have thus established that such „mapping“ networks are universal approximators" [2].
Diese Vorzüge haben damit zu tun, dass vorhergehende Funktionsschichten die Daten für nachfolgende Schichten vorbereiten können. So lassen sich Aufgabenstellungen wie zum Beispiel das XOR-Problem (exklusives Oder), an dem lineare Klassifizierer wie das einfache Perzeptron oder die logistische Regression scheitern, Schritt für Schritt einer Lösung näherbringen [3].
Neuronale Netze können aber noch weitaus mehr. Sie können sich so gut den Trainingsdaten anpassen, dass man diese Fähigkeit künstlich beschneiden muss. Tatsächlich taucht mit dem Verschwinden des einen Problems – der eingeschränkten Leistungsfähigkeit einfacher Algorithmen – ein anderes Problem am Horizont auf: das Problem der Überanpassung (Overfitting).
Dabei geht es um die Frage, wie man zu einem Modell kommt, das seine Leistungsfähigkeit nicht nur dazu nutzt, eine durch die Trainingsdaten gestellte Aufgabe zu lösen, sondern dazu, eine Aufgabe so zu lösen, dass sich damit auch andere, bisher ungesehene Daten angemessen bearbeiten lassen. Das ist das Problem der Generalisierung, und es ist im Deep Learning ein gravierendes Problem, weil komplexe Algorithmen eben alle Möglichkeiten haben, sich mit komplizierten Funktionen an die Eigenarten der Trainingsdaten anzupassen [4].
In diesem Artikel sehen wir uns nur die einfachste Form neuronaler Netze an, die sogenannten Feed-Forward-Netze, die aus vollständig verbundenen Schichten bestehen. Auch wenn sich diese Modelle zur Verarbeitung von Textdaten kaum eignen, können wir daran die Funktionsweise und Einstellungsoptionen demonstrieren und die Eigenarten neuronaler Netze kennenlernen.
Neuronen und neuronale Netze
Auch wenn die Begrifflichkeiten Neuron und neuronales Netz nur Metaphern sind, mit denen keine funktionale Verwandtschaft mit biologischen Neuronen bzw. Gehirnen angezeigt werden soll, hilft die Analogie doch, deren Funktionsweise zu veranschaulichen. Aus dieser Perspektive verhalten sich Lernalgorithmen wie die lineare oder logistische Regression, wie einfache Lernzellen. Die Eingaben (x-Werte) kann man sich analog zu elektrischen Impulsen vorstellen, die von einem Sinnesorgan eingefangen werden und auf die Lernzelle treffen (vgl. Abb. 1). Sie aktivieren den Algorithmus und veranlassen ihn zu einer Reaktion. Die Art der Reaktion ist dabei abhängig von der Stärke der Impulse und der Gewichte, die an die Impulse gekoppelt werden. In den Gewichten kommt das aus der Vergangenheit Gelernte zum Ausdruck, in den Impulsen die aktuelle Situation.
Im Falle einer einfachen Lernzelle entspricht die Reaktion der Summe dieser gewichteten Eingaben, die mithilfe einer Aktivierungsfunktion in eine bestimmte Form gezwungen werden. Die Reaktion könnte zum Beispiel darin bestehen, bestimmte Muskeln zu aktivieren, zum Beispiel das Augenlid (y) zu schließen, wenn ein Windstoß (x) kommt. Wenn wir eine solche Reaktion simulieren möchten, gibt es nur zwei Möglichkeiten: Entweder wir schließen das Lid oder wir schließen es nicht. Dieses Problem können wir leicht als binäre Klassifikationsaufgabe deuten und zum Beispiel mit einer logistischen Aktivierungsfunktion eine sigmoide Ausgabe produzieren, die die Wahrscheinlichkeit angibt, mit der das Lid geschlossen werden soll. Man kann sich aber auch andere Aktivierungsfunktionen vorstellen; zum Beispiel eine Stufen-Funktion, die nur genau zwei Zustände annehmen kann und die eher die Arbeit eines biologischen Neurons imitiert, das entweder feuert (1) oder nicht feuert (0). Aber auch andere Varianten sind denkbar, wie wir in Kürze sehen werden.
Was unterscheidet nun ein künstliches Neuron von einem neuronalen Netz? Die Antwort ist in den verwendeten Begrifflichkeiten bereits enthalten. Genau wie ein Gehirn aus einer Vielzahl von Neuronen besteht, besteht ein neuronales Netz aus einer Vielzahl künstlicher Neuronen, die zur Verarbeitung einer Ausgabe zusammengeschlossen werden. Die einfachste Form eines neuronalen Netzes ist ein vollständig verbundenes Netz (s. Abb. 2).
Dieses einfache Netz besteht aus insgesamt drei Schichten (Layer). Die erste Schicht bildet die Eingabeschicht (Input Layer). Die Eingabeschicht hat nur die Aufgabe, die eingehenden x-Werte aufzunehmen und an die erste verdeckte Schicht (Hidden Layer) weiterzuleiten. Die erste (und in diesem Fall einzige) verdeckte Schicht besteht aus drei Neuronen. Die Bezeichnung vollständig verbundenes Netz bzw. vollständig verbundene Schicht rührt daher, dass alle Eingaben an jedes Neuron einer Schicht zur Verarbeitung weitergegeben werden. Jedes Neuron in der ersten aktiven Schicht erhält zum Beispiel alle x-Werte zur Berechnung einer Ausgabe. Das ist nicht immer so. Wir werden später Anwendungen sehen, in denen nur bestimmte Neuronen mit bestimmten Eingaben anderer Schichten gefüttert werden.
Was ist nun der Vorteil der Zusammenschaltung mehrerer Neuronen zu einem Verbund? Zum Beispiel ermöglicht es die Bearbeitung komplexer nichtlinearer Aufgabenstellungen, mit denen einzelne Neuronen überfordert wären. Stellen Sie sich eine eigentlich ganz einfache Aufgabe vor, eine Entweder-oder-Entscheidung (kein XOR-Problem): Das Neuron soll entweder feuern, wenn ein eingehender x-Wert sehr hoch oder wenn ein eingehender x-Wert sehr niedrig ist. Ein einzelnes Neuron ist dazu nicht in der Lage. Es kann entweder darauf trainiert werden, auf hohe Werte zu reagieren oder auf niedrige Werte, aber nicht auf beide Fälle. Gehen wir das Problem dagegen mit zwei Neuronen an, die die gleichen Eingaben erhalten, kann sich das eine auf die Verarbeitung niedriger Werte und das andere auf die Verarbeitung hoher Werte spezialisieren. Das erste Neuron feuert immer dann, wenn ein x-Wert mit hohen Werten angeliefert wird, das andere, wenn ein x-Wert mit niedrigen Werten angeliefert wird. Wenn die Eingaben sich dagegen im mittleren Bereich bewegen, reagiert weder das eine noch das andere Neuron. Jetzt brauchen wir noch ein Neuron in der Ausgabeschicht, das den Output der vorherigen Neuronen als Eingabe empfängt. Dieses Output-Neuron produziert erst die eigentliche Reaktion. Es kommuniziert mit der Außenwelt, wohingegen die beiden verdeckten Neuronen nur mit dem Ausgabeneuron (bzw. der Input-Layer) kommunizieren. Da die Neuronen in der vorherigen Schicht aber schon die ganze Arbeit verrichtet haben, muss jetzt nur noch geprüft werden, ob entweder das eine oder das andere vorgelagerte Neuron gefeuert hat – wenn das der Fall ist, feuert auch das Ausgabeneuron, wenn nicht, feuert es nicht.
Auf diese Weise lassen sich auch weitaus komplexere Probleme (zum Beispiel das XOR-Problem, bei dem das Ausgabeneuron nur feuern soll, wenn entweder die eine oder die andere Eingabevariable einen hohen Wert annimmt, aber nicht, wenn beide das tun) lösen. Entscheidend ist dabei allerdings, dass die Ausgaben aus den verdeckten Schichten mithilfe einer nichtlinearen Funktion wie zum Beispiel der logistischen aktiviert werden. Mathematisch kann man nämlich zeigen, dass eine Hintereinanderschaltung von Neuronen, die jeweils lineare Ausgaben produzieren, nichts anderes als eine lineare Funktion des Inputs erzeugen, die für die Lösung nichtlinearer Problemstellungen ungeeignet sind [4]. Die Aktivierung der Ausgaben der Neuronen in den verdeckten Schichten ist also keine Kosmetik, sondern leistet einen wesentlichen Beitrag zur Funktionalität des Netzes.
Das Beispiel verdeutlicht aber nicht nur die Bearbeitung komplexer Probleme im Zusammenspiel mehrerer Neuronen. Es gibt auch einen Einblick in die Abläufe bei der Produktion von Schätzwerten: Wie man sieht, werden die Daten von vorne nach hinten durchgeleitet – deshalb auch der Name Feed-Forward-Netz. Wir starten in der Eingabeschicht, die die x-Daten zur Verfügung stellt. Nehmen wir der Einfachheit halber an, das Netz sei auf die Verarbeitung von genau drei Features als Input eingestellt. In der ersten verdeckten Schicht, die aus drei Neuronen besteht, kommen bei jedem Neuron also genau drei x-Werte zur Verarbeitung an. Diese drei Werte werden mit jeweils drei Gewichten versehen, die den jeweiligen Einfluss einer Eingabe auf die Ausgabe justieren. Während also in jedem dieser Neuronen die gleichen x-Daten verarbeitet werden, können in jedem Neuron jeweils spezifische Gewichte zum Einsatz kommen. Die Tatsache, dass die Konstante (das Intercept, im Deep Learning auch Bias genannt) in der Abbildung nicht aufgeführt ist, heißt übrigens nicht, dass sie nicht existiert. Man kann den Bias auch als einfaches Gewicht behandeln, das unabhängig vom Datensatz, der durchgeschleust wird, mit einem x-Wert von 1 multipliziert wird.
Wenn die ersten Neuronen mit ihrer Arbeit fertig sind, entsteht am Ende in jedem Neuron genau eine Ausgabe. Diese Ausgabe wird mit einer Aktivierungsfunktion in eine bestimmte Form gebracht. Von dort aus geht die Reise weiter in die nächste Schicht. Das Neuron in der Ausgabeschicht (Output Layer) erhält die Ausgaben der Hidden Layer als Input. Die ursprünglichen x-Daten spielen an dieser Stelle keine Rolle mehr, nur noch die durch die verdeckte Schicht vorverarbeiteten Daten. Das Neuron in der Ausgabeschicht selbst funktioniert dafür aber genau gleich wie die Neuronen in der verdeckten Schicht. Jedem eingehenden Signal wird ein Gewicht zugewiesen, das Stärke und Richtung des Einflusses auf die Ausgabe festlegt. Dass es sich bei den eingehenden Signalen jetzt um Ausgaben anderer Neuronen handelt, spielt dabei keine Rolle. Der einzige Unterschied ist, dass die Ausgabeschicht Ausgabewerte produziert, die tatsächlich als Zielwerte fungieren. Abhängig von der Aufgabenstellung muss dieser Wert daher auf die zu schätzende Zieleinheit eingestellt werden. Dazu verwendet man wiederum eine spezifische Aktivierungsfunktion. Bei einer Klassifizierungsaufgabe wäre das eine logistische bzw. eine Softmax-Funktion, bei einer linearen Schätzaufgabe würde man dagegen ohne Aktivierung arbeiten, um eine stetige Ausgabe zu produzieren.
Wie neuronale Netze lernen
Die Arbeit eines neuronalen Netzes unterteilt sich genau wie die Arbeit eines Machine-Learning-Algorithmus in zwei Phasen. In der ersten Phase, der Trainingsphase, werden die Gewichte (w) eingestellt. Dabei werden Trainingsdaten, bestehend aus Features (x) und Targets (y), verwendet, um die Gewichte so einzustellen, dass bei einem gegebenen Satz von Features die Zielvariable möglichst exakt getroffen wird.
Die zweite Phase bezeichnet die Anwendungsphase. Jetzt wird das angelernte Modell, das die justierten Gewichte beinhaltet, zur Produktion von Schätzwerten auf Grundlage gegebener x-Werte herangezogen. Die Zielwerte sind in dieser Phase unbekannt, zum Beispiel, wenn eingehende Mails danach klassifiziert werden, ob sie Spam-Mails sind. Wie diese zweite Phase im Detail funktioniert, haben wir im vorherigen Abschnitt gesehen. Kurz gesagt, beruht sie einfach auf der Durchleitung von x-Werten durch das Netz (Forward-Propagation), wobei die Ausgabe der letzten Schicht den Schätzwert bildet, mit dem wir arbeiten können.
Weitaus schwieriger als diese Forward-Propagation-Phase gestaltet sich die erste Phase, das Training eines neuronalen Netzes. Um zu verstehen, was genau geschieht, müssen wir uns zunächst an den Lernprozess einer einfachen Lernzelle erinnern.
Im Mittelpunkt dieses Lernansatzes steht die Optimierung von Gewichten, die sich an einer Kostenfunktion (im Deep Learning meist Verlustfunktion genannt) orientiert. Die Gewichte werden dabei nicht in einem einzelnen Rechenschritt bestimmt, sondern im Zuge des Trainings iterativ, also Schritt für Schritt um jeweils einen kleinen Betrag in die richtige Richtung gedreht. Die Optimierung der Gewichte orientiert sich dabei an den partiellen Gradienten der Verlustfunktion: Bildlich ausgedrückt besteht ein Lernschritt darin, dass jedes Gewicht ein kleines Stück in jene Richtung gedreht wird, mit der sich das Modell tiefer ins Tal der Verlustfunktion bewegen lässt.
Genau so funktioniert auch das Training eines neuronalen Netzes, mit dem Unterschied, dass bei einem neuronalen Netz eine Vielzahl an Lernzellen existieren, deren Gewichte justiert werden müssen, und mit dem gravierenden Haken, dass für alle Neuronen, die einer verdeckten Schicht angehören, kein Zielwert existiert, an dem man sich beim Anlernen orientieren könnte (daher auch der Name verdeckte Schicht oder Hidden Layer).
Das ist tatsächlich eine ziemlich große Einschränkung. Schließlich ist die Verlustfunktion nichts anderes als eine mehr oder weniger komplexe Formel, die den Unterschied zwischen tatsächlichen Zielwerten und geschätzten Zielwerten zum Ausdruck bringt. Wenn wir eine einfache Lernzelle trainieren, können wir anhand dieser Kenngröße immer gleich prüfen, wie sich Veränderungen der einzelnen Gewichte im Hinblick auf das Ziel des Trainings, die Minimierung der Differenzen zwischen Ist und Soll auswirken. Bei Lernzellen, die einer versteckten Schicht angehören, ist das nicht möglich. Folglich können wir uns bei der Optimierung der Gewichte einer verdeckten Schicht auch nicht unmittelbar an einer Verlustfunktion orientieren, die sich auf die Ausgabe dieser Schicht bezieht. Stattdessen müssen wir die Gewichte aller Neuronen über den Vergleich von Ist- und Sollwerten am Ausgang des neuronalen Netzes optimieren.
Das Verfahren, das dazu eingesetzt wird, nennt sich Backpropagation. Dabei werden die Fehler aus dem Output in die Lernzellen vorgelagerter Schichten nach einem bestimmten Schlüssel, der sich an deren Beitrag zum Output orientiert, zurückpropagiert. Danach können die Gewichte mithilfe des Gradientenabstiegsverfahrens optimiert werden. Dabei geht man wiederum iterativ vor. Bei jedem Lernschritt wird geprüft, in welche Richtung die Gewichtungskoeffizienten gedreht werden müssen, um ins Tal der Fehlerfunktion zu gelangen.
Im Falle neuronaler Netze ist diese Fehlerfunktion ziemlich kompliziert. Ihr Verlauf hängt ja nicht nur von einem oder einigen wenigen Gewichten ab, sondern im Normalfall von einer Vielzahl von Gewichten, die innerhalb des Netzes über Funktionen von Funktionen verbunden sind. Man muss sie sich als multidimensionale Landschaft vorstellen, deren Talsohle mit rechnerischen Mitteln in begrenzter Zeit nicht auffindbar wäre. Da diese Funktion aber differenzierbar ist, können wir die Gradienten berechnen und daran ablesen, in welche Richtung die Gewichte gedreht werden müssen, um zumindest einen kleinen Schritt in Richtung Tal der Verlustfunktion zu gelangen.
Das klingt vielleicht einfacher, als es ist. In Wahrheit haben sich viele helle Köpfe für lange Zeit die Zähne an der Lösung dieser Aufgabe ausgebissen [5]. Wer die Berechnungsschritte im Detail nachverfolgen will, sei auf die Literatur verwiesen [6]. Für uns sind an dieser Stelle lediglich die groben Verfahrensschritte interessant. Das Training beinhaltet schließlich immer beide Phasen: Zum einen die ForwardPropagation-Phase, die Voraussetzung für die Produktion von Schätzwerten am Ausgang des Netzes ist – ohne sie können keine Fehler zwischen Soll- und Istwerten berechnet werden. Zum anderen die Backpropagation-Phase, die wir einleiten, sobald der Fehler im Output des Netzes berechnet wurde. Erst jetzt beginnt das Netz zu lernen, indem es die Gewichte optimiert.
Bevor wir also mit dem Training eines neuronalen Netzes beginnen, müssen wir jede Lernzelle mit einem Satz an Gewichten initialisieren, damit es arbeitsfähig ist. Diese initialen Gewichte bestehen normalerweise aus Zufallswerten (gezogen zum Beispiel aus der Standardnormalverteilung). Auf diese Weise ermöglichen wir eine erste Durchleitung von x-Werten durch das Netz und erzeugen Ausgaben von Zielwerten, die wir mit den Sollwerten vergleichen können. Wir starten also an einem beliebigen Punkt innerhalb der multidimensionalen Landschaft, die die Verlustfunktion bildet, und können jetzt damit beginnen, unsere Gewichte zu optimieren. Sobald der Fehler berechnet wurde, kann der Backpropagation-Mechanismus greifen. Jetzt werden die Fehler zurückpropagiert, die partiellen Gradienten abgeleitet und damit die einzelnen Gewichte optimiert, sodass der Fehler am Ausgang kleiner wird. Danach folgt der nächste Schwung an Trainingsdaten. Im Vergleich zum ersten Durchlauf sollten wir dadurch in der Ausgabe dem Sollwert schon ein kleines Stückchen näher gekommen sein. Aber natürlich sind wir noch lange nicht fertig ‒ außer wir hätten durch Zufall den Prozess bereits nahe der Talsohle unserer Landschaft gestartet, was bei den Abermillionen Möglichkeiten für die Einstellung der Gewichte als so gut wie ausgeschlossen betrachtet werden kann. Jedenfalls beginnt der Prozess jetzt von Neuem: Berechnung des Fehlers, Ableitung der Gradienten, Update der Gewichte. Dann kommen neue Trainingsdaten und so weiter.
Dieser Artikel ist ein Auszug aus dem Buch
Deep Natural Language Processing
Einstieg in Word Embedding, Sequence-to-Sequence-Modelle und Transformer mit Python
Hanser Fachbuchverlag, April 2022 - 256 Seiten
- C. C. Aggarwal (2018): Neural Networks and Deep Learning. Cham, CH: Springer. S. 317
- K. Hornig, M. Stinchombe und H. White (1989): Multilayer Feedforward Networks are Universal Approximators, in: Neural Networks 2(5), S. 363
- S. Franklin (2014): History, Motivations, and Core Themes, in: K. Frankish, W. M. Ramsey (Hg.): The Cambridge Handbook of Artificial Intelligence. Cambridge University Press: Cambridge, MA., S. 19
- I. Goodfellow, Y. Bengio und A. Courville (2016): Deep Learning. Cambridge, MS: The MIT Press., S. 167
- Y. LeCun, Y. Bengio und G. Hinton (2015): Deep Learning, in: Nature 521(28).
- T. Rashid (2017): Neuronale Netze selbst programmieren. Heidelberg: O’Reilly/dpunkt.