循环神经网络(RNN)及衍生LSTM、GRU详解
我们之前所学的全连接神经网络(DNN)和卷积神经网络(CNN),他们的前一个输入和后一个输入是没有关系的。但是当我们处理序列信息的时候,某些前面的输入和后面的输入是有关系的,比如:当我们在理解一句话意思时,孤立的理解这句话的每个词是不够的,我们需要处理这些词连接起来的整个序列;这个时候我们就需要使用到循环神经网络(Recurrent Neural Network)。
RNN在自然语言处理领域最先被使用起来,RNN可以为语言模型进行建模:
我没有完成上级布置给我的任务,所以 被开除了
让电脑来填写下划线的词最有可能的是『我』,而不太可能是『小明』,甚至是『吃饭』。
语言模型就是这样的东西:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么。
基本循环神经网络
我们首先来学习 基本的 循环神经网络,结构由 输入层、一个隐藏层和输出层 组成。
我们将上图的基本RNN结构在时间维度展开(RNN是一个链式结构,每个时间片使用的是相同的参数):
RNN时间线展开图
现在看上去就会清楚许多,这个网络在
式1是隐藏层的计算公式,它是循环层。
式2是输出层的计算公式,V是输出层的权重矩阵,g是激活函数。
隐含层有两个输入,第一是
如果反复把式1带入式2,我们将得到:
从上面可以看出,循环神经网络的输出值
双向循环神经网络
对于语言模型来说,很多时候光看前面的词是不够的,比如下面这句话:我的手机坏了,我打算 一部新的手机。 我们这个时候就需要双向循环神经网络。
从上图可以看出,双向卷积神经网络的隐藏层要保存两个值,一个A参与正向计算,另一个值A'参与反向计算。最终的输出值
现在,我们已经可以看出一般的规律:正向计算时,隐藏层的值
从上面三个公式我们可以看到,正向计算和反向计算不共享权重,也就是说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按照时间维度展开:
可以看到在
LSTM的输入有三个:当前时刻网络的输出值
LSTM的输出有两个:当前时刻LSTM输出值
注意:记忆单元
LSTM 的关键是单元状态,即贯穿图表顶部的水平线,有点像传送带。这一部分一般叫做单元状态(cell state)它自始至终存在于LSTM的整个链式系统中。
图 LSTM中的单元状态
记忆单元状态的计算公式:
遗忘门
图7:LSTM的遗忘门
输入门
LSTM的输入门和单元状态更新值的计算方式
输出门
最后,为了计算预测值
我们来看公式2、3、4、5,都是型如
4 个权重(或偏置)被整合为了1 个。如此,原本单独执行4 次的仿射变换通过1 次计算即可完成,可以加快计算速度。这是因为矩阵库计算“大矩阵”时通常会更快,而且通过将权重整合到一起管理,源代码也会更简洁。
假设
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计算图,
GRU 中进行的计算由上述 4 个式子表示(这里 xt和 ht−1 都是行向量),如图所示,GRU 没有记忆单元,只有一个隐藏状态
RNN的实现
由于用TensorFlow和Keras实现RNN模型篇幅过长,我分成了两篇文章去写:
TensorFlow中实现RNN,彻底弄懂time_step
参考文献
【书籍】深度学习进阶——自然语言处理
【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
作者:凌逆战
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
本文章不做任何商业用途,仅作为自学所用,文章后面会有参考链接,我可能会复制原作者的话,如果介意,我会修改或者删除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?