1. 程式人生 > 其它 >深度學習教程 | 序列模型與RNN網路

深度學習教程 | 序列模型與RNN網路

收藏ShowMeAI檢視更多精彩內容


本系列為吳恩達老師《深度學習專業課程》學習與總結整理所得,對應的課程視訊可以在這裡檢視。

引言

ShowMeAI前一篇文章 CNN應用:人臉識別和神經風格轉換 中我們對以下內容進行了介紹:

  • 人臉識別
  • Siamese網路
  • 三元組損失Triplet loss
  • 人臉驗證
  • CNN表徵
  • 神經網路風格遷移
  • 1D與3D卷積

本篇內容開始我們將對吳恩達老師的第5門課《Recurrent Neural Networks》進行總結梳理。這門課主要介紹迴圈神經網路(RNN)的基本概念、模型結構細節和具體應用。

自然語言(文字)和音訊都是時序前後相互關聯的資料,對於這些序列資料我們會使用特殊的神經網路:迴圈神經網路(Recurrent Neural Network,RNN)來進行處理。使用RNN實現的應用包括下圖中所示

  • 語音識別
  • 音樂發生器
  • 情感分類
  • DNA序列分析
  • 機器翻譯
  • 視訊動作識別
  • 命名實體識別

1.數學符號

我們來看一個自然語言處理中「命名實體識別」的例子,瞭解一下序列模型的數學符號命名規則。

示例語句為:王剛和李明去上班了

該句話包含9個字,簡單地表示\(y\)即為\(1 \times 9\)向量,每位對應字是否為人名的一部分,\(1\)表示是,\(0\)表示否。很明顯,該句話中「王」,「剛」,「李」,「明」均是人名成分,所以,對應的輸出\(y\)可表示為:

\[y = \begin{bmatrix} 1\\ 1\\ 0\\ 1\\ 1\\ 0\\ 0\\ 0\\ 0 \end{bmatrix} \]

\(y^{\left \langle t \right \rangle}\)表示序列對應位置的輸出,使用\(T_y\)表示輸出序列長度,我們有\(1\leq t\leq T_y\)

我們把輸入\(x\)表示為:

\[x = \begin{bmatrix} x^{\left \langle 1 \right \rangle}\\ x^{\left \langle 2 \right \rangle}\\ x^{\left \langle 3 \right \rangle}\\ x^{\left \langle 4 \right \rangle}\\ x^{\left \langle 5 \right \rangle}\\ x^{\left \langle 6 \right \rangle}\\ x^{\left \langle 7 \right \rangle}\\ x^{\left \langle 8 \right \rangle}\\ x^{\left \langle 9 \right \rangle} \end{bmatrix} \]

其中,\(x^{\left \langle t \right \rangle}\)表示序列對應位置的輸入,\(T_x\)表示輸入序列長度。注意,在當前序列標註示例彙總,\(T_x=T_y\),其他序列問題也存在\(T_x\neq T_y\)的情況。

我們用什麼樣的方式來表示\(x^{\left \langle t \right \rangle}\)呢,在機器學習場景下我們依舊希望是向量化的表徵,一種方法是我們會構建一個囊括大部分出現的字的詞彙庫vocabulary。例如一個包含10000個字的詞彙庫為:

\[\left[ \begin{matrix} 我 \\ 你 \\ \cdot \\ \cdot \\ \cdot \\ 王 \\ \cdot \\ \cdot \\ \cdot \\ 剛 \\ \cdot \\ \cdot \\ \cdot \\ 置 \end{matrix} \right] \]

該詞彙庫可看成是\(10000 \times 1\)的向量。注意,在自然語言處理中,針對不同的問題,我們構建的詞彙庫的基礎元素是不一樣的,命名實體識別場景下會用字,有些場景下會用詞(中文有分詞操作),有些大型任務下的詞彙庫可達百萬級別的詞彙量。

構建了詞彙庫之後,可以基於one-hot編碼,把居中每個\(x^{\left \langle t \right \rangle}\)都表示成\(10000 \times 1\)的向量,詞彙表中與\(x^{\left \langle t \right \rangle}\)對應的位置為1,其它位置為0。有時候會出現詞彙表之外的單詞,可以使用UNK或其他字串來佔位表示。

多樣本的情況,以上序列模型對應的命名規則可表示為:\(X^{(i){\left \langle t \right \rangle}}\)\(y^{(i){\left \langle t \right \rangle}}\)\(T_x^{(i)}\)\(T_y^{(i)}\)。其中,\(i\)表示第\(i\)個樣本。不同樣本的\(T_x^{(i)}\)\(T_y^{(i)}\)都有可能不同。

2.迴圈神經網路模型

對於序列模型,如果使用標準的神經網路,其模型結構如下:

使用標準的神經網路模型存在兩個問題:

不同樣本的輸入序列長度或輸出序列長度不同,即\(T_x^{(i)}\neq T_x^{(j)}\)\(T_y^{(i)}\neq T_y^{(j)}\),造成模型難以統一

  • 解決辦法之一是設定一個最大序列長度,對每個輸入和輸出序列補零並統一到最大長度。但是這種做法實際效果並不理想。

標準神經網路結構無法共享序列不同\(x^{\left \langle t \right \rangle}\)之間的特徵

  • 例如,如果某個\(x^{\left \langle t \right \rangle}\)即「王」是人名成分,那麼句子其它位置出現了「王」,也很可能也是人名。這是共享特徵的結果,如同CNN網路特點一樣。但是,上圖所示的網路不具備共享特徵的能力。

  • 共享特徵還有助於減少神經網路中的引數數量,一定程度上減小了模型的計算複雜度。例如上圖所示的標準神經網路,假設每個\(x^{\left \langle t \right \rangle}\)擴充套件到最大序列長度為100,且詞彙表長度為10000,則輸入層就已經包含了\(100 \times 10000\)個神經元了,權重引數很多,運算量將是龐大的。

綜上,我們瞭解到標準神經網路和CNN都不適合解決序列建模問題,而有一類神經網路:迴圈神經網路(Recurrent Neural Network,RNN)非常擅長處理序列建模問題的。它的典型結構如下:

這是一個沿著序列從左到右依次傳遞(展開)的模型。上面的例子中,輸入輸出長度直接有\(T_x=T_y\)的關係。模型裡\(x^{\left \langle t \right \rangle}\)\(\hat{y}^{\left \langle t \right \rangle}\)之間是隱藏神經元。\(a^{\left \langle t \right \rangle}\)會傳入到第\(t+1\)個元素中,作為輸入。初始的\(a^{{\left \langle t \right \rangle}}\)一般為零向量。

RNN包含三類權重係數\(W_{ya},W_{aa},W_{ax}\)且每個時間步是共享這3組權重的。如下圖所示:

2.1 前向傳播(Forward Propagation)

我們來展開看看RNN,下圖是每個時間步的神經元展開的內部結構:

前向傳播過程的計算公式如下:

公式中:

  • \(g_1\)是啟用函式,通常選擇\(tanh\),有時也用ReLU。
  • \(g_2\)可選sigmoid或Softmax,取決於解決的問題和需要的輸出型別。
  • 圖示中RNN Cell的函式,是上方公式的一個特例,是最常用的原始形態RNN。

我們簡化一下公式,把\(W_{aa}\)\(W_{ax}\)水平並列為一個矩陣\(W_a\),同時\(a^{\left \langle t-1 \right \rangle}\)和$ x^{\left \langle t \right \rangle}$堆疊成一個矩陣。可以得到如下的簡化版前向傳播計算公式:

\[W_a = [W_{aa}, W_{ax}] \] \[a^{\left \langle t \right \rangle} = g_1(W_a [a^{\left \langle t-1 \right \rangle}; x^{\left \langle t \right \rangle}] + b_a) \] \[\hat{y}^{\left \langle t \right \rangle} = g_2(W_{ya}a^{\left \langle t \right \rangle} + b_y) \]

我們上面介紹到的RNN為單向RNN,按照時間步從左到右單向依次運算得到結果,\(\hat{y}^{\left \langle t \right \rangle}\)只與前序的元素有關。但有些情況下\(\hat {y}^{\left \langle t \right \rangle}\)也與後續元素有關。如下例,單憑前面的字,無法確定「王」是否為人名的一部分,而新增後續(右側)的資訊可以有效輔助判斷。

  • 他說,「王剛已經離開學校了」
  • 他說,「王府內已經沒有差事可做了」

依賴兩側資訊的RNN叫做「雙向RNN」,簡稱為BRNN(Bidirectional RNN)後面的內容部分會再介紹到。

2.2 反向傳播(Backpropagation)

我們先為RNN場景定義一個損失函式。假如我們的問題是判斷序列當前輸入的字是否是人名的一部分,這是典型的二分類問題,我們採用交叉熵損失函式,計算公式如下所示:

\[L^{\left \langle t \right \rangle}(\hat {y}^{\left \langle t \right \rangle}, y^{\left \langle t \right \rangle}) = -y^{\left \langle t \right \rangle}log\hat {y}^{\left \langle t \right \rangle} - (1 - y^{\left \langle t \right \rangle})log(1-\hat {y}^{\left \langle t \right \rangle}) \]

我們沿著序列把每個位置的損失函式相加,得到整個序列的成本函式如下:

\[J = L(\hat {y}, y) = \sum^{T_x}_{t=1} L^{\left \langle t \right \rangle}(\hat {y}^{\left \langle t \right \rangle}, y^{\left \langle t \right \rangle}) \]

因為損失函式本身計算依賴時序上每個時間點,RNN的反向傳播也被稱為沿著時間的反向傳播(Backpropagation through time),這裡的反向傳播從右向左計算的過程就像是沿著時間倒推。

詳細的計算公式如下所示:

\[a^{\left \langle t \right \rangle}=\tanh \left(W_{a x} x^{\left \langle t \right \rangle}+W_{a a} a^{\left \langle t-1 \right \rangle}+b\right) \] \[\frac{\partial \tanh (x)}{\partial x}=1-\tanh (x)^{2} \] \[\frac{\partial a^{\left \langle t \right \rangle}}{\partial W_{a x}}=\left(1-\tanh \left(W_{a x} x^{\left \langle t \right \rangle}+W_{a a} a^{\left \langle t-1 \right \rangle}+b\right)^{2}\right) x^{ T} \] \[\frac{\partial a^{\left \langle t \right \rangle}}{\partial W_{a a}}=\left(1-\tanh \left(W_{a x} x^{\left \langle t \right \rangle}+W_{a a} a^{\left \langle t-1 \right \rangle}+b\right)^{2}\right) a^{T} \] \[\frac{\partial a^{\left \langle t \right \rangle}}{\partial b}=\sum_{b a t c h}\left(1-\tanh \left(W_{a x} x^{\left \langle t \right \rangle}+W_{a a} a^{\left \langle t-1 \right \rangle}+b\right)^{2}\right) \] \[\frac{\partial a^{\left \langle t \right \rangle}}{\partial x^{\left \langle t \right \rangle}}=W_{a x}{ }^{T} \cdot\left(1-\tanh \left(W_{a x} x^{\left \langle t \right \rangle}+W_{a a} a^{\left \langle t-1 \right \rangle}+b\right)^{2}\right) \] \[\frac{\partial a^{\left \langle t \right \rangle}}{\partial a^{\left \langle t-1 \right \rangle}}=W_{a a}{ }^{T} \cdot\left(1-\tanh \left(W_{a x} x^{\left \langle t-1 \right \rangle}+W_{a a} a^{(t-1\rangle}+b\right)^{2}\right) \]

2.3 RNN不同結構

前面我們提到的RNN,都是滿足輸入輸出長度一致的,即\(T_x=T_y\),但實際有很多型別的RNN輸入長度和輸出長度不一致。根據輸入及輸出長度關係,RNN可以分為以下結構:

  • 一對一(One to one):\(T_x=1,T_y=1\)
  • 一對多(One to many):\(T_x=1,T_y>1\)
  • 多對一(Many to one):\(T_x>1,T_y=1\)
  • 多對多(Many to many):\(T_x=T_y\)
  • 多對多(Many to many):\(T_x\neq T_y\)

3.語言模型

語言模型(Language Model)是自然語言處理(NLP)中最基本和最重要的任務之一。即使到今天大家看到很多新型神經網路模型訓練使用的基礎任務之一都還是語言模型。RNN模型能比較好地建立語言模型。

我們以一個例子來解釋一下什麼是語言模型。我們用演算法進行語音識別時,一句語音有兩種識別結果:

  • 歷史總是不斷重演
  • 力使總事不斷蟲眼

我們都能判斷,第二句話更有可能是正確的識別結果。語言模型是一個可以計算出這兩句話各自的出現概率的模型。比如預估第一句話概率為\(10^{-13}\),第二句話概率為\(10^{-10}\)

在語音識別的場景下,我們就可以利用語言模型得到各自語句的概率,選擇概率最大的語句作為正確的識別結果。概率計算的表示式為:

\[P(y^{\left \langle 1 \right \rangle},y^{\left \langle 2 \right \rangle},\cdots,y^{\left \langle T_y \right \rangle}) \]

那麼這個有用的語言模型如何構建呢,又和我們這裡提到的RNN有什麼關係呢,我們會按照如下步驟來操作:

  • ① 採集資料構建一個足夠大的訓練集(當前很多大模型都是以千萬和億為資料量級的),訓練集由大量的語句語料庫(corpus)構成。
  • ② 對語料庫的句子進行切分詞(tokenize)。對英文可以做單詞歸一化和切分單詞,中文按照字或者詞做切分(詞用得更多)。
  • ③ 基於分詞過後的句庫,建立vocabulary,對每個字/詞進行one-hot編碼。

例如下面這句話:

我愛吃油潑辣子彪彪面,味道非常好。

我們會給每句話結束末尾,加上< EOS >作為語句結束符。如果語句中遇到詞彙表中沒有的單詞,用< UNK >表示。假設單詞「彪」不在詞彙表中,則上面這句話可表示為:

我愛吃油潑辣子 < UNK > < UNK >面,味道非常好。< EOS >

將標誌化後的訓練集用於訓練RNN,過程如下圖所示:

訓練過程解析

① 第1個時間步中,輸入的\(a^{\left \langle 0 \right \rangle}\)\(x^{\left \langle 1 \right \rangle}\)都是零向量,\(\hat {y}^{\left \langle 1 \right \rangle}\)是通過Softmax預測出的字典中每個詞作為第一個詞出現的概率;

② 第2個時間步中,輸入的\(x^{\left \langle 2 \right \rangle}\)是訓練樣本的標籤中的第一個單詞\(y^{\left \langle 1 \right \rangle}\)(即「我」)和上一層的啟用項\(a^{\left \langle 1 \right \rangle}\),輸出的\(y^{\left \langle 2 \right \rangle}\)表示的是通過Softmax預測出的、詞語「我」後面出現字典中的其他每個詞的條件概率。

③ 以此類推,最後就可以得到整個句子出現的概率。

定義損失函式為:

\[L(\hat {y}^{\left \langle t \right \rangle}, y^{\left \langle t \right \rangle}) = -\sum_t y_i^{\left \langle t \right \rangle} log \hat {y}^{\left \langle t \right \rangle} \]

成本函式為:

\[J = \sum_t L^{\left \langle t \right \rangle}(\hat {y}^{\left \langle t \right \rangle}, y^{\left \langle t \right \rangle}) \]

4.取樣-序列生成

對於訓練好的語言模型,可以通過取樣(Sample)構建新的序列(對應上例就是產出新的句子),也可以進而瞭解這個模型學習到了一些什麼。

具體過程如下:

① 第1個時間步輸入\(a^{\left \langle 0 \right \rangle}\)\(x^{\left \langle 1 \right \rangle}\)為零向量,輸出預測出的字典中每個詞作為第一個詞出現的概率,根據Softmax的分佈進行隨機取樣(np.random.choice)

② 將上一步取樣得到的\(\hat {y}^{\left \langle 1 \right \rangle}\)作為第二個時間步的輸入\(x^{\left \langle 2 \right \rangle}\)

③ 以此類推,直到取樣到EOS,至此基於語言模型生成的一個完整句子序列生成完畢。

我們可以通過模型生成的句子,理解模型通過語料庫學習到的知識(詞語組合與分佈)。

以中文為例,上面構建語言模型,很多時候會基於「詞」來構建,但因為中文詞彙非常多,難免會出現未知標識(UNK)。

一種處理方式是可以基於「字」粒度建模,因為字總體數量有限得多,出現位置標識的可能性也更小。但這種方法也有缺點:得到的序列過多過長,且訓練成本更高。

總體來說,大家看到基於詞彙構建的語言模型更為常用。

5.RNN 梯度消失與梯度爆炸

梯度消失與梯度爆炸是深度神經網路中很重要的問題,對於RNN而言,序列較長也容易有對應的問題,我們來對這兩個問題做一點展開講解。

5.1 梯度消失

序列資料的特點是:可能存在距離上跨度很大的依賴關係,在我們前面提到的語言模型例子中,體現的就是某個詞對與其相距較遠的一個詞有強依賴。如下有2句英文句子:

The cat, which already ate a bunch of food, was full.

The cats, which already ate a bunch of food, were full.

很顯然兩句話裡,主語數量級和對應的be動詞是相互對應的:was受cat影響;were受cats影響。而它們之間都相隔了很多單詞。而一般的RNN模型每個元素受其周圍附近的影響較大,不擅長捕獲這種長期依賴關係。上面兩句話的這種依賴關係,由於跨度很大,普通的RNN網路容易出現梯度消失,捕捉不到它們之間的依賴,造成語法錯誤。

普通RNN裡的梯度消失問題比較難解決,可以使用調整結構的GRU和LSTM (下文會介紹到)作為緩解梯度消失問題的方案。

5.2 梯度爆炸

在反向傳播時,隨著層數的增多,梯度不僅可能指數型下降,也有可能指數型上升,即梯度爆炸。不過梯度爆炸比較容易發現,因為引數會急劇膨脹到數值溢位(可能顯示為 NaN)。

對於梯度爆炸,常用的處理辦法是採用梯度修剪(Gradient Clipping):觀察梯度向量,如果它大於某個閾值,則縮放梯度向量以保證其不會太大。

6.GRU(門控迴圈單元)

GRU(Gated Recurrent Units, 門控迴圈單元)改善了RNN的隱藏層,使其可以更好地捕捉深層連線,並改善了梯度消失問題。

依舊以前面提到的這個句子為例:

The cat, which already ate a bunch of food, was full.

模仿人類從左到右讀上面這個句子的方式:

  • GRU單元有一個新的變數稱為\(c\),代表記憶細胞(Memory Cell),其作用是提供記憶的能力,記住例如前文主語是單數還是複數等資訊。在時間\(t\),記憶細胞的值\(c^{\left \langle t \right \rangle}\)等於輸出的啟用值\(a^{\left \langle t \right \rangle}\)\(\tilde{c}^{\left \langle t \right \rangle}\)代表下一個\(c\)的候選值。

  • \(\Gamma_r\)代表相關門(Relevance Gate),表示\(\tilde{c}^{\left \langle t \right \rangle}\)\(c^{\left \langle t \right \rangle}\)的相關性。

  • $ \Gamma_u$代表更新門(Update Gate),用於決定什麼時候更新記憶細胞的值。

以上結構的具體公式為:

\[\Gamma _r = \sigma(W_r[c^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_r) \] \[\Gamma _u = \sigma(W_u[c^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_u) \] \[\tilde{c}^{\left \langle t \right \rangle} = tanh(W_c[\Gamma _r \ast c^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_c) \] \[c^{\left \langle t \right \rangle} = (1 - \Gamma _u) \times c^{\left \langle t-1 \right \rangle} + \Gamma _u \times \tilde{c}^{\left \langle t \right \rangle} \] \[a^{\left \langle t \right \rangle} = c^{\left \langle t \right \rangle} \]

當使用sigmoid作為啟用函式\(\sigma\)來得到\(\Gamma_u\)時,\(\Gamma_u\)的值在0到1的範圍內,且大多數時間非常接近於0或1。當\(\Gamma _u = 1\)時,\(c^{\left \langle t \right \rangle}\)被更新為\(\tilde{c}^{\left \langle t \right \rangle}\),否則保持為\(c^{\left \langle t-1 \right \rangle}\)。因為\(\Gamma_u\)可以很接近0,因此\(c^{\left \langle t \right \rangle}\)幾乎就等於\(c^{\left \langle t-1 \right \rangle}\)。在經過很長的序列後,\(c\)的值依然被維持,從而實現「記憶」的功能。

相關論文:

Cho et al., 2014. On the properties of neural machine translation: Encoder-decoder approaches

Chung et al., 2014. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling

7.LSTM(長短期記憶)

LSTM(Long Short Term Memory,長短期記憶)網路比GRU更加靈活和強大,它額外引入了遺忘門(Forget Gate)\(\Gamma _f\)和輸出門(Output Gate)\(\Gamma_o\)。其結構圖和公式如下:

\[\Gamma _u = \sigma(W_u[a^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_u) \] \[\Gamma _f = \sigma(W_f[a^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_f) \] \[\tilde{c}^{\left \langle t \right \rangle} = tanh(W_c[a^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_c) \] \[c^{\left \langle t \right \rangle} = \Gamma^{\left \langle t \right \rangle}_u \times \tilde{c}^{\left \langle t \right \rangle} + \Gamma^{\left \langle t \right \rangle}_f \times {c}^{\left \langle t-1 \right \rangle} \] \[\Gamma_o = \sigma(W_o[a^{\left \langle t-1 \right \rangle}, x^{\left \langle t \right \rangle}] + b_o) \] \[a^{\left \langle t \right \rangle} = \Gamma _o^{\left \langle t \right \rangle} \times tanh(c^{\left \langle t \right \rangle}) \]

以上為1個LSTM單元的結構,多個LSTM單元按時間次序連線起來,就得到LSTM網路。

上面講到的是簡化版的LSTM。在更為常用的版本中,幾個門值不僅取決於\(a^{\left \langle t-1 \right \rangle}\)\(x^{\left \langle t \right \rangle}\),有時也可以關聯上一個記憶細胞輸入的值\(c^{\left \langle t-1 \right \rangle}\),這被稱為窺視孔連線(Peephole Connection)。這時,和GRU不同,\(c^{\left \langle t-1 \right \rangle}\)和門值是一對一的。

\(c^{0}\)常被初始化為零向量。

相關論文:Hochreiter & Schmidhuber 1997. Long short-term memory

8.雙向迴圈神經網路(BRNN)

我們前面提到了,單向的RNN在某一時間步只能使用之前輸入的序列資訊。雙向迴圈神經網路(Bidirectional RNN,BRNN)可以在序列的任意位置使用之前和之後的資料。其工作原理是增加一個反向迴圈層,結構如下圖所示:

因此,有\(\hat{y}^{\left \langle t \right \rangle}= g(W_y[\overrightarrow{a}^{\left \langle t \right \rangle}, \overleftarrow{a}^{\left \langle t \right \rangle}] + b_y)\)

這個改進的方法不僅能用於基本的RNN,也可以用於GRU或LSTM。缺點是需要完整的序列資料,才能預測任意位置的結果。例如構建語音識別系統,需要等待使用者說完並獲取整個語音表達,才能處理這段語音並進一步做語音識別。因此,實際應用會有更加複雜的模組。

9.深度迴圈神經網路(DRNN)

為了進一步提升模型的學習能力,我們可以在RNN的每個時間步上,也增加隱層數量,構建深度迴圈神經網路(Deep RNN)。結構如下圖所示:

\(a^{[2]\left \langle 3 \right \rangle}\)為例,有\(a^{[2]\left \langle 3 \right \rangle} = g(W_a^{[2]}[a^{[2]\left \langle 2 \right \rangle}, a^{[1]\left \langle 3 \right \rangle}] + b_a^{[2]})\)

參考資料

ShowMeAI系列教程推薦

推薦文章