Je passais en revue cet exemple de modèle de langage LSTM sur github (lien) . Ce que cela fait en général est assez clair pour moi. Mais j'ai encore du mal à comprendre ce que fait l'appel contiguous()
, qui se produit plusieurs fois dans le code.
Par exemple, les lignes 74/75 du code et les 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())
Ainsi, à 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 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 mais pas pour la séquence en entrée car les deux variables sont constituées des mêmes données.
Comment targets
pourrait-il être non contigu et inputs
toujours contigu?
EDIT: J'ai essayé de laisser de côté l'appelant contiguous()
, mais cela entraîne 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 nécessaire d'appeler contiguous()
dans cet exemple.
(Pour que cela reste lisible, j’ai évité de poster le code complet ici, vous pouvez le trouver en utilisant le lien GitHub ci-dessus.)
Merci d'avance!
Il existe peu d'opérations sur Tenseur dans PyTorch qui ne modifient pas vraiment le contenu du tenseur, mais indiquent simplement comment convertir les index en tenseur en emplacement d'octet. Ces opérations comprennent:
narrow()
,view()
,expand()
ettranspose()
Par exemple: Lorsque vous appelez transpose()
, PyTorch ne génère pas de nouveau tenseur avec une nouvelle présentation, il modifie simplement les méta-informations de l'objet Tensor. Ainsi, offset et stride correspondent à la nouvelle forme. Le tenseur transposé et le tenseur d'origine partagent effectivement 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 contig entre en jeu. Ci-dessus x
est contigu, mais y
ne l’est pas parce que sa structure de mémoire est différente de celle d’un tenseur de même forme créé à partir de rien. Notez que le mot "contigu" est un peu trompeur car ce n’est pas que le contenu du tenseur s’étale 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 crée une copie du tenseur afin que l'ordre des éléments soit identique à celui d'un tenseur de même forme créé à partir de rien.
Normalement, vous n'avez pas besoin de vous inquiéter à ce sujet. Si PyTorch s'attend à un tenseur contigu, mais si ce n'est pas le cas, vous obtiendrez RuntimeError: input is not contiguous
, puis vous ajouterez simplement un appel à contiguous()
.
De la documentation de pytorch :
contigu () → Tenseur
Returns a contiguous tensor containing the same data as self
tenseur. Si tenseur de soi est contigu, cette fonction renvoie le tenseur de soi.
Où contiguous
signifie ici contigu en mémoire. Ainsi, la fonction contiguous
n’affecte en rien votre tenseur cible, elle s'assure simplement qu’il est stocké dans une partie de mémoire contiguë.
Comme dans la réponse précédente, contigous () alloue fragments de mémoire contigus, ce sera utile lorsque nous sommes passage du code ténor à c ou c ++ où sont les tenseurs passé en tant que pointeurs
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 () un tenseur puis le remodelons (visualisez). Commençons par créer un tenseur contigu:
aaa = torch.Tensor( [[1,2,3],[4,5,6]] )
print(aaa.stride())
print(aaa.is_contiguous())
#(3,1)
#True
Stride () return (3,1) signifie que: lors du déplacement de la première dimension de chaque étape (ligne par ligne), nous devons déplacer 3 étapes dans la mémoire. Lorsque vous vous déplacez le long de la deuxième dimension (colonne par colonne), nous devons déplacer 1 étape 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 venues au tenseur:
bbb = aaa.transpose(0,1)
print(bbb.stride())
print(bbb.is_contiguous())
ccc = aaa.narrow(1,1,2) ## equivalent to matrix slicing aaa[:,1:3]
print(ccc.stride())
print(ccc.is_contiguous())
ddd = aaa.repeat(2,1 ) # The first dimension repeat once, the second dimension repeat twice
print(ddd.stride())
print(ddd.is_contiguous())
## 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())
fff = aaa.unsqueeze(2).repeat(1,1,8).view(2,-1,2)
print(fff.stride())
print(fff.is_contiguous())
#(1, 3)
#False
#(3, 1)
#False
#(3, 1)
#True
#(3, 1, 0)
#False
#(24, 2, 1)
#True
Ok, nous pouvons trouver que transpose (), narrow () et tenseur slicing, et expand () rendra le tenseur généré non contigu. Fait intéressant, repeat () et view () ne le rendent pas 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 discontinu. Ceci est probablement dû au fait que view () nécessite que le tenseur soit stocké de manière contiguë afin de pouvoir effectuer une remise en forme rapide en mémoire. par exemple:
bbb.view(-1,3)
nous aurons l'erreur:
---------------------------------------------------------------------------
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
Pour résoudre ce problème, ajoutez simplement contiguous () à un tenseur non contigu, pour créer une copie contiguë, puis appliquez view ().
bbb.contiguous().view(-1,3)
#tensor([[1., 4., 2.],
[5., 3., 6.]])