數學 · RNN(一)· 從 NN 到 RNN

(RNN 我也沒學多久,不少東西可能理解上會有偏差,還望觀眾老爺們不吝賜教 ( σω)σ)

(不過知乎居然沒有 RNN 的話題啊摔!!!)

RNN 的思想

無論是 NN 還是 CNN,它們處理的數據都是相對固定的。NN 自不用提,一旦網路結構確定下來之後,輸入數據的維度便隨之確定了下來;而雖說 CNN 能夠通過 RoI Pooling 等手段來接受不同長寬的圖片輸入,但它們大多只是在最後一步做出了調整、並沒有特別根本地解決了問題。而循環神經網路(Recurrent Neural Network,常簡稱為 RNN)則通過非常巧妙的形式、讓模型能用同一套結構非常自然地處理不同的輸入數據,這使得 RNN 在處理序列問題(比如各種 NLP

Tasks)時顯得得心應手(註:這並非是絕對的結論,只能說從設計的理念上來看確實更為自然;事實上在特定的序列問題上 CNN 能夠表現得比 RNN 更好,比如 Facebook FAIR 團隊最近弄出的那個 CNN 翻譯模型……)

與 CNN 類似,RNN 也可以說是 NN 的一種拓展,但從思想上來說,RNN 和 NN、CNN 相比已經有了相當大的不同:

  • NN 只是單純地接受數據,認為不同樣本之間是獨立的
  • CNN 注重於挖掘單個結構性樣本(比如說圖像)相鄰區域之間的關聯
  • RNN 注重於挖掘樣本與樣本之間的序關聯(這是我瞎掰的一個詞 ( σω)σ)

我們已經說過,CNN 是通過局部連接和權值共享來做到相鄰區域的特徵提取的,那麼 RNN 是如何做到提取序特徵的呢?關鍵就在於「狀態(State)」的引入。換句話說,在 RNN 中,輸入不是簡單地通過權值矩陣(NN 的做法)或卷積(CNN 的做法)來得到輸出,而是要先得出一個 State、然後再由這個 State 得到輸出。這樣一來,只需讓 State 之間能夠「通信」,那麼當前樣本就能夠影響到下一個、乃至於下 n 個樣本的 State;由於輸出是由 State 決定的,所以影響到了 State 意味著能夠影響輸出,亦即當前樣本能夠影響到下一個、乃至於下n 個樣本的輸出,從而能使結果反映出序特徵。這就是 RNN 挖掘序關聯的一般性思路。事實上,我們可以把 State 理解為網路結構的「記憶」——它能幫助網路「記住」之前看到過的樣本的某些信息,並結合最新的樣本所帶來的信息來進行決策

可以通過下圖來直觀認知一下樸素 RNN 的結構:

其中,x_{t-1},x_t,x_{t+1}o_{t-1},o_t,o_{t+1}s_{t-1},s_t,s_{t+1}可以分別視為第t-1,t,t+1「時刻」的輸入、輸出與 State。不難看出對於每一個時刻而言,樸素的 RNN 都可視為一個普通的神經網路:

其中,完全可以視每一個「x_irightarrow s_irightarrow o_i,(i=...,t-1,t,t+1,...)」結構為一個神經網路。再考慮到 State 的傳遞:

亦即s_t=W_t^{(sx)}x_t+W^{(ss)}_{t-1}s_{t-1},那麼各個分量的值就很容易求出來了(以t時刻為例):

o_{t} = W_{t}^{left( text{os} right)}s_{t} = W_{i}^{left( text{os} right)}left( W_{t}^{left( text{sx} right)}x_{t} + W_{t - 1}^{left( text{ss} right)}s_{t - 1} right)

在實際應用中,會視情況決定各個W_{i}^{left( os right)}W_{i}^{left( sx right)}W_{i}^{left( ss right)}之間的關係(i = 1,...,t,ldots)。一種樸素而自然的方法就是令它們都相等:

begin{align} W_{1}^{left( {os} right)} &= ldots = W_{t}^{left( {os} right)} = ldots triangleq V  W_{1}^{left( {sx} right)} &= ldots = W_{t}^{left( {sx} right)} = ldots triangleq U  W_{1}^{left( {ss} right)}& = ldots = W_{t}^{left( {ss} right)} = ldots triangleq W end{align}

此時有

o_{i} = Vs_{i} = Vleft( Ux_{i} + Ws_{i - 1} right),  i = 1,...,t,ldots

且一般認為

s_{0} = mathbf{0 =}left( 0,0,ldots,0 right)^{T}

注意:RNN的傳統定義認為上面那些圖所示的 State 的傳遞是由一個「循環」的神經網路「展開」後得到的:

這樣的話各個W_{i}^{left( {os} right)}W_{i}^{left( {sx} right)}W_{i}^{left( ss right)}之間天然就應該相等;但個人認為 State 的通信才是 RNN 最為關鍵之處(雖然這個認為也沒啥根據就是了……),所以用了種更為一般的敘述形式。雖然傳統的定義方式有著諸多好處(比如減少了參數量等),但它畢竟降低了結構的「自由度」

我們知道 NN 中有激活函數和變換函數的概念,將它應用到RNN中是非常自然的(視s為隱藏層、o為輸出層即可):

s_{t} = phi_{t}^{left( {sx} right)}left( Ws_{t - 1} + Ux_{t} right)

o_{i} = phi_{i}^{left( {os} right)}left( Vs_{i} right) = phi_{i}^{left( {os} right)}left( Vphi_{i}^{left( {sx} right)}left( Ws_{i - 1} + Ux_{i} right) right),  i = 1,...,t,ldots

其中,各個phi_{i}^{left( {os} right)}phi_{i}^{left( {sx} right)}通常都會取為相等的函數:

phi_{1}^{left( {os} right)} = ldots = phi_{t}^{left( {os} right)} = ldots = f

phi_{1}^{left( {sx} right)} = ldots = phi_{t}^{left( {sx} right)} = ldots = g

那麼最後就能得到等式:

o_{i} = fleft( Vs_{i} right) = fleft( text{Vg}left( Ws_{i - 1} + Ux_{i} right) right),  i = 1,...,t,ldots

此即樸素 RNN 的「前向傳導演算法」。舉一個簡單的例子:假設現在U,V是單位陣,W是單位陣的兩倍,輸入序列為:

left( 1,0,0,ldots,0 right)^{T} rightarrow left( 0,1,0,ldots,0 right)^{T} rightarrow left( 0,0,1,ldots,0 right)^{T} rightarrow ldots rightarrow left( 0,0,0,ldots,1 right)^{T}

亦即輸入矩陣也是單位陣。為方便討論,我們不使用激活函數和變換函數,此時易知:

begin{align} o_{1} &= Vs_{1} = Vleft( Ux_{1} right) = x_{1}  o_{2} &= Vs_{2} = Vleft( Ws_{1} + Ux_{2} right) = 2s_{1} + x_{2}  ldots  o_{t} &= Vs_{t} = Vleft( Ws_{t - 1} + Ux_{t} right) = 2s_{t - 1} + x_{t} end{align}

亦即輸出序列為:

left( 1,0,0,ldots,0 right)^{T} rightarrow left( 2,1,0,ldots,0 right)^{T} rightarrow left( 4,2,1,ldots,0 right)^{T} rightarrow ldots rightarrow left( 2^{n - 1},2^{n - 2},2^{n - 3},ldots,1 right)^{T}

其中n為輸入數據的維度

RNN 的結構

上一節我們敘述了一種最樸素的 RNN 的結構,這一節我們將會介紹一些 RNN 中經常會用到的、相對而言不太平凡的結構。不過雖然在形式上有所改變,但它們的思想內核都是一致的,相應的「前向傳導演算法」也都相差不多

首先是輸入與輸出的對應關係。在上一節中,不難發現我們每接受一個輸入、就會產生一個輸出(這種結構叫「One to One」)。這其實並不是一個必然的對應關係,最簡單的例子就是情感分析:我們只有在接受完某個段落的所有單詞後,才會產生一個輸出,此時的模型輸入與輸出的對應關係當如下圖所示(這種結構叫「Many to One」):

其中x_{1},x_{2},ldots,x_{t}表示句子中的t個詞,o代表最終輸出的情感標籤。不難得知此時的「前向傳導演算法」即為(簡單起見,暫時不考慮激活函數和變換函數):

fleft( x right) = {Vs}_{t} = Vleft( Ux_{t} + Ws_{t - 1} right) = Vleft( Ux_{t} + Wleft( Ux_{t - 1} + Ws_{t - 2} right) right) = ldots

類似的,我們完全可以根據具體的應用場景提出「One to Many」、「Many to Many」等結構:

(話說 ProcessOn 畫這種圖意外的很好用,不過那些字母是用 Word + 畫圖 + 截屏弄的……好累啊豈可修)

它們的「前向傳導演算法」都是類似的,這裡就不再贅述

目前為止討論過的 RNN 的 State 的傳遞都是相當樸素的線性傳遞,那麼是否能改進這種傳遞方式呢?答案自然是肯定的。接下來我們就介紹一些異於普通線性傳遞的循環結構,在後文介紹 LSTMs 時則會介紹如何針對傳遞本身進行改進

線性傳遞雖然確實能夠利用上「歷史信息」,但它卻無法利用上「未來的信息」。以翻譯為例,我們常常會需要「聯繫上下文」來得出某句話的含義,線性傳遞允許我們「聯繫上文」、但「聯繫下文」卻無法做到。針對這一點,Bidirectional

RNN(可譯為「雙向 RNN」)應運而生:

這種 RNN 的「前向傳導演算法」會稍微複雜一點,但內核是沒有改變的。以t時刻為例:

begin{align} o_{t} &= W_{t}^{left( {os} right)}s_{t} + W_{t}^{left( {oh} right)}h_{t}  &= W_{t}^{left( {os} right)}left( W_{t - 1}^{left( {ss} right)}s_{t - 1} + W_{t}^{left( {sx} right)}x_{t} right) + W_{t}^{left( {oh} right)}left( W_{t}^{left( {hh} right)}h_{t + 1} + W_{t}^{left( {hx} right)}x_{t} right) end{align}

雖說雙向RNN比較好地解決了「過去」與「未來」相結合的問題,但不難看出它和我們說過的所有 RNN 都有一個共同的「缺點」——看上去太「淺」了(只有一層 State 隱藏層)。由 NN 和 CNN 的相關討論可知,更深的網路結構通常能帶來更好的性能,那麼是否能將 RNN 變得更深呢?答案同樣是肯定的。不過實踐證明,更深的 RNN 在帶來更強的學習能力的同時,往往也需要多得多的訓練數據來支撐,在這一點上 RNN 並不能「免俗」

深層 RNN 的提法有許多種,這裡僅展示其中比較自然的一個:

注意:雙向 RNN 的思想可以自然地移植到該 Deep RNN 上,由此得到的結構即為 Deep Bidirectional RNN(可譯為「深層雙向 RNN」)

RNN 的訓練

至今我們討論並計算過許多次 RNN 的輸出,但還沒有提及如何進行 RNN 的訓練。事實上與 NN、CNN 類似,RNN 在利用「前向傳導演算法」計算輸出的同時,也是通過反向傳播演算法來進行訓練的。本節將會進行相應的推導,並敘述 RNN 訓練中可能會遇到的兩個問題——梯度消失和梯度爆炸

與 NN 和 CNN 相比,RNN 的 BP 演算法最大的不同在於 State 之間的通信。不過這一點反映在數學公式上也只是在應用鏈式法則時多出一項而已,所以如果能夠理解 NN 的 BP 演算法的話,推導出 RNN 的 BP 演算法並非難事。不過相對而言這部分還是比較繁瑣的,所以我打算單獨開一個章節來講,彼時我們會回顧本章所說的「前向傳導演算法」,然後在說完 BPTT(亦即 RNN 的 BP)後、會給一個樸素的 Numpy 實現,這裡就先按下不表

LSTMs

LSTMs 全稱是 Long Short Term Memory networks,它是一個相當有趣、強大的 RNN 結構,已被廣泛地應用在諸多的實際問題中。雖說 LSTMs 有著不少的變種,不過它們的核心思想都在於改良 State 的傳遞過程,所以本節將只會介紹一種經典有效的 LSTMs 結構並希望能夠通過它來將 LSTMs 的內核闡述清楚

目前為止講過的 RNN 網路的 State 之間的通信都是樸素的線性傳遞,具體而言:

  • State 本身只是個單一的向量
  • State 之間的通信僅僅是通過矩陣進行線性變換

不難看出,如此單薄的結構是無法達到足夠的表現能力的。不過由於 RNN 結構的可擴展性很強,所以我們完全可以比較輕易地對它進行各種改良。比如說對於第一條來說,我們完全可以把 State 看作是一個「單元(cell)」,這個單元中可以僅包含單一的向量、也可以包含諸多錯綜複雜的東西:

在把 State 視為 cell 後,我們就可以在 cell 中任性地設計各種結構以改進 State 了:

可以看出,此時我們(通過定製 cell)獲得了「定製」State 的能力;而事實上 LSTMs 往簡單里說的話,無非就是定製了一種比較厲害的 cell 而已。比如說,一個經典的 LSTMs 的 cell 是長這樣的:

其中,圓圈代表著層結構、小正方形則代表著代數運算,phi_{1}phi_{2}則分別代表著 Sigmoid 函數和 Tanh 函數。雖說看上去相當的複雜,不過只要一步一步地分析其思想和結果,就會發現理解它其實並不是特別困難

首先可以看到的是,相比起樸素的線性傳遞,LSTMs 的 cell 的傳遞過程要更為複雜、且時間通道也從原來的一條變為了兩條。有趣的是,在 LSTMs cell 中位於上方的時間通道(h^{left( {old} right)} rightarrow h^{left( {new} right)})里僅包含了兩個代數運算而沒有包含任意的層結構,這意味著它信息傳遞的方式會更為「直接」。相應的「前向傳導演算法」是平凡的(其中r_{1}r_{2}留待後文討論)(註:統一使用「*」表示 element

wise 乘法,使用「times」表示矩陣乘法):

h^{left( {new} right)} = h^{left( {old} right)}*r_{1} + r_{2}

而位於下方的時間通道(s^{left( {old} right)} rightarrow s^{left( {new} right)})則運用了大量的層結構。在 LSTMs 中,我們通常稱這些層結構為「門(Gates)」,它擁有著很好的直觀(以自然語言處理問題為例):

  • 我們人類在處理自然語言問題時,通常會「有選擇」地「接受」或「遺忘」語句中的信息,傳統的 RNN 沒有類似的機制,LSTMs 則通過 Gates 來模擬相應的過程
  • 具體而言,我們知道 Sigmoid 函數取值區間為 0 到 1,那麼當 Sigmoid 對應的層結構輸出 0 時,就對應著「遺忘」這個過程;當輸出 1 時,自然就對應著「接受」這個過程。事實上這也是 Sigmoid 層叫「門」的原因——它能決定「放哪些數據進來」和決定「不讓哪些數據通過」

可以看到下方的時間通道中有三個 Sigmoid

Gate 和一個 Tanh Gate,它們除了和輸入直接相連之外,還連接著由上一個 cell 帶來的、下方的時間通道的信息。下面就從左往右地看一下各個 Gate 的表現(注意:不少人認為只有 Sigmoid 層才叫 Gate,不過筆者認為統一稱謂會更方便討論):

  • 最左邊的 Sigmoid Gate 通常被稱為「遺忘門(Forget

    Gate)」,它對應的「前向傳導演算法」為:r_{1} = phi_{1}left( W_{1} times x^{*} right),其中我們用x^* triangleq leftlbrack x,s^{left( {old} right)} rightrbrack表示將輸入樣本x和下方時間通道信息s^{(old)}「聯接(Concatenate)」起來。比如,若x = left( x_{1},ldots,x_{n} right)^{T},  s^{left( {old} right)} = left( s_{1}^{left( {old} right)},ldots,s_{p}^{left( {old} right)} right)^{T},則leftlbrack x,s^{left( {old} right)} rightrbrack triangleq left( x_{1},ldots,x_{n},s_{1}^{left( {old} right)},ldots,s_{p}^{left( {old} right)} right)^{T}
  • 第二個 Sigmoid Gate 通常被稱為「輸入門(Input

    Gate)」,第三個 Tanh Gate 則允許網路結構「駁回」歷史信息(因為 Tanh 的函數區間為到[-1,1])。它們對應的「前向傳導演算法」為:g_{1} = phi_{1}left( W_{2} times x^{*} right),g_{1} = phi_{2}left( W_{3} times x^{*} right),r_{2} = g_{1}*g_{2}
  • 第四個 Sigmoid Gate 通常被稱為「輸出門(Output

    Gate)」,它為輸出和傳向下一個 cell 的下方通道信息作出了貢獻。對應的「前向傳導演算法」為:g_{3} = phi_{1}left( W_{4} times x^{*} right)
  • 綜上即可算出:o = s^{left( text{new} right)} = phi_{2}left( h^{left( text{new} right)} right)*g_{3}

以上就是該經典 LSTMs 的「前向傳導演算法」的全部細節,下面說一些注意事項與個人理解:

  • 下方時間通道中,每一個 Gate 都同時連接著輸入和歷史信息,這意味著每個 cell 都能有將歷史信息進行複雜整合的能力
  • 每個 Gate 對應的權值矩陣是不同的(W_{1}sim W_{4}),切勿以為它們會「共享權值」
  • 每個 Gate 的「職能」也是不同的(至少我們期望他們的職能不同),具體而言:
    • 遺忘門控制著時間通道信息的遺忘程度
    • 輸入門控制著當前輸入和下方通道信息對上方通道信息的影響
    • 輸出門控制著當前輸入和通道信息對輸出和下方通道信息的影響

      注意:儘管如此,各個 Gate 的職能仍然或多或少地有重疊,所以適當地進行結構的刪減在某些場合下是合理的
  • 上方時間通道信息可視為「歷史信息」與「樣本信息」的某種組合
  • 每一時刻的輸出和相應的下方時間通道信息是一致的

以上我們就大概地過了一遍 RNN 的主要內容,我會在後續章節中說明如何利用 Tensorflow 來實現 LSTMs 的 cell 並做好封裝,同時我們會利用這個封裝來驗證一些直觀上的認知

希望觀眾老爺們能夠喜歡~

(猛戳我進入下一章! ( σ'ω')σ)

推薦閱讀:

譯文|LIME-分類器預測結果的解釋
AI安全如何入門(上)
1.2 如何看待與理解神經網路
《Learning to Rank using Gradient Descent》
機器學習-異常檢測演算法(三):Principal Component Analysis

TAG:数学 | 机器学习 | 神经网络 |