Präsentation ist auch online sirbubbls.github.io/backpropagation-seminar
GitHub: https://github.com/sirbubbls/backpropagation-seminar
Deep Learning (Ian Goodfellow, Yoshua Bengio & Aaron Courville)
Beispiele für alle praktischen Beispiele dieser Präsentation sind in diesem Repository zu finden.
Der Gradient ist der Vektor aller partiellen Ableitungen einer Funktion \(f\).
Notation: \(\nabla f(x)\)
\(f(x) = 2x_1^2 + x_2^3\)
\(\rightarrow \nabla_xf(x)=\left(\begin{array}{c} f'_{x_1} \\ f'_{x_2} \end{array}\right)= \left(\begin{array}{c} 4x_1 \\ 3x^2_2 \end{array}\right)\)
Bei mehreren Funktionen (o.a. Komponenten Funktionen).
Bei mehreren Funktionen bilden deren Gradienten eine Jacobi Matrix.
\[ J(f) =\left (\begin{array}{cc} \nabla f_1 \\ \nabla f_2 \end{array} \right ) \]
Der Gradient Descent Algorithmus wird dafür verwendet ein lokales Minimum einer Funktion zu bestimmen.
Funktion \(f(x)=x_1^2-x_2^2\) ist gegeben.
Also: \(\nabla_xf(x)=\left(\begin{array}{c} f'_{x_1} \\ f'_{x_2} \end{array}\right)= \left(\begin{array}{c} 2x_1 \\ -2x_2 \end{array}\right)\)
Wir starten mit einem beliebigen Punkt: z.B. \(\left(\begin{array}{c} 2 \\ 1 \end{array}\right)\) und setzen ein:
\(\left(\begin{array}{c} 2x_1 \\ -2x_2 \end{array}\right) = \left(\begin{array}{c} 2 * 2 \\ -2 * 1 \end{array}\right) = \left(\begin{array}{c} 4 \\ -2 \end{array}\right)\)
\(Neuer\ Punkt = \left(\begin{array}{c} 2 \\ 1 \end{array}\right) - \lambda \left(\begin{array}{c} 4 \\ -2 \end{array}\right)\) mit \(\lambda: learning\ rate\)
Typischerweise werden Operationen in artificial neural networks nicht mit mathematischen Formeln angegeben, sondern als Graph dargestellt.
Jede Node in einem Graph \(G\) repräsentiert eine mathematische Operation oder eine Input Variable.
Beispielsweise:
\[ y = a+b \]
\(x=y+z\)
\(a=x\odot z\)
Als Vorbild dienen Neuronale Netze in der Biologie, jedoch sind beide Felder doch unterschiedlicher als man vielleicht erwarten würde.
In diesem Vortrag werden nur fully connected feed forward networks behandelt.
Parameter werden üblicherweise als Theta (\(\theta\)) notiert.
Der Lernalgorithmus soll die Parameter \(\theta\) so verändern, dass sich \(f\) so nah wie möglich an \(f^*\) annähert.
Formale Definition für ein neuronales Netz: \(y=f(x; \theta)\) und \(y = f^*(x)\)
Wir teilen das Netzwerk in Schichten (Layer) auf.
Jeder Layer bildet eine Funktion \(f^{i}\), mit \(i=Layer\ Index\) ab.
Somit ist ein neurales Netzwerk eine Kette an Funktionen \(f\).
Ein Netz mit \(3\) Layern wäre somit \(f^2(f^1(f^0(X)))\) mit \(X=Input\ Data\)
Jeder Layer enthält mindestens folgende Informationen:
Jedes Neuron eines Layers \(L_i\) ist mit allen Neuronen des Layers \(L_{i-1}\) verbunden.
Da wir bei Neural Networks oft versuchen non-lineare Zusammenhänge zu approximieren, benötigen wir auch eine nicht-lineare Komponente in unserem NN.
Rectified Linear Unit (\(ReLU\))
\(f(x)=max(0, x)\)
\(Leaky\ ReLU\)
\(f(x)=\begin{cases} x &\quad if\ x > 0 \\ 0.1x &\quad else \end{cases}\)
Sigmoid Function
\(f(x)= \frac{1}{1+e^{-t}}\)
Eine Funktion um zu bestimmen wie ’nah’ wir uns an unserem erwarteten Inference Wert befinden.
In dieser Präsentation benutzen wir die Euklidean-Distance \((x-y)^2\) als Cost Function.
Ein Layer in einem Feed-Forward Neural Network besteht aus folgenden Elementen:
Jedes Neuron enthält einen Vektor mit Weights \(w\), der Angibt wie stark jeder Input gewichtet wird.
\(a_1*w_1+a_2*w_2\) oder \(a_{L-1}W_L\)
Um die Aktivierungen (\(a\)) eines Layers zu berechen können wir folgende Formel benutzen:
\(a_L = \sigma(W_La_{L-1} + b_L)\)
Der berechnete Vektor \(a_L\) dient dem Layer \(L+1\) als Input.
\[ a = \sigma(a_{L-1}W_L+b) \]
\(W=\left[\begin{array}{ccc} 1 & 1 \\ 1 & 1 \end{array}\right]\)
\(b=\left [\begin{array}{ccc} 0 \\ -1 \end{array} \right]\)
\[ XW=\left[\begin{array}{ccc} 0 & 0 \\ 0 & 1 \\ 1 & 0 \\ 1 & 1 \end{array} \right] \left[\begin{array}{ccc} 1 & 1 \\ 1 & 1 \end{array}\right]= \left[\begin{array}{ccc} 0 & 0 \\ 1 & 1 \\ 1 & 1 \\ 2 & 2 \end{array} \right] \]
\[ XW + b= \left[\begin{array}{ccc} 0 & 0 \\ 1 & 1 \\ 1 & 1 \\ 2 & 2 \end{array} \right] + \left(\begin{array}{ccc} 0 \\ -1 \end{array}\right)= \left[\begin{array}{ccc} 0 & -1 \\ 1 & 0 \\ 1 & 0 \\ 2 & 1 \end{array} \right] \]
\(ReLU:= f(x)=max(0, x)\)
\[ relu(XW+b)= relu(\left[\begin{array}{ccc} 0 & -1 \\ 1 & 0 \\ 1 & 0 \\ 2 & 1 \end{array} \right])= \left[\begin{array}{ccc} 0 & 0 \\ 1 & 0 \\ 1 & 0 \\ 2 & 1 \end{array} \right] \]
Die Aktivierungsfunktion wird auf jedes Element der Matrix ausgeführt.
Multiplizieren der Output Matrix des ersten Layers mit den Weights des Output Layers (\(w\)). \[ w= relu(XW+b)* \left[\begin{array}{ccc} 1 \\ -2 \end{array}\right]= \left[\begin{array}{ccc} 0 & 0 \\ 1 & 0 \\ 1 & 0 \\ 2 & 1 \end{array} \right]* \left[\begin{array}{ccc} 1 \\ -2 \end{array}\right]= \left[\begin{array}{ccc} 0 \\ 1 \\ 1 \\ 0 \end{array}\right] \]
Input: \(\left[\begin{array}{ccc} 0 & 0 \\ 0 & 1 \\ 1 & 0 \\ 1 & 1 \end{array} \right]\)
Predictions: \(\left[\begin{array}{ccc} 0 \\ 1 \\ 1 \\ 0 \end{array}\right]\)
def forward(X): a = X for layer in L: a = layer.weights @ a + layer.bias return a
\[ O(w) \]
Ein fundamentaler Baustein, von neuralen Netzen.
Backpropagation ist kein Lernalgorithmus/Optimierungsalgorithmus, sondern aussschlißlich für die Generierung der Gradients jedes Layers zuständig.
Also suchen wir folgende Gradients:
Die Kettenregel ist nützlich um Ableitungen aus schon bereits vorhandenen Ableitungen zu konstruieren.
\[y=g(x)\ und\ z=f(g(x))=f(y)\]
Dann besagt die Kettenregel: \(\frac{dz}{dx} = \frac{dz}{dy} \frac{dy}{dx}\)
\[ x = f(w),\ y=f(x),\ z=f(y) \]
\[ \frac{d z}{d w}= \frac{d z}{d y} \frac{d y}{d x} \frac{d x}{d w} = f'(y)f'(x)f'(w) \\ = f'(f(f(w)))f'(f(w))f'(w) \]
Wir benötigen folgende Werte aus jedem Layer um den Backpropagation Algorithmus ausführen zu können.
\(f'(y)f'(x)f'(w)\): Speichern der Zwischenergebnisse in Variablen \(f'(f(f(w)))f'(f(w))f'(w)\): Neu Evaluierung der Zwischenergebnisse
Forward Propagation ausführen.
Wir berechnen den Gradienten der Cost Function \(J\). \(J = \frac{1}{2} (y-X)^2 \rightarrow \nabla_y J = X - y\)
Erst müssen wir den Gradienten in Relation zu den pre activation function values berechnen.
\(\nabla_{a^{k}} J = g \odot f'(a^{(k)})\)
mit \(f'(x) := Ableitung\ der\ Aktivierungsfunktioin\)
Weight Gradienten berechnen. \[ f(w, a, b) = w*a+b \] \(g * \frac{\partial}{\partial w} = g * (a+0)\)
\(\nabla_{w^k} J = ga^{k-1}\)
Bias Gradienten berechnen. \[ f(w, a, b) = w*a+b \] \(g * \frac{\partial}{\partial b}= g * 1\)
\(\nabla_{b^{k}} J = g\)
\(\nabla a^{k-1} J = w^kg\)
Basic Backpropagation Implementation
In der Praxis werden keine Vektoren als Input Daten benutzt, sondern Matrizen (siehe XOR
Beispiel).
\[
Input = \left[\begin{array}{ccc} 0 & 0 \\ 0 & 1 \\ 1 & 0 \\ 1 & 1 \end{array} \right]
\]
Wir erhalten nun auch mehrere Gradienten in Form einer Matrix. Wir können nun den Durchschnitt der Gradienten nutzen um unsere Weights anzupassen.
Mini Batch Backpropagation Implementation
Wichtig
Folgende Komplexitäten beziehen sich ausschließlich auf den Backpropagation Algorithmus.
Backpropagation besitzt die gleiche Laufzeitkomplexität wie Forward-propagation.
\[ O(w) \]
\[ O(mh) \]
Bisher haben wir uns nur mit Backpropagation in Zusammenhang mit neuronalen Netzwerken beschäftigt.
Backpropagation kann aber auch generell für andere Anwendungen eingesetzt werden.
Es existieren zwei verschiedene Möglichkeiten die Berechnungen der Gradients durchzuführen.
Die Input Variablen werden durch Zahlenwerte ersetzt und daraufhin (wie besprochen) alle nötigen Gradienten berechnet.
Beim der Symbol to Symbol Herangehensweise wird zuerst der Graph mit allen Ableitungen mit der Hilfe von symbolischen Werten konstruiert.
Später wird dann der Graph mit der Hilfe eines eigenen Algorithmus ausgewertet.
Vorteil
Ableitungen eines höheren Grads können berechnet werden, indem man den Backpropagation Algorithmus auf einen bereits abgeleiteten Graphen ausführt.
Wir betrachten einen computational Graph, jede Node in dem Graph repräsentiert eine Variable in Form eines Tensors.
get_operation()
get_consumers()
get_inputs()
bprop()
Benötigt ist:
Wir definieren \(G'\) als alle Variablen, die Vorfahren von \(z\) und Nachfahren von \(T\) sind.
In grad_table
können wir Variablen Gradients zuweisen.
grad_table[z] = 1
(da \(\frac{\partial z}{\partial z} = 1\))
In jedem Loop rufen wir die Funktion build_grad
auf.
for v in T: build_grad(v, G, G_1, grad_table) return [grad_table[v] for v in T]
build_grad(v, G, G_1, grad_table)
def build_grad(v, G, G_1, grad_table): if v in grad_table: return grad_table[v] i = 1 for c in get_consumers(v, G_1): op = get_operation(c) d = build_grad(c, G, G_1, grad_table) g[i] = op.bprop(get_inputs(c, G_1), v, d) i += 1 g = sum(g) grad_table[v] = g return g
bprop
Funktion
op.bprop(inputs, X, G)
inputs
: Liste an Inputs, die wir der Operation zur Verfügung stellen
X
: Input, dessen Ableitung wir berechnen wollen
G
: Gradient des Outputs der Operation
Wir wollen \(\frac{\partial u_1}{\partial u_4}\) bestimmen.
Dadurch ist der Backpropagation Algorithmus sehr allgemein anwendbar.
Jede Operation ist für seine eigene Differenzierung verantwortlich und benötigt keine weiteren Informationen.
Klassische machine learning Algorithmen wurden in den 90er Jahren mehr genutzt als neuronale Netzwerke.
Durch die hohe Speicheranforderung wurden NN ab ca. 2006 immer vermehrter eingesetzt und bilden heute einen fundamentalen Baustein von maschinellem Lernen.
Beide treibenden Algorithmen von neuronalen Netzwerken haben sich seit den 80er Jahren nicht wesentlich verändert.
Bessere Resultate sind besser Hardware und Dataset Optimierung zu verdanken.