pytorch中LSTM对变长句子的处理
在自然语言处理问题中,以最简单的句子分类为例(比如句子级别情感分类),来说明LSTM对变长句子的处理。(看了网上关于这部分的介绍,大抵互相复制,但感觉都没有说清楚具体怎么用,通过实践,个人总结了使用方法,写在这里)
围绕两个函数来说明处理过程:nn.utils.rnn.pack_padded_sequence()和nn.utils.rnn.pad_packed_sequence()
nn.utils.rnn.pack_padded_sequence()
函数说明:该函数共有三个参数,依次为input(输入),length(句子实际长度),batch_first(输入的第一维是否为batch_size,默认为false,通常我们都设置成true)。该函数的返回值是一个PackedSequence对象。接下来,我们详细叙述一下该函数的输入输出。
- input和length:这个参数是我们输入的句子和句子实际长度。具体来说,在一个batch中,比如我们有64个句子(通过填充到统一长度),length里保存了句子的实际长度。在送到该函数的时候,我们要求句子按照实际长度降序的顺序排列,也就是说实际长度长的句子在前,实际长度短的句子在后。举例来说,对于一个size为(4,3)的tensor a:a=torch.Tensor([[1,2,3],[1,0,0],[2,3,0],[4,0,0]])。原始的length为3,1,2,1(用0填充到统一长度),我们需要按照长度降序排列一下再送给该函数,这部分可以在主函数而不是forward函数中完成,需要用到的函数为np.argsort(),这部分可自行百度。排序后的输入和长度为a=torch.Tenson([[1,2,3],[2,3,0],[1,0,0],[4,0,0]]),length为3,2,1,1。这样处理之后才能送给该函数。(此时a的size为(4,3),我们使用unsqueeze()函数扩展最后一维使size变成(4,3,1),可以理解为batch_size=4,每个句子最长为3,1为每个单词的表示(实际中,应该是size应该为(batch_size,max_length,word_dim))
这部分需要注意的是在排序的时候,标签也要对应上。 - 输出:经过上述处理之后的句子输入到nn.utils.rnn.pack_padded_sequence()中会的到什么呢?(输入的size为(4,3,1))答案是:PackedSequence(data=tensor([[1.],[2.],[1.],[4.],[2.],[3.],[3.]]), batch_sizes=tensor([4, 2, 1]))。我们来解释一下这个结果,PackedSequence包含两个部分:data和batch_size。在第一个时间步,LSTM单元处理每个句子的第一个数即1,2,1,4,长度为4;在第二个时间步,LSTM单元处理每个句子的第二个数即2,3,长度为2(还有2个0被“压缩”了);在第三个时间步,LSTM单元处理每个句子的第三个数即3,长度为1(还有3个0被“压缩”了)。这样就很方便的理解了输出的data和batch_size!
- 此时,我们把得到的PackedSequence对象送给一个LSTM得到输出,
lstm=nn.LSTM(1, 1, batch_first=True)
out,cn=lstm(a)
此时的out是什么呢?它还是一个PackedSequence对象,为PackedSequence(data=tensor([[-0.1365],[-0.0763],[-0.1365],[-0.0081],[-0.1218],[-0.0587],[-0.0730]], grad_fn=), batch_sizes=tensor([4, 2, 1]))。这个输出和输入的PackedSequence对象是对应的。接下来我们需要“解压”该对象,需要用到的函数是nn.utils.rnn.pad_packed_sequence()
nn.utils.rnn.pad_packed_sequence()
函数说明:该函数的参数有两个参数,依次为sequence和batch_first,batch_first和nn.utils.rnn.pack_padded_sequence()一样,而sequence就是我们经过LSTM后得到的PackedSequence,那么对于刚刚的PackedSequence使用上述函数得到的是什么呢?out, length = nn.utils.rnn.pad_packed_sequence(out, batch_first=True)。得到两个返回值,结果是:tensor([[[-0.0024],[-0.0222],[-0.0447]], [[-0.0219],[-0.0446],[ 0.0000]], [[-0.0024],[ 0.0000],[ 0.0000]], [[-0.0630],[ 0.0000], [ 0.0000]]], grad_fn=
tensor([3, 2, 1, 1])。可以看到,[0.0000]是填充部分的输出,如果我们只是取最后一维,那么必然是有问题的,我们需要根据实际长度来取“最后一个”。如何取“最后一个”可以很简单的使用for循环结合实际长度来做,此部分不做赘述。
总结
在使用LSTM批处理变长序列的时候,分为以下几步:
1)按照句子实际长度降序排列句子
2)使用nn.utils.rnn.pack_padded_sequence()函数“压缩”句子
3)将“压缩”后的对象送给LSTM得到输出
4)对输出使用nn.utils.rnn.pad_packed_sequence()函数进行“解压缩”
5)根据句子实际长度来获得自己需要的部分