循環神經網絡RNN結構被廣泛應用於自然語言處理、機器翻譯、語音識別、文字識別等方向。本文主要介紹經典的RNN結構,以及RNN的變種(包括Seq2Seq結構和Attention機制)。希望這篇文章能夠幫助初學者更好地入門。
經典的RNN結構這就是最經典的RNN結構,它的輸入是:
輸出為:
也就是說,輸入和輸出序列必有相同的時間長度!
圖2假設輸入 ( ) 是一個長度為 ( ) 的列向量:
隱藏層 是一個長度為 ( ) 的列向量:
輸出 是一個長度為 ( ) 的列向量:
其中 , , 都是由人工設定的。
圖3時刻輸入層--> 時刻隱藏層:
時刻隱藏層--> 時刻隱藏層:
時刻隱藏層--> 時刻輸出層:
需要注意的是,對於任意時刻 ,所有的權值(包括 , , , , , )都相等,這也就是RNN中的「權值共享」,極大的減少參數量。
其實RNN可以簡單的表示為:
圖4還有一個小細節:在 時刻,如果沒有特別指定初始狀態,一般都會使用全0的 作為初始狀態輸入到 中
Sequence to Sequence模型圖5在Seq2Seq結構中,編碼器Encoder把所有的輸入序列都編碼成一個統一的語義向量Context,然後再由解碼器Decoder解碼。在解碼器Decoder解碼的過程中,不斷地將前一個時刻 的輸出作為後一個時刻 的輸入,循環解碼,直到輸出停止符為止。
圖6接下來以機器翻譯為例,看看如何通過Seq2Seq結構把中文「早上好」翻譯成英文「Good morning」:
將「早上好」通過Encoder編碼,並將最後 時刻的隱藏層狀態 作為語義向量。
以語義向量為Decoder的 狀態,同時在 時刻輸入<start>特殊標識符,開始解碼。之後不斷的將前一時刻輸出作為下一時刻輸入進行解碼,直接輸出<stop>特殊標識符結束。
當然,上述過程只是Seq2Seq結構的一種經典實現方式。與經典RNN結構不同的是,Seq2Seq結構不再要求輸入和輸出序列有相同的時間長度!
圖7進一步來看上面機器翻譯例子Decoder端的 時刻數據流,如圖7:
還有一點細節,就是如何將前一時刻輸出類別index(數值)送入下一時刻輸入(向量)進行解碼。假設每個標籤對應的類別index如下:
'<start>' : 0,
'<stop>' : 1,
'good' : 2,
'morning' : 3,
...
已知<start>標誌符index為0,如果需要將<start>標誌符輸入到input層,就需要把類別index=0轉變為一個 長度的特定對應向量。這時就需要應用嵌入 (embedding) 方法。
圖8 嵌入 (embedding)假設有 個詞,最簡單的方法就是使用 長度的one-hot編碼,詞表alphabet如下:
'<start>' : 0 <> label('<start>')=[1, 0, 0, 0, 0,..., 0]
'<stop>' : 1 <> label('<stop>') =[0, 1, 0, 0, 0,..., 0]
'hello': 2 <> label('hello') =[0, 0, 1, 0, 0,..., 0]
'good' : 3 <> label('good') =[0, 0, 0, 1, 0,..., 0]
'morning' : 4 <> label('morning')=[0, 0, 0, 0, 1,..., 0]
..
但是使用one-hot編碼進行嵌入過於稀疏,所以我們使用一種更加優雅的辦法:
如此不停循環解碼。
可以看到,其實Seq2Seq引入嵌入機制解決從label index數值到輸入向量的維度恢復問題。在Tensorflow中上述過程通過以下函數實現:
tf.nn.embedding_lookup
而在pytorch中通過以下接口實現:
torch.nn.Embedding
需要注意的是:train和test階段必須使用一樣的embedding矩陣!否則輸出肯定是亂碼。
當然,還可以使用word2vec/glove/elmo/bert等更加「精緻」的嵌入方法,也可以在訓練過程中迭代更新embedding。這些內容超出本文範圍,不再詳述。embedding入門請參考:https://zhuanlan.zhihu.com/p/89637281
Seq2Seq訓練問題值得一提的是,在seq2seq結構中將 作為下一時刻輸入 進網絡,那麼某一時刻輸出 錯誤就會導致後面全錯。在訓練時由於網絡尚未收斂,這種蝴蝶效應格外明顯。
圖9為了解決這個問題,Google提出了大名鼎鼎的Scheduled Sampling(即在訓練中 按照一定概率選擇輸入 或 時刻對應的真實值,即標籤,如圖10),既能加快訓練速度,也能提高訓練精度。
圖10Scheduled Sampling對應文章如下:
Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks連結:https://arxiv.org/pdf/1506.03099.pdf
Attention注意力機制
圖11在Seq2Seq結構中,encoder把所有的輸入序列都編碼成一個統一的語義向量Context,然後再由Decoder解碼。由於context包含原始序列中的所有信息,它的長度就成了限制模型性能的瓶頸。如機器翻譯問題,當要翻譯的句子較長時,一個Context可能存不下那麼多信息,就會造成精度的下降。除此之外,如果按照上述方式實現,只用到了編碼器的最後一個隱藏層狀態,信息利用率低下。
所以如果要改進Seq2Seq結構,最好的切入角度就是:利用Encoder所有隱藏層狀態 解決Context長度限制問題。
接下來了解一下attention注意力機制基本思路(Luong Attention)
圖12考慮這樣一個問題:由於Encoder的隱藏層狀態 代表對不同時刻輸入 的編碼結果:
即Encoder狀態 , , 對應編碼器對「早」,「上」,「好」三個中文字符的編碼結果。那麼在Decoder時刻 通過3個權重 , , 計算出一個向量 :
然後將這個向量與前一個狀態拼接在一起形成一個新的向量輸入到隱藏層計算結果:
Decoder時刻 :
Decoder時刻 和 同理,就可以解決Context長度限制問題。由於 , , 不同,就形成了一種對編碼器不同輸入 對應 的「注意力」機制(權重越大注意力越強)。
那麼到底什麼是LuongAttention注意力機制?
圖13這裡的 可以通過以下三種方式計算:
所謂Dot就是向量內積,而General通過乘以 權重矩陣進行計算( 是 大小的矩陣)。一般經驗General方法好於Dot方法,Concat方法略去不講。
這裡 和 大小都是 ,拼接後會變大。由於需要恢復為原來形狀,所以乘以全連接 矩陣。當然不恢復也可以,但是會造成Decoder RNN cell變大。
也可以根據需要,把新生成的狀態 繼續送入RNN繼續進行學習。其中 和 參數需要通過學習獲得。
圖14在實際應用中當輸入一組 ,除了可以獲得輸出 ,還能提取出 與 對應的權重數值 並畫出來,如圖15,這樣就可以直觀的看到時刻 注意力機制到底「注意」了什麼。
圖15 注意力機制中的權重可以看到,整個Attention注意力機制相當於在Seq2Seq結構上加了一層「包裝」,內部通過函數 計算注意力向量 ,從而給Decoder RNN加入額外信息,以提高性能。無論在機器翻譯,語音識別,自然語言處理(NLP),文字識別(OCR),Attention機制對Seq2Seq結構都有很大的提升。
如何向RNN加入額外信息Attention機制其實就是將的Encoder RNN隱藏層狀態加權後獲得權重向量 ,額外加入到Decoder中,給Decoder RNN網絡添加額外信息,從而使得網絡有更完整的信息流。
圖16 RNN添加額外信息的3中方式所以,假設有額外信息 (如上文中的注意力向量 ),給RNN網絡添加額外信息主要有以下3種方式:
ADD:直接將 疊加在輸出 上。
MLP:新添加一個對 的感知單元 。
特別說明:上文介紹的LuongAttention僅僅是注意力機制的一種具體實現,不代表Attention僅此一種。事實上Seq2Seq+Attention還有很多很玩法。望讀者了解!