Die obigen Antworten haben die Frage warum sehr gut beantwortet . Ich möchte nur ein Beispiel hinzufügen, um die Verwendung von besser zu verstehen pack_padded_sequence
.
Nehmen wir ein Beispiel
Hinweis: pack_padded_sequence
Erfordert sortierte Sequenzen im Stapel (in absteigender Reihenfolge der Sequenzlängen). Im folgenden Beispiel wurde der Sequenzstapel bereits nach weniger Unordnung sortiert. Besuchen Sie diesen Hauptlink für die vollständige Implementierung.
Zuerst erstellen wir einen Stapel von 2 Sequenzen mit unterschiedlichen Sequenzlängen wie unten. Wir haben insgesamt 7 Elemente in der Charge.
- Jede Sequenz hat eine Einbettungsgröße von 2.
- Die erste Sequenz hat die Länge: 5
- Die zweite Sequenz hat die Länge: 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]
Wir füllen seq_batch
auf, um den Stapel von Sequenzen mit der gleichen Länge von 5 zu erhalten (die maximale Länge im Stapel). Jetzt hat die neue Charge insgesamt 10 Elemente.
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]]])
"""
Dann packen wir die padded_seq_batch
. Es wird ein Tupel von zwei Tensoren zurückgegeben:
- Das erste sind die Daten, die alle Elemente im Sequenzstapel enthalten.
- Die zweite ist die,
batch_sizes
die zeigt, wie die Elemente durch die Schritte miteinander in Beziehung stehen.
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]))
"""
Jetzt übergeben wir das Tupel packed_seq_batch
an die wiederkehrenden Module in Pytorch, wie z. B. RNN, LSTM. Dies erfordert nur 5 + 2=7
Berechnungen im wiederkehrenden Modul.
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>)))
"""
Wir müssen output
zurück in den gepolsterten Stapel der Ausgabe konvertieren :
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])
"""
Vergleichen Sie diesen Aufwand mit dem Standard
In dem Standard - Weg, wir brauchen nur das weitergeben padded_seq_batch
zu lstm
Modul. Es sind jedoch 10 Berechnungen erforderlich. Es werden mehrere Berechnungen mehr für Füllelemente durchgeführt, die rechnerisch ineffizient wären .
Beachten Sie, dass dies nicht zu ungenauen Darstellungen führt, sondern viel mehr Logik benötigt, um korrekte Darstellungen zu extrahieren.
- Wenn wir für LSTM (oder wiederkehrende Module) mit nur Vorwärtsrichtung den verborgenen Vektor des letzten Schritts als Darstellung für eine Sequenz extrahieren möchten, müssten wir verborgene Vektoren aus dem T (th) -Schritt aufnehmen, wobei T. ist die Länge der Eingabe. Das Aufnehmen der letzten Darstellung ist falsch. Beachten Sie, dass T für verschiedene Eingaben im Stapel unterschiedlich ist.
- Für bidirektionales LSTM (oder wiederkehrende Module) ist es noch umständlicher, da zwei RNN-Module verwaltet werden müssten, eines mit Auffüllung am Anfang der Eingabe und eines mit Auffüllung am Ende der Eingabe und Schließlich werden die verborgenen Vektoren wie oben erläutert extrahiert und verkettet.
Mal sehen, den Unterschied:
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 >))
"""
Die obigen Ergebnisse zeigen , dass hn
, cn
unterscheiden sich in zwei Wege , während output
von zwei Möglichkeiten , auf unterschiedliche Werte für die Polsterelemente führen.