PyTorch - zusammenhängend ()


90

Ich habe dieses Beispiel eines LSTM-Sprachmodells auf Github (Link) durchgearbeitet . Was es im Allgemeinen macht, ist mir ziemlich klar. Aber ich habe immer noch Schwierigkeiten zu verstehen, was das Aufrufen contiguous()bewirkt, was im Code mehrmals vorkommt.

Beispielsweise werden in Zeile 74/75 der Codeeingabe und Zielsequenzen des LSTM erstellt. Daten (gespeichert in ids) sind zweidimensional, wobei die erste Dimension die Stapelgröße ist.

for i in range(0, ids.size(1) - seq_length, seq_length):
    # Get batch inputs and targets
    inputs = Variable(ids[:, i:i+seq_length])
    targets = Variable(ids[:, (i+1):(i+1)+seq_length].contiguous())

So wie ein einfaches Beispiel, bei der Verwendung von Losgröße 1 und seq_length10 inputsund targetssieht wie folgt aus :

inputs Variable containing:
0     1     2     3     4     5     6     7     8     9
[torch.LongTensor of size 1x10]

targets Variable containing:
1     2     3     4     5     6     7     8     9    10
[torch.LongTensor of size 1x10]

Im Allgemeinen ist meine Frage also, was macht contiguous()und warum brauche ich es?

Außerdem verstehe ich nicht, warum die Methode für die Zielsequenz und nicht für die Eingabesequenz aufgerufen wird, da beide Variablen aus denselben Daten bestehen.

Wie könnte es targetsnicht zusammenhängend und inputsdennoch zusammenhängend sein?

BEARBEITEN: Ich habe versucht, das Anrufen wegzulassen contiguous(), aber dies führt zu einer Fehlermeldung bei der Berechnung des Verlusts.

RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231

Daher ist es offensichtlich contiguous()notwendig, dieses Beispiel aufzurufen .

(Um dies lesbar zu halten, habe ich es vermieden, den vollständigen Code hier zu veröffentlichen. Er kann über den obigen GitHub-Link gefunden werden.)

Danke im Voraus!


Ein aussagekräftigerer Titel wäre nützlich. Ich schlage vor, Sie verbessern den Titel oder schreiben zumindest eine tldr; to the point summarymit einer kurzen Zusammenfassung des Punktes.
Charlie Parker


Antworten:


186

Es gibt nur wenige Operationen für Tensor in PyTorch, die den Inhalt des Tensors nicht wirklich ändern, sondern nur, wie Indizes in Tensor in Byte-Position konvertiert werden. Diese Operationen umfassen:

narrow(), view(), expand()Undtranspose()

Beispiel: Wenn Sie aufrufen transpose(), generiert PyTorch keinen neuen Tensor mit neuem Layout, sondern ändert lediglich die Metainformationen im Tensor-Objekt, sodass Versatz und Schritt für eine neue Form gelten. Der transponierte Tensor und der ursprüngliche Tensor teilen tatsächlich die Erinnerung!

x = torch.randn(3,2)
y = torch.transpose(x, 0, 1)
x[0, 0] = 42
print(y[0,0])
# prints 42

Hier kommt das Konzept des Zusammenhängens ins Spiel . Oben xist zusammenhängend, aber ynicht, weil sich sein Speicherlayout von einem Tensor derselben Form unterscheidet, der von Grund auf neu erstellt wurde. Beachten Sie, dass das Wort "zusammenhängend" etwas irreführend ist, da der Inhalt des Tensors nicht auf nicht verbundene Speicherblöcke verteilt ist. Hier werden Bytes noch in einem Speicherblock zugeordnet, aber die Reihenfolge der Elemente ist unterschiedlich!

Wenn Sie aufrufen contiguous(), wird tatsächlich eine Kopie des Tensors erstellt, sodass die Reihenfolge der Elemente dieselbe ist, als ob der Tensor derselben Form von Grund auf neu erstellt worden wäre.

Normalerweise brauchen Sie sich darüber keine Sorgen zu machen. Wenn PyTorch einen zusammenhängenden Tensor erwartet, dies aber nicht der Fall RuntimeError: input is not contiguousist, erhalten Sie einen Anruf und fügen ihn einfach hinzu contiguous().


Ich bin gerade wieder darauf gestoßen. Ihre Erklärung ist sehr gut! Ich frage mich nur: Wenn die Blöcke im Speicher nicht weit verbreitet sind, was ist das Problem mit einem Speicherlayout, das sich "von einem Tensor derselben Form unterscheidet, der von Grund auf neu erstellt wurde" ? Warum ist es nur für einige Operationen erforderlich, zusammenhängend zu sein?
MBT

4
Ich kann dies nicht definitiv beantworten, aber ich vermute, dass ein Teil des PyTorch-Codes eine vektorisierte Hochleistungsimplementierung der in C ++ implementierten Operationen verwendet und dieser Code keine willkürlichen Offsets / Schritte verwenden kann, die in den Metainformationen von Tensor angegeben sind. Dies ist jedoch nur eine Vermutung.
Shital Shah

1
Warum konnte der Angerufene nicht einfach contiguous()von selbst anrufen ?
information_interchange

Möglicherweise, weil Sie es nicht zusammenhängend wollen und es immer schön ist, die Kontrolle darüber zu haben, was Sie tun.
Shivam13juna

2
Eine andere beliebte Tensoroperation ist die permute, die auch einen nicht "zusammenhängenden" Tensor zurückgeben kann.
Oleg

31

Aus der [Pytorch-Dokumentation] [1]:

zusammenhängend () → Tensor

Returns a contiguous tensor containing the same data as self 

Tensor. Wenn der Selbsttensor zusammenhängend ist, gibt diese Funktion den Selbsttensor zurück.

Wo contiguoushier nicht nur zusammenhängend im Speicher bedeutet, sondern auch in derselben Reihenfolge im Speicher wie die Indexreihenfolge: Wenn Sie beispielsweise eine Transposition durchführen, werden die Daten im Speicher nicht geändert, sondern wenn Sie dann einfach die Zuordnung von Indizes zu Speicherzeigern ändern Beim Anwenden contiguous()werden die Daten im Speicher so geändert, dass die Zuordnung von Indizes zum Speicherort kanonisch ist. [1]: http://pytorch.org/docs/master/tensors.html


1
Vielen Dank für Ihre Antwort! Können Sie mir sagen, warum / wann die Daten zusammenhängend sein müssen? Ist es nur Leistung oder ein anderer Grund? Benötigt PyTorch für einige Vorgänge zusammenhängende Daten? Warum müssen Ziele zusammenhängend sein und Eingaben nicht?
MBT

Es ist nur für die Leistung. Ich weiß nicht, warum die Codes dies für Ziele tun, aber nicht für Eingaben.
Patapouf_ai

2
Anscheinend erfordert Pytorch, dass die Ziele im Verlust im Speicher zusammenhängend sind, aber die Eingaben des Neuralnetzes müssen diese Anforderung nicht erfüllen.
Patapouf_ai

2
Grosses Dankeschön! Ich denke, das macht für mich Sinn. Ich habe festgestellt, dass contiguous () auch auf die Ausgabedaten (die natürlich früher die Eingabe waren) in der Vorwärtsfunktion angewendet wird, sodass sowohl Ausgaben als auch Ziele bei der Berechnung des Verlusts zusammenhängend sind. Danke vielmals!
MBT

1
@patapouf_ai Nein. Ihre Erklärung ist falsch. Wie in der richtigen Antwort ausgeführt, geht es überhaupt nicht um zusammenhängende Speicherblöcke.
Akaisteph7

14

tensor.contiguous () erstellt eine Kopie des Tensors, und das Element in der Kopie wird zusammenhängend im Speicher gespeichert. Die zusammenhängende () Funktion wird normalerweise benötigt, wenn wir zuerst einen Tensor transponieren () und ihn dann umformen (anzeigen). Lassen Sie uns zunächst einen zusammenhängenden Tensor erstellen:

aaa = torch.Tensor( [[1,2,3],[4,5,6]] )
print(aaa.stride())
print(aaa.is_contiguous())
#(3,1)
#True

Die Rückgabe von stride () (3,1) bedeutet: Wenn wir uns bei jedem Schritt (Zeile für Zeile) entlang der ersten Dimension bewegen, müssen wir 3 Schritte im Speicher verschieben. Wenn wir uns entlang der zweiten Dimension (Spalte für Spalte) bewegen, müssen wir 1 Schritt im Speicher verschieben. Dies zeigt an, dass die Elemente im Tensor zusammenhängend gespeichert sind.

Jetzt versuchen wir, Come-Funktionen auf den Tensor anzuwenden:

bbb = aaa.transpose(0,1)
print(bbb.stride())
print(bbb.is_contiguous())

#(1, 3)
#False


ccc = aaa.narrow(1,1,2)   ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())

#(3, 1)
#False


ddd = aaa.repeat(2,1)   # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())

#(3, 1)
#True


## expand is different from repeat.
## if a tensor has a shape [d1,d2,1], it can only be expanded using "expand(d1,d2,d3)", which
## means the singleton dimension is repeated d3 times
eee = aaa.unsqueeze(2).expand(2,3,3)
print(eee.stride())
print(eee.is_contiguous())

#(3, 1, 0)
#False


fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())

#(24, 2, 1)
#True

Ok, wir können feststellen, dass transponieren (), schmales () und Tensor-Schneiden und expandieren () den erzeugten Tensor nicht zusammenhängend machen. Interessanterweise macht repeat () und view () es nicht uneinheitlich. Die Frage ist nun: Was passiert, wenn ich einen nicht zusammenhängenden Tensor verwende?

Die Antwort ist, dass die view () -Funktion nicht auf einen nicht zusammenhängenden Tensor angewendet werden kann. Dies liegt wahrscheinlich daran, dass view () erfordert, dass der Tensor zusammenhängend gespeichert wird, damit er sich schnell im Speicher umformen kann. z.B:

bbb.view(-1,3)

Wir werden den Fehler bekommen:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-63-eec5319b0ac5> in <module>()
----> 1 bbb.view(-1,3)

RuntimeError: invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Call .contiguous() before .view(). at /pytorch/aten/src/TH/generic/THTensor.cpp:203

Um dies zu lösen, fügen Sie einfach contiguous () zu einem nicht zusammenhängenden Tensor hinzu, um eine zusammenhängende Kopie zu erstellen, und wenden Sie dann view () an.

bbb.contiguous().view(-1,3)
#tensor([[1., 4., 2.],
        [5., 3., 6.]])

10

Da in der vorherigen Antwort contigous () zusammenhängende Speicherblöcke zugewiesen wurden , ist es hilfreich, wenn wir den Tensor an c- oder c ++ - Backend-Code übergeben, in dem Tensoren als Zeiger übergeben werden


3

Die akzeptierten Antworten waren so großartig und ich habe versucht, den transpose()Funktionseffekt zu täuschen . Ich habe die beiden Funktionen erstellt, mit denen das samestorage()und das überprüft werden können contiguous.

def samestorage(x,y):
    if x.storage().data_ptr()==y.storage().data_ptr():
        print("same storage")
    else:
        print("different storage")
def contiguous(y):
    if True==y.is_contiguous():
        print("contiguous")
    else:
        print("non contiguous")

Ich habe dieses Ergebnis als Tabelle überprüft und erhalten:

Funktionen

Sie können den Prüfcode unten überprüfen, aber geben wir ein Beispiel, wenn der Tensor nicht zusammenhängend ist . Wir können view()diesen Tensor nicht einfach anrufen , wir würden ihn brauchen reshape()oder wir könnten auch anrufen .contiguous().view().

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.view(6) # RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
  
x = torch.randn(3,2)
y = x.transpose(0, 1)
y.reshape(6)

x = torch.randn(3,2)
y = x.transpose(0, 1)
y.contiguous().view(6)

Weiterhin ist zu beachten, dass es Methoden gibt, die am Ende zusammenhängende und nicht zusammenhängende Tensoren erzeugen . Es gibt Methoden, die auf demselben Speicher ausgeführt werden können , und einige Methoden flip()erstellen einen neuen Speicher (lesen: Klonen des Tensors) vor der Rückkehr.

Der Prüfcode:

import torch
x = torch.randn(3,2)
y = x.transpose(0, 1) # flips two axes
print("\ntranspose")
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nnarrow")
x = torch.randn(3,2)
y = x.narrow(0, 1, 2) #dim, start, len  
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\npermute")
x = torch.randn(3,2)
y = x.permute(1, 0) # sets the axis order
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nview")
x = torch.randn(3,2)
y=x.view(2,3)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nreshape")
x = torch.randn(3,2)
y = x.reshape(6,1)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nflip")
x = torch.randn(3,2)
y = x.flip(0)
print(x)
print(y)
contiguous(y)
samestorage(x,y)

print("\nexpand")
x = torch.randn(3,2)
y = x.expand(2,-1,-1)
print(x)
print(y)
contiguous(y)
samestorage(x,y) 

0

Soweit ich das verstehe, eine zusammengefasstere Antwort:

Zusammenhängend ist der Begriff, der verwendet wird, um anzuzeigen, dass das Speicherlayout eines Tensors nicht mit seinen angekündigten Metadaten oder Forminformationen übereinstimmt.

Meiner Meinung nach ist das Wort "zusammenhängend" ein verwirrender / irreführender Begriff, da es in normalen Kontexten bedeutet, dass das Gedächtnis nicht in getrennten Blöcken verteilt ist (dh "zusammenhängend / verbunden / kontinuierlich").

Einige Vorgänge benötigen diese zusammenhängende Eigenschaft möglicherweise aus irgendeinem Grund (höchstwahrscheinlich Effizienz in GPU usw.).

Beachten Sie, dass dies .viewein weiterer Vorgang ist, der dieses Problem verursachen kann. Schauen Sie sich den folgenden Code an, den ich durch einfaches Aufrufen von zusammenhängend behoben habe (anstelle des typischen Transponierungsproblems, das ihn hier verursacht, ist dies ein Beispiel, das verursacht wird, wenn ein RNN mit seiner Eingabe nicht zufrieden ist):

        # normal lstm([loss, grad_prep, train_err]) = lstm(xn)
        n_learner_params = xn_lstm.size(1)
        (lstmh, lstmc) = hs[0] # previous hx from first (standard) lstm i.e. lstm_hx = (lstmh, lstmc) = hs[0]
        if lstmh.size(1) != xn_lstm.size(1): # only true when prev lstm_hx is equal to decoder/controllers hx
            # make sure that h, c from decoder/controller has the right size to go into the meta-optimizer
            expand_size = torch.Size([1,n_learner_params,self.lstm.hidden_size])
            lstmh, lstmc = lstmh.squeeze(0).expand(expand_size).contiguous(), lstmc.squeeze(0).expand(expand_size).contiguous()
        lstm_out, (lstmh, lstmc) = self.lstm(input=xn_lstm, hx=(lstmh, lstmc))

Fehler, den ich bekommen habe:

RuntimeError: rnn: hx is not contiguous


Quellen / Ressource:

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.