之前面的博客中,我们已经描述了
基本的RNN模型
。但是基本的RNN模型有一些缺点难以克服。其中梯度消失问题(Vanishing Gradients)最难以解决。为了解决这个问题,GRU(Gated Recurrent Unit)神经网络应运而生。本篇博客将描述GRU神经网络的工作原理。GRU主要思想来自下面两篇论文:
Cho et al., 2014. On the Properties of Neural Machine Translation: Encoder-Decoder Approaches
Chung et al., 2014. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling
一、梯度消失(Vanishing Gradients)
梯度消失(Vanishing Gradients)是深度学习中一个很重要的问题。即在一个比较深层次的网络中,梯度的计算会随着层数的增加接近于0,导致网络参数无法更新。与之相对应的还有个问题是梯度爆炸(Exploding Gradients),即梯度相乘结果太大,最终计算得到了NaN值(变量过大导致溢出)。与梯度消失相比,梯度爆炸相对容易解决。一般的做法是,当梯度的结果大于某个值的时候对所有的梯度重新调整(rescaling gradients),然后继续运行。但是梯度消失这个问题就相对难一点。而标准的递归神经网络就很难解决梯度消失的问题。而GRU就是为了解决RNN的梯度消失问题。
为了解决标准RNN的梯度消失问题,GRU提出使用两个向量,即更新门(update gate)和重置门(reset gate)的概念,用来决定什么样的信息应该被传递给输出。它可以保存很久之前的信息,也会去掉不相关的信息。我们举个例子(来自吴恩达Sequence Models的课程)。
The
cat
, which already ate …,
was
full.
注意到这个句子开始提了一个单数名词
cat
,后面接的是
was
。而中间的这些单词其实对这个
was
是没起到作用的。因此实际上,深度学习在学习
was
这个单词的时候,只需要记住
cat
就可以了。下面我们看GRU如何解决这个问题。
首先,我们回顾一下标准RNN中一个RNN-CELL里面的处理,如下图所示:
每个RNN-CELL的输入都包含了前一阶段的输出a^{< t-1>}a<t−1>和该阶段的数据x^{< t>}x<t>。首先根据这两个变量计算a^{< t>}a<t>,然后根据a^{< t>}a<t>计算输出的标签\hat{y}^{< t>}y^<t>。我们可以看到,标准的RNN使用aa变量来传递保存前面的信息。这里就会导致梯度消失和保存了很多不相关的信息了。
而GRU中的一个CELL的输入还是一样,前一个阶段的状态变量c^{< t-1>}c<t−1>和输入数据x^{< t>}x<t>,输出也依然是新的状态变量c^{< t>}c<t>和预测标签\hat{y}^{< t>}y^<t>,但是它的计算方法却是如下所示:
先定义一个临时变量\tilde{c}^{< t>}c~<t>,其计算方法如下:
\tilde{c}^{< t>} = \text{tanh}(W_c[c^{< t-1>},x^{< t>}] + b_c)c~<t>=tanh(Wc[c<t−1>,x<t>]+bc)
然后定义一个更新门变量\Gamma_uΓu,计算方法如下:
\Gamma_u = \sigma(W_u[c^{< t-1>},x^{< t>}] + b_u)Γu=σ(Wu[c<t−1>,x<t>]+bu)
这里的\sigmaσ是sigmoid函数,一般来说这个\Gamma_uΓu是一个接近于0或者1的值。它的作用就是决定了这一阶段的状态变量c^{< t>}c<t>是重新计算,还是沿用前面的状态变量:
c^{< t>} = \Gamma_u \times \tilde{c}^{< t>} + (1-\Gamma_u) \times c^{< t-1>}c<t>=Γu×c~<t>+(1−Γu)×c<t−1>
可以看到,当\Gamma_u=0Γu=0的时候,c^{< t>} = c^{< t-1>}c<t>=c<t−1>,表示当前cell的状态变量不需要重新计算,直接使用上一个阶段即可。当\Gamma_u=1Γu=1的时候,c^{< t>} = \tilde{c}^{< t>}c<t>=c~<t>,它是重新计算的结果。最后,我们依然使用c^{< t>}c<t>来预测该cell的标签\hat{y}^{< t>}y^<t>。
可以看到上述修改之后会使得最优情况下的状态变量只会保留前面有用的信息,而不会保存太多的信息,进而导致梯度消失或者是计算成本的上升。我们还是以上述案例为例,理想情况下,我们应该计算得到如下结果:
the | cat | which | already | ate | … | was | full |
---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 1 | ||
新计算 | 同上 | 同上 | 同上 | 同上 | 新计算 |
这里的第二行计算结果是\Gamma_uΓu,可以看到cat对应的值是1,说明需要在这里重新计算状态变量cc,后面的单词,一直到was之前的\Gamma_u=0Γu=0,说明,这些单词对应的状态变量都和\text{cat}cat一样。最终,到was的时候,它所依赖的前一阶段的状态变量都是cat计算的,然后was的结果需要根据前一阶段重新计算,即它是根据cat单词计算的,而cat是单数形式,所以这里的was概率最大。这里只是一个简单的示例,表明原理。
上述GRU的计算过程是一个简化版本。完整的GRU的Cell计算还包括一个额外的变量,即重置门\Gamma_rΓr,它的作用是告诉先前的状态和当前的状态是多么的相关。其完整的计算过程如下(注意,这里新变量影响了\tilde{c}^{< t>}c~<t>的计算):
\tilde{c}^{< t>} = \text{tanh}(W_c[\Gamma_r\times c^{< t-1>},x^{< t>}] + b_c)c~<t>=tanh(Wc[Γr×c<t−1>,x<t>]+bc)
\Gamma_u = \sigma(W_u[c^{< t-1>},x^{< t>}] + b_u)Γu=σ(Wu[c<t−1>,x<t>]+bu)
\Gamma_r = \sigma(W_r[c^{< t-1>},x^{< t>}] + b_r)Γr=σ(Wr[c<t−1>,x<t>]+br)
c^{< t>} = \Gamma_u \times \tilde{c}^{< t>} + (1-\Gamma_u) \times c^{< t-1>}c<t>=Γu×c~<t>+(1−Γu)×c<t−1>
上面就是GRU如何使用更新门和重置门保存和过滤信息的原理,可以看到,GRU并不会每次都把之前的变量拿过来计算,只会保存相关的信息,因而会解决梯度消失的问题。与LSTM相比,GRU的变量更少,计算更简单,在某些场景下,它的表现能够和LSTM一样好,因此可以替代LSTM的计算。
研究者设计过各种不同的版本来保存信息,也解决梯度消失的问题。GRU是其中最具代表性和最广泛应用的一个。我们可以根据实际情况来更改这种方法。