循环神经网络
基本循环神经网络

左图是一个简单的循环神经网络(Recurrent Neural Network),由输入层、一个隐藏层和一个输出层组成。
如果将有W的带箭头的圈去掉,它就变成了最普通的全连接神经网络。x是一个向量,代表输入层的值。s是一个向量,代表隐藏层的值。U是输入层到隐藏层的权重矩阵。o也是一个向量,代表输出层的值。V是隐藏层到输出层的权重矩阵。
循环神经网络的隐藏层的值s不仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。权重矩阵W就是隐藏层上一次的值作为这一次的输入的权重。
将上面的图展开,也可以画成下面的样子:

可以看到,这个网络在t时刻接收到输入xt之后,隐藏层的值是st,输出值是ot.此外,st的值还取决于st−1.
otst=g(Vst)=f(Uxt+Wst−1)(式1)(式2)
式1是输出层的计算公式。输出层是一个全连接层,其中的每个节点都和隐藏层的每个节点相连。V是输出层的权重矩阵,g是激活函数。
式2是隐藏层的计算公式。它是循环层。U是输入x的权重矩阵,W是上一次的值st−1作为这一次输入的权重矩阵,f是激活函数。
循环层和全连接层的区别就是循环层多了一个权重矩阵W。
如果将式2反复代入式1,可以得到:
ot=Vf(Uxt+Wf(Uxt−1+Wf(Uxt−2+⋯)))
可以看出,循环神经网络的输出值ot,是受前面历次输入值影响的。因此循环神经网络可以往前看任意多个输入值。
双向神经网络
对于语言模型来说,很多时候还需要看下文。 此时基本循环神经网络无法进行建模,我们需要双向循环神经网络,如图。

以y2为例:
y2=g(VA2+V′A2′)
A2和A2′则分别计算:
A2A2′=f(WA1+Ux2)=f(W′A3′+U′x2)
可以看出,正向计算时,隐藏层的值st与st−1有关;反向计算时,隐藏层的值st′与st+1′有关。最终的输出取决于正向和反向计算的加和。
otstst′=g(Vst+V′st′)=f(Uxt+Wst−1)=f(U′xt+W′st+1′)
其中,正向计算(2)和反向计算(3)不共享权重,即U和U′,W和W′都是不同的权重矩阵。
深度循环神经网络

上面的循环神经网络只有一个隐藏层,也可以堆叠两个以上的隐藏层,如左图所示。这样就得到了深度循环神经网络。
将第i个隐藏层的值表示为st(i)、st′(i),则深度循环神经网络的计算可表示为:
otst(i)st′(i)=g(V(i)st(i)+V′(i)st′(i))=f(U(i)xt(i−1)+W(i)st−1)=f(U′(i−1)xt+W′(i)st+1′)
循环神经网络的训练
循环神经网络的训练算法:BPTT
BPTT算法是针对循环层的训练算法,其基本原理和BP相同,也包含三个步骤:
- 前向计算每个神经元的输出值
- 反向传播每个神经元的误差项δj,即误差函数Ed对神经元j的加权输入netj的偏导数
- 计算每个权重的梯度
- 用随机梯度下降算法更新权重
循环层如下图所示:

前向计算
st=f(Uxt+Wst−1)
假设输入向量x的维度是m,输出向量s的维度是n,则矩阵U的维度是n×m,矩阵W的维度是n×n.
上面的式子便可以展开成以下形式:
s1ts2t⋮snt=f(u11u21⋮un1u12⋯u22⋯⋱un2⋯u1mu2m⋮unmx1x2⋮xm+ω11ω21⋮ωn1ω12⋯ω22⋯⋱ωn2⋯ω1nω2n⋮ωnns1t−1s2t−1⋮snt−1)
误差项的计算
BTPP算法将第l层t时刻的误差项δtl值沿两个方向传播,一个方向是传递到上一层网络,得到δtl−1,这部分只和权重矩阵U有关;另一个方向是将其沿时间线传递到初始t1时刻,得到δ1l
,这部分只和权重矩阵W
有关。
用向量nett
表示神经元在t
时刻的加权输入,因为:
nettst−1=Uxt+Wst−1=f(nett−1)
因此:
∂nett−1∂nett=∂st−1∂nett∂nett−1∂st−1
用a表示列向量,用aT表示行向量。上式第一项是向量函数对向量的求导,其结果为雅可比矩阵
∂st−1∂nett=∂s1t−1∂net1t∂s1t−1∂net2t⋮∂s1t−1∂netnt∂s2t−1∂net1t∂s2t−1∂net2t⋮∂s2t−1∂netnt⋯⋯⋱⋯∂snt−1∂net1t∂snt−1∂net2t⋮∂snt−1∂netnt=ω11ω21⋮ωn1ω12ω22⋮ωn2⋯⋯⋱⋯ω1nω2n⋮ωnn=W
∂nett−1∂st−1=∂net1t−1∂s1t−1∂net1t−1∂s2t−1⋮∂net1t−1∂snt−1∂net2t−1∂s1t−1∂net2t−1∂s2t−1⋮∂net2t−1∂snt−1⋯⋯⋱⋯∂netnt−1∂s1t−1∂netnt−1∂s2t−1⋮∂netnt−1∂snt−1=f′(net1t−1)0⋮00f′(net2t−1)⋮0⋯⋯⋱⋯00⋮f′(netnt−1)=diag[f′(nett−1)]
其中diag[a]表示根据向量a创建的一个对角矩阵。
最后,将两项合在一起,可得:
∂nett−1∂nett=∂st−1∂nett⋅∂nett−1∂st−1=W⋅diag[f′(nett−1)]=ω11f′(net1t−1)ω21f′(net1t−1)⋮ωn1f′(net1t−1)ω12f′(net2t−1)ω22f′(net2t−1)⋮ωn2f′(net2t−1)⋯⋯⋱⋯ω1nf′(netnt−1)ω2nf′(netnt−1)⋮ωnnf′(netnt−1)
上式描述了将δ沿时间向前传递一个时刻的规律。由此可得任意时刻k的误差项δk:
δkT=∂netk∂E=∂nett∂E⋅∂netk∂nett=∂nett∂E⋅∂nett−1∂nett⋅∂nett−2∂nett−1⋯∂netk∂netk+1=Wdiag[f′(nett−1)]⋅Wdiag[f′(nett−2)]⋯Wdiag[f′(netk)]⋅δtl=δtT⋅i=k∏t−1Wdiag[f′(neti)]
这就是将误差项沿时间反向传播的算法。
循环层的加权输入
netl与上一层的**加权输入netl−1**的关系如下:
nettl=Uatl−1+Wst−1atl−1=fl−1(nettl−1)
其中nettl是第l层神经元的加权输入(假设第l层是循环层);nettl−1是第l−1层的加权输入;atl−1是第l−1层神经元的输出;fl−1是第l−1层的激活函数。
∂nettl−1∂nettl=∂atl−1∂nettl⋅∂nettl−1∂atl−1=U⋅diag[f′l−1(nettl−1)]
因此,
(δtl−1)T=∂nettl−1∂E=∂nettl∂E⋅∂nettl−1∂nettl=(δtl)TUdiag[f′l−1(nettl−1)]
以上就是将误差项传递到上一层的算法。
权重梯度的计算
最后一步是计算每个权重的梯度。首先,计算误差函数E对权重矩阵W的梯度∂W∂E

上图是目前为止前两步中已经计算得到的量,包括每个时刻t循环层输出的值st,以及误差项δt。
权重矩阵在t时刻的梯度:
∂W∂E=∂nett∂E⋅∂W∂nett
其中,∂nett∂E=δt;∂W∂nett=(st−1)T,因为nett=Wst−1+Uxt+b
因此,
∂W∂E=∂nett∂E⋅∂W∂nett=δt⋅st−1T=δ1ts1t−1δ2ts1t−1⋮δnts1t−1δ1ts2t−1δ2ts2t−1⋮δnts2t−1⋯⋯⋱⋯δ1tsnt−1δ2tsnt−1⋮δntsnt−1
其中,δit表示t时刻误差项向量的第i个分量;sit−1表示t−1时刻循环层第i个神经元的输出值。
至此,已经求得权重矩阵W在t时刻的梯度∇WtE,最终梯度∇WE是各个时刻的梯度之和:
∇WE=i=1∑t∇WiE
上式即为循环层权重矩阵W的梯度公式。
同权重矩阵W类似,可以求得权重矩阵U的计算方法。
∇UtE∇UE=δ1tx1tδ2tx1t⋮δntx1tδ1tx2tδ2tx2t⋮δntx2t⋯⋯⋱⋯δ1txmtδ2txmt⋮δntxmt=i=1∑t∇UiE
RNN的梯度爆炸和消失问题
前面介绍的几种RNN并不能很好的处理较长的序列,主要原因是RNN在训练中很容易发生梯度爆炸和梯度消失问题,这导致训练时梯度不能在较长序列中一直传递下去,从而使RNN无法捕捉到长距离的影响。
原因如下式:
δkT=δtT⋅i=k∏t−1W⋅diag[f′(neti)]∥δkT∥≤∥δtT∥⋅i=k∏t−1∥W∥⋅∥diag(f′(neti))∥∥δkT∥≤∥δtT∥⋅(βWβf)t−k
上式中β定义为矩阵模的上界。∥δtT∥⋅(βWβf)t−k是一个指数函数,如果t−k很大,会导致误差项增长或缩小的非常快(取决于β大于还是小于1),这样就会导致相应的梯度爆炸和梯度消失问题。
通常梯度爆炸问题更容易处理,因为梯度爆炸时程序会收到NAN错误。可以设置一个梯度阈值,当梯度超过这个阈值的时候可以直接截取。
梯度消失更难检测,也更难处理。有三种方法可以应对梯度消失问题:
- 合理的初始化权重值,使每个神经元尽可能不要取极大或极小值,以避开梯度消失的区域。
- 使用ReLU函数代替Sigmoid和tanh作为激活函数。
- 使用其它结构的RNN,如长短时记忆网络(LSTM)和GRU。
RNN应用举例:基于RNN的语言模型
首先把词依次输入到RNN中,每输入一个词,RNN就输出截止到目前为止,下一个最可能的词。如,依次输入:
我 昨天 上学 迟到 了
RNN的输出如下图所示:

其中s和e是两个特殊词,分别表示一个序列的开始和结束。
向量化
为了让语言模型能处理词语,必须将其表达为向量的形式。神经网络的输入是词,按照下面的步骤可将其向量化:
- 建立一个包含所有词的词典,每个词在词典里面有一个唯一的编号。
- 任意一个词都可以用一个N维one-hot向量来表示。N是词点中词的个数。
如下图所示。

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

Softmax层
将softmax层作为神经网络的输出层就能让神经网络输出概率。以下是softmax函数的定义:
g(zi)=∑kezkezi

以y1的计算为例:
y1=e1+e2+e3+e4e1=0.03
这样,输出向量y就有如下特征:
- 每一项是取值为0~1的正数
- 所有项的总和是1
这样就可以将这些输出看作是概率。
语言模型的训练
可以用监督学习的方法对语言模型进行训练。首先准备训练数据集,获取输入-标签对:
输入 |
标签 |
s |
我 |
我 |
昨天 |
昨天 |
上学 |
上学 |
迟到 |
迟到 |
了 |
了 |
e |
然后按照上面的向量化方法对输入x和标签y进行向量化。最后,使用交叉熵误差函数作为优化目标,对模型进行优化。
交叉熵误差
当神经网络的输出层是softmax层时,一般选用交叉熵误差函数作为误差函数E。其定义如下:
L(y,o)=−N1n∈N∑ynlogon
上式中,N是训练样本的个数,向量yn是样本的标记,向量on是网络的输出。标记yn是一个one-hot向量。
假设y1=[1,0,0,0],o=[0.03,0.09,0.24,0.64],那么交叉熵误差为:
L(y,o)=−N1n∈N∑ynlogon=−y1logo1=−(1∗log0.03+0∗⋯)=3.51
也可以选择其他函数作为误差函数,如MSE。但是对概率进行建模时,更常见选择交叉熵误差函数。