Je passais par cet exemple de modèle de langage LSTM sur github (lien) . Ce qu'il fait en général est assez clair pour moi. Mais j'ai encore du mal à comprendre ce que fait l'appel contiguous()
, ce qui se produit plusieurs fois dans le code.
Par exemple, à la ligne 74/75 du code d'entrée et des séquences cibles du LSTM sont créées. Les données (stockées dans ids
) sont bidimensionnelles, la première dimension étant la taille du lot.
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())
Donc, à titre d'exemple simple, lorsque vous utilisez la taille de lot 1 et seq_length
10 inputs
et targets
ressemble à ceci:
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]
Donc, en général, ma question est la suivante: qu'est-ce que c'est contiguous()
et pourquoi en ai-je besoin?
De plus, je ne comprends pas pourquoi la méthode est appelée pour la séquence cible et non pour la séquence d'entrée, car les deux variables sont composées des mêmes données.
Comment pourrait targets
être non contigu et inputs
toujours contigu?
EDIT:
J'ai essayé de ne pas appeler contiguous()
, mais cela conduit à un message d'erreur lors du calcul de la perte.
RuntimeError: invalid argument 1: input is not contiguous at .../src/torch/lib/TH/generic/THTensor.c:231
Il est donc évidemment contiguous()
nécessaire d' appeler cet exemple.
(Pour garder cela lisible, j'ai évité de publier le code complet ici, il peut être trouvé en utilisant le lien GitHub ci-dessus.)
Merci d'avance!
tldr; to the point summary
résumé concis au point.Réponses:
Il y a peu d'opérations sur Tensor dans PyTorch qui ne changent pas vraiment le contenu du tenseur, mais seulement comment convertir les indices en tenseur en octet. Ces opérations comprennent:
Par exemple: lorsque vous appelez
transpose()
, PyTorch ne génère pas de nouveau tenseur avec une nouvelle disposition, il modifie simplement les méta-informations dans l'objet Tensor afin que le décalage et la foulée soient pour une nouvelle forme. Le tenseur transposé et le tenseur d'origine partagent en effet la mémoire!x = torch.randn(3,2) y = torch.transpose(x, 0, 1) x[0, 0] = 42 print(y[0,0]) # prints 42
C'est là que le concept de contigu entre en jeu. Ci
x
- dessus est contigu mais cey
n'est pas parce que sa disposition de mémoire est différente d'un tenseur de même forme fait à partir de zéro. Notez que le mot «contigu» est un peu trompeur car ce n'est pas que le contenu du tenseur est réparti autour de blocs de mémoire déconnectés. Ici, les octets sont toujours alloués dans un bloc de mémoire mais l'ordre des éléments est différent!Lorsque vous appelez
contiguous()
, il fait en fait une copie de tensor afin que l'ordre des éléments soit le même que si un tenseur de même forme était créé à partir de zéro.Normalement, vous n'avez pas à vous en soucier. Si PyTorch attend un tenseur contigu mais si ce n'est pas le cas, vous obtiendrez
RuntimeError: input is not contiguous
et vous ajouterez simplement un appel àcontiguous()
.la source
contiguous()
par lui-même?permute
, qui peut également renvoyer un tenseur non "contigu".De la [documentation pytorch] [1]:
Où
contiguous
ici signifie non seulement contigu en mémoire, mais aussi dans le même ordre en mémoire que l'ordre des index: par exemple, faire une transposition ne change pas les données en mémoire, cela change simplement la carte des index aux pointeurs de mémoire, si vous alors l'appliquercontiguous()
changera les données en mémoire de sorte que la carte des index à l'emplacement mémoire soit celle canonique. [1]: http://pytorch.org/docs/master/tensors.htmlla source
tensor.contiguous () créera une copie du tenseur, et l'élément de la copie sera stocké dans la mémoire de manière contiguë. La fonction contiguous () est généralement requise lorsque nous transposons d'abord () un tenseur, puis le remodelons (visualisons). Commençons par créer un tenseur contigu:
Le retour stride () (3,1) signifie que: lors du déplacement le long de la première dimension à chaque pas (ligne par ligne), nous devons nous déplacer de 3 pas dans la mémoire. Lors du déplacement le long de la deuxième dimension (colonne par colonne), il faut se déplacer d'un pas dans la mémoire. Cela indique que les éléments du tenseur sont stockés de manière contiguë.
Maintenant, nous essayons d'appliquer les fonctions come au tenseur:
Ok, nous pouvons trouver que transpose (), narrow () et tenseur slicing, et expand () rendront le tenseur généré non contigu. Il est intéressant de noter que repeat () et view () ne le rend pas non-contigu. Alors maintenant, la question est: que se passe-t-il si j'utilise un tenseur non contigu?
La réponse est que la fonction view () ne peut pas être appliquée à un tenseur non contigu. Ceci est probablement dû au fait que view () nécessite que le tenseur soit stocké de manière contiguë afin de pouvoir effectuer un remodelage rapide en mémoire. par exemple:
nous obtiendrons l'erreur:
Pour résoudre cela, ajoutez simplement contiguous () à un tenseur non contigu, pour créer une copie contiguë puis appliquez view ()
la source
Comme dans la réponse précédente contigous () alloue des blocs de mémoire contigus , cela sera utile lorsque nous passons le tenseur au code backend c ou c ++ où les tenseurs sont passés en tant que pointeurs
la source
Les réponses acceptées étaient si bonnes, et j'ai essayé de duper
transpose()
l'effet de fonction. J'ai créé les deux fonctions permettant de vérifier lesamestorage()
et lecontiguous
.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")
J'ai vérifié et j'ai obtenu ce résultat sous forme de tableau:
Vous pouvez consulter le code du vérificateur ci-dessous, mais donnons un exemple lorsque le tenseur n'est pas contigu . Nous ne pouvons pas simplement faire appel
view()
à ce tenseur, nous en aurions besoinreshape()
ou nous pourrions aussi appeler.contiguous().view()
.De plus, il existe des méthodes qui créent des tenseurs contigus et non contigus à la fin. Il existe des méthodes qui peuvent fonctionner sur un même stockage , et certaines méthodes
flip()
qui créeront un nouveau stockage (lire: cloner le tenseur) avant le retour.Le code du vérificateur:
la source
D'après ce que je comprends, une réponse plus résumée:
À mon avis, le mot contigu est un terme déroutant / trompeur car dans des contextes normaux, il signifie lorsque la mémoire n'est pas répartie dans des blocs déconnectés (c'est-à-dire son "contigu / connecté / continu").
Certaines opérations peuvent avoir besoin de cette propriété contiguë pour une raison quelconque (efficacité très probable dans gpu, etc.).
Notez que
.view
c'est une autre opération qui peut provoquer ce problème. Regardez le code suivant que j'ai corrigé en appelant simplement contiguous (au lieu du problème de transposition typique qui le cause, voici un exemple qui est causé lorsqu'un RNN n'est pas satisfait de son entrée):Erreur que j'avais l'habitude d'obtenir:
Sources / Ressource:
la source