1997 - LSTM 長短期記憶#
案件摘要#
1997 年,Hochreiter 與 Schmidhuber 發表 LSTM(Long Short-Term Memory):用三個閘門守護一條「細胞狀態」的高速通道,讓梯度能跨越數百個時間步而不消散:
一句話:記憶不是靠反覆改寫,而是靠加法與閘門。這是循環神經網路第一次真正學會「長期記憶」,統治序列建模近二十年。
前因 -- 為什麼會有這個案子#
- 1986 年反向傳播隨時間展開(BPTT)後(見 1986-反向傳播演算法.md),循環網路理論上可訓練——但實務上梯度在時間軸上指數級消失或爆炸。
- 1991 年 Hochreiter 的學士論文首次嚴格證明:RNN 的梯度大小由遞迴雅可比矩陣的譜半徑決定, \(\left\|\prod_{k} W_h \right\|\) 隨時間步指數衰減——跨越 10 步以上,誤差訊號就歸零。長期依賴(long-term dependencies)學不會。
- Schmidhuber 的動機:真實的序列任務(語言、控制、時間序列)中,關鍵事件間隔可達數百步——「現在的決定取決於一千步之前的線索」,淺記憶無能為力。
- 當時的替代方案(如 1994 年 Bengio 等的分析)都指向同一死結:要嘛梯度消失,要嘛爆炸。
- 另一條線索:同為 1997 年,1997-DeepBlue搜尋智能.md 代表符號派搜尋的巔峰——連接派需要一個能處理序列的殺手級模型來證明自己。
線索與推理 -- 數學式、程式、理論#
第一條線索:病灶的解剖——為什麼 RNN 忘記#
RNN 的狀態遞迴為 \(h_t = \tanh(W_h h_{t-1} + W_x x_t)\) 。對 \(t_0\) 時刻的輸入求梯度:
若 \(W_h\) 的譜半徑 \(<1\) ,乘積指數衰減; \(>1\) 則爆炸。而且: \(\tanh'\) 在大部分區間接近 0,進一步掐死訊號。結論:梯度無法跨越長距離——不是優化技巧的問題,是架構的問題。
第二條線索:解方——細胞狀態與三個閘門#
LSTM 的解方是兩條路徑分離:細胞狀態 \(c_t\) 走加法高速通道,隱藏狀態 \(h_t\) 走讀寫介面:
梯度沿細胞狀態傳播時:
當遺忘閘 \(f_t \approx 1\) 且輸入閘 \(i_t \approx 0\) ,梯度幾乎無衰減地直通——網路可以「保持記憶、不動它」,想寫才寫。這就是「常數誤差轉盤(constant error carousel)」。
| 閘門 | 類比 | 梯度角色 |
|---|---|---|
| 遺忘閘 \(f_t\) | 刪除鍵 | 控制 \(\frac{\partial c_t}{\partial c_{t-1}} \approx f_t\) ,接近 1 時梯度直通 |
| 輸入閘 \(i_t\) | 寫入鍵 | 防止新資訊冲掉舊梯度 |
| 輸出閘 \(o_t\) | 讀取鍵 | 讓記憶長存但不必然影響當下輸出 |
第三條線索:記憶的實證——合成測試與真實任務#
LSTM 論文的測試是「嵌入式 Reber 文法」:要判斷字串是否合法,必須記住數十步之前的一個字元。傳統 RNN 與後來的強化學習方法學不會,LSTM 幾千步內收斂。此後:
- 2000 年代 Gers 加上「窺孔(peephole)」連接,LSTM 學會數百步的計時任務。
- 2005 年前後的手寫生成與線上辨識(Schmidhuber 團隊的 CTC 2006)讓 LSTM 走進真實序列。
- 2007–2014 年:語音辨識(Google 的聲學模型)、機器翻譯、語言建模——LSTM 統治序列建模近二十年,是深度學習時代第一個「工業級序列智能」。
- 2014 年 Seq2Seq 用 LSTM 編碼-解碼翻譯,再加上注意力機制——這是 Transformer 的直系前奏(伏筆)。
第三條線索補遺:雙向與多層——LSTM 的組合術#
實務中的 LSTM 很少是單層單向:
| 變體 | 結構 | 適用 |
|---|---|---|
| 單向 LSTM | \(h_t\) 只看過去 | 線上預測、語言生成 |
| 雙向 LSTM(BiLSTM) | 前向 + 後向兩條鏈 | 需要上下文:語音辨識、NER |
| 深層 LSTM | 多層堆疊 | 大容量表示:Google 翻譯用 8 層 |
雙向的偵探視角:讓「未來」也成為線索——判斷一個詞的詞性,右邊的上下文與左邊同等重要。這個「前後文都是線索」的思想,在 2017-Transformer.md 的自注意力中被推到極致:任意位置互為上下文。
LSTM 的深層結構性限制: \(h_t\) 依賴 \(h_{t-1}\) ,必須逐步串行計算——訓練長序列無法並行,GPU 派不上用場。兇手的伏筆早已寫在架構裡:直連任意距離 + 完美並行的 2017-Transformer.md,將以自注意力取代循環,把 LSTM 送進歷史。
結案 -- 後果與影響#
- 證明「架構設計可以對抗梯度消失」——閘門與加法高速通道的思想,日後直接催生 2015 年 ResNet 的殘差連接 \(y = F(x) + x\) (見「科學與歷史/神經網路/」)。
- LSTM 成為 2010 年代序列建模的預設模型:語音、翻譯、手寫、音樂生成、時間序列。
- 閘門的「選擇性讀寫」理念滲透到整個深度學習:GRU(2014)、記憶網路、神經圖靈機。
- 2014 年 Seq2Seq(LSTM 編碼器 + 解碼器 + 注意力)開啟神經機器翻譯——注意力從 LSTM 的附屬品,在 2017 年反客為主(見 2017-Transformer.md)。
- 伏筆:「LSTM 被謀殺——兇手不是更強的記憶,而是直連任意距離 + 完美並行」。2017 年 Transformer 捨棄循環,用自注意力讓任意兩個位置直連,且序列計算完美並行,LSTM 退位。
關鍵人物與文獻#
- Sepp Hochreiter / Jürgen Schmidhuber:LSTM 的提出者。
- Hochreiter, Untersuchungen zu dynamischen neuronalen Netzen(學士論文,梯度消失證明), 1991。
- Hochreiter & Schmidhuber, Long Short-Term Memory, Neural Computation, 1997。
- Gers, Schmidhuber & Cummins, Learning to Forget: Continual Prediction with LSTM, 1999(遺忘閘與窺孔)。
- Sutskever, Vinyals & Le, Sequence to Sequence Learning with Neural Networks, 2014(Seq2Seq)。
- Vaswani et al., Attention Is All You Need, 2017(LSTM 的接棒者)。
- 相關案件:1986-反向傳播演算法.md、1997-DeepBlue搜尋智能.md、2017-Transformer.md(見「科學與歷史/人工智慧/」與「科學與歷史/神經網路/」)
補充 -- 程式實作(python + numpy + pytorch)#
本案 LSTM 閘門方程與「延遲回憶」實證的最小可執行版本,見 _code/1997-LSTM.py(已實測可跑):
# 1997 - LSTM 長短期記憶 (Hochreiter & Schmidhuber)
# 公式: f,i,o 閘 + c_t = f⊙c_{t-1} + i⊙c̃ (加法高速通道, ∂c_t/∂c_{t-1}≈f 直通梯度)
import numpy as np
import torch
import torch.nn as nn
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def lstm_cell_numpy(x, h_prev, c_prev, P):
"""單步 LSTM 前向 (numpy 手寫, 展示閘門數學)."""
z = np.concatenate([h_prev, x])
f = sigmoid(P["Wf"] @ z + P["bf"])
i = sigmoid(P["Wi"] @ z + P["bi"])
c_tilde = np.tanh(P["Wc"] @ z + P["bc"])
c = f * c_prev + i * c_tilde # 加法! 梯度直通的關鍵
o = sigmoid(P["Wo"] @ z + P["bo"])
h = o * np.tanh(c)
return h, c, {"f": f, "i": i, "o": o}
def main():
rng = np.random.default_rng(0)
d, hdim = 3, 4
P = {k: rng.standard_normal((hdim, hdim + d)) * 0.5 for k in ["Wf", "Wi", "Wc", "Wo"]}
P.update({k: np.zeros(hdim) for k in ["bf", "bi", "bc", "bo"]})
P["bf"] += 1.0 # forget-bias trick: 初始多記少忘
h, c = np.zeros(hdim), np.zeros(hdim)
print("numpy LSTM cell: 輸入 5 步, 觀察遺忘閘 f≈1 時細胞狀態被保留")
for t in range(5):
x = rng.standard_normal(d)
h, c, g = lstm_cell_numpy(x, h, c, P)
print(f" t={t} f={np.round(g['f'], 2)} |c|={np.linalg.norm(c):.2f}")
# torch: 延遲回憶任務 -- 看到開頭符號(0/1), 經過 T 步雜訊後複述 (RNN 殺手, LSTM 拿手)
torch.manual_seed(0)
T, N = 15, 1000
X = torch.randint(2, 4, (N, T)) # 其餘步為 2/3 雜訊
X[:, 0] = torch.randint(0, 2, (N,)) # 首符號只有 0/1 (平衡兩類)
y = X[:, 0].long()
net = nn.Sequential(nn.Embedding(4, 8), nn.LSTM(8, 32, batch_first=True),
)
lstm, clf = net[1], nn.Linear(32, 2)
opt = torch.optim.Adam(list(lstm.parameters()) + list(clf.parameters()) + list(net[0].parameters()), lr=1e-2)
for ep in range(30):
opt.zero_grad()
_, (hn, _) = lstm(net[0](X))
loss = nn.CrossEntropyLoss()(clf(hn[-1]), y)
loss.backward()
opt.step()
with torch.no_grad():
_, (hn, _) = lstm(net[0](X))
acc = (clf(hn[-1]).argmax(1) == y).float().mean().item()
print(f"torch LSTM 延遲 {T} 步回憶準確率: {acc:.2f} (普通 RNN 在此任務≈0.5 猜測水平)")
if __name__ == "__main__":
main()
執行結果(python3 _code/1997-LSTM.py,torch 2.12.0,CPU):
numpy LSTM cell: 輸入 5 步, 觀察遺忘閘 f≈1 時細胞狀態被保留
t=0 f=[0.82 0.67 0.74 0.65] |c|=0.56
t=1 f=[0.87 0.38 0.83 0.54] |c|=1.14
t=2 f=[0.81 0.48 0.85 0.58] |c|=1.39
t=3 f=[0.44 0.81 0.8 0.84] |c|=0.88
t=4 f=[0.47 0.89 0.68 0.84] |c|=0.71
torch LSTM 延遲 15 步回憶準確率: 1.00 (普通 RNN 在此任務≈0.5 猜測水平)
程式解說:numpy 版把本文第二條線索的六行方程逐行寫成程式——關鍵是 c = f*c_prev + i*c_tilde 的加法:當遺忘閘 f≈1、輸入閘 i≈0,細胞狀態原樣保留、梯度直通,即「常數誤差轉盤」。torch 版是論文「嵌入式 Reber 文法」測試的縮影:首符號 0/1 被 14 步雜訊淹沒後仍須複述,普通 RNN 在此只剩 0.5 猜測水平,LSTM 達 1.00——長程依賴問題被加法通道解決。實測附帶一課:任務設計必須平衡兩類(首符號 0/1 各半),否則多數類基線(0.75)會掩蓋模型到底學會了沒有。