RNN,LSTM,GRU的结构解析

  • RNN结构及代码
  • 什么是RNN模型
  • RNN模型的构造
  • RNN模型代码
  • RNN模型的优缺点
  • LSTM结构及代码
  • 什么是LSTM模型
  • LSTM的结构
  • Bi-LSTM的简单介绍
  • GRU结构及代码
  • 什么是GRU模型
  • GRU模型的结构
  • GRU使用实例
  • RNN结构及其变体就说完了,有什么问题欢迎留言。


RNN结构及代码

什么是RNN模型

RNN(Recurrent Neural Network)中文叫做循环神经网络,一般以序列为输入,通过网络内部的结构设计,有效捕捉序列之间的关系特征,一般也以序列的方式输出。

RNN单层神经网络示意图:

改进RNN模型 rnn网络模型_改进RNN模型


本文主要讲基于RNN内部结构的不同模型,读者可自行查阅输入输出对应的不同结构分类。

RNN模型的构造

传统RNN内部结构图:

改进RNN模型 rnn网络模型_自然语言处理_02


在三个时间步的展开模型来看,每一个时间步的输入由上一个时间步的隐层输出和当前时间步的输入组成,经过拼接(一般是concatenate拼接)之后,再经过一个线性层(激活函数为tanh),得到ht,它将作为下一个时间步的输入和x(t+1)一起进入下一步的结构体,以此类推。

根据结构分析出内部计算公式:

改进RNN模型 rnn网络模型_自然语言处理_03

RNN模型代码

代码:

# @Time : 2020/4/14 19:25 
# coding=utf-8
# @Author : yyyh
# @File : RNN的使用.py 
# @Software: PyCharm

# 导入相关的包
import torch
import torch.nn as nn

# 实例化RNN模型
"""
:param 5:词嵌入输入特征维度大小,和input中的5对应
:param 6:隐藏层神经元的个数,和h0中的6对应
:param 1:隐藏层数量,和h0中的1对应
"""
rnn = nn.RNN(5, 6, 2)
"""
:param 1: 样本数量
:param 3: sequence_max,和h0中的3对应
:param 5: 词嵌入输入特征维度
"""
input = torch.randn(1, 3, 5)
"""
:param 1:隐藏层数量
:param 3:sequence_max
:param 6:隐藏层神经元个数
"""
h0 = torch.randn(2, 3, 6)
"""
:param input:输入张量 x
:param h0: 隐藏层张量 h
"""
output, hn = rnn(input, h0)
# 打印输出output和hn
# 在隐藏层为1时,output和hn相同
print("output:",output)
print("hn:",hn)

输出:

output: tensor([[[-0.7867, -0.7039, -0.3834, -0.0288,  0.4341, -0.4648],
         [-0.4802, -0.3080,  0.2104, -0.0559, -0.5498, -0.7767],
         [ 0.1709,  0.3427,  0.8293, -0.1393, -0.2858,  0.5117]]],
       grad_fn=<StackBackward>)
hn: tensor([[[-0.5685, -0.3215, -0.3606, -0.7036,  0.9374,  0.9256],
         [ 0.4004,  0.2676,  0.7251, -0.3436, -0.0162, -0.2642],
         [ 0.6436,  0.6334,  0.4475,  0.1994, -0.9964,  0.0750]],

        [[-0.7867, -0.7039, -0.3834, -0.0288,  0.4341, -0.4648],
         [-0.4802, -0.3080,  0.2104, -0.0559, -0.5498, -0.7767],
         [ 0.1709,  0.3427,  0.8293, -0.1393, -0.2858,  0.5117]]],
       grad_fn=<StackBackward>)

代码参数含义已在代码片段中给出。

RNN模型的优缺点

RNN的优点:内部结构简单,计算需要的时间空间低,参数量少,对于短序列的任务表现很好。

RNN的缺点:在解决长序列之间的关联时,RNN表现很差,在进行反向传播时,由于过长的序列,导致梯度消失或梯度爆炸。

LSTM结构及代码

什么是LSTM模型

LSTM:长短时记忆结构,RNN的变体。和RNN相比,在长序列的任务情境下表现更好,有效的缓解了梯度爆炸或梯度消失现象。

LSTM的结构

LSTM 的结构相比于RNN来说更加复杂,它的结构包含了四个部分:

·遗忘门
·输入门
·细胞状态
·输出门

LSTM的内部结构图:

改进RNN模型 rnn网络模型_改进RNN模型_04


遗忘门部分结构图和计算公式

改进RNN模型 rnn网络模型_实例化_05


遗忘门结构分析

与传统的RNN结构相似,将当前时间步输入和上一个时间步隐层输出进行拼接,通过线性层变换,然后进行sigmoid函数激活,得到ft,可以将ft看做阀值,与细胞状态相乘,决定保留多少上一个细胞状态的信息,也就是遗忘上一个细胞多少信息,该ft由x(t)和h(t-1)计算得出。

输入门结构结构图和计算公式

改进RNN模型 rnn网络模型_重置_06


输入门结构分析输入门的计算公式有两个,第一个是产生输入门门值的公式,和遗忘门几乎相同,区别只在于它们之后参与的计算不同,作用和遗忘门也几乎相同,意味着输入信息有多少要进行过滤,第二个公式和RNN计算公式相同,对LSTM来说,他得到的是当前的细胞状态,而不是RNN中的隐含状态。

细胞状态更新图和计算公式

改进RNN模型 rnn网络模型_自然语言处理_07


细胞状态更新分析

没有全连接层,只是将遗忘门得到的门值和上一个时间步的细胞状态进行相乘,再加上输入门门值与当前时间步未更新的细胞状态相乘的结果,得到更新后的Ct,作为下一个时间步的输入。整个细胞状态更新过程就是对遗忘门和输入门的应用过程。

输出门的图示和公式

改进RNN模型 rnn网络模型_Time_08


输出门结构分析

输出门的公式为两个,第一个计算输出门的门值,和遗忘门、输入门计算公式一样,第二个是使用输出门门值产生隐含状态ht,输出门门值作用在更新后的细胞状态上,并做tanh激活,得到ht作为下一个时间步输入的一部分。

输出门的作用是为了产生隐含状态ht

Bi-LSTM的简单介绍

Bi-LSTM即双向LSTM,没有改变LSTM本身任何的内部结构,只是将LSTM应用两次,且方向不同,再将两次得到的LSTM结果进行拼接作为最终输出。

改进RNN模型 rnn网络模型_实例化_09


LSTM使用实例

# @Time : 2020/4/14 19:46 
# coding=utf-8
# @Author : yyyh
# @File : LSTM的使用.py 
# @Software: PyCharm

# 导入相关包
import torch.nn as nn
import torch
"""
:param 5:输入x的特征维度,词嵌入维度
:param 6:隐藏层神经元的个数
:param 2:隐藏层数量
"""
rnn = nn.LSTM(5, 6, 2)
"""
:param 1:当前批次样本个数
:param 3:当前样本的sequence_length
:param 5:词嵌入维度
"""
input = torch.randn(1, 3, 5)
"""
:param 2:隐藏层层数
:param 3:当前样本的sequence_length 
:param 6:隐藏层神经元数量
"""
h0 = torch.randn(2, 3, 6)
c0 = torch.randn(2, 3, 6)
output, (hn, cn) = rnn(input, (h0, c0))
print(output)
print(hn)
print(cn)

输出结果

tensor([[[-0.1228, -0.1415, -0.1053,  0.1872, -0.2348,  0.0737],
         [-0.1172, -0.0098, -0.1188,  0.3458, -0.1227, -0.2155],
         [ 0.0203, -0.0830,  0.5517,  0.0798, -0.0607,  0.1796]]],
       grad_fn=<StackBackward>)
tensor([[[ 0.1647, -0.3809, -0.1870,  0.3308,  0.0873,  0.3530],
         [-0.2332,  0.3570,  0.1824,  0.0929,  0.1138,  0.1632],
         [ 0.0838,  0.0606, -0.0411,  0.0227, -0.4045,  0.4157]],

        [[-0.1228, -0.1415, -0.1053,  0.1872, -0.2348,  0.0737],
         [-0.1172, -0.0098, -0.1188,  0.3458, -0.1227, -0.2155],
         [ 0.0203, -0.0830,  0.5517,  0.0798, -0.0607,  0.1796]]],
       grad_fn=<StackBackward>)
tensor([[[ 0.3699, -0.4572, -0.7117,  0.7798,  0.1936,  0.5971],
         [-0.4052,  0.9017,  0.3248,  0.1949,  0.7401,  0.1876],
         [ 0.1145,  0.0988, -0.1330,  0.0818, -0.7254,  0.5698]],

        [[-0.3671, -0.1729, -0.1263,  0.5163, -0.3227,  0.5533],
         [-0.1416, -0.0147, -0.2166,  0.5292, -0.1761, -0.4792],
         [ 0.0335, -0.0996,  0.7675,  0.1586, -0.0704,  0.6233]]],
       grad_fn=<StackBackward>)

LSTM的优点:LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸, 虽然并不能杜绝这种现象, 但在更长的序列问题上表现优于传统RNN.
缺点:由于内部结构相对较复杂, 因此训练效率在同等算力下较传统RNN低很多.

GRU结构及代码

什么是GRU模型

GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象.

GRU模型的结构

GRU的结构和计算要比LSTM更简单, 它的核心结构可以分为两个部分去解析:
·更新门
·重置门

更新门重置门的图示和公式:

改进RNN模型 rnn网络模型_改进RNN模型_10


结构说明

和之前分析过的LSTM中的门控一样, 首先计算更新门和重置门的门值, 分别是z(t)和r(t), 计算方法就是使用X(t)与h(t-1)拼接进行线性变换, 再经过sigmoid激活. 之后重置门门值作用在了h(t-1)上, 代表控制上一时间步传来的信息有多少可以被利用. 接着就是使用这个重置后的h(t-1)进行基本的RNN计算, 即与x(t)拼接进行线性变化, 经过tanh激活, 得到新的h(t). 最后更新门的门值会作用在新的h(t),而1-门值会作用在h(t-1)上, 随后将两者的结果相加, 得到最终的隐含状态输出h(t), 这个过程意味着更新门有能力保留之前的结果, 当门值趋于1时, 输出就是新的h(t), 而当门值趋于0时, 输出就是上一时间步的h(t-1).

GRU使用实例
# @Time : 2020/4/14 20:19 
# coding=utf-8
# @Author : yyyh
# @File : GRU的使用.py 
# @Software: PyCharm
# 导入相关包
import torch
import torch.nn as nn
"""
:param 5:输入x的特征维度,词嵌入维度
:param 6:隐藏层神经元的个数
:param 2:隐藏层数量
"""
rnn = nn.GRU(5, 6, 2)
"""
:param 1:当前批次样本个数
:param 3:当前样本的sequence_length
:param 5:词嵌入维度
"""
input = torch.randn(1, 3, 5)
"""
:param 2:隐藏层层数
:param 3:当前样本的sequence_length 
:param 6:隐藏层神经元数量
"""
h0 = torch.randn(2, 3, 6)
output, hn = rnn(input, h0)
print("output:",output)
print("output.shape:",output.shape)
print("hn:",hn)
print("hn.shape:",hn.shape)

输出:

output: tensor([[[-1.0669,  0.4703,  0.5340, -0.3898, -0.1216,  0.2891],
         [-0.2170, -0.6732, -0.2140,  0.1169,  0.1503,  0.4186],
         [-0.1232, -0.5851, -0.4347,  0.3280, -0.4647, -0.3623]]],
       grad_fn=<StackBackward>)
output.shape: torch.Size([1, 3, 6])
hn: tensor([[[-0.4123, -1.1749,  0.8075, -0.2456,  1.2554,  0.2781],
         [-0.2166, -0.2677, -0.2461, -0.5060, -0.0048, -0.4343],
         [-0.1977,  0.4078, -0.8181,  0.6586,  0.0210,  0.3222]],

        [[-1.0669,  0.4703,  0.5340, -0.3898, -0.1216,  0.2891],
         [-0.2170, -0.6732, -0.2140,  0.1169,  0.1503,  0.4186],
         [-0.1232, -0.5851, -0.4347,  0.3280, -0.4647, -0.3623]]],
       grad_fn=<StackBackward>)
hn.shape: torch.Size([2, 3, 6])
RNN结构及其变体就说完了,有什么问题欢迎留言。