Dies ist eine alte Version des Dokuments!
Nikolai, Adam
Viele kennen neuronale Netzwerke als Diagramme mit Punkten, die in Spalten angeordnet sind und durch Linien miteinander verbunden werden. Auf den ersten Blick wirkt das ziemlich technisch, aber die Grundidee dahinter ist eigentlich leicht zu verstehen. Ein neuronales Netzwerk versucht, Muster in Daten zu erkennen, so wie wir Menschen Dinge wiedererkennen, wenn wir sie oft genug gesehen haben.
In unserem Beispiel besteht das Netzwerk aus drei Schichten. Die erste Schicht hat 784 Eingabewerte. Das können zum Beispiel die einzelnen Pixel eines Bildes sein, auf dem ein Buchstabe zu sehen ist. Diese Informationen gelangen dann in die mittlere Schicht mit 128 Neuronen. Dort versucht das Netzwerk, wichtige Merkmale zu finden wie Linien, Rundungen oder Ecken. Danach geht alles weiter zur Ausgabeschicht mit 52 Neuronen. Jedes dieser Neuronen steht für einen Buchstaben des Alphabets, jeweils in Grossbuchstaben und Kleinbuchstaben. Am Ende entscheidet das Netzwerk, welcher Buchstabe am besten zum eingegebenen Bild passt.
So zeigt das Diagramm sehr anschaulich, wie ein neuronales Netzwerk Informationen verarbeitet und Schritt für Schritt immer näher an die richtige Antwort kommt.
Weiter gehts
Wenn du dir die Grafik anschaust, siehst du auf der linken Seite die lila Kreise. Jeder dieser Kreise ist ein einzelner Neuron, und zusammen bilden sie die Input Layer. Was steckt da drin? Ganz einfach, jeden Pixelwert des Buchstabenbildes, umgerechnet auf einen Wert zwischen 0 und 1:
inputVector = 1.0 - arr.flatten() / 255.0
Weiss wird zu 0.0, schwarz zu 1.0. Und weil das Bild 28×28 Pixel gross ist, entstehen genau 784 Werte, deshalb steht in der Grafik auch x₁, x₂ bis x₇₈₄. Schau jetzt auf die Linien zwischen den lila und den grünen Kreisen. Jede einzelne Linie ist ein Gewicht. Es verbindet jeden Input-Neuron mit jedem Hidden-Neuron und jede Verbindung hat einen eigenen Wert, der bestimmt wie stark dieser Input Einfluss hat. Im Code stecken alle diese Gewichte zusammen in einer Matrix:
self.W1 = np.random.randn(inputSize, hiddenSize) * 0.01
784 Inputs mal 128 Hidden Neurons gibt über 100'000 Gewichte, nur für diesen einen Übergang. Die werden am Anfang zufällig gesetzt und dann durch das Training langsam verbessert. Die grünen Kreise in der Mitte, h₁ bis h₁₂₈, sind die Hidden Layer. Jeder dieser Neuronen nimmt alle 784 Eingabewerte, multipliziert sie mit seinen Gewichten und addiert noch einen Bias dazu. Der Bias ist ein freier Wert der dem Neuron erlaubt seine Schwelle unabhängig vom Input zu verschieben. Mathematisch sieht das so aus:
z=(x1⋅w1)+(x2⋅w2)+…+(x784⋅w784)+b
Und im Code passiert das für alle 128 Neuronen auf einmal:
self.z1 = X @ self.W1 + self.b1
Dieses z ist noch nicht das finale Ergebnis des Neurons. Es ist die rohe gewichtete Summe, auch pre-activation genannt. Ob der Neuron jetzt wirklich etwas weitergibt, entscheidet ReLU. Ist z grösser als 0, kommt der Wert durch. Ist er kleiner, wird er auf 0 gesetzt:
self.a1 = self.relu(self.z1)
Was danach rauskommt fliesst zu den orangen Kreisen rechts in der Grafik, der Output Layer. Dort passiert genau dasselbe nochmal, mit Gewichten W₂ und Bias b₂:
self.z2 = self.a1 @ self.W2 + self.b2
Nur der letzte Schritt ist anders. Statt ReLU kommt jetzt Softmax, der die Rohwerte in Wahrscheinlichkeiten umwandelt. Am Ende gibt jeder der 52 orangen Neuronen eine Wahrscheinlichkeit aus und der Buchstabe mit dem höchsten Wert ist die Vorhersage des Netzes.
Wenn du dir nochmal die Grafik anschaust, steht unter den grünen Kreisen „ReLU Aktivierung“ und unter den orangen „Softmax“. Das sind die zwei Aktivierungsfunktionen im Netz, und sie machen eigentlich sehr unterschiedliche Dinge. Fangen wir mit ReLU an. ReLU steht für Rectified Linear Unit, auf Deutsch in etwa „gleichgerichtete lineare Einheit“. Nachdem ein Neuron seine gewichtete Summe z berechnet hat, wird diese einfach durch ReLU geschickt. Die Regel ist denkbar simpel, ist z grösser als 0, kommt der Wert unverändert durch. Ist z kleiner oder gleich 0, wird er auf 0 gesetzt. Das war's:
def relu(z):
return np.maximum(0, z)
Mathematisch sieht das so aus:
f(z)={zwenn z>00sonstf(z) = \begin{cases} z & \text{wenn } z > 0
0 & \text{sonst} \end{cases}f(z)={z0wenn z>0sonst
Warum macht man das überhaupt? Ohne eine Aktivierungsfunktion wäre das ganze Netz nur eine einzige grosse lineare Gleichung, egal wie viele Schichten man draufpackt. ReLU bringt die Nichtlinearität rein, die das Netz braucht um wirklich komplexe Muster zu lernen. Ein Neuron der 0 ausgibt ist quasi stumm, er gibt nichts weiter. Ein Neuron der einen positiven Wert ausgibt feuert und beeinflusst die nächste Schicht.
Bei der Output Layer ist ReLU aber nicht mehr geeignet. Dort brauchen wir keine stummen Neuronen, wir brauchen Wahrscheinlichkeiten. Genau das macht Softmax. Er nimmt alle 52 Rohwerte der Output-Neuronen auf einmal und rechnet sie so um, dass sie zusammen 1.0 ergeben:
def softmax(z):
e = np.exp(z - np.max(z, axis=1, keepdims=True)) return e / e.sum(axis=1, keepdims=True)
Mathematisch passiert folgendes, jeder Rohwert wird mit der Exponentialfunktion hochgerechnet, und dann durch die Summe aller hochgerechneten Werte geteilt:
σ(zi)=ezi∑j=152ezj\sigma(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{52} e^{z_j}}σ(zi)=∑j=152ezjezi
Das sorgt dafür, dass ein hoher Rohwert eine hohe Wahrscheinlichkeit bekommt und ein tiefer eine kleine. Am Ende schaut das Netz einfach welcher der 52 Neuronen die höchste Wahrscheinlichkeit hat, und das ist dann der vorhergesagte Buchstabe:
labels[np.argmax(probs)]
ReLU und Softmax haben also ganz verschiedene Rollen. ReLU filtert innerhalb des Netzes und schafft die Nichtlinearität. Softmax macht am Schluss aus rohen Zahlen eine lesbare Aussage, nämlich wie sicher das Netz ist, welchen Buchstaben es gerade sieht.
Ein Perzeptron ist eigentlich der Vorläufer von allem was wir bisher besprochen haben. Es ist das einfachste denkbare künstliche Neuron, erfunden in den 1950er Jahren. Die Idee dahinter ist genau dieselbe wie bei unserem Neuron, Inputs reinkommen, mit Gewichten multiplizieren, Bias addieren, und dann eine Entscheidung treffen. Der Unterschied ist, dass ein klassisches Perzeptron am Ende nur 0 oder 1 ausgeben kann, also entweder ja oder nein. Erkenne ich den Buchstaben oder nicht. Das Problem dabei ist schnell klar. Mit nur 0 und 1 kann man keine Wahrscheinlichkeiten ausdrücken und man kann auch keine komplexen Muster lernen. Ein einzelnes Perzeptron kann zum Beispiel nicht unterscheiden ob ein Pixel oben links oder unten rechts liegt, es sieht alles als eine einzige Entscheidung. Genau deshalb wurde das Feedforward-Netz entwickelt. Die Idee ist simpel, man nimmt nicht ein Perzeptron sondern viele, stapelt sie in Schichten übereinander und verbindet sie. Das ist genau das was du in der Grafik siehst. Die Informationen fliessen dabei immer nur in eine Richtung, von links nach rechts, von der Input Layer durch die Hidden Layer bis zur Output Layer. Kein Rückweg, kein Kreis, nur vorwärts. Daher der Name Feedforward.
- künstliches Neuron
- Input
- Gewicht
- Bias
- Aktivierungsfunktion
- Perzeptron
- Feedforward-Netz
- gewichtete Summe
- Schwellenwert
- Entscheidungsgrenze
- Backpropagation
- Input Layer
- Hidden Layer
- Output Layer
- Tiefe neuronale Netze (Deep Neural Networks)
- flache vs. tiefe Netze
Evtl. ein ganz simples Beispiel zeigen und durchgehen (AND).