JGU Logo JGU Logo JGU Logo JGU Logo

Institut für Informatik

Modellierung 2

Michael Wand
David Hartmann
Sommersemester 2022

Lehreinheit 9

Forward- & Backwardpass
Letzte Änderung: 02. May 2022, 11:00 Uhr
Abgabe: Montag, der 27.06.2021, 12 Uhr

  /  



Efficent BackProp

2012 (Neural Networks: Tricks of the trade), Ursprüngliche Version: 1998

"What I cannot create, I do not understand.".

Richard Feynman
I use this quote in many of my talks.

Yann LeCun

https://twitter.com/ylecun/status/1066052750998417409



Wiederholung Neuronale Netze


Linearer Layer
Ein linearer Layer \(f : \mathbb{R}^d \rightarrow \mathbb{R}^{d'}\) ist eine affine lineare Abbildung, die Eingabedaten der Dimension \(d\) auf Ausgabedaten der Dimension \(d'\) abbildet. In endlichen Vektorräumen lässt sich eine solche Abbildung durch eine Matrix \(W\in \mathbb{R}^{d\times d'}\) und einen Vektor \(b\in \mathbb{R}^{d'}\) darstellen. D.h.: \[ f(x) := x \cdot W + b \] Wir nennen \(W\) die Gewichtsmatrix (oder kurz: Gewichte) und \(b\) den Bias des Layers.
Jede Komponente \(f_i\) von \(f\) ist hierbei das Skalarprodukt \(f_i := x\cdot w_i\), wobei \(w_i\) den \(i\)-ten Spaltenvektor von \(W\) darstellt. Man nennt die Komponenten \(f_i\) die Neuronen des Netzes1.


Aktivierungsfunktion / Aktivierugslayer:
Ganz abstrakt ist eine Aktivierungsfunktion eine solche, die die die Eingabe eines Neurons auf eine Zahl abbildet.
Typischerweise erwarten wir, dass eine Aktivierungsfunktion

  • die Ausgabe des linearen Layers komponentenweise (insbesondere ohne Wissen der anderen Komponenten) berechnet
  • nach der abstrakten Definition also auch die Dimension des linearen Layers beibehält
  • schnell bzw. einfach zu evaluieren ist
  • nicht-linear ist

In dieser Lehreinheit schauen wir uns eine Aktivierungsfunktion an: Rectified Linear Units (ReLU).

\( \operatorname{ReLU}(x) := \max(x, 0) \)


Softmax-Cross-Entropy Loss:
Bevor wir unser Netz zusammenbauen können, definieren wir die Qualität unseres Netzwerkes für die unten beschriebene Klassifizierungsaufgabe.
Nehmen wir an wir haben bereits unser Netz \(f\) definiert, das die Eingabe \(x\) auf \(c\) viele Ausgaben abbildet, wobei \(c\) die Anzahl an Klassen im Datensatz sei. Konzeptionell möchten wir erreichen, dass \(\arg\max_c f(x) = y\) gilt, also dass der Index des maximalen Ausschlags des Neuronalen Netzes gerade der Klasse \(y \in \{0,\dots,c-1\}\) entspricht.


In der Vorlesung wurde erklärt, dass eine Möglichkeit dies zu erreichen ist, die Ausgabe zurst per softmax als Wahrscheinlichkeiten zu interpretieren2 \[ \operatorname{softmax}(x): \mathbb{R}^c \rightarrow \mathbb{R}^c, \quad \operatorname{softmax}(x) \mapsto \left(\frac{e^{x_i}}{\sum_j^c e^{x_j}}\right)_{i=0,\dots,c-1} \] und dann die Kreuzentropie zwischen der geschätzten Wahrscheinlichkeitsverteilung des Neuronalen Netzes \(\hat y := \operatorname{softmax}(f(x))\) und der wahren Verteilung \(p_i := P(y=i)\) der Zugehörigkeit zur Klasse \(i\) zu bestimmen. Bei der Klassifizierung kennen wir diese wahre Verteilung: Sie ist \(1\) für den Klassenindex den wir kennen und \(0\) sonst (one-hot Vektor). In diesem speziellen Fall vereinfacht sich der CE-Loss aus der Vorlesung zu \[ \begin{align} \operatorname{SingleClassSoftmaxCE}(f(x), y) &: \mathbb{R}^c \rightarrow \mathbb{R},\\ \operatorname{SingleClassSoftmaxCE}(f(x), y) &:= - \log\left(\operatorname{softmax}(f(x))_y\right) \\ &\phantom{:}= -f(x)_y + \log \left(\sum_i^c e^{f(x)_i} \right) \end{align} \]


Hinweis: Der zweite Summand ist leider numerisch instabil. (Bestimmen Sie beispielsweise den Softmax des Vektors \((1, -10, 1000)\) mit PyTorch.) Stattdessen kann man den logarithmierten Softmax auch wie hier beschrieben numerisch stabil bestimmen.





[1] Streng genommen ist es eher umgekehrt: Die Lineare Abbildung ist historisch lediglich eine Form von Neuronen. Da sich das Skalarprodukt als eigentliche Operation bewährt hat und alle anderen Arten in der Praxis (außer in Feldern, die näher an der Biologie sind, wie etwa der Neurowissenschaft) ersetzt hat, werden wir in dieser Veranstaltung lediglich lineare Layer untersuchen.
[2] Diese Ansicht ist in vielen Fällen mit Vorsicht zu genießen, jedoch stellt softmax für uns sicher, dass die Ausgaben auch normiert sind und zumindest mathematisch als Wahrscheinlichkeiten interpretiert werden können.


Aufgabe: Forward Pass

Wir starten mit dem Problem. Gegeben ein einfacher 2D-Datensatz, bestehend aus jeweils wenigen Klassen von Punkten. Wir wollen ein Neuronales Netz entwerfen, das solche Datenssätze selbstständig zu klassifizieren lernt.



Als Datensatz dient uns entweder ein selbstgebauter Datensatz, beispielsweise ein Gitter mit einer festen und ungefähr gleichverteilte Anzahl an Klassen (siehe Bild) oder aber einer der vorgefertigten Spielzeug-Datensätze von SciKit-Learn.


Wichtig: Damit Sie gute Ergebnisse erziehlen müssen Sie vorher dafür sorgen, dass der Datensatz normiert in das Netz kommt, d.h. den Mittelwert 0 hat bei einer Standardabweichung von 1.


Wir schauen uns Netze der Form \[ f(x) := \left(f_n \circ a \circ f_{n-1} \circ a \circ \dots a \circ f_2 \circ a \circ f_1\right)(x) \] an, wobei \(a\) die oben beschriebene ReLU-Aktivierungsfunktion ist und \(f_i\) Lineare Layer mit den jeweiligen Parametern \(W_i \in \mathbb{R}^{c_{i-1}\times c_{i}}\) und \(b_i\in \mathbb{R}^{c_i}\).
Wir starten mit zufällig gewählten Parametern. Konkret wählen wir \(W_i \sim \mathcal{N}(0,1)\) und \(b=0.1\).


Aufgabe: Implementieren Sie den Forward-Pass, d.h. eine Auswertung des Netzes auf einem Datenpunkt \(x\).


Hinweise:

Aufgabe: Backward Pass / BackProp


Nun zur Optimierung. Wir schauen uns die Cross-Entropie der Ausgabe des Netzwerks an, \(y(x) := \operatorname{SingleClassSoftmaxCE}(f(x), y)\). Die Frage, wie wir die Gewichte ändern müssen, damit sich die Energiefunktion (a.k.a. Loss) lokal verbessert, lässt sich leicht Beantworten: Die Ableitung zeigt in die Richtung des steilsten Anstiegs. Wir haben in Lehreinheit 8 bereits verschiedene Algorithmen kennengelernt, die diese Idee zur Optimierung nutzen.


Der einfachheit Halber implementieren wir Stochastic Gradient Descent:
Um den Loss zu minimieren gehen wir einfach eine strikte Distanz \(W_i \rightarrow \lambda \cdot \nabla \frac{\partial y}{\partial W_i}\).


Doch wie erhalten wir diese Gradienten effizient? Die Antwort darauf zeigt uns die Kettenregel. Wir leiten den Loss nach einem beliebigen Gewicht mitten im Netzwerk ab. \[ \frac{\partial y}{\partial W_i} = \frac{\partial \operatorname{SCSCE}}{\partial f_n} \cdot \frac{\partial f_n}{\partial a} \cdot \frac{\partial a}{\partial f_{n-1}} \cdot \dots \cdot \frac{\partial f_{i+1}}{\partial a} \cdot \frac{\partial a}{\partial f_i} \cdot \frac{\partial f_i}{\partial W_i} \]


Die Faktorisierung der Ableitung durch die Kettenregel ermöglicht es uns den folgenden Algorithmus.


Für jeden Layer im Netzwerk berechnen wir die lokale Ableitung des Ergebnisses nach den Eingaben und nach den Gewichten. Für einen linearen Layer \(f_i(x) := x\cdot W_i + b_i\) ist dies einfach (siehe auch hier): \[ \begin{align} \frac{\partial f_i}{\partial W_i} &:= x\\ \frac{\partial f_i}{\partial x} &:= W_i^T\\ \frac{\partial f_i}{\partial b_i} &:= 1 \end{align} \]


Aufgaben:

  1. Wie sieht das im Fall der Aktivierungsfunktion \(a\) aus und im Falle des Softmax-CrossEntropy-Losses?
  2. Um also ein spezifisches Gewicht \(W_i\) anzupassen, müssen wir zuerst den Loss bestimmen und dann der Kettenregel folgend alle Faktoren bis zum Layer \(f_{i+1}\) aufmultiplizieren und zuletzt mit \(W_i^T\) multiplizieren. Das Ergebnis skaliert mit \(\lambda\) können wir direkt auf die Gewichte anwenden. Praktischerweise ändern sich die Multiplikationen für alle folgenden Layer nicht, d.h. wir können eine Menge Rechnungen sparen.
  3. Implementieren Sie die ganze Optimierungspipeline und plotten Sie die Verläufe von Loss und Accuracy.
    • Testen Sie verschiedene Netz-Größen (Anzahl Layer, Anzahl Dimensionen \(c_i\) auf den verschiedenen Layern) und verschiedene Batch-Größen (also die Anzahl an Punkten, die bei jedem Forward-/Backwardpass neu gewürfelt werden).

Aufgabe: Visualisierung

Die starke Expressivität der Neuronalen Netze kommt von den Nichtlinearitäten. (Falls wir uns alle nichtlinearen Layer im oben beschriebenen Netzwerk \(f\) wegdenken würden, könnten wir nämlich alle linearen Teile in einen einzigen linearen Layer zusammenfassen. In der Vorlesung wurde erklärt, wie ein Neuronales Netz den Eingaberaum in Zellen unterteilt und es so schafft in verschiedenen Bereichen des Eingaberaumes unterschiedliche Rechnungen durchzuführen. Wir dies hier nachimplementieren.


  1. Zeigen Sie, dass ein Neuronales Netz ohne Aktivierungsfunktionen durch einen einzigen linearen Layer dargestellt werden kann.
  2. Wir definieren ein festes Gitter von 2D-Punkten, zum Beispiel \(\{(h,w) |h,w \in \{-5,-4.9,-4.8,\dots\}\}\). Wir können dies Beispielsweise als Bild verstehen, das wir per matplotlib.plt.imshow einfach per darstellen. Einfach erklärt „fotografieren“ wir den Eingaberaum durch die Linse des Neuronalen Netzwerkes.
  3. Nun zur Farbe: Wir möchten die Zellen „sehen“, in die das Neuronale Netz den Eingaberaum unterteilt. Die Funktion des Softmax ist es die Auswahl Operation \(\max\) ableitbar in allen Argumenten zu formulieren. Der entsprechend gewählte Index \(\arg\max\) gibt uns die vom Netz gewählte Klasse zurück. Diesen Index können wir verwenden, um den Eingaberaum entsprechend der Klassifizierung einzufärben — je eine ein-eindeutige Farbe für jede Klasse.
  4. Ähnlich können wir auch alle Zwischenergebnisse in einem Layer \(i\) auswerten.
    Dazu inferieren alle Zwischenergebnisse bis zu genau diesem Layer \(i\) und weisen Allen möglichen Ausgaben möglichst verschiedene Farben zu.

    Konkret schauen wir uns also zuerst das Zwischenergebniss des \(i\)-ten linearen Layers an, \[ y_i(x) := \left(f_{i} \circ a \circ \dots a \circ f_2 \circ a \circ f_1\right)(x). \] Dadurch, dass im nächsten Layer die Aktivierungsfunktion alle Komponenten, die kleiner als 0 sind auf 0 setzt, können wir die Zellen messen, indem wir alle Punkte gleich einfärben, die das selbe Aktiviergunsmuster \(\operatorname{sign}(y_i(x)) \in \{-1,1\}^{c_i}\) haben. (Den Fall, dass wir die 0 genau treffen ignorieren wir).

    Färben Sie die Pixel des Bildes nach den Aktiviergunsmustermustern. (Also alle Pixel mit dem Muster \({-1,1,-1,...}\) gelb, alle Pixel mit dem Muster \({1,1,1,...}\) grün, usw.
  5. Was haben alle Eingaben der selben Farbe gemeinsam?
  6. Was ist der Grund, dass der in 4. beschriebene Algorithmus Voronoi-artige Zellen aufzeigt? (Schauen Sie dazu noch einmal in die Vorlesungsvideos rein.)
  7. Was können Sie nun „interaktiv“ beobachten?
    • In der Initialisierung des Netzwerkes (noch keine Optimierung geschehen?)
    • Im fertig trainierten Netz?
    • Wie verhält sich die Visualisierung während des Trainings?
    • Insbesondere: Was ist der Anteil an mit Daten gefüllten Zellen zur Anzahl an möglichen Zellen?
  8. Im Hinblick auf nächste Woche:
    Wie ändert sich die Visualisierung mit
    • breiteren Layern (höhere Dimensionen \(c_i\))
    • oder mehr Layern (größeres \(n\))
    • oder einer anderen Initialisierung, konkret: was ändert sich, wenn die Initialisierung der Gewichte nicht \(\mathcal{N}(0,1)\), sondern \(\mathcal{N}(0,s)\) verteilt sind.