RNN的变体
- 前言
- 一、长短期记忆网络(LSTM)
-
- [1.1 LSTM结构分析](#1.1 LSTM结构分析)
-
- [1.1.1 遗忘门](#1.1.1 遗忘门)
-
- [1.1.1.1 遗忘门结构图与计算公式](#1.1.1.1 遗忘门结构图与计算公式)
- [1.1.1.2 结构分析](#1.1.1.2 结构分析)
- [1.1.1.3 遗忘门的由来](#1.1.1.3 遗忘门的由来)
- [1.1.1.4 遗忘门的内部演示](#1.1.1.4 遗忘门的内部演示)
- [1.1.2 输入门](#1.1.2 输入门)
-
- [1.1.2.1 输入门结构图与计算公式](#1.1.2.1 输入门结构图与计算公式)
- [1.1.2.2 结构分析](#1.1.2.2 结构分析)
- [1.1.2.3 输入门的内部演示](#1.1.2.3 输入门的内部演示)
- [1.1.3 细胞状态](#1.1.3 细胞状态)
-
- [1.1.3.1 细胞状态结构图与计算公式](#1.1.3.1 细胞状态结构图与计算公式)
- [1.1.3.2 结构分析](#1.1.3.2 结构分析)
- [1.1.3.3 细胞状态的内部演示](#1.1.3.3 细胞状态的内部演示)
- [1.1.4 输出门](#1.1.4 输出门)
-
- [1.1.4.1 输出门的结构和计算公式](#1.1.4.1 输出门的结构和计算公式)
- [1.1.4.2 结构分析](#1.1.4.2 结构分析)
- [1.1.4.3 输出门的内部演示](#1.1.4.3 输出门的内部演示)
- [1.2 Bi-LSTM介绍](#1.2 Bi-LSTM介绍)
-
- [1.2.1 Bi-LSTM结构示意图](#1.2.1 Bi-LSTM结构示意图)
- [1.2.2 Bi-LSTM结构分析](#1.2.2 Bi-LSTM结构分析)
- [1.3 代码演示](#1.3 代码演示)
- [1.4 LSTM 的优缺点](#1.4 LSTM 的优缺点)
- 二、门控循环单元(GRU)
-
- [2.1 概述](#2.1 概述)
- [2.2 GRU内部结构图和计算公式](#2.2 GRU内部结构图和计算公式)
- [2.3 结构分析](#2.3 结构分析)
- [2.4 Bi-GRU介绍](#2.4 Bi-GRU介绍)
- [2.5 代码演示](#2.5 代码演示)
- [2.6 GRU的优缺点](#2.6 GRU的优缺点)
- 总结
前言
- 传统 RNN 对长序列的语义捕捉效果不好,当反向传播的时候,会发生梯度消失或者爆炸的现象。
- 为了解决这种现象,人们创造了 LSTM 模型和 GRU 模型来解决长期信息保存和短期输入缺失的问题
一、长短期记忆网络(LSTM)
- 结构示意图
- 它的核心结构可以分为四个部分去解析:
- 遗忘门
- 输入门
- 细胞状态
- 输出门
1.1 LSTM结构分析
1.1.1 遗忘门
1.1.1.1 遗忘门结构图与计算公式
1.1.1.2 结构分析
- 首先将当前时间步的输入 x t x_t xt 与上一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1 拼接得到 [ x t , h t − 1 ] [ x_t,h_{t-1}] [xt,ht−1]
- 然后通过一个全连接层也就是乘以一个参数矩阵 W f W_f Wf 再加上偏置
- 最后通过 sigmoid 函数进行激活得到 f t f_t ft
1.1.1.3 遗忘门的由来
- 因为最后得到的 f t f_t ft 将作用到上一层的细胞状态上,代表遗忘过去的多少信息
- 因为遗忘门的门值是由 x t x_t xt , h t − 1 h_{t-1} ht−1 计算得来的,因此整个公式意味着根据当前时间步和上一个时间步隐含状态来决定遗忘掉上一层细胞状态所携带的过往信息
1.1.1.4 遗忘门的内部演示
1.1.2 输入门
1.1.2.1 输入门结构图与计算公式
1.1.2.2 结构分析
- 第一个公式是产生输入门门值 i t i_t it 的公式
- 与遗忘门公式几乎相同,区别只是在于他们之后要作用的目标上
- 第二个公式是产生当前的细胞状态 C ^ t \hat C_t C^t:
- 与传统的 RNN 内部结构计算相似
1.1.2.3 输入门的内部演示
1.1.3 细胞状态
1.1.3.1 细胞状态结构图与计算公式
1.1.3.2 结构分析
- 第一步:细胞状态的更新就是将刚刚得到的遗忘门门值与上一个时间步输出的细胞状态 C t − 1 C_{t-1} Ct−1 相乘
- 第二步:输入门门值 i t i_t it 与当前时间步未更新的细胞状态 C ^ t \hat C_t C^t 相乘
- 将前两步得到的结果进行相加得到当前时间步的细胞状态 C t C_t Ct 给下一个时间步用
1.1.3.3 细胞状态的内部演示
1.1.4 输出门
1.1.4.1 输出门的结构和计算公式
1.1.4.2 结构分析
- 第一个公式是产生输出门门值 o t o_t ot 的公式
- 与遗忘门、输入门公式几乎相同,区别只是在于他们之后要作用的目标上
- 第二个公式是产生当前时间步的隐藏状态 h t h_t ht:
- 输出门门值与到经过 tanh 激活后的当前时间步的细胞状态相乘得到当前时间步的隐藏状态 h t h_t ht
1.1.4.3 输出门的内部演示
1.2 Bi-LSTM介绍
- Bi-LSTM 即双向 LSTM,它没有改变 LSTM 本身任何的内部结构,只是将 LSTM 应用两次且方向不同 ,再将两次得到的 LSTM 结果进行拼接作为最终输出
1.2.1 Bi-LSTM结构示意图
1.2.2 Bi-LSTM结构分析
- 我们看到图中对"我爱中国"这句话或者叫这个输入序列,进行了从左到右和从右到左两次 LSTM 处理,将得到的结果张量进行了拼接作为最终输出
- 这种结构能够捕捉语言语法中一些特定的前置或后置特征,增强语义关联,但是模型参数和计算复杂度也随之增加了一倍,一般需要对语料和计算资源进行评估后决定是否使用该结构
1.3 代码演示
演示代码如下:
python
import torch
from torch import nn
def dm_my_LSTM():
"""
input_size: 输入张量 x中特征维度的大小
hidden_size: 隐层张量 h中特征维度的大小
num_layers: 隐含层的数量
batch_first: 是否选择 在实例化 LSTM后 接受 参数的时候, 把批次大小batch_size放在前面 如果为True, 则使用; 默认不使用
当 batch_first=True时:
输入数据的维度顺序为(batch_size, sequence_length, input_size)
第一个维度是批量大小 (batch_size), 即一次处理的数据样本数量
第二个维度是序列长度(sequence_length), 即每个样本中的时间步或序列元素数量
第三个维度是输入维度(input_size), 即每个时间步的输入特征数量。
当 batch_first=False时:
输入数据的维度顺序为(sequence_length, batch_size, input_size)。
bidirectional: 是否选择使用双向 LSTM, 如果为True, 则使用; 默认不使用.
"""
# LSTM的参数含义 (input_size, hidden_size, num_layers)
lstm = nn.LSTM(5, 6, 1, batch_first=True)
"""
input: 输入张量 x
h0: 初始化的隐层张量 h
c0: 初始化的细胞状态张量 c
"""
# input参数含义 (batch_size, sequence_length, input_size)
input = torch.randn(4, 3, 5)
# 初始化的 h0 和 c0 可以不用传
# h0 = torch.randn(1, 3, 6)
# c0 = torch.randn(1, 3, 6)
# output, (hn, cn) = lstm(input, (h0, c0))
output, (hn, cn) = lstm(input)
print('输出结果是:', output)
print('最后的隐藏状态是:', hn)
print('最后的细胞状态是:', cn)
if __name__ == '__main__':
dm_my_LSTM()
1.4 LSTM 的优缺点
- LSTM 优势
- LSTM 的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸,虽然并不能杜绝这种现象,但在更长的序列问题上表现优于传统 RNN
- LSTM缺点
- 由于内部结构相对较复杂,因此训练效率在同等算力下较传统RNN低很多
二、门控循环单元(GRU)
2.1 概述
- GRU(Gated Recurrent Unit)也称门控循环单元结构,它也是传统RNN的变体,同LSTM一样能够有效捕捉长序列之间的语义关联,缓解梯度消失或爆炸现象
- 同时它的结构和计算要比LSTM更简单,它的核心结构可以分为两个部分去解析:
- 更新门
- 重置门
2.2 GRU内部结构图和计算公式
2.3 结构分析
- 重置门
- 上一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1 与当前时间步 x t x_t xt 的输入经过 sigmoid 激活得到 r t r_t rt
- 上一步得到的 r t r_t rt 与上一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1相乘得到的结果还有当前时间步 x t x_t xt 的输入,一起经过 tanh 激活函数得到重置过的隐藏状态 h ^ t \hat h_t h^t
- 更新门
- 上一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1 与当前时间步 x t x_t xt 的输入经过 sigmoid 激活得到 z t z_t zt
- 最后 1 − z t 1-z_t 1−zt 乘以上一个时间步的隐藏状态 h t − 1 h_{t-1} ht−1 再加上 z t z_t zt乘以重置门得到的 h ^ t \hat h_t h^t的结果作为当前时间步的隐藏状态 h t h_t ht 输出
2.4 Bi-GRU介绍
- Bi-GRU 与 Bi-LSTM 的逻辑相同,都是不改变其内部结构,而是将模型应用两次且方向不同,再将两次得到的 LSTM 结果进行拼接作为最终输出
- 具体参见 1.2 Bi-LSTM介绍
2.5 代码演示
演示代码如下:
python
import torch
from torch import nn
def dm_my_GRU():
"""
input_size: 输入张量 x中特征维度的大小
hidden_size: 隐层张量 h中特征维度的大小
num_layers: 隐含层的数量
batch_first: 是否选择 在实例化 GRU后 接受 参数的时候, 把批次大小batch_size放在前面 如果为True, 则使用; 默认不使用
当 batch_first=True时:
输入数据的维度顺序为(batch_size, sequence_length, input_size)
第一个维度是批量大小 (batch_size), 即一次处理的数据样本数量
第二个维度是序列长度(sequence_length), 即每个样本中的时间步或序列元素数量
第三个维度是输入维度(input_size), 即每个时间步的输入特征数量。
当 batch_first=False时:
输入数据的维度顺序为(sequence_length, batch_size, input_size)。
bidirectional: 是否选择使用双向 GRU , 如果为True, 则使用; 默认不使用.
"""
rnn = nn.GRU(5, 6, 2, batch_first=True)
# input参数含义 (batch_size, sequence_length, input_size)
# 因为使用了 batch_first=True
input = torch.randn(1, 3, 5)
# 可以传 h0 也可以不传, 实例化的GRU对象会自动创建一个 h0
# h0 = torch.randn(2, 3, 6)
# output, hn = rnn(input, h0)
output, hn = rnn(input)
print('GRU的输出结果是:', output)
print('GRU的最后输出的隐层张量是:', hn)
if __name__ == '__main__':
dm_my_GRU()
2.6 GRU的优缺点
- 优点
- GRU 和 LSTM 作用相同,在捕捉长序列语义关联时,能有效抑制梯度消失或爆炸,效果都优于传统 RNN 且计算复杂度相比 LSTM 要小
- 缺点
- GRU 仍然不能完全解决梯度消失问题,同时其作用 RNN 的变体,有着 RNN 结构本身的一大弊端,即不可并行计算,这在数据量和模型体量逐步增大的未来,是 RNN 发展的关键瓶颈
总结
- 以上就是我们总结的 RNN 的变体------LSTM 和 GRU 大致介绍。