目录
一、认识RNN
二、RNN模型分类
三、传统RNN模型
3.1 结构分析
3.2 Pytorch构建RNN模型
3.3 优缺点
一、认识RNN
RNN(Recurrent Neural Network),中文称作循环神经网络,一般以序列数据为输入,通过网络内部的结构设计有效捕捉序列之间的关系特征,一般也是以序列形式进行输出
一般单层神经网络结构:
RNN单层网络结构:
以时间步对RNN进行展开后的单层网络结构:
RNN的循环机制使模型隐层上一时间步产生的结果,能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响
因为RNN结构能够很好利用序列之间的关系,因此针对自然界具有连续性的输入序列,如人类的语言、语音等进行很好的处理,广泛应用于NLP领域的各项任务,如文本分类、情感分析、意图识别、机器翻译等
第一步:用户输入了"What time is it ?",首先需要进行分词,因为RNN是按照顺序工作的,每次只接收一个单词进行处理
第二步:先将单词"What"输送给RNN,将产生一个输出O1
第三步:继续将单词"time"输送给RNN,但此时RNN不仅仅利用"time"来产生输出O2,还会使用来自上一层隐层输出O1作为输入信息
第四步:重复上述步骤,直到处理完所有单词,将隐层输出O5进行处理来解析用户意图
二、RNN模型分类
按照输入和输出的结构进行分类:
- N vs N - RNN
- N vs 1 - RNN
- 1 vs N - RNN
- N vs M - RNN
按照RNN的内部构造进行分类:
- 传统RNN
- LSTM
- Bi-LSTM
- GRU
- Bi-GRU
N vs N - RNN
RNN最基础的结构形式,最大的特点就是:输入和输出序列是等长的。由于这个限制的存在,使其适用范围比较小,可用于生成等长度的合辙诗句
N vs 1 - RNN
有时候要处理的问题输入是一个序列,而要求输出是一个单独的值而不是序列,应该怎样建模呢?只要在最后一个隐层输出h上进行线性变换就可以了,大部分情况下,为了更好的明确结果,还要使用 sigmoid 或者 softmax 进行处理。这种结构经常被应用在文本分类问题上
1 vs N - RNN
若输入不是序列而输出为序列的情况怎么处理呢?最常采用的一种方式就是使该输入作用于每次的输出之上。这种结构可用于将图片生成文字任务等
N vs M - RNN
这是一种不限输入输出长度的RNN结构,由编码器和解码器两部分组成,两者的内部结构都是某类RNN,也被称为 seq2seq 架构。输入数据首先通过编码器,最终输出一个隐含变量c,之后最常用的做法是使用这个隐含变量c作用在解码器进行解码的每一步上,以保证输入信息被有效利用
seq2seq 架构最早被提出应用于机器翻译,因为其输入输出不受限制,如今也是应用最广的RNN模型结构。在机器翻译、阅读理解、文本摘要等众多领域都进行了非常多的应用实践
三、传统RNN模型
3.1 结构分析
中间的方块部分,其输入有两部分,分别是 h(t-1) 以及 x(t),代表上一时间步的隐层输出,以及此时间步的输入,进入 RNN 结构体后,会"融合"到一起(将二者进行拼接, 形成新的张量 [x(t),h(t-1)]),之后这个新的张量将通过一个全连接层(线性层),该层使用 tanh 作为激活函数,最终得到该时间步的输出 h(t),将作为下一个时间步的输入和 x(t+1) 一起进入结构体。以此类推
内部计算公式:
激活函数tanh的作用:用于帮助调节流经网络的值,将值压缩在-1和1之间
在默写地方可以看到下面这个公式,本质上是等价的
表示输入数据的权重
表示输入数据的偏置
表示输入隐藏状态的权重
表示输入隐藏状态的偏置
最后对输出的结果使用 tanh 激活函数进行计算,得到该神经元的输出
循环网络层可以有多个神经元
依次将 "你爱我" 三个字分别送入到每个神经元进行计算,假设词嵌入时 "你爱我" 的维度为 128,经过循环网络 "你爱我" 三个字的词向量维度就会变成 4。循环神经网络的的神经元个数会影响到输出的数据维度
3.2 Pytorch构建RNN模型
nn.RNN类初始化主要参数解释:
- input_size:输入张量x中特征维度的大小
- hidden_size:隐层张量h中特征维度的大小
- num_layers:隐含层的数量
- nonlinearity:激活函数的选择,默认是tanh
nn.RNN类实例化对象主要参数解释:
- input:输入张量x
- h0:初始化的隐层张量h
RNN 层输入的数据为三个维度:(seq_len,batch_size,input_size)
import torch
import torch.nn as nn
# 输入单个数据
def test01():
# seq_len, batch_size, input_size
inputs = torch.randn(1, 1, 128)
# 隐藏层 num_layers, batch_size, hidden_size
hn = torch.zeros(1, 1, 256)
# input_size 输入词向量的维度
# hidden_size 隐藏层的大小, 隐藏层的神经元个数, 影响最终输出结果的维度
rnn = nn.RNN(input_size=128, hidden_size=256)
outputs, hn = rnn(inputs, hn)
print("outputs shape", outputs.shape)
# outputs shape torch.Size([1, 1, 256])
print("hidden shape", hn.shape)
# hidden shape torch.Size([1, 1, 256])
# 输入句子
def test02():
# seq_len, batch_size, input_size
inputs = torch.randn(8, 1, 128)
hn = torch.zeros(1, 1, 256)
rnn = nn.RNN(input_size=128, hidden_size=256)
outputs, hn = rnn(inputs, hn)
print("outputs shape", outputs.shape)
# outputs shape torch.Size([8, 1, 256])
print("hidden shape", hn.shape)
# hidden shape torch.Size([1, 1, 256])
# 输入批量数据
def test03():
# seq_len, batch_size, input_size
inputs = torch.randn(8, 32, 128)
hn = torch.zeros(1, 32, 256)
rnn = nn.RNN(input_size=128, hidden_size=256)
outputs, hn = rnn(inputs, hn)
print("outputs shape", outputs.shape)
# outputs shape torch.Size([8, 32, 256])
print("hidden shape", hn.shape)
# hidden shape torch.Size([1, 32, 256])
if __name__ == "__main__":
# test01()
# test02()
test03()
import torch
import torch.nn as nn
def main():
# seq_len, batch_size, input_size
inputs = torch.randn(1, 3, 5)
# num_layers, batch_size, hidden_size
hn = torch.zeros(1, 3, 6)
rnn = nn.RNN(input_size=5, hidden_size=6, num_layers=1)
outputs, hn = rnn(inputs, hn)
print(outputs.shape)
print(outputs)
print(hn.shape)
print(hn)
if __name__ == "__main__":
main()
# torch.Size([1, 3, 6])
# tensor([[[-0.0967, -0.5609, 0.5251, 0.8019, -0.4680, -0.3090],
# [-0.5468, -0.9248, 0.7486, -0.1423, -0.8194, -0.5981],
# [ 0.7297, -0.3720, 0.2796, 0.6966, -0.0226, -0.1303]]],
# grad_fn=<StackBackward0>)
# torch.Size([1, 3, 6])
# tensor([[[-0.0967, -0.5609, 0.5251, 0.8019, -0.4680, -0.3090],
# [-0.5468, -0.9248, 0.7486, -0.1423, -0.8194, -0.5981],
# [ 0.7297, -0.3720, 0.2796, 0.6966, -0.0226, -0.1303]]],
# grad_fn=<StackBackward0>)
3.3 优缺点
传统RNN的优势
由于内部结构简单,对计算资源要求低,相比RNN变体:LSTM和GRU模型参数总量少了很多,在短序列任务上性能和效果都表现优异
传统RNN的缺点
传统RNN在解决长序列之间的关联时, 通过实践,证明经典RNN表现很差,原因是在进行反向传播的时候,过长的序列导致梯度的计算异常,发生梯度消失或爆炸
seq_len是多少就需要进行多少次链式求导,更新权重时使用的是所有时间步梯度的总和
梯度消失或爆炸
根据反向传播算法和链式法则,梯度的计算可以简化为以下公式
其中 sigmoid 的导数值域是固定的,在 [0,0.25] 之间,而一旦公式中的 w 也小于1,那么通过这样的公式连乘后,最终的梯度就会变得非常非常小,这种现象称作梯度消失。反之,若人为的增大w的值,使其大于1,那么连乘够就可能造成梯度过大,称作梯度爆炸
梯度消失或爆炸的危害:
若在训练过程中发生了梯度消失,权重无法被更新,最终导致训练失败。梯度爆炸所带来的梯度过大,大幅度更新网络参数,在极端情况下,结果会溢出(NaN值)