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_length
10 inputs
und targets
sieht 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 targets
nicht zusammenhängend und inputs
dennoch 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!
tldr; to the point summary
mit einer kurzen Zusammenfassung des Punktes.Antworten:
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:
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
x
ist zusammenhängend, abery
nicht, 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 contiguous
ist, erhalten Sie einen Anruf und fügen ihn einfach hinzucontiguous()
.quelle
contiguous()
von selbst anrufen ?permute
, die auch einen nicht "zusammenhängenden" Tensor zurückgeben kann.Aus der [Pytorch-Dokumentation] [1]:
Wo
contiguous
hier 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 Anwendencontiguous()
werden die Daten im Speicher so geändert, dass die Zuordnung von Indizes zum Speicherort kanonisch ist. [1]: http://pytorch.org/docs/master/tensors.htmlquelle
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:
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:
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:
Wir werden den Fehler bekommen:
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.
quelle
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
quelle
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 dassamestorage()
und das überprüft werden könnencontiguous
.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:
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 brauchenreshape()
oder wir könnten auch anrufen.contiguous().view()
.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:
quelle
Soweit ich das verstehe, eine zusammengefasstere Antwort:
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
.view
ein 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):Fehler, den ich bekommen habe:
Quellen / Ressource:
quelle