Jawaban di atas menjawab pertanyaan mengapa sangat baik. Saya hanya ingin menambahkan contoh untuk lebih memahami penggunaan pack_padded_sequence
.
Mari kita ambil contoh
Catatan: pack_padded_sequence
membutuhkan urutan yang diurutkan dalam kelompok (dalam urutan panjang urutan yang menurun). Dalam contoh di bawah ini, sekuens batch sudah diurutkan untuk mengurangi kekacauan. Kunjungi tautan inti ini untuk implementasi penuh.
Pertama, kami membuat kumpulan 2 urutan dengan panjang urutan berbeda seperti di bawah ini. Kami memiliki 7 elemen dalam batch secara total.
- Setiap urutan memiliki ukuran embedding 2.
- Urutan pertama memiliki panjang: 5
- Urutan kedua memiliki panjang: 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]
Kami pad seq_batch
untuk mendapatkan batch urutan dengan panjang yang sama yaitu 5 (Panjang maksimal dalam batch). Sekarang, kumpulan baru memiliki total 10 elemen.
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]]])
"""
Kemudian, kami mengemas padded_seq_batch
. Ini mengembalikan tupel dari dua tensor:
- Yang pertama adalah data termasuk semua elemen dalam urutan batch.
- Yang kedua adalah
batch_sizes
yang akan memberi tahu bagaimana elemen-elemen tersebut terkait satu sama lain melalui langkah-langkahnya.
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]))
"""
Sekarang, kami meneruskan tupel packed_seq_batch
ke modul berulang di Pytorch, seperti RNN, LSTM. Ini hanya membutuhkan 5 + 2=7
perhitungan dalam modul berulang.
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>)))
"""
Kita perlu mengonversi output
kembali ke kumpulan output yang empuk:
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])
"""
Bandingkan upaya ini dengan cara standar
Dengan cara standar, kita hanya perlu meneruskan modul padded_seq_batch
ke lstm
. Namun, itu membutuhkan 10 perhitungan. Ini melibatkan beberapa komputasi lebih lanjut pada elemen padding yang secara komputasi tidak efisien.
Perhatikan bahwa ini tidak mengarah ke representasi yang tidak akurat , tetapi membutuhkan lebih banyak logika untuk mengekstrak representasi yang benar.
- Untuk LSTM (atau modul berulang) dengan hanya arah maju, jika kita ingin mengekstrak vektor tersembunyi dari langkah terakhir sebagai representasi untuk suatu urutan, kita harus mengambil vektor tersembunyi dari langkah T (th), di mana T adalah panjang input. Mengambil representasi terakhir tidak akan benar. Perhatikan bahwa T akan berbeda untuk input yang berbeda dalam batch.
- Untuk Bi-directional LSTM (atau modul berulang), ini bahkan lebih rumit, karena seseorang harus memelihara dua modul RNN, satu yang bekerja dengan padding di awal input dan satu dengan padding di akhir input, dan akhirnya mengekstrak dan menggabungkan vektor tersembunyi seperti dijelaskan di atas.
Mari kita lihat perbedaannya:
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 >))
"""
Hasil di atas menunjukkan bahwa hn
, cn
berbeda dalam dua hal sedangkan output
dari dua cara mengarah pada nilai yang berbeda untuk elemen padding.