TensorFlow系列專題(七):一文綜述RNN循環神經網絡

一 前言

前饋神經網絡不考慮數據之間的關聯性,網絡的輸出只和當前時刻網絡的輸入相關。然而在解決很多實際問題的時候我們發現,現實問題中存在著很多序列型的數據,例如文本、語音以及視頻等。這些序列型的數據往往都是具有時序上的關聯性的,既某一時刻網絡的輸出除了與當前時刻的輸入相關之外,還與之前某一時刻或某幾個時刻的輸出相關。而前饋神經網絡並不能處理好這種關聯性,因為它沒有記憶能力,所以前面時刻的輸出不能傳遞到後面的時刻。

此外,我們在做語音識別或機器翻譯的時候,輸入和輸出的數據都是不定長的,而前饋神經網絡的輸入和輸出的數據格式都是固定的,無法改變。因此,需要有一種能力更強的模型來解決這些問題。

在過去的幾年裡,循環神經網絡的實力已經得到了很好的證明,在許多序列問題中,例如文本處理、語音識別以及機器翻譯等,循環神經網絡都取得了顯著的成績。循環神經網絡也正被越來越多的應用到其它領域。

RNN知識結構

在本章中,我們將會從最簡單的循環神經網絡開始介紹,通過實例掌握循環神經網絡是如何解決序列化數據的,以及循環神經網絡前向計算和參數優化的過程及方法。在此基礎上我們會介紹幾種循環神經網絡的常用結構,既雙向循環神經網絡、深度循環神經網絡以及遞歸神經網絡。我們會使用TensorFlow實現循環神經網絡,掌握使用TensorFlow搭建簡單循環神經網絡的方法。

此外,我們還會學習一類結構更為複雜的循環神經網絡——門控循環神經網絡,包括長短期記憶網絡(LSTM)和門控制循環單元(GRU),這也是目前最常使用的兩種循環神經網絡結構。最後我們還會介紹一種注意力模型:Attention-based model,這是近兩年來的研究熱點。在下一章的項目實戰中,我們會使用到Attention-based model以及前面提到的LSTM等模型解決一些實際的問題。

本章內容結構如下:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

圖1 本章內容結構

簡單循環神經網絡

簡單循環網絡(simple recurrent networks,簡稱SRN)又稱為Elman network,是由Jeff Elman在1990年提出來的。Elman在Jordan network(1986)的基礎上進行了創新,並且簡化了它的結構,最終提出了Elman network。Jordan network和Elman network的網絡結構如下圖所示。

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

圖2 Jordan network(左)和Elman network(右)

圖片引用自ResearchGate:https://www.researchgate.net/figure/A-recurrent-neural-network-as-proposed-by-Jordan-1986_fig5_277603865

從圖2中可以很直觀的看出,兩種網絡結構最主要的區別在於記憶單元中保存的內容不同。Jordan network的記憶單元中保存的是整個網絡最終的輸出,而Elman network的記憶單元只保存中間的循環層,所以如果是基於Elman network的深層循環神經網絡,那麼每一個循環的中間層都會有一個相應的記憶單元。有興趣深究的讀者可以查閱Elman和Jordan的論文:https://crl.ucsd.edu/~elman/Papers/fsit.pdf,http://cseweb.ucsd.edu/~gary/PAPER-SUGGESTIONS/Jordan-TR-8604.pdf。

Jordan network和Elman network都可以擴展應用到深度學習中來,但由於Elman network的結構更易於擴展(Elman network的每一個循環層都是相互獨立的,因此網絡結構的設計可以更加靈活。另外,當Jordan network的輸出層與循環層的維度不一致時還需要額外的調整,而Elman network則不存在該問題。),因此當前主流的循環神經網絡都是基於Elman network的,例如我們後面會介紹的LSTM等。所以,通常我們所說的循環神經網絡(RNN),默認指的就是Elman network結構的循環神經網絡。本書中所提到的循環神經網絡,如果沒有特別註明,均指Elman network結構的循環神經網絡。

RNN的基本結構

循環神經網絡的基本結構如下圖所示(注意:為了清晰,圖中沒有畫出所有的連接線。):

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

圖3 循環神經網絡的基本結構

關於循環神經網絡的結構有很多種不同的圖形化描述,但是其所表達的含義都與圖1一致。將循環神經網絡的結構與一般的全連接神經網絡比較,我們會發現循環神經網絡只是多了一個記憶單元,而這個記憶單元就是循環神經網絡的關鍵所在。

從圖3我們可以看到,循環神經網絡的記憶單元會保存時刻時循環層(既圖3中的隱藏層)的狀態,並在時刻,將記憶單元的內容和時刻的輸入一起給到循環層。為了更直觀的表示清楚,我們將循環神經網絡按時間展開,如圖4所示。

圖4所示,左邊部分是一個簡化的循環神經網絡示意圖,右邊部分是將整個網絡按時間展開後的效果。在左邊部分中,是神經網絡的輸入,是輸入層到隱藏層之間的權重矩陣,是記憶單元到隱藏層之間的權重矩陣,是隱藏層到輸出層之間的權重矩陣,是隱藏層的輸出,同時也是要保存到記憶單元中,並與下一時刻的一起作為輸入,是神經網絡的輸出。

從右邊的展開部分可以更清楚的看到,RNN每個時刻隱藏層的輸出都會傳遞給下一時刻,因此每個時刻的網絡都會保留一定的來自之前時刻的歷史信息,並結合當前時刻的網絡狀態一併再傳給下一時刻。

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

圖4 循環神經網絡及其按時間展開後的效果圖,圖片來源於Nature

理論上來說,RNN是可以記憶任意長度序列的信息的,即RNN的記憶單元中可以保存此前很長時刻網絡的狀態,但是在實際的使用中我們發現,RNN的記憶能力總是很有限,它通常只能記住最近幾個時刻的網絡狀態,在本章的第4節裡,我們會具體討論這個問題。

RNN的運算過程和參數更新

1. RNN的前向運算

在一個全連接的循環神經網絡中,假設隱藏層只有一層。在時刻神經網絡接收到一個輸入,則隱藏層的輸出為:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

式1

上式中,函數是隱藏層的激活函數,在TensorFlow中默認是tanh函數。參數U和W在前面介紹過,分別是輸入層到隱藏層之間的權重矩陣和記憶單元到隱藏層之間的權重矩陣,參數是偏置項。在神經網絡剛開始訓練的時候,記憶單元中沒有上一個時刻的網絡狀態,這時候就是一個初始值。

在得到隱藏層的輸出後,神經網絡的輸出為:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

式2

上式中,函數g()是輸出層的激活函數,當我們在做分類問題的時候,函數g()通常選為Softmax函數。參數V是隱藏層到輸出層的參數矩陣,參數是偏置項。

我們先看看TensorFlow源碼中關於RNN隱藏層部分的計算。這部分代碼在TensorFlow源碼中的位置是:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/python/ops/rnn_cell_impl.py。

在rnn_cell_impl.py文件中定義了一個抽象類RNNCell,其它實現RNN的類都會繼承這個類,例如BasicRNNCell、BasicLSTMCell以及GRUCell等。我們以BasicRNNCell類為例,所有繼承了RNNCell的類都需要實現一個call方法,BasicRNNCell類中的call方法的實現如下:

def call(self, inputs, state):
"""Most basic RNN: output = new_state
= act(W * input + U * state + B)."""
gate_inputs = math_ops.matmul(
array_ops.concat([inputs, state], 1), self._kernel)
gate_inputs = nn_ops.bias_add(gate_inputs, self._bias)
output = self._activation(gate_inputs)
return output, output

從上面的TensorFlow源碼裡可以看到,TensorFlow隱藏層的計算結果即是該層的輸出,同時也作為當前時刻的狀態,作為下一時刻的輸入。第2、3行的註釋說明了"call"方法的功能:output = new_state = act(W * input + U * state + B),其實就是實現了我們前面給出的公式6.1。第5行代碼中的"self._kernel"是權重矩陣,第6行代碼中的"self._bias"是偏置項。

這裡有一個地方需要注意一下,這段代碼在實現W * input + U * state + B 時,沒有分別計算W * input和U * state,然後再相加,而是先用"concat"方法,將前一時刻的狀態"state"和當前的輸入"inputs"進行拼接,然後用拼接後的矩陣和拼接後的權重矩陣相乘。可能有些讀者剛開始看到的時候不太能理解,其實效果是一樣的,我們看下面這個例子:

我們有四個矩陣:a、b、c和d:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

公式3

假設我們想要計算,則有:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

公式4

如果我們把矩陣a和b、c和d先分別拼接到一起,得到e和f兩個矩陣:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

公式5

再來計算,會得到同樣的結果:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

公式6

下面我們用一段代碼實現循環神經網絡中完整的前向計算過程。

上面代碼裡所使用的RNN結構如下:

TensorFlow系列專題(七):一文綜述RNN循環神經網絡

圖5 代碼中使用的RNN網絡結構

在上面的示例代碼中,我們用了一個如圖5所示的簡單循環神經網絡。該網絡結構輸入層有兩個單元,隱藏層有兩個神經元,輸出層一個神經元,所有的激活函數均為tanh函數。在第四行代碼中我們定義了輸入數據,總共三個time-step,每個time-step處理一個輸入。我們將程序運行過程中各個參數以及輸入和輸出的值以表格的形式展示如下(讀者可以使用下表的數據驗算一遍RNN的前向運算,以加深印象):

2. RNN的參數更新

循環神經網絡中參數的更新主要有兩種方法:隨時間反向傳播(backpropagation through time,BPTT)和實時循環學習(real-time recurrent learning,RTRL)。這兩種算法都是基於梯度下降,不同的是BPTT算法是通過反向傳播的方式來更新梯度,而RTRL算法則是使用前向傳播的方式來更新梯度。目前,在RNN的訓練中,BPTT是最常用的參數更新的算法。

BPTT算法和我們在前饋神經網絡上使用的BP算法本質上沒有任何區別,只是RNN中的參數存在時間上的共享,因此RNN中的參數在求梯度的時候,存在沿著時間的反向傳播。所以RNN中參數的梯度,是按時間展開後各級參數梯度的總和。

本此介紹了簡單RNN網絡的構造原理,下一篇我們將會以實戰的形式介紹如何用TensorFlow實現RNN。


對深度學習感興趣,熱愛Tensorflow的小夥伴,歡迎關注我們的網站http://www.panchuang.net 我們的公眾號:磐創AI。


分享到:


相關文章: