6.2 LSTM(1997)與 GRU、Seq2Seq(2014)#
一、歷史背景:為「長記憶」而設計的細胞#
6.1 節診斷出普通 RNN 的絕症:雅可比連乘使長程梯度指數消失,且儲存記憶與梯度穩定在結構上互斥。Sepp Hochreiter(1991 年的文憑論文即診斷此問題)與 Jürgen Schmidhuber 於 1997 年發表 LSTM(Long Short-Term Memory,長短期記憶網路),給出第一個系統性的解法。其核心思想可以一句話概括:
與其讓梯度在非線性迷宮中連乘,不如修一條「近恆等」的線性高速公路,再用可學習的門控制它的開關。
LSTM 自 1997 年發表後沉寂多年,2000 年代中期起在語音辨識(Google 2015 年起用於語音搜尋)、機器翻譯中大量落地;2014 年 Cho 等人的 GRU(簡化版)與 Seq2Seq(編碼器-解碼器)把循環網路推到神經機器翻譯的舞台中心——同時也暴露出固定向量瓶頸,直接催生了下一章的注意力革命。本章是 RNN 時代的黃金與黃昏。
二、LSTM:數學架構全解#
2.1 細胞狀態:梯度的高速公路#
LSTM 引入兩條狀態線:細胞狀態 \(c_t\) (長期記憶)與隱狀態 \(h_t\) (短期輸出)。細胞狀態的更新以加法為主:
梯度沿 \(c\) 的回傳路徑為:
這就是解藥的全部數學:梯度不再經過 \(W_{hh}\) 的矩陣連乘與飽和非線性,而是乘以一個每步可學習、通常趨近 1 的標量(門值)。當 \(f_t \approx 1\) ,梯度像流過恆等映射一樣存活——遺忘門學到「忘記」( \(f\to 0\) )或「銘記」( \(f\to 1\) )的時間模式。連乘問題被轉化為「門值是否保持接近 1」的學習問題。
2.2 三個門的完整公式#
輸入 \(x_t\) 與上一隱狀態 \(h_{t-1}\) 先拼接,經線性變換產生各門:
flowchart LR
X["x_t"] --> J["拼接 [h_{t-1}, x_t]"]
H["h_{t-1}"] --> J
J --> F["遺忘門 f_t=σ(·)"]
J --> I["輸入門 i_t=σ(·)"]
J --> C["候選 c̃_t=tanh(·)"]
COLD["c_{t-1}"] -->|"×f_t"| ADD((+))
C -->|"×i_t"| ADD
ADD --> CNEW["c_t(細胞狀態,線性通路)"]
CNEW --> T["tanh"]
J --> O["輸出門 o_t=σ(·)"]
T -->|"×o_t"| HNEW["h_t"]
HNEW -->|"→ 下一時步"| J
CNEW -->|"→ 下一時步"| COLD設計的三個精妙之處:
- 乘法門控 + 加法更新:記憶的寫入/清除是乘性的、逐元素的、由 sigmoid 平滑控制——「門」是可微的開關,整體仍可反向傳播。
- 讀寫分離:寫入細胞( \(i_t \tilde{c}_t\) )與讀出細胞( \(o_t \tanh(c_t)\) )由不同門控制,記憶可以被「保管而不暴露」。
- 候選記憶的獨立性: \(\tanh\) 候選 \(\tilde c_t\) 與門值解耦,避免「內容與開關互相干擾」。
(2000 年 Gers 加入的窺視孔連接(peephole)讓門直接看到 \(c\) ,進一步提升精確計時能力;實務上常省略。)
三、GRU(2014):極簡主義的門控#
Cho 等人在機器翻譯工作中提出門控循環單元(GRU),把 LSTM 的三門兩態簡化為兩門一態:
與 LSTM 的對比:
| 維度 | LSTM | GRU |
|---|---|---|
| 參數量 | 較多(3 門 + 2 態) | 少約 25% |
| 記憶/輸出分離 | 有( \(c\) 與 \(h\) ) | 無( \(h\) 一態兩用) |
| 表現 | 大資料、長依賴略優 | 多數任務與 LSTM 相當 |
| 定位 | 工業長序列主力 | 資源受限與快速原型 |
GRU 的哲學意義:它證明「加法式狀態更新 + 可學習門控」這一原則才是本質,LSTM 的其他結構是可精簡的細節。這種「把架構歸約到最小充分原則」的方法論,也是日後 Transformer 精簡 Seq2Seq 的精神。
四、Seq2Seq(2014):循環網路的巔峰與瓶頸#
4.1 編碼器-解碼器架構#
Sutskever、Vinyals、Le(2014,Google)與 Cho 等(2014)同時提出序列到序列(Seq2Seq)框架解決機器翻譯:
- 編碼器把源句壓縮成一個固定向量 \(s\) (context vector);
- 解碼器以 \(s\) 為初始條件,自回歸地生成目標句;
- 訓練目標是整個目標句的對數似然,用 BPTT 端到端學習。
這是神經機器翻譯(NMT)的起點:翻譯第一次成為「一個可微的端到端模型」,取代了數十年的「對齊 + 短語表 + 語言模型」流水線工程。
4.2 固定向量瓶頸#
架構的優雅掩不住它的死穴:無論源句多長多複雜,全部信息必須擠進固定維度的向量 \(s\) 。長句翻譯時編碼器的記憶被沖刷,BLEU 分數隨句長顯著下滑(Bahdanau 2015 論文的圖 1 正是這條曲線)。理論上,把任意長序列無損壓縮到定長向量,資訊論上不可能。
這個瓶頸是本章與下一章的臨界點:它不是訓練技巧能解的(LSTM 已把梯度問題解決),而是表示架構的天花板。解法呼之欲出——解碼器不該只看一個壓縮向量,而應該隨時回頭查閱編碼器的全部隱狀態。這就是注意力(第 7 章)。
flowchart LR
A["1997 LSTM<br>解決梯度消失"] --> B["2014 GRU<br>門控原則極簡化"]
B --> C["2014 Seq2Seq<br>NMT 端到端化"]
C --> D["瓶頸:定長 context vector<br>長句性能崩塌"]
D --> E["2015 Bahdanau Attention<br>解碼時直接查閱全部編碼狀態"]
E --> F["2017 Transformer<br>全面捨棄循環"]五、程式碼實作#
5.1 從零實作 LSTM 單元#
import torch, torch.nn as nn
class MyLSTMCell(nn.Module):
def __init__(self, input_dim, hidden_dim):
super().__init__()
# 四個門的線性變換合成一個大矩陣(效率慣例)
self.W = nn.Linear(input_dim + hidden_dim, 4 * hidden_dim)
self.h_dim = hidden_dim
def forward(self, x, state):
h, c = state
gates = self.W(torch.cat([h, x], dim=-1))
f, i, o, c_tilde = gates.chunk(4, dim=-1)
f, i, o = torch.sigmoid(f), torch.sigmoid(i), torch.sigmoid(o)
c_tilde = torch.tanh(c_tilde)
c_new = f * c + i * c_tilde # ← 加法高速公路:梯度在此存活
h_new = o * torch.tanh(c_new)
return h_new, c_new
# 驗證與官方實作等價:
torch.manual_seed(0)
cell = MyLSTMCell(4, 8)
ref = nn.LSTMCell(4, 8)
with torch.no_grad():
W = torch.cat([ref.weight_hh, ref.weight_ih], dim=1).T
cell.W.weight.copy_(W)
cell.W.bias.copy_(ref.bias_ih + ref.bias_hh)
x = torch.randn(2, 4); h = torch.zeros(2, 8); c = torch.zeros(2, 8)
mine = cell(x, (h, c)); theirs = ref(x, (h, c))
print(torch.allclose(mine[0], theirs[0], atol=1e-6),
torch.allclose(mine[1], theirs[1], atol=1e-6)) # True True
5.2 手寫 Seq2Seq 翻譯器(玩具版)#
import torch, torch.nn as nn
class Encoder(nn.Module):
def __init__(self, vocab, emb=32, hid=64):
super().__init__()
self.emb = nn.Embedding(vocab, emb)
self.rnn = nn.GRU(emb, hid, batch_first=True)
def forward(self, src):
_, h = self.rnn(self.emb(src))
return h # context vector(瓶頸所在!)
class Decoder(nn.Module):
def __init__(self, vocab, emb=32, hid=64):
super().__init__()
self.emb = nn.Embedding(vocab, emb)
self.rnn = nn.GRU(emb, hid, batch_first=True)
self.out = nn.Linear(hid, vocab)
def forward(self, tgt, h):
out, _ = self.rnn(self.emb(tgt), h)
return self.out(out) # 每步對詞表打分
class Seq2Seq(nn.Module):
def __init__(self, src_v, tgt_v, **kw):
super().__init__()
self.enc, self.dec = Encoder(src_v, **kw), Decoder(tgt_v, **kw)
def forward(self, src, tgt):
return self.dec(tgt[:, :-1], self.enc(src)) # teacher forcing
# 訓練迴圈(真實翻譯需要單詞表與語料;此處示範張量流)
V = 100
model = Seq2Seq(V, V)
src = torch.randint(0, V, (16, 10)); tgt = torch.randint(0, V, (16, 12))
loss = nn.functional.cross_entropy(
model(src, tgt).reshape(-1, V), tgt[:, 1:].reshape(-1))
loss.backward()
print("Seq2Seq 端到端梯度已回傳;注意 context vector 維度 = hid = 64")
print("→ 無論 src 多長,全部語義被壓進這 64 維 —— 瓶頸即將引爆注意力革命")
5.3 測量瓶頸:句長 vs 性能#
import torch, torch.nn as nn
# 以「回聲任務」模擬:編碼長度 L 的隨機序列,解碼復述其中第 k 個符號
model = Seq2Seq(20, 20)
model.eval()
with torch.no_grad():
for L in [5, 10, 20, 40]:
src = torch.randint(0, 20, (64, L))
correct = 0
for k in range(L):
tgt_in = torch.full((64, 2), 0) # 簡化:直接問第 k 位
logits = model(src, torch.cat([src[:, :k+1], torch.zeros(64,1,dtype=torch.long)], 1))
correct += (logits.argmax(-1)[:, k] == src[:, k]).sum().item()
print(f"句長 {L:3d}: 重建準確率 = {correct / (64*L):.3f}")
# 準確率隨句長單調下降 —— 定長向量的信息容量天花板,Bahdanau 圖 1 的玩具重現
六、LSTM 時代的遺產與黃昏#
2015-2017 年,LSTM/GRU + Attention 的組合統治了 NLP:Google 翻譯 2016 年上線神經機器翻譯(GNMT,8 層 LSTM 編碼器 + 注意力),語音識別全面循環化。但黃昏已至,三個結構性缺陷註定被取代:
- 無法並行: \(h_t\) 依賴 \(h_{t-1}\) ,訓練必須串行;GPU 的萬核平行能力(第 8 章)在 RNN 上浪費殆盡。資料量增大時,訓練時間線性增長且無法用硬體抵消。
- 記憶的乘性脆弱:即使有門控,超長依賴(數百步以上)仍逐漸衰減;注意力可以直接連接任意兩位置。
- **路徑長度 \(O(n)\) **:位置 \(i\) 與 \(j\) 的信息交換需 \(|i-j|\) 步;Transformer 的自注意力把路徑縮為 \(O(1)\) 。
2017 年《Attention Is All You Need》以「捨棄循環,純注意力」宣告 RNN 時代的終結——但 LSTM 的思想遺產深刻存續:門控(Transformer 中的殘差與歸一化承擔類似的梯度保護功能)、加法式狀態更新(近年狀態空間模型 Mamba 的選擇性掃描可視為 LSTM 原則在線性注意方向的復活)。
七、本節重點#
- LSTM 的本質是加法式細胞狀態 + 乘法門控:梯度沿 \(c_t = f_t \odot c_{t-1} + i_t \odot \tilde c_t\) 回傳時只需乘以門值(可學習、趨近 1),把連乘消失問題轉化為可學習的記憶管理問題。
- 遺忘/輸入/輸出三門實現「寫入、保留、暴露」的解耦控制;GRU 證明最小充分架構是「更新門 + 重置門 + 凸組合更新」,參數省 25% 而效能多數相當。
- Seq2Seq(2014)以編碼器-解碼器把翻譯端到端化,開啟神經機器翻譯時代,但其定長 context vector 瓶頸使長句性能崩塌——瓶頸是表示層面的,訓練技巧無解。
- 瓶頸的直接解法是讓解碼器查閱全部編碼狀態(Bahdanau Attention,2015,7.1),最終演化為捨棄循環的 Transformer(2017,7.2)。
- RNN 輸給 Transformer 的三個結構性原因:串行不可並行、長路徑依賴、乘性記憶脆弱;其門控與加法更新思想在 Mamba 等新架構中復活。
八、思考問題#
- 請推導 LSTM 中 \(\partial c_t/\partial c_{t-1} = f_t\) (對角陣),並與普通 RNN 的 \(W_{hh}^\top \odot \phi'\) 對比,說明「近恆等梯度通路」的條件是什麼。遺忘門偏置 \(b_f\) 初始化為 1(或 2)的慣例有何理論根據?
- GRU 的更新 \(h_t = (1-z_t)h_{t-1} + z_t\tilde h_t\) 是凸組合。這種「插值式」更新與 LSTM 的「加法式」 \(c_t = f_t c_{t-1} + i_t \tilde c_t\) 有何表達能力差異?
- Seq2Seq 的瓶頸在資訊論上為何不可繞過?給出資訊論論證(提示:定長向量的互資訊上限)。什麼樣的任務對瓶頸不敏感?
- 若你是 2016 年的工程師,GPU 預算固定,面對 10 倍增長的訓練語料:LSTM 的串行訓練與 Transformer 的可並行訓練各意味什麼?請從「算力擴展性」角度論證架構選擇。
- 門控思想(可微開關)在 Transformer 中以何種形式存續?請比較殘差連接、LayerNorm 與門控在「保護梯度流」上的異同。