SimpleRNN 并不是 Keras 中唯一可用的循环层,还有另外两个: LSTM 和 GRU。在实践中总会用到其中之一。因为 SimpleRNN 通常过于简化,没有实用价值。
SimpleRNN 的最大问题是,在时刻 t,理论上来说,它应该能够记住许多时间步之前见过的信息,但实际上它是不可能学到这种长期依赖的。其原因在于梯度消失问题( vanishing gradient problem) 。LSTM 层和 GRU 层都是为了解决这个问题而设计的。

LSTM 介绍

LSTM 层背后的长短期记忆( LSTM, long short-term memory)算法由 Hochreiter 和 Schmidhuber 在 1997 年开发,是二人研究梯度消失问题的重要成果。

LSTM 层是 SimpleRNN 层的一种变体,它增加了一种携带信息跨越多个时间步的方法。假设有一条传送带,其运行方向平行于你所处理的序列。序列中的信息可以在任意位置跳上传送带,然后被传送到更晚的时间步,并在需要时原封不动地跳回来。这实际上就是 LSTM 的原理:它保存信息以便后面使用,从而防止较早期的信号在处理过程中逐渐消失

先来看一下 SimpleRNN 单元(因为有许多个权重矩阵,所以对单元中的 W 和 U 两个矩阵添加下标字母 o( Wo 和 Uo),表示输出 ):
image.png

我们向这张图像中添加额外的数据流,其中携带着跨越时间步的信息。它在不同的时间步的值叫作 Ct,其中 C 表示携带( carry)。这些信息将会对单元产生以下影响:它将与输入连接和循环连接进行运算(通过一个密集变换,即与权重矩阵作点积,然后加上一个偏置,再应用一个激活函数),从而影响传递到下一个时间步的状态(通过一个激活函数和一个乘法运算)。从概念上来看,携带数据流**是一种调节下一个输出和下一个状态的方法**(见图 6-14)。到目前为止都很简单。
image.png
下面来看这一方法的精妙之处,即携带数据流下一个值的计算方法。

携带数据流下一个值的计算方法

涉及三个不同的变换,这三个变换的形式都和 SimpleRNN 单元相同:

y = activation(dot(state_t, U) + dot(input_t, W) + b)``

但这三个变换都具有各自的权重矩阵,我们分别用字母 ijk 作为下标。目前的 LSTM 模型架构伪代码如下所示:

  1. output_t = activation(dot(state_t, Uo) + dot(input_t, Wo) + dot(C_t, Vo) + bo)
  2. i_t = activation(dot(state_t, Ui) + dot(input_t, Wi) + bi)
  3. f_t = activation(dot(state_t, Uf) + dot(input_t, Wf) + bf)
  4. k_t = activation(dot(state_t, Uk) + dot(input_t, Wk) + bk)
  5. c_t+1 = i_t * k_t + c_t * f_t # 对 i_t、 f_t 和 k_t 进行组合,可以得到新的携带状态(下一个 c_t)

给出了添加上述架构之后的图示。 LSTM 层的内容我就介绍完了:
image.png

如果要更哲学一点,你还可以解释每个运算的目的。比如你可以说,将 c_t 和 f_t 相乘,是为了故意遗忘携带数据流中的不相关信息。同时, i_t 和 k_t 都提供关于当前的信息,可以用新信息来更新携带轨道。
归根结底,这些解释并没有多大意义,因为这些运算的实际效果是由参数化权重决定的,而权重是以端到端的方式进行学习,每次训练都要从头开始,不可能为某个运算赋予特定的目的。 RNN 单元的类型(如前所述)决定了你的假设空间,即在训练期间搜索良好模型配置的空间,但它不能决定 RNN 单元的作用,那是由单元权重来决定的。同一个单元具有不同的权重,可以实现完全不同的作用。因此,组成 RNN 单元的运算组合,最好被解释为对搜索的一组约束,而不是一种工程意义上的设计

对于研究人员来说,这种约束的选择(即如何实现 RNN 单元)似乎最好是留给最优化算法来完成(比如遗传算法或强化学习过程),而不是让人类工程师来完成。在未来,那将是我们构建网络的方式。总之,你不需要理解关于 LSTM 单元具体架构的任何内容。作为人类,理解它不应该是你要做的。你只需要记住 LSTM 单元的作用:允许过去的信息稍后重新进入,从而解决梯度消失问题

实战:LSTM 应用于 IMDB 评论情感分类

现在我们来看一个更实际的问题:使用 LSTM 层来创建一个模型,然后在 IMDB 数据上训练模型。
这个网络与前面介绍的 SimpleRNN 网络类似。你只需指定 LSTM 层的输出维度,其他所有参数(有很多)都使用 Keras 默认值。

  1. from keras.layers import LSTM
  2. model = Sequential()
  3. model.add(Embedding(max_features, 32))
  4. model.add(LSTM(32))
  5. model.add(Dense(1, activation='sigmoid'))
  6. model.compile(optimizer='rmsprop',
  7. loss='binary_crossentropy',
  8. metrics=['acc'])
  9. history = model.fit(input_train, y_train,
  10. epochs=10,
  11. batch_size=128,
  12. validation_split=0.2)

image.pngimage.png
这一次,验证精度达到了 89%。还不错,肯定比 SimpleRNN 网络好多了,这主要是因为 LSTM 受梯度消失问题的影响要小得多。

但对于一种计算量如此之大的方法而言,这个结果也说不上是突破性的。为什么 LSTM 不能表现得更好?一个原因是你没有花力气来调节超参数,比如嵌入维度或 LSTM 输出维度。另一个原因可能是缺少正则化。但说实话,主要原因在于,适用于评论分析全局的长期性结构(这正是 LSTM 所擅长的),对情感分析问题帮助不大。对于这样的基本问题,观察每条评论中出现了哪些词及其出现频率就可以很好地解决。这也正是第一个全连接方法的做法。但还有更加困难的自然语言处理问题,特别是问答和机器翻译,这时 LSTM 的优势就明显了