使用 tensorflow 中的 DropoutWrapper 引发的问题
最近炼(tiao)丹(can)的时候遇到了 RNN 模型过拟合比较严重的问题,当时只是在 RNN 的输入特征加了 dropout。于是尝试在 RNN 的状态向量中也引入 dropout,具体方法可以查看参考文献1,tensorflow 也根据此文献实现了针对 RNN 的 dropout,函数如下:
class DropoutWrapper(RNNCell):
"""Operator adding dropout to inputs and outputs of the given cell."""
def __init__(self, cell, input_keep_prob=1.0, output_keep_prob=1.0,
state_keep_prob=1.0, variational_recurrent=False,
input_size=None, dtype=None, seed=None,
dropout_state_filter_visitor=None):
其中三个 keep_prob 分别决定 RNN 的输入、输出、状态向量的 dropout 概率。我最初没有用文献1提出的在 RNN 内部使用 dropout,所以只是把 input_keep_prob 设小于1的概率。然后在引入 RNN dropout 时,将 input_keep_prob 和 state_keep_prob 都设置为 小于1的值,并且把 variational_recurrent 设置为 True,此参数决定了是否使用文献1的方式。
注意如果将 state_keep_prob 设置为小于1的值,一般都会把 variational_recurrent 设置为 True,使用上述方式对状态进行 dropout。否则,每个时间步的状态进行随机的 dropout 会导致 RNN 几乎无法记录长期依赖特征,这样的 dropout 反而会使 RNN 性能变差。
当时使用的是 GRUCell,是普通 RNN 的改进版。兴高采烈地对 GRUCell 引入了 DropoutWrapper,state_keep_prob 设为 0.8,variational_recurrent 设为 True,然后开始训练。结果一脸懵逼,loss 直接变成了 nan,根据以往的经验猜测是发生了梯度爆炸。接着,我把 state_keep_prob 设为 1.0 即不引入状态的 dropout,看是否是这里引起的,果然在这样修改之后 loss 又正常下降了。最后猜测是不是 GRU 的特殊结构导致进行这样的 dropout 时会有问题,于是将 GRUCell 替换成了 LSTMCell,state_keep_prob 改回0.8,没想到这回 loss 正常下降了!总结下来是:tensorflow 的 variational_recurrent dropout 与 LSTM 结合能正常使用,但是与 GRU 结合却有问题。
发现问题根结
由于不确定是代码的 bug 还是算法结构的问题,所以 Google 一下,发现果然有大牛已经发现了问题。大牛在参考文献3 tensorflow 库的 issues 里提出代码和算法结构存在的问题。
下图是 GRU 的计算结构:
然后看看 LSTM,LSTM 的结构如下图所示。由图中结构可以看到,LSTM 的状态向量 h 每次都与矩阵相乘后再使用,这样可以保证即使每个时间步 h 的某些值会乘以 1/keep_rate,在与矩阵相乘后不会造成像 GRU 那样 h 的值呈指数上升的情况。
但是在这个 issue 里面大牛还是指出了 LSTM 在当时用这样的 dropout 有问题,于是看了这个 issue 关闭时关联的修改 commit(参考文献4)。发现当时tensorflow 在实现 variational rnn dropout 时没有严格遵守文献1的方法,它对 LSTM 的记忆状态 c 也进行了类似的 dropout,这样就导致了 c 的值会想 GRU 的 h 那样指数爆炸。
最后,tensorflow 在参考文献4的 commit 里面修改了这个 bug,去掉了对 c 的 dropout,但是仍然没有解决 GRU 的问题。所以目前可以将 LSTM 和 variational rnn dropout 结合使用,但不能将 GRU 与 variational rnn dropout 结合使用。
总结与解决方案
- 由于 GRU 与 LSTM 结构差异,variantional RNN dropout 可以很好地在 LSTM 中使用,但是不能在 GRU 中使用,variantional RNN dropout 有这样的局限性。
- 由于 tensorflow 1.4.0 之前的版本在 DropoutWrapper 中错误的实现了 variantional RNN dropout,所以当时在 LSTM 中使用也会有问题,但是在 1.4.0 版本中已经解决了,参考文献4。
-
对于 GRU,可以尝试参考文献2中提出的 dropout 方式,该方式不会对状态向量进行 dropout,只对一些门控进行 dropout,所以这样的方式适用于 GRU 和 LSTM。如图所示,左右两边分别是在 LSTM 中进行 variantional RNN dropout 和门控dropout的结构,虚线表示 dropout 连接。只是 tensorflow 中没有实现右边这种 dropout,可以参考文献5自己实现。
参考文献
1、《A Theoretically Grounded Application of Dropout in Recurrent Neural Networks》
2、《Recurrent Dropout without Memory Loss》
3、https://github.com/tensorflow/tensorflow/issues/11650
4、https://github.com/tensorflow/tensorflow/commit/cb3314159fe102419289d394246d7ac9c2a422c1
5、https://github.com/stas-semeniuta/drop-rnn