[NLP] RNN 前向傳播、延時間反向傳播 BPTT 、延時間截斷反向傳播 TBTT


原創作品,轉載請注明出處哦~

RNN: Feed Forward, Back Propagation Through Time and Truncated Backpropagation Through Time

了解RNN的前向、后向傳播算法的推導原理是非常重要的,這樣,

1. 才會選擇正確的激活函數;

2. 才會選擇合適的前向傳播的timesteps數和后向傳播的timesteps數;

3. 才會真正理解為什么會梯度消失和爆炸;

4. 才會從根源上想怎樣盡量去避免梯度消失和梯度爆炸;

5. 才會知道為什么Attention的提出的意義;

6. 才會知道Google Transformer這個模型設計時候,是怎么想到要這樣做的……

原來這些都是聯系在一起的,都是由於傳播的原理所決定的。

現在把看到的資料和自己的想法總結一下,分享給大家,歡迎批評指正。

參考資料:Ilya Sutskever, Training Recurrent Neural Networks, Thesis, 2013

1. RNN的前向傳播

<1> 前向傳播過程與損失函數

給定一個輸入序列$(v_1, ..., v_T)$ (我們用$v_1^T$表示), RNN通過以下算法計算隱層狀態 $h_1^T$ 和 序列的輸出 $z_1^T$:

1: for $t$ from $1$ to $T$ do

2:   $u_t \leftarrow W_{hv}v_t + W_{hh}h_{t - 1} + b_h$

3:   $h_t \leftarrow e(u_t)$

4:   $o_t \leftarrow W_{oh}h_t + b_o$

5:   $z_t \leftarrow g(o_t)$

6: end for

其中,$e(\cdot)$ 和 $g(\cdot)$ 分別是隱層和輸出層的非線性激活函數。$h_0$是存儲第一個隱層狀態的向量表示。那么RNN的損失函數可以表示為各個時間步(timestep)的損失之和:

$$L(z , y) = \sum_{t = 1}^TL(z_t; y_t) \tag1$$

<2> 激活函數的選擇

這部分內容是參考《百面機器學習》這本書的介紹。公式推導還是采用<1>中的符號表示。

Question: 在循環神經網絡中能否使用ReLU作為損失函數?

Answer: 可以的。但是需要對矩陣的初始值做一定的限制,否則十分容易引發數值問題。原因如下:

(1) 首先是前向傳播中的第 $T$ 個單元的數值可能趨於0或無窮的問題

對於RNN的前向傳播過程,有

$$u_t \leftarrow W_{hv}v_t + W_{hh}h_{t - 1} + b_h \tag2$$

$$h_t \leftarrow e(u_t)\tag3$$

那么將 $h_{t-1}$ 的(1)形式的表示帶入(2)中,得到:

$$u_t \leftarrow W_{hv}\,v_t + W_{hh}\,e(W_{hv}v_{t-1} + W_{hh}\,h_{t - 2} + b_h ) + b_h \tag4$$

若采用ReLU代替公式中的激活函數 $e(\cdot)$ ,並且假設ReLU函數一直處於激活狀態(e(x) = x), 則有

$$u_t \leftarrow W_{hv}\,v_t + W_{hh}(W_{hv}v_{t-1} + W_{hh}\,h_{t - 2} + b_h ) + b_h \tag5$$

繼續將其展開,會得到 $T$ 個 $W$連乘。如果 $W$ 不是單位矩陣,最終結果將會趨於0或無窮,引發嚴重的數值問題。

(2) 在反向傳播中同樣非常容易出現梯度消失或爆炸的問題

$$\frac{ \partial{u_t}}{\partial{u_{t-1}}} = W_{hh}\cdot diag[e'(u_{t - 1})] \tag6$$

(推導過程這里先不介紹,<2>中會有更詳細的推導)

若采用ReLU代替公式中的激活函數 $e(\cdot)$ ,並且假設ReLU函數一直處於激活狀態(e(x) = x), 則 $diag[e'(u_{t - 1})]$為單位矩陣,有$\frac{ \partial{u_t}}{\partial{u_{t-1}}} = W_{hh}$。在經歷了$t$層梯度傳遞后,$\frac{ \partial{u_t}}{\partial{u_{1}}} = (W_{hh})^t$。那么,即使采用了ReLU函數,只要 $W$ 不是單位矩陣,梯度還是會出現消失或者爆炸的情況。

(3) 為什么CNN中不會出現這樣的問題?

Answer: 因為CNN中每一層的卷積權重不同,並且初始化時它們是獨立同分布的,因此可以相互抵消,多層之后一般不會出現嚴重的數值問題。而RNN中則是公用的權值矩陣W,因此~

(4) 如果用ReLU怎樣盡量避免這樣的數值問題呢?

當采用ReLU作為循環神經網絡中隱層的激活函數時,只有當$W$的取值在單位矩陣附近時才能有較好效果,因此需要將 $W$ 初始化為單位矩陣。實驗證明,初始化$W$為單位矩陣並使用ReLU為激活函數,在一些應用中與LSTM模型效果相當,並且學習速度比LSTM更快,是一個值得嘗試的小技巧。

2. RNN: Back Propagation Through Time

找了一些資料,Ilya Sutskever, Training Recurrent Neural Networks, Thesis, 2013中給出的算法如下所示,但是個人以為,其在計算$W_hh$時有問題。

1: for $t$ from $T$ to $1$ do

2:   $\mathrm{d}{o_t} \leftarrow {g'(o_t)} \cdot {\frac{\mathrm{d}{L(z_t;\, y_t)}}{\mathrm{d}{z_t}}}$

3:   $\mathrm{d}b_o  \leftarrow \mathrm{d}b_o + \mathrm{d}o_t$

4:   $\mathrm{d}W_{oh} \leftarrow \mathrm{d}W_{oh} + \mathrm{d}o_t h_t^{\mathrm{T}}$

5:   $\mathrm{d}h_t \leftarrow \mathrm{d}h_t + W_{oh}^{\mathrm{T}}  \mathrm{d}o_t$

6:   $\mathrm{d}u_t \leftarrow {e'(u_t)} \cdot \mathrm{d}h_t$

7:   $\mathrm{d}W_{hv} \leftarrow \mathrm{d}W_{hv} + \mathrm{d}u_tv_t^\mathrm{T}$

8:   $\mathrm{d}b_h \leftarrow \mathrm{d}b_h + \mathrm{d}u_t$

9:   $\mathrm{d}W_{hh} \leftarrow \mathrm{d}W_{hh} + \mathrm{d}u_th_{t - 1}^\mathrm{T}$

10:    $\mathrm{d}h_{t - 1} \leftarrow W_{hh}^\mathrm{T}\mathrm{d}u_t$

11: end for

12: Return $\mathrm{d} \theta = [\mathrm{d}W_{hv}, \mathrm{d}W_{hh}, \mathrm{d}W_{oh}, \mathrm{d}b_h, \mathrm{d}b_o, \mathrm{d}h_{\color{Red}0}]$

其中,表紅色的是數字'0'而不是字母'o'。

這里3,4,5,7,8,9行的變量梯度是沿時間累加的。

在傳播過程中,並沒有更新變量值,而是每一時刻都存儲着當前時刻的梯度,從T時刻到1時刻,反向傳播完成后,return來對變量值進行更新。

-----------正確求解$\frac{\partial L}{\partial W_{hh}}$的方法如下:--------------

在計算梯度時,有一點要非常注意:

$$u_t \leftarrow W_{hv}v_t + W_{hh}h_{t - 1} + b_h \tag2$$

中,對$W_{hh}$求偏導時,要注意(2)式中,既要對$W_{hh}$求偏導,$h_{t - 1}$也是關於$W_{hh}$的函數,所以$h_{t - 1}$也要對$W_{hh}$求偏導!

也就是說,當計算$\frac{\partial L_t}{\partial W_{hh}}$時,取決於$h_{t - 1}$,$h_{t - 2}$, ... ,$h_1$。

舉個簡單的例子,把無關變量W_{hv}所在一項看做常數a,偏置設為0:

$$S_k = a + WS_{k-1} \tag7$$

$$\frac{\partial{S_k}}{\partial{W}} = \frac{\partial{S_k}}{\partial{W}} + \frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot  \frac{\partial{S_{k-1}}}{\partial{W}} \tag8$$

這樣寫不嚴謹,但方便說明: $\frac{\partial{S_k}}{\partial{W}}$是將(7)中的 $S_{k -1}$ 看做常數,對 $W$ 求偏導得到;$\frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot  \frac{\partial{S_{k-1}}}{\partial{W}}$ 是將(7) 中的$S_{k-1}$對$W$求偏導得到。那么,

$$\frac{\partial{S_k}}{\partial{W}} = \frac{\partial{S_k}}{\partial{W}} + \frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot \frac{\partial{S_{k-1}}}{\partial{W}}$$

$$ = \frac{\partial{S_k}}{\partial{W}} + \frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot [ \frac{\partial{S_{k-1}}}{\partial{W}} + \frac{\partial{S_{k-1}}}{\partial{S_{k-2}}} \cdot \frac{\partial{S_{k-2}}}{\partial{W}}]$$

$$ =  \frac{\partial{S_k}}{\partial{W}} +  \frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot  \frac{\partial{S_{k-1}}}{\partial{W}}    + \frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot  \frac{\partial{S_{k-1}}}{\partial{S_{k-2}}} \cdot  \frac{\partial{S_{k-2}}}{\partial{W}}   +  \frac{\partial{S_k}}{\partial{S_{k-1}}} \cdot  \frac{\partial{S_{k-1}}}{\partial{S_{k-2}}} \cdot \cdot \cdot  \frac{\partial{S_1}}{\partial{W}} \tag9$$

舉個例子:

$$\frac{\partial L_3}{\partial W_{hh}} =\frac{\partial L_3}{\partial h_3} \cdot \frac{\partial h_3}{\partial  u_3}\cdot \frac{\partial u_3}{\partial  W_{hh}}$$

$$+ \frac{\partial L_3}{\partial h_3} \cdot \frac{\partial h_3}{\partial  u_3}\cdot \frac{\partial u_3}{\partial  h_2} \cdot  \frac{\partial  h_2}{\partial u_2} \cdot \frac{\partial u_2}{\partial  W_{hh}}$$

$$+  \frac{\partial L_3}{\partial h_3} \cdot \frac{\partial h_3}{\partial  u_3}\cdot \frac{\partial u_3}{\partial  h_2} \cdot  \frac{\partial  h_2}{\partial u_2} \cdot \frac{\partial u_2}{\partial  h_1} \cdot \frac{\partial  h_1}{\partial u_1} \cdot  \frac{\partial u_1}{\partial  W_{hh}} \tag{10}$$

$$\frac{\partial L_2}{\partial W_{hh}} =\frac{\partial L_2}{\partial h_2} \cdot \frac{\partial h_2}{\partial  u_2}\cdot \frac{\partial u_2}{\partial  W_{hh}}$$

$$+ \frac{\partial L_2}{\partial h_2} \cdot \frac{\partial h_2}{\partial  u_2}\cdot \frac{\partial u_2}{\partial  h_1} \cdot  \frac{\partial  h_1}{\partial u_1} \cdot \frac{\partial u_1}{\partial  W_{hh}}\tag{11}$$

$$\frac{\partial L_1}{\partial W_{hh}} =\frac{\partial L_1}{\partial h_1} \cdot \frac{\partial h_1}{\partial  u_1}\cdot \frac{\partial u_1}{\partial  W_{hh}} \tag{12}$$

那么,反向傳播T時間后,對$W_{hh}$ 權值進行更新。

$$\frac{\partial L}{\partial W_{hh}} =  \frac{\partial L_1}{\partial W_{hh}} + \frac{\partial L_2}{\partial W_{hh}} + \frac{\partial L_3}{\partial W_{hh}} \tag{13}$$

其梯度為(10)(11)(12)所求值之和。

那么,$$W_{hh} \leftarrow W_{hh} + \gamma \Delta W_{hh}\tag{14}$$

這樣,RNN梯度爆炸和衰減的原因也明了了。RNN的傳播機制類似於“蝴蝶效應”,在不斷的權值相乘中,一點點小的變動都會在t時間的傳播中被指數級放大。那么在輸入句子長度較大時,學習長程依賴關系會變得很困難。

為了更好地學得句子長程依賴關系,有一些方法,比如我們熟知的LSTM,GRU等,本文不做贅述。本文要介紹的是Truncated Backpropagation Through Time,通過調節RNN正向、反向傳播的時間步長度,在一定程度上緩解RNN傳播中的數值問題。

3. RNN: Truncated Back Propagation Through Time

<1> TBPTT 算法簡介

[Williams, Ronald J., and Jing Peng. "An efficient gradient-based algorithm for on-line training of recurrent network trajectories."]

TBPTT (Truncated Back Propagation Through Time) 可能是訓練RNN中最實用的方法。

BPTT有一個主要的問題:對單個參數的更新的cost很高,這樣RNN就很難適應大數量的迭代。舉個例子,對長度為1000的輸入序列進行反向傳播,其代價相當於1000層的神經網絡進行前向后向傳播。

Naive的改進方法: 如果可以把這個長度為1000的句子切分成50個長度為20的句子,然后將每個長度為20的句子單獨訓練,那么計算量就會大大降低。

但是,該方法只能學得這每個切分部分內部的依賴關系,而無法看到20個時間步之外的更多時序依賴關系。

TBPTT:類似與Naive的方法,但有一點改進。

TBPTT中,每次處理一個時間步,每前向傳播 $k_1$ 步,后向傳播 $k_2$ 步。如果 $k_2$ 比較小,那么其計算代價將會降低。這樣,它的每一個隱層狀態可能經過多次時間步迭代計算產生的,也包含了更多更長的過去信息。在一定程度上,避免了naive方法中無法獲取截斷時間步之外信息的問題。

TNPTT算法:

1: for $t$ from 1 to $T$ do 

2:   Run the RNN for one step, computing $h_t$ and $z_t$

3:   if $t$ divides $k_1$ then

4:     Run BPTT(as described in 2), from $t$ down to $t - k_2$

5:   end if

6: end for

那么k1, k2應該選多大呢?

<2> $k_1$, $k_2$ 大小選擇

參考鏈接:https://machinelearningmastery.com/gentle-introduction-backpropagation-time/

首先需要想,$k_1$, $k_2$ 是做什么的呢?

$k_1$: 每經過k1時間步的前向傳播,對參數進行一次更新。那么由於k1控制着參數更新的頻率,其也影響着訓練的速度快慢。

$k_2$: 需要進行BPTT的時間步數。一般來說,它需要大一些,來獲取更多的時序信息。但是過大又會引起梯度數值問題。

符號$n$表示序列總時間步的長度。

(1) TBPTT(n, n): 傳統的BPTT

(2) TBPTT(1, n): 每向前處理一個時間步,便后向傳播所有已看到的時間步。(Williams and Peng提出的經典的TBPTT)

(3) TBPTT($k_1$,1): 網絡並沒有足夠的時序上下文來學習,嚴重的依賴內部狀態和輸入。

(4) TBPTT($k_1$,$k_2$), where$k_1$ < $k_2$ < n:  對於每個序列,都進行了多次更新,可以加速訓練。

(5) TBPTT(k1,k2), where k1=k2: 同Naive方法。

TensorFlow中默認采用的是(5)這個方式。

In order to make the learning process tractable, it is common practice to create an "unrolled" version of the network, which contains a fixed number (num_steps) of LSTM inputs and outputs. The model is then trained on this finite approximation of the RNN. This can be implemented by feeding inputs of length num_steps at a time and performing a backward pass after each such input block.

TensorFlow 中采用的 TBPTT(k1, k2),其中(k1 = k2 = num_steps) 實現方式的圖示:

圖1. TensorFlow TBPTT方式圖示

上圖來源於 https://r2rt.com/styles-of-truncated-backpropagation.html

在這篇blog中,通過代碼實現對比了這位作者想驗證的TBPTT(1, k2) 和 TensorFlow中這種的優劣。

圖2. TBPTT(1, k2) 反向傳播的圖示

具體內容大家可以參考上面網頁鏈接詳細閱讀。

這位博主實驗得出的結論:

1. 對於相同的時間步:TBPTT(1, k2) 優於 TBPTT(k1, k2)

2. 對於相同的序列長:TBPTT(1, k2) 喪失了優勢。

同時給出兩點建議:
1. TBPTT(1, k2) 和 TBPTT(1, n) (其中n表示序列總長) 的時間代價相差不大,並且TBPTT(1, n)效果會更好一點,因此並不是很有必要采用TBPTT(1, k2);

2. 由實驗得知,在相同時間步時,TensorFlow的TBPTT(k1, k2)效果並不如TBPTT(1, k2),這表示TBPTT(1, k2) 可能不能學得更加全面的序列的信息(同上文分析的naive方式的不足),因此,可以考慮采用TBPTT(k1, k2)(其中k1 < k2 < n)這種方式。

============================================================

4. 番外篇

這樣RNN的正向和反向傳播就整理完畢了。最后說一些自己的小發現,就是對Transformer模型設計的理解。

雖然Attention Is All You Need這篇論文拿在手里看過很久了,The Illustrated Transformer這篇blog對其算法實現做了很詳細生動的講解,The Annotated Transformer這篇用pytorch實現模型,同樣給出了非常詳盡的介紹。但自己之前只是知道它是如何實現的,知道它效果不錯,但是卻沒想過,這個模型的設計者當初是怎么想到用這個方法來做的。現在想了想,可能並不對,但也算是把這些內容串起來了。

1. 首先看Seq2Seq模型吧,從encoder對輸入序列進行編碼,所有輸入內容最終都被編碼進encoder的最后一個單元,自然會面臨 前文所述的數值問題的風險,也可能因為維度原因無法表征整個輸入序列的完整信息,也可能因為輸入序列很長,到最后一個單元不能很好的存儲長程依賴關系等等。

2. 之后在decoder中加入了attention機制,每翻譯一個token, 都回看encoder中的各個輸入$x_t$的隱層表示$h_t$,計算相似度,求得context的表征,作為輔助信息 輸入到decoder的單元中,做預測。

這樣的確每次翻譯一個token時,可以將重點放在encoder輸入的與預測詞相關的詞的$h_t$上,但是,只要用到了RNN,其在前向、后向傳播中,還是用的這一套算法理論,還是有$W$權值的累乘,那么,還是會有這樣的數值問題的可能。

那么,有沒有什么辦法,可以不用引入這樣的$W$的累乘呢?

我們可不可以直接將decoder的詞與encoder的輸入求相似度呢?而不是與encoder的前向后向傳播之后(引入了$W$的累乘之后)的$h_t$求相似度呢?

這樣self-attention就出現了。

3. 可以把self-attention中兩兩單詞之間經過attetion后獲得的sum的表征,類比與RNN的前向后向傳播后獲取的$h_t$,它們都表示該詞與整個序列上下文的關系。

那么,transformer中,encoder的self-attention可以類比RNN前向后向傳播獲取隱層表征$h_t$,以此獲取每個輸入詞與輸入序列上下文的關系;

每預測一個詞的時候,decoder就用其作為query來查encoder中的key, value,做出預測;

每預測出一個詞,就是一次訓練,更新參數;之后拿着已經預測出的1~t個詞,作為decoder的輸入,再預測第t + 1的詞。transformer中decoder的self-attention同樣相當於Seq2Seq2中decoder獲取$h'_t$.

之后,transformer同樣需要用decoder的query來查encoder中的key-value,完成最終預測。

思想基本就是,用self-attention獲取的加權value的sum表征,代替隱層狀態$h_t$,表示每個詞與上下文的關系。

但是,self-attention只用到了兩兩詞之間向量相似度的運算,而這些向量中並沒有詞的相對位置的信息,因此,transformer的最大問題就是,目前還沒有完美的獲取position的方法。雖然有論文中提出的絕對位置編碼,后面google又提出相對位置編碼,但只要在做self-attention這個運算中,沒有用到位置信息,這個問題就還不能徹底解決。

(番外篇這些話,是自己的想法,可能並不嚴謹。)

=======================================

     感謝您的支持! [支付寶] 您的鼓勵是我的光~ O(∩_∩)O

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM