Las respuestas anteriores abordaron muy bien la pregunta por qué . Solo quiero agregar un ejemplo para comprender mejor el uso de pack_padded_sequence.
Tomemos un ejemplo
Nota: pack_padded_sequencerequiere secuencias ordenadas en el lote (en orden descendente de longitudes de secuencia). En el siguiente ejemplo, el lote de secuencia ya estaba ordenado para reducir el desorden. Visite este enlace esencial para la implementación completa.
Primero, creamos un lote de 2 secuencias de diferentes longitudes de secuencia como se muestra a continuación. Tenemos 7 elementos en el lote en total.
- Cada secuencia tiene un tamaño de incrustación de 2.
- La primera secuencia tiene la longitud: 5
- La segunda secuencia tiene la longitud: 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]
Rellenamos seq_batchpara obtener el lote de secuencias con la misma longitud de 5 (la longitud máxima en el lote). Ahora, el nuevo lote tiene 10 elementos en 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]]])
"""
Luego, empacamos el padded_seq_batch. Devuelve una tupla de dos tensores:
- El primero son los datos que incluyen todos los elementos del lote de secuencia.
- El segundo es el
batch_sizesque le dirá cómo los elementos se relacionan entre sí por los pasos.
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]))
"""
Ahora, pasamos la tupla packed_seq_batcha los módulos recurrentes en Pytorch, como RNN, LSTM. Esto solo requiere 5 + 2=7cálculos en el módulo recurrente.
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>)))
"""
Necesitamos convertir de outputnuevo al lote de salida acolchado:
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])
"""
Compare este esfuerzo con la forma estándar
De forma estándar, solo necesitamos pasar el módulo padded_seq_batchto lstm. Sin embargo, requiere 10 cálculos. Implica varios cálculos más sobre elementos de relleno que serían computacionalmente ineficientes.
Tenga en cuenta que no conduce a representaciones inexactas , pero necesita mucha más lógica para extraer representaciones correctas.
- Para LSTM (o cualquier módulo recurrente) con solo dirección de avance, si quisiéramos extraer el vector oculto del último paso como una representación de una secuencia, tendríamos que recoger los vectores ocultos de T (th) paso, donde T es la longitud de la entrada. Recoger la última representación será incorrecto. Tenga en cuenta que T será diferente para diferentes entradas en lote.
- Para LSTM bidireccionales (o cualquier módulo recurrente), es aún más engorroso, ya que habría que mantener dos módulos RNN, uno que funciona con relleno al comienzo de la entrada y otro con relleno al final de la entrada, y finalmente extrayendo y concatenando los vectores ocultos como se explicó anteriormente.
Veamos la diferencia:
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 >))
"""
Los resultados anteriores muestran que hn, cnson diferentes de dos maneras mientras que outputa partir de dos maneras dar lugar a diferentes valores para los elementos de relleno.