RNN (recurrent neural network) memproses sekuens langkah demi langkah sambil membawa satu vektor keadaan tersembunyi yang meringkas masa lalu. Topik ini menuliskan persamaan rekurensinya, menjalankan rekurensi yang sama secara manual dan lewat nn.RNN untuk membuktikan kesetaraannya, dan membahas apa yang diingat serta dilupakan oleh keadaan tersebut. Setelah selesai, mekanika hidden state tidak lagi abstrak dan menjadi loop kecil yang dapat dihitung.
Persamaan rekurensi dan maknanya#
Pada setiap langkah waktu , RNN menerima dua masukan, yaitu vektor masukan saat itu dan keadaan tersembunyi sebelumnya , lalu menghasilkan keadaan baru:
Perhatikan bahwa bobotnya dibagi untuk seluruh langkah waktu, sehingga satu pasang matriks melayani sekuens sepanjang apa pun, dan itulah warisan pembagian bobot dari konvolusi. Keadaan berperan ganda, yaitu keluaran langkah bagi RNN sederhana sekaligus memori yang dibawa ke langkah berikutnya. Tanh menekan nilainya ke rentang sampai 1, yang menjaga keadaan tetap terkendali sepanjang waktu dengan harga gradien yang menyusut pada rantai panjang.
Interpretasi mekanisnya adalah pencampuran dua informasi. Suku menyatakan apa yang dilihat sekarang, suku menyatakan apa yang diingat, dan tanh memaksa hasil campurannya kembali ke skala yang stabil. Bobot yang dipelajari menentukan seberapa keras masa lalu dipertahankan, dan pada titik ekstremnya bernilai dekat identitas sehingga informasi terbawa jauh, atau dekat nol sehingga hanya langkah terakhir yang berarti.
Rekurensi manual dan nn.RNN yang setara#
Mekanika di atas cukup kecil untuk dihitung dua kali. Kode berikut memutar RNN satu unit pada lima langkah dengan loop manual, lalu mengulanginya dengan nn.RNN yang bobotnya disalin persis, dan membandingkan seluruh jejak keadaannya.
import torch
import torch.nn as nn
torch.manual_seed(0)
rnn = nn.RNN(input_size=1, hidden_size=1, batch_first=True)
x = torch.randn(1, 5, 1)
h = torch.tanh(rnn.weight_ih_l0 @ x[0, 0] + rnn.bias_ih_l0 + rnn.bias_hh_l0)
jejak_manual = [h]
for t in range(1, 5):
h = torch.tanh(rnn.weight_ih_l0 @ x[0, t] + rnn.weight_hh_l0 @ h + rnn.bias_ih_l0 + rnn.bias_hh_l0)
jejak_manual.append(h)
jejak_manual = torch.stack(jejak_manual)
jejak_modul, h_akhir = rnn(x)
print("bentuk keluaran modul:", tuple(jejak_modul.shape), "| bentuk h akhir:", tuple(h_akhir.shape))
print("jejak identik :", torch.allclose(jejak_manual.detach().flatten(), jejak_modul.detach().flatten()))bentuk keluaran modul: (1, 5, 1) | bentuk h akhir: (1, 1, 1)
jejak identik : TrueLoop manual dan modul menghasilkan jejak keadaan yang identik, dan perbandingan bentuknya menunjukkan konvensi PyTorch, yaitu keluaran per langkah berbentuk batch, panjang waktu, lalu dimensi tersembunyi, sedangkan keadaan akhir berbentuk lapisan, batch, dimensi. Empat bobot yang tampak pada loop, yaitu matriks masukan, matriks keadaan, dan dua vektor bias, adalah seluruh parameter RNN sederhana, dan tidak ada yang tersembunyi di balik nn.RNN.
Apa yang diingat keadaan tersembunyi#
Keadaan tersembunyi adalah kompresi yang berjalan, yaitu ringkasan tetap berukuran sama meskipun sekuensnya memanjang, dan seperti semua kompresi, ia menyeimbangkan apa yang dipertahankan. Untuk ketergantungan dekat, mekanikanya bekerja baik karena informasi hanya melewati beberapa tahap tanh, dan pada topik LSTM dan GRU nanti masalah utamanya bermula dari ketergantungan jauh yang melemah sepanjang jalan.
Perilaku menyusutnya sinyal dapat dilihat langsung dari sensitivitas keadaan akhir terhadap langkah awal. Kode berikut mengganggu masukan langkah pertama sedikit lalu mengukur perubahan keadaan pada setiap langkah, sehingga memudarnya pengaruh masa lalu terlihat pada angka.
import torch
import torch.nn as nn
torch.manual_seed(0)
rnn = nn.RNN(input_size=1, hidden_size=1, batch_first=True)
with torch.no_grad():
x1 = torch.randn(1, 5, 1)
x2 = x1.clone()
x2[0, 0] += 0.5
h1, _ = rnn(x1)
h2, _ = rnn(x2)
selisih = (h1 - h2).abs().squeeze(-1).squeeze(0)
print("selisih keadaan per langkah:", [round(v, 5) for v in selisih.tolist()])selisih keadaan per langkah: [0.00062, 2e-05, 0.0, 0.0, 0.0]Selisih yang ditimbulkan perubahan di langkah pertama terus menyusut menuju akhir sekuens, dan pengurangannya per langkah itulah yang disebut gradien yang menghilang pada rantai rekurensi. Bukan berarti RNN tidak berguna, karena banyak masalah hanya menuntut memori pendek, tetapi angka ini menetapkan batasnya dengan ukuran yang nyata sebelum perbaikannya dipelajari.
Keluaran per langkah dan keluaran akhir#
Pemakaian keluaran RNN mengikuti masalah yang dipecahkan. Klasifikasi sekuens biasanya memakai keadaan akhir saja, karena seluruh sekuens telah diringkas ke dalamnya, sedangkan tugas yang menuntut jawaban per langkah, misalnya pelabelan setiap kata, memakai seluruh jejak keluaran. nn.RNN mengembalikan keduanya sekaligus, dan pilihan memakai yang mana adalah keputusan desain pertama saat menyambungkan RNN ke layer berikutnya.
Topik LSTM dan GRU membangun sisa modul ini di atas kerangka yang sama, dengan LSTM dan GRU yang mengganti satu tanh menjadi gerbang-gerbang yang mampu menjaga sinyal lebih lama.
Ringkasan#
- RNN menghitung dengan bobot yang dibagi seluruh langkah waktu.
- Loop manual dan
nn.RNNmenghasilkan jejak keadaan yang identik, sehingga modul tersebut tidak menyembunyikan mekanika apa pun. - Keadaan tersembunyi adalah kompresi berjalan yang pengaruhnya terhadap langkah awal menyusut per langkah, dan itulah akar masalah memori jauh.
- Keluaran per langkah dipakai untuk tugas per posisi, sedangkan keadaan akhir dipakai untuk klasifikasi utuh sekuens.
Referensi#
- torch.nn.RNN — PyTorch — Dokumentasi yang menyatakan persamaan rekurensi dan konvensi bentuk masukan-keluaran.
- Modern Recurrent Neural Networks — Dive into Deep Learning — Bab yang membandingkan RNN, GRU, dan LSTM dari persamaan yang sama.
- Understanding LSTM Networks — Colah — Tulisan klasik yang memvisualisasikan aliran keadaan dan masalahnya.