【Pytorch】详解RNN网络中文本的pack和pad操作

1. 引言

RNN模型一般设定固定的文本长度(text sequence length,可理解为文本序列在时间维度上的步数 time step),以保证网络输出层数据维度的一致性。但在训练和测试时,难以保证输入文本长度的一致性,因此常常需要截断操作(即将超过预设长度的文本截断)和pad操作(即对不足预设长度的文本进行补0填充)。pad操作需满足:
(1)pad后,不足预设长度的文本用相同特征维度的0填充;
(2)pad的部分不参与forward和backward计算。

Pytorch中,在文本数据的transfrom以及RNN网络的输入阶段,均充分考虑了pad操作。其主要体现在:
(1)RNN、LSTM和GRU等网络的输入数据均可为PackedSequence类型数据;
(2)可通过pad_sequencepack_sequencepack_padded_sequencepad_packed_sequence等操作,实现pad和pack操作。

2. pack和pad操作

那么,究竟pad和pack操作对原始数据会有何影响?下面通过一个简单的示例来体现。

from torch.nn.utils.rnn import pack_sequence, pad_sequence,pad_packed_sequence, pack_padded_sequence, 

text1 = torch.tensor([1,2,3,4])    # 可视为有4个文字的样本
text2 = torch.tensor([5,6,7])  # 可视为有3个文字的样本
text3 = torch.tensor([8,9])    # 可视为有2个文字的样本
sequences = [text1, text2, text3]  # 三个文本序列
2.1 pack操作
[Input]  pack_sequence(sequences)
[Output] PackedSequence(data=tensor([1, 5, 8, 2, 6, 9, 3, 7, 4]), batch_sizes=tensor([3, 3, 2, 1]))

pack操作将原来的二维数据(batch*sequence)进行了压缩,但其排列是按照列(即sequence的顺序)进行排列,每个时间步一次性输出batch上的所有样本。
【Pytorch】详解RNN网络中文本的pack和pad操作pack后的返回值包括两数据。一类为data,即压缩后的数据;而另一类batch_sizes表示每个时间步,batch中包含的样本量。
拿本例来说,因为总共有4个时间步(即文本序列长度为4),所以batch_sizes的长度为4。在第1、2个时间内步,所有样本均有编码,所有对应值为3,而在第三个时间步,只有前两个样本有编码,所以对应值为2。

值的注意的是,sequences列表内的各元素长度必须按照降序排列,也就是越长的文本应放在前面,整个Batch*Sequence矩阵为上三角阵。

前文提到的RNN网络中可以接收的Input数据可以为PackedSequence类型数据,即是类似于这里的返回值。

2.2 pad操作
[Input]  pad_sequence(sequences)
[Output] tensor([[1, 5, 8],
        		[2, 6, 9],
        		[3, 7, 0],
        		[4, 0, 0]])

[Input]  pad_sequence(sequences, batch_first=True)
[Output] tensor([[1, 2, 3, 4],
        		[5, 6, 7, 0],
        		[8, 9, 0, 0]])

pad操作即是将不同长度的文本序列进行对齐的填充过程。默认情况下,参数batch_first=False,其返回值的第一个维度将变成sequence,而第二个维度才为batch。当然,可以通过设置batch_first=True,使得返回值的第一个维度为batch,从而保持与输入值的一致性。
【Pytorch】详解RNN网络中文本的pack和pad操作
pack操作不同,pad操作对于sequences列表内的各元素长度顺序并无要求。

2.3 关于batch顺序的思考

观察上述pack和pad操作,返回结果均倾向于按照序列sequece的顺序进行输出,而将batch的输出顺序后置,其实这是pytorch中整个RNN网络的统一推荐用法,观察RNNLSTMGRU等网络架构,参数batch_first的默认值均为False

这里简单对比下文本数据在batch_first=True下的数据维度 [Batch, Sequence, Features] 和在batch_first=False下的**[Sequence, Batch, Features]**两种数据排列方式的异同。

我们可以将第一个维度视为循环维度。对于前者,其循环项为Batch内的每一个样本,因此无法同时喂入RNN网络处理;而对于后者,其循环项为时间步,可以一次性将每个时间步内的全量样本喂入RNN网络处理。所以后者这种cross-batch的方式更为推荐。不过pytorch内做了处理,无论将batch_first设为True或者False,其内部计算时均采用每个时间步cross-batch的并行方式,但需要注意网络模型的batch_first设置应与输入数据的batch维度保持一致性。

3. pack_padded_sequence和pad_packed_sequence

因为RNN网络可以接受的是PackedSequence类型数据(通过pack操作实现),而pad操作又可以实现不等长文本的填充对齐,所以自然会想到将两个操作联合起来,这就是pytorch提供的pack_padded_sequencepad_packed_sequence功能。

3.1· pack_padded_sequence

顾名思义,将经pad后的文本序列在做pack,从而实现对文本缺失位置的填0和维度压缩。

[Input]  pack_padded_sequence(pad_sequence(sequences,batch_first=True),lengths=[4,3,3], batch_first=True)
[Output] PackedSequence(data=tensor([1, 5, 8, 2, 6, 9, 3, 7, 0, 4, 0]), batch_sizes=tensor([3, 3, 3, 2]))

【Pytorch】详解RNN网络中文本的pack和pad操作
pack_padded_sequence函数的作用过程可分解为如下步骤:
(1)接收一个padded_sequence数据;
(2)根据batch_first参数明确该数据的布局(默认为batch_first=False);
(3)根据lengths参数明确batch内各样本的时间步长,选择数据;
(4)将上述数据按照时间维度进行压缩,得到目标的PackedSequence类型数据
右上可见,在pack_padded_sequence函数有两个重要参数:

  • batch_first:用于明确输入的padded_sequence数据的布局型式
  • lengths:用于明确batch内各样本截取的时间长度列表,注意列表内的元素必须为降序

再看一个例子:

[Input]  pack_padded_sequence(pad_sequence(sequences,batch_first=True),lengths=[3,2,2,2])
[Output] PackedSequence(data=tensor([1, 2, 3, 4, 5, 6, 7, 0, 8]), batch_sizes=tensor([4, 4, 1]))

是否和想象的不一样?下面来图解下其具体的变换过程:
【Pytorch】详解RNN网络中文本的pack和pad操作
其与之前例子的最大区别在于batch_first取默认值False,因此即使传进来的数据本身是Batch*Sequence的布局,但对于pack_padded_sequence函数会将其认为是Sequence*Batch的,因此其lenghts参数的长度必须为4(错误的认为是4个样本),而返回的batch_sizes的长度为3(错误的认为只有3个时间步长)!

总体来看,在使用pack_padded_sequence需要牢记各参数的意义(见下图),其中最重要的就是要保证pad_sequencebatch_first参数与pack_padded_sequencebatch_first参数的一致性!
【Pytorch】详解RNN网络中文本的pack和pad操作

3.2· 再看pack操作

观察pack_sequence源码,不难发现,其本质就是调用了pack_padded_sequence函数:

def pack_sequence(sequences):
	return pack_padded_sequence(pad_sequence(sequences), [v.size(0) for v in sequences])

注意到其在padpack阶段都未设置batch_first参数(即batch_first=False),而lenghts设为各文本的真实长度,所以保证了pack_sequence的有效性。

3.3· pad_packed_sequence

pad_packed_sequence函数即为pack_padded_sequence的逆操作,其在参数设定时也许注意通过batch_first控制返回值的维度顺序,同时可通过设置total_lengths来控制pad后的总步长(该值必须不小于输入PackedSequence数据的步长数)。

[Input] pad_packed_sequence(pack_sequence(sequences),total_length=5,batch_first=True)
[Output] (tensor([[1, 2, 3, 4, 0],
         [5, 6, 7, 0, 0],
         [8, 9, 0, 0, 0]]), tensor([4, 3, 2]))

4. 小结

在RNN网络中,文本的pad操作用于各文本长度的对其;而pack操作用于实现文本序列数据的压缩。在使用时,需要注意如下事项:
(1)参数batch_first用于设定文本数据的布局,在进行pad、pack以及RNN网络训练时,需结合输入端和输出端的要求,进行明确和统一;
(2)参数bacth_sizes指沿着时间维度,每个时间步的所包含的样本数量;
(3)参数lengths指沿着样本维度,每个样本需要截断的时间步长;
(4)在实际操作中,最常用的是通过pad_packed_sequenceh和pack_padded_sequence实现对文本的填充和相互转化。

[References]
1.pytorch中LSTM的细节分析理解

【Pytorch】详解RNN网络中文本的pack和pad操作【Pytorch】详解RNN网络中文本的pack和pad操作 guofei_fly 发布了62 篇原创文章 · 获赞 11 · 访问量 1万+ 私信 关注
上一篇:Python GUI编程(Tkinter)CheckButton多选框控件


下一篇:x86_64处理器的指针赋值是原子操作吗?