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

RNN单层网络结构

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

- RNN的循环机制使模型隐层上一时间步产生的结果,能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响
RNN模型的作用
因为RNN结构能够很好利用序列之间的关系,因此针对自然界具有连续性的输入序列,如人类的语言、语音等进行很好的处理,广泛应用于NLP领域的各项任务,如文本分类、情感分析、意图识别、机器翻译等。
用户意图识别的例子:

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

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

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

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

最后,将最终的隐层输出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 | import torch |
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值)

