Tensoren in PyTorch¶
Tensoren ≈ Arrays¶
Vier Fragen: Was ist ein Array? Was ist ein Tensor? Was ist ein Array in NumPy? Was ist ein Tensor in Torch?
Ein Array ist eine Sammlung von Daten von gleichem Typ, die fortlaufend im Speicher liegen (können).
Ein Tensor ist, je nachdem, wen man fragt:
Physiker: ein Objekt, das sich tensoriell transformiert.
Mathematiker: ein Element in einem Tensorproduktraum.
Informatiker: ein fancy Wort für Arrays.
Ein Tensor in Torch ist eine Sammlung von Daten von gleichem Typ (dem
dtype), die fortlaufend im Speicher liegen – und zwar potentiell im GPU-Speicher – optimiert für Gradientenabstiegsmethoden (Attributgrad, dazu mehr in den Kapiteln „Gradientenabstieg“ und „Autograd und Berechnungsgraph“).
Man kann einen Torch-Tensor aus einem NumPy-Array herstellen, sogar ohne zusätzlichen Speicher zu verbrauchen (torch.from_numpy teilt sich den Speicher mit dem Array). Lesen Sie dazu das PyTorch-Tutorial „Tensors“.
Ausflug: Tensoren in der Mathematik¶
Der Informatiker hat nicht ganz Unrecht, aber das mathematische Konzept hilft zu verstehen, was shape und reshape eigentlich bedeuten. Wer den Ausflug überspringen will, kann bei „Ausflug beendet“ weiterlesen.
Tensorprodukt. Gegeben Vektorräume (z.B. und ) kann man das Tensorprodukt bilden: ist definiert als ein Vektorraum, sodass es eine 1:1-Korrespondenz gibt
Insbesondere gibt es eine bilineare Abbildung , . Wenn und Basen sind, so bilden die eine Basis von ; insbesondere ist und .
Dualraum. Man kann den Dualraum bilden: , die Menge der linearen Abbildungen , ist ein neuer Vektorraum. Man nennt die Elemente von Vektoren, die Elemente von Kovektoren oder Linearformen. Wenn eine Basis von ist, bilden die Linearformen mit für und sonst eine Basis von , die duale Basis. Ein Skalarprodukt braucht man dafür nicht. Man sagt, die Elemente von sind kontravariant in und kovariant in – kovariant ist immer der Faktor mit dem . In der Einstein-Notation steht der kontravariante Index oben und der kovariante unten; die Matrix aus dem übernächsten Absatz hat also Komponenten . Die Basisvektoren tragen es genau umgekehrt, .
Gegeben mit der Standardbasis und dem Standardskalarprodukt können wir Vektoren in Kovektoren verwandeln (und zurück) durch Transponieren: Der Spaltenvektor wird zum Zeilenvektor , und die Matrixmultiplikation ist das Skalarprodukt. Weil die Standardbasis orthonormal ist, gilt hier ; in einer schiefen Basis stimmt das nicht.
Skalare, Vektoren und Matrizen als Tensoren. Gegeben einen Vektorraum lässt sich das iterierte Tensorprodukt ( mal) betrachten, mit der Konvention (denn ). Eine Matrix lässt sich auffassen als lineare Abbildung ; die Spalten der Matrix sind , die Bilder der Basisvektoren. Für jeden Vektor ist stets
und damit
Jedes Element eines Tensorproduktraums wird Tensor genannt – also auch Skalare , Vektoren und lineare Abbildungen . Ein Skalar ist ein Tensor der Stufe 0, ein Vektor der Stufe 1, eine Matrix der Stufe 2; die Stufe zählt die Faktoren im Tensorprodukt und heißt in NumPy und PyTorch ndim. Im Englischen und in der Dokumentation von TensorFlow heißt dieselbe Zahl rank, weshalb man auch „Tensor vom Rang 2“ liest – beide Wörter sind gebräuchlich. Achtung, Kollision: Der Rang einer Matrix ist in der linearen Algebra etwas ganz anderes, nämlich die Dimension ihres Bildes. torch.zeros(3,3).ndim ist 2, torch.linalg.matrix_rank(torch.zeros(3,3)) dagegen 0. Und was ist für ein Tensor? Eine bilineare Abbildung , zum Beispiel – oder eine lineare Abbildung oder vier Indizes, je nach Lesart.
Obacht: lineare Abbildung ≠ Matrix. Jede Matrix lässt sich als lineare Abbildung interpretieren. Umgekehrt lässt sich, gegeben eine Wahl von Basen, jede lineare Abbildung als Matrix schreiben, die darstellende Matrix. Genauso hat der gleiche Vektor verschiedene darstellende Vektoren für verschiedene Basen . Zu einem Tensor lässt sich ebenso eine Darstellung finden, wenn man Basen von , und wählt: Sie hat zu jedem Tripel von Indizes ein „Matrixelement“, nämlich den Koeffizienten von . Das lässt sich dreidimensional darstellen, analog zur zweidimensionalen Darstellung für Matrizen.
Zurück zu NumPy & Co. In der Mathematik müssen wir uns zu einem Tensor merken, wie das Tensorprodukt zustande kam, um zu wissen, welche Variablen kovariant und welche kontravariant sind. Wählt man überall Basen, so verschwindet der Unterschied – man sieht nur noch die Koeffizienten. In NumPy arbeiten wir implizit mit Darstellungen in der Standardbasis von . Ist der Shape eines Arrays a z.B. a.shape == (4, 10, 2), so bedeutet dies mathematisch gesehen
Antwort: Wegen hat eine Basis der Form mit insgesamt Elementen, also ist a.reshape(-1, 2) == a.reshape(40, 2) derselbe Tensor, nur mit anderer Klammerung der Faktoren. Reshape ändert keine Daten, nur die Interpretation des Speichers.
Ausflug beendet.
Das Bild, das man sich für PyTorch merken sollte. Der Shape sagt nur, wie viele Zahlen in jede Richtung gehen, nicht, welche Richtung was bedeutet. Das muss man sich selbst merken, und genau an dieser Stelle entstehen die Fehler, die keinen Fehler werfen, sondern ohne Murren einen sinnlosen Output produzieren (der Autor hat einmal bei einem quadratischen Bild die beiden Achsen vertauscht und lange gesucht). Die Konvention, die fast überall in PyTorch gilt: Die erste Achse ist der Batch. Ein Tensor mit Shape (32, 784) sind 32 Datenpunkte mit je 784 Features, eine Zeile pro Datenpunkt. (32, 28, 28) sind 32 Bilder, also 32 Matrizen übereinandergestapelt; (32, 1, 28, 28) sind dieselben Bilder mit ausgeschriebener Kanalachse, (32, 3, 28, 28) mit drei Farbkanälen. Die Faltungsschichten im Kapitel „Faltung und Convolutional Neural Networks“ verlangen die Kanalachse, deshalb ist die übliche Form für Bildbatches; solange keine Faltung folgt, lässt man den einen Kanal oft weg. nn.Linear und fast alle anderen Layer wirken auf die letzte Achse und lassen die Batch-Achse in Ruhe. Das „fast“ betrifft die rekurrenten Schichten: nn.RNN, nn.LSTM, nn.GRU und nn.Transformer erwarten voreingestellt (seq, batch, feature) und legen den Batch damit auf die zweite Achse; batch_first=True stellt die Regel wieder her. Wer einen Tensor vor sich hat, sollte als Erstes sagen können, welche Achse der Batch ist und was die übrigen Achsen bedeuten; reshape und view ändern nur die Klammerung, nicht die Bedeutung.
Übung¶
Welchen Shape hat das Ergebnis? (a)
torch.zeros(4,3) @ torch.zeros(3,7), (b)x.sum(dim=0)fürxmit Shape , (c)x.unsqueeze(1)fürxmit Shape , (d)torch.zeros(4,1) + torch.zeros(1,6). Tipp: Bei (d) hilft die Broadcasting-Regel: Achsen der Länge 1 werden gedehnt.Ein Batch von 32 Graustufenbildern der Größe soll in eine Faltungsschicht. Welchen Shape hat er in der Konvention , und welchen nach
nn.Flatten()?
Antwortprobe: Das Produkt der Shape-Einträge ändert sich bei (2) nicht.