2021年9月24日 星期五

機器學習筆記 : 遞迴神經網路 (RNN) 閱讀摘要

以下是最近看 "精通機器學習 (第二版)" 這本書第 15 與 16 章 RNN 的筆記 :
  1. 遞迴神經網路 RNN (Recurrent Neural Network) 是一種可從時間序列中學習 (可以處理任意長度之序列), 在某種程度上可以預測未來的神經網路, 其應用包括預測股價, 預測自駕車行駛軌跡, 自動翻譯, 語音輸入法等. 
  2. 前饋神經網路的觸發只往一個方向流動, 即從輸入層到輸出層. 遞迴神經網路基本上就是在前饋神經網路上加一個從輸出拉一個連結指向後方, 也就是將神經元的輸出也送回給自己, 於是遞迴神經元在每個時步 (或稱時框 frame) t 接收輸入 X(t) 以及自己上一個時步 t-1 的輸出 Y(t-1). 因此一個遞迴神經元就有兩個權重參數, 一個是輸入 X(t) 的權重 Wx, 另一個是上一個時步的輸出 Y(t-1) 的權重 Wy. 只有一個神經元時, 這些值都是純量, 當建立一層遞迴神經元時就變成向量了. 一個 RNN 神經元結構如下圖 (U, V, W 是權重, 前一個狀態經過一個時步延遲回授至輸入端做加成運算), 右方是按時步展開的示意圖 :


  3. 在 RNN 中激活函數最常用的是雙曲正切 tanh() 函數. 
  4. 因為遞迴神經元的輸出是之前所有時步的輸入的函數, 因此它具有記憶能力. 在神經網路中, 能夠跨越時步保留某些某些狀態成分者稱為記憶細胞 (memory cell). 單一遞迴神經元, 或一層遞迴神經元是非常基本的記憶細胞, 只能夠學習短期的模式 (大約 10 個時步長度, 因任務而異). 這種基本的記憶細胞, 其狀態就是輸出, 但較複雜之記憶細胞就不一定如此. 
  5. RNN 可以同時接收與一個輸入序列並產生一個輸出序列,  這種將序列變成序列的網路 (sequence-to-sequence network) 很適合用來預測股價之類的時間序列. 也可以將輸入序列傳入網路, 然後只取最後一個輸出 (忽略其他時步的輸出), 這種將序列變成向量的網路 (sequence-to-vector network) 適合用來分析情緒. 也可以在每一個時步將同一個向量反覆傳給網路, 讓它輸出一個序列, 這是一種向量變序列的網路 (vector-to-sequence network). 還有一種雙步驟模型叫做稱為編碼-解碼網路 (encoder-decoder), 前面的編碼網路 (encoder) 是將序列變成向量, 而後面的解碼網路 (decoder) 則是將向量變成序列, 這種網路的應用之一是用來將一種語言翻譯成另外一種語言, 其效果會比使用一個序列到序列的 RNN 好很多. 
  6. 在 TensorFlow 中可以使用 Keras 實作簡單的 RNN, 例如只有一個遞迴層與一個神經元的 簡單 RNN 網路如下 :

    model=keras.models.Sequential([
        keras.layers.SimpleRNN(1, input_shape=[None, 1])
        ])

    此處 input_shape 參數的第一個元素設為 None 表示不指定輸入序列的長度, 表示此 RNN 可以處理任何數量的時步序列, 第二個元素 1 表示 1 個輸出序列.  
    預設情況下, SimpleRNN 使用的激活函數是輸出值位於 -1 與 1 之間的雙曲正切 (飽和) 函數 tanh(), 且遞迴層只傳回最終輸出, 若要使其在每個時步都回傳輸出, 則必須設定 return_sequence=True 參數.
    將多層的 RNN 神經元疊在一起就形成了深度 RNN, 例如下面是一個三層的 SimpleRNN 網路 (除了最後一層外都要設定 return_sequence=True 參數, 否則會輸出一個 2D 陣列) :

    model=keras.models.Sequential([
        keras.layers.SimpleRNN(20, return_sequence=True, input_shape=[None, 1]),
        keras.layers.SimpleRNN(20, return_sequence=True),
        keras.layers.SimpleRNN(1)
        ])
  7. 簡單的 RNN 已經足以預測一般的序列, 但若用來處理很長的序列, 表現就會變差. 其次用很長的序列來訓練 RNN 網路時, 因為要執行很多時步, 使得未展開的 RNN 變成很深的網路, 此時就會像任何一種深度神經網路一樣遇到梯度不穩定問題, 使得訓練可能永遠不會結束, 而且會逐漸忘記序列的前幾個輸入, 因為資料通過 RNN 時經過轉換, 每一個時步都會有一些資訊會遺失, 經過一段時間後, RNN 的狀態就幾乎沒有前幾個輸入的蹤跡了. 
  8. LSTM (長短期記憶, Long Short-Term Memory) 細胞是 Sepp Hochreiter 與 Jurgen Schmidhuber 於 1997 年提出來解決 RNN 短期記憶缺陷的結構, 它可偵測序列資料中的長期相依性, 且訓練的收斂速度很快. 在 Keras 中可以直接用 LSTM 來取代 SimpleRNN 層 : 

    model=keras.models.Sequential([
        keras.layers.LSTM(20, return_sequence=True, input_shape=[None, 1]),
        keras.layers.LSTM(20, return_sequence=True),
        keras.layers.TimeDistributed(keras.layers.Dense(1))
        ])
  9. LSTM 細胞的外觀與一般神經元差異僅在於它的狀態分成兩種 : 短期狀態 h(t) 與長期狀態 c(t). 其內部結構主要有遺忘閘 (forget gate), 輸入閘 (input gate), 以及輸出閘 (output gate) 構成, 遺忘閘負責控制長期狀態的哪些部分要移除, 輸入閘控制輸出的那些部分應該加入長期狀態 (即學習辨識重要的輸入), 而輸出閘則控制長期狀態的那些部分應該在每個時步中讀取與輸出. LSTM 網路的主要概念是, 它可以學會該將甚麼存成長期狀態, 該丟棄甚麼記憶, 以及該從長期狀態中讀取甚麼輸出. 
  10. LSTM 有幾種變體, 其中最熱門的是 2014 年由 Kyunghyun Cho 等人提出的 GRU (Gated Recurrent Unit), 它是簡單版的 LSTM, 但表現卻一樣好. 其簡化主要是將兩個狀態合併成一個 h(t), 以及用一個閘門控制器 z(t) 來控制遺忘閘與輸入閘, 若 z(t) 輸出 1 表示遺忘閘是開的且輸入閘是關的; 反之若輸出 0 表示遺忘閘是關的且輸入閘是開的. 也就是每當細胞必須儲存記憶時, 它就會先將儲存該記憶的位置清除, 這種情況在 LSTM 很常見. 另一個簡化是 GRU 沒有輸出閘, 每一個時步都會把全部狀態向量輸出, 但增加一個閘控制器 r(t) 用來控制要將之前狀態的哪一個部分傳給主階層 g(t).
  11. LSTM 與 GRU 是 RNN 成功的主因, 雖然它們比簡單的 RNN 更能夠處理較長的序列, 但仍然是非常受限的短期記憶, 因為它們難以在 100 個時步以上的序列 (例如語音或長句子) 中學習長期模式. 解決此問題的辦法之一是使用 1D 捲積層, 作法與 CNN 的 2D 捲積層類似, 只是這裡使用 1D 過濾器而已. 1D 捲積層使用多個捲積核掃描序列, 每個核產生一個 1D 特徵圖, 每個核都會學習一個長度不超過核大小的序列模式, 例如使用 10 個核, 則 1D 捲積層將輸出 10 個長度相等的一維序列 (或者也可視為一個 10 維序列), 這種結構就是將 1D 捲積層與遞迴層混合建立一個神經網路. 藉著縮短序列, 捲積層可以協助 GRU 層偵測較長的模型, 例如下面範例使用 20 個 1D 捲積核, 核大小為 4, 步幅為 2, 填補方式為 valid : 

    model=keras.models.Sequential([
        keras.layers.Conv1D(filters=20, kernel_size=4, strided=2, padding="valid",
                                           input_shape=[None, 1]),
        keras.layers.GRU(20, return_sequence=True),
        keras.layers.GRU(20, return_sequence=True),
        keras.layers.TimeDistributed(keras.layers.Dense(10))
        ])
  12. 2015 年 Andrej Karpathy 發表了一篇部落格文章 "The Unreasonable Effectiveness of RNN", 介紹如何訓練 RNN 來預測句子的下一個字元, 這個後來被稱為字元 RNN (character RNN) 的技術可用來生成新文章, 每次一個字元. 


下面是閱讀 "TensorFlow 自然語言處理" 第六章 "RNN 遞迴神經網路" 的摘要筆記 :
  1. RNN 是一種用來處理時間序列的特殊神經網路, RNN 會持續使用一個狀態變數 (state variable) 來擷取序列中存在的各種特定模式, 適合用來處理具有時間順序性的序列資料, 例如文字或股價等.
  2. 傳統的正向饋送神經網路並沒有能力擷取出序列資料中的特定模式, 除非資料所採用的特徵表達方式即可擷取出序列中的特定模式, 但是要取得這種特徵表達方式是很困難的. 替代方式是替每一個時間序列位置準備一組特徵組合, 但這種做法會耗掉大量記憶體. RNN 捨棄這種作法, 採用在任一時間都共用同一組參數, 當我們觀察序列中的每個輸入, 狀態變數就會隨時間更新, 只要結合狀態向量, 就可以根據先前所觀察到的序列值, 預測出序列的下一個值. 由於一次只處理單一個資料, 因此 RNN 可以處理任意長度之資料, 不需要再使用特殊標記把資料填充到特定長度. 
  3. RNN 會持續維護一個狀態變數, 這個狀態變數會因為 RNN 持續看到更多資料而隨時間演變, 它會隨時間的推移, 透過一組遞迴連結 (recurrent connection) 持續進行更新. 遞迴連結的存在就是 RNN 與正向饋送網路在結構上最主要的差異. 它可以被理解為 RNN 在過去所學習到一系列記憶彼此間的連結, 而這些連結全都與 RNN 目前的狀態變數有關. 總之, 遞迴連結使 RNN 可以根據過去的記憶更新目前的狀態變數, 使其有能力根據先前與目前的輸入進行預測. 
  4. 正向饋送神經網路在時間 t 所預測的輸出 yt 完全只能依據目前的輸入 xt 來決定, 亦即它完全不知道 xt 以前的狀況, 如果目前的輸入除了與目前輸入有關, 還受到過去的輸入影響 (例如時間序列資料), 那麼正向饋送神經網路將得到失敗的結果, 亦即, 它在序列型任務中完全不管用. 

沒有留言 :