網上有很多Simple RNN的BPTT(Backpropagation through time,隨時間反向傳播)算法推導。下面用自己的記號整理一下。
我之前有個習慣是用下標表示樣本序號,這里不能再這樣表示了,因為下標需要用做表示時刻。
典型的Simple RNN結構如下:

圖片來源:[3]
約定一下記號:
輸入序列 $\textbf x_{(1:T)} =(\textbf x_1,\textbf x_2,...,\textbf x_T)$ ;
標記序列 $\textbf y_{(1:T)} =(\textbf y_1,\textbf y_2,...,\textbf y_T)$ ;
輸出序列 $\hat{\textbf y}_{(1:T)} =(\hat{\textbf y}_1,\hat{\textbf y}_2,...,\hat{\textbf y}_T)$ ;
隱層輸出 $\textbf h_t\in\mathbb R^H$ ;
隱層輸入 $\textbf s_t\in\mathbb R^H$ ;
過softmax之前輸出層的輸出 $\textbf z_t$ 。
(一)Simple RNN的BPTT
那么對於Simple RNN來說,前向傳播過程如下(省略了偏置):
$$\textbf s_t=U\textbf h_{t-1}+W\textbf x_t$$
$$\textbf h_t=f (\textbf s_t)$$
$$\textbf z_t=V\textbf h_t$$
$$\hat{\textbf y}_t=\text{softmax}(\textbf z_t)$$
其中 $f$ 是激活函數。注意,三個權重矩陣在時間維度上是共享的。這可以理解為:每個時刻都在執行相同的任務,所以是共享的。
既然每個時刻都有輸出 $\hat{\textbf y}_t$ ,那么相應地,每個時刻都會有損失。記 $t$ 時刻的損失為 $\mathcal L_t$ ,那么對於樣本 $\textbf x_{(1:T)}$ 來說,損失 $\mathcal L$ 為
$$\mathcal L=\sum_{t=1}^T\mathcal L_t$$
使用交叉熵損失函數,那么
$$\mathcal L_t=-\textbf y_t^{\top}\log\hat{\textbf y}_t$$
一、 $\mathcal L$ 對 $V$ 的梯度
下面首先求取 $\mathcal L$ 對 $V$ 的梯度。根據chain rule:$\dfrac{\partial \textbf z}{\partial \textbf x}=\dfrac{\partial \textbf y}{\partial \textbf x}\dfrac{\partial \textbf z}{\partial \textbf y}$ 、$\dfrac{\partial z}{\partial X_{ij}}=(\dfrac{\partial z}{\partial\textbf y})^{\top}\dfrac{\partial\textbf y}{\partial X_{ij}}$ ,有
$$\frac{\partial \mathcal L_t}{\partial V_{ij}}=(\frac{\partial \mathcal L_t}{\partial\textbf z_t})^{\top}\frac{\partial\textbf z_t}{\partial V_{ij}}$$
這里其實和BP是一樣的,前一項相當於是誤差項 $\delta$ ,后一項等於
$$\frac{\partial \textbf z_t}{\partial V_{ij}}=\frac{\partial V\textbf h_t}{\partial V_{ij}}=(0,...,[\textbf h_t]_j,...,0)^{\top}$$
只有第 $i$ 行非零,$[\textbf h_t]_j$ 是指 $\textbf h_t$ 的第 $j$ 個元素。參考上一篇博客的結尾部分,可知前一項等於
$$\frac{\partial \mathcal L_t}{\partial\textbf z_t}=\hat{\textbf y}_t-\textbf y_t$$
所以有
$$\frac{\partial \mathcal L_t}{\partial V_{ij}}=[\hat{\textbf y}_t-\textbf y_t]_i[\textbf h_t]_j$$
從而有
$$\frac{\partial \mathcal L_t}{\partial V}=(\hat{\textbf y}_t-\textbf y_t)\textbf h_t^{\top}=(\hat{\textbf y}_t-\textbf y_t)\otimes \textbf h_t$$
向量外積是矩陣的Kronecker積在向量下的特殊情況。因此,
$$\frac{\partial \mathcal L}{\partial V}=\sum_{t=1}^T(\hat{\textbf y}_t-\textbf y_t)\otimes \textbf h_t$$
二、 $\mathcal L$ 對 $U$ 的梯度
繼續求取 $\mathcal L$ 對 $U$ 的梯度。在求 $\frac{\partial \mathcal L_t}{\partial U}$ 時,需要注意到一個事實,那就是不光 $t$ 時刻的隱狀態與 $U$ 有關,之前所有時刻的隱狀態都與 $U$ 有關。

圖片來源:[1]
所以,根據chain rule:
$$\frac{\partial \mathcal L_t}{\partial U}=\sum_{k=1}^t\frac{\partial\textbf s_k}{\partial U}\frac{\partial \mathcal L_t}{\partial\textbf s_k}$$
下面使用和之前類似的套路求解:先求對一個矩陣一個元素的梯度。
$$\frac{\partial \mathcal L_t}{\partial U_{ij}}=\sum_{k=1}^t(\frac{\partial \mathcal L_t}{\partial\textbf s_k})^{\top}\frac{\partial\textbf s_k}{\partial U_{ij}}$$
前一項先定義為 $\delta_{t,k}=\dfrac{\partial \mathcal L_t}{\partial\textbf s_k}$ ,對於后一項:
$$\frac{\partial\textbf s_k}{\partial U_{ij}}=\frac{\partial(U\textbf h_{k-1}+W\textbf x_k)}{\partial U_{ij}}=(0,...,[\textbf h_{k-1}]_j,...,0)^{\top}$$
只有第 $i$ 行非零,$[\textbf h_{k-1}]_j$ 是指 $\textbf h_{k-1}$ 的第 $j$ 個元素。現在來求解 $\delta_{t,k}=\dfrac{\partial \mathcal L_t}{\partial\textbf s_k}$ ,使用上篇文章求 $\delta^{(l)}$ 的套路:
$$\begin{aligned}\delta_{t,k}&=\frac{\partial \mathcal L_t}{\partial\textbf s_k}\\&=\frac{\partial \textbf h_k}{\partial\textbf s_{k}}\frac{\partial \textbf s_{k+1}}{\partial\textbf h_{k}}\frac{\partial \mathcal L_t}{\partial\textbf s_{k+1}}\\&=\text{diag}(f'(\textbf s_k))U^{\top}\delta_{t,k+1}\\&=f'(\textbf s_{k})\odot (U^{\top}\delta_{t,k+1})\end{aligned}$$
一種特殊情況是當 $\delta_{t,t}$ ,有
$$\begin{aligned}\delta_{t,t}&=\frac{\partial \mathcal L_t}{\partial\textbf s_t}\\&=\frac{\partial \textbf h_t}{\partial\textbf s_t}\frac{\partial \textbf z_t}{\partial\textbf h_t}\frac{\partial \mathcal L_t}{\partial\textbf z_t}\\&=\text{diag}(f'(\textbf s_{t}))V^{\top}(\hat{\textbf y}_t-\textbf y_t)\\&=f'(\textbf s_{t})\odot (V^{\top}(\hat{\textbf y}_t-\textbf y_t))\end{aligned}$$
所以,
$$\frac{\partial \mathcal L_t}{\partial U_{ij}}=\sum_{k=1}^t[\delta_{t,k}]_i[\textbf h_{k-1}]_j$$
$$\frac{\partial \mathcal L_t}{\partial U}=\sum_{k=1}^t\delta_{t,k}\textbf h_{k-1}^{\top}=\sum_{k=1}^t\delta_{t,k}\otimes\textbf h_{k-1}$$
因此,
$$\frac{\partial \mathcal L}{\partial U}=\sum_{t=1}^T\sum_{k=1}^t\delta_{t,k}\otimes\textbf h_{k-1}$$
三、$\mathcal L$ 對 $W$ 的梯度
觀察 $\textbf s_t=U\textbf h_{t-1}+W\textbf x_t$ 這個式子,不難發現只要把剛剛推導的結果做一下簡單的替換就可以直接得到新的結果:
$$\frac{\partial \mathcal L_t}{\partial W}=\sum_{k=1}^t\delta_{t,k}\otimes\textbf x_{k}$$
$$\frac{\partial \mathcal L}{\partial W}=\sum_{t=1}^T\sum_{k=1}^t\delta_{t,k}\otimes\textbf x_{k}$$
總的來說,沒有寫什么insightful的東西,就是記錄一下而已。使用的套路都是BP中使用的(其實就是很基本的chain rule)。但是需要注意的是,這里實際上是在時間維度上的展開。如果是跟普通的神經網絡那樣構造多個隱層,則需要在“縱向”上繼續擴展,形成所謂的深度RNN。因為Theano等自動求導工具的存在,所以如果只是為了編程的話,很多情況下其實也不太需要手推了。

深度雙向RNN。圖片來源:[2]
(二)梯度消失(gradient vanishing)
我們考察一下下面這個梯度:
$$\frac{\partial \mathcal L_t}{\partial U}=\frac{\partial \textbf h_t}{\partial U}\frac{\partial \hat{\textbf y}_t}{\partial \textbf h_t}\frac{\partial \mathcal L_t}{\partial \hat{\textbf y}_t}$$
這里的 $\dfrac{\partial \textbf h_t}{\partial U}$ 比較麻煩,是因為各個時刻共享了參數:$\textbf h_t$ 這個參數是和 $\textbf h_{t-1}$ 、$U$ 有關的,而 $\textbf h_{t-1}$ 又和 $\textbf h_{t-2}$ 、$U$ 有關。所以參照 [5] ,可以寫成以下形式(讀 [5] 的時候需要注意其前向傳播過程和 [4] 一樣,與本文是有區別的,但在這里不妨礙理解):
$$\frac{\partial \mathcal L_t}{\partial U}=\sum_{k=1}^t\frac{\partial \textbf h_k}{\partial U}\frac{\partial \textbf h_t}{\partial \textbf h_k}\frac{\partial \hat{\textbf y}_t}{\partial \textbf h_t}\frac{\partial \mathcal L_t}{\partial \hat{\textbf y}_t}$$
其中,
$$\begin{aligned}\frac{\partial \textbf h_t}{\partial \textbf h_k}&=\prod_{i=k+1}^t\frac{\partial \textbf h_i}{\partial \textbf h_{i-1}}\\&=\prod_{i=k+1}^t\frac{\partial \textbf s_i}{\partial \textbf h_{i-1}}\frac{\partial f(\textbf s_i)}{\partial \textbf s_i}\\&=\prod_{i=k+1}^tU^{\top}\text{diag}{f'(\textbf s_i)}\end{aligned}$$
從這個式子可以看出,當使用tanh或logistic激活函數時,由於導數值分別在0到1之間、0到1/4之間,所以如果權重矩陣 $U$ 的范數也不很大,那么經過 $t-k$ 次傳播后,$\dfrac{\partial \textbf h_t}{\partial \textbf h_k}$ 的范數會趨於0,也就導致了梯度消失問題。其實從上面誤差項的表達式也可以看出,$\delta_{t,k}$ 與 $\delta_{t,k+1}$ 是乘一個導函數的關系,這個導函數值域在0到1之間(tanh)、0到1/4之間(logistic),那么隨着時間的累積,當然會造成梯度消失問題。
為了緩解梯度消失,可以使用ReLU、PReLU來作為激活函數,以及將 $U$ 初始化為單位矩陣(而不是用隨機初始化)等方式。
(普通的前饋深層神經網絡也會存在梯度消失,只不過那里是“縱向”上的。)
也就是說,雖然Simple RNN從理論上可以保持長時間間隔的狀態之間的依賴關系,但是實際上只能學習到短期依賴關系。這就造成了“長期依賴”問題。打個比方,你對着模型說了一大段話,“你好,我叫小明,balabala……,很高興認識你”。模型聽完之后回答你:“很高興認識你,你叫什么?我叫小紅。”——模型已經忘了你叫什么了。
需要通過帶LSTM單元的RNN來緩解梯度消失問題,現在一般把使用LSTM單元的RNN就直接叫LSTM了。LSTM單元引入了門機制(Gate),通過遺忘門、輸入門和輸出門來控制流過單元的信息。我們知道,Simple RNN之所以有梯度消失是因為誤差項之間的相乘關系;如果用LSTM推導,會發現這個相乘關系變成了相加關系,所以可以緩解梯度消失。
(三)梯度爆炸(gradient exploding)
而對於梯度爆炸問題,通常就是使用比較簡單的策略,也就是gradient clipping梯度裁剪:如果在一次迭代中各個權重的梯度平方和大於某個閾值,那么為避免權重的變化值太大,求一個縮放因子(閾值除以平方和),將所有的梯度乘以這個因子。TensorFlow里提供了很多種梯度裁剪的函數,直接看API吧。
參考:
[1] 《神經網絡與深度學習講義》
[2] Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs
Recurrent Neural Networks Tutorial, Part 3 – Backpropagation Through Time and Vanishing Gradients
[3] BPTT算法推導
[4] On the difficulty of training RNN
[6] 知乎:deep bidirectional RNN +LSTM 用於癲癇檢測的疑問?
[7] caffe里的clip gradient是什么意思?
