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

循环神经网络

基本循环神经网络

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

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

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

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

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

ot=g(Vst)st=f(Uxt+Wst−1)\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是上一次的值st−1s_{t-1}作为这一次输入的权重矩阵,ff是激活函数。

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

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

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

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

双向神经网络

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

以y2y_2为例:

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

A2A_2和A2′A_2'则分别计算:

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

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

ot=g(Vst+V′st′)st=f(Uxt+Wst−1)st′=f(U′xt+W′st+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)不共享权重,即UU和U′U',WW和W′W'都是不同的权重矩阵。

深度循环神经网络

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

将第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(i−1)+W(i)st−1)st′(i)=f(U′(i−1)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. 用随机梯度下降算法更新权重

循环层如下图所示:

前向计算

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

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

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

[s1ts2t⋮snt]=f([u11u12⋯u1mu21u22⋯u2m⋮⋱⋮un1un2⋯unm][x1x2⋮xm]+[ω11ω12⋯ω1nω21ω22⋯ω2n⋮⋱⋮ωn1ωn2⋯ωnn][s1t−1s2t−1⋮snt−1])\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算法将第ll层tt时刻的误差项δtl\delta^l_t值沿两个方向传播,一个方向是传递到上一层网络,得到δtl−1\delta_t^{l-1},这部分只和权重矩阵UU有关;另一个方向是将其沿时间线传递到初始t1t_1时刻,得到δ1l\delta_1^l
,这部分只和权重矩阵WW
有关。

用向量nettnet_t

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

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

因此:

∂nett∂nett−1=∂nett∂st−1∂st−1∂nett−1\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表示行向量。上式第一项是向量函数对向量的求导,其结果为雅可比矩阵

∂nett∂st−1=[∂net1t∂s1t−1∂net1t∂s2t−1⋯∂net1t∂snt−1∂net2t∂s1t−1∂net2t∂s2t−1⋯∂net2t∂snt−1⋮⋮⋱⋮∂netnt∂s1t−1∂netnt∂s2t−1⋯∂netnt∂snt−1]=[ω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}

∂st−1∂nett−1=[∂s1t−1∂net1t−1∂s1t−1∂net2t−1⋯∂s1t−1∂netnt−1∂s2t−1∂net1t−1∂s2t−1∂net2t−1⋯∂s2t−1∂netnt−1⋮⋮⋱⋮∂snt−1∂net1t−1∂snt−1∂net2t−1⋯∂snt−1∂netnt−1]=[f′(net1t−1)0⋯00f′(net2t−1)⋯0⋮⋮⋱⋮00⋯f′(netnt−1)]=diag[f′(nett−1)]\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创建的一个对角矩阵。

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

∂nett∂nett−1=∂nett∂st−1⋅∂st−1∂nett−1=W⋅diag[f′(nett−1)]=[ω11f′(net1t−1)ω12f′(net2t−1)⋯ω1nf′(netnt−1)ω21f′(net1t−1)ω22f′(net2t−1)⋯ω2nf′(netnt−1)⋮⋮⋱⋮ωn1f′(net1t−1)ωn2f′(net2t−1)⋯ωnnf′(netnt−1)]\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=∂E∂netk=∂E∂nett⋅∂nett∂netk=∂E∂nett⋅∂nett∂nett−1⋅∂nett−1∂nett−2⋯∂netk+1∂netk=W diag[f′(nett−1)]⋅W diag[f′(nett−2)]⋯W diag[f′(netk)]⋅δtl=δtT⋅∏i=kt−1W diag[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与上一层的**加权输入netl−1net^{l-1}**的关系如下:

nettl=Uatl−1+Wst−1atl−1=fl−1(nettl−1)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层是循环层);nettl−1net_t^{l-1}是第l−1l-1层的加权输入;atl−1a_t^{l-1}是第l−1l-1层神经元的输出;fl−1f^{l-1}是第l−1l-1层的激活函数。

∂nettl∂nettl−1=∂nettl∂atl−1⋅∂atl−1∂nettl−1=U⋅diag[f′l−1(nettl−1)]\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]

因此,

(δtl−1)T=∂E∂nettl−1=∂E∂nettl⋅∂nettl∂nettl−1=(δtl)TU diag[f′l−1(nettl−1)]\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的梯度∂E∂W\frac{\partial E}{\partial W}

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

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

∂E∂W=∂E∂nett⋅∂nett∂W\frac{\partial E}{\partial W}=\frac{\partial E}{\partial net_t}\cdot\frac{\partial net_t}{\partial W}

其中,∂E∂nett=δt\frac{\partial E}{\partial net_t}=\delta_t;∂nett∂W=(st−1)T\frac{\partial net_t}{\partial W}=(s_{t-1})^T,因为nett=Wst−1+Uxt+bnet_t=Ws_{t-1}+Ux_t+b

因此,

∂E∂W=∂E∂nett⋅∂nett∂W=δt⋅st−1T=[δ1ts1t−1δ1ts2t−1⋯δ1tsnt−1δ2ts1t−1δ2ts2t−1⋯δ2tsnt−1⋮⋮⋱⋮δnts1t−1δnts2t−1⋯δntsnt−1]\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个分量;sit−1s_i^{t-1}表示t−1t-1时刻循环层第ii个神经元的输出值。

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

∇WE=∑i=1t∇WiE\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=1t∇UiE\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=δtT⋅∏i=kt−1W⋅diag[f′(neti)]∥δkT∥≤∥δtT∥⋅∏i=kt−1∥W∥⋅∥diag(f′(neti))∥∥δkT∥≤∥δtT∥⋅(βWβf)t−k\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)t−k\|\delta_t^T\| \cdot (\beta_W \beta_f)^{t-k}是一个指数函数,如果t−kt-k很大,会导致误差项增长或缩小的非常快(取决于β\beta大于还是小于11),这样就会导致相应的梯度爆炸和梯度消失问题。

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

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

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

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

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

我 昨天 上学 迟到 了

RNN的输出如下图所示:

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

向量化

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

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

如下图所示。

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

如下图所示。

Softmax层

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

g(zi)=ezi∑kezkg(z_i)=\frac{e^{z_i}}{\sum_ke^{z_k}}

以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. 每一项是取值为0~10~1的正数
  2. 所有项的总和是1

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

语言模型的训练

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

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

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

交叉熵误差

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

L(y,o)=−1N∑n∈Nynlog⁡onL(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)=−1N∑n∈Nynlog⁡on=−y1log⁡o1=−(1∗log⁡0.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。但是对概率进行建模时,更常见选择交叉熵误差函数。