玩統計機器學習筆記

循環神經網路Recurrent Neural Networks

理解 RNN 如何以共享權重處理序列、為何隨時間反向傳播會造成梯度爆炸與消失,以及 LSTM 與 GRU 如何用閘門機制解決這個問題。

第 11 章,共 11 章|初版 2019 年 4 月・2026 年 10 月修訂

本章內容
  1. 11.1序列資料與 RNN
  2. 11.2隨時間反向傳播
  3. 11.3梯度爆炸與消失
  4. 11.4長短期記憶
  5. 11.5閘控循環單元
  6. 11.6LSTM 與 GRU 的比較
  7. 11.7後續發展
  8. 11.8參考資料

文字、語音、感測器讀數、交易紀錄……許多資料天生就是序列:順序本身帶有資訊,長度也不固定。前饋網路與第 10 章的 CNN 都沒有「先後」的概念,前饋網路還要求輸入大小固定。循環神經網路(Recurrent Neural Network, RNN)以一個隱藏狀態(hidden state)作為記憶,逐步讀入序列,每一步都用同一組權重更新這個狀態。

本章從最基本的 RNN 開始,說明它的訓練方式「隨時間反向傳播」,以及隨之而來的根本困難:梯度爆炸與梯度消失。接著介紹兩種以閘門(gate)緩解長期依賴問題的架構 LSTM 與 GRU,並比較兩者的取捨。

11.1序列資料與 RNNSequences and Vanilla RNN

最基本的 RNN(常稱為 vanilla RNN)在第 tt 步讀入輸入 xtx_t,結合上一步的隱藏狀態 ht−1h_{t-1},計算新的隱藏狀態與輸出:

ht=tanh⁡(Whhht−1+Wxhxt+bh)yt=Whyht+by \begin{aligned} h_t &= \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b_h) \\ y_t &= W_{hy} h_t + b_y \end{aligned}

hth_t 摘要了到目前為止看過的所有輸入,初始狀態 h0h_0 通常設為零向量。輸出 yty_t 視任務而定,例如在字元級語言模型中,yty_t 經過 softmax 後就是「下一個字元」的機率分布。

這組式子的關鍵是權重在時間上共享:WhhW_{hh}、WxhW_{xh}、WhyW_{hy} 在每一個時間步都相同。這帶來三個好處:參數量與序列長度無關;同一個模型可以處理任意長度的序列;在某個位置學到的模式,也能套用在其他位置。這與 CNN 讓同一個濾波器在空間上滑動是同樣的想法,只是共享的維度從空間換成了時間。

依輸入與輸出的對應方式,RNN 可以組成不同的結構:一對多(例如由一張影像產生描述文字)、多對一(例如判斷一段評論的情緒)、多對多(例如機器翻譯,或為每一個時間步標註類別)。

11.2隨時間反向傳播Backpropagation Through Time

訓練 RNN 時,把它沿時間展開(unroll),就變成一個很深的前饋網路,每一層對應一個時間步,而且所有層共用同一組權重。在這個展開的網路上套用反向傳播,就稱為隨時間反向傳播(Backpropagation Through Time, BPTT)。

總損失是各時間步損失的和 L=∑t=1TLtL = \sum_{t=1}^{T} L_t。由於權重是共享的,梯度要把每一個時間步的貢獻加總。以 WhhW_{hh} 為例:

∂L∂Whh=∑t=1T∑k=1t∂Lt∂ht ∂ht∂hk ∂+hk∂Whh \frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^{T} \sum_{k=1}^{t} \frac{\partial L_t}{\partial h_t} \, \frac{\partial h_t}{\partial h_k} \, \frac{\partial^{+} h_k}{\partial W_{hh}}

其中 ∂+hk/∂Whh\partial^{+} h_k / \partial W_{hh} 只計算第 kk 步「直接」用到 WhhW_{hh} 的部分。中間的 ∂ht/∂hk\partial h_t / \partial h_k 描述第 kk 步的狀態對第 tt 步的影響,它是下一節梯度爆炸與消失的根源。

截斷式 BPTT

對很長的序列(例如整本書),完整的 BPTT 要把整條序列的激勵值都存在記憶體中,而且讀完整條序列才能更新一次參數。截斷式 BPTT(truncated BPTT)把序列切成長度固定的區段(例如 25 個字元):

  • 前向傳播時,隱藏狀態從上一個區段延續下來,不重新歸零,因此模型仍能「看到」更早的資訊。
  • 反向傳播只在區段內進行,梯度不會穿越區段邊界。

代價是模型難以透過梯度學到比區段更長的依賴關係,但記憶體用量固定,更新也更頻繁。Andrej Karpathy 的 min-char-rnn.py 是很好的最小參考實作:約一百行 NumPy 程式,完整包含字元級 RNN 的前向傳播、截斷式 BPTT、梯度裁剪與 Adagrad 更新。它的訓練迴圈結構大致如下:

h = np.zeros((hidden_size, 1))           # 隱藏狀態跨區段延續
for start in range(0, len(data) - seq_length, seq_length):
    inputs  = data[start : start + seq_length]
    targets = data[start + 1 : start + seq_length + 1]   # 目標是下一個字元
    loss, grads, h = forward_backward(inputs, targets, h)  # 只在這一段內做 BPTT
    for g in grads:
        np.clip(g, -5, 5, out=g)         # 逐元素裁剪,避免梯度爆炸
    update(params, grads)

11.3梯度爆炸與消失Exploding and Vanishing Gradients

由 hj=tanh⁡(Whhhj−1+Wxhxj+bh)h_j = \tanh(W_{hh} h_{j-1} + W_{xh} x_j + b_h) 可得,狀態對更早狀態的梯度是一連串的乘積:

∂ht∂hk=∏j=k+1t∂hj∂hj−1=∏j=k+1tdiag⁡ ⁣(1−hj2)Whh \frac{\partial h_t}{\partial h_k} = \prod_{j=k+1}^{t} \frac{\partial h_j}{\partial h_{j-1}} = \prod_{j=k+1}^{t} \operatorname{diag}\!\left(1 - h_j^{2}\right) W_{hh}

其中 hj2h_j^2 是逐元素平方。每往回一步,梯度就要再乘一次同一個矩陣 WhhW_{hh},就像純量 wt−kw^{t-k} 一樣,會隨距離呈指數變化。由於 tanh 的導數不超過 1,行為主要取決於 WhhW_{hh} 的最大奇異值(largest singular value)σmax⁡\sigma_{\max}:

  • σmax⁡<1\sigma_{\max} < 1:梯度消失(vanishing gradients)。梯度的範數不超過 σmax⁡ t−k\sigma_{\max}^{\,t-k},也就是至少以這個指數速度衰減,遠處的時間步幾乎收不到誤差訊號,模型學不到長期依賴。這個條件足以保證梯度消失。
  • σmax⁡>1\sigma_{\max} > 1:可能梯度爆炸(exploding gradients)。梯度可能指數成長,損失突然跳到極大值或變成 NaN。這是爆炸的必要條件,但不一定會發生,因為 tanh 的導數可能把它壓回來。

梯度爆炸:梯度裁剪

梯度爆炸容易偵測,也容易處理。最常用的是依範數裁剪(gradient clipping by norm):梯度 gg 的範數超過門檻 θ\theta 時,等比例縮小:

∥g∥>θ⟹g←θ∥g∥ g \lVert g \rVert > \theta \quad \Longrightarrow \quad g \leftarrow \frac{\theta}{\lVert g \rVert} \, g

這樣保留了梯度的方向,只限制更新步伐的大小。另一種做法是逐元素裁剪(clip by value),把每個分量限制在 [−c,c][-c, c] 之內,例如上面 min-char-rnn.py 的做法;它實作簡單,但會稍微改變梯度的方向。其他最佳化技巧請見第 8 章。

梯度消失:閘門單元

梯度消失則麻煩得多。微弱的梯度無法靠縮放救回來,因為來自遠處的訊號早已淹沒在近處時間步的梯度之中。根本的解法是改變架構,讓資訊與梯度有一條不必反覆乘上 WhhW_{hh} 的路徑,這正是 LSTM 與 GRU 的閘門設計。

11.4長短期記憶LSTM

長短期記憶(Long Short-Term Memory, LSTM)由 Hochreiter 與 Schmidhuber 在 1997 年提出。它在隱藏狀態 hth_t 之外,另外加入一條細胞狀態(cell state)CtC_t。細胞狀態像一條沿著時間運行的輸送帶,只透過逐元素的乘法與加法被少量修改,資訊可以在上面長距離傳遞;要擦除什麼、寫入什麼、輸出什麼,則由閘門控制。每一步從上一步接收兩個輸入:Ct−1C_{t-1} 與 ht−1h_{t-1}。

LSTM 有四個由 [ht−1,xt][h_{t-1}, x_t] 計算出的元件:

元件 符號 激勵函數 作用
遺忘閘(forget gate) ftf_t σ\sigma 擦除:決定 Ct−1C_{t-1} 的每個分量保留多少
輸入閘(input gate) iti_t σ\sigma 決定是否寫入、寫入多少
候選值(gate gate) C~t\tilde{C}_t(或 gtg_t) tanh⁡\tanh 決定要寫入什麼內容
輸出閘(output gate) oto_t σ\sigma 決定細胞狀態有多少輸出到 hth_t

完整的式子如下,其中 [ht−1,xt][h_{t-1}, x_t] 表示把兩個向量串接,⊙\odot 表示逐元素乘積(Hadamard product):

ft=σ(Wf[ht−1,xt]+bf)it=σ(Wi[ht−1,xt]+bi)C~t=tanh⁡(WC[ht−1,xt]+bC)ot=σ(Wo[ht−1,xt]+bo)Ct=ft⊙Ct−1+it⊙C~tht=ot⊙tanh⁡(Ct) \begin{aligned} f_t &= \sigma(W_f [h_{t-1}, x_t] + b_f) \\ i_t &= \sigma(W_i [h_{t-1}, x_t] + b_i) \\ \tilde{C}_t &= \tanh(W_C [h_{t-1}, x_t] + b_C) \\ o_t &= \sigma(W_o [h_{t-1}, x_t] + b_o) \\ C_t &= f_t \odot C_{t-1} + i_t \odot \tilde{C}_t \\ h_t &= o_t \odot \tanh(C_t) \end{aligned}

三個閘門都使用 sigmoid,輸出介於 0 與 1,相當於對每一個維度各自做「開多少」的軟性開關;候選值使用 tanh,輸出介於 −1 與 1,代表要寫入的內容。實作上通常把四個權重矩陣疊成一個大矩陣,一次矩陣乘法就算出四個元件。

為什麼 LSTM 能緩解梯度消失

關鍵在細胞狀態的更新是加法:Ct=ft⊙Ct−1+it⊙C~tC_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t。沿著細胞狀態這條路徑往回看(暫不考慮經由閘門的間接路徑),

∂Ct∂Ct−1=diag⁡(ft) \frac{\partial C_t}{\partial C_{t-1}} = \operatorname{diag}(f_t)

梯度往回傳遞許多步時,只會乘上一連串的遺忘閘數值,而不是反覆乘上同一個權重矩陣,也不必每一步都經過 tanh 的導數壓縮。只要遺忘閘接近 1,梯度就能幾乎不衰減地傳到很遠的過去,形成一條梯度的「高速公路」,與第 10 章 ResNet 的跳躍連接是相同的精神。實務上常把遺忘閘的偏差 bfb_f 初始化為 1 或更大,讓模型一開始傾向於「記住」。

LSTM 並不能防止梯度爆炸,訓練時通常仍搭配梯度裁剪。

11.5閘控循環單元GRU

閘控循環單元(Gated Recurrent Unit, GRU)由 Cho et al. 在 2014 年提出,可以看成 LSTM 的簡化版:它沒有獨立的細胞狀態,只保留隱藏狀態 hth_t,閘門也從三個減為兩個。

rt=σ(Wr[ht−1,xt]+br)zt=σ(Wz[ht−1,xt]+bz)h~t=tanh⁡(Wh[rt⊙ht−1,xt]+bh)ht=(1−zt)⊙ht−1+zt⊙h~t \begin{aligned} r_t &= \sigma(W_r [h_{t-1}, x_t] + b_r) \\ z_t &= \sigma(W_z [h_{t-1}, x_t] + b_z) \\ \tilde{h}_t &= \tanh(W_h [r_t \odot h_{t-1}, x_t] + b_h) \\ h_t &= (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t \end{aligned}
  • 重設閘(reset gate)rtr_t:計算候選狀態 h~t\tilde{h}_t 時,要參考多少過去的狀態。rtr_t 接近 0 時,模型忽略過去,像是從這一步重新開始。
  • 更新閘(update gate)ztz_t:新狀態中,候選狀態所佔的比例。ztz_t 接近 0 時,ht≈ht−1h_t \approx h_{t-1},直接沿用舊狀態;接近 1 時,以候選狀態取代。

更新式是舊狀態與候選狀態的凸組合,同時扮演 LSTM 中遺忘閘與輸入閘的角色,只是兩者被綁定為和等於 1:保留得越多,寫入得就越少。當 ztz_t 接近 0 時,∂ht/∂ht−1\partial h_t / \partial h_{t-1} 接近單位矩陣,梯度同樣能長距離傳遞,這就是 GRU 緩解梯度消失的方式。部分實作(例如 cuDNN 與 PyTorch)把 rtr_t 乘在 WhW_h 與 ht−1h_{t-1} 相乘之後,而不是之前,細節略有不同,但概念相同。

11.6LSTM 與 GRU 的比較LSTM vs. GRU

每一組「權重矩陣加偏差」的參數量為 nh(nh+nx)+nhn_h(n_h + n_x) + n_h,其中 nhn_h 是隱藏狀態的維度、nxn_x 是輸入的維度。LSTM 有四組(三個閘門加候選值),GRU 有三組(兩個閘門加候選狀態):

項目 LSTM GRU
狀態 hth_t 與 CtC_t 只有 hth_t
閘門 遺忘、輸入、輸出 重設、更新
參數量 4 (nh(nh+nx)+nh)4\,(n_h(n_h+n_x)+n_h) 3 (nh(nh+nx)+nh)3\,(n_h(n_h+n_x)+n_h)
適合情境 長序列、資料量大時的穩健預設 參數較少、運算較快,資料少時較不易過擬合

例如 nh=128n_h = 128、nx=64n_x = 64 時,每一組有 128⋅192+128=24,704128 \cdot 192 + 128 = 24{,}704 個參數,LSTM 共 98,816 個,GRU 共 74,112 個,約少四分之一。(部分框架為每個閘門設兩組偏差,實際數字會略多一些。)

實證研究(例如 Chung et al., 2014;Jozefowicz et al., 2015)發現,兩者都明顯優於 vanilla RNN,但彼此之間沒有一致的勝負,表現差異往往小於超參數調校帶來的差異。實務上可以先用 LSTM 作為預設,在運算資源有限、或想加快實驗迭代時改用 GRU 比較看看。

11.7後續發展Beyond RNNs

RNN 有兩個結構上的限制:計算必須沿時間逐步進行,難以在 GPU 上平行化;而且即使有閘門,相隔很遠的兩個位置之間仍要經過許多步才能傳遞資訊。注意力機制(attention; Bahdanau et al., 2015)讓模型在每一步直接查看輸入序列的所有位置;Transformer(Vaswani et al., 2017)則完全移除循環結構,只用自注意力(self-attention),訓練時可以完全平行化,任兩個位置之間的路徑長度也是常數。在機器翻譯、語言模型等長序列任務上,Transformer 已大致取代了 RNN;不過 RNN 每一步只需要固定大小的狀態,在串流處理或資源受限的情境中仍有用處。

11.8參考資料References

  • Goodfellow, Bengio & Courville, Deep Learning, MIT Press, 2016,第 10 章 Sequence Modeling: Recurrent and Recursive Nets。
  • Stanford CS231n, Convolutional Neural Networks for Visual Recognition,Recurrent Neural Networks 單元講義。
  • deeplearning.ai, Sequence Models(Coursera 深度學習專項課程第五門課)。
  • Andrej Karpathy, min-char-rnn.py(GitHub Gist, 2015),以及 "The Unreasonable Effectiveness of Recurrent Neural Networks"(部落格文章, 2015)。
  • Christopher Olah, "Understanding LSTM Networks"(部落格文章, 2015)。
  • Hochreiter & Schmidhuber, "Long Short-Term Memory", Neural Computation, 1997;Gers, Schmidhuber & Cummins, "Learning to Forget: Continual Prediction with LSTM", Neural Computation, 2000。
  • Cho et al., "Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation", EMNLP 2014。
  • Pascanu, Mikolov & Bengio, "On the Difficulty of Training Recurrent Neural Networks", ICML 2013。
  • Chung, Gulcehre, Cho & Bengio, "Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling", arXiv 2014;Jozefowicz, Zaremba & Sutskever, "An Empirical Exploration of Recurrent Network Architectures", ICML 2015。
  • Bahdanau, Cho & Bengio, "Neural Machine Translation by Jointly Learning to Align and Translate", ICLR 2015;Vaswani et al., "Attention Is All You Need", NIPS 2017。