最近在使用Keras和Pytorch处理时间序列数据,在变长数据的输入处理上踩了很多坑。一般的通用做法都需要先将一个batch中的所有序列padding到同一长度,然后需要在网络训练时屏蔽掉padding的值。在pytorch和keras中都有各自对padding值的处理,简单做一下总结。
Keras
- 使用Masking层
Keras中自带的屏蔽padding值的方式,在网络结构比较简单时使用很方便。
model = Sequential()
model.add(Masking(mask_value=0., input_shape=(timesteps, features)))
model.add(LSTM(32))
缺点是一些层(Conv1D、Global mean pooling或用户自己实现的层)不支持Masking机制,导致无法compile模型。
2. 添加Masking支持
既然Keras原生的层不支持Masking机制,我们可以重新复现该层,并使其支持Masking。
具体需要继承keras.layers.Layer类,在实现时设置self.supports_masking =True,并实现并修改compute_mask和compute_output_shape函数,使其支持Mask的传递。
具体可以参考https://github.com/CyberZHG/keras-self-attention/blob/master/keras_self_attention/seq_self_attention.py中是如何在Attention中实现Mask支持的
3. 使用keras-trans-mask
无意中发现了这个工具,具体效果没试过,但是看介绍使用起来还比较方便。
Pytorch
- pack sequence
同样是Pytorch中标准的处理变长输入的操作,常规步骤是pad_sequence -> pack_padded_sequence -> RNN -> pad_packed_sequence
具体可以参考这个回答,对各个函数解释的很详细。
尹相楠:PyTorch 训练 RNN 时,序列长度不固定怎么办?zhuanlan.zhihu.com然而这种方法存在缺点,即如果我们要对RNN内部的运算进行修改时(如自己实现LSTM或GRU),就没办法直接使用pack操作了,这时候就需要用到下面的方法
2. 重新实现AutogradRNN,加入pack支持
类似于Keras,我们同样可以自定义RNN,使其支持pack_padded_sequence输入。然而这一操作过于复杂,需要从AutogradRNN开始重构。
根据官方论坛和github上的issue,目前还没有办法灵活地自定义RNNCell(似乎自定义的RNNCell训练慢也与此有关,现在的官方RNN实现和Cudnn耦合程度过高)。
因为这个方法太过复杂,所以我并没有仔细研究,有兴趣的可以自己参考下面代码实现。
https://github.com/huggingface/torchMoji/blob/master/torchmoji/lstm.pygithub.com通用方法
- 按长度划分Batch
既然问题出在同一个batch中序列长度不一致,那么按照序列长度划分batch就没有这一问题了。然而这一方法存在一些缺点/隐患:
- batch大小不一致,一些batch中样本过少,导致训练很慢
- 划分训练集/验证集很麻烦,尤其是K折交叉验证
- 可能会导致batch之间样本分布不一致,会产生performance上的微妙影响
最后一条我也不是特别确定,只不过在实验时发现了可能会出现这一问题,欢迎大佬解释。
2. 手动屏蔽loss
无论是Masking层还是Pack sequence,本质上都是防止padding操作产生错误的梯度影响网络训练,那么我们只需在计算损失函数时将padding对应的timestep输出和target均置0即可。
个人理解pytorch中的pack sequence操作只是为了提升计算效率,真正屏蔽padding仍然需要在最后计算损失函数时将对应的timestep梯度置0。
以上就是对Keras和Pytorch中处理RNN变长序列输入的方法总结,希望能对他人有所帮助。如果有哪些地方我的理解有误,请及时纠正。