Zu Beginn des Trainingsprozesses müssen die Parameter eines neuronalen Netzes mit Werten gefüllt werden. Die Parameter sind ja genau das Herzstück eines neuronalen Netzes, in dem das trainierte “Wissen” gespeichert wird. Doch welche initialen Werte sollte man verwenden?

Die Forschung der letzten Jahre zeigte bereits, dass die

Auswahl der Initialisierung einen wesentlichen Einfluß auf die Leistungs- und Lernfähigkeit des jeweiligen neuronalen Netzes hat.

In diesem Artikel werden wir schrittweise verschiedene Möglichkeiten der Initialisierung besprechen. Die Implementierungen der Beispiele basieren auf den Arbeiten von Jeremy Howard von der UCSF.

Warum Überlegungen zu Initialisierung von Parametern?

Das primäre Ziel einer guten Initialisierung der Parameter eines neuronalen Netzes soll folgende Probleme während des Trainingsprozesses verhindern:

  • Exploding Gradients: Während der Multiplikation der Gradienten entstehen derartig große Zahlen, dass diese nicht mehr durch den entsprechenden Zahlentyp im Computer dargestellt werden.
  • Vanishing Gradients: Die Gradienten erhalten sehr kleine Werte, die durch laufende Multiplikation so klein werden, dass der Zahlentyp im Computer nur mehr 0-Werte darstellen kann.

Beide Probleme führen dazu, dass das neuronale Netz nur sehr langsam oder gar nicht lernt.

Aus den Erklärungen zu explodierenden bzw. verschwindenden Gradienten kann man bereits ableiten, dass diese Probleme umso tragender werden, je tiefer das neuronale Netz ist - sprich, je mehr Layers im Netz enthalten sind.

Die Berechnungen während eines Trainingsvorgangs in einem neuronalen Netz basieren im Wesentlichen auf Matrix-Multiplikationen. Ein Vorwärtsprozess (forward pass) in einem neuronalen Netz bedeutet eine Verkettung von Matrixmultiplikationen über die verschiedenen Layer hinweg. Die beiden beteiligten Matrizen sind die jeweiligen Parameter des Layers und die Eingangswerte des Layers. Das Ergebnis einer solchen Multiplikation wird zum Eingangswert für den nachfolgenden Layer.

Matrixmultiplikation innerhalb eines neuronalen Netzes

Um diese Multiplikationen zu verdeutlichen, betrachten wir folgendes Beispiel. Unser Eingangswert ist ein Vektor X. Das Standardvorgehen beim Training von neuronalen Netzen ist die Normalisierung der Eingangswerte auf eine Normalverteilung mit einem Mittelwert von 0 und einer Standardabweichung von 1.

x = torch.randn(512)

Weiter wollen wir annehmen, dass wir ein einfachen neuronales Netz ohne Aktivierungen mit 100 Layer bauen. Jeder Layer in unserem Netz hat also eine Matrix a, welche die Parameterwerte für den jeweiligen Layer beinhaltet.

Wenn wir nun einen forward-pass in unserem Netz simulieren wollen, müssen wir für jeden Layer die Eingangswerte, mit der jeweiligen Parameter-Matrix multiplizieren. Dies machen wir für alle 100 Layer unseres Netzes:

for i in range(100):
    a = torch.randn(512, 512)
    x = a @ x

Geben wir nach der Berechnung die den Mittelwert und die Standardabweichung von x aus:

print(x.mean(), x.std())

So würden wir folgendes Ergebnis erhalten:

(tensor(nan), tensor(nan))

Das bedeutet, dass die Werte für die Parameter innerhalb des neuronalen Netzes so hoch geworden sind, dass auch der Computer nicht mehr in der Lage war, die Standardabweichung und den Mittelwert als Zahlenwerte zu interpretieren.

Wir können nun prüfen ab welchem Layer die konkreten Werte über den Zahlenbereich des Computers hinausgeschossen sind (also “explodiert” sind).

for i in range(100):
    a = torch.randn(512, 512)
    x = a @ x
    if torch.isnan(x.std()): break
print(i)

Wir erhalten 28 als Ausgabe. Das heißt, bereits nach nicht einmal einem Drittel aller Layer konnte der Mittelwert bzw. die Standardabweichung nicht mehr bestimmt werden.

Offensichtlich haben wir unsere Parameter also mit zu hohen Werten initialisiert!

Unser neuronales Netz kann also aufgrund der zu hohen initialen Werte der Parameter nicht lernen.

Wie finden wir die optimale Initialisierung der Parameter in einem neuronalen Netz?

Die Berechnungen, die während des forward-pass durch unser Netzwerk durchgführt werden, sind reine Matrix-Multiplikationen. Das heißt, auf jedem Layer ist das Ergebnis y nur das Produkt aus Eingangswerten und Parametermatrix. Diese Multikationen werden über alle Layer hinweg durchgeführt. Somit ergibt sich am Ende für jedes einzelne Element i aus y folgende Formel:

$y_i=\sum_{k=0}^{n-1}a_{ik}x_k$

…wobei _i_ der Zeilenindex der Parametermatrix a ist und k der Spaltenindex in der Parametermatrix und der Elementindex im Ergebnisvektor y. Die Variable n ist die Gesamtanzahl von Elementen in der Eingangsmatrix X.

Die mathematische Formulierung können wir so als Python Code schreiben:

y[i] = sum([c*d for c,d in zip(a[i], x)])

Nun können wir auch zeigen, dass für einen bestimmten Layer, das Matrizzenprodukt aus der Eingangsmatrix X und der Parametermatrix a die Standardabweichung im Mittel rund um einen Wert von $\sqrt{512}$ hat. Die 512 sind in unserem Fall die Anzahl der Eingangswerte in unser neuronales Netz. Verallgemeinert können wir also sagen, dass die mittlere Standardabweichung über alle Layer hinweg im Bereich von $\sqrt{n}$ liegt - wobei n die Anzahl von Eingangswerten in den ersten Layer darstellt.

mean, var = 0., 0.
for i in range(10000):
    x = torch.randn(512)
    a = torch.randn(512, 512)
    y = a @ x
    mean += y.mean().item()
    var += y.pow(2).mean().item()
mean/10000, math.sqrt(var/10000)

Ausgabe:

0.00889449315816164, 22.629779825053976

Betrachten wir nun zum Vergleich $\sqrt{512}$:

math.sqrt(512)

Ausgabe:

22.627416997969522

Dieses Ergebnis ist nicht weiter verwunderlich. Denn im Zuge der Matrixmultiplikation add wir 512 Produkt der elementweisen Multplikation eines Elements der Eingangsmatrix X mit einer Spalte aus der Parametermatrix a. In unserem Beispiel haben wir sowohl X, wie auch a mit Werten aus einer Normalverteilung befüllt. Jedes dieser 512 Produkte hat einen Mittelwert von 0 und eine Standardabweichung von 1.

Daher muss auch die Summe dieser 512 Produkte einen Mittelwert von 0 und deshalb eine Standardabweichung von $\sqrt{512}$ haben.

Genau aus diesem Grund haben wir bei unserem vorigen Beispiel ein “Explodieren” der Gradienten bereits nach 28 Multiplikationen feststellen können.

In unserem einfachen neuronalen Netz mit 100 Layern möchten wir eine Standardabweichung von 1 für jeden einzelnen Layer. Auf diese Weise könnten wir beliebig viele Matrixmultiplikationen aneinander ketten, ohne dass die Gradienten Gefahr laufen zu explodieren oder zu verschwinden. Wir könnten auf diese Weise also die Gefahren der exploding bzw. vanishing gradients abweisen.

Wenn wir also zu Beginn jeden zufälligen Wert in unserer Parametermatrix a durch $\sqrt{512}$ dividieren, bedeutet dies in Folge, dass die elementweise Multiplikation, die jeweils ein Element in unseren Ergebnisvektor y liefert, im Mittel eine Varianz von $1/\sqrt{n}$ aufweist.

Wir können dies wieder mit einem kleinen Python-Script testen:

mean, var = 0., 0.
for i in range(10000):
    x = torch.randn(1)
    a = torch.randn(1)*,math.sqrt(1./512)
    y = a*x
    mean += y.item()
    var += y.pow(2).item()
mean/10000, math.sqrt(var/10000)

(-2.091224115950183e-05, 0.0020251519136443882)

1/512

0.001953125

Die Standardabweichung unseres Ergebnisvektors y, der durch die Matrixmultiplikationen berechnet wurde, ist somit genau 1. Auch das testen wir wieder mit einem kleinen Script:

mean, var = 0., 0.
for i in range(10000):
    x = torch.randn(512)
    a = torch.randn(512, 512)*,math.sqrt(1./512)
    y = a @ x
    mean += y.mean().item()
    var += y.pow(2).mean().item()
mean/10000, math.sqrt(var/10000)

(0.0005563282515970058, 1.0004933748755085)

Nun können wir mit diesem Wissen unser ursprüngliches neuronales Netz mit 100 Layern erneut berechnen:

for i in range(100):
    a = torch.randn(512, 512)
    x = a @ x
x.mean(), x.std()

(tensor(0.0358), tensor(1.0825))

Und tatsächlich, keine vanishing oder exploding gradients mehr - auch nach unseren 100 Layern nicht.

Doch sind wir noch nicht ganz am Ziel. Wir haben aus Gründen der Einfachheit die Aktivierungsfunktionen der Layer außer Acht gelassen! Ein neuronales Netz ohne Aktivierungsfunktionen würden wir jedoch in der Praxis nicht einsetzen. Erst durch den Einsatz, dieser nicht-linearen, Funktionen können tiefe neuronale Netze jene Erfolge liefern, die wir in der jüngeren Vergangenheit gesehen haben.

Weitere Posts aus dieser Serie:

Quelle: https://towardsdatascience.com/weight-initialization-in-neural-networks-a-journey-from-the-basics-to-kaiming-954fb9b47c79