Les réponses ci-dessus n'a abordé la question pourquoi très bien. Je veux juste ajouter un exemple pour mieux comprendre l'utilisation de l' pack_padded_sequence
.
Prenons un exemple
Remarque: pack_padded_sequence
nécessite séquences triées dans le lot (dans l'ordre décroissant de séquence de longueur). Dans l'exemple ci-dessous, la séquence de lots ont déjà été triés pour moins encombrer. Visitez ce gist lien pour la pleine mise en œuvre.
Tout d'abord, nous créons un lot de 2 séquences de différentes longueurs de séquence ci-dessous. Nous avons 7 éléments dans le lot totalement.
- Chaque séquence a l'incorporation de la taille 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 pad seq_batch
pour obtenir le lot de séquences avec la même longueur de 5 (La longueur maximum du lot). Maintenant, le nouveau lot a 10 éléments totalement.
# pad the seq_batch
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 les emballons l' padded_seq_batch
. Elle renvoie un tuple de deux tenseurs:
- La première est que les données, y compris tous les éléments dans la séquence de lot.
- La deuxième est l'
batch_sizes
qui vous dira comment les éléments liés les uns aux autres par les différentes étapes.
# pack the padded_seq_batch
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
à la récurrente des modules de Pytorch, tels que les RNN, LSTM. Cela nécessite seulement 5 + 2=7
des calculs dans le recurrrent module.
lstm = nn.LSTM(input_size=2, hidden_size=3, batch_first=True)
output, (hn, cn) = lstm(packed_seq_batch.float()) # pass float tensor instead long tensor.
"""
>>> 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 avons besoin de convertir output
dos au collier de lot de production:
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])
"""
Comparer cet effort avec le standard
De façon standard, nous avons seulement besoin de passer l' padded_seq_batch
de lstm
module. Cependant, elle nécessite 10 calculs. Il s'agit de plusieurs calcule plus sur le remplissage des éléments de calcul inefficace.
-
Notez que cela ne conduise pas à inexacte des représentations, mais il faut beaucoup plus de logique à l'extrait de corriger les représentations.
- Pour LSTM (ou toute récurrent modules) avec seulement vers l'avant, si nous tenons à extraire le caché vecteur de la dernière étape en tant que représentation d'une séquence, nous aurions à ramasser caché des vecteurs à partir de T(th) de l'étape, où T est la longueur de l'entrée. La cueillette jusqu'à la dernière représentation sera incorrect. Notez que T sera différent pour les différentes entrées dans le lot.
- Pour le Bi-directionnelle LSTM (ou toute récurrent modules), il est même de plus en plus lourde, que l'on pourrait avoir à maintenir deux RNN des modules, l'un qui fonctionne avec un rembourrage au début de l'input et l'autre avec un rembourrage à la fin de l'entrée, et enfin l'extraction et la concaténation de l'caché vecteurs comme expliqué ci-dessus.
Nous allons voir la différence:
# The standard approach: using padding batch for recurrent modules
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 l' hn
, cn
sont différentes dans les deux façons, tandis que output
à partir de deux chemins conduisent à des valeurs différentes pour le rembourrage des éléments.