人工智能

Tensorflow RNN中使用dropout的一些坑

2019-03-12  本文已影响6262人  theoqian

使用 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的方式。

注意文献1提出的 RNN dropout 方式是对于一个序列的所有时间步,输入向量、输出向量、状态向量分别采用相同的 dropout mask,如图所示,相同颜色表示相同的 dropout mask: variational RNN dropout.png
注意如果将 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 的计算结构:

GRU.png 在对 GRU 的状态向量引入 dropout 后结构如下图,图中 h* 表示 dropout 后的状态向量。我们知道,dropout 根据 1-keep_rate 的概率生成一个 mask 向量,根据 mask 对状态向量中某些值置0,然后为了保证向量在后续与权值矩阵 W 相乘后得到的结果向量的 scale 与不 dropout 时一致,会对向量 h 中的对应 mask 非 0 的值除以 keep_rate。在我的实验中 dropout 对h中每个对应 mask 非0值除以0.8即乘以1.25。如前所述,由于变分 RNN dropout 中所有时间步的 dropout mask 都是相同的,所以对于一个长 n 的序列,状态向量 h* 中有些值在这个序列中永远是0,而另外一些值每经过一个时间步就要乘以 1/keep_rate(我的实验中是1.25),一个序列计算完后状态向量的值要乘以 (1/keep_rate)n,这些值在长序列情况下会变得非常大甚至溢出。这样就解释了为什么将 GRU 和 variational RNN dropout 结合使用的时候 loss 会变成 nan。 GRU with dropout.png
然后看看 LSTM,LSTM 的结构如下图所示。由图中结构可以看到,LSTM 的状态向量 h 每次都与矩阵相乘后再使用,这样可以保证即使每个时间步 h 的某些值会乘以 1/keep_rate,在与矩阵相乘后不会造成像 GRU 那样 h 的值呈指数上升的情况。 LSTM.png
但是在这个 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 结合使用。

总结与解决方案

参考文献

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

上一篇 下一篇

猜你喜欢

热点阅读