在详解RNN网络中文本的pack和pad操作中曾介绍了pack
和pad
的相关机制和相关函数的基本用法,本文在前文基础上,通过实例来进一步演示文本数据张量在整个计算过程中的变化。
1. 不进行pack操作的示例
import torch
import torch.nn as nn
a = torch.tensor([[1,0], [4,5]]) # B*L
torch.manual_seed(0)
ebd = nn.Embedding(10,3,padding_idx=0) # 显示进行embedding的mask
lstm = nn.LSTM(input_size=3, hidden_size=3,batch_first=True)
embedding = ebd(a)
out, (h, c) = lstm(embedding)
embeding
的结果中体现了padding_idx
的作用。
但是由于RNN的递归式结构,其隐藏层结果不光取决于此时的输入,还取决于上一时间步的隐藏层,因此此时padding
位置处的token的隐藏层结果仍有值(见下面的输出结果,分别表示out
和h
张量)
由上可见,在不进行pack操作时进行RNN计算,不光增大了计算量,而且其输出结果并非可真实采信的。当然,在后续网络中,可通过人工的mask
进行数据的屏蔽,但其比较费力,仍建议采用如下的pack操作。
2. 进行pack操作的示例
import torch
import torch.nn as nn
a = torch.tensor([[1,0], [4,5]]) # B*L
torch.manual_seed(0)
ebd = nn.Embedding(10,3) # 此时完全可以不设置padding_index
lstm = nn.LSTM(input_size=3, hidden_size=3, batch_first=True)
embedding = ebd(a)
embedding_pack = pack_padded_sequence(embedding, lengths=(a!=0).sum(dim=-1), enforce_sorted=False, batch_first=True)
out, (h, c) = lstm(embedding_pack)
out2 = pad_packed_sequence(out2, batch_first=True)
embedding_pack
的结果如下::
注意在最近版本的pytorch中,pack_padded_sequence
函数添加了enforce_sorted
参数,因此并不一定要对batch内的长度进行降序排序。
out
的结果也是个PackedSequence
对象:
我们将其利用pad_packed_sequence
展开:
返回值为一个元组,第一个元素为展开的输出张量,其中padding位置处的out
数据变为了0,第二个元素为各序列的实际文本长度。
对照下h
,可见其截断到非padding位置处,与我们的需要相符和。