Hier erzeugen wir aus dem Ausdruck x + 3 eine neue Variable y. Eben haben wir x als PyTorch-Tensor mit dem Wert 3.5 erzeugt. Welchen Wert wird also y haben?
Probieren Sie es aus.
Abbildung 1-7: Arithmetik mit einem Tensor
Wie Abbildung 1-7 zeigt, hat y den Wert 6.5, was Sinn ergibt, denn 3.5 + 3 = 6.5. Außerdem sehen wir, dass y ebenfalls ein PyTorch-Tensor ist.
Sicherlich erinnern Sie sich daran, dass auch NumPy-Arrays in der gleichen Weise funktionieren. Diese Vertrautheit kommt uns entgegen, und durch die Übereinstimmung mit NumPy ist es zudem einfacher, PyTorch zu erlernen.
Automatische Gradienten mit PyTorch
Sehen wir uns nun an, wie sich PyTorch von reinem Python und NumPy abhebt und es so besonders macht. Der folgende Code erzeugt genau wie zuvor einen Tensor x, dieses Mal aber geben wir PyTorch eine zusätzliche Option requires_grad= True mit. Wir werden bald sehen, was diese Option bewirkt.
# PyTorch-Tensor
x = torch.tensor(3.5, requires_grad=True)
print(x)
Führen Sie den Code aus und sehen Sie sich an, was für x ausgegeben wird (siehe Abbildung 1-8).
Abbildung 1-8: Eine Tensoroperation mit einer zusätzlichen Option
Wie Abbildung 1-8 zeigt, hat x den Wert 3.5000 und ist vom Typ tensor. Aus der Ausgabe geht auch hervor, dass für den Tensor x die Option requires_grad auf True gesetzt ist.
Wir erzeugen nun wie zuvor eine neue Variable y aus x, dieses Mal aber mit einem anderen Ausdruck:
# y wird als Funktion von x definiert
y = (x-1) * (x-2) * (x-3)
print(y)
Der Code berechnet y aus dem Ausdruck (x-1) * (x-2) * (x-3). Führen Sie den Code aus.
Abbildung 1-9: Eine Variable als Funktion definieren
Wie Abbildung 1-9 zeigt, beträgt der Wert von y wie erwartet 1.8750. Das ergibt sich daraus, dass x gleich 3.5 ist und somit (3.5-1) * (3.5-2) * (3.5-3) das Ergebnis 1.8750 liefert.
Abbildung 1-10 mit dem Graphen der Funktion y = (x-1) * (x-2) * (x-3) veranschaulicht, was wir eben berechnet haben.
Abbildung 1-10: Der Graph der Funktion y = (x-1) * (x-2) * (x-3)
Bislang erscheint alles normal und vertraut.
Tatsächlich hat PyTorch aber zusätzliche Arbeit geleistet, die wir nicht gesehen haben. Denn PyTorch hat nicht einfach den Wert 1.8750 berechnet und in einen Tensor namens y gestellt. Vielmehr hat sich PyTorch tatsächlich daran erinnert, dass y mathematisch in Form von x definiert ist.
Wären x und y normale Python-Variablen oder sogar NumPy-Arrays, käme Python nicht auf die Idee, dass y von x kommt. Das ist auch nicht notwendig. Nachdem der Wert von y berechnet ist, und zwar aus x, ist nur noch dieser Wert an sich wichtig und dass er in y steht. Fertig.
PyTorch-Tensoren funktionieren anders. Sie merken sich, aus welchen anderen Tensoren sie berechnet werden und wie. Im Beispiel erinnert sich PyTorch daran, dass y von x gekommen ist.
Weshalb ist das nützlich? Schauen wir mal.
Sie werden sich erinnern, dass es bei den Berechnungen zum Trainieren eines neuronalen Netzes erforderlich ist, den Fehlergradienten per Analysis zu bestimmen, das heißt die Rate, mit der sich der Ausgabefehler infolge veränderter Gewichte für die Netzverknüpfungen ändert.
Die Ausgabe eines neuronalen Netzes wird von den Verknüpfungsgewichten bestimmt. Diese Ausgabe hängt von den Gewichten genau so ab, wie y von x abhängt. Sehen wir uns also an, wie PyTorch die Änderungsrate von y ermitteln kann, wenn sich x verändert.
Wir berechnen den Gradienten von y bei x = 3.5, das heißt, dy/dx bei x = 3.5.
Hierfür muss PyTorch für y feststellen, von welchen Tensoren es abhängt und wie die mathematische Form dieser Abhängigkeit aussieht. Dann kann es dy/dx berechnen.
# Gradienten berechnen
y.backward()
Diese einzelne Anweisung erledigt das alles. PyTorch betrachtet y, sieht, dass es von (x-1) * (x-2) * (x-3) kommt, und ermittelt automatisch den Gradienten dy/dx, der – wenn Sie es auflösen – den Ausdruck 3x2 - 12x + 11 ergibt.
Diese Anweisung berechnet auch den numerischen Wert dieses Gradienten und setzt ihn in den Tensor x neben den eigentlichen Wert von x. Da x gleich 3.5 ist, wird der Gradient zu 3*(3.5*3.5) - 12*(3.5) + 11 = 5.75.
Der Graph in Abbildung 1-11 veranschaulicht, wo wir diesen Gradienten berechnet haben.
Abbildung 1-11: Berechnung des Gradienten an der Stelle x = 3.5
Das ist ein beeindruckendes Arbeitsergebnis, das wir von y.backward() mit nur einer einzigen Anweisung bekommen haben!
Wir können den numerischen Wert des Gradienten, der in den Tensor x gestellt wurde, inspizieren:
# Wert des Gradienten bei x = 3.5
x.grad
Führen Sie den Code aus, um zu kontrollieren, ob er korrekt funktioniert.
Abbildung 1-12: Den Wert eines Gradienten ermitteln
Wie Abbildung 1-12 zeigt, hat er funktioniert!
An der Option requires_grad=True, die wir für den Tensor x gesetzt haben, erkennt PyTorch, dass wir an der Berechnung eines Gradienten in Bezug auf x interessiert sind.
Es ist also zu sehen, dass PyTorch-Tensoren reicher sind als normale Python-Variablen und NumPy-Arrays. Ein PyTorch-Tensor kann enthalten:
zusätzliche Informationen über den primären Zahlenwert hinaus, beispielsweise einen Gradientenwert,
Informationen darüber, von welchen anderen Tensoren er abhängt, und die mathematische Form dieser Abhängigkeit.
Wir haben hier ein Beispiel für eine sehr wertvolle Fähigkeit gesehen. Diese Fähigkeit, Tensoren zu verknüpfen und ein automatisches Differenzieren durchzuführen,