RNN 网络中文本的 pack 和 pad 操作

RNN 模型一般设定固定的文本长度(text sequence length,可理解为文本序列在时间维度上的步数 time step),以保证网络输出

层数据维度的一致性。但在训练和测试时,难以保证输入文本长度的一致性,因此常常需要截断操作(即将超过预设长度的文本截断)

和 pad 操作(即对不足预设长度的文本进行补 0 填充)。

Pytorch 中,在文本数据的 transfrom 以及 RNN 网络的输入阶段,均充分考虑了 pad 操作。其主要体现在:

   (1)RNN、LSTM 和 GRU 等网络的输入数据均可为 PackedSequence 类型数据;

   (2)可通过 pad_sequence、pack_sequence、pack_padded_sequence 和 pad_packed_sequence 等操作,实现 pad 和 pack 操作。

1. pack_sequence

import torch
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]  # 三个文本序列拼接

x = pack_sequence(sequences)
print(x)

"""
PackedSequence(data=tensor([1, 5, 8, 2, 6, 9, 3, 7, 4]), batch_sizes=tensor([3, 3, 2, 1]),
               sorted_indices=None, unsorted_indices=None)
"""

   输入数据是由 tensor 列表,每个 tensor 表示一个序列数据。pack 后的返回值包括两数据。一类为 data,即压缩后的数据;

   而另一类 batch_sizes 表示每个时间步 batch 中包含的样本量。

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

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

    

2. pad_sequence

import torch
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]  # 三个文本序列拼接

x = pad_sequence(sequences)
print(x)

"""
tensor([[1, 5, 8],
        [2, 6, 9],
        [3, 7, 0],
        [4, 0, 0]])
"""

   pad 操作即是将不同长度的文本序列进行对齐的填充过程。默认情况下,参数 batch_first=False,这里指定的是输出数据的形状,

   有些函数的这个参数是用来指明输入数据的形状,注意区分。pad_sequence 输入数据的形状和 pack_sequence 是一样的。

   与 pack 操作不同,pad 操作对于 sequences 列表内的各元素长度顺序并无要求。

   观察上述 pack 和 pad 操作,返回结果均倾向于按照序列 sequece 的顺序进行输出,而将 batch 的输出顺序后置,其实这是 pytorch 中

   整个 RNN 网络的统一推荐用法,观察 RNN、LSTM 和 GRU 等网络架构,参数 batch_first 的默认值均为 False!

    

3. pad_packed_sequence

   顾名思义,这个函数的输入是 pad_sequence 函数的输出,也就是填充后的数据。

import torch
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]  # 三个文本序列拼接

x = pad_sequence(sequences, batch_first=True)    # batch_first 指定输出数据的形状
print(x)
y = pack_padded_sequence(x, lengths=[4,3,2], batch_first=True)   # batch_first 指明输入数据的形状
print(y)

"""
tensor([[1, 2, 3, 4],
        [5, 6, 7, 0],
        [8, 9, 0, 0]])
PackedSequence(data=tensor([1, 5, 8, 2, 6, 9, 3, 7, 4]), batch_sizes=tensor([3, 3, 2, 1]), 
               sorted_indices=None, unsorted_indices=None)
"""

    pack_padded_sequence 函数的作用过程可分解为如下步骤:

   (1)接收一个 padded_sequence 数据;

   (2)根据 batch_first 参数明确该数据的布局(默认为 batch_first=False);

   (3)根据 lengths 参数明确 batch 内各样本的时间步长,选择数据;注意列表内的元素必须为降序。

   (4)将上述数据按照时间维度进行压缩,得到目标的 PackedSequence 类型数据。

4. pack_padded_sequence

   pad_packed_sequence 函数即为 pack_padded_sequence 的逆操作,其在参数设定时也许注意通过 batch_first 控制返回值的维度顺序,

   同时可通过设置 total_lengths 来控制 pad 后的总步长(该值必须不小于输入 PackedSequence 数据的步长数)。

import torch
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]  # 三个文本序列拼接

x = pack_sequence(sequences)
print(x)
y = pad_packed_sequence(x, total_length=5, batch_first=True)
print(y)

"""
PackedSequence(data=tensor([1, 5, 8, 2, 6, 9, 3, 7, 4]), batch_sizes=tensor([3, 3, 2, 1]), 
               sorted_indices=None, unsorted_indices=None)
(tensor([[1, 2, 3, 4, 0],
        [5, 6, 7, 0, 0],
        [8, 9, 0, 0, 0]]), tensor([4, 3, 2]))
"""
原文地址:https://www.cnblogs.com/yanghh/p/14232960.html