笔记参考和图片来源@hanbingtao:零基础入门深度学习(5) - 循环神经网络

循环神经网络

基本循环神经网络

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image.png)

左图是一个简单的循环神经网络(Recurrent Neural Network),由输入层、一个隐藏层和一个输出层组成。

如果将有WW的带箭头的圈去掉,它就变成了最普通的全连接神经网络。xx是一个向量,代表输入层的值。ss是一个向量,代表隐藏层的值。UU是输入层到隐藏层的权重矩阵。oo也是一个向量,代表输出层的值。VV是隐藏层到输出层的权重矩阵。

循环神经网络的隐藏层的值ss不仅取决于当前这次的输入xx,还取决于上一次隐藏层的值ss。权重矩阵WW就是隐藏层上一次的值作为这一次的输入的权重。

将上面的图展开,也可以画成下面的样子:

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 1.png)

可以看到,这个网络在tt时刻接收到输入xtx_t之后,隐藏层的值是sts_t,输出值是oto_t.此外,sts_t的值还取决于st1s_{t-1}.

ot=g(Vst)st=f(Uxt+Wst1)\begin{align}o_t&=g(Vs_t)\tag{式1}\\s_t&=f(Ux_t+Ws_{t-1})\tag{式2}\end{align}

式1是输出层的计算公式。输出层是一个全连接层,其中的每个节点都和隐藏层的每个节点相连。VV是输出层的权重矩阵,gg是激活函数。

式2是隐藏层的计算公式。它是循环层UU是输入xx的权重矩阵,WW是上一次的值st1s_{t-1}作为这一次输入的权重矩阵,ff是激活函数。

循环层和全连接层的区别就是循环层多了一个权重矩阵WW

如果将式2反复代入式1,可以得到:

ot=Vf(Uxt+Wf(Uxt1+Wf(Uxt2+)))o_t=Vf(Ux_t+Wf(Ux_{t-1}+Wf(Ux _{t-2}+\cdots)))

可以看出,循环神经网络的输出值oto_t,是受前面历次输入值影响的。因此循环神经网络可以往前看任意多个输入值

双向神经网络

对于语言模型来说,很多时候还需要看下文。 此时基本循环神经网络无法进行建模,我们需要双向循环神经网络,如图。

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 2.png)

y2y_2为例:

y2=g(VA2+VA2)y_2=g(VA_2+V'A_2')

A2A_2A2A_2'则分别计算:

A2=f(WA1+Ux2)A2=f(WA3+Ux2)\begin{aligned}A_2&=f(WA_1+Ux_2)\\A_2'&=f(W'A_3'+U'x_2)\end{aligned}

可以看出,正向计算时,隐藏层的值sts_tst1s_{t-1}有关;反向计算时,隐藏层的值sts_t'st+1s_{t+1}'有关。最终的输出取决于正向和反向计算的加和。

ot=g(Vst+Vst)st=f(Uxt+Wst1)st=f(Uxt+Wst+1)\begin{align}o_t&=g(Vs_t+V's_t')\\s_t&=f(Ux_t+Ws_{t-1})\\s_t'&=f(U'x_t+W's_{t+1}')\end{align}

其中,正向计算(2)和反向计算(3)不共享权重,即UUUU',WWWW'都是不同的权重矩阵。

深度循环神经网络

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 3.png)

上面的循环神经网络只有一个隐藏层,也可以堆叠两个以上的隐藏层,如左图所示。这样就得到了深度循环神经网络

将第ii个隐藏层的值表示为st(i)s_t^{(i)}st(i)s_t'^{(i)},则深度循环神经网络的计算可表示为:

ot=g(V(i)st(i)+V(i)st(i))st(i)=f(U(i)xt(i1)+W(i)st1)st(i)=f(U(i1)xt+W(i)st+1)\begin{align}o_t&=g(V^{(i)}s_t^{(i)}+V'^{(i)}s_t'^{(i)})\\s_t^{(i)}&=f(U^{(i)}x_t^{(i-1)}+W^{(i)}s_{t-1})\\s_t'^{(i)}&=f(U'^{(i-1)}x_t+W'^{(i)}s_{t+1}')\end{align}

循环神经网络的训练

循环神经网络的训练算法:BPTT

BPTT算法是针对循环层的训练算法,其基本原理和BP相同,也包含三个步骤:

  1. 前向计算每个神经元的输出值
  2. 反向传播每个神经元的误差项δj\delta_j,即误差函数EdE_d对神经元jj的加权输入netjnet_j的偏导数
  3. 计算每个权重的梯度
  4. 用随机梯度下降算法更新权重

循环层如下图所示:

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 4.png)

前向计算

st=f(Uxt+Wst1)s_t=f(Ux_t+Ws_{t-1})

假设输入向量xx的维度是mm,输出向量ss的维度是nn,则矩阵UU的维度是n×mn\times m,矩阵WW的维度是n×nn\times n.

上面的式子便可以展开成以下形式:

[s1ts2tsnt]=f([u11u12u1mu21u22u2mun1un2unm][x1x2xm]+[ω11ω12ω1nω21ω22ω2nωn1ωn2ωnn][s1t1s2t1snt1])\begin{bmatrix}s^t_1\\s^t_2\\\vdots\\s^t_n\end{bmatrix}=f(\begin{bmatrix}u_{11}&u_{12}\quad\cdots &u_{1m}\\u_{21}&u_{22}\quad\cdots &u_{2m}\\\vdots&\qquad\ddots&\vdots\\u_{n1}&u_{n2}\quad\cdots& u_{nm}\end{bmatrix}\begin{bmatrix}x_1\\x_2\\\vdots\\x_m\end{bmatrix}+\begin{bmatrix}\omega_{11}&\omega_{12}\quad\cdots &\omega_{1n}\\\omega_{21}&\omega_{22}\quad\cdots &\omega_{2n}\\\vdots&\qquad\ddots&\vdots\\\omega_{n1}&\omega_{n2}\quad\cdots& \omega_{nn}\end{bmatrix}\begin{bmatrix}s^{t-1}_1\\s^{t-1}_2\\\vdots\\s^{t-1}_n\end{bmatrix})

误差项的计算

BTPP算法将第lltt时刻的误差项δtl\delta^l_t值沿两个方向传播,一个方向是传递到上一层网络,得到δtl1\delta_t^{l-1},这部分只和权重矩阵UU有关;另一个方向是将其沿时间线传递到初始t1t_1时刻,得到δ1l\delta_1^l
,这部分只和权重矩阵WW
有关。

用向量nettnet_t

表示神经元在tt
时刻的加权输入,因为:

nett=Uxt+Wst1st1=f(nett1)\begin{aligned}net_t&=Ux_t+Ws_{t-1}\\s_{t-1}&=f(net_{t-1})\end{aligned}

因此:

nettnett1=nettst1st1nett1\frac{\partial net_t}{\partial net_{t-1}}=\frac{\partial net_t}{\partial s_{t-1}}\frac{\partial s_{t-1}}{\partial net_{t-1}}

aa表示列向量,用aTa^T表示行向量。上式第一项是向量函数对向量的求导,其结果为雅可比矩阵

nettst1=[net1ts1t1net1ts2t1net1tsnt1net2ts1t1net2ts2t1net2tsnt1netnts1t1netnts2t1netntsnt1]=[ω11ω12ω1nω21ω22ω2nωn1ωn2ωnn]=W\begin{aligned}\frac{\partial net_t}{\partial s_{t-1}}&=\begin{bmatrix}\frac{\partial net_1^t}{\partial s_1^{t-1}}&\frac{\partial net_1^t}{\partial s_2^{t-1}}&\cdots&\frac{\partial net_1^t}{\partial s_n^{t-1}}\\\frac{\partial net_2^t}{\partial s_1^{t-1}}&\frac{\partial net_2^t}{\partial s_2^{t-1}}&\cdots&\frac{\partial net_2^t}{\partial s_n^{t-1}}\\\vdots&\vdots&\ddots&\vdots\\\frac{\partial net_n^t}{\partial s_1^{t-1}}&\frac{\partial net_n^t}{\partial s_2^{t-1}}&\cdots&\frac{\partial net_n^t}{\partial s_n^{t-1}}\end{bmatrix}\\&=\begin{bmatrix}\omega_{11}&\omega_{12}&\cdots&\omega_{1n}\\\omega_{21}&\omega_{22}&\cdots&\omega_{2n}\\\vdots&\vdots&\ddots&\vdots\\\omega_{n1}&\omega_{n2}&\cdots&\omega_{nn}\end{bmatrix}\\&=W\end{aligned}

st1nett1=[s1t1net1t1s1t1net2t1s1t1netnt1s2t1net1t1s2t1net2t1s2t1netnt1snt1net1t1snt1net2t1snt1netnt1]=[f(net1t1)000f(net2t1)000f(netnt1)]=diag[f(nett1)]\begin{aligned} \frac{\partial {s}_{t-1}}{\partial {net}_{t-1}} &= \begin{bmatrix} \frac{\partial s^{t-1}_1}{\partial net^{t-1}_1} & \frac{\partial s^{t-1}_1}{\partial net^{t-1}_2} & \cdots & \frac{\partial s^{t-1}_1}{\partial net^{t-1}_n} \\ \frac{\partial s^{t-1}_2}{\partial net^{t-1}_1} & \frac{\partial s^{t-1}_2}{\partial net^{t-1}_2} & \cdots & \frac{\partial s^{t-1}_2}{\partial net^{t-1}_n} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{\partial s^{t-1}_n}{\partial net^{t-1}_1} & \frac{\partial s^{t-1}_n}{\partial net^{t-1}_2} & \cdots & \frac{\partial s^{t-1}_n}{\partial net^{t-1}_n} \end{bmatrix} \\ &= \begin{bmatrix} f'(net^{t-1}_1) & 0 & \cdots & 0 \\ 0 & f'(net^{t-1}_2) & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & f'(net^{t-1}_n) \end{bmatrix} \\[10pt] &= \mathrm{diag}\left[f'(net^{t-1})\right] \end{aligned}

其中diag[a]\text{diag}[a]表示根据向量aa创建的一个对角矩阵。

最后,将两项合在一起,可得:

nettnett1=nettst1st1nett1=Wdiag[f(nett1)]=[ω11f(net1t1)ω12f(net2t1)ω1nf(netnt1)ω21f(net1t1)ω22f(net2t1)ω2nf(netnt1)ωn1f(net1t1)ωn2f(net2t1)ωnnf(netnt1)]\begin{aligned}\frac{\partial {net}_t}{\partial {net}_{t-1}} &= \frac{\partial {net}_t}{\partial {s}_{t-1}} \cdot \frac{\partial {s}_{t-1}}{\partial {net}_{t-1}} \\[5pt]&= W \cdot \mathrm{diag}\left[f'(net_{t-1})\right] \\&= \begin{bmatrix} \omega_{11} f'(net^{t-1}_1) & \omega_{12} f'(net^{t-1}_2) & \cdots & \omega_{1n} f'(net^{t-1}_n) \\ \omega_{21} f'(net^{t-1}_1) & \omega_{22} f'(net^{t-1}_2) & \cdots & \omega_{2n} f'(net^{t-1}_n) \\ \vdots & \vdots & \ddots & \vdots \\ \omega_{n1} f'(net^{t-1}_1) & \omega_{n2} f'(net^{t-1}_2) & \cdots & \omega_{nn} f'(net^{t-1}_n) \end{bmatrix} \end{aligned}

上式描述了将δ\delta沿时间向前传递一个时刻的规律。由此可得任意时刻kk的误差项δk\delta_k

δkT=Enetk=Enettnettnetk=Enettnettnett1nett1nett2netk+1netk=Wdiag[f(nett1)]Wdiag[f(nett2)]Wdiag[f(netk)]δtl=δtTi=kt1Wdiag[f(neti)]\begin{aligned}\delta_k^T &= \frac{\partial E}{\partial \mathbf{net}_k} \\&= \frac{\partial E}{\partial \mathbf{net}_t} \cdot \frac{\partial \mathbf{net}_t}{\partial \mathbf{net}_k} \\&= \frac{\partial E}{\partial \mathbf{net}_t} \cdot \frac{\partial \mathbf{net}_t}{\partial \mathbf{net}_{t-1}} \cdot \frac{\partial \mathbf{net}_{t-1}}{\partial \mathbf{net}_{t-2}} \cdots \frac{\partial \mathbf{net}_{k+1}}{\partial \mathbf{net}_k} \\&= W \, \mathrm{diag}\left[f'(\mathbf{net}_{t-1})\right] \cdot W \, \mathrm{diag}\left[f'(\mathbf{net}_{t-2})\right] \cdots W \, \mathrm{diag}\left[f'(\mathbf{net}_k)\right] \cdot \delta_t^l \\&= \delta_t^T \cdot \prod_{i=k}^{t-1} W \, \mathrm{diag}\left[f'(\mathbf{net}_i)\right]\end{aligned}

这就是将误差项沿时间反向传播的算法。

循环层的加权输入

netlnet^l与上一层的**加权输入netl1net^{l-1}**的关系如下:

nettl=Uatl1+Wst1atl1=fl1(nettl1)net_t^l=Ua_t^{l-1}+Ws_{t-1}\\a_{t}^{l-1}=f^{l-1}(net_t^{l-1})

其中nettlnet_t^l是第ll层神经元的加权输入(假设第ll层是循环层);nettl1net_t^{l-1}是第l1l-1层的加权输入atl1a_t^{l-1}是第l1l-1层神经元的输出;fl1f^{l-1}是第l1l-1层的激活函数。

nettlnettl1=nettlatl1atl1nettl1=Udiag[fl1(nettl1)]\frac{\partial \mathbf{net}_t^l}{\partial \mathbf{net}_t^{l-1}} = \frac{\partial \mathbf{net}_t^l}{\partial \mathbf{a}_t^{l-1}} \cdot \frac{\partial \mathbf{a}_t^{l-1}}{\partial \mathbf{net}_t^{l-1}} = \mathbf{U} \cdot \mathrm{diag}\left[f'^{l-1}(\mathbf{net}_t^{l-1})\right]

因此,

(δtl1)T=Enettl1=Enettlnettlnettl1=(δtl)TUdiag[fl1(nettl1)]\begin{aligned}(\delta_t^{l-1})^T &= \frac{\partial E}{\partial \mathbf{net}_t^{l-1}} \\&= \frac{\partial E}{\partial \mathbf{net}_t^l} \cdot \frac{\partial \mathbf{net}_t^l}{\partial \mathbf{net}_t^{l-1}} \\&= (\delta_t^l)^T \mathbf{U} \, \mathrm{diag}\left[f'^{l-1}(\mathbf{net}_t^{l-1})\right]\end{aligned}

以上就是将误差项传递到上一层的算法。

权重梯度的计算

最后一步是计算每个权重的梯度。首先,计算误差函数EE对权重矩阵WW的梯度EW\frac {\partial E}{\partial W}

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 5.png)

上图是目前为止前两步中已经计算得到的量,包括每个时刻tt循环层输出的值sts_t,以及误差项δt\delta_t

权重矩阵在tt时刻的梯度:

EW=EnettnettW\frac{\partial E}{\partial W}=\frac{\partial E}{\partial net_t}\cdot\frac{\partial net_t}{\partial W}

其中,Enett=δt\frac{\partial E}{\partial net_t}=\delta_tnettW=(st1)T\frac{\partial net_t}{\partial W}=(s_{t-1})^T,因为nett=Wst1+Uxt+bnet_t=Ws_{t-1}+Ux_t+b

因此,

EW=EnettnettW=δtst1T=[δ1ts1t1δ1ts2t1δ1tsnt1δ2ts1t1δ2ts2t1δ2tsnt1δnts1t1δnts2t1δntsnt1]\begin{aligned}\frac{\partial E}{\partial W}&=\frac{\partial E}{\partial net_t}\cdot\frac{\partial net_t}{\partial W}\\&=\delta_t\cdot s_{t-1}^T\\&= \begin{bmatrix} \delta_1^t s_1^{t-1} & \delta_1^t s_2^{t-1} & \cdots & \delta_1^t s_n^{t-1} \\ \delta_2^t s_1^{t-1} & \delta_2^t s_2^{t-1} & \cdots & \delta_2^t s_n^{t-1} \\ \vdots & \vdots & \ddots & \vdots \\ \delta_n^t s_1^{t-1} & \delta_n^t s_2^{t-1} & \cdots & \delta_n^t s_n^{t-1} \end{bmatrix} \end{aligned}

其中,δit\delta_i^t表示tt时刻误差项向量的第ii个分量;sit1s_i^{t-1}表示t1t-1时刻循环层ii个神经元的输出值。

至此,已经求得权重矩阵WWtt时刻的梯度WtE\nabla_{W_t}E,最终梯度WE\nabla_WE是各个时刻的梯度之和:

WE=i=1tWiE\nabla_WE=\sum_{i=1}^{t}\nabla_{W_i}E

上式即为循环层权重矩阵WW的梯度公式。

同权重矩阵WW类似,可以求得权重矩阵UU的计算方法。

UtE=[δ1tx1tδ1tx2tδ1txmtδ2tx1tδ2tx2tδ2txmtδntx1tδntx2tδntxmt]UE=i=1tUiE\begin{aligned}\nabla_{U_t} E &=\begin{bmatrix}\delta_1^t x_1^t & \delta_1^t x_2^t & \cdots & \delta_1^t x_m^t \\\delta_2^t x_1^t & \delta_2^t x_2^t & \cdots & \delta_2^t x_m^t \\\vdots & \vdots & \ddots & \vdots \\\delta_n^t x_1^t & \delta_n^t x_2^t & \cdots & \delta_n^t x_m^t\end{bmatrix}\\\nabla_UE&=\sum_{i=1}^t\nabla_{U_i}E\end{aligned}

RNN的梯度爆炸和消失问题

前面介绍的几种RNN并不能很好的处理较长的序列,主要原因是RNN在训练中很容易发生梯度爆炸梯度消失问题,这导致训练时梯度不能在较长序列中一直传递下去,从而使RNN无法捕捉到长距离的影响。

原因如下式:

δkT=δtTi=kt1Wdiag[f(neti)]δkTδtTi=kt1Wdiag(f(neti))δkTδtT(βWβf)tk\delta_k^T = \delta_t^T \cdot \prod_{i=k}^{t-1} W \cdot \mathrm{diag}\left[ f'(net_i) \right]\\ \|\delta_k^T\| \leq \|\delta_t^T\| \cdot \prod_{i=k}^{t-1} \|W\| \cdot \left\| \mathrm{diag}(f'(net_i)) \right\| \\ \|\delta_k^T\| \leq \|\delta_t^T\| \cdot (\beta_W \beta_f)^{t-k}

上式中β\beta定义为矩阵模的上界。δtT(βWβf)tk\|\delta_t^T\| \cdot (\beta_W \beta_f)^{t-k}是一个指数函数,如果tkt-k很大,会导致误差项增长或缩小的非常快(取决于β\beta大于还是小于11),这样就会导致相应的梯度爆炸梯度消失问题。

通常梯度爆炸问题更容易处理,因为梯度爆炸时程序会收到NAN错误。可以设置一个梯度阈值,当梯度超过这个阈值的时候可以直接截取。

梯度消失更难检测,也更难处理。有三种方法可以应对梯度消失问题:

  1. 合理的初始化权重值,使每个神经元尽可能不要取极大或极小值,以避开梯度消失的区域。
  2. 使用ReLU\text {ReLU}函数代替Sigmoid\text{Sigmoid}tanh\tanh作为激活函数。
  3. 使用其它结构的RNN,如长短时记忆网络(LSTM)和GRU。

RNN应用举例:基于RNN的语言模型

首先把词依次输入到RNN中,每输入一个词,RNN就输出截止到目前为止,下一个最可能的词。如,依次输入:

我 昨天 上学 迟到 了

RNN的输出如下图所示:

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 6.png)

其中s和e是两个特殊词,分别表示一个序列的开始和结束。

向量化

为了让语言模型能处理词语,必须将其表达为向量的形式。神经网络的输入是,按照下面的步骤可将其向量化

  1. 建立一个包含所有词的词典,每个词在词典里面有一个唯一的编号。
  2. 任意一个词都可以用一个N维one-hot向量来表示。N是词点中词的个数。

如下图所示。

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 7.png)

神经网络的输出也是一个N维向量,向量中每个元素对应词点钟相应的词是下一个词的概率。

如下图所示。

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 8.png)

Softmax层

将softmax层作为神经网络的输出层就能让神经网络输出概率。以下是softmax函数的定义:

g(zi)=ezikezkg(z_i)=\frac{e^{z_i}}{\sum_ke^{z_k}}

![image.png](/Users/jbx/Downloads/私人与共享 3/循环神经网络 1e4b9196b5df80ae9824cbfbbd7e3b61/image 9.png)

y1y_1的计算为例:

y1=e1e1+e2+e3+e4=0.03\begin{aligned}y_1&=\frac{e^1}{e^1+e^2+e^3+e^4}\\&=0.03\end{aligned}

这样,输出向量yy就有如下特征:

  1. 每一项是取值为010~1的正数
  2. 所有项的总和是1

这样就可以将这些输出看作是概率。

语言模型的训练

可以用监督学习的方法对语言模型进行训练。首先准备训练数据集,获取输入-标签对:

输入 标签
s
昨天
昨天 上学
上学 迟到
迟到
e

然后按照上面的向量化方法对输入xx和标签yy进行向量化。最后,使用交叉熵误差函数作为优化目标,对模型进行优化。

交叉熵误差

当神经网络的输出层是softmax层时,一般选用交叉熵误差函数作为误差函数EE。其定义如下:

L(y,o)=1NnNynlogonL(y,o)=-\frac1N\sum_{n\in N}y_n\log o_n

上式中,NN是训练样本的个数,向量yny_n是样本的标记,向量ono_n是网络的输出。标记yny_n是一个one-hot向量。

假设y1=[1,0,0,0],o=[0.03,0.09,0.24,0.64]y_1=[1,0,0,0],o=[0.03,0.09,0.24,0.64],那么交叉熵误差为:

L(y,o)=1NnNynlogon=y1logo1=(1log0.03+0)=3.51\begin{aligned}L(y,o)&=-\frac1N\sum_{n\in N}y_n\log o_n\\&=-y_1\log o_1\\&=-(1*\log 0.03+0*\cdots)\\&=3.51\end{aligned}

也可以选择其他函数作为误差函数,如MSE。但是对概率进行建模时,更常见选择交叉熵误差函数。