一、什么是RNN模型

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

一般单层神经网络结构:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(1)

RNN单层网络结构:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(2)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(3)

RNN模型的作用

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

下面我们将以一个用户意图识别的例子进行简单地分析:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(4)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(5)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(6)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(7)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(8)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(9)

RNN模型的分类

这里我们将从两个角度对RNN模型进行分类. 第一个角度是输入和输出的结构, 第二个角度是RNN的内部构造.

按照输入和输出的结构进行分类:

按照RNN的内部构造进行分类:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(10)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(11)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(12)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(13)

二、传统RNN模型传统RNN的内部结构图

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(14)

结构解释图:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(15)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(16)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(17)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(18)

Pytorch中传统RNN工具的使用:

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

# 导入工具包 >>> import torch >>> import torch.nn as nn >>> rnn = nn.RNN(5, 6, 1) >>> input = torch.randn(1, 3, 5) >>> h0 = torch.randn(1, 3, 6) >>> output, hn = rnn(input, h0) >>> output tensor([[[ 0.4282, -0.8475, -0.0685, -0.4601, -0.8357, 0.1252], [ 0.5758, -0.2823, 0.4822, -0.4485, -0.7362, 0.0084], [ 0.9224, -0.7479, -0.3682, -0.5662, -0.9637, 0.4938]]], grad_fn=<StackBackward>) >>> hn tensor([[[ 0.4282, -0.8475, -0.0685, -0.4601, -0.8357, 0.1252], [ 0.5758, -0.2823, 0.4822, -0.4485, -0.7362, 0.0084], [ 0.9224, -0.7479, -0.3682, -0.5662, -0.9637, 0.4938]]], grad_fn=<StackBackward>)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(19)

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

梯度消失或爆炸的危害:

三、LSTM模型LSTM的内部结构图

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(20)

结构解释图:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(21)

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

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(22)

遗忘门结构分析:

遗忘门内部结构过程演示:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(23)

激活函数sigmiod的作用:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(24)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(25)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(26)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(27)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(28)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(29)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(30)

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(31)

nn.LSTM使用示例:

# 定义LSTM的参数含义: (input_size, hidden_size, num_layers) # 定义输入张量的参数含义: (sequence_length, batch_size, input_size) # 定义隐藏层初始张量和细胞初始状态张量的参数含义: # (num_layers * num_directions, batch_size, hidden_size) >>> import torch.nn as nn >>> import torch >>> rnn = nn.LSTM(5, 6, 2) >>> input = torch.randn(1, 3, 5) >>> h0 = torch.randn(2, 3, 6) >>> c0 = torch.randn(2, 3, 6) >>> output, (hn, cn) = rnn(input, (h0, c0)) >>> output tensor([[[ 0.0447, -0.0335, 0.1454, 0.0438, 0.0865, 0.0416], [ 0.0105, 0.1923, 0.5507, -0.1742, 0.1569, -0.0548], [-0.1186, 0.1835, -0.0022, -0.1388, -0.0877, -0.4007]]], grad_fn=<StackBackward>) >>> hn tensor([[[ 0.4647, -0.2364, 0.0645, -0.3996, -0.0500, -0.0152], [ 0.3852, 0.0704, 0.2103, -0.2524, 0.0243, 0.0477], [ 0.2571, 0.0608, 0.2322, 0.1815, -0.0513, -0.0291]], [[ 0.0447, -0.0335, 0.1454, 0.0438, 0.0865, 0.0416], [ 0.0105, 0.1923, 0.5507, -0.1742, 0.1569, -0.0548], [-0.1186, 0.1835, -0.0022, -0.1388, -0.0877, -0.4007]]], grad_fn=<StackBackward>) >>> cn tensor([[[ 0.8083, -0.5500, 0.1009, -0.5806, -0.0668, -0.1161], [ 0.7438, 0.0957, 0.5509, -0.7725, 0.0824, 0.0626], [ 0.3131, 0.0920, 0.8359, 0.9187, -0.4826, -0.0717]], [[ 0.1240, -0.0526, 0.3035, 0.1099, 0.5915, 0.0828], [ 0.0203, 0.8367, 0.9832, -0.4454, 0.3917, -0.1983], [-0.2976, 0.7764, -0.0074, -0.1965, -0.1343, -0.6683]]], grad_fn=<StackBackward>)

四、LSTM优势

GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时它的结构和计算要比LSTM更简单, 它的核心结构可以分为两个部分去解析:

GRU的内部结构图和计算公式

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(32)

结构解释图:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(33)

GRU的更新门和重置门结构图:

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(34)

nn.GRU使用示例:

>>> import torch >>> import torch.nn as nn >>> rnn = nn.GRU(5, 6, 2) >>> input = torch.randn(1, 3, 5) >>> h0 = torch.randn(2, 3, 6) >>> output, hn = rnn(input, h0) >>> output tensor([[[-0.2097, -2.2225, 0.6204, -0.1745, -0.1749, -0.0460], [-0.3820, 0.0465, -0.4798, 0.6837, -0.7894, 0.5173], [-0.0184, -0.2758, 1.2482, 0.5514, -0.9165, -0.6667]]], grad_fn=<StackBackward>) >>> hn tensor([[[ 0.6578, -0.4226, -0.2129, -0.3785, 0.5070, 0.4338], [-0.5072, 0.5948, 0.8083, 0.4618, 0.1629, -0.1591], [ 0.2430, -0.4981, 0.3846, -0.4252, 0.7191, 0.5420]], [[-0.2097, -2.2225, 0.6204, -0.1745, -0.1749, -0.0460], [-0.3820, 0.0465, -0.4798, 0.6837, -0.7894, 0.5173], [-0.0184, -0.2758, 1.2482, 0.5514, -0.9165, -0.6667]]], grad_fn=<StackBackward>)

五、注意力机制

什么是注意力:

什么是注意力计算规则:

将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(35)

将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法.

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(36)

将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.

rnn模型是什么(了解RNN模型LSTM模型GRU模型)(37)

  • 说明:当注意力权重矩阵和V都是三维张量且第一维代表为batch条数时, 则做bmm运算.bmm是一种特殊的张量乘法运算.
  • bmm运算演示:

# 如果参数1形状是(b × n × m), 参数2形状是(b × m × p), 则输出为(b × n × p) >>> input = torch.randn(10, 3, 4) >>> mat2 = torch.randn(10, 4, 5) >>> res = torch.bmm(input, mat2) >>> res.size() torch.Size([10, 3, 5])

什么是注意力机制
  • 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.
  • 说明: NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型.
注意力机制的作用
  • 在解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量, 无法存储过多信息的情况.
  • 在编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).
注意力机制实现步骤
  • 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算.
  • 第二步: 根据第一步采用的计算方法, 如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接, 如果是转置点积, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接.
  • 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.

常见注意力机制的代码分析:

import torch import torch.nn as nn import torch.nn.functional as F class Attn(nn.Module): def __init__(self, query_size, key_size, value_size1, value_size2, output_size): """初始化函数中的参数有5个, query_size代表query的最后一维大小 key_size代表key的最后一维大小, value_size1代表value的导数第二维大小, value = (1, value_size1, value_size2) value_size2代表value的倒数第一维大小, output_size输出的最后一维大小""" super(Attn, self).__init__() # 将以下参数传入类中 self.query_size = query_size self.key_size = key_size self.value_size1 = value_size1 self.value_size2 = value_size2 self.output_size = output_size # 初始化注意力机制实现第一步中需要的线性层. self.attn = nn.Linear(self.query_size self.key_size, value_size1) # 初始化注意力机制实现第三步中需要的线性层. self.attn_combine = nn.Linear(self.query_size value_size2, output_size) def forward(self, Q, K, V): """forward函数的输入参数有三个, 分别是Q, K, V, 根据模型训练常识, 输入给Attion机制的 张量一般情况都是三维张量, 因此这里也假设Q, K, V都是三维张量""" # 第一步, 按照计算规则进行计算, # 我们采用常见的第一种计算规则 # 将Q,K进行纵轴拼接, 做一次线性变化, 最后使用softmax处理获得结果 attn_weights = F.softmax( self.attn(torch.cat((Q[0], K[0]), 1)), dim=1) # 然后进行第一步的后半部分, 将得到的权重矩阵与V做矩阵乘法计算, # 当二者都是三维张量且第一维代表为batch条数时, 则做bmm运算 attn_applied = torch.bmm(attn_weights.unsqueeze(0), V) # 之后进行第二步, 通过取[0]是用来降维, 根据第一步采用的计算方法, # 需要将Q与第一步的计算结果再进行拼接 output = torch.cat((Q[0], attn_applied[0]), 1) # 最后是第三步, 使用线性层作用在第三步的结果上做一个线性变换并扩展维度,得到输出 # 因为要保证输出也是三维张量, 因此使用unsqueeze(0)扩展维度 output = self.attn_combine(output).unsqueeze(0) return output, attn_weights

调用:

query_size = 32 key_size = 32 value_size1 = 32 value_size2 = 64 output_size = 64 attn = Attn(query_size, key_size, value_size1, value_size2, output_size) Q = torch.randn(1,1,32) K = torch.randn(1,1,32) V = torch.randn(1,32,64) out = attn(Q, K ,V) print(out[0]) print(out[1])

输出效果:

tensor([[[ 0.4477, -0.0500, -0.2277, -0.3168, -0.4096, -0.5982, 0.1548, -0.0771, -0.0951, 0.1833, 0.3128, 0.1260, 0.4420, 0.0495, -0.7774, -0.0995, 0.2629, 0.4957, 1.0922, 0.1428, 0.3024, -0.2646, -0.0265, 0.0632, 0.3951, 0.1583, 0.1130, 0.5500, -0.1887, -0.2816, -0.3800, -0.5741, 0.1342, 0.0244, -0.2217, 0.1544, 0.1865, -0.2019, 0.4090, -0.4762, 0.3677, -0.2553, -0.5199, 0.2290, -0.4407, 0.0663, -0.0182, -0.2168, 0.0913, -0.2340, 0.1924, -0.3687, 0.1508, 0.3618, -0.0113, 0.2864, -0.1929, -0.6821, 0.0951, 0.1335, 0.3560, -0.3215, 0.6461, 0.1532]]], grad_fn=<UnsqueezeBackward0>) tensor([[0.0395, 0.0342, 0.0200, 0.0471, 0.0177, 0.0209, 0.0244, 0.0465, 0.0346, 0.0378, 0.0282, 0.0214, 0.0135, 0.0419, 0.0926, 0.0123, 0.0177, 0.0187, 0.0166, 0.0225, 0.0234, 0.0284, 0.0151, 0.0239, 0.0132, 0.0439, 0.0507, 0.0419, 0.0352, 0.0392, 0.0546, 0.0224]], grad_fn=<SoftmaxBackward>)

,