0%

RNN架构解析

RNN架构解析

认识RNN模型

什么是RNN模型

  • RNN(Recurrent Neural Network),中文称为循环神经网络,它一般以序列数据为输入,通过网络内部的结构设计有效捕捉序列之间的关系特征,一般也是以序列形式进行输出。

一般单层神经网络结构

RNN单层网络结构

以时间步对RNN进行展开后的单层网络结构

  • RNN的循环机制使模型隐层上一时间步产生的结果,能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响

RNN模型的作用

  • 因为RNN结构能够很好利用序列之间的关系,因此针对自然界具有连续性的输入序列,如人类的语言、语音等进行很好的处理,广泛应用于NLP领域的各项任务,如文本分类、情感分析、意图识别、机器翻译等。

  • 用户意图识别的例子:

    1. 用户输入了"What time is it?",首先需要对它进行基本的分词,因为RNN是按照顺序工作的,每次只接收一个单词进行处理

    2. 首先将单词"What"输送给RNN,它将产生一个输出O1

    3. 继续将单词"time"输送给RNN,但此时RNN不仅利用"time"来产生输出O2,还会使用来自上一层输出O1作为输入信息

    4. 重复这样的步骤,直到处理完所有的单词

    5. 最后,将最终的隐层输出O5进行处理来解析用户意图

RNN模型分类

  • 从两个角度对RNN模型进行分类。第一个角度是输入和输出的结构,第二个角度是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模型

传统RNN的内部结构图

结构解释:

内部结构分析:

  • 将目光集中在中间的方块部分,它的输入有两部分,分别是h(t-1)以及x(t),代表上一时间步的隐层输出,以及此时间步的输入,它们进入RNN结构体后,会”融合”到一起,这种融合我们根据结构解释可知,是将二者进行拼接,形成新的张量[x(t), h(t-1)],之后这个新的张量将通过一个全连接层(线性层),该层使用tanh作为激活函数,最终得到该时间步的输出h(t),它将作为下一个时间步的输入和x(t+1)一起进入结构体,以此类推。

  • 内部结构过程演示:

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

    \(h_t = tanh(W_t[X_t, h_{t-1}]+b_t)\)

  • 激活函数tanh的作用:

    • 用于帮助调节流神经网络的值,tanh函数将值压缩在-1和1之间

Pytorch 中传统RNN工具的使用

  • 位置:在torch.nn工具包中,通过torch.nn.RNN可调用

nn.RNN类初始化主要参数

  • input_size:输入张量x中特征维度的大小
  • hidden_size:隐层张量h中的特征维度的大小
  • num_layers:隐含层的数量
  • nonlinearity:激活函数的选择,默认是tanh

nn.RNN类实例化对象主要参数解释

  • input:输入张量x
  • h0:初始化的隐层张量h

nn.RNN使用示例

1
2
3
4
5
6
7
8
9
10
11
import torch
import torch.nn as nn
# 5:输入维度; 6:隐层神经元个数; 1:隐层数量
rnn = nn.RNN(5, 6, 1)
# 1:序列长度; 3:批次数量; 5:输入维度
input = torch.randn(1, 3, 5)
# 1:隐层数量; 3:批次数量; 6:隐层神经元个数
h0 = torch.randn(1, 3, 6)
output, hn = rnn(input, h0)
print(output)
print(hn)

tensor([[[ 0.9708, -0.7598, 0.3379, -0.3241, -0.6467, -0.9129],

[ 0.5139, -0.6625, -0.0972, -0.3982, -0.1671, -0.1925],

[ 0.4847, -0.3489, 0.7641, 0.8466, 0.7624, 0.8629]]], grad_fn=)

tensor([[[ 0.9708, -0.7598, 0.3379, -0.3241, -0.6467, -0.9129],

[0.5139, -0.6625, -0.0972, -0.3982, -0.1671, -0.1925],

[ 0.4847, -0.3489, 0.7641, 0.8466, 0.7624, 0.8629]]], grad_fn=)

传统RNN的优势

  • 由于内部结构简单,对计算资源要求低,相比之后要学习的RNN变体:LSTM和GRU模型参数总量少了很多,在短序列任务上性能和效果都表现优异

传统RNN的缺点

  • 传统RNN在解决长序列之间的关联时,通过实践,证明经典RNN表现很差,原因是在进行反向传播时,过长的序列导致梯度的计算异常,发生梯度消失或爆炸

什么是梯度消失或爆炸?

  • 根据反向传播算法和链式法则,梯度的计算可以简化为:

    \(D_n = \sigma'(z_1)w_1 \cdot \sigma'(z_2)w_2\cdots \sigma'(z_n)w_n\)

  • 其中\(\sigma\)的导数值域是固定的,在[0, 0.25]之间,而一旦公式中的\(w\)也小于1,则通过这样的公式连乘后,最终的梯度就会变得非常小,这种现象称为梯度消失。反之,如果人为的增大w的值,使其大于1,则连乘就可能造成梯度过大,称为梯度爆炸

梯度消失或爆炸的危害

  • 如果在训练过程中发生了梯度消失,权重无法更新,最终会导致训练失败;梯度爆炸所带来的梯度过大,则会大幅度更新网络参数,在极端情况下,结果会溢出(NaN值)