跳轉到

1997 - LSTM 長短期記憶#

案件摘要#

1997 年,Hochreiter 與 Schmidhuber 發表 LSTM(Long Short-Term Memory):用三個閘門守護一條「細胞狀態」的高速通道,讓梯度能跨越數百個時間步而不消散:

\[ c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t \]

一句話:記憶不是靠反覆改寫,而是靠加法與閘門。這是循環神經網路第一次真正學會「長期記憶」,統治序列建模近二十年。

前因 -- 為什麼會有這個案子#

  • 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\) 時刻的輸入求梯度:

\[ \frac{\partial L_T}{\partial h_{t_0}} = \frac{\partial L_T}{\partial h_T} \prod_{t=t_0+1}^{T} \frac{\partial h_t}{\partial h_{t-1}}, \qquad \frac{\partial h_t}{\partial h_{t-1}} = \mathrm{diag}(\tanh') \cdot W_h \]

若 \(W_h\) 的譜半徑 \(<1\) ,乘積指數衰減; \(>1\) 則爆炸。而且: \(\tanh'\) 在大部分區間接近 0,進一步掐死訊號。結論:梯度無法跨越長距離——不是優化技巧的問題,是架構的問題。

第二條線索:解方——細胞狀態與三個閘門#

LSTM 的解方是兩條路徑分離:細胞狀態 \(c_t\) 走加法高速通道,隱藏狀態 \(h_t\) 走讀寫介面:

\[ \begin{aligned} f_t &= \sigma(W_f [h_{t-1}, x_t] + b_f) &&\text{遺忘閘:該刪多少舊記憶}\\ i_t &= \sigma(W_i [h_{t-1}, x_t] + b_i) &&\text{輸入閘:該寫多少新記憶}\\ \tilde{c}_t &= \tanh(W_c [h_{t-1}, x_t] + b_c) &&\text{候選記憶}\\ c_t &= f_t \odot c_{t-1} + i_t \odot \tilde{c}_t &&\text{細胞狀態(加法!)}\\ o_t &= \sigma(W_o [h_{t-1}, x_t] + b_o) &&\text{輸出閘:該讀出多少}\\ h_t &= o_t \odot \tanh(c_t) \end{aligned} \]

梯度沿細胞狀態傳播時:

\[ \frac{\partial c_t}{\partial c_{t-1}} = f_t \odot (\cdot) + \underbrace{\frac{\partial (i_t \odot \tilde c_t)}{\partial c_{t-1}}}_{\text{間接項}} \]

當遺忘閘 \(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)會掩蓋模型到底學會了沒有。