RNN
中文分詞、詞性標注、命名實體識別、機器翻譯、語音識別都屬於序列挖掘的范疇。序列挖掘的特點就是某一步的輸出不僅依賴於這一步的輸入,還依賴於其他步的輸入或輸出。在序列挖掘領域傳統的機器學習方法有HMM(Hidden Markov Model,隱馬爾可夫模型)和CRF(Conditional Random Field,條件隨機場),近年來又開始流行深度學習算法RNN(Recurrent Neural Networks,循環神經網絡)。

圖1 RNN網絡結構
比如一個句子中有5個詞,要給這5個詞標注詞性,那相應的RNN就是個5層的神經網絡,每一層的輸入是一個詞,每一層的輸出是這個詞的詞性。
- $x_t$是第t層的輸入,它可以是一個詞的one-hot向量,也可以是Distributed Representation向量。
- $s_t$是第t層的隱藏狀態,它負責整個神經網絡的記憶功能。$s_t$由上一層的隱藏狀態和本層的輸入共同決定,$s_t=f(Ux_t+Ws_{t-1})$,$f$通常是個非線性的激活函數,比如tanh或ReLU。由於每一層的$s_t$都會向后一直傳遞,所以理論上$s_t$能夠捕獲到前面每一層發生的事情(但實際中太長的依賴很難訓練)。
- $o_t$是第t層的輸出,比如我們預測下一個詞是什么時,$o_t$就是一個長度為$V$的向量,$V$是所有詞的總數,$o_{t}[i]$表示下一個詞是$w_i$的概率。我們用softmax函數對這些概率進行歸一化。$o_t=softmax(Vs_t)$。
- 值得一提的是,每一層的參數$U, W, V$都是共享的,這樣極大地縮小了參數空間。
- 每一層並不一定都得有輸入和輸出,隱藏單元才是RNN的必備武器。比如對句子進行情感分析時只需要最后一層給一個輸出即可。
RNN采用傳統的backpropagation+梯度下降法對參數進行學習,第$t$層的誤差函數跟第$o_t$直接相關,而$o_t$依賴於前面每一層的$x_i$和$s_i$,$i \le t$,這就是所謂的Backpropagation Through Time (BPTT)。在《神經網絡調優》中我已講到過這種深層神經網絡容易出現梯度消失或梯度爆炸的問題,為了避免網絡太“深”,有些人對RNN進行改造,避免太長的依賴,即$o_t$只依賴於$\{x_i, s_i\}$,其中$t-n \le i \le t$。LSTM也屬於一種改良的RNN,但它不是強行把依賴鏈截斷,而是采用了一種更巧妙的設計來繞開了梯度消失或梯度爆炸的問題,下文會詳細講解LSTM。
RNN的變體
雙向RNN
雙向RNN認為$o_t$不僅依賴於序列之前的元素,也跟$t$之后的元素有關,這在序列挖掘中也是很常見的事實。

圖2 Bidirectional RNNs網絡結構
深層雙向RNN
在雙向RNN的基礎上,每一步由原來的一個隱藏層變成了多個隱藏層。

圖3 Deep Bidirectional RNNs網絡結構
LSTM
前文提到,由於梯度消失/梯度爆炸的問題傳統RNN在實際中很難處理長期依賴,而LSTM(Long Short Term Memory)則繞開了這些問題依然可以從語料中學習到長期依賴關系。比如“I grew up in France... I speak fluent (French)”要預測()中應該填哪個詞時,跟很久之前的"France"有密切關系。
傳統RNN每一步的隱藏單元只是執行一個簡單的tanh或ReLU操作。

圖4 傳統RNN每個模塊內只是一個簡單的tanh層

圖5 LSTM每個循環的模塊內又有4層結構:3個sigmoid層,1個tanh層
LSTM每個模塊的4層結構后文會詳細說明,先來解釋一下基本的圖標。

圖6 圖標說明
粉色的圓圈表示一個二目運算。兩個箭頭匯合成一個箭頭表示2個向量首尾相連拼接在一起。一個箭頭分叉成2個箭頭表示一個數據被復制成2份,分發到不同的地方去。
LSTM內部結構詳解
LSTM的關鍵是細胞狀態$C$,一條水平線貫穿於圖形的上方,這條線上只有些少量的線性操作,信息在上面流傳很容易保持。

圖7 細胞狀態的傳送帶
第一層是個忘記層,決定細胞狀態中丟棄什么信息。把$h_{t-1}$和$x_t$拼接起來,傳給一個sigmoid函數,該函數輸出0到1之間的值,這個值乘到細胞狀態$C_{t-1}$上去。sigmoid函數的輸出值直接決定了狀態信息保留多少。比如當我們要預測下一個詞是什么時,細胞狀態可能包含當前主語的性別,因此正確的代詞可以被選擇出來。當我們看到新的主語,我們希望忘記舊的主語。

圖8 細胞狀態忘記一部分,保留一部分
上一步的細胞狀態$C_{t-1}$已經被忘記了一部分,接下來本步應該把哪些信息新加到細胞狀態中呢?這里又包含2層:一個tanh層用來產生更新值的候選項$\tilde{C}_t$,tanh的輸出在[-1,1]上,說明細胞狀態在某些維度上需要加強,在某些維度上需要減弱;還有一個sigmoid層(輸入門層),它的輸出值要乘到tanh層的輸出上,起到一個縮放的作用,極端情況下sigmoid輸出0說明相應維度上的細胞狀態不需要更新。在那個預測下一個詞的例子中,我們希望增加新的主語的性別到細胞狀態中,來替代舊的需要忘記的主語。

圖9 更新細胞狀態
現在可以讓舊的細胞狀態$C_{t-1}$與$f_t$(f是forget忘記門的意思)相乘來丟棄一部分信息,然后再加個需要更新的部分$i_t * \tilde{C}_t$(i是input輸入門的意思),這就生成了新的細胞狀態$C_t$。

圖10 生成新的細胞狀態
最后該決定輸出什么了。輸出值跟細胞狀態有關,把$C_t$輸給一個tanh函數得到輸出值的候選項。候選項中的哪些部分最終會被輸出由一個sigmoid層來決定。在那個預測下一個詞的例子中,如果細胞狀態告訴我們當前代詞是第三人稱,那我們就可以預測下一詞可能是一個第三人稱的動詞。

圖11 循環模塊的輸出
LSTM實現
參數更新方法 http://nicodjimenez.github.io/2014/08/08/lstm.html。核心是實現了$\frac{dL(t)}{dh(t)}$和$\frac{dL(t+1)}{ds(t)}$反向遞歸計算。
對應的代碼實現 https://github.com/nicodjimenez/lstm。總共不到200行代碼。
GRU
GRU(Gated Recurrent Unit)是LSTM最流行的一個變體,比LSTM模型要簡單。

圖12 GRU
