Читать книгу GANs mit PyTorch selbst programmieren - Tariq Rashid - Страница 12

Berechnungsgraphen

Оглавление

Diese eben vorgestellte automatische Gradientenberechnung scheint magisch zu sein, aber das ist sie natürlich nicht.

Es lohnt sich trotzdem, ein wenig davon zu verstehen, weil dieses Wissen uns später helfen wird, wenn es um den Aufbau größerer Netze geht.

Sehen Sie sich das sehr einfache Netz in Abbildung 1-13 an. Es ist kein neuronales Netz, sondern lediglich eine Folge von Berechnungen.

Abbildung 1-13: Ein sehr einfaches Netz

Abbildung 1-13 zeigt einen Eingang x, der verwendet wird, um y zu berechnen, das dann verwendet wird, um z, den Ausgang, zu berechnen.

Stellen Sie sich vor, dass y und z wie in Abbildung 1-14 gezeigt berechnet werden.

Abbildung 1-14: Berechnung von y und z

Wenn wir wissen wollen, wie sich die Ausgabe z verändert, wenn x variiert, müssen wir einige Berechnungen anstellen, um den Gradienten dy/dx zu ermitteln. Wir führen das schrittweise durch.


Abbildung 1-15: Schritte beim Berechnen des Gradienten

Die erste Zeile ist die Kettenregel der Differenzialrechnung. Als Ausgangspunkt ist sie uns hier sehr nützlich. Falls Sie eine Auffrischung brauchen: Einer der Anhänge meines Buchs Neuronale Netze selbst programmieren ist eine Einführung in die Analysis und die Kettenregel.

Wir haben also gerade herausgefunden, dass die Ausgabe z mit x als 4x variiert. Wenn x = 3.5 ist, wird dz/dx zu 4*3.5 = 14.

Wenn y in Form von x und z in Form von y definiert ist, erstellt PyTorch ein Bild, wie diese Tensoren verknüpft sind. Dieses Bild ist ein sogenannter Berechnungsgraph.

Abbildung 1-16 zeigt, wie er für unser Beispiel aussehen könnte.

Abbildung 1-16: Der Berechnungsgraph für unser Beispiel

In Abbildung 1-16 sehen Sie, wie y aus x und z aus y berechnet wird. Zusätzlich fügt PyTorch Rückwärtspfeile hinzu, die zeigen, wie sich y ändert, wenn sich x ändert und wie sich z ändert, wenn sich y ändert. Das sind die Gradienten, mit denen ein neuronales Netz während des Trainings aktualisiert wird. Die Analysis übernimmt PyTorch, wir brauchen diese Berechnungen nicht selbst anzustellen.

Um herauszufinden, wie z sich ändert, wenn x sich ändert, folgen wir dem Pfad von z zurück nach x über y, wobei wir die Gradienten zusammenfassen. Dies ist praktisch die Kettenregel der Differenzialrechnung.

Probieren wir mit Code aus, ob es funktioniert. Legen Sie ein neues Notebook an, importieren Sie torch und geben Sie danach den folgenden Code ein, um die Beziehungen zwischen x, y und z einzurichten:

# Einen einfachen Graphen in Bezug auf x, y und z einrichten

x = torch.tensor(3.5, requires_grad=True)

y = x*x

z = 2*y + 3

PyTorch erstellt den Berechnungsgraphen aber nur in der Vorwärtsrichtung. Wir müssen PyTorch mit der Funktion backward() dazu bringen, die Gradienten in Rückwärtsrichtung zu berechnen:

# Gradienten berechnen

z.backward()

Der Gradient dz/dx ist im Tensor x als x.grad gespeichert.

# Den Gradienten bei x = 3.5 berechnen

x.grad

Führen Sie den Code aus, um zu kontrollieren, ob er funktioniert.


Abbildung 1-17: Den Gradienten an der Position x = 3.5 berechnen

Die Antwort ist 14, genau wie wir es oben per Hand berechnen haben. Großartig!

Beachten Sie, dass der Gradientenwert im Tensor x angibt, wie sich z ändert. Das hängt damit zusammen, dass wir PyTorch mittels z.backward() angewiesen haben, von z aus rückwärts zu rechnen. Somit ist x.grad gleich dz/dx und nicht dy/dx.

Bei den meisten praktischen neuronalen Netzen weisen die Knoten mehrere eingehende und mehrere ausgehende Verbindungen auf. Sehen wir uns ein anderes einfaches Beispiel an, in dem mehr als eine Verbindung zu einem Knoten führt (siehe Abbildung 1-18).

Abbildung 1-18: Ein einfaches neuronales Netz, das Knoten mit mehreren eingehenden und ausgehenden Verbindungen enthält

Wie Abbildung 1-18 zeigt, tragen beide Eingänge – a und b – zu x und y bei, und die Ausgabe z wird sowohl aus x als auch aus y berechnet.

Notieren wir die Beziehungen zwischen diesen Knoten:


Abbildung 1-19: Die Beziehungen zwischen den Knoten des neuronalen Netzes

Die Gradienten können wir genau wie zuvor berechnen (siehe Abbildung 1-20).

Abbildung 1-20: Berechnung der Gradienten

Diese Informationen fügen wir jetzt in den Berechnungsgraphen ein (siehe Abbildung 1-21).

Abbildung 1-21: Der Berechnungsgraph mit den hinzugefügten Gradienten

Den Gradienten dz/da können wir nun leicht berechnen, indem wir dem Pfad von z zurück nach a folgen. Eigentlich gibt es zwei Pfade, denn einer geht durch x und der andere durch y. Das ist in Ordnung, wir folgen beiden und addieren einfach die Ausdrücke der beiden Pfade. Das ist sinnvoll, da beide Pfade von a nach z zum Wert von z beitragen. Das wäre auch der Fall gewesen, wenn wir dz/da mit der Kettenregel der Differenzialrechnung berechnet hätten.


Abbildung 1-22: Die Änderungsrate von z bezüglich a über die beiden Pfade durch x und y

Der erste Pfad durch x liefert uns 2 * 2, und der zweite Pfad durch y ergibt 3 * 10a. Somit beträgt die Rate, bei der sich z mit a ändert, 4 + 30a.

Wenn a gleich 2 ist, wird dz/da zu 4 + 30*2 = 64.

Überprüfen wir, ob dies auch so von PyTorch berechnet wird. Zuerst richten wir die Beziehungen so ein, dass PyTorch seinen Berechnungsgraphen erstellen kann.

# Einen einfachen Graphen mit x, y und z einrichten

a = torch.tensor(2.0, requires_grad=True)

b = torch.tensor(1.0, requires_grad=True)

x = 2*a + 3*b

y = 5*a*a + 3*b*b*b

z = 2*x + 3*y

Dann lösen wir die Gradientenberechnungen aus und fragen den Wert im Tensor a ab:

# Gradienten berechnen

z.backward()

# Wert des Gradienten bei a = 2.0

a.grad

Kontrollieren wir, ob PyTorch die Antwort genau so berechnet, wie wir es getan haben (siehe Abbildung 1-23).

Abbildung 1-23: Gradienten berechnen lassen und Wert bei a = 2.0 abrufen

Das tut es!

Praktische neuronale Netze sind im Allgemeinen größer als dieses kleine Netz, doch PyTorch erstellt einen Berechnungsgraphen in genau der gleichen Weise und verwendet die gleiche Methode, den Pfaden rückwärts zu folgen, um die Gradienten zu berechnen.

Vielleicht ist es nicht ganz klar, wie sich das eben Gezeigte zu dem Fehler eines neuronalen Netzes und der Aktualisierung interner Gewichte verhält. Dazu kommen wir jetzt.

Wenn z die Ausgabe eines Netzes ist, wie in unserem einfachen Beispiel oben, und die korrekte Ausgabe t sein sollte, ergibt sich der Fehler E zu (z-t) oder üblicherweise (z-t)2. Dieser Fehler E ist lediglich ein anderer Knoten am Ende des Netzes mit (z-t)2 als Berechnung von z zu E. Er und nicht z ist jetzt effektiv der Ausgabeknoten. PyTorch kann dann den Gradienten der neuen Ausgabe E in Bezug auf die Eingänge zum Netz berechnen.

Wie Neuronale Netze selbst programmieren erläutert hat, verwenden wir dE/dw, wobei w ein Gewicht im Netz ist, um ein neuronales Netz zu trainieren. Wir haben mit dz/da gearbeitet, wobei a ein Eingang und kein Gewicht ist. Stellt das ein Problem dar? Nein, weil wir Gewichte genauso gut als Knoten auffassen können.

Abbildung 1-24 zeigt, wie z sowohl vom Signal aus y als auch vom Gewicht w2 abhängt. Diese Beziehung könnte z = w2 * y sein, was Ihnen von neuronalen Netzen her vertraut sein dürfte.

Abbildung 1-24: Der Ausgang z hängt sowohl vom Signal aus y als auch vom Gewicht w2 ab.

Abbildung 1-24 zeigt, wie dz/dw2 in der gleichen Weise wie dz/dy berechnet werden kann. Und genau wie zuvor können wir dem Pfad von z zurück nach w1 folgen, um zum Beispiel dz/dw1 zu ermitteln.

Was wir hier gesehen haben, ist eine leicht vereinfachte Version dessen, was tatsächlich innerhalb von PyTorch passiert, doch die Kerngedanken sind die gleichen. Wenn wir die Modelle verstehen, die eben besprochen wurden, werden wir in der Lage sein, anspruchsvollere PyTorch-Netze zu erstellen und dabei häufiger erfolgreich zu sein.

Der Code, den wir gerade geschrieben haben, um die Berechnungsgraphen zu erkunden, steht online zur Verfügung:

  https://github.com/makeyourownneuralnetwork/gan/blob/master/01_pytorch_computation_graph.ipynb

Doch genug der Theorie, wir wollen nun mit PyTorch ein nützliches neuronales Netz erzeugen.

GANs mit PyTorch selbst programmieren

Подняться наверх