原創作品,轉載請注明出處哦~
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 算法簡介
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 lengthnum_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