[关闭]
@songying 2019-05-04T01:32:46.000000Z 字数 3300 阅读 3421

Pytorch 是如何处理变长序列的

博客文章


前言

最近在由 TensorFlow 迁移至 Pytorch, 不得不说,真的香啊。 在写模型的时候发现 Pytorch 中处理变长序列与 TensorFlow 有很大的不同, 因此此处谈谈我自己的理解。

此外, 我对 LSTM, GRU 进行了二次加工, 将对变长序列的处理封装到内部细节中,感兴趣的可以看看:NLP-Pytorch

从 LSTM 谈起[1]

首先, 注意到这里LSTM的计算公式与我们常见的LSTM有所区别,虽然区别不大,但还是要提一下,因为后面的参数初始化会有所不同:

  1. class torch.nn.LSTM(*args, **kwargs)
  2. -- 参数列表:
  3. -- input_size: x 的特征维度
  4. -- hidden_size: 隐层的特征维度
  5. -- num_layers: LSTM 层数,默认为1
  6. -- bias: 是否采用 bias 如果为False,则不采用。默认为True
  7. -- batch_first: True 则输入输出的数据格式为 [batch_size, seq_len, feature_dim],默认为False
  8. -- dropout: dropout会在除最后一层外都进行dropout 默认为0
  9. -- bidirectional: 是否采用双向,默认为False
  10. -- 输入数据:
  11. -- input: [seq_len, batch_size, input_size], 输入的特征矩阵
  12. -- h_0: [num_layers * num_directions, batch_size, hidden_size], 初始时 h 状态, 默认为0
  13. -- c_0: [num_layers * num_directions, batch_size, hidden_size], 初始时 cell 状态, 默认为0
  14. -- 输出数据:
  15. -- output: [seq_len, batch_size, num_directions * hidden_size], 最后一层的所有隐层输出
  16. -- h_n : [num_layers * num_directions, batch, hidden_size], 所有层的最后一个时刻隐层状态
  17. -- c_n : [num_layers * num_directions, batch, hidden_size], 所有层的最后一格时刻的 cell 状态
  18. -- Wb参数:
  19. -- weight_ih_l[k]: 与输入x相关的第k层权重 W 参数, W_ii, W_if, W_ig, W_io
  20. -- weight_hh_l[k]: 与上一时刻 h 相关的第k层权重参数, W_hi, W_hf, W_hg, W_ho
  21. -- bias_ih_l[k]: 与输入x相关的第k b 参数, b_ii, b_if, b_ig, b_io
  22. -- bias_hh_l[k]: 与上一时刻 h 相关的第k b 参数, b_hi, b_hf, b_hg, b_ho

需要注意的一点是, LSTM中所有的W,b 参数默认采用均匀分布 : , 有一些初始化方法能够加速收敛过程, 因此,很多情况下我们需要自己初始化这些参数,我在 NLP-Pytorch: LSTM 对LSTM 进行了简要封装。

对比 GRU [1]

同样为了更鲜明的表明参数的初始化, 这里将 GRU 搬过来:

  1. class torch.nn.GRU(*args, **kwargs)
  2. -- 参数列表:与 LSTM 的一致, 不赘述了
  3. -- 输入序列:input, h_0; LSTM 差不多,只是省略了 cell 状态
  4. -- 输出序列:output, h_n; LSTM 差不多,只是省略了 cell 状态
  5. -- Wb参数:
  6. -- weight_ih_l[k]: 与输入x相关的第k层权重 W 参数, W_ir, W_iz, W_in
  7. -- weight_hh_l[k]: 与上一时刻 h 相关的第k层权重参数, W_hr, W_hz, W_hn
  8. -- bias_ih_l[k]: 与输入x相关的第k b 参数, b_ir, b_iz, b_in
  9. -- bias_hh_l[k]: 与上一时刻 h 相关的第k b 参数, b_hr, b_hz, b_hn

与LSTM 一样, W,b参数的初始化默认采用均匀分布 :

如何处理变长序列?[2]

我们知道,在文本的处理过程中,句子的长度是不一的,对于这种数据,我们往往采用<PAD> 将每个句子扩充到一样的长度, 然后我们就可以使用LSTM或GRU来处理了。

但仔细一想, 又有些不对,我们将句子扩充,那么扩充的信息必然会对我们的结果产生影响,这与我们正常的思路完全不同,虽然我自己做比较实验表明,二者之间差距并不明显,但我个人认为这是数据集与<PAD>初始化的关系。

那么,Pytorch 中如何处理这种变长的情况,去掉<PAD> 呢,答案就是 torch.nn.utils.rnn.pack_padded_sequence()以及 torch.nn.utils.rnn.pad_packed_sequence()

压缩序列

压缩序列所使用的API为torch.nn.utils.rnn.pack_padded_sequence(), 其目的是将多余的 <PAD> 去除,获得一个干净的,最初的序列。

  1. pack_padded_sequence(...)
  2. -- 参数列表:
  3. -- input: <PAD> batch 序列
  4. -- lengths: input 中每个序列的长度
  5. -- batch_first: 如果为True input 必须是 [batch_size, seq_len, input_size], 参见LSTM
  6. -- enforce_sorted: 如果为True 那么 input 中的序列需要按照 长度递减排列
  7. -- 返回值:
  8. 一个 PackedSequence 对象

我们获得干净的序列之后,就可以将其放入 LSTM 中了, 具体可参见我的实现:NLP-Pytorch: LSTM

解压序列

我们通过 LSTM 对压缩后的序列处理后,还需要将压缩后的信息解压缩,本质上是将数据从PackedSequence 类型转化为Tensor ,此部分主要是做 Attention 的时候会用到。

  1. pad_packed_sequence(...)
  2. -- 参数列表:
  3. -- sequence 一个PackedSequence 对象
  4. -- batch_first:
  5. -- padding_value: padding 该序列的values
  6. -- total_length: Padding 到多长, 一般为None
  7. -- Returns
  8. -- output: padding 信息的序列输出
  9. -- output_lengths: 每个序列没有Padding之前的长度

最后

最后,知乎上有一个很有趣的问题:你在训练RNN的时候有哪些特殊的trick, 十分值得一看,感兴趣的可以自己做一做相关的实验,ok, 就酱。

Reference

[1][Pytorch 官方文档]](https://pytorch.org/docs/stable/nn.html)

[2]pytorch中如何处理RNN输入变长序列padding

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注