pytorch 中的 RNN 如何处理作为打包序列给出的填充序列?
How padded sequences given as packed sequences are dealt by RNN in pytorch?
在pytorch中,我们可以将打包序列作为RNN的输入。从official doc开始,RNN的输入可以如下。
input (seq_len, batch, input_size): tensor containing the features of the input sequence. The input can also be a packed variable length sequence.
例子
packed = torch.nn.utils.rnn.pack_padded_sequence(embedded, input_lengths)
outputs, hidden = self.rnn(packed, hidden)
outputs, output_lengths = torch.nn.utils.rnn.pad_packed_sequence(outputs)
这里,embedded
是批量输入的嵌入表示。
我的问题是,RNN中packed sequences是如何计算的?如何通过打包表示批量计算填充序列的隐藏状态?
根据this relevent question的matthew_zeng的回答:填充元素不计算输出,hidden将是最后一个有效输入后的隐藏状态。
对于第二个问题:填充序列处的隐藏状态不会被计算。
要回答这是怎么回事,让我们先看看 pack_padded_sequence
为我们做了什么:
from torch.nn.utils.rnn import pad_sequence, pad_packed_sequence, pack_padded_sequence
raw = [ torch.ones(25, 300) / 2,
torch.ones(22, 300) / 2.3,
torch.ones(15, 300) / 3.2 ]
padded = pad_sequence(raw) # size: [25, 3, 300]
lengths = torch.as_tensor([25, 22, 15], dtype=torch.int64)
packed = pack_padded_sequence(padded, lengths)
到目前为止我们随机创建了三个不同长度的张量(RNN 上下文中的时间步长),我们首先将它们填充到相同的长度,然后 packed 它。现在如果我们 运行
print(padded.size())
print(packed.data.size()) # packed.data refers to the "packed" tensor
我们将看到:
torch.Size([25, 3, 300])
torch.Size([62, 300])
显然 62 不是来自 25 * 3。所以 pack_padded_sequence
所做的只是根据我们传递给 pack_padded_sequence
的 lengths
张量保留每个批次条目的有意义的时间步长(即,如果我们将 [25, 25, 25] 传递给它,即使原始张量没有改变,packed.data
的大小仍将是 [75, 300])。简而言之,rnn 甚至不会看到带有 pack_padded_sequence
的 pad 时间步长
现在让我们看看将padded
和packed
传递给rnn
后有什么区别
rnn = torch.nn.RNN(input_size=300, hidden_size=2)
padded_outp, padded_hn = rnn(padded) # size: [25, 3, 2] / [1, 3, 2]
packed_outp, packed_hn = rnn(packed) # 'PackedSequence' Obj / [1, 3, 2]
undo_packed_outp, _ = pad_packed_sequence(packed_outp)
# return "h_n"
print(padded_hn) # tensor([[[-0.2329, -0.6179], [-0.1158, -0.5430],[ 0.0998, -0.3768]]])
print(packed_hn) # tensor([[[-0.2329, -0.6179], [ 0.5622, 0.1288], [ 0.5683, 0.1327]]]
# the output of last timestep (the 25-th timestep)
print(padded_outp[-1]) # tensor([[[-0.2329, -0.6179], [-0.1158, -0.5430],[ 0.0998, -0.3768]]])
print(undo_packed_outp.data[-1]) # tensor([[-0.2329, -0.6179], [ 0.0000, 0.0000], [ 0.0000, 0.0000]]
padded_hn
和 packed_hn
的值不同,因为 rnn 确实为 padded
计算了填充,但没有为 'packed'(PackedSequence 对象)计算填充,这也可以从最后的隐藏状态可以观察到:padded
中的所有三个批次条目都得到了非零的最后隐藏状态,即使其长度小于 25。但是对于 packed
,较短数据的最后隐藏状态是未计算(即 0)
p.s。另一个观察:
print([(undo_packed_outp[:, i, :].sum(-1) != 0).sum() for i in range(3)])
会给我们 [tensor(25), tensor(22), tensor(15)]
,它与我们输入的实际长度对齐。
在pytorch中,我们可以将打包序列作为RNN的输入。从official doc开始,RNN的输入可以如下。
input (seq_len, batch, input_size): tensor containing the features of the input sequence. The input can also be a packed variable length sequence.
例子
packed = torch.nn.utils.rnn.pack_padded_sequence(embedded, input_lengths)
outputs, hidden = self.rnn(packed, hidden)
outputs, output_lengths = torch.nn.utils.rnn.pad_packed_sequence(outputs)
这里,embedded
是批量输入的嵌入表示。
我的问题是,RNN中packed sequences是如何计算的?如何通过打包表示批量计算填充序列的隐藏状态?
根据this relevent question的matthew_zeng的回答:填充元素不计算输出,hidden将是最后一个有效输入后的隐藏状态。
对于第二个问题:填充序列处的隐藏状态不会被计算。
要回答这是怎么回事,让我们先看看 pack_padded_sequence
为我们做了什么:
from torch.nn.utils.rnn import pad_sequence, pad_packed_sequence, pack_padded_sequence
raw = [ torch.ones(25, 300) / 2,
torch.ones(22, 300) / 2.3,
torch.ones(15, 300) / 3.2 ]
padded = pad_sequence(raw) # size: [25, 3, 300]
lengths = torch.as_tensor([25, 22, 15], dtype=torch.int64)
packed = pack_padded_sequence(padded, lengths)
到目前为止我们随机创建了三个不同长度的张量(RNN 上下文中的时间步长),我们首先将它们填充到相同的长度,然后 packed 它。现在如果我们 运行
print(padded.size())
print(packed.data.size()) # packed.data refers to the "packed" tensor
我们将看到:
torch.Size([25, 3, 300])
torch.Size([62, 300])
显然 62 不是来自 25 * 3。所以 pack_padded_sequence
所做的只是根据我们传递给 pack_padded_sequence
的 lengths
张量保留每个批次条目的有意义的时间步长(即,如果我们将 [25, 25, 25] 传递给它,即使原始张量没有改变,packed.data
的大小仍将是 [75, 300])。简而言之,rnn 甚至不会看到带有 pack_padded_sequence
现在让我们看看将padded
和packed
传递给rnn
rnn = torch.nn.RNN(input_size=300, hidden_size=2)
padded_outp, padded_hn = rnn(padded) # size: [25, 3, 2] / [1, 3, 2]
packed_outp, packed_hn = rnn(packed) # 'PackedSequence' Obj / [1, 3, 2]
undo_packed_outp, _ = pad_packed_sequence(packed_outp)
# return "h_n"
print(padded_hn) # tensor([[[-0.2329, -0.6179], [-0.1158, -0.5430],[ 0.0998, -0.3768]]])
print(packed_hn) # tensor([[[-0.2329, -0.6179], [ 0.5622, 0.1288], [ 0.5683, 0.1327]]]
# the output of last timestep (the 25-th timestep)
print(padded_outp[-1]) # tensor([[[-0.2329, -0.6179], [-0.1158, -0.5430],[ 0.0998, -0.3768]]])
print(undo_packed_outp.data[-1]) # tensor([[-0.2329, -0.6179], [ 0.0000, 0.0000], [ 0.0000, 0.0000]]
padded_hn
和 packed_hn
的值不同,因为 rnn 确实为 padded
计算了填充,但没有为 'packed'(PackedSequence 对象)计算填充,这也可以从最后的隐藏状态可以观察到:padded
中的所有三个批次条目都得到了非零的最后隐藏状态,即使其长度小于 25。但是对于 packed
,较短数据的最后隐藏状态是未计算(即 0)
p.s。另一个观察:
print([(undo_packed_outp[:, i, :].sum(-1) != 0).sum() for i in range(3)])
会给我们 [tensor(25), tensor(22), tensor(15)]
,它与我们输入的实际长度对齐。