Pytorch中的RNN及变种模型与变长序列的处理

2022 年 1 月 17 日 极市平台
↑ 点击 蓝字  关注极市平台

作者 | 小新 
来源 | https://lhyxx.top/ 
编辑 | 极市平台
本文仅作学术分享,版权归原作者所有,侵删。

极市导读

 

本文则通过总结最近对pytorch中RNN模型的使用,来从如何编码使用RNN模型的角度,来着力于提升实际动手操作的能力。 >>加入极市CV技术交流群,走在计算机视觉的最前沿

对于 RNN 模型的原理说明,已经是非常熟悉了,网上也有很多详细的讲解文章。本文就不赘述 RNN 模型的数学原理了,而是从实际代码实现与使用的角度来阐述。毕竟,“原理我都懂了,但是就是不会用”,应该是很多同学的通病。理论和实际应用还是有一定的gap的。

本文则通过总结最近对pytorch中RNN模型的使用,来从如何编码使用RNN模型的角度,来着力于提升实际动手操作的能力。

本文主要内容:

  • RNN模型(包括GRU以及LSTM)的使用说明
  • 处理变长序列的方法以及一些小技巧
  • pack_padded_sequence 与 pad_packed_sequence
  • 使用RNN进行文本处理的基本步骤

RNN模型(包括GRU以及LSTM)的使用说明

torch.nn.RNN

RNN原理

基本RNN计算公式如下:

其中 是时刻 的隐状态 (hidden state) , 时刻的输入, 时刻的状态。

因此,简单来说一句话:RNN就是根据当前时刻的输入和上一时 刻的状态求当前时刻的状态,就可以简化成一个函数:

参数说明

  • 模型参数

    • input_size:输入向量维度
    • hidden_size:隐层状态维度
    • num_layers:RNN层数
    • nonlinearity:使用哪种非线性激活函数。[tanh, relu],Default:tanh
    • bias:bool, 如果为False,则不使用偏置项。Default: True
    • batch_first: bool, 如果为True,则输入形状为(batch, seq, feature)。默认为False,因此RNN的默认输入形状为(seq, batch, feature)
    • dropout:float, 指定dropout率,Default: 0
    • bidirectional:是否为双向。Default:False
  • 输入说明

    • input:Tensor,形状(seq_len, batch, input_size)。或者是使用 torch.nn.utils.rnn.pack_padded_sequence()进行pack过的对象。
    • h_0:(num_layers * num_directions, batch, hidden_size)。如果RNN初始状态没有指定,则默认为全零张量。如果bidirectional为True,则num_directions=2,否则为1。
  • 输出说明

    • output:(seq_len, batch, num_directions * hidden_size)。输出 最后一层每个step的隐层特征。如果输入是 torch.nn.utils.rnn.PackedSequence对象,则输出也是经过packed的对象,需要使用 torch.nn.utils.rnn.pack_sequence() 给变回Tensor。如果指定 batch_first=True,则输出形状为(batch, seq_len, num_directions * hidden_size)。
    • h_n:(num_layers * num_directions, batch, hidden_size)。输出 每一层最后一个step的隐层特征
  1. 对于RNN模型的输出output,可以使用 output.view(seq_len, batch, num_directions, hidden_size)来分离方向维度,第0维是前向,第1维是反向。
    对于RNN模型的隐层状态h_n,可以使用 h_n.view(num_layers, num_directions, batch, hidden_size)来分离层数维度和方向维度。
  2. 对于双向RNN来说,前向传播中,最后一个step是最后时刻的输出,即 output[-1, :, :]。而对于反向传播中,第0个step是最后时刻的状态,即 output[0, :, :]

RNN用例

rnn = nn.RNN(10202)
input = torch.randn(5310)
h0 = torch.randn(2320)
output, hn = rnn(input, h0)

torch.nn.GRU

GRU原理

GRU计算公式如下:

GRU中增加了两个门控装置,分别是reset 和 update 门,分别对应 则是经过门控之前的下一时刻的状态,然后将下一时刻的状态 与上一时刻的状态 通过reset门和update门进行加劝分配得到最终的下一时刻的状态

公式中 * 表示Hadamard积, 表示sigmoid函数。根据公式,我们可以将三个门控分别看作三个函数:

至于这些函数应该如何实现和设计,就都是使用神经网络自己去根据数据学习拟合的了,在训练的过程中不断调整函数中的参数,从而最终学到合适的函数。这也是神经网络的强大之处,人们只需要指定变量之间的关系,至于他们到底有什么关系,就交给神经网络自己去根据数据拟合了,只要有足够大规模的训练数据即可。

参数说明

GRU模型与RNN在使用上可以说完全一致。基本参数可以参见RNN部分的参数说明。

  • 模型参数

    • input_size
    • hidden_size
    • num_layers
    • bias
    • batch_first
    • dropout
    • bidirectional
  • 输入参数

    • input :(seq_len, batch, input_size)
    • h_0 :(num_layers * num_directions, batch, hidden_size)
  • 输出参数

    • output :(seq_len, batch, num_directions * hidden_size)
    • h_n :(num_layers * num_directions, batch, hidden_size)

GRU用例

rnn = nn.RNN(10202)
input = torch.randn(5310)
h0 = torch.randn(2320)
output, hn = rnn(input, h0)

torch.nn.LSTM

LSTM原理

LSTM计算公式如下:

LSTM中增加了三个门控装置, 分别是 input, forget, output 门, 分别对应 。公式中 表示Hadamard积, 表示sigmoid 函数。

根据公式,我们可以将三个门控分别看作三个函数:

也就是说, 三个门控装置都是根据输入 和上一时刻的隐状态 决定的。 是不经过门控时的下一时刻的状态。得到三个门控信号以及不经门控时的下一时刻的状态 后,更新cell状态,也就是上一时刻的cell状态经过forget门来控制遗忘部分内容,下一时刻的状态经过输入门控制输入部分内容,共同得到下一时刻的cell状态。最后cell状态经过output门控制输出部分内容,从而输出

参数说明

LSTM模型的基本参数与RNN和GRU相同。稍微有些不同的地方在于模型的输入与输出。LSTM模型除了每个step的隐状态 之外,还有每个step的cell状态 。cell状态与输出的hidden状态上面公式已经解释了,也就是cell的状态并没有直接输出,而是通过一个输出门来控制输出哪些内容。

  • 模型参数

    • input_size
    • hidden_size
    • num_layers
    • bias
    • batch_first
    • dropout
    • bidirectional
  • 输入参数

    • input :(seq_len, batch, input_size)
    • h_0 :(num_layers * num_directions, batch, hidden_size),初始状态。若不提供则默认为全零。
    • c_0 :(num_layers * num_directions, batch, hidden_size),初始cell状态。若不提供则默认全零。
  • 输出参数

    • output :(seq_len, batch, num_directions * hidden_size),最后一层每个step的输出特征。
    • h_n :(num_layers * num_directions, batch, hidden_size),每一层的最后一个step的输出特征。
    • c_n :(num_layers * num_directions, batch, hidden_size),每一层的最后一个step的cell状态。

LSTM用例

rnn = nn.LSTM(10202)
input = torch.randn(5310)
h0 = torch.randn(2320)
c0 = torch.randn(2320)
output, (hn, cn) = rnn(input, (h0, c0))

处理变长序列

在处理文本数据时,通常一个batch中的句子长度都不一样。
例如如下几个句子

[it is a lovely day]
[i love chinese food]
[i love music]

对应词典
{pad:0, it:1is:2, a:3, lovely:4, day:5, i:6, love:7, chinese:8, food:9, music:10}

而pytorch中Tensor一定是所有向量的维度都相同的。因此在处理变长序列时,需要进行以下步骤:

首先,将所有文本padding成固定长度。

[12345]
[67890]
[671000]

然后将单词转化为one_hot的形式,变为(batch,seq_len,vocabsize)形状的Tensor。

到这里,所有句子都padding成了相同的长度,但是现在还不能直接送到RNN中,因为句子中有大量的0(PAD),这些PAD也送入RNN中的话,也会影响对句子的计算过程。为了排除这些PAD的影响,pytorch提供了两个函数torch.nn.utils.rnn.pack_padded_sequencetorch.nn.utils.rnn.pad_packed_sequence。下面介绍一下这两个函数的使用。

pack and pad

torch.nn.utils.rnn.pack_padded_sequence(input, lengths, batch_first=False, enforce_sorted=True)

  • 参数
    • input: Tensor, (seq_len, batch, *),*表示可以是任意维度,如果是one-hot表示,则是vocabsize维度,如果是其他embedding表示,则是对应embedding的维度。这里输入的是padding之后得到的Tensor,因此seq_len都是固定长度的。
    • lengths:Tensor, batch中每个句子的真实长度。
    • batch_first:第一维度是否是batch
    • enforce_sorted

torch.nn.utils.rnn.pad_packed_sequence(sequence, batch_first=False, padding_value=0.0, total_length=None)

  • 参数
    • sequence:batch to pad
    • batch_first:if True, the output will be in B x T x * format.
    • padding_value:values for padded elements.
    • total_length:if not None, the output will be padded to have length total_length

在实际使用中,pack_padded_sequence函数将padding之后的Tensor作为输入,pack_padded_sequence输出一个PackedSequence对象,其中Tensor中padding的部分都去掉了,也就是只保留了序列的真实长度。

然后经过RNN模型或者双向RNN模型,得到输出。

之后再利用pad_packed_sequence函数将RNN的输出结果变回来。

一个使用示例如下:

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
import numpy as np

# padding函数
x: [[w1, w2,...], [w1, w2, ...], ...]
def padding(x):
    maxlen = max([len(l) for l in x])
    for sen in x:
        if len(sen) < maxlen:
            sen.extend([0] * (maxlen-len(sen)))
    return x

# 句子单词映射到onehot表示
# V: 单词表, padded_tokens:padding之后的输入
def id2onehot(V, padded_tokens):
    onehot = np.eye((len(V)))
    embeddings = []
    for sen in padded_tokens:
        sen_embdding = []
        for i,tokenid in enumerate(sen):
            sen_embdding.append(onehot[tokenid].tolist())
        embeddings.append(sen_embdding)
    # print(embeddings)
    return torch.FloatTensor(embeddings)

V = {'PAD':0'a':1'b':2'c':3'd':4}
sentences = ['abcd''d''acb']
sen_lens = [len(x) for x in sentences]

tokens = []
for sen in sentences:
    token = []
    for c in sen:
        token.append(V[c])
    tokens.append(token)

padded_tokens = padding(tokens)
X = id2onehot(V, padded_tokens)
# print(X)

torch.random.manual_seed(10)
# 定义一个双向lstm网络层
lstm = nn.LSTM(53, num_layers=1, batch_first=True, bidirectional=True)  

X = X.float()
# 压紧数据,去掉padding部分
packed = pack_padded_sequence(X, torch.tensor(sen_lens), batch_first=True, enforce_sorted=False)
print(packed)

# 通过lstm进行计算,得到的结果也是压紧的
output, hidden = lstm(packed)

# 解压,恢复成带padding的形式
encoder_outputs, lenghts = pad_packed_sequence(output, batch_first=True)  
print(encoder_outputs)

上面的例子中,输入的句子长度是没有经过排序的,输入到pack_padded_sequence函数的输入句子并不是按照长度排序的。网上很多教程都说必须要将输入句子按照长度进行排序,然后输入到pack_padded_sequence中,之后再把顺序变回来。

但是我看pytorch官方手册里,pack_padded_sequence函数实际上有一个参数enforce_sorted的,该参数默认是True。如果该参数为True,则输入的句子应该按照长度顺序排序。而如果是False的话,实际上输入句子不排序也可以。那么什么情况下需要将该参数设为True呢?手册上是这么写的:

For unsorted sequences, use enforce_sorted = False. If enforce_sorted is True, the sequences should be sorted by length in a decreasing order, i.e. input[:,0] should be the longest sequence, and input[:,B-1] the shortest one. enforce_sorted = True is only necessary for ONNX export.

即只有使用ONNX export时,必须将该参数设为True。也就是说,平常使用的时候,不是必须设为True的,我们将该参数设为False,就可以直接输入无序的句子了,不用再手动对其排序,之后再变回来了。这样省事多了。

如果觉得有用,就请分享到朋友圈吧!

△点击卡片关注极市平台,获取 最新CV干货

公众号后台回复“transformer”获取最新Transformer综述论文下载~


极市干货
课程/比赛: 珠港澳人工智能算法大赛 保姆级零基础人工智能教程
算法trick 目标检测比赛中的tricks集锦 从39个kaggle竞赛中总结出来的图像分割的Tips和Tricks
技术综述: 一文弄懂各种loss function 工业图像异常检测最新研究总结(2019-2020)


CV技术社群邀请函 #

△长按添加极市小助手
添加极市小助手微信(ID : cvmart4)

备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)


即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群


每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~


觉得有用麻烦给个在看啦~   
登录查看更多
1

相关内容

RNN:循环神经网络,是深度学习的一种模型。
12篇顶会论文,深度学习时间序列预测经典方案汇总!
专知会员服务
53+阅读 · 2022年4月11日
【NeurIPS 2021】流形上的注意力机制:规范等变的Transformer
深度学习激活函数全面综述论文
专知会员服务
70+阅读 · 2021年10月1日
【ICML2021】具有线性复杂度的Transformer的相对位置编码
专知会员服务
24+阅读 · 2021年5月20日
【ICML2020-华为港科大】RNN和LSTM有长期记忆吗?
专知会员服务
74+阅读 · 2020年6月25日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
实践教程 | 浅谈 PyTorch 中的 tensor 及使用
极市平台
1+阅读 · 2021年12月14日
基于Pytorch的动态卷积复现
极市平台
2+阅读 · 2021年11月7日
深度学习Pytorch框架Tensor张量
极市平台
0+阅读 · 2021年11月1日
你有哪些深度学习(rnn、cnn)调参的经验?
七月在线实验室
10+阅读 · 2019年3月27日
三次简化一张图:一招理解LSTM/GRU门控机制
机器之心
15+阅读 · 2018年12月18日
基于LSTM深层神经网络的时间序列预测
论智
21+阅读 · 2018年9月4日
基础 | GRU神经网络
黑龙江大学自然语言处理实验室
27+阅读 · 2018年3月5日
干货|从LSTM到Seq2Seq
全球人工智能
15+阅读 · 2018年1月9日
完全图解RNN、RNN变体、Seq2Seq、Attention机制
AI研习社
12+阅读 · 2017年9月5日
【深度学习基础】4. Recurrent Neural Networks
微信AI
16+阅读 · 2017年7月19日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
1+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
2+阅读 · 2008年12月31日
Arxiv
0+阅读 · 2022年4月19日
Vision-and-Language Pretrained Models: A Survey
Arxiv
3+阅读 · 2022年4月15日
Disentangled Information Bottleneck
Arxiv
12+阅读 · 2020年12月22日
Arxiv
19+阅读 · 2018年10月25日
Arxiv
22+阅读 · 2018年8月30日
VIP会员
相关VIP内容
12篇顶会论文,深度学习时间序列预测经典方案汇总!
专知会员服务
53+阅读 · 2022年4月11日
【NeurIPS 2021】流形上的注意力机制:规范等变的Transformer
深度学习激活函数全面综述论文
专知会员服务
70+阅读 · 2021年10月1日
【ICML2021】具有线性复杂度的Transformer的相对位置编码
专知会员服务
24+阅读 · 2021年5月20日
【ICML2020-华为港科大】RNN和LSTM有长期记忆吗?
专知会员服务
74+阅读 · 2020年6月25日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
相关资讯
实践教程 | 浅谈 PyTorch 中的 tensor 及使用
极市平台
1+阅读 · 2021年12月14日
基于Pytorch的动态卷积复现
极市平台
2+阅读 · 2021年11月7日
深度学习Pytorch框架Tensor张量
极市平台
0+阅读 · 2021年11月1日
你有哪些深度学习(rnn、cnn)调参的经验?
七月在线实验室
10+阅读 · 2019年3月27日
三次简化一张图:一招理解LSTM/GRU门控机制
机器之心
15+阅读 · 2018年12月18日
基于LSTM深层神经网络的时间序列预测
论智
21+阅读 · 2018年9月4日
基础 | GRU神经网络
黑龙江大学自然语言处理实验室
27+阅读 · 2018年3月5日
干货|从LSTM到Seq2Seq
全球人工智能
15+阅读 · 2018年1月9日
完全图解RNN、RNN变体、Seq2Seq、Attention机制
AI研习社
12+阅读 · 2017年9月5日
【深度学习基础】4. Recurrent Neural Networks
微信AI
16+阅读 · 2017年7月19日
相关基金
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
1+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
2+阅读 · 2008年12月31日
相关论文
Arxiv
0+阅读 · 2022年4月19日
Vision-and-Language Pretrained Models: A Survey
Arxiv
3+阅读 · 2022年4月15日
Disentangled Information Bottleneck
Arxiv
12+阅读 · 2020年12月22日
Arxiv
19+阅读 · 2018年10月25日
Arxiv
22+阅读 · 2018年8月30日
Top
微信扫码咨询专知VIP会员