Saya mencoba untuk mereplikasi Bagaimana menggunakan pengepakan untuk input urutan variabel-panjang untuk rnn tapi saya rasa saya harus terlebih dahulu memahami mengapa kita perlu "mengemas" urutan.
Saya mengerti mengapa kita perlu "mengemas" mereka tetapi mengapa "pengepakan" (melalui pack_padded_sequence
) perlu?
Penjelasan tingkat tinggi apa pun akan dihargai!
Jawaban:
Saya telah menemukan masalah ini juga dan di bawah ini adalah yang saya temukan.
Saat melatih RNN (LSTM atau GRU atau vanilla-RNN), sulit untuk menumpuk urutan panjang variabel. Misalnya: jika panjang urutan dalam kelompok ukuran 8 adalah [4,6,8,5,4,3,7,8], Anda akan memasukkan semua urutan dan itu akan menghasilkan 8 urutan panjang 8. Anda akan berakhir dengan melakukan 64 komputasi (8x8), tetapi Anda hanya perlu melakukan 45 komputasi. Selain itu, jika Anda ingin melakukan sesuatu yang mewah seperti menggunakan dua arah-RNN, akan lebih sulit untuk melakukan komputasi batch hanya dengan padding dan Anda mungkin akan melakukan lebih banyak komputasi daripada yang diperlukan.
Sebaliknya, PyTorch memungkinkan kita untuk mengemas urutan, urutan yang dikemas secara internal adalah tupel dari dua daftar. Salah satunya berisi elemen urutan. Elemen disisipkan oleh langkah waktu (lihat contoh di bawah) dan lainnya berisi
ukuran setiap urutanukuran batch di setiap langkah. Ini berguna untuk memulihkan urutan aktual serta memberi tahu RNN apa ukuran batch di setiap langkah waktu. Ini telah ditunjukkan oleh @Aerin. Ini dapat diteruskan ke RNN dan ini akan mengoptimalkan komputasi secara internal.Saya mungkin kurang jelas di beberapa hal, jadi beri tahu saya dan saya dapat menambahkan lebih banyak penjelasan.
Berikut contoh kodenya:
sumber
Berikut adalah beberapa penjelasan visual 1 yang mungkin membantu mengembangkan intuisi yang lebih baik untuk fungsionalitas
pack_padded_sequence()
Mari kita asumsikan kita memiliki
6
total urutan (panjang variabel). Anda juga dapat menganggap angka ini6
sebagaibatch_size
hyperparameter. (batch_size
Akan bervariasi tergantung pada panjang urutan (lihat Gambar 2 di bawah))Sekarang, kami ingin meneruskan urutan ini ke beberapa arsitektur jaringan neural berulang. Untuk melakukannya, kita harus memasukkan semua urutan (biasanya dengan
0
s) dalam batch kita ke panjang urutan maksimum di batch kita (max(sequence_lengths)
), yang pada gambar di bawah ini adalah9
.Jadi, persiapan data harus sudah selesai sekarang, bukan? Tidak juga .. Karena masih ada satu masalah yang mendesak, terutama dalam hal berapa banyak komputasi yang harus kita lakukan jika dibandingkan dengan perhitungan yang sebenarnya diperlukan.
Demi pemahaman, mari kita asumsikan juga bahwa kita akan mengalikan matriks
padded_batch_of_sequences
bentuk di atas(6, 9)
dengan matriks bobotW
bentuk(9, 3)
.Jadi, kita harus melakukan operasi
6x9 = 54
perkalian dan6x8 = 48
penjumlahan (nrows x (n-1)_cols
), hanya untuk membuang sebagian besar hasil yang dihitung karena akan menjadi0
s (di mana kita memiliki bantalan). Penghitungan aktual yang diperlukan dalam kasus ini adalah sebagai berikut:Itu BANYAK penghematan lebih banyak bahkan untuk contoh ( mainan ) yang sangat sederhana ini . Anda sekarang dapat membayangkan berapa banyak komputasi (akhirnya: biaya, energi, waktu, emisi karbon, dll.) Yang dapat dihemat menggunakan
pack_padded_sequence()
tensor besar dengan jutaan entri, dan lebih dari jutaan sistem di seluruh dunia melakukannya, lagi dan lagi.Fungsionalitas
pack_padded_sequence()
dapat dipahami dari gambar di bawah ini, dengan bantuan kode warna yang digunakan:Sebagai hasil dari penggunaan
pack_padded_sequence()
, kita akan mendapatkan tupel tensor yang berisi (i) yang diratakan (sepanjang sumbu-1, pada gambar di atas)sequences
, (ii) ukuran tumpukan yang sesuai,tensor([6,6,5,4,3,3,2,2,1])
untuk contoh di atas.Tensor data (yaitu urutan yang diratakan) kemudian dapat diteruskan ke fungsi objektif seperti CrossEntropy untuk perhitungan kerugian.
1 kredit gambar untuk @sgrvinod
sumber
Jawaban di atas menjawab pertanyaan mengapa sangat baik. Saya hanya ingin menambahkan contoh untuk lebih memahami penggunaan
pack_padded_sequence
.Mari kita ambil contoh
Pertama, kami membuat kumpulan 2 urutan dengan panjang urutan berbeda seperti di bawah ini. Kami memiliki 7 elemen dalam batch secara total.
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.# 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]]]) """
Kemudian, kami mengemas
padded_seq_batch
. Ini mengembalikan tupel dari dua tensor:batch_sizes
yang akan memberi tahu bagaimana elemen-elemen tersebut terkait satu sama lain melalui langkah-langkahnya.# 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])) """
Sekarang, kami meneruskan tupel
packed_seq_batch
ke modul berulang di Pytorch, seperti RNN, LSTM. Ini hanya membutuhkan5 + 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()) # 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>))) """
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
kelstm
. 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.
Mari kita lihat perbedaannya:
# 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 >)) """
Hasil di atas menunjukkan bahwa
hn
,cn
berbeda dalam dua hal sedangkanoutput
dari dua cara mengarah pada nilai yang berbeda untuk elemen padding.sumber
Menambah jawaban Umang, saya menemukan ini penting untuk diperhatikan.
Item pertama dalam tupel yang dikembalikan
pack_padded_sequence
adalah data (tensor) - tensor yang berisi urutan yang dikemas. Item kedua adalah tensor bilangan bulat yang menyimpan informasi tentang ukuran batch di setiap langkah urutan.Yang penting di sini adalah item kedua (Ukuran batch) mewakili jumlah elemen di setiap langkah urutan dalam batch, bukan variasi panjang urutan yang diteruskan
pack_padded_sequence
.Misalnya, data yang diberikan
abc
danx
: class:PackedSequence
akan berisi dataaxbc
denganbatch_sizes=[2,1,1]
.sumber
Saya menggunakan urutan paket empuk sebagai berikut.
di mana text_lengths adalah panjang urutan individu sebelum padding dan urutan diurutkan sesuai dengan urutan penurunan panjang dalam batch tertentu.
Anda dapat melihat contohnya di sini .
Dan kami melakukan pengepakan sehingga RNN tidak melihat indeks padded yang tidak diinginkan saat memproses urutan yang akan mempengaruhi kinerja keseluruhan.
sumber