Topik ini memperkenalkan jaringan saraf berulang (recurrent neural network, RNN) beserta varian LSTM, dua layer PyTorch yang memproses sekuens langkah demi langkah. Pembahasan mencakup mekanisme rekurensi pada nn.RNN, bentuk keluarannya, perluasan menuju nn.LSTM dengan cell state, serta keterbatasan yang kelak memotivasi mekanisme attention. Setelah topik ini, kedua layer tersebut dapat digunakan dengan bentuk input dan output yang benar.
Rekurensi pada RNN#
RNN membaca sekuens satu langkah demi satu langkah sambil memelihara sebuah vektor bernama hidden state. Vektor itu diperbarui pada setiap langkah dengan rumus yang sama, sehingga bobot yang digunakan di langkah mana pun tidak berubah:
Dalam rumus tersebut, adalah fitur pada langkah , sedangkan adalah hidden state langkah sebelumnya yang menjadi jalur informasi antara langkah. Hidden state awal diisi nol bila tidak diberikan. Diagram berikut membentangkan proses itu sepanjang waktu agar aliran state terlihat.
Layer nn.RNN mengimplementasikan rumus di atas. Argumen konstruktor yang terpenting adalah input_size, yaitu jumlah fitur pada satu langkah, dan hidden_size, yaitu ukuran hidden state. Contoh berikut menggunakan data sensor dengan empat fitur, lima langkah, dan dua contoh dalam satu batch.
import torch
torch.manual_seed(0)
rnn = torch.nn.RNN(input_size=4, hidden_size=8, batch_first=True)
# input: (batch, panjang_sekuens, fitur) = (2, 5, 4)
x = torch.randn(2, 5, 4)
output, hn = rnn(x)
# output: (2, 5, 8) = (batch, panjang_sekuens, hidden_size)
# hn : (1, 2, 8) = (num_layers, batch, hidden_size)
print("Bentuk output:", tuple(output.shape))
print("Bentuk hn :", tuple(hn.shape))Bentuk output: (2, 5, 8)
Bentuk hn : (1, 2, 8)Keluaran output berisi hidden state untuk setiap langkah, sedangkan hn berisi hidden state terakhir untuk setiap layer. Perlu diperhatikan bahwa dimensi pertama hn adalah jumlah layer, bukan batch, meskipun batch_first=True sudah dipilih.
Keluaran per langkah dan state akhir#
Dua keluaran nn.RNN berkaitan secara langsung. Nilai output pada langkah terakhir sama persis dengan baris layer terakhir hn, sehingga keduanya memuat angka yang sama dengan penyajian yang berbeda:
import torch
torch.manual_seed(0)
rnn = torch.nn.RNN(input_size=4, hidden_size=8, batch_first=True)
# input: (2, 5, 4) = (batch, panjang_sekuens, fitur)
x = torch.randn(2, 5, 4)
output, hn = rnn(x)
# output: (2, 5, 8), hn: (1, 2, 8); bandingkan langkah terakhir output
# dengan lapisan terakhir hn
print("output[:, -1, :] == hn[-1] :", torch.equal(output[:, -1, :], hn[-1]))
print("Selisih maksimum :", (output[:, -1, :] - hn[-1]).abs().max().item())output[:, -1, :] == hn[-1] : True
Selisih maksimum : 0.0Pilihan di antara keduanya bergantung pada tugas. Untuk klasifikasi sekuens, state akhir lazim digunakan sebagai ringkasan seluruh sekuens sebelum dimasukkan ke layer pengklasifikasi. Untuk tugas yang menghasilkan keluaran di setiap langkah, seperti pelabelan per kata, output yang berukuran sama dengan sekuensnya itulah yang digunakan.
Bentuk output yang sama panjang dengan sekuensnya juga memudahkan penyambungan ke layer lain. Keluaran setiap langkah dapat langsung diberikan ke nn.Linear tanpa pengolahan tambahan apabila prediksi diperlukan per langkah. Apabila hanya akhir sekuens yang penting, banyak implementasi mengambil hn semata agar tensor perantara tidak perlu dipelihara. Pilihan antara keduanya ditentukan oleh bentuk keluaran tugas, bukan oleh kemampuan layer, karena angka yang dibandingkan di atas memang sama. Pada penggunaan lanjutan, keluaran per langkah dari layer terbawah juga menjadi masukan bagi layer rekuren di atasnya.
LSTM: gerbang dan cell state#
RNN murni menulis ulang hidden state sepenuhnya pada setiap langkah, sehingga informasi lama mudah terhapus oleh input baru. LSTM (long short-term memory) menambahkan cell state , sebuah jalur memori yang mengalir antara langkah dan diatur oleh tiga gerbang bernilai nol sampai satu, yaitu gerbang input , gerbang forget , dan gerbang output . Pembaruan cell state-nya mengikuti
dengan sebagai perkalian per elemen, sedangkan adalah usulan konten baru. Gerbang forget yang bernilai besar membuat informasi lama bertahan di cell state tanpa ditulis ulang penuh. Layer nn.LSTM mengembalikan satu tuple tambahan karena cell state ikut dikeluarkan:
import torch
torch.manual_seed(0)
lstm = torch.nn.LSTM(input_size=4, hidden_size=8, batch_first=True)
# input: (batch, panjang_sekuens, fitur) = (2, 5, 4)
x = torch.randn(2, 5, 4)
output, (hn, cn) = lstm(x)
# output: (2, 5, 8) = (batch, panjang_sekuens, hidden_size)
# hn, cn: (1, 2, 8) = (num_layers, batch, hidden_size)
print("Bentuk output:", tuple(output.shape))
print("Bentuk hn :", tuple(hn.shape))
print("Bentuk cn :", tuple(cn.shape))
# dua lapisan: dimensi pertama hn dan cn ikut bertambah
lstm_2lapis = torch.nn.LSTM(input_size=4, hidden_size=8, num_layers=2, batch_first=True)
output2, (hn2, cn2) = lstm_2lapis(x)
print("Dua lapisan, hn:", tuple(hn2.shape))Bentuk output: (2, 5, 8)
Bentuk hn : (1, 2, 8)
Bentuk cn : (1, 2, 8)
Dua lapisan, hn: (2, 2, 8)Bentuk output, hn, dan cn mengikuti pola nn.RNN, dengan tambahan bahwa cn memuat cell state per layer. Argumen num_layers menumpuk beberapa layer secara vertikal, dan terlihat pada keluaran bahwa dimensi pertama hn ikut bertambah mengikuti jumlah layer, sedangkan dimensi batch tetap di tengah. Varian nn.GRU menyederhanakan gerbang LSTM menjadi dua, dengan pola bentuk keluaran yang sama.
Keterbatasan rekurensi#
Keterbatasan pertama bersifat matematis. Gradien yang mengalir mundur melewati ratusan langkah waktu cenderung mengecil hingga hilang, sehingga hubungan antara dua kata yang jauh terpisah sulit dipelajari oleh RNN murni. Cell state pada LSTM meredam masalah ini, tetapi tidak menghapusnya, dan sekuens yang sangat panjang tetap menantang bagi keluarga layer rekuren.
Keterbatasan kedua bersifat komputasi. Langkah harus menunggu hidden state langkah selesai, sehingga perhitungan sepanjang waktu tidak dapat diparalelkan dan pelatihan menjadi lambat pada sekuens panjang. LSTM memperpanjang rentang ketergantungan yang dapat dipelajari, tetapi biaya komputasi sekuensialnya tetap ada. Untuk meredam kedua masalah tersebut, praktik umum membatasi panjang sekuens, menumpuk beberapa layer rekuren, atau memotong gradien yang meledak dengan gradient clipping.
Kedua keterbatasan ini pula yang mendorong mekanisme attention, yang memungkinkan setiap posisi mengakses seluruh sekuens secara langsung tanpa menumpuk informasi pada satu rantai state.
Kesalahan umum#
Kesalahan pertama adalah memberikan tensor tanpa mengatur batch_first=True. Layer kemudian membaca (batch, panjang_sekuens, fitur) sebagai (panjang_sekuens, batch, fitur), dan bila dua dimensi depan kebetulan berukuran sama, tidak ada error yang muncul, hanya hasil yang salah tanpa disadari. Opsi batch_first=True sebaiknya dituliskan secara eksplisit sejak konstruksi layer.
Kesalahan kedua adalah memberikan indeks token langsung ke layer rekuren tanpa konversi. Indeks token bertipe long, sedangkan bobot layer bertipe float, sehingga pemanggilan berhenti dengan pesan berikut:
import torch
rnn = torch.nn.RNN(input_size=4, hidden_size=8, batch_first=True)
# indeks token bertipe long diberikan langsung ke nn.RNN
x = torch.zeros(2, 5, dtype=torch.long) # (2, 5) = (batch, panjang_sekuens)
try:
output, hn = rnn(x)
except (RuntimeError, ValueError) as e:
print(type(e).__name__ + ":", e)ValueError: RNN input dtype (torch.int64) does not match weight dtype (torch.float32). Convert input: input.to(torch.float32), or convert model: model.to(torch.int64)Pesan errornya menyarankan konversi dtype, tetapi jalan yang benar untuk data teks bukan mengubah indeks menjadi float. Indeks token diteruskan ke layer embedding terlebih dahulu, dan keluaran bertipe float itulah yang dimasukkan ke layer rekuren.
Kesalahan ketiga adalah menganggap output atau hn sebagai prediksi akhir. Keduanya masih berupa hidden state berukuran hidden_size, bukan kelas atau nilai yang diinginkan, sehingga layer lain, lazimnya nn.Linear, ditempatkan di atasnya untuk mengubah ringkasan tersebut menjadi keluaran tugas.
Ringkasan#
- RNN memproses sekuens langkah demi langkah dengan hidden state yang diteruskan antara langkah, dan satu set bobot digunakan ulang di semua langkah.
outputberisi hidden state setiap langkah dari layer terakhir, sedangkanhnberisi state akhir per layer dengan bentuk(num_layers, batch, hidden_size)apa pun nilaibatch_first.- LSTM menambahkan cell state dan tiga gerbang agar informasi jarak jauh bertahan lebih baik daripada RNN murni.
- Rekurensi menyulitkan paralelisasi dan melemahkan gradien pada sekuens panjang, dua alasan kemunculan attention.
Referensi#
- torch.nn.RNN — PyTorch — Dokumentasi layer RNN, termasuk rumus rekurensi serta bentuk input, output, dan h_n.
- torch.nn.LSTM — PyTorch — Dokumentasi LSTM beserta rumus gerbang dan bentuk (h_n, c_n) yang dikembalikannya.
- NLP From Scratch: Classifying Names with a Character-Level RNN — PyTorch — Tutorial resmi yang menggunakan nn.RNN untuk mengklasifikasikan sekuens karakter.