《Sequence Models》課堂筆記

Lesson 5 Sequence Models

這篇文章其實是 Coursera 上吳恩達老師的深度學習專業課程的第五門課程的課程筆記。

參考了其他人的筆記繼續歸納的。

符號定義

假如我們想要建立一個能夠自動識別句中人名地名等位置的序列模型,也就是一個命名實體識別問題,這常用於搜索引擎。命名實體識別系統可以用來查找不同類型的文本中的人名、公司名、時間、地點、國家名和貨幣名等等。

我們輸入語句 "Harry Potter and Herminoe Granger invented a new spell." 作爲輸入數據 \(x\),我們想要這個序列模型輸出 \(y\),使得輸入的每個單詞都對應一個輸出值,同時這個 \(y\) 能夠表明輸入的單詞是否是人名的一部分。技術上來說,還有更加複雜的輸出形式,它不僅能夠表明輸入詞是否是人名的一部分,它還能夠告訴你這個人名在這個句子裏從哪裏開始到哪裏結束。

以簡單的輸出形式爲例。這個輸入數據是 9 個單詞組成的序列,所以最終我們會有 9 個特徵集合來表示這 9 個單詞,並按序列中的位置進行索引,\(x^{<1>},x^{<2>}\) 直到 \(x^{<9>}\) 來索引不同的位置。

輸出數據也是一樣,用 \(y^{<1>},y^{<2>}\)\(y^{<9>}\) 來表示輸出數據。同時使用 \(T_x\) 來表示輸入序列的長度,\(T_y\) 表示輸出序列的長度。在這裏例子裏,\(T_x=9\),且 \(T_x=T_y\)

想要表示一個句子裏的單詞,首先需要做一張詞表(或者說詞典),也就是列一列我們的表示方法中用到的單詞。以下圖這個詞表爲例,它是一個 10,000 個單詞大小的詞表。這對現代自然語言處理應用來說太小了,對於一般規模的商業應用來說 30,000 到 50,000 詞大小的詞表比較常見,有些大型互聯網公司會有百萬詞等。

我們以這個 10,000 詞的詞表爲例。我們用 one-hot 表示法來表示詞典裏的每個單詞,也就是說 \(x^{<1>}\) 表示 Harry 這個單詞,而 Harry 在詞表中的第 4075 行,所以 \(x^{<1>}\) 最終表示爲一個長度爲 10,000,在 4075 行爲 1,其餘行爲 0 的向量。同理,其他的詞也這樣進行編碼。

循環神經網絡模型 (Recurrent Neural Network Model)

如果直接把 9 個 one-hot 向量輸入到一個標準神經網絡中,經過一些隱藏層,最終會輸出 9 個值爲 0 或者 1 的項來表明每個輸入單詞是否是人名的一部分。

但是結果發現這種方法並不好,主要有兩個問題。

  • 輸入和輸出數據在不同例子中可以有不同的長度,不是所有的例子都有相同的 \(T_x\)\(T_y\)。而且即使每個句子都有最大長度,我們可以填充使每個輸入語句都達到最大長度,但這仍然不是一個很好的方式。
  • 這樣一個神經網絡結構,它並不共享從文本的不同位置上學到的特徵。也就是說,如果神經網絡已經學習到了在位置 1 出現的 Harry 可能是人名的一部分,那麼如果 Harry 出現在其他位置,它也能自動識別其爲人名的一部分的話就好了。這其實類似於卷積神經網絡中,我們希望將圖片的局部學到的內容快速推廣到圖片的其他部分。所以用一個更好的表達方式,能夠讓我們減少模型中參數的數量。

循環神經網絡如下圖所示。將第一個詞輸入一個神經網絡層,讓神經網絡嘗試預測輸出,判斷這是否是人名的一部分。而接下來第二個詞,它不僅用 \(x^{<2>}\) 來預測 \(y^{<2>}\),它也會輸入來自上一層神經網絡的激活值,接下來的詞也以此類推。所以在每一個時間步中,循環神經網絡傳遞一個激活值到下一個時間步中用於計算。如果 \(T_x\)\(T_y\) 不相等,這個結果會需要作出一些改變。

要開始整個流程,在零時刻需要構造一個激活值 \(a^{<0>}\),這通常是零向量。當然也有其他初始化 \(a^{<0>}\) 的方法,不過使用零向量的僞激活值是最常見的選擇。

循環神經網絡是從左向右掃描數據,同時每個時間步的參數也是共享的。我們用 \(W_{ax}\) 來表示管理着從 \(x_{<1>}\) 到隱藏層的連接的一系列參數,而激活值也就是水平聯繫是由參數 \(W_aa\) 決定的,同理,輸出結果由 \(W_ya\) 決定。這些參數在每個時間步都是相同的。

這個循環神經網絡的一個缺點就是它只使用了這個序列中之前的信息來做出預測,如預測 \(\hat{y}^{<3>}\) 時,它沒有用到 \(x^{<4>},x^{<5>}\) 等的信息。所以對於這兩個句子

Teddy Roosevelt was a great President.

Teddy bears are on sale!

爲了判斷 Teddy 是否是人名的一部分,僅僅知道句中前兩個詞是完全不夠的。所以後續我們需要使用雙向循環神經網絡 (BRNN) 來解決這個問題。

我們仍以單向神經網絡爲例瞭解其計算過程。

一般開始先輸入 \(a^{<0>}\),接着就是前向傳播過程。
\[ a^{<1>} = g_{1}(W_{{aa}}a^{< 0 >} + W_{{ax}}x^{< 1 >} + b_{a})\\ \hat y^{< 1 >} = g_{2}(W_{{ya}}a^{< 1 >} + b_{y})\\ \cdots \cdots \]
循環神經網絡用的激活函數經常是 tanh,偶爾也會用 ReLU。

前向傳播公式的泛化公式如下,在 t 時刻
\[ a^{< t >} = g_{1}(W_{aa}a^{< t - 1 >} + W_{ax}x^{< t >} + b_{a})\\ \hat y^{< t >} = g_{2}(W_{{ya}}a^{< t >} + b_{y}) \]
我們的符號約定,以 \(W_{ax}\) 爲例,第二個下標意味着它要乘以某個 \(x\) 類型的量,然後第一個下標 \(a\) 表示它是用來計算某個 \(a\) 類型的變量。其他幾個矩陣符號也是同理。

爲了簡化這些符號,我們可以簡化一下,第一個計算 \(a^{<t>}\) 的公式可以寫作
\[ a^{<t>} =g(W_{a}\left\lbrack a^{< t-1 >},x^{} \right\rbrack +b_{a}) \]
然後我們定義 \(W_a\) 爲矩陣 \(W_{aa}\)\(W_{ax}\) 水平並列放置,即 \([ {{W}_{aa}}\vdots {{W}_{ax}}]=W_{a}\)。而 \(\left\lbrack a^{< t - 1 >},x^{< t >}\right\rbrack\) 表示的是將這兩個向量堆在一起,即 \(\begin{bmatrix}a^{< t-1 >} \\ x^{< t >} \\\end{bmatrix}\)。這樣,我們就把兩個參數矩陣壓縮成了一個參數矩陣,當我們建立更復雜模型時,這能簡化我們要用到的符號。

同理,對於 \(\hat y^{< t >}\) 的計算,也可以寫作
\[ \hat y^{< t >} = g(W_{y}a^{< t >} +b_{y}) \]
RNN 前向傳播示意圖如下。

nn-

穿越時間的反向傳播

爲了計算反向傳播,我們先定義一個元素損失函數。
\[ L^{}( \hat y^{},y^{}) = - y^{}\log\hat y^{}-( 1- y^{})log(1-\hat y^{}) \]
它對應的是序列中一個具體的詞,如果它是某個人的名字,那麼 \(y^{<t>}\) 的值爲 1,然後神經網絡將輸出這個詞是名字的概率值。它被定義爲標準邏輯迴歸損失函數,也叫交叉熵損失函數 (cross entropy loss)

整個序列的損失函數爲
\[ L(\hat y,y) = \ \sum_{t = 1}^{T_{x}}{L^{< t >}(\hat y^{< t >},y^{< t >})} \]
也就是把每個單獨時間步的損失函數都加起來。

在這個反向傳播過程中,最重要的信息傳遞或者說最重要的遞歸運算就是這個從右到左的運算,所以它被叫做穿越時間反向傳播 (backpropagation through time)

RNN 反向傳播示意圖如下。

nn_cell_backpro

不同類型的循環神經網絡

並不是所有的情況都滿足 \(T_x=T_y\)。比如電影情感分類,輸出 \(y\) 可以是 1 到 5 的整數,而輸入是一個序列。

之前的命名實體識別問題,屬於多對多 (many-to-many) 的結構。因爲輸入序列有很多的輸入,而輸出序列也有很多的輸出。還有一種多對多結構,和命名實體識別問題不同,它的輸入和輸出的序列可能是不同長度的。例如,機器翻譯,不同語言對於同一句話可能會有不同的長度的語句。而情感分類問題,屬於多對一 (many-to-one) 的結構。因爲它有很多輸入,然後輸出一個數字。當然也有一對一 (one-to-one) 結構,也就是標準的神經網絡。
其實還有一對多 (one-to-many) 的結構。例子是音樂生成,我們可以使用神經網絡通過我們輸入的一個整數(用來表示音樂類型或者第一個音符等信息)來生成一段音樂。

語言模型和序列生成

假如我們在做一個語音識別系統,聽到一個句子

The apple and pear (pair) salad was delicious.

語音識別系統就要判斷,在這個句子中是 pear 還是 pair。這裏,就要使用一個語言模型,它能計算出這兩句話各自的可能性。

這個概率指的是,假設我們隨機拿起一張報紙,打開任意郵件,或者任意網頁或者聽某人說一句話,這個即將從世界上的某個地方得到的句子會是某個特定句子的概率是多少。

使用 RNN 建立出這樣的模型,首先需要一個訓練集,包含一個很大的英文文本語料庫 (corpus) 或者其他的語言(這取決於我們的目的)。語料庫是自然語言處理的一個專有名詞,意思就是很長的或者說數量衆多的句子組成的文本。

如果訓練集中有這麼一句話

Cats average 15 hours of sleep a day.

那麼首先將這個句子標記化,就是像之前那樣,建立一個詞典,然後將每個單詞都轉換爲對應的 one-hot 向量。然後我們要定義句子的結尾,一般的做法就是增加一個額外的標記,叫做 EOS,用來表示句子的結尾。這樣能幫助我們明白一個句子什麼時候結束。

在標記化的過程中,我們可以自行決定要不要把標點符號看成標記。如果要把標點符號看作標記的話,那麼我們建立的詞典也應該加入這些標點符號。

如果訓練集有一些詞不在建立的詞典裏,如下面這個句子

The Egyptian Mau is a bread of cat.

Mau 這個詞可能比較少見,並不在我們建立的詞典裏。這種情況下,我們可以把 Mau 替換成一個叫做 UNK 的代表未知詞的標誌,我們只針對 UNK 建立概率模型,而不是針對這個具體的詞 Mau。

完成標記化後,意味着輸入的句子都映射到了各個標誌上。下一步就是構建 RNN。

仍然以 "Cats average 15 hours of sleep a day。“ 作爲輸入爲例。在第 0 個時間步,計算激活項 \(a^{<1>}\),它是以 \(x^{<1>}\) 作爲輸入的函數,而\(x^{<1>},a^{<1>}\) 都會被設爲全爲 0 的向量。於是 \(a^{<1>}\) 要做的就是它會通過 softmax 進行一些預測來計算出第一個詞可能會是什麼,結果爲 \(\hat{y}^{<1>}\)。這一步其實就是通過一個 softmax 層來預測詞典中任意單詞會是第一個詞的概率。

在下一時間步中,使用激活項 \(a^{<1>}\),然後輸入 \(x^{<2>}\) 告訴模型,第一個詞是 Cats,以此來計算第二個詞會是什麼。同理,輸出結果同意經過 softmax 層進行預測,預測這些詞的概率。以此類推。

爲了訓練這個網絡,我們需要定義代價函數。在某個時間步 \(t\),如果真正的詞是 \(y^{<t>}\),而神經網絡的 softmax 層預測結果值爲 \(\hat{y}^{<t>}\)。那麼 softmax 損失函數爲
\[ L\left( \hat y^{<t>},y^{<t>}\right) = - \sum_{i}^{}{y_{i}^{<t>}\log\hat y_{i}^{<t>}} \]
而總體損失函數爲
\[ L = \sum_{t}^{}{L^{< t >}\left( \hat y^{<t>},y^{<t>} \right)} \]
也就是把所有單個預測的損失函數相加。

如果我們用很大的訓練集來訓練這個 RNN,那麼我們可以通過開頭一系列單詞來預測之後單詞的概率。假設一個新句子只有三個單詞,那麼這個句子的概率計算如下
\[ P(y^{<1>},y^{<2>},y^{<3>})=P(y^{<1>})P(y^{<2>}|y^{<1>})P(y^{<3>}|y^{<1>},y^{<2>}) \]

對新序列採樣

在訓練一個序列模型之後,要想了解這個模型學到了什麼,一種非正式的方法就是進行一次新序列採樣。

我們要做的就是對這些概率分佈進行採樣來生成一個新的單詞序列。

第一步要做的就是對我們想要模型生成的第一個詞進行採樣。輸入 \(x^{<1>},a^{<1>}\) 爲 0 向量,然後得到一個 softmax 結果,根據這個 softmax 的分佈進行隨機採樣。也就是對這個結果使用 numpy 命令 (np.random.choice)。

然後根據模型結構,以此類推。直到得到 EOS 標識或者達到所設定的時間步。如果不想採樣到未知標識 UNK,可以拒絕採樣到的未知標識,繼續在剩下的詞中進行重採樣。

根據實際應用,也可以構建一個基於字符的 RNN 結構,這樣字典僅包含從 a 到 z 的字母,也可以再包含一些標點符號,特殊字符,數字等。這樣序列 \(y^{<1>},y^{<2>},\cdots\) 將會是單獨的字符而不是單詞。

這種結構優點是,我們不必擔心會出現未知的標識。而一個主要缺點就是,最後會得到太多太長的序列,計算成本比較高昂。

門控循環單元 (Gated recurrent unit, GRU)

循環神經網絡的梯度消失

對於下面兩個句子。

The cat, which already ate ......, was full.

The cats, which ate ......, were full.

前面的名詞和動詞應該保持一致的單複數形式,但是基本的 RNN 模型不擅長捕獲這種長期依賴效應。因爲梯度消失問題,後面層的輸出誤差很難影響前面層的計算。

儘管梯度爆炸也是會出現,但是梯度爆炸很明顯。因爲指數級大的梯度會讓參數變得極其大,以至於網絡參數崩潰,我們會看到很多 NaN,這意味着網絡計算出現了數值溢出。如果發現了梯度爆炸問題,一個解決辦法就是用梯度修剪。梯度修剪的意思就是觀察梯度向量,如果它大於某個閾值,縮放梯度向量,保證它不會太大。

GRU

標準的 RNN 單元如下圖所示。

1521560729

使用 GRU 可以使 RNN 更好地捕捉深層連接,並改善梯度消失問題。

仍然使用上面提到的單複數例子。GRU 會有個新的變量稱爲 \(c\),代表細胞 (cell),即記憶細胞。記憶細胞的作用是提供了記憶的能力,比如貓是單數還是複數,當它看到之後的句子的時候,它仍能夠判斷句子的主語是單數還是複數。於是在時間 \(t\) 處,有記憶細胞 \(c^{<t>}\),然後 GRU 實際輸出了激活值 \(a^{<t>}\),且 \(c^{<t>}=a^{<t>}\)

在每個時間步,我們將用一個候選值重寫記憶細胞,即 \({\tilde{c}}^{<t>}\)。然後我們用 tanh 函數來計算
\[ {\tilde{c}}^{<t>} =tanh(W_{c}\left\lbrack c^{<t-1>},x^{<t>} \right\rbrack +b_{c}) \]
GRU 中真正重要的思想是我們有一個門,記爲 \(\Gamma_{u}\),其中下標 \(u\) 代表更新 (update) 。它是一個 0 到 1 之間的值。它的計算方式如下
\[ \Gamma_{u}= \sigma(W_{u}\left\lbrack c^{<t-1>},x^{<t>} \right\rbrack +b_{u}) \]
對於大多數可能的輸入,sigmoid 函數的輸出總是非常接近 0 或者 1,所以這個值大多數情況下也是非常接近 0 或 1 的。

所以 GRU 的關鍵部分就是使用 \(\tilde{c}\) 來更新 \(c\),然後使用門來決定是否真的要更新。即
\[ c^{<t>} = \Gamma_{u}*{\tilde{c}}^{<t>} +\left( 1- \Gamma_{u} \right)*c^{<t-1>} \]
GRU 的一個簡化示意圖如下。

因爲 \(\Gamma_u\) 很接近 0,那麼更新式子就會變成 \(c^{<t>}=c^{<t-1>}\)。也就是說,即使經過很多很多的時間步,\(c^{<t>}\) 的值也很好地被維持了,這就是緩解梯度消失問題的關鍵。

而對於一個完整的 GRU,我們需要在計算第一個式子中給記憶細胞的新候選值加上一個新的項。我們要添加一個新的門 \(\Gamma_r\),其中下標 \(r\) 可以代表相關性 (relevance)。這個門的作用是告訴我們,計算出的下一個 \(c^{<t>}\) 的候選值 \(\tilde{c}^{<t-1>}\)\(c^{<t-1>}\) 有多大的相關性。它的計算方式如下
\[ \Gamma_{r}= \sigma(W_{r}\left\lbrack c^{<t-1>},x^{<t>} \right\rbrack + b_{r}) \]
那麼完整的 GRU 計算公式則爲
\[ \tilde{c}^{<t>}=tanh(W_c[\Gamma_r\times c^{<t-1>},x^{<t>}]+b_c)\\ \Gamma_{u}= \sigma(W_{u}\left\lbrack c^{<t-1>},x^{<t>} \right\rbrack +b_{u})\\ \Gamma_{r}= \sigma(W_{r}\left\lbrack c^{<t-1>},x^{<t>} \right\rbrack + b_{r})\\ c^{<t>} = \Gamma_{u}*{\tilde{c}}^{<t>} +\left( 1- \Gamma_{u} \right)*c^{<t-1>}\\ a^{<t>}=c^{<t>} \]

長短期記憶單元 (long short term memory unit, LSTM unit)

LSTM 是一個比 GRU 更加強大和通用的版本。

LSTM 的主要公式如下
\[ \tilde{c}^{<t>}=tanh(W_c[a^{<t-1>},x^{<t>}]+b_c)\\ \Gamma_u=\sigma(W_u[a^{<t-1>},x^{<t>}]+b_u)\\ \Gamma_f=\sigma(W_f[a^{<t-1>},x^{<t>}]+b_f)\\ \Gamma_o=\sigma(W_o[a^{<t-1>},x^{<t>}]+b_o)\\ c^{<t>}=\Gamma_u \times \tilde{c}^{<t>}+\Gamma_f \times c^{<t-1>}\\ a^{<t>}=\Gamma_o \times c^{<t>} \]
在 LSTM 中,我們不再有 \(a^{<t>}=c^{<t>}\),我們專門使用 \(a^{<t>}\) 或者 \(a^{<t-1>}\),而不是用 \(c^{<t-1>}\),也不再用相關門 \(\Gamma_r\)。LSTM 保留了更新門,但不僅僅由更新門來控制,加入了遺忘門 (the forget gate) \(\Gamma_f\)輸出門 (the output gate) \(\Gamma_o\)

所以給了記憶細胞選擇權去維持舊的值 \(c^{<t-1>}\) 或者加上新的值 \(\tilde{c}^{<t>}\)

LSTM 示意圖如下。

可以發現在上圖中的序列中,上面有條線顯示了只要正確地設置了遺忘門和更新門,LSTM 是很容易把 \(c^{<0>}\) 的值一直往下傳遞的。當然,這個圖示和一般使用的版本有些許不同。最常用的版本的門值不僅取決於 \(a^{<t-1>}\)\(x^{<t>}\),偶爾也可以偷窺一下 \(c^{<t-1>}\) 的值(上圖中編號 13),這叫做窺視孔連接 (peephole connection)

LSTM 前向傳播圖:

ST

STM_rn

LSTM 反向傳播計算:

門求偏導
\[ d \Gamma_o^{\langle t \rangle} = da_{next}*\tanh(c_{next}) * \Gamma_o^{\langle t \rangle}*(1-\Gamma_o^{\langle t \rangle})\\ d\tilde c^{\langle t \rangle} = dc_{next}*\Gamma_i^{\langle t \rangle}+ \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) * i_t * da_{next} * \tilde c^{\langle t \rangle} * (1-\tanh(\tilde c)^2)\\ d\Gamma_u^{\langle t \rangle} = dc_{next}*\tilde c^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) * \tilde c^{\langle t \rangle} * da_{next}*\Gamma_u^{\langle t \rangle}*(1-\Gamma_u^{\langle t \rangle})\\ d\Gamma_f^{\langle t \rangle} = dc_{next}*\tilde c_{prev} + \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) * c_{prev} * da_{next}*\Gamma_f^{\langle t \rangle}*(1-\Gamma_f^{\langle t \rangle}) \]
參數求偏導
\[ dW_f = d\Gamma_f^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T\\ dW_u = d\Gamma_u^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T\\ dW_c = d\tilde c^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T\\ dW_o = d\Gamma_o^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \]
爲了計算 \(db_f, db_u, db_c, db_o\),需要各自對 \(d\Gamma_f^{\langle t \rangle}, d\Gamma_u^{\langle t \rangle}, d\tilde c^{\langle t \rangle}, d\Gamma_o^{\langle t \rangle}\) 求和。

最後,計算隱藏狀態、記憶狀態和輸入的偏導數。
\[ da_{prev} = W_f^T*d\Gamma_f^{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c^{\langle t \rangle} + W_o^T * d\Gamma_o^{\langle t \rangle} \\ dc_{prev} = dc_{next}\Gamma_f^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} * (1- \tanh(c_{next})^2)*\Gamma_f^{\langle t \rangle}*da_{next} \\ dx^{\langle t \rangle} = W_f^T*d\Gamma_f^{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c_t + W_o^T * d\Gamma_o^{\langle t \rangle} \]
什麼時候用 GRU,什麼時候用 LSTM,其實沒有統一的標準。

GRU 的優點是,它是個更加簡單的模型,所以容易創建一個更大的網絡,而且它只有兩個門,在計算性上也運行得更快,然後它可以擴大模型的規模。

但是 LSTM 更加強大和靈活。現在大部分的人還是會把 LSTM 作爲默認的選擇來嘗試。

雙向循環神經網絡

我們以一個只有 4 個單詞的句子爲例。那麼這個網絡會有一個前向的循環單元爲 \({\overrightarrow{a}}^{<1>},{\overrightarrow{a}}^{<2>},{\overrightarrow{a}}^{<3>},{\overrightarrow{a}}^{<4>}\),這四個循環單元輸入,都會得到對應的輸出 \(\hat{y}^{<1>},\hat{y}^{<2>},\hat{y}^{<3>},\hat{y}^{<4>}\)

接下來,我們增加一個反向循環層,\({\overleftarrow{a}}^{<1>},{\overleftarrow{a}}^{<2>},{\overleftarrow{a}}^{<3>},{\overleftarrow{a}}^{<4>}\),同樣這一層也向上連接。這樣,這個網絡如下所示。先前向計算,然後再反向計算,把所有激活值都計算完了就可以計算預測結果了。

這些單元可以是標準 RNN 單元,也可以是 GRU 或者 LSTM 單元。而且實踐中,很多 NLP 問題,有 LSTM 單元的雙向 RNN 模型是用得最多的。

BRNN 的缺點就是需要完整的數據序列,才能預測任意位置。

深層循環神經網絡 (Deep RNNs)

一個標準的神經網絡,首先是輸入 \(x\),然後堆疊上隱含層。深層 RNN 類似,堆疊隱含層,然後每層按時間展開就是了,如下圖所示。

對於標準的神經網絡,可能有很深的網絡,但是對於 RNN 來說,有三層就已經不少了。由於時間的維度,RNN 網絡會變得相當大。

詞嵌入 (Word embedding)

詞嵌入是語言表示的一種方式,可以讓算法自動的理解一些類似的詞。比如男人對女人,國王對王后等等。

之前我們是用詞典的 one-hot 向量來表示詞,比如說 man 在詞典中第 5391 個位置,那麼它的 one-hot 向量標記爲 \(O_{5391}\)。這種表示方法的一大缺點就是它把每個詞都孤立起來了,使得算法對相關詞的泛化能力不強。

舉個例子,我們的語言模型已經學習到了 "I want a glass of orange juice",但是當它看到 "I want a glass of apple ____" 時,算法可能無法填出 juice 這個單詞。算法不知道 apple 和 orange 的關係很接近,因爲任何兩個 one-hot 向量的內積都是 0。

但是如果我們用特徵化來表示每個詞,假如說這些特徵維度 Gender, Royal, Age 等等,這樣對於不同的單詞,算法會泛化得更好。

當然,我們最終學習的特徵可能不會像 Gender, Royal 等這些比較好理解,甚至不太好用實際意義去解釋。

接下來,我們可以把詞嵌入應用到命名實體識別任務當中,儘管我們可能只有一個很小的訓練集,100,000 個單詞,甚至更小。我們可以使用遷移學習,把互聯網上免費獲得的大量的無標籤文本中學習到的知識遷移到一個任務中。

所以,如何用詞嵌入做遷移學習的步驟如下:

  • 先從大量的文本集中學習詞嵌入。一個非常大的文本集,或者可以下載網上預訓練好的詞嵌入模型,網上可以知道不少,而且詞嵌入模型一般都有許可。
  • 用這些詞嵌入模型遷移到我們的新的只有少量標註訓練集的任務中,比如說用一個 300 維的詞嵌入來表示單詞。
  • 在新的任務上訓練模型,可以選擇要不要繼續微調,用新的數據調整詞嵌入。當然,一般來說,只有新數據有比較大的數據量時,纔會進行微調。

假如說我們以這四個維度的特徵來表徵詞。詞的特徵向量都以符號 \(e\) 表示。

那麼
\[ e_{\text{man}} - e_{\text{woman}} = \begin{bmatrix} - 1 \\ 0.01 \\ 0.03 \\ 0.09 \\ \end{bmatrix} - \begin{bmatrix} 1 \\ 0.02 \\ 0.02 \\ 0.01 \\ \end{bmatrix} = \begin{bmatrix} - 2 \\ - 0.01 \\ 0.01 \\ 0.08 \\ \end{bmatrix} \approx \begin{bmatrix} - 2 \\ 0 \\ 0 \\ 0 \\ \end{bmatrix}\\ e_{\text{king}} - e_{\text{queen}} = \begin{bmatrix} - 0.95 \\ 0.93 \\ 0.70 \\ 0.02 \\ \end{bmatrix} - \begin{bmatrix} 0.97 \\ 0.95 \\ 0.69 \\ 0.01 \\ \end{bmatrix} = \begin{bmatrix} - 1.92 \\ - 0.02 \\ 0.01 \\ 0.01 \\ \end{bmatrix} \approx \begin{bmatrix} - 2 \\ 0 \\ 0 \\ 0 \\ \end{bmatrix} \]
可以發現這兩組向量相減得到的向量基本一致。也就表明,這兩對詞都只在 gender 這個特徵維度有顯著差異。

我們可以使用餘弦相似度來表徵這些向量的相似度。
\[ \text{sim}\left( u,v \right) = \frac{u^{T}v}{\left| \left| u \right| \right|_{2}\left| \left| v \right| \right|_{2}} \]
這樣,我們就能通過計算相似度來找到相近的詞。

當我們應用算法來學習詞嵌入時,其實是在學習一個嵌入矩陣 (embedding matrix)

假設我們的詞典有 10,000 個單詞,我們要做的就是學習一個嵌入矩陣 \(E\),它將是一個 \(300\times10,000\) 的矩陣。這個矩陣的各列代表的是詞典中 10,000 個單詞所代表的特徵向量。

學習詞嵌入

建立一個語言模型是學習詞嵌入的好方法。

如何建立神經網絡來預測序列中的下一個單詞呢?首先,以下圖中的句子爲例。先使用 one-hot 向量表示這些單詞,然後生成一個參數矩陣 \(E\),用 \(E\) 乘以 one-hot 向量 \(o\),這樣得到嵌入向量 \(e\)。於是我們有了很多 300 維的嵌入向量,把它們放進神經網絡中,然後再通過一個 softmax 層,然後 softmax 分類器會在 10,000 個可能的輸出中預測結尾這個單詞。

實際上,更常見的是有一個固定的歷史窗口。舉個例子,我們總是想預測給定四個單詞(也可以是其他的個數)後的下一個單詞,這樣就可以適應很長或者很短的句子。用一個固定的歷史窗口意味着可以處理任意長度的句子,因爲輸入的維度總是固定的。所以,這個模型的參數就是矩陣 \(E\),對所有的單詞用的都是同一個矩陣 \(E\)

當然除了選前四個單詞,還有其他的上下文構建方式。但是建立語言模型,用目標詞的前幾個單詞作爲上下文是常見做法。

Word2Vec

假設在訓練集中給定了一個這樣的句子 "I want a glass of orange juice to go along with my cereal.",在 skip-gram 模型中,我們要做的是抽取上下文和目標詞配對,來構造一個監督學習問題。上下文不一定總是目標單詞之間離得最近的四個單詞或 n 個單詞。

我們要做的是隨機選一個詞作爲上下文詞,然後隨機在一定詞距內選另一個詞作爲目標詞。於是我們將構造一個監督學習問題,它給定上下文詞,要求預測在這個詞一定詞距內隨機選擇的某個目標詞。顯然,這不是個非常簡單的學習問題。但是,構造這個監督學習問題的目標並不是想要解決這個監督學習問題本身,而是想要使用這個學習問題來學到一個好的詞嵌入模型。

我們要解決的基本的監督學習問題是學習一種映射關係,從上下文 \(c\) 到某個目標詞 \(t\)。從 one-hot 向量 \(O_c\) 開始,然後乘以嵌入矩陣 \(E\) 得到上下文詞的嵌入向量,\(e_c=EO_c\)。接着,把向量 \(e_c\) 喂入 softmax 單元,輸出 \(\hat{y}\),預測不同目標詞的概率:
\[ Softmax:p(t|c)=\frac{e^{\theta_t^T e_c}}{\sum_{j=1}^{10,000}e^{\theta_j^T e_c}} \]
其中 \(\theta_t\) 是一個與輸出 \(t\) 有關的參數,即某個詞 \(t\) 和標籤相符的概率是多少,這裏省略了 softmax 中的偏差項,想要加上的話也是可以加上的。

於是 softmax 的損失函數爲
\[ L(\hat{y},y)=-\sum_{i=1}^{10,000}y_i\log{\hat{y_i}} \]
矩陣 \(E\) 將會有很多參數,優化這個關於所有這些參數的損失函數,就能得到一個較好的嵌入向量集。這個就叫做 skip-gram 模型。

這個算法首要的問題就是計算速度,尤其是在 softmax 模型中,每次要計算這個概率,就要對詞典中所有詞做求和計算,這個求和操作是相當慢的。

這裏有一些解決方案,如分級 (hierarchical) 的 softmax 分類器負採樣 (Negative Sampling)

分級 softmax 分類器

這個分類器的意思是,通過一層一層的節點來分類詞。這樣計算成本與詞典大小的對數成正比,而不是詞典大小的線性函數。在實踐中,不會使用一棵完美平衡的分類樹或者說一棵左邊和右邊分支的詞數相同的對稱樹,而是會被構造成常用詞在頂部,不常用的詞在樹的更深處。這是一種加速 softmax 分類器的方法。

負採樣

這個算法要做的是構造一個新的監督學習算法。給定一對單詞來預測者是否是一對上下文詞-目標詞 (context-target)。

比如,orange 和 juice 爲一對正樣本,orange 和 king 爲一對負樣本。我們要做的就是採樣得到一個上下文詞和一個目標詞。正樣本的生成方式與 word2vec 類似,先抽取一個上下文詞,在一定詞距內選一個目標詞,標記爲 1。然後爲了生成一個負樣本,我們將用相同的上下文詞,再在字典中隨機選一個詞,標記爲 0。如果我們挑選負樣本的時候,從字典中隨機選到的詞,正好出現在了詞距內,但是我們標記爲負樣本也沒關係。

然後,我們將構造一個監督學習問題。我們的算法輸入詞對,預測其標籤。

K 值的選取。論文作者推薦小數據集的話,K 從 5 到 20 比較好;如果數據集很大,K 就選的小一點,如 K 等於 2 到 5。這個例子中,我們使 \(K=4\)

我們定義一個邏輯迴歸模型,給定輸入的 \(c,t\) 對(上下文詞 \(c\) 和目標詞 \(t\))的條件下輸出 \(y=1\) 的概率,即
\[ P(y=1|c,t)=\sigma(\theta_t^Te_c) \]
把它畫成一個神經網絡,如果輸入詞是 orange,即第 6257 個詞,那麼輸入它的 one-hot 向量,乘以嵌入矩陣 \(E\),獲得嵌入向量 \(e_{6257}\)。這樣,我們得到了 10,000 個可能的邏輯迴歸分類問題。其中一個是用來判斷目標詞是否是 juice 的分類器。但不是每次迭代都訓練全部 10,000 個,\(K=5\) 時,我們只訓練其中的 5 個。訓練對應真正目標詞那一個分類器,再訓練 4 個隨機選取的負樣本,所以不使用一個巨大的 softmax,而是把它轉變爲多個二分類問題。二分類問題每個都很容易計算,而且每次迭代只要訓練它們其中的幾個。

其中一個重要的細節就是如何選取負樣本。一個方法是根據語言中的經驗頻率對這些詞進行採樣,但是 like, the, of, and 這種詞有很高的頻率。另一個就是用 1 除以詞典總詞數,即 \(\frac{1}{|v|}\),均勻且隨機地抽取負樣本,但是這對於英文單詞的分佈是非常沒有代表性的。作爲一個折中,論文作者根據經驗,採用以下方式進行採樣,也就是實際觀察到的英文文本的分佈:
\[ P(w_i)=\frac{{f(w_i)^{\frac{3}{4}}}}{\sum_{j=1}^{10,000}{f(w_j)}^{\frac{3}{4}}} \]
也就是 \(f(w_i)\) 是觀測到的在語料庫中的某個單詞的詞頻,通過 \(\frac{3}{4}\) 次方的計算,使其處於完全獨立的分佈和訓練集的觀測分佈兩個極端之間。

GloVe 詞向量

GloVe 算法不如 Word2Vec 或是 Skip-Gram 模型用的多,但是也有研究者熱衷於它,可能是因爲其簡便性。

GloVe 代表用詞表示的全局變量。還是挑選語料庫中位置相近的兩個詞,即上下文-目標詞。GloVe 算法做的就是使其關係開始明確化。假設 \(X_{ij}\) 是單詞 \(i\) 在單詞 \(j\) 上下文中出現的次數,那麼這裏 \(i\)\(j\) 就和 \(t\)\(c\) 的功能一樣。

如果對於上下文的定義是目標詞一定範圍詞距的單詞,那麼 \(X_{ij}=X_{ji}\);而如果對於上下文的定義爲目標詞的前一個單詞,那麼 \(X_{ij}\)\(X_{ji}\) 就不會相同。

不過對於 GloVe 算法,我們可以定義上下文和目標詞爲任意兩個位置相近的單詞,假設是左右各 10 詞的距離,那麼 \(X_{{ij}}\) 就是一個能夠獲取單詞 \(i\) 和單詞 \(j\) 出現位置相近時或是彼此接近的頻率的計數器。GloVe 模型做的就是進行優化,將它們之間的差距進行最小化處理。
\[ minimize \sum_{i=1}^{10,000} \sum_{j=1}^{10,000} f(X_{ij})(\theta_i^Te_j+b_i+b_j'-\log{X_{ij}})^2 \]
而如果 \(X_{ij}=0\) 的話,\(log0\) 爲未定義,爲負無窮大。所以公式中加上了一個額外的加權項 \(f(X_{ij})\),這樣 \(X_{ij}=0\) 時,我們有 \(0log0=0\)。這個加權項還有一個作用是,有些詞在英語中出現十分頻繁如 this, is, of, a 等,它們被叫作停止詞,加權項可以給予大量有意義的運算給不常用詞,同樣給停止詞更大但不至於過分的權重。因此,有一些對加權函數 \(f\) 的選擇有着啓發性的原則。

情感分類 (Sentiment Classification)

情感分類任務就是看一段文本,然後分辨這個人是否喜歡他們在討論的這個東西,這是自然語言處理中最重要的模塊之一,經常用在許多應用中。情感分類一個最大的挑戰就是可能標記的訓練集沒有那麼多,但是有了詞嵌入,即使只有中等大小的標記的訓練集,也能構建一個不錯的情感分類器。

下圖是一個簡單的情感分類模型。假設輸入爲 "The dessert is excellent",我們從詞典中取出這些詞,然後形成 one-hot 向量,乘以嵌入矩陣 \(E\) 來獲取嵌入向量。其中嵌入矩陣可以從很大的訓練集上訓練獲得。接着,對這些嵌入向量進行求和或者平均,就會得到一個特徵向量,把它輸入 softmax 分類器,輸出 \(\hat{y}\) 也就是一星到五星的概率值。

這個算法運用的平均值運算單元適用於任何長短的評論,它實際上會把所有單詞的意思給平均起來。

這個算法有一個問題就是沒有考慮詞序,尤其是這樣一個負面的評價。

"Completely lacking in good taste, good service, and good ambiance."

這個句子中出現了很多 good,分類器很可能會認爲這是一個好的評價。

這樣,我們有一個更加複雜的模型來處理,使用 RNN 來做情感分類。如下圖所示。

詞嵌入糾偏

一個已經完成學習的詞嵌入可能會輸出ManComputer Programmer,同時輸出WomanHomemaker,那個結果看起來是錯的,並且它執行了一個十分不良的性別歧視。因此根據訓練模型所使用的文本,詞嵌入能夠反映出性別、種族、年齡、性取向等其他方面的偏見,一件我尤其熱衷的事是,這些偏見都和社會經濟狀態相關,我認爲每個人不論你出身富裕還是貧窮,亦或是二者之間,我認爲每個人都應當擁有好的機會,同時因爲機器學習算法正用來制定十分重要的決策,它也影響着世間萬物,從大學錄取到人們找工作的途徑,到貸款申請,不論你的的貸款申請是否會被批准,再到刑事司法系統,甚至是判決標準,學習算法都在作出非常重要的決策,所以我認爲我們儘量修改學習算法來儘可能減少或是理想化消除這些非預期類型的偏見是十分重要的。

假設說我們已經完成一個詞嵌入的學習,先我們要做的事就是辨別出我們想要減少或想要消除的特定偏見的趨勢。

以性別偏見爲例。主要有以下三個步驟。

  • 對於性別偏見來說。我們將一些性別相關的詞對進行嵌入向量相減,如 \(e_{he}-e_{she},e_{male}-e_{female}\),然後將這些值取平均。這個趨勢,看起來就是性別趨勢,但是與我們想要處理的特定偏見無關,所以這就是個無偏的性別趨勢。實際上,它會用一個更加複雜的算法——奇異值分解(SVU),和主成分分析很類似。

  • 中和步驟。對於那些定義不確切的詞可以將其處理一下。如 grandmother, grandfather 這些詞定義中本來就含有性別意義,而 doctor, babysitter 這些詞我們希望它是中立的。所以對於中立詞,我們想要減少他們在水平方向上的距離。

  • 均衡步。意思是說你可能會有這樣的詞對,grandmother和grandfather,或者是girl和boy,對於這些詞嵌入,你只希望性別是其區別。那爲什麼要那樣呢?在這個例子中,babysitter和grandmother之間的距離或者說是相似度實際上是小於babysitter和grandfather之間的(上圖編號1所示),因此這可能會加重不良狀態,或者可能是非預期的偏見,也就是說grandmothers相比於grandfathers最終更有可能輸出babysitting。所以在最後的均衡步中,我們想要確保的是像grandmother和grandfather這樣的詞都能夠有一致的相似度,或者說是相等的距離,和babysitter或是doctor這樣性別中立的詞一樣。這其中會有一些線性代數的步驟,但它主要做的就是將grandmother和grandfather移至與中間軸線等距的一對點上(上圖編號2所示),現在性別歧視的影響也就是這兩個詞與babysitter的距離就完全相同了(上圖編號3所示)。所以總體來說,會有許多對像grandmother-grandfather,boy-girl,sorority-fraternity,girlhood-boyhood,sister-brother,niece-nephew,daughter-son這樣的詞對,我們可能想要通過均衡步來解決它們。

均衡背後的關鍵思想是確保一對特定的單詞與49維\(g_\perp\)距離相等 。均衡步驟還可以確保兩個均衡步驟現在與\(e_{receptionist}^{debiased}\) 距離相同,或者用其他方法進行均衡。下圖演示了均衡算法的工作原理:

qualize1

主要步驟如下:

$$
\mu = \frac{e_{w1} + e_{w2}}{2}\

\mu_{B} = \frac {\mu * \text{bias_axis}}{||\text{bias_axis}||_2} + ||\text{bias_axis}||_2 *\text{bias_axis}\

\mu_{\perp} = \mu - \mu_{B} \

e_{w1B} = \sqrt{ |{1 - ||\mu_{\perp} ||^2_2} |} * \frac{(e_{{w1}} - \mu_{\perp}) - \mu_B} {|(e_{w1} - \mu_{\perp}) - \mu_B)|}\

e_{w2B} = \sqrt{ |{1 - ||\mu_{\perp} ||^2_2} |} * \frac{(e_{\text{w2}} - \mu_{\perp}) - \mu_B} {|(e_{w2} - \mu_{\perp}) - \mu_B)|} \

$e_1 = e_{w1B} + \mu_{\perp} \
$e_2 = e_{w2B} + \mu_{\perp}
$$

序列模型

基礎模型

如何構建一個網絡來實現機器翻譯呢?比如實現輸出法語句子 "Jane visite I'Afrique en septembre.",輸出英語句子 "Jane is visiting Africa in September."。

首先,建立一個網絡,這個網絡叫編碼網絡 (encoder network),如下圖編號 1 所示。它是一個 RNN 的結構,RNN 的單元可以是 GRU 也可以是 LSTM。每次只向該網絡中輸入一個法語單詞,將輸入序列接收完畢後,這個 RNN 網絡會輸出一個向量來代表這個輸入序列。在這個網絡後面,我們建立一個解碼網絡 (decoder network),如下圖編號2所示。它以編碼網絡的輸出作爲輸入,被訓練爲每次輸出一個翻譯後的單詞,一直到它輸出序列的結尾或者句子結尾標記。

這個模型簡單地用一個編碼網絡來對輸入的法語句子進行編碼,然後用一個解碼網絡來生成對應的英語翻譯。

與此類似的結構也被用來做圖像描述,給出一張圖片,如下圖中的貓的圖片,它能自動地輸出該圖片的描述:一隻貓坐在椅子上。

我們之前已經知道如何將圖片輸入到卷積神經網絡中,比如一個預訓練的 AlexNet 結構(上圖編號 2)。然後讓其學習圖片的編碼或者學習圖片的一系列特徵,也就是去掉 softmax 單元(上圖編號 3)後的部分會輸出一個 4096 維的特徵向量,也就是一個圖像的編碼網絡。然後把這個向量輸入到 RNN 中(上圖編號 4),使用 RNN 來生成圖像的描述。

選擇最可能的句子

我們可以把機器翻譯看成是建立一個條件語言模型,在語言模型中上方是一個我們之前建立的模型,這個模型可以估計句子的可能性,也就是語言模型所做的事情。而機器翻譯分爲兩部分:編碼網絡(下圖綠色)和解碼網絡(下圖紫色),而我們發現解碼網絡其實和語言模型幾乎一模一樣。不同在於語言模型總是以零向量(下圖編號 4)開始,而機器翻譯的編碼網絡會計算出一系列向量(下圖編號 2)來表示輸入的句子,解碼網絡則以這個句子的特徵開始,而不是零向量。所以吳恩達老師稱之爲條件語言模型 (conditional language model)

我們想實現真正地通過模型將法語翻譯成英文,通過輸入的法語句子得到各種英文翻譯所對應的可能性。\(x\) 在這裏是法語句子 "Jane visite I'Afrique en septembre"。我們不想讓模型隨機地輸出,即從得到的分佈中進行隨機取樣,而是找到一個英語句子 \(y\),使得條件概率最大化。

解決這種問題,最通用的算法就是集束搜索 (Beam Search),而不用貪心搜索 (Greedy Search)

貪心搜索指的是一種來自計算機科學的算法。生成第一個詞的分佈以後,它將會根據條件語言模型挑選出最有可能的第一個詞進入機器翻譯模型中,然後繼續挑選最有可能的第二個詞,接着一直往後挑選最有可能的詞。

但是我們真正需要的是一次性挑選出整個單詞序列,從 \(y^{<1>},y^{<2>}\)\(y^{<T_y>}\) 來使得整體的概率最大化。所以貪心算法並不管用。

上圖中編號 1 的翻譯明顯比編號 2 的好,所以我們希望機器翻譯模型會輸出第一個句子的 \(P(y|x)\) 比第二個句子要高。但如果使用貪心算法來挑選出了 "Jane is" 作爲前兩個詞,因爲在英語中 going 更加常見,所以模型會選擇 "Jane is going" 而不是 "Jane is visiting" 作爲翻譯,最終得到一個欠佳的句子。

集束搜索算法首先做的就是挑選要輸出的英語翻譯中的第一個單詞,爲了簡化問題,我們忽略大小寫,列出了 10,000 個詞的詞彙表。集束搜索的第一步是用這個網絡(綠色是編碼網絡;紫色是解碼網絡),來評估第一個單詞的概率值。給定輸入序列 \(x\),即法語句子,輸出 \(y\) 的概率值是多少。

貪婪算法只會挑選最可能的一個單詞,然後繼續,而集束搜索則會考慮多個選擇。集束搜索算法會有一個參數 \(B\),稱爲集束寬 (beam width)。本例中我們設爲 3,意味着集束搜索一次會考慮 3 個詞,然後把結果存在計算機內存裏以便後面嘗試使用這三個詞。

假設我們選出了第一個單詞三個最有可能的選擇爲 in, jane, september,集束搜索的第二步會針對每個第一個單詞考慮第二個單詞是什麼,如下圖編號 1。爲了評估第二個詞的概率值,我們用神經網絡,綠色是編碼部分(下圖編號 2)。對於解碼部分,當決定單詞 in 後面是什麼時,解碼器的第一個輸出 \(y^{<1>}\) 爲 in (下圖編號 3),然後把它喂回下一個網絡單元(下圖編號 4)。這裏的目的是找出第一個單詞是 in 的情況下,第二個單詞是什麼,即 \(y^{<2>}\) (下圖編號 5)。

在第二步中,我們更關心的是要找到最可能的單詞對(下圖編號 7),而不僅僅是最大概率的第二個單詞。按照條件概率的準則,單詞對的概率可以表示爲第一個單詞的概率(下圖編號 8)乘以以第一個單詞爲條件的第二個單詞的概率(下圖編號 9),而後者可以從編號 10 的網絡中得到。

同理,對於第一個單詞的第二個備選 "jane" ,第三個備選 "september" 也是同樣的步驟。由於我們一直用的集束寬爲 3,並且詞彙表裏有 10,000 個單詞,那麼最終會有 \(3\times10,000\) 也就是 30,000 個可能的結果。然後依舊按照單詞對的概率選出前三個,減少到集束寬的大小。集束搜索算法會保存這些結果,然後用於下一次集束搜索。

接下來的步驟,繼續選擇與第二步類似。值得注意的是,如果集束寬等於 1,只考慮一種可能結果,這實際上就變成了貪婪搜索算法。

改進集束搜索

有一些小技巧可以幫助集束搜索算法運行的更好。

長度歸一化 (length normalization) 就是對集束搜索算法稍作調整的一種方式。集束搜索其實就是最大化
\[ \begin{equation} \mathop{\arg\max}_{y} \prod_{t=1}^{T_y}P(y^{<t>}|x,y^{<1>},\dots,y^{<t-1>}) \end{equation} \]
而連乘的乘積其實就是 \(P(y^{<1>},\dots,y^{<{T_y}>}|x)\)。如果計算它,其實相乘的這些概率值都是小於 1 的,通常遠小於 1。而很多小於 1 的數相乘,會得到很小很小的數字,會造成數值下溢 (numerical underflow)。指的是數值太小了,導致電腦的浮點表示不能精確地存儲。因此在實踐中,我們取 log 值,從而得到一個數值上更穩定的算法。即
\[ \begin{equation} \mathop{\arg\max}_{y} \sum_{t=1}^{T_y}\log P(y^{<t>}|x,y^{<1>},\dots,y^{<t-1>}) \end{equation} \]
對於目標函數,還可以做一些改變,可以使得機器翻譯表現得更好。如果使用上面的目標函數,那麼對於一個很長的句子,這個句子的概率會很低,因爲乘了很多項小於 1 的數字。所以這個目標函數有一個缺點是,它可能不自然地傾向於簡短的翻譯結果。我們可以不再最大化這個目標函數,而是對其進行歸一化,通過除以翻譯結果的單詞數 \(T_y\)。這樣就是取每個單詞的概率對數值的平均了,這樣很明顯地減少了對輸出長的結果的懲罰。即
\[ \begin{equation} \frac{1}{T_y^{\alpha}} \sum_{t=1}^{T_y}\log P(y^{<t>}|x,y^{<1>},\dots,y^{<t-1>})\end{equation} \]
上式中的參數 \(\alpha\),可以使得歸一化更加柔和,\(\alpha\) 可以等於 0.7。如果 \(\alpha\) 等於 1,就相當於完全用句子長度來歸一化,如果 \(\alpha\) 等於 0,就相當於完全沒有歸一化。它就是算法另一個超參數,需要調整大小來得到最好的結果。

對於如何選擇集束寬參數 \(B\)\(B\) 越大,考慮的選擇越多,找到的句子可能越好;但是算法的計算代價也會越大,算法會運行得慢一些,內存佔用也會增大。在實踐中,其實使用 \(B=3\) 有點偏小。在生產中,經常可以看到把集束寬設爲 10,集束寬爲 100 對於生產系統來說有點過大;但對於科研來說,人們想獲得最好的結果用來發表論文,所以經常可以看到集束寬爲 1,000 甚至 3,000。對很多應用來說,從集束寬爲 1,到 3,到 10,可能可以看到一個很大的提升;但是當集束寬從 1,000 增加到 3,000 時,效果可能就沒那麼明顯了。

集束搜索的誤差分析

以下面的例子來說明。

仍然需要翻譯法語句子 "Jane visite I'Afrique en septembre"。假設機器翻譯的 dev 集中,也就是開發集 (development set),人工是這樣翻譯的 "Jane visits Africa in September",記爲 \(y^*\)。當已經完成學習 RNN 模型,也就是已完成學習的翻譯模型中運行集束搜索算法時,它輸出的翻譯爲 "Jane visited Africa last September",記爲 \(\hat{y}\)

我們的模型有兩個主要部分:RNN 模型和集束搜索算法。現在,我們想要找出造成輸出 \(\hat{y}\) 這個不太好的翻譯的原因。

RNN 實際上是個編碼器和解碼器,它會計算 \(P(y|x)\)。我們可以使用這個模型來計算 \(P(y^*|x)\)\(P(\hat{y}|x)\),然後比較一下這兩個值哪個更大。

第一種情況: \(P(y^*|x)>P(\hat{y}|x)\)

這種情況下,意味着集束搜索選擇了 \(\hat{y}\),也就是集束搜索算法此時不能夠輸出一個使 \(P(y|x)\) 最大化的 \(y\) 值,因爲集束搜索算法的目的就是尋找一個 \(y\) 值來使它更大。

因此這種情況下,我們能夠得出是集束搜索算法出錯了。

第二種情況: \(P(y^*|x)\le P(\hat{y}|x)\)

這種情況下,意味着相比與 \(\hat{y}\)\(y^*\) 成爲輸出的可能性更小,但是後者其實上是比前者更好的翻譯結果。也就是說,這種情況下,是 RNN 模型出了問題。

所以誤差分析的過程其實就如下圖這樣。先遍歷開發集,然後在其中找出算法產生的錯誤。通過這個過程,我們就能夠執行誤差分析,得出集束搜索算法和 RNN 模型出錯的比例,來指導模型的優化。

Bleu 得分

Bleu 代表的是 bilingual evaluation understudy (雙語評估替補),這是一種常見的衡量機器翻譯的準確性的方法。

假如我們有一個法語句子 "Le chat est sur le tapis",然後其對應的一個人工翻譯參考爲 "The cat is on the mat"。不過有多種相當不錯的翻譯。所以其他的人,也許會翻譯爲 "There is a cat on the mat"。實際上,這兩個都是很好的翻譯。Bleu 得分做的就是,給定一個機器生成的翻譯,它能夠自動地計算一個分數來衡量機器翻譯的好壞。直覺告訴我們,只要這個機器生成的翻譯與任何一個人工翻譯的結果足夠接近,那麼它就會得到一個高的 Bleu 分數。

我們以一個極端的例子爲例。假設機器翻譯 (MT) 的輸出是 "the the the the the the the"。這顯然是一個十分糟糕的翻譯。衡量機器翻譯輸出質量的方法之一,是觀察輸出結果的每一個詞看其是否出現在參考中,這杯稱作是機器翻譯的精確度。這種情況下,機器翻譯輸出了七個單詞並且這七個詞中的每一個都出現在了參考 1 或是參考 2。單詞 the 在兩個參考中都出現了,所以看上去每個詞都是很合理的,即這個精確度就是 \(\frac{7}{7}\),看起來是一個極好的精確度。

所以這種方法並不是很有用,將其進行改良,我們把每一個單詞的計分上限定爲它在參考句子中出現的最多次數。在參考 1 中,單詞 the 出現了兩次;參考 2 中,單詞 the 出現了一次。所以單詞 the 的得分上限爲 2。那麼這個改良後的精確度爲 \(\frac{2}{7}\)。分母爲 7 個詞中單詞 the 總共出現的次數,分子爲單詞 the 在參考中的出現的計數。

到目前爲止,我們都只是關注單獨的單詞。如果我們想考慮成對的單詞,定義一下二元詞組 (bigrams) 的 Bleu 得分。當然這僅僅只是最終的 Bleu 得分的一部分,可能會考慮單個單詞以及二元或多元詞組。在下面的例子中,我們分別統計 MT 輸出的二元詞組在 MT 輸出和參考中的計數。因此 \(\frac{4}{6}=\frac{2}{3}\) 爲二元詞組的改良後的精確度。

現在我們將其泛化爲 n 元詞組,其精確度定義爲
\[ P_n=\frac{\sum_{n-grams\in\hat{y}}Count_{clip}(n-gram)}{\sum_{n-grams\in\hat{y}}Count(n-gram)} \]
最終的 Bleu 得分被定義爲(以綜合 \(P_1,P_2,P_3,P_4\) 爲例)
\[ Combined Bleu score = exp(\frac{1}{4}\sum_{n=1}^4P_n) \]
實際上還會用到額外的一個叫做 \(BP\) 的懲罰因子來調整,其意思爲簡短懲罰 (brevity penalty)。那麼定義則爲
\[ Combined Bleu score = BP\cdot exp(\frac{1}{4}\sum_{n=1}^4P_n)\\ BP=\begin{cases} 1 & if\ MT\_output\_length > reference\_output\_length\\ exp(1-\frac{reference\_output\_length}{MT\_output\_length}) & otherwise \end{cases} \]

注意力模型 (Attention Model)

像下圖這樣一個很長的法語句子,我們的神經網絡中,綠色部分的編碼器要做的就是讀整個句子,然後記憶整個句子,再在感知機中傳遞。而對於紫色部分的解碼器,它將生成英文翻譯。

但是人工翻譯並不會通過讀整個法語句子,再記憶裏面的東西,然後從零開始翻譯成英語句子。人工翻譯會一部分一部分地翻譯,因爲記憶整個句子是非常困難的。對於機器翻譯來說也是如此,對於短句子效果可能非常好,有相對高的 Bleu 分數,但是對於長句子,它的表現就會變差。

注意力模型源於機器翻譯,但也推廣到了其他應用領域。

仍然以法語句子 "Jane visite I'Afrique en Septerbre" 爲例。假定我們使用一個雙向的 RNN,爲了計算每個輸入單詞的特徵集。它可以使用 GRU 或者 LSTM 作爲基本單元,實踐中, LSTM 使用得更爲經常一些。然後,使用另一個 RNN 生成對應的英文翻譯,我們使用記號 \(S\) 表示這個 RNN 的隱藏狀態而不用 \(A\)

當我們嘗試生成英文翻譯的第一個詞時,我們應該看對應法語句子的第一個單詞及它附近的詞。所以注意力模型就會計算注意力權重,我們使用 \(\alpha^{<1,1>}\) 來表示當生成第一個詞時,注意力放在第一塊信息處的權重。對應的有 \(\alpha^{<1,2>},\alpha^{<1,3>}\)。把他們綜合起來作爲翻譯第一個詞的上下文語境,記爲 \(C\),這就是這個 RNN 的一個單元。其他單詞以此類推,直到最終生成 <EOS>。

再次說明,注意力權重 \(\alpha^{<t,t>}\) 表示的是,生成第 t 個英文詞時,需要花多少注意力在第 t 個法語詞上面。

我們仍然使用 \(t\) 來表示時間步,\(a^{<t>}\) 就是時間步 \(t\) 上的特徵向量。使用 \(t'\) 來索引法語句子裏面的詞。那麼 \(t=1\) 時的上下文語境,就是通過計算注意力權重(上圖編號 1)和其對應的特徵向量(上圖編號 2)的乘積和。即
\[ C^{<1>}=\sum_{t'}\alpha^{<1,t'>}a^{<t'>} \]
注意,在一個時間步中,所有的注意力權重均爲非負,且它們的和爲 1,即
\[ \sum_{t'}\alpha^{<1,t'>}=1 \]

\(\alpha^{<t,t'>}\) 是花費在 \(a^{<t'>}\) 上的注意力權重。它的公式如上圖所示。計算它之前,我們需要先計算 \(e^{<t,t'>}\),關鍵要用 softmax 以確保這些權重加起來等於 1。

計算 \(e\) 值可以訓練一個上圖所示的小型的神經網絡。我們不知道具體的函數去計算它,但是可以使用梯度下降算法計算一個正確的函數。

這個算法的一個缺點就是它要花費三次方的時間,也就是說這個算法的複雜度是 \(O(n^3)\)。但是在機器翻譯的應用上,輸入和輸出的句子一般不會太長,可能三次方的消耗也是可以接受的。

語音識別 (Speech recognition)

語音識別問題指的是,輸出音頻片段 \(x\) 自動地生成文本 \(y\)

我們使用注意力模型來構建語音識別系統。就是在橫軸上,也就是輸入音頻的不同時間幀上,用注意力模型來輸出文本描述。

也可以使用 CTC 損失函數來做語言識別,其中 CTC 指的是 Connectionist Temporal Classification。

其算法思想如下:

假設語言片段內容爲 "the quick brown fox",這時我們使用一個新的網絡,結構如上圖所示。輸入的 \(x\) 與輸出 \(y\) 的長度是一樣的,示例的只是一個簡單的單向 RNN 結構。在實踐中,它可以是雙向的 LSTM 或 GRU,並且通常是很深的模型。注意,這裏時間步的數量非常大。在語音識別中,通常輸入的時間步數量要比輸出的時間步數量多出很多。這種情況下,CTC 損失函數允許 RNN 生成類似這樣的輸出 "ttt",然後一個空白符,我們以下劃線表示,然後 "h_eee___" 等。這樣的輸出(如上圖所示)對應的就是 "the q"。這樣,需要輸出的內容其實只有 19 個字符,但是神經網絡允許有很多這種重複的字符和很多插入在其中的空白符,使得它能強制輸出 1000 個字符。

觸發詞檢測 (Trigger Word Detection)

現在有很多智能系統有其對應的觸發詞模塊,如下圖所示。

對於觸發詞檢測,最好的算法是什麼,目前還沒有一個廣泛的定論。

我們以一個算法爲例。現在有一個 RNN 結構,我們需要把一個音頻片段計算出它的聲譜圖特徵 (spectrogram features) 得到特徵向量 \(x^{<1>},x^{<2>},\dots\)。然後,把它放到另一個 RNN 中,再定義目標標籤 \(y\)。假如音頻片段中的某一點爲剛剛說完一個觸發詞,那麼之前的目標標籤都設爲 0,這點之後對應觸發詞的音頻特徵設爲 1。這樣的標籤方案對於 RNN 來說是可行的,並且確實運行得不錯。不過該算法一個明顯的缺點就是它構建了一個很不平衡的訓練集,0 的數量比 1 多太多了。

這裏有一個解決方法,雖然聽起來有點簡單粗暴,但確實能使其變得更容易訓練。比起只在一個時間步上去輸出 1,其實你可以在輸出變回 0 之前,多次輸出 1,或說在固定的一段時間內輸出多個 1。這樣的話,就稍微提高了 1 與 0 的比例。

References

[1] Coursera深度學習教程中文筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章