我在Keras中如何正确地将dropout与RNN结合使用,特别是与GRU单元结合使用时感到困惑。Keras的文档引用了这篇论文(https://arxiv.org/abs/1512.05287),我理解应在所有时间步上使用相同的dropout掩码。这可以通过在定义GRU层时使用dropout参数来实现。我不理解的是:
-
为什么互联网上有许多例子,包括Keras自己的例子(https://github.com/keras-team/keras/blob/master/examples/imdb_bidirectional_lstm.py)以及Andrew Ng的Coursera序列模型课程中的“触发词检测”作业,他们显式地添加了一个dropout层“model.add(Dropout(0.5))”,在我看来,这将为每个时间步添加一个不同的掩码。
-
上述论文提到这样做是不合适的,因为这种dropout噪声在所有时间步上的累积可能会导致信号丢失以及长期记忆的丢失。但是,这些模型(在每个时间步使用不同的dropout掩码)是如何学习并表现良好的呢?
我自己训练了一个在每个时间步使用不同dropout掩码的模型,虽然我没有得到我想要的结果,但模型能够过拟合训练数据。在我看来,这否定了“噪声累积”和“信号丢失”在所有时间步上的观点(我的输入到GRU层的序列有1000个时间步)。
任何关于这种情况的见解、解释或经验都将有所帮助。谢谢。
更新:
为了更清楚地说明,我将提到Keras Dropout层的文档摘录(“noise_shape: 表示将与输入相乘的二进制dropout掩码形状的1D整数张量。例如,如果你的输入形状为(batch_size, timesteps, features),并且你希望dropout掩码在所有时间步上相同,你可以使用noise_shape=(batch_size, 1, features)”。因此,我认为可以看出,当显式使用Dropout层并且需要在每个时间步使用相同的掩码(如论文中提到的)时,我们需要编辑这个noise_shape参数,而我之前链接的例子中并没有这样做。
回答:
正如Asterisk在他的评论中解释的,循环单元内的dropout和单元输出后的dropout之间存在根本的区别。这是你问题中链接的Keras教程中的架构:
model = Sequential()model.add(Embedding(max_features, 128, input_length=maxlen))model.add(Bidirectional(LSTM(64)))model.add(Dropout(0.5))model.add(Dense(1, activation='sigmoid'))
你在LSTM完成计算后添加了一个dropout层,这意味着在这个单元中不会有更多的循环传递。想象这个dropout层是在教网络不要依赖于特定时间步的特定特征的输出,而是要概括不同特征和时间步的信息。这里的dropout与前馈架构没有区别。
Gal & Ghahramani在他们论文中(你在问题中链接的)提出的是在循环单元内部使用dropout。在那里,你在序列的时间步之间丢弃输入信息。我发现这篇博客文章对理解论文及其与Keras实现的关系非常有帮助。