Model seq2seq pada section sebelumnya menyelesaikan tugas salin-geser dengan sempurna, dan LSTM serta GRU sudah meredam sebagian besar vanishing gradient. Namun tiga hambatan struktural tetap menempel pada keluarga model rekuren: komputasi yang wajib berurutan, pengaruh token jauh yang memudar, dan context vector berukuran tetap. Ketiganya nyaris tak terasa pada sekuens empat token, tetapi menjadi penentu biaya dan kualitas begitu kalimat panjang, korpus besar, dan model raksasa masuk ke dalam perhitungan. Chapter ini membuktikan ketiganya dengan angka pada RNN mini yang dapat kita jalankan langsung, sehingga keputusan Transformer melepas rekurensi sepenuhnya terasa sebagai jawaban yang wajar, bukan lompatan mendadak.
Rantai Berurutan yang Tak Bisa Diparalelkan #
RNN menghitung hidden state satu per satu: h_t = tanh(Wh @ h_{t-1} + Wx @ x_t). Rumus pendek itu menyiratkan ketergantungan penuh — h_2 tidak bisa dihitung sebelum h_1 selesai, dan seterusnya sampai h_T. GPU dirancang untuk memroses operasi matriks raksasa dalam satu pemanggilan, tetapi rantai rekuren memaksa T pemanggilan kecil yang berurutan; waktu training tumbuh seiring panjang sekuens berapa pun jumlah prosesor yang tersedia. Masalahnya bukan jumlah aritmetika, melainkan kedalaman dependensi yang tidak bisa dibagi ke banyak pemroses.
Diagram di atas memperlihatkan rantai tersebut: informasi dari x_1 harus melintasi empat hidden state perantara sebelum mencapai h_5, sementara arsitektur tanpa rekurensi memroses seluruh posisi bersamaan.
Pengaruh Token Jauh yang Memudar #
Ketergantungan berantai juga punya sisi kualitatif. Chapter Vanishing Gradient pada RNN
menunjukkan bahwa gradien yang mundur lintas t langkah menyusut kira-kira sebesar ρ^t dengan ρ jari-jari spektral Wh. Cerminannya di arah maju: pengaruh sebuah token terhadap hidden state akhir ikut melemah pada jarak yang sama, sehingga model cenderung memprioritaskan informasi baru. LSTM meredam gejala ini lewat cell state, tetapi tidak menghapusnya — dan biayanya parameter empat kali lipat.
Context Vector yang Selalu Sempit #
Hambatan ketiga spesifik untuk seq2seq. Seluruh makna sekuens sumber harus lolos melalui satu context vector berdimensi H sebelum mencapai decoder, seperti dibahas pada chapter Sequence-to-Sequence . Kapasitas vektor itu tetap: 20 token maupun 200 token sama-sama dirangkum menjadi H nilai. Semakin panjang kalimat, semakin banyak informasi yang harus berbagi tempat yang sama — bottleneck yang tidak bisa diselesaikan LSTM sebesar apa pun karena strukturnya tetap satu vektor rangkuman.
Ketiga Hambatan dalam Satu Program #
Program berikut mengukur ketiga hambatan itu dalam tiga bagian: bagian A membandingkan rantai rekuren t1..t5 dengan operasi matriks tunggal, bagian B mengukur jejak token uji pada h_20 untuk lima posisi penempatan, dan bagian C menghitung rasio kompresi context vector:
"""Tiga hambatan struktural RNN yang memotivasi Transformer.
Bagian A membandingkan perhitungan rekuren (rantai t1..t5 yang wajib
berurutan) dengan operasi matriks yang memproses seluruh token sekaligus.
Bagian B mengukur berapa besar pengaruh token pada posisi 1..20 yang masih
tersisa di hidden state akhir ketika jari-jari spektral Wh = 0.9.
Bagian C merangkum kompetisi muatan pada context vector seq2seq.
"""
import numpy as np
np.random.seed(0)
D, H, T = 4, 6, 5
Wx = np.random.randn(H, D) * 0.5
Wh = np.random.randn(H, H) * 0.5
x = np.random.randn(T, D)
print("== Bagian A: rantai berurutan vs operasi matriks ==")
print()
print("RNN -- setiap langkah MENUNGGU hasil langkah sebelumnya:")
h = np.zeros(H)
for t in range(T):
h = np.tanh(Wh @ h + Wx @ x[t])
print(f" t={t + 1}: h_{t + 1} = tanh(Wh @ h_{t} + Wx @ x_{t + 1})"
f" |h| = {np.linalg.norm(h):.3f}")
print(f" Kedalaman rantai: {T} langkah berurutan untuk {T} token")
print()
print("Layer tanpa rekurensi -- seluruh token dalam SATU pemanggilan:")
Y = np.tanh(x @ Wx.T)
print(f" Y = tanh(X @ Wx^T) selesai sekali jalan, bentuk {Y.shape}")
for t in range(T):
print(f" Y[{t}] = {np.round(Y[t], 3)}")
print()
print("Biaya langkah berurutan saat panjang sekuens naik:")
for panjang in (5, 100, 1000):
print(f" T={panjang:4d} -> RNN {panjang:4d} langkah berurutan"
f" | matriks tetap 1 operasi")
print()
print("== Bagian B: pengaruh token jauh memudar (rho=0.9, T=20) ==")
print()
np.random.seed(1)
T2, H2, D2 = 20, 6, 6
Wh2 = 0.9 * np.linalg.qr(np.random.randn(H2, H2))[0] # spektral tepat 0.9
Wx2 = 0.4 * np.eye(H2, D2)
def hidden_akhir(x_seq):
"""Jalankan RNN mini dan kembalikan hidden state terakhir."""
h = np.zeros(H2)
for t in range(T2):
h = np.tanh(Wh2 @ h + Wx2 @ x_seq[t])
return h
delta = np.full(D2, 0.5) # token uji yang sama diletakkan di satu posisi
h_dasar = hidden_akhir(np.zeros((T2, D2)))
jejak = []
print("Norma jejak token uji pada hidden state akhir h_20:")
for p in (1, 5, 10, 15, 20):
x_seq = np.zeros((T2, D2))
x_seq[p - 1] = delta
jejak.append(np.linalg.norm(hidden_akhir(x_seq) - h_dasar))
print(f" token di posisi {p:2d}: ||h_20 - h_20 dasar|| = {jejak[-1]:.4f}")
print()
print(f" Rasio jejak posisi 1 : posisi 20 = {jejak[0] / jejak[-1]:.3f}"
f" (teori 0.9^19 = {0.9 ** 19:.3f})")
print()
print("== Bagian C: context vector berukuran tetap ==")
print()
print(f" Sekuens sumber {T2} token x {D2} dimensi = {T2 * D2} nilai")
print(f" Rangkuman seq2seq : 1 context vector {H2} dimensi = {H2} nilai")
print(f" Rasio kompresi : {T2 * D2 / H2:.0f} : 1 -- tetap {H2} nilai")
print(" entah kalimatnya 20 atau 200 token.")Output yang Dihasilkan:
== Bagian A: rantai berurutan vs operasi matriks ==
RNN -- setiap langkah MENUNGGU hasil langkah sebelumnya:
t=1: h_1 = tanh(Wh @ h_0 + Wx @ x_1) |h| = 1.774
t=2: h_2 = tanh(Wh @ h_1 + Wx @ x_2) |h| = 1.465
t=3: h_3 = tanh(Wh @ h_2 + Wx @ x_3) |h| = 1.631
t=4: h_4 = tanh(Wh @ h_3 + Wx @ x_4) |h| = 1.963
t=5: h_5 = tanh(Wh @ h_4 + Wx @ x_5) |h| = 1.642
Kedalaman rantai: 5 langkah berurutan untuk 5 token
Layer tanpa rekurensi -- seluruh token dalam SATU pemanggilan:
Y = tanh(X @ Wx^T) selesai sekali jalan, bentuk (5, 6)
Y[0] = [-0.995 -0.609 -0.875 -0.633 0.143 0.774]
Y[1] = [-0.2 -0.42 0.127 -0.237 -0.272 -0.844]
Y[2] = [-0.281 -0.49 0.201 -0.157 -0.554 0.894]
Y[3] = [ 0.185 0.957 -0.653 0.322 0.87 -0.892]
Y[4] = [-0.75 -0.593 -0.055 -0.402 -0.581 0.645]
Biaya langkah berurutan saat panjang sekuens naik:
T= 5 -> RNN 5 langkah berurutan | matriks tetap 1 operasi
T= 100 -> RNN 100 langkah berurutan | matriks tetap 1 operasi
T=1000 -> RNN 1000 langkah berurutan | matriks tetap 1 operasi
== Bagian B: pengaruh token jauh memudar (rho=0.9, T=20) ==
Norma jejak token uji pada hidden state akhir h_20:
token di posisi 1: ||h_20 - h_20 dasar|| = 0.0582
token di posisi 5: ||h_20 - h_20 dasar|| = 0.0889
token di posisi 10: ||h_20 - h_20 dasar|| = 0.1518
token di posisi 15: ||h_20 - h_20 dasar|| = 0.2649
token di posisi 20: ||h_20 - h_20 dasar|| = 0.4835
Rasio jejak posisi 1 : posisi 20 = 0.120 (teori 0.9^19 = 0.135)
== Bagian C: context vector berukuran tetap ==
Sekuens sumber 20 token x 6 dimensi = 120 nilai
Rangkuman seq2seq : 1 context vector 6 dimensi = 6 nilai
Rasio kompresi : 20 : 1 -- tetap 6 nilai
entah kalimatnya 20 atau 200 token.Mari kita telaah kode di atas baris per baris:
- Baris 12-17:
np.random.seed(0)membuat contoh deterministik; ukuran ditetapkan (D=4 fitur, H=6 hidden, T=5 token), lalu bobotWx,Wh, dan sekuensxdibangkitkan acak. - Baris 22-27: perulangan rekuren lima langkah. Baris 24 menghitung
hbaru darihlama — inilah titik ketergantungannya; baris 25-26 mencetak setiap langkah t1..t5 beserta normanya, dan baris 27 menegaskan kedalaman rantai. - Baris 30-34: operasi pembanding
Y = tanh(X @ Wx^T)pada baris 31 memroses kelima token dalam satu pemanggilan NumPy; hasil per baris langsung tersedia bersamaan. - Baris 37-40: tabel biaya untuk T = 5, 100, dan 1000 — jumlah langkah berurutan RNN tumbuh linear, sedangkan operasi matriks tetap satu.
- Baris 46-49: matriks ortogonal hasil dekomposisi QR diskalakan 0,9 sehingga jari-jari spektral
Wh2tepat 0,9 — alat yang sama dengan chapter Vanishing Gradient agar pengukuran bersih dari kebetulan inisialisasi. - Baris 52-57: fungsi
hidden_akhirmenjalankan 20 langkah rekuren pada sekuens masukan dan mengembalikan hidden state terakhir. - Baris 60-68: token uji bernilai 0,5 diletakkan pada satu posisi dalam kalimat 20 token yang kosong, lalu norma selisih
h_20terhadap dasar diukur untuk posisi 1, 5, 10, 15, dan 20. - Baris 70-71: rasio jejak posisi awal terhadap posisi akhir dibandingkan dengan prediksi teori ρ^19.
- Baris 74-79: hitungan kompresi context vector seq2seq — 120 nilai masukan dirangkum menjadi 6 nilai.
Pembacaan hasilnya lugas. Bagian A memperlihatkan rantai t1..t5 yang tidak bisa disusun ulang — setiap baris keluaran menunggu baris sebelumnya — sementara kelima baris Y lahir dari satu operasi. Bagian B menunjukkan jejak token mengecil seiring posisinya menjauh dari ujung: 0,4835 di posisi 20 menyusut menjadi 0,0582 di posisi 1; rasio 0,120 berdekatan dengan prediksi 0,9^19 = 0,135. Bagian C merangkum persaingan muatan: rasio kompresi 20:1 yang berubah menjadi 200:1 pada kalimat sepuluh kali lebih panjang tanpa satu pun nilai tambahan di context vector.
Dari RNN ke Transformer #
| Aspek | RNN rekuren | Target desain Transformer |
|---|---|---|
| Paralelisasi training | T langkah berurutan, apa pun jumlah prosesor | seluruh posisi dalam sedikit operasi matriks besar |
| Jarak token i ke j | melewati |i−j| langkah perantara | satu koneksi attention langsung |
| Rangkuman sekuens | satu context vector berukuran tetap | tiap posisi membaca semua posisi lewat bobot |
| Gradien lintas posisi | menyusut seperti ρ^T | jalur perkalian pendek berkat koneksi langsung |
Makalah Attention Is All You Need (Vaswani dkk., 2017) menjawab ketiga hambatan itu sekaligus dengan melepas rekurensi sepenuhnya. Langkah pertamanya sebenarnya lebih tua: sebelum Transformer ada, attention sudah dipakai untuk memperbaiki hambatan ketiga — decoder seq2seq diberi akses ke seluruh hidden state encoder dengan bobot relevansi. Chapter berikutnya membedah ide itu dari sel-sarnya: bagaimana bobot relevansi dihitung, dan bagaimana rata-rata berbobot menggantikan context vector yang tetap.