import torch
= torch.Tensor([4,2])
a = torch.Tensor([2,-1])
b
print(a)
print(b)
tensor([4., 2.])
tensor([ 2., -1.])
PyTorch ist - wie TensorFlow - eine Open-Source-Bibliothek für Maschinelles Lernen in Python. PyTorch wurde ursprünglich von der KI-Gruppe bei Facebook, genannt FAIR - Facebook AI Research (oder oft nur “Facebook AI”) - entwickelt. PyTorch tritt in direkte Konkurrenz zu Keras (Google) und soll daher in zwei Anhängen vorgestellt werden.
Zentrales Werkzeug in PyTorch sind natürlich Tensoren. Im Gegensatz zu Keras, das sich stark auf die Bibliothek NumPy stützt, hat PyTorch eigene Mechanismen, um effizient mit Tensoren zu arbeiten. Dieses Kapitel erläutert daher die Handhabe von Daten, insbesondere von Tensoren, in PyTorch. Das zweite Kapitel in Anhang F geht dann auf das Thema Neuronale Netze ein.
Damit Sie PyTorch nutzen können, müssen Sie die Bibliothek auf Ihrem Rechner und in Ihrer entsprechenden Umgebung (falls Sie Umgebungen nutzen) installieren.
Anweisungen dazu finden Sie unter PyTorch - GET STARTED
Wenn Sie z.B. Anaconda nutzen, reicht dieses Kommando:
conda install pytorch torchvision -c pytorch
Eventuell müssen Sie andere Bibliotheken, wie z.B. NumPy updaten, damit PyTorch läuft. Das machen Sie einfach mit
conda install numpy
PyTorch hat seine eigenen Versionen von vielen NumPy-Datenformaten und Operationen - hier konzentrieren wir uns auf Tensoren. Diese Ausführungen gehen davon aus, dass Sie vertraut sind mit Tensoren, NumPy-Arrays und entsprechenden Operationen in NumPy.
Eine schöne Überblicksseite zu PyTorch-Tensoren finden Sie unter PyTorch - Basic operations
API: torch.Tensor
Tensor
Statt eines NumPy-Arrays nutzen wir in PyTorch die dortigen Tensor-Objekte. Dazu muss man das Paket torch
importieren.
Hier sehen wir einfache 1-dimensionale Arrays.
import torch
= torch.Tensor([4,2])
a = torch.Tensor([2,-1])
b
print(a)
print(b)
tensor([4., 2.])
tensor([ 2., -1.])
Es gilt die Notation mit eckigen Klammern:
0] a[
tensor(4.)
Man beachte, dass man durch den obigen Zugriff wieder einen Tensor erhält, nicht einen Skalar. Will man den eigentlichen Skalar-Wert erhalten, benutzt man item
.
0].item() a[
4.0
Es gibt wie in TensorFlow auch die Shape-Eigenschaft. Hier haben wir 1-dimensionale Tensoren der Länge 2.
print(a.shape)
print(b.shape)
torch.Size([2])
torch.Size([2])
Für Einzelwerte bekommt man einen “Null-Tensor”:
0].shape a[
torch.Size([])
Man kann mit rand
schnell Tensoren mit Zufallswerten erzeugen. Hier übergibt man die gewünschte Shape.
API: torch.rand
= torch.rand(2,3)
c c
tensor([[0.2032, 0.0992, 0.0777],
[0.1460, 0.8337, 0.7634]])
Mit den Befehlen ones
, zeros
und eye
kann man einfach Standardmatrizen erzeugen.
Mit ones
erzeugt man eine komplett mit Einsen gefüllte Matrix.
2, 3) torch.ones(
tensor([[1., 1., 1.],
[1., 1., 1.]])
Bei zeros
entsprechend mit Nullen:
2, 3) torch.zeros(
tensor([[0., 0., 0.],
[0., 0., 0.]])
Mit eye
(wegen des Gleichlauts mit dem “I” in “Identity”) erzeugt man eine Identitätsmatrix (muss quadratisch sein, d.h. Zeilenzahl = Spaltenzahl).
4) torch.eye(
tensor([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
Mit arange
kann man einen Tensor mit den Werten 1, 2, 3, etc. erzeugen.
Wichtig: Der standardmäßig verwendete Datentyp der Werte ist Integer, nicht Float (s.u.).
9) torch.arange(
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
Möchte man Float-Werte erzwingen, kann man dies mit einem Paramter tun (Möglichkeit 1):
9, dtype=torch.float32) torch.arange(
tensor([0., 1., 2., 3., 4., 5., 6., 7., 8.])
Sie können den Grenzwert auch als Float kennzeichnen (Möglichkeit 2):
9.0) torch.arange(
tensor([0., 1., 2., 3., 4., 5., 6., 7., 8.])
Bei der Aufzählung können Sie auch mit Start- und Endwert (letzterer ist exklusiv) arbeiten.
5, 10) torch.arange(
tensor([5, 6, 7, 8, 9])
Boolesche Operationen/Filter sind möglich:
= torch.Tensor([0.7, 0, 0.3, 0.6])
d = d > 0.5
e e
tensor([ True, False, False, True])
Man kann das benutzen, um einen Tensor zu filtern:
> 0.5] d[d
tensor([0.7000, 0.6000])
Jetzt kann man die booleschen Werte in dem Tensor wieder in Zahlen umwandeln:
int() e.
tensor([1, 0, 0, 1], dtype=torch.int32)
view
Man kann sich Tensoren so vorstellen, dass alle Werte intern in einer langen, 1-dimensionalen Liste gespeichert sind (bei einer Matrix: alle Zeilen hintereinander gereiht). Die tatsächliche Shape wird sozusagen “oben drauf” gepackt. Eine Liste von 6 Zahlen kann ein Vektor der Länge 6 sein, eine 2x3-Matrix oder eine 3x2-Matrix. So kann man sich das Konzept des “Reshape” vorstellen. Man ändert einfach die Shape der gleichen linearen Liste von Werten.
Das Verändern der Shape (in NumPy die Methode reshape
) funktioniert in PyTorch mit der Tensor-Methode view, die nicht-destruktiv ist, d.h. sie verändert den Tensor nicht.
Als Beispiel nehmen wir eine 3x2-Matrix.
= torch.Tensor([[1, 2], [3, 4], [5, 6]])
a a
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
Diese möchten wir in einen Vektor der Länge 6 umwandeln.
6) a.view(
tensor([1., 2., 3., 4., 5., 6.])
Alternativ können wir eine 3x2-Matrix erzeugen.
2, 3) a.view(
tensor([[1., 2., 3.],
[4., 5., 6.]])
Allerdings ist view nicht-destruktiv, so dass der ursprüngliche Tensor unverändert bleibt.
a
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
Eine -1 bedeutet, dass es egal ist, wie viele Elemente die Dimension enthält.
-1) a.view(
tensor([1., 2., 3., 4., 5., 6.])
Bei mehreren Dimension, wird die unspezifizierte Dimension ausgerechnet.
-1, 3) a.view(
tensor([[1., 2., 3.],
[4., 5., 6.]])
3, -1) a.view(
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
Im Extremfall kann es sein, dass die unspezifizierte Dimension die Länge 1 hat, d.h. man fügt einfach äußere Klammern hinzu. Das kommt tatsächlich in der Praxis häufiger vor, dass man z.B. nur einen (Test-)Tensor hat, aber eine Liste von Tensoren als Input benötigt. Dann fügt man einfach mit view in Kombination mit -1 eine Dimension hinzu (welche die Liste repräsentiert).
-1, 6) a.view(
tensor([[1., 2., 3., 4., 5., 6.]])
-1, 6).shape a.view(
torch.Size([1, 6])
Mit der Kombination von arange
und view
kann man schnell Test-Tensoren erzeugen.
12).view(2,6) torch.arange(
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
12).view(3,4) torch.arange(
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
stack
und cat
Beim Stapeln können wir mehrere Tensoren der gleichen Form zu einem neuen Tensor zusammenfügen. Der neue Tensor hat (im Gegensatz zur Konkatenation) hat eine Dimension mehr. Die neue Dimension steht an erster Stelle in der resultierenden Shape.
API: torch.stack
= torch.arange(5)
a = torch.arange(5, 10)
b = torch.arange(10, 15)
c a, b, c
(tensor([0, 1, 2, 3, 4]),
tensor([5, 6, 7, 8, 9]),
tensor([10, 11, 12, 13, 14]))
Beim Stapeln werden eine Reihe von \(n\) Tensoren zusammengefügt zu einem neuen Tensor. Der neue Tensor hat eine neue Dimension der Größe \(n\). Dabei müssen alle Tensoren die gleiche Form (shape) haben.
Im Beispiel werden 3 Vektoren der Länge 5 zusammengefügt zu einer 3x5-Matrix.
= torch.stack((a, b, c))
d print(d.shape)
d
torch.Size([3, 5])
tensor([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
Im nächsten Beispiel werden 3 Matrizen der Form 2x2 gestapelt in einem 3x2x2-Tensor.
= torch.arange(4).view(2, 2)
a = torch.arange(4, 8).view(2, 2)
b = torch.arange(8, 12).view(2, 2)
c a, b, c
(tensor([[0, 1],
[2, 3]]),
tensor([[4, 5],
[6, 7]]),
tensor([[ 8, 9],
[10, 11]]))
= torch.stack((a, b, c))
d print(d.shape)
d
torch.Size([3, 2, 2])
tensor([[[ 0, 1],
[ 2, 3]],
[[ 4, 5],
[ 6, 7]],
[[ 8, 9],
[10, 11]]])
Bei der Konkatenation fügen wir mehrere Tensoren entlang einer ihrer Dimensionen aneinander. Die Dimension, entlang welcher zusammengefügt wird, erweitert sich somit. Alle anderen Dimensionen müssen die gleich Größe aufweisen. Der resultierende Tensor hat (im Gegensatz zum Stapeln) die gleiche Anzahl von Dimensionen wie die ursprünglichen Tensoren.
AP: torch.cat
= torch.arange(6).view(3, 2)
a = torch.arange(6, 12).view(3, 2)
b print(a)
print(b)
tensor([[0, 1],
[2, 3],
[4, 5]])
tensor([[ 6, 7],
[ 8, 9],
[10, 11]])
Standardmäßig wird entlang Dimension 0 (= Zeilen) konkateniert. Aus zwei 3x2-Matrizen wird also eine 6x2-Matrix.
torch.cat((a, b))
tensor([[ 0, 1],
[ 2, 3],
[ 4, 5],
[ 6, 7],
[ 8, 9],
[10, 11]])
Wenn man entlang Dimension 1 (= Spalten) konkateniert, wird aus zwei 3x2-Matrizen eine 3x4-Matrix.
=1) torch.cat((a, b), dim
tensor([[ 0, 1, 6, 7],
[ 2, 3, 8, 9],
[ 4, 5, 10, 11]])
= torch.arange(8).view(4, 2)
c c
tensor([[0, 1],
[2, 3],
[4, 5],
[6, 7]])
Wenn man entlang Dimension d konkateniert, müssen alle anderen Größen der Tensoren gleich sein. Beispiel: Konkateniert man entlang Dimension 0, dann muss Dimension 1 gleich sein.
Hier konkatenieren wir eine 3x2-Matrix mit einer 4x2-Matrix entlang Dimension 0 und erhalten eine 7x2.Matrix.
= torch.cat((a, c))
d print(a.shape)
print(c.shape)
print(d.shape)
d
torch.Size([3, 2])
torch.Size([4, 2])
torch.Size([7, 2])
tensor([[0, 1],
[2, 3],
[4, 5],
[0, 1],
[2, 3],
[4, 5],
[6, 7]])
matmul
Besonders wichtig ist die Funktion matmul
. Stattdessen kann man auch den Operator @
verwenden, z.B. bei
a @ b
Wir listen hier aber sämliche relevante Funktionen auf.
Hier das Skalarprodukt zweier Vektoren (engl. dot product):
= torch.Tensor([1, 2])
a = torch.Tensor([3, 4]) b
Wir nutzen die Funktion dot
:
a.dot(b)
tensor(11.)
Tensor a
wird dabei nicht verändert:
a
tensor([1., 2.])
Alternativ auch so:
torch.dot(a, b)
tensor(11.)
Man kann auch die Funktion matmul
nutzen, die eigentlich für Matrizen gedacht ist:
a.matmul(b)
tensor(11.)
torch.matmul(a, b)
tensor(11.)
Hier kann man alternativ den Operator @
einsetzen:
@ b a
tensor(11.)
Am üblichsten sind matmul
und @
(siehe unten).
= torch.arange(4, dtype=torch.float32).view(2,2)
c = torch.arange(5, 9, dtype=torch.float32).view(2,2)
d print(c)
print(d)
tensor([[0., 1.],
[2., 3.]])
tensor([[5., 6.],
[7., 8.]])
Für die Multiplikation eines Tensoren mit einem Skalar benutzt man Broadcasting:
10 * a
tensor([10., 20.])
10 * c
tensor([[ 0., 10.],
[20., 30.]])
Mit der Funktion matmul
kann man Tensoren miteinander multiplizieren. Der Name kommt man “matrix multiply”.
c.matmul(d)
tensor([[ 7., 8.],
[31., 36.]])
c.matmul(a)
tensor([2., 8.])
Am schönsten lässt sich die Multiplikation mit dem Operator @
schreiben:
@ a c
tensor([2., 8.])
Es gibt noch zwei Funktionen für Spezialfälle, am einfachsten benutzt man aber immer matmul
.
Zwei Matrizen multipliziert man mit mm
(für “matrix-matrix”).
c.mm(d)
tensor([[ 7., 8.],
[31., 36.]])
Eine Matrix und ein Vektor multipliziert man mit mv
:
print(c.shape)
print(a.shape)
torch.mv(c, a)
torch.Size([2, 2])
torch.Size([2])
tensor([2., 8.])