標籤:

循環神經網路(RNN)介紹3:RNN的反向傳播演算法Backpropagation Through Time (BPTT)

專欄的上兩篇文章簡要介紹了RNN以及如何使用keras部署,但是有一部分內容我們並沒有說的很清楚,就是BPTT演算法。在這篇文章中,我們來專門談一談什麼是BPTT。

讓我們快速回顧一下RNN的基本公式。x_t是當前時間步的輸入,s_t是當前的狀態,hat y_t是輸出。

s_t = tanh(Ux_t + Ws_{t-1})

hat y_t = softmax(Vs_t)

我們使用交叉熵作為代價函數:

begin{aligned} E_t(y_t, hat{y}_t) &= - y_{t} log hat{y}_{t} E(y, hat{y}) &=sumlimits_{t} E_t(y_t,hat{y}_t)  & = -sumlimits_{t} y_{t} log hat{y}_{t} end{aligned}

這裡,y_t是真實值,hat y_t是我們的預測值。我們通常將完整的序列(句子)作為一個訓練樣本,因此總的代價是每次步驟(單詞)中的代價的總和。

我們的目標是計算代價關於權重U、V和W關的梯度,然後使用隨機梯度下降法(SGD)學習權重。就像我們把各步的代價相加一樣,我們也在一個訓練樣本中將每一步的梯度相加:frac{partial E}{partial W} = sumlimits_{t} frac{partial E_t}{partial W}.

為了計算這些梯度,我們使用微分的鏈式法則。這就是從代價開始的反向傳播演算法。對於本文的其餘部分,我們將使用E_3作為示例。

begin{aligned} frac{partial E_3}{partial V} &=frac{partial E_3}{partial hat{y}_3}frac{partialhat{y}_3}{partial V} &=frac{partial E_3}{partial hat{y}_3}frac{partialhat{y}_3}{partial z_3}frac{partial z_3}{partial V} &=(hat{y}_3 - y_3) otimes s_3  end{aligned}

在上面的公式中,z_3 =Vs_3otimes是兩個向量的外積。如果你不知道最後的結果是如何得到的,不要緊,因為這裡省略了計算步驟,你可以自己嘗試計算這些導數(很好的練習!)。我們需要記住的是,frac{partial E_3}{partial V}只與當前時間步的值hat{y}_3, y_3, s_3有關。所以,如果有了當前時間步的這些值,可以利用上面的公式方便的計算代價關於權重V的梯度。

但是計算frac{partial E_3}{partial W}(或者對於U)就不同了。至於原因,請看下面的關於求代價關於W的梯度的鏈式法則展開公式:

begin{aligned} frac{partial E_3}{partial W} &= frac{partial E_3}{partial hat{y}_3}frac{partialhat{y}_3}{partial s_3}frac{partial s_3}{partial W} end{aligned}

注意,s_3 = tanh(Ux_t + Ws_2)s_2有關, s_2又與Ws_1有關, 如此反覆. 所以如果我們想求W的導數,我們不能僅把s_2當做常數。我們需要繼續應用鏈式法則,所以我們就得到:

begin{aligned} frac{partial E_3}{partial W} &= sumlimits_{k=0}^{3} frac{partial E_3}{partial hat{y}_3}frac{partialhat{y}_3}{partial s_3}frac{partial s_3}{partial s_k}frac{partial s_k}{partial W} end{aligned}

我們需要考慮每一時間步的梯度。換句話說,因為W在每一步都被使用直到最後的輸出,所以我們需要從t=3開始後向傳播梯度直到t=0:

請注意,這與我們在深層前饋神經網路中使用的標準反向傳播演算法是完全相同的。關鍵的區別是,我們在每一步中對W的梯度進行求和。在傳統的神經網路中,我們不需要跨層共享權重,所以我們不需要求和。但在我看來,BPTT只是在展開的RNN上進行的標準反向傳播的一個花哨的名字而已。就像反向傳播一樣,你可以定義一個向量delta來表示,例如:delta_2^{(3)} = frac{partial E_3}{partial z_2} =frac{partial E_3}{partial s_3}frac{partial s_3}{partial s_2}frac{partial s_2}{partial z_2} 其中 z_2 = Ux_2+ Ws_1. 這樣BPTT和標準的反向傳播就統一起來了.

一個簡單的BPTT的部署代碼如下:

def bptt(self, x, y):nn T = len(y)nn # Perform forward propagationnn o, s = self.forward_propagation(x)nn # We accumulate the gradients in these variablesnn dLdU = np.zeros(self.U.shape)nn dLdV = np.zeros(self.V.shape)nn dLdW = np.zeros(self.W.shape)nn delta_o = onn delta_o[np.arange(len(y)), y] -= 1.nn # For each output backwards...nn for t in np.arange(T)[::-1]:nn dLdV += np.outer(delta_o[t], s[t].T)nn # Initial delta calculation: dL/dznn delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))nn # Backpropagation through time (for at most self.bptt_truncate steps)nn for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:nn # print "Backpropagation step t=%d bptt step=%d " % (t, bptt_step)nn # Add to gradients at each previous stepnn dLdW += np.outer(delta_t, s[bptt_step-1]) nn dLdU[:,x[bptt_step]] += delta_tnn # Update delta for next step dL/dz at t-1nn delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)nn return [dLdU, dLdV, dLdW]n

這也給了我們關於為什麼標準的RNN很難訓練的啟發:序列(句子)可能很長,可能是20個單詞或更多,因此需要在許多層中進行反向傳播。在實踐中,許多人限定反向傳播為有限的步驟(也是因為有梯度消失的問題存在)。

推薦閱讀:

簡單的Char RNN生成文本
用循環神經網路進行文件無損壓縮:斯坦福大學提出DeepZip
開源代碼「All in One」:6 份最新「Paper + Code」等你復現 | PaperDaily #12
循環神經網路RNN介紹1:什麼是RNN、為什麼需要RNN、前後向傳播詳解、Keras實現

TAG:RNN |