Deep learning “我们为什么要这样做?”;“包装”;PyTorch中的序列?

Deep learning “我们为什么要这样做?”;“包装”;PyTorch中的序列?,deep-learning,pytorch,recurrent-neural-network,tensor,zero-padding,Deep Learning,Pytorch,Recurrent Neural Network,Tensor,Zero Padding,我试图复制,但我想我首先需要理解为什么我们需要“打包”序列 我理解为什么我们需要“填充”它们,但为什么需要“打包”(通过pack\u padded\u sequence)呢 任何高层解释都将不胜感激 我也偶然发现了这个问题,下面是我的答案 在训练RNN(LSTM或GRU或香草RNN)时,很难对可变长度序列进行批处理。例如:如果大小为8的批次中的序列长度为[4,6,8,5,4,3,7,8],则将填充所有序列,这将导致8个长度为8的序列。您最终将进行64次计算(8x8次),但只需进行45次计算。此外

我试图复制,但我想我首先需要理解为什么我们需要“打包”序列

我理解为什么我们需要“填充”它们,但为什么需要“打包”(通过
pack\u padded\u sequence
)呢


任何高层解释都将不胜感激

我也偶然发现了这个问题,下面是我的答案

在训练RNN(LSTM或GRU或香草RNN)时,很难对可变长度序列进行批处理。例如:如果大小为8的批次中的序列长度为[4,6,8,5,4,3,7,8],则将填充所有序列,这将导致8个长度为8的序列。您最终将进行64次计算(8x8次),但只需进行45次计算。此外,如果您想做一些奇特的事情,比如使用双向RNN,那么仅通过填充就很难进行批处理计算,并且最终可能会进行比所需更多的计算

相反,PyTorch允许我们打包序列,内部打包序列是两个列表的元组。一个包含序列的元素。元素按时间步长交错(见下面的示例),其他元素包含每个序列的大小以及每个步骤的批量大小。这有助于恢复实际序列,并告诉RNN每个时间步的批量大小。@Aerin已经指出了这一点。这可以传递给RNN,它将在内部优化计算

我可能在某些方面不清楚,所以请告诉我,我可以补充更多的解释

下面是一个代码示例:

 a = [torch.tensor([1,2,3]), torch.tensor([3,4])]
 b = torch.nn.utils.rnn.pad_sequence(a, batch_first=True)
 >>>>
 tensor([[ 1,  2,  3],
    [ 3,  4,  0]])
 torch.nn.utils.rnn.pack_padded_sequence(b, batch_first=True, lengths=[3,2])
 >>>>PackedSequence(data=tensor([ 1,  3,  2,  4,  3]), batch_sizes=tensor([ 2,  2,  1]))

除了乌曼的回答之外,我发现这一点很重要

pack\u padded\u sequence
返回的元组中的第一项是包含压缩序列的数据(张量)-张量。第二项是一个整数张量,包含每个序列步骤的批量大小信息

然而,这里重要的是第二项(批次大小)表示批次中每个序列步骤的元素数量,而不是传递给
pack\u padded\u sequence
的不同序列长度

例如,给定数据
abc
x
:class:
PackedSequence
将包含带有
batch\u size=[2,1,1]

我使用了包装填充顺序,如下所示

packed_embedded = nn.utils.rnn.pack_padded_sequence(seq, text_lengths)
packed_output, hidden = self.rnn(packed_embedded)
其中,text_length是填充前单个序列的长度,序列根据给定批次内的长度降序排序

您可以查看一个示例


我们进行打包,这样RNN在处理序列时不会看到不需要的填充索引,这会影响整体性能

上述答案很好地解决了问题为什么。我只想添加一个例子,以便更好地理解
pack\u padded\u sequence
的用法

让我们举个例子 注:
pack\u padded\u sequence
要求批次中的排序序列(按序列长度的降序排列)。在下面的示例中,序列批次已经排序,以减少混乱。访问以全面实施

首先,我们创建一批2个不同序列长度的序列,如下所示。我们这批总共有7种元素

  • 每个序列的嵌入大小为2
  • 第一个序列的长度为:5
  • 第二个序列的长度为:2
导入火炬
seq_batch=[火炬张量([[1,1],
[2, 2],
[3, 3],
[4, 4],
[5, 5]]),
火炬张量([[10,10],
[20, 20]])]
seq_lens=[5,2]
我们点击
seq_batch
以获得长度等于5(批次中的最大长度)的序列批次。现在,新批次共有10个元素

#填充顺序批次
填充顺序批次=torch.nn.utils.rnn.pad顺序(顺序批次,批次优先=真)
"""
>>>填充顺序批次
张量([[1,1],,
[ 2,  2],
[ 3,  3],
[ 4,  4],
[ 5,  5]],
[[10, 10],
[20, 20],
[ 0,  0],
[ 0,  0],
[ 0,  0]]])
"""
然后,我们将
填充的\u seq\u批次
打包。它返回两个张量的元组:

  • 第一个是包含序列批中所有元素的数据
  • 第二个是
    batch_size
    ,它将通过步骤说明元素之间的相互关系
#包装填充的顺序批次
包装顺序批次=火炬.nn.utils.rnn.pack\u填充顺序(填充顺序批次,长度=顺序镜头,批次第一=真)
"""
>>>包装的_顺序_批次
打包顺序(
数据=张量([[1,1],
[10, 10],
[ 2,  2],
[20, 20],
[ 3,  3],
[ 4,  4],
[ 5,  5]]), 
批次大小=张量([2,2,1,1,1]))
"""
现在,我们将元组
packed_seq_batch
传递给Pytorch中的递归模块,如RNN、LSTM。这只需要在复发模块中进行
5+2=7
计算

lstm=nn.lstm(输入大小=2,隐藏大小=3,批处理大小=True)
输出,(hn,cn)=lstm(packed_seq_batch.float())#传递浮点张量而不是长张量。
"""
>>>输出#打包顺序
PackedSequence(数据=张量(
[[-3.6256e-02、1.5403e-01、1.6556e-02],
[-6.3486e-05、4.0227e-03、1.2513e-01],
[-5.3134e-02、1.6058e-01、2.0192e-01],
[-4.3123e-05、2.3017e-05、1.4112e-01],
[-5.9372e-02、1.0934e-01、4.1991e-01],
[-6.0768e-02、7.0689e-02、5.9374e-01],
[-6.0125e-02,4.6476e-02,7.1243e-01]],梯度fn=),批次尺寸=张量([2,2,1,1,1]))
>>>hn
张量([-6.0125e-02,4.6476e-02,7.1243e-01],
[-4.3123e-05,2.3017e-05,1.4112e-01]],梯度fn=),
>>>cn
张量([-1.8826e-01,5.8109e-02,1.2209e+00],
 9-mult  8-add 
 8-mult  7-add 
 6-mult  5-add 
 4-mult  3-add 
 3-mult  2-add 
 2-mult  1-add
---------------
32-mult  26-add
   
------------------------------  
#savings: 22-mult & 22-add ops  
          (32-54)  (26-48)