Les réponses ci-dessus ont très bien répondu à la question de savoir pourquoi . Je veux juste ajouter un exemple pour mieux comprendre l'utilisation de pack_padded_sequence
.
Prenons un exemple
Remarque: pack_padded_sequence
nécessite des séquences triées dans le lot (dans l'ordre décroissant des longueurs de séquence). Dans l'exemple ci-dessous, le lot de séquence a déjà été trié pour moins d'encombrement. Visitez ce lien essentiel pour la mise en œuvre complète.
Tout d'abord, nous créons un lot de 2 séquences de différentes longueurs de séquence comme ci-dessous. Nous avons 7 éléments dans le lot au total.
- Chaque séquence a une taille d'incorporation de 2.
- La première séquence a la longueur: 5
- La deuxième séquence a la longueur: 2
import torch
seq_batch = [torch.tensor([[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5]]),
torch.tensor([[10, 10],
[20, 20]])]
seq_lens = [5, 2]
Nous complétons seq_batch
pour obtenir le lot de séquences de longueur égale à 5 (la longueur maximale du lot). Maintenant, le nouveau lot contient 10 éléments au total.
padded_seq_batch = torch.nn.utils.rnn.pad_sequence(seq_batch, batch_first=True)
"""
>>>padded_seq_batch
tensor([[[ 1, 1],
[ 2, 2],
[ 3, 3],
[ 4, 4],
[ 5, 5]],
[[10, 10],
[20, 20],
[ 0, 0],
[ 0, 0],
[ 0, 0]]])
"""
Ensuite, nous emballons le fichier padded_seq_batch
. Il renvoie un tuple de deux tenseurs:
- Le premier est les données comprenant tous les éléments du lot de séquence.
- Le second est le
batch_sizes
qui dira comment les éléments sont liés les uns aux autres par les étapes.
packed_seq_batch = torch.nn.utils.rnn.pack_padded_sequence(padded_seq_batch, lengths=seq_lens, batch_first=True)
"""
>>> packed_seq_batch
PackedSequence(
data=tensor([[ 1, 1],
[10, 10],
[ 2, 2],
[20, 20],
[ 3, 3],
[ 4, 4],
[ 5, 5]]),
batch_sizes=tensor([2, 2, 1, 1, 1]))
"""
Maintenant, nous passons le tuple packed_seq_batch
aux modules récurrents dans Pytorch, tels que RNN, LSTM. Cela ne nécessite que des 5 + 2=7
calculs dans le module recurrrent.
lstm = nn.LSTM(input_size=2, hidden_size=3, batch_first=True)
output, (hn, cn) = lstm(packed_seq_batch.float())
"""
>>> output # PackedSequence
PackedSequence(data=tensor(
[[-3.6256e-02, 1.5403e-01, 1.6556e-02],
[-6.3486e-05, 4.0227e-03, 1.2513e-01],
[-5.3134e-02, 1.6058e-01, 2.0192e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01],
[-5.9372e-02, 1.0934e-01, 4.1991e-01],
[-6.0768e-02, 7.0689e-02, 5.9374e-01],
[-6.0125e-02, 4.6476e-02, 7.1243e-01]], grad_fn=<CatBackward>), batch_sizes=tensor([2, 2, 1, 1, 1]))
>>>hn
tensor([[[-6.0125e-02, 4.6476e-02, 7.1243e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01]]], grad_fn=<StackBackward>),
>>>cn
tensor([[[-1.8826e-01, 5.8109e-02, 1.2209e+00],
[-2.2475e-04, 2.3041e-05, 1.4254e-01]]], grad_fn=<StackBackward>)))
"""
Nous devons reconvertir output
le lot de sortie rembourré:
padded_output, output_lens = torch.nn.utils.rnn.pad_packed_sequence(output, batch_first=True, total_length=5)
"""
>>> padded_output
tensor([[[-3.6256e-02, 1.5403e-01, 1.6556e-02],
[-5.3134e-02, 1.6058e-01, 2.0192e-01],
[-5.9372e-02, 1.0934e-01, 4.1991e-01],
[-6.0768e-02, 7.0689e-02, 5.9374e-01],
[-6.0125e-02, 4.6476e-02, 7.1243e-01]],
[[-6.3486e-05, 4.0227e-03, 1.2513e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]],
grad_fn=<TransposeBackward0>)
>>> output_lens
tensor([5, 2])
"""
Comparez cet effort avec la méthode standard
De façon standard, il suffit de passer le padded_seq_batch
au lstm
module. Cependant, cela nécessite 10 calculs. Il implique plusieurs calculs plus sur des éléments de remplissage qui seraient inefficaces en termes de calcul .
Notez que cela ne conduit pas à des représentations inexactes , mais nécessite beaucoup plus de logique pour extraire les représentations correctes.
- Pour LSTM (ou tout module récurrent) avec uniquement une direction avant, si nous souhaitons extraire le vecteur caché de la dernière étape comme représentation d'une séquence, nous devrons récupérer les vecteurs cachés de T (ème) étape, où T est la longueur de l'entrée. La reprise de la dernière représentation sera incorrecte. Notez que T sera différent pour différentes entrées dans le lot.
- Pour le LSTM bidirectionnel (ou tout module récurrent), c'est encore plus encombrant, car il faudrait maintenir deux modules RNN, un qui fonctionne avec un remplissage au début de l'entrée et un avec un remplissage à la fin de l'entrée, et enfin extraire et concaténer les vecteurs cachés comme expliqué ci-dessus.
Voyons la différence:
output, (hn, cn) = lstm(padded_seq_batch.float())
"""
>>> output
tensor([[[-3.6256e-02, 1.5403e-01, 1.6556e-02],
[-5.3134e-02, 1.6058e-01, 2.0192e-01],
[-5.9372e-02, 1.0934e-01, 4.1991e-01],
[-6.0768e-02, 7.0689e-02, 5.9374e-01],
[-6.0125e-02, 4.6476e-02, 7.1243e-01]],
[[-6.3486e-05, 4.0227e-03, 1.2513e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01],
[-4.1217e-02, 1.0726e-01, -1.2697e-01],
[-7.7770e-02, 1.5477e-01, -2.2911e-01],
[-9.9957e-02, 1.7440e-01, -2.7972e-01]]],
grad_fn= < TransposeBackward0 >)
>>> hn
tensor([[[-0.0601, 0.0465, 0.7124],
[-0.1000, 0.1744, -0.2797]]], grad_fn= < StackBackward >),
>>> cn
tensor([[[-0.1883, 0.0581, 1.2209],
[-0.2531, 0.3600, -0.4141]]], grad_fn= < StackBackward >))
"""
Les résultats ci-dessus montrent que hn
, cn
sont différents de deux manières tandis que output
de deux manières conduisent à des valeurs différentes pour les éléments de remplissage.