循环神经网络(RNN)及衍生LSTM、GRU详解

  我们之前所学的全连接神经网络(DNN)和卷积神经网络(CNN),他们的前一个输入和后一个输入是没有关系的。但是当我们处理序列信息的时候,某些前面的输入和后面的输入是有关系的,比如:当我们在理解一句话意思时,孤立的理解这句话的每个词是不够的,我们需要处理这些词连接起来的整个序列;这个时候我们就需要使用到循环神经网络(Recurrent Neural Network)

RNN在自然语言处理领域最先被使用起来,RNN可以为语言模型进行建模:

我没有完成上级布置给我的任务,所以  被开除了

让电脑来填写下划线的词最有可能的是『我』,而不太可能是『小明』,甚至是『吃饭』。

语言模型就是这样的东西:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么。

基本循环神经网络

  我们首先来学习 基本的 循环神经网络,结构由 输入层、一个隐藏层和输出层 组成。

  x是输入向量,o是输出向量,s表示隐藏层的值;U是输入层到隐藏层的权重矩阵V是隐藏层到输出层的权重矩阵循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。权重矩阵W就是隐藏层上一次的值作为这一次的输入的权重。

  我们将上图的基本RNN结构在时间维度展开(RNN是一个链式结构,每个时间片使用的是相同的参数):

RNN时间线展开图

现在看上去就会清楚许多,这个网络在t时刻接收到输入xt之后,隐藏层的值是st,输出值是ot关键一点是,st的值不仅仅取决于xt,还取决于st1

1st=f(Uxt+Wst1)

2ot=g(Vst)

  式1是隐藏层的计算公式,它是循环层。U是输入x的权重矩阵,W是上一次隐藏层值St1作为这一次的输入的权重矩阵,f是激活函数。

  式2是输出层的计算公式,V是输出层的权重矩阵,g是激活函数。

  隐含层有两个输入,第一是Uxt向量的乘积,第二是上一隐含层输出的状态st1W的乘积。等于上一个时刻计算的st1需要缓存一下,在本次输入xt一起计算,共同输出最后的ot

如果反复把式1带入式2,我们将得到:

(1)ot=g(Vst)(2)=Vf(Uxt+Wst1)(3)=Vf(Uxt+Wf(Uxt1+Wst2))(4)=Vf(Uxt+Wf(Uxt1+Wf(Uxt2+Wst3)))(5)=Vf(Uxt+Wf(Uxt1+Wf(Uxt2+Wf(Uxt3+...))))

从上面可以看出,循环神经网络的输出值ot,是受前面历次输入值xtxt1xt2xt3...影响的,这就是为什么循环神经网络可以往前看任意多个输入值的原因。这样其实不好,因为如果太前面的值和后面的值已经没有关系了,循环神经网络还考虑前面的值的话,就会影响后面值的判断。

双向循环神经网络

对于语言模型来说,很多时候光看前面的词是不够的,比如下面这句话:我的手机坏了,我打算  一部新的手机。 我们这个时候就需要双向循环神经网络。

从上图可以看出,双向卷积神经网络的隐藏层要保存两个值,一个A参与正向计算,另一个值A'参与反向计算。最终的输出值y2取决于A2A2。其计算方法为:

y2=g(VA2+VA2)

A2A2则分别计算:

(6)A2=f(WA1+Ux2)(7)A2=f(WA3+Ux2)

现在,我们已经可以看出一般的规律:正向计算时,隐藏层的值StSt1有关;反向计算时,隐藏层的值StSt+1有关;最终的输出取决于正向和反向计算的加和。现在,我们仿照式1和式2,写出双向循环神经网络的计算方法:

(8)ot=g(Vst+Vst)(9)st=f(Uxt+Wst1)(10)st=f(Uxt+Wst+1)

从上面三个公式我们可以看到,正向计算和反向计算不共享权重,也就是说U和U'、W和W'、V和V'都是不同的权重矩阵。

梯度爆炸和消失问题

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

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

梯度消失更难检测,而且也更难处理一些。总的来说,我们有三种方法应对梯度消失问题:

1、合理的初始化权重值。初始化权重,使每个神经元尽可能不要取极大或极小值,以躲开梯度消失的区域。

2、使用relu代替sigmoid和tanh作为激活函数。原理请参考上一篇文章零基础入门深度学习(4) - 卷积神经网络的激活函数一节。

3、使用其他结构的RNNs,比如长短时记忆网络(LTSM)和Gated Recurrent Unit(GRU),这是最流行的做法。

长短期记忆网络

  原始RNN的隐藏层只有一个状态,即h,它对于短期的输入非常敏感。那么如果我们再增加一个门(gate)机制用于控制特征的流通和损失,即c,让它来保存长期的状态,这就是长短时记忆网络(Long Short Term Memory,LSTM)。

新增加的状态c,称为单元状态。我们把LSTM按照时间维度展开:

可以看到在t时刻,

LSTM的输入有三个:当前时刻网络的输出值xt、上一时刻LSTM的输出值ht1、以及上一时刻的记忆单元向量ct1

LSTM的输出有两个:当前时刻LSTM输出值ht、当前时刻的隐藏状态向量ht、和当前时刻的记忆单元状态向量ct

注意:记忆单元c在LSTM 层内部结束工作,不向其他层输出。LSTM的输出仅有隐藏状态向量h。

  LSTM 的关键是单元状态,即贯穿图表顶部的水平线,有点像传送带。这一部分一般叫做单元状态(cell state)它自始至终存在于LSTM的整个链式系统中。

图  LSTM中的单元状态

记忆单元状态的计算公式:

1ct=ftct1+itgt

遗忘门

  ft叫做遗忘门,表示Ct1的哪些特征被用于计算Ctft是一个向量,向量的每个元素均位于(0~1)范围内。通常我们使用 sigmoid 作为激活函数,sigmoid 的输出是一个介于于(0~1)区间内的值,但是当你观察一个训练好的LSTM时,你会发现门的值绝大多数都非常接近0或者1,其余的值少之又少。

图7:LSTM的遗忘门

2ft=σ(xtWx(f)+hh1Wh(f)+b(f))

输入门

  C~t 表示单元状态更新值,由输入数据xt和隐节点ht1经由一个神经网络层得到,单元状态更新值的激活函数通常使用tanhit叫做输入门,同 ft 一样也是一个元素介于(0~1)区间内的向量,同样由xtht1经由sigmoid激活函数计算而成

LSTM的输入门和单元状态更新值的计算方式

3:it=σ(xtWx(i)+ht1Whi+b(i))

4gt=tanh(xtWx(g)+ht1Wh(g)+b(g))

输出门

  最后,为了计算预测值y^t和生成下个时间片完整的输入,我们需要计算隐节点的输出 ht

5ot=σ(xtWx(o)+ht1Wh(o)+b(o))

6ht=ottanh(ct)

  我们来看公式2、3、4、5,都是型如xWx+hWh+b的格式,因此可以整合为通过一个式子进行,如下图所示:

4 个权重(或偏置)被整合为了1 个。如此,原本单独执行4 次的仿射变换通过1 次计算即可完成,可以加快计算速度。这是因为矩阵库计算“大矩阵”时通常会更快,而且通过将权重整合到一起管理,源代码也会更简洁。

  假设WxWhb分别包含4 个权重(或偏置),此时LSTM 的计算图如下图所示。

复制代码
import tensorflow as tf
from tensorflow.keras import layers

inputs = tf.random.normal((64, 6, 10))
LSTM_layer = layers.LSTM(units=20, return_sequences=True, return_state=True)
outputs, final_memory_state, final_carry_state = LSTM_layer(inputs)
print(outputs.shape, final_memory_state.shape, final_carry_state.shape)
# (64, 6, 20) (64, 20) (64, 20)

print(len(LSTM_layer.weights))  # 3
print(LSTM_layer.weights[0].shape)  # Wx (10, 80)
print(LSTM_layer.weights[1].shape)  # Wh (20, 80)
print(LSTM_layer.weights[2].shape)  # bias (80,)
复制代码

GRU

LSTM 的参数太多,计算需要很长时间。因此,最近业界又提出了 GRU(Gated RecurrentUnit,门控循环单元)。GRU 保留了 LSTM使用门的理念,但是减少了参数,缩短了计算时间。

相对于 LSTM 使用隐藏状态和记忆单元两条线,GRU只使用隐藏状态。异同点如下:

GRU的计算图

GRU计算图,σ节点和tanh节点有专用的权重,节点内部进行仿射变换(“1−”节点输入x,输出1 − x)

2.1z=σ(xtWx(z)+ht1Wh(z)+b(z))

2.2r=σ(xtWx(r)+ht1Wh(r)+b(r))

2.3h~=tanh(xtWx+(rht1)Wh+b)

2.4ht=(1z)ht1+zh~

GRU 中进行的计算由上述 4 个式子表示(这里 xt和 ht−1 都是行向量),如图所示,GRU 没有记忆单元,只有一个隐藏状态h在时间方向上传播。这里使用rz共两个门(LSTM 使用 3 个门),r称为 reset 门,z称为 update 门。

  r(reset门)决定在多大程度上“忽略”过去的隐藏状态。根据公式2.3,如果r是 0,则新的隐藏状态h~仅取决于输入xt。也就是说,此时过去的隐藏状态将完全被忽略。

  z(update门)是更新隐藏状态的门,它扮演了 LSTM 的 forget 门和input 门两个角色。公式2.4 的(1z)ht1部分充当 forget 门的功能,从过去的隐藏状态中删除应该被遗忘的信息。zh~的部分充当 input 门的功能,对新增的信息进行加权。

 

RNN的实现

由于用TensorFlow和Keras实现RNN模型篇幅过长,我分成了两篇文章去写:

TensorFlow中实现RNN,彻底弄懂time_step

Keras实现RNN模型

RNN项目

参考文献

【书籍】深度学习进阶——自然语言处理

【colah's blog】Understanding LSTM Networks

【知乎】详解LSTM

【 easyAI】循环神经网络 – Recurrent Neural Network | RNN

【Cmd Markdown】零基础入门深度学习(5)--循环神经网络

 【IndoML】Pengenalan Long Short Term Memory (LSTM) dan Gated Recurrent Unit (GRU) – RNN Bagian 2

作者:凌逆战
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
本文章不做任何商业用途,仅作为自学所用,文章后面会有参考链接,我可能会复制原作者的话,如果介意,我会修改或者删除。

posted @   凌逆战  阅读(9772)  评论(5编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示

目录导航