Mục lục
- Mục tiêu bài học
- Recap Vanilla RNN forward
- Long-range dependency — ví dụ tiếng Pháp
- BPTT — tích Jacobian qua time step
- Jacobian một step \( \partial h_t / \partial h_{t-1} \)
- Hai factor: \( \tanh'(z) \) và \( W_{hh} \)
- Spectral radius của \( W_{hh} \) quyết định
- Demo numeric — \( 0.5^{100} \) và \( 1.1^{100} \)
- Exploding gradient — gradient clipping
- Vì sao vanishing khó fix hơn exploding
- Diagnose — plot gradient norm theo time step
- Vì sao MLP sâu không vanish nặng như RNN
- Solutions cho vanishing — kiến trúc + init
- Lịch sử — Hochreiter, Bengio, LSTM, attention
- Hậu quả thực tế cho production
- Code Python — diagnose & clip
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Viết được biểu thức gradient qua BPTT và chỉ ra factor nhân lặp \( T \) lần.
- Phân tích vì sao spectral radius của \( W_{hh} \) và \( \tanh'(z) \) cùng quyết định gradient vanish hay explode.
- Tính được nhanh magnitude: \( 0.5^{100} \approx 10^{-30} \), \( 0.9^{100} \approx 2.7 \cdot 10^{-5} \).
- Apply gradient clipping bằng
torch.nn.utils.clip_grad_norm_để xử lý exploding gradient. - Diagnose vanishing bằng cách plot gradient norm theo time step / layer.
- Hiểu vì sao vanishing cần kiến trúc mới (LSTM, GRU, attention), không thể "rescale" như exploding.
- Đặt B37 (LSTM) và B38 (GRU) trong context: lời giải lịch sử cho vanishing.
Bài này nối tiếp Bài 35 — Vanilla RNN, đào sâu vấn đề gradient đã ghi nhận ở mục 14 của bài đó. Bài Bài 37 — LSTM là lời giải thiết kế đầu tiên.
Recap Vanilla RNN forward
Vanilla RNN (B35) tại mỗi time step cập nhật hidden state:
\[ h_t = \tanh(W_{xh}\, x_t + W_{hh}\, h_{t-1} + b_h) \]
Đặt \( z_t = W_{xh}\, x_t + W_{hh}\, h_{t-1} + b_h \), gọi \( z_t \) là pre-activation. Khi đó \( h_t = \tanh(z_t) \).
Tính chất quan trọng cho bài này:
- Cùng \( W_{hh} \) được áp dụng ở mọi \( t \) — weight sharing through time.
- Activation \( \tanh \) bị saturate: khi \( |z| \) lớn, \( \tanh(z) \to \pm 1 \) và \( \tanh'(z) \to 0 \).
- Đạo hàm \( \tanh'(z) = 1 - \tanh^2(z) \in (0, 1] \), max tại \( z = 0 \) bằng 1.
Ba tính chất này gặp nhau trong BPTT tạo ra vanishing gradient.
Long-range dependency — ví dụ tiếng Pháp
Một ví dụ kinh điển từ Bengio và cộng sự (1994):
"Tôi sinh ra ở Pháp ... [nhiều câu khác] ... vì vậy tôi nói tiếng [?]."
Để dự đoán token cuối là "Pháp" (ngôn ngữ), model cần ghi nhớ từ "Pháp" (địa danh) cách hàng chục token trước. Đây là long-range dependency.
Thực nghiệm trên Vanilla RNN cho thấy: model "quên" context cách \( > 10\text{–}20 \) step. Lý thuyết giải thích bằng vanishing gradient — tín hiệu học cần đi ngược từ token cuối qua chuỗi dài để cập nhật weight liên quan đến token "Pháp" ở đầu, nhưng gradient bị "tan" về 0 trước khi tới nơi.
Các task khác cũng gặp long-range dependency:
- Code completion: dấu mở ngoặc cách xa cần khớp với dấu đóng.
- Time series forecasting: chu kỳ yearly, weekly cần truyền qua hàng trăm step.
- Music generation: motif lặp lại sau hàng chục nốt.
- DNA / protein: dependency giữa các vị trí cách xa hàng nghìn base pair.
BPTT — tích Jacobian qua time step
Xét loss \( L \) tính tại step \( T \) (many-to-one). Để cập nhật \( W_{hh} \), ta cần gradient \( \partial L / \partial W_{hh} \). Vì \( W_{hh} \) tham gia ở mọi step, gradient là tổng đóng góp qua mọi \( t \):
\[ \frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^{T} \frac{\partial L}{\partial h_T} \cdot \frac{\partial h_T}{\partial h_t} \cdot \frac{\partial h_t}{\partial W_{hh}} \]
Phần "đi xuyên thời gian" nằm ở thừa số \( \partial h_T / \partial h_t \). Theo chain rule:
\[ \frac{\partial h_T}{\partial h_t} = \prod_{k=t+1}^{T} \frac{\partial h_k}{\partial h_{k-1}} \]
Tích này có \( T - t \) factor. Khi \( T - t \) lớn (vd 100), tích trở thành cực nhỏ hoặc cực lớn tùy magnitude từng factor — đó là gốc của vanishing / exploding gradient.
Tương tự, gradient của loss theo hidden state ban đầu:
\[ \frac{\partial L}{\partial h_0} = \frac{\partial L}{\partial h_T} \prod_{t=1}^{T} \frac{\partial h_t}{\partial h_{t-1}} \]
Đây là biểu thức trực diện nhất để thấy gradient bị triệt tiêu khi truyền ngược qua \( T \) step.
Jacobian một step \( \partial h_t / \partial h_{t-1} \)
Để hiểu tích trên, ta cần biểu thức của một factor. Từ \( h_t = \tanh(z_t) \) với \( z_t = W_{xh} x_t + W_{hh} h_{t-1} + b_h \):
\[ \frac{\partial h_t}{\partial h_{t-1}} = \mathrm{diag}\!\left( \tanh'(z_t) \right) \cdot W_{hh} \]
Đây là một ma trận \( d_h \times d_h \). Diagonal \( \mathrm{diag}(\tanh'(z_t)) \) chứa các giá trị \( \tanh'(z_{t,i}) = 1 - \tanh^2(z_{t,i}) \) cho từng phần tử hidden. Ma trận \( W_{hh} \) là weight recurrent. Một số tài liệu (vd Pascanu và cộng sự, 2013) viết:
\[ \frac{\partial h_t}{\partial h_{t-1}} = W_{hh}^\top \, \mathrm{diag}\!\left( \tanh'(z_t) \right) \]
(Khác nhau ở quy ước row/column gradient. Magnitude tích vẫn giống.)
Tích Jacobian qua \( T - t \) step trở thành:
\[ \frac{\partial h_T}{\partial h_t} = \prod_{k=t+1}^{T} \mathrm{diag}\!\left( \tanh'(z_k) \right) \cdot W_{hh} \]
Mỗi factor đóng góp 2 thứ làm "co" hoặc "giãn" gradient.
Hai factor: \( \tanh'(z) \) và \( W_{hh} \)
Factor 1 — \( \tanh'(z_t) \in (0, 1] \). Đây là factor luôn co lại:
- Tại \( z = 0 \): \( \tanh'(0) = 1 \) — không co.
- Tại \( z = \pm 2 \): \( \tanh'(\pm 2) \approx 0.07 \) — co 14 lần.
- Khi \( |z| \to \infty \) (tanh saturate): \( \tanh'(z) \to 0 \) — gradient triệt tiêu.
Trong RNN train với init thông thường, pre-activation \( z_t \) sẽ rời 0 sau vài step → \( \tanh'(z_t) < 1 \) gần như luôn xảy ra. Nhân nhiều factor \( < 1 \) qua \( T \) step → tích \( \to 0 \).
Factor 2 — \( W_{hh} \). Đây là factor có thể co hoặc giãn:
- Spectral radius \( \rho(W_{hh}) < 1 \): tích \( W_{hh}^k \) co theo cấp số nhân — vanishing.
- Spectral radius \( \rho(W_{hh}) > 1 \): tích bùng nổ — exploding.
- Spectral radius \( \rho(W_{hh}) = 1 \): biên ổn định lý thuyết — khó duy trì khi train.
Kết hợp 2 factor: ngay cả khi \( W_{hh} \) "vừa phải" (\( \rho \approx 1 \)), nhân thêm \( \tanh'(z) \le 1 \) ép tích về \( < 1 \) → vanishing là kết quả mặc định của Vanilla RNN.
Spectral radius của \( W_{hh} \) quyết định
Spectral radius \( \rho(W_{hh}) \) là giá trị tuyệt đối lớn nhất của eigenvalue. Với tích \( W_{hh}^k \) trên một vector hidden ngẫu nhiên:
\[ \| W_{hh}^k v \| \le \| W_{hh} \|^k \cdot \| v \|, \qquad \| W_{hh} \| \approx \rho(W_{hh}) \]
(Approx này dùng spectral norm; với ma trận đối xứng hoặc đường chéo hóa được, đẳng thức xảy ra dọc eigenvector lớn nhất.)
Bengio và cộng sự (1994) chứng minh: điều kiện sufficient để gradient vanish khi \( T \to \infty \) là singular value lớn nhất của Jacobian một step nhỏ hơn 1. Với \( \tanh \), khi \( \rho(W_{hh}) < 1 / \sup |\tanh'| = 1 \), gradient vanish chắc chắn.
Pascanu và cộng sự (2013) bổ sung phía exploding: nếu \( \rho(W_{hh}) > 1 \), tồn tại hidden trajectory để gradient bùng nổ exponential — trong thực hành, exploding xảy ra ngay cả khi điều kiện đủ chưa đáp ứng nếu \( \tanh'(z) \) gần 1 (init nhỏ).
Hệ quả thiết kế:
- Init \( W_{hh} \) sao cho \( \rho(W_{hh}) \approx 1 \) (orthogonal init) giảm vanishing ban đầu.
- Vẫn không tránh được nhân \( \tanh'(z) \) → cần kiến trúc gating (LSTM/GRU).
Demo numeric — \( 0.5^{100} \) và \( 1.1^{100} \)
Để cảm nhận magnitude, giả sử factor scalar trung bình của Jacobian một step là \( c = \| \tanh'(z) \cdot W_{hh} \| \). Tích \( T \) step:
\[ c^T \]
| \( c \) | \( T = 10 \) | \( T = 50 \) | \( T = 100 \) |
|---|---|---|---|
| 0.5 | \( \approx 10^{-3} \) | \( \approx 10^{-15} \) | \( \approx 10^{-30} \) |
| 0.9 | \( \approx 0.35 \) | \( \approx 5.2 \cdot 10^{-3} \) | \( \approx 2.7 \cdot 10^{-5} \) |
| 1.0 | 1.0 | 1.0 | 1.0 |
| 1.1 | \( \approx 2.6 \) | \( \approx 117 \) | \( \approx 1.4 \cdot 10^{4} \) |
| 1.5 | \( \approx 58 \) | \( \approx 6.4 \cdot 10^{8} \) | \( \approx 4.1 \cdot 10^{17} \) |
Quan sát:
- \( c = 0.5 \), \( T = 100 \): gradient bị nén còn \( 10^{-30} \) — vượt độ chính xác
float32(\( \sim 10^{-7} \)) → underflow về 0 hoàn toàn. - \( c = 0.9 \), \( T = 100 \): còn \( 2.7 \cdot 10^{-5} \) — chưa underflow nhưng so với gradient ở step gần (\( \sim 1 \)) thì nhỏ hơn \( 10^4 \) lần → effective không học.
- \( c = 1.1 \), \( T = 100 \): gradient \( 10^4 \) — bắt đầu unstable; \( c = 1.5 \) → \( 10^{17} \) → NaN ngay.
Đây là ý do: chuỗi \( T > 50 \) gần như không thể train Vanilla RNN với gradient nguyên thủy.
Exploding gradient — gradient clipping
Exploding gradient dễ phát hiện: thấy NaN trong loss sau vài iteration, hoặc loss tăng vọt rồi crash. Fix phổ biến — gradient clipping (Pascanu và cộng sự, 2013):
\[ g \leftarrow g \cdot \frac{\text{threshold}}{\max(\|g\|, \text{threshold})} \]
Nếu \( \|g\| \le \text{threshold} \) thì giữ nguyên; nếu lớn hơn thì scale về norm bằng đúng threshold. Hướng gradient không đổi, chỉ giới hạn magnitude.
import torch.nn as nn
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
Threshold điển hình:
- RNN ngôn ngữ: 0.25 — 5.0 (thường 1.0).
- Time series LSTM: 1.0 — 10.0.
- Transformer: ít cần clipping nặng, thường 1.0 cho an toàn.
Lưu ý: clip_grad_norm_ tính norm trên toàn bộ parameter (concat tất cả gradient thành 1 vector). Có biến thể clip_grad_value_ cắt theo từng element — ít dùng hơn vì méo hướng gradient.
Vì sao vanishing khó fix hơn exploding
Bất đối xứng giữa hai vấn đề:
- Exploding: gradient có magnitude lớn nhưng vẫn có tín hiệu. Hướng đúng, chỉ cần rescale → clipping đủ.
- Vanishing: gradient tụt về 0 → mất tín hiệu. Không có gì để "scale lên" — nhân với hằng số chỉ ra một con số khác gần 0. Hơn nữa, với
float32đã underflow, dữ liệu thực sự không tồn tại.
Vì vậy, vanishing không thể fix bằng hậu xử lý gradient. Cần một trong hai hướng:
- Thay đổi kiến trúc để gradient có đường đi không phải nhân tích Jacobian lặp đi lặp lại — đây là motivation của LSTM (B37): cell state \( c_t \) đi qua gating cộng (thay vì nhân) tạo ra "highway" cho gradient.
- Bypass time step hoàn toàn — Transformer (Vaswani và cộng sự, 2017) dùng attention nối trực tiếp mọi cặp token, gradient chỉ qua 1 layer attention chứ không nhân T lần.
Diagnose — plot gradient norm theo time step
Trước khi đổi kiến trúc, cần xác nhận RNN có thực sự vanish trên data của mình. Cách diagnose: plot \( \| \partial L / \partial h_t \| \) theo \( t \).
- Vanishing: norm tại \( t = 1 \) thấp hơn nhiều bậc so với \( t = T \) (vd \( 10^{-10} \) vs \( 10^{0} \)).
- Exploding: norm tăng exponential khi đi ngược về \( t = 1 \), hoặc NaN xuất hiện trong loss.
- Healthy: norm relatively flat (cùng order of magnitude) qua time step.
import torch
x = torch.randn(1, 100, 4) # (B=1, T=100, d_x=4)
h = torch.zeros(1, 8, requires_grad=True)
W_xh = torch.randn(4, 8) * 0.1
W_hh = torch.randn(8, 8) * 0.3 # spectral radius nhỏ → vanish
hidden_states = [h]
for t in range(100):
h = torch.tanh(x[:, t] @ W_xh + h @ W_hh)
h.retain_grad() # giữ gradient để query sau
hidden_states.append(h)
loss = hidden_states[-1].sum()
loss.backward()
grad_norms = [hs.grad.norm().item() if hs.grad is not None else 0.0
for hs in hidden_states]
# In ra để thấy vanish: grad_norms[0] << grad_norms[-1]
Trên hardware thật, vẽ grad_norms theo \( t \) bằng matplotlib — đường cong tụt theo cấp số nhân khi đi từ \( T \) về 0 là dấu hiệu vanishing.
Vì sao MLP sâu không vanish nặng như RNN
MLP sâu cũng có vấn đề vanishing — nhưng nhẹ hơn RNN cùng độ sâu. Lý do:
| Tiêu chí | MLP sâu \( L \) layer | RNN \( T \) step |
|---|---|---|
| Weight | \( W^{(1)}, W^{(2)}, \ldots, W^{(L)} \) — khác nhau | Cùng \( W_{hh} \) qua mọi step |
| Variance Jacobian | Có thể trung bình ra (không monotonic) | Tích lũy theo cấp số nhân |
| Activation phổ biến | ReLU (đạo hàm 0 hoặc 1) | \( \tanh \) (đạo hàm \( \le 1 \)) |
| Trick có sẵn | BatchNorm, residual connection (B23), He init | Ít trick áp được trực tiếp |
Modern MLP/CNN với residual connection (ResNet, He và cộng sự, 2015) có thể train sâu 100+ layer dễ dàng. Vanilla RNN qua \( T = 100 \) step thì không.
Đây là gợi ý: nếu áp được ý tưởng residual / skip connection lên RNN, vanishing sẽ giảm — LSTM cell state đi thẳng qua time là một dạng "residual through time".
Solutions cho vanishing — kiến trúc + init
Các hướng giảm vanishing đã được đề xuất qua các năm, từ tweak đến thay kiến trúc hẳn:
- LSTM (B37, Hochreiter & Schmidhuber, 1997): thêm cell state \( c_t \) update bằng phép cộng có gate (forget gate). Gradient theo \( c_t \) đi qua đường tuyến tính, tránh nhân tích \( \tanh' \). Vẫn là RNN nhưng dùng cell có "highway".
- GRU (B38, Cho và cộng sự, 2014): simplified LSTM, ít gate hơn (update + reset), tham số ít hơn ~25% nhưng gần ngang LSTM trên nhiều task.
- Residual / skip connection cho RNN: cộng thẳng \( h_{t-1} \) vào \( h_t \). Ý tưởng từ ResNet áp dụng theo time.
- Attention (Bahdanau và cộng sự, 2014; B40): decoder attend trực tiếp vào hidden state mọi step của encoder, bypass nhân tích Jacobian qua time.
- Transformer (Vaswani và cộng sự, 2017): vứt bỏ recurrence, chỉ dùng attention. Gradient qua 1 layer attention, không có "qua time".
- Activation thay thế: ReLU thay \( \tanh \) — đạo hàm là 0 hoặc 1, không saturate phía dương. Đỡ vanishing nhưng dễ exploding hơn (Le và cộng sự, 2015: IRNN).
- Orthogonal init cho \( W_{hh} \): \( W_{hh} \) là ma trận trực giao → singular value đều bằng 1 → \( \rho(W_{hh}) = 1 \) ban đầu. Giảm vanish trong giai đoạn đầu train (Saxe và cộng sự, 2013).
- Identity init: \( W_{hh} = I \) (Le và cộng sự, 2015). Init "không quên" — hidden state truyền y nguyên qua time, gradient cũng truyền y nguyên.
- Truncated BPTT: cắt chuỗi thành chunk ngắn (vd 32 step), backward chỉ trong chunk. Không giải quyết vanishing nội tại nhưng giới hạn vấn đề trong chunk.
Trong thực hành hiện nay: dùng LSTM/GRU khi cần RNN; còn Transformer là default cho hầu hết task chuỗi dài có đủ data và compute.
Lịch sử — Hochreiter, Bengio, LSTM, attention
- 1991: Hochreiter trong luận văn diploma (Đại học München) chỉ ra bản chất vanishing/exploding gradient của RNN qua phân tích Jacobian — phát hiện được công nhận muộn vì viết bằng tiếng Đức và không xuất bản rộng.
- 1994: Bengio, Simard, Frasconi xuất bản "Learning Long-Term Dependencies with Gradient Descent is Difficult" trên IEEE — phân tích formal, chứng minh sufficient condition.
- 1997: Hochreiter & Schmidhuber công bố LSTM (Neural Computation) — kiến trúc đầu tiên giải quyết vanishing hiệu quả.
- 2013: Pascanu, Mikolov, Bengio (ICML) — "On the difficulty of training Recurrent Neural Networks", đề xuất gradient clipping và phân tích kỹ exploding.
- 2014: Cho và cộng sự công bố GRU — simplification của LSTM. Cùng năm: Bahdanau và cộng sự đề xuất attention cho NMT.
- 2017: Vaswani và cộng sự (NeurIPS) — "Attention Is All You Need", Transformer bỏ hẳn RNN, dependency dài học được trực tiếp qua self-attention.
- 2018–nay: LSTM/GRU thoái vị trong NLP scale lớn (BERT, GPT đều Transformer). Vẫn còn dùng trong streaming, on-device, time series tabular.
Một số giai thoại: Hochreiter (1991) bị reject paper LSTM ban đầu nhiều lần — bị cho là quá phức tạp cho lợi ích thu được. Đến khi GPU + RNN-LM bùng nổ 2014–2016 (Karpathy, Sutskever), LSTM mới được chú ý đại trà. Câu chuyện cho thấy: một số thiết kế cần đợi compute và data đủ lớn để chứng minh giá trị.
Hậu quả thực tế cho production
- Vanilla RNN gần như không xuất hiện trong production model 2025+. Tài liệu vẫn dạy để hiểu motivation các kiến trúc sau.
- LSTM / GRU vẫn dùng trong: on-device ASR (Apple, Google), keyboard next-word prediction, time series forecasting (M5 competition Top 10 đa số dùng LSTM/GRU baseline), một số agent control loop.
- Transformer chiếm phần lớn NLP, vision (ViT), code generation, multimodal.
- Mamba / State Space Model (Gu & Dao, 2023): hồi sinh ý tưởng RNN với cell được thiết kế để không vanish, parallelizable. Đang là hướng nghiên cứu sôi động.
Khi đọc paper hoặc maintain code legacy:
- Thấy
nn.RNNđơn thuần trên chuỗi dài → cờ đỏ. Verify training loss có tụt không, plot gradient norm. - Thấy
clip_grad_norm_+nn.LSTM+num_layers=2, dropout=0.2→ setup chuẩn cho LSTM training.
Code Python — diagnose & clip
Demo end-to-end: train vanilla RNN trên chuỗi 200 step, observe vanishing, apply clipping cho exploding.
import torch
import torch.nn as nn
torch.manual_seed(0)
T, d_x, d_h, V = 200, 16, 32, 8
class TinyRNN(nn.Module):
def __init__(self):
super().__init__()
self.rnn = nn.RNN(d_x, d_h, batch_first=True, nonlinearity="tanh")
self.head = nn.Linear(d_h, V)
def forward(self, x):
h, _ = self.rnn(x)
return self.head(h[:, -1]) # many-to-one
model = TinyRNN()
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
def step(clip=False):
x = torch.randn(32, T, d_x)
y = torch.randint(0, V, (32,))
logits = model(x)
loss = loss_fn(logits, y)
opt.zero_grad()
loss.backward()
if clip:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# In gradient norm theo parameter
g_norms = {n: p.grad.norm().item() for n, p in model.named_parameters()}
opt.step()
return loss.item(), g_norms
for i in range(5):
loss, gn = step(clip=True)
print(f"iter {i} loss={loss:.4f} |grad W_hh|={gn['rnn.weight_hh_l0']:.4e}")
Quan sát điển hình trên \( T = 200 \):
- Không clip:
|grad W_hh|có thể nhảy về \( 10^2 \) → \( 10^6 \) trong vài iter → loss NaN. - Có clip
max_norm=1.0: gradient bị giữ trong phạm vi an toàn, loss giảm chậm. - Loss khó hội tụ vì task random, nhưng đủ thấy tác dụng của clipping.
Demo numeric magnitude:
print(0.5 ** 100) # 7.888e-31 → underflow trong nhiều phép tính
print(0.9 ** 100) # 2.656e-05 → còn dùng được nhưng yếu
print(1.1 ** 100) # 13780.6 → bắt đầu unstable
Bài tập
- Tính bằng tay \( 0.9^{100} \), \( 0.95^{100} \), \( 1.05^{100} \). Với
float32(giới hạn \( \sim 10^{-38} \) đến \( 10^{38} \)), giá trị nào underflow / overflow? Suy ra với \( T = 100 \) thì \( c \) tối thiểu phải bao nhiêu để gradient không underflow? - Train một
nn.RNNtrên next-char prediction (skeleton từ B35 mục 18) vớiseq_len = 50rồiseq_len = 200. Theo dõi loss training: có hiện tượng plateau khiseq_len = 200không? - Thêm
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)vào training loop ở câu 2 và so sánh: số iteration tới khi loss tụt dưới ngưỡng, NaN xuất hiện không. - Plot
|grad|củarnn.weight_hh_l0qua các iteration, có và không clipping. Vẽ trên cùng axis log y. - Init
W_hhorthogonal:nn.init.orthogonal_(model.rnn.weight_hh_l0). Train lại, theo dõi gradient norm. Có cải thiện so với default init không? - Verify công thức Jacobian: cho \( h_t = \tanh(W_{hh} h_{t-1}) \) (bỏ \( x_t \) cho đơn giản), tính \( \partial h_t / \partial h_{t-1} \) theo công thức tay, so với
torch.autograd.functional.jacobiantrên một input cụ thể. - Cho \( W_{hh} \) 2×2 với eigenvalue \( \{0.8, 1.2\} \). Tính tích \( W_{hh}^{50} \) và \( W_{hh}^{100} \). Quan sát: eigenvalue 0.8 vanish, 1.2 explode — gradient bùng nổ dominate theo direction eigenvector của 1.2.
Gợi ý đáp án ngắn
- \( 0.9^{100} \approx 2.66 \cdot 10^{-5} \) (còn float32). \( 0.95^{100} \approx 5.92 \cdot 10^{-3} \) (rộng). \( 1.05^{100} \approx 131.5 \) (ổn). Để gradient không underflow \( 10^{-38} \) sau 100 step: \( c \ge 10^{-0.38} \approx 0.41 \).
- \( T = 200 \) thường plateau loss cao rồi loanh quanh. \( T = 50 \) tụt nhanh hơn.
- Clipping giúp tránh NaN khi gradient bùng. Loss tụt chậm hơn so với explode-rồi-restart, nhưng ổn định.
- Đường
|grad|không clip: spike lớn (\( 10^3 \) hoặc hơn). Có clip: bám sát ngưỡng (\( \le 1 \)). - Orthogonal init: spectral radius \( = 1 \) ban đầu → gradient ít vanish trong giai đoạn đầu. Sau train một chút, \( W_{hh} \) thay đổi, hiệu ứng giảm dần.
- Tay: \( \partial h_t / \partial h_{t-1} = \mathrm{diag}(1 - \tanh^2(W_{hh} h_{t-1})) \cdot W_{hh} \). Khớp tới sai số numeric \( 10^{-6} \).
- \( W_{hh}^{100} \) có spectral radius \( \approx 1.2^{100} \approx 8.3 \cdot 10^{7} \). Tích vector qua direction eigenvector 1.2 bùng nổ; direction 0.8 vanish. Net: gradient lệch hẳn về direction explode.
Tóm tắt
- BPTT lan gradient ngược qua \( T \) time step: \( \partial L / \partial h_0 = \partial L / \partial h_T \cdot \prod_{t=1}^{T} \partial h_t / \partial h_{t-1} \).
- Mỗi factor một step: \( \partial h_t / \partial h_{t-1} = \mathrm{diag}(\tanh'(z_t)) \cdot W_{hh} \).
- \( \tanh'(z) \in (0, 1] \) → factor luôn co; nhân \( T \) lần với \( T \) lớn → tích \( \to 0 \) (vanishing).
- Spectral radius \( W_{hh} > 1 \) → tích bùng nổ (exploding) → NaN.
- Demo numeric: \( 0.5^{100} \approx 10^{-30} \), underflow float32; \( 1.1^{100} \approx 10^4 \) đã unstable.
- Vanilla RNN không học được long-range dependency \( > 10\text{–}20 \) step trong thực tế.
- Exploding dễ fix: gradient clipping
clip_grad_norm_(params, max_norm=1.0). - Vanishing khó fix vì tín hiệu mất hoàn toàn — không scale lên được.
- Hướng giải quyết vanishing: LSTM (B37), GRU (B38), residual/skip, attention/Transformer, orthogonal init, identity init.
- MLP sâu vanish nhẹ hơn vì weight khác nhau qua layer, có BatchNorm + residual + ReLU; RNN dùng chung \( W_{hh} \) qua time nên tích lũy nặng.
- Diagnose: plot \( \| \partial L / \partial h_t \| \) theo \( t \) — vanish khi norm tụt cấp số nhân, explode khi norm bùng exponential.
- Lịch sử: Hochreiter 1991 phát hiện; Bengio 1994 formal; LSTM 1997 lời giải đầu tiên; Pascanu 2013 clipping; Bahdanau 2014 attention; Vaswani 2017 Transformer.
- Vanilla RNN giờ chỉ học để hiểu motivation; production dùng LSTM/GRU hoặc Transformer.
- Bengio, Simard, Frasconi (1994) - Learning Long-Term Dependencies with Gradient Descent is Difficult
- Hochreiter & Schmidhuber (1997) - Long Short-Term Memory
- Hochreiter (1991) - Untersuchungen zu dynamischen neuronalen Netzen (Diploma thesis)
- Pascanu, Mikolov, Bengio (2013) - On the difficulty of training Recurrent Neural Networks (ICML)
- Saxe, McClelland, Ganguli (2013) - Exact solutions to the nonlinear dynamics of learning in deep linear neural networks
- Le, Jaitly, Hinton (2015) - A Simple Way to Initialize Recurrent Networks of Rectified Linear Units (IRNN)
- Bahdanau, Cho, Bengio (2014) - Neural Machine Translation by Jointly Learning to Align and Translate
- Cho et al. (2014) - Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation (GRU)
- Vaswani et al. (2017) - Attention Is All You Need
- Gu & Dao (2023) - Mamba: Linear-Time Sequence Modeling with Selective State Spaces
- PyTorch Docs - clip_grad_norm_
- PyTorch Docs - nn.init.orthogonal_
- Goodfellow, Bengio, Courville - Deep Learning (Chương 10: Sequence Modeling)
- Andrej Karpathy - The Unreasonable Effectiveness of Recurrent Neural Networks
- Stanford CS231n - Recurrent Neural Networks
