Читать книгу PyTorch für Deep Learning - Ian Pointer - Страница 38
Tensoroperationen
ОглавлениеWenn Sie sich die PyTorch-Dokumentation (https://oreil.ly/1Ev0-) anschauen, werden Sie feststellen, dass es eine Vielzahl an Funktionen gibt, die Sie auf Tensoren anwenden können – alles vom Finden des Maximalwerts bis zur Anwendung einer Fourier-Transformation. Im vorliegenden Buch müssen Sie nicht alle Funktionen kennen, um die Bilder, Texte und Töne in Tensoren zu verwandeln und sie für unsere Zwecke zu manipulieren. Sie werden dennoch einige benötigen. Ich empfehle Ihnen auf jeden Fall, spätestens aber nach dem Lesen dieses Buchs, einen Blick in die Dokumentation zu werfen. Gehen wir nun alle Funktionen durch, die in den kommenden Kapiteln verwendet werden.
Zum einen müssen wir häufig das Element mit dem Maximalwert in einem Tensor sowie den korrespondierenden Index des Elements finden (da dieses oft der Kategorie angehört, für die sich das neuronale Netz in seiner endgültigen Vorhersage entschieden hat). Dies können wir mit den Funktionen max() und argmax() vornehmen. Ebenso können wir die Funktion item() verwenden, um einen Wert aus einem eindimensionalen Tensor zu extrahieren.
torch.rand(2,2).max()
> tensor(0.4726)
torch.rand(2,2).max().item()
> 0.8649941086769104
In manchen Situationen möchten wir den Typ eines Tensors ändern, zum Beispiel von einem LongTensor in einen FloatTensor. Dies können wir mit der Funktion to() erreichen:
long_tensor = torch.tensor([[0,0,1],[1,1,1],[0,0,0]])
long_tensor.type()
> 'torch.LongTensor'
float_tensor = torch.tensor([[0,0,1],[1,1,1],[0,0,0]]).to(dtype=torch.float32)
float_tensor.type()
> 'torch.FloatTensor'
Die meisten Funktionen, die auf einem Tensor operieren und einen Tensor zurückgeben, erzeugen gleichzeitig einen neuen Tensor, um das Ergebnis zu speichern. Wenn Sie jedoch Arbeitsspeicher sparen wollen, vergewissern Sie sich, ob eine In-Place-Funktion definiert ist. Diese besitzt den gleichen Namen wie die ursprüngliche Funktion, lediglich mit einem angehängten Unterstrich (_).
random_tensor = torch.rand(2,2)
random_tensor.log2()
>tensor([[-1.9001, -1.5013],
[-1.8836, -0.5320]])
random_tensor.log2_()
> tensor([[-1.9001, -1.5013],
[-1.8836, -0.5320]])
Eine weitere gängige Operation ist die Umformung eines Tensors. Dies kann oft vonnöten sein, weil Ihre neuronale Netzwerkschicht eine etwas andere Form der Eingabe erfordert als die, die Sie gerade einspeisen. Zum Beispiel ist der Datensatz des Modified National Institute of Standards and Technology (MNIST) mit handgeschriebenen Ziffern eine Sammlung von 28 × 28 Pixel großen Bildern. Er besteht jedoch aus Arrays der Länge 784. Um die Netzwerke, die wir aufbauen, zu verwenden, müssen wir diese wieder in 1 × 28 × 28-Tensoren umwandeln (die führende 1 entspricht der Anzahl der Farbkanäle – für gewöhnlich Rot, Grün und Blau –, da die MNIST-Ziffern aber nur in Graustufen gehalten sind, haben wir lediglich einen Kanal). Die Umformung können wir entweder mit der Funktion view() oder mit reshape() durchführen:
flat_tensor = torch.rand(784)
viewed_tensor = flat_tensor.view(1,28,28)
viewed_tensor.shape
> torch.Size([1, 28, 28])
reshaped_tensor = flat_tensor.reshape(1,28,28)
reshaped_tensor.shape
> torch.Size([1, 28, 28])
Dabei ist es wichtig, dass Sie den Tensor in eine Form überführen, bei der er die gleiche Anzahl an Gesamtelementen wie sein Original umfasst. Wenn Sie es mit flat_tensor.reshape(3,28,28) versuchen, werden Sie einen Fehler wie diesen sehen:
RuntimeError Traceback (most recent call last)
<ipython-input-26-774c70ba5c08> in <module>()
----> 1 flat_tensor.reshape(3,28,28)
RuntimeError: shape '[3, 28, 28]' is invalid for input of size 784
Sie fragen sich jetzt vielleicht, worin der Unterschied zwischen view() und reshape() besteht. Die Antwort ist, dass view() als ein Abbild des ursprünglichen Tensors funktioniert – wenn also die zugrunde liegenden Daten geändert werden, wird sich auch das Abbild ändern (und umgekehrt). Allerdings kann view() Fehler verursachen, wenn das erforderliche Abbild nicht kontinuierlich ist; d.h., wenn der Tensor nicht denselben Speicherblock teilt, den er belegen würde, wenn ein neuer Tensor mit der erforderlichen Form von Grund auf erstellt werden würde. Wenn dies geschieht, müssen Sie tensor.contiguous() aufrufen, bevor Sie view() verwenden können. Da reshape() jedoch all das hinter den Kulissen erledigt, empfehle ich im Allgemeinen die Verwendung von reshape() anstelle von view().
Schließlich müssen Sie möglicherweise die Dimensionen eines Tensors neu anordnen. Dies wird Ihnen wahrscheinlich bei Bildern begegnen, da diese oft in Form von [Höhe, Breite, Kanal]-Tensoren gespeichert sind. PyTorch sieht jedoch die Anordnung [Kanal, Höhe, Breite] vor, um diese zu verarbeiten. Sie können die Funktion permute() nutzen, um diesem Problem auf eine ziemlich einfache Art und Weise zu begegnen:
hwc_tensor = torch.rand(640, 480, 3)
chw_tensor = hwc_tensor.permute(2,0,1)
chw_tensor.shape
> torch.Size([3, 640, 480])
Hier haben wir gerade die Funktion permute auf einen [640,480,3]-Tensor angewendet, wobei die Argumente den Indizes der Dimensionen des Tensors entsprechen. Dadurch haben wir erreicht, dass die letzte Dimension (2, da der Index bei 0 beginnt) an der ersten Stelle unseres Tensors steht, gefolgt von den restlichen zwei Dimensionen in ihrer ursprünglichen Reihenfolge.