从信息隐匿的角度谈 LSTM:从 Stack 到 Nest

从信息隐匿的角度谈 LSTM:从 Stack 到 Nest

LSTM 的奇葩设计


LSTM 是个奇葩:它的状态是 (c, h) 这样一个 pair,其中 c 用于存储一些信息,而 h 用于上层输出。这里 (c, h) 分离的直觉,我称之为信息隐匿

  • 让 c 在一条高速公路上行进(相邻时间步的 c 只有加性更新,没有非线性变换)有利于保持信息在跨越多个时间步后仍然清晰可辨。
  • 又因为模型当前的输出可能并不需要它记住的所有历史信息,而是只跟其中一部分信息有关,所以 h 用于把 c 中和当前输出相关的部分提取出来。

图示如下,相信大家都看过:

LSTM

这样以来,c 负责存储信息,h 负责提取信息,两部分各司其职,互相配合,最终模型效果就很好。但是如果这么讲的话,c 和 h 其实是不对等的啊,c 在底层,而 h 是在 c 的上层操作的,这一点从公式 h_t = o_t * \tanh(c_t ) 也能看出来。

除了 LSTM 这个奇葩,还有谁对别人藏着掖着啊~别的 RNN Cell 基本上都是有一说一,隐层存什么就输出什么,比如 GRU。我把我知道的都告诉模型上层,至于上层用不用那是上层的事,不归我管。在论文 An Empirical Exploration of Recurrent Network Architectures 中也通过实验的方法证实,output gate 一般用处不大:"We discovered that the input gate is important, that the output gate is unimportant, and that the forget gate is extremely significant on all problems except language modelling"。再比如说,LSTM 还有一种变体叫 peephole connection,就是计算各个门的时候再偷看一眼之前的 cell state,最终效果和普通的 LSTM 各有千秋。这个变种就更是神经病了,先是把 cell 藏起来,然后又去偷看一眼,到底想搞什么啊?


Stacked LSTM 及其问题


如果单层 LSTM 表达能力不够,通常会使用 Stacking 把多层 LSTM 摞起来,下层的输出作为上层的输入,进而增强模型的表达能力。乍一看似乎没什么问题(事实上这是目前的主流做法),但是其实这很不符合直觉啊。我个人能接受 Vanilla RNN 和 GRU 堆叠多层,但是 LSTM 堆叠多层就显得很奇怪了(虽然说效果还不错)。

我的理由是这样的:如果从直觉上讲,input gate 用于控制模型读入多少信息,output gate 用于控制模型输出多少信息,那么两层 LSTM 嵌套在一起的时候,当下层 LSTM 的信息要流动到上层时,需要先经过下层的 output gate 筛选一次,再经过上层的 input gate 筛选一次,这是何必呢?难道不应该底层把所有信息都暴露给上层对于上层做决定更有利吗,或者至少是让上层决定它自己需要什么信息?简单用流程图解释下:


普通 LSTM 的信息流动(方括号表示在同一层内部):

单层:

输入 -> 【input gate 选择后的信息 -> c -> output gate 选择后的信息】 -> 输出

双层或多层:

输入 -> 【input gate 选择后的信息 -> c -> output gate 选择后的信息】 -> 【input gate 选择后的信息 -> c -> output gate 选择后的信息】 -> 输出


可是,是不是下面这样的结构更好呢?(未验证,仅为脑洞)

单层(此时与普通的 LSTM 等价,就看认为 c 和输出之间的门属于哪一层):

输入 -> 【input gate 选择后的信息 -> c 】-> 【输出层自己的 input gate 选择后的信息 -> 输出】

双层或多层:

输入 -> 【input gate 选择后的信息 -> c】 -> 【input gate 选择后的信息 -> c】-> 【输出层 input gate 选择后的信息 -> 输出】


这样一来,在多层的情形下,就少了中间几个隐层的 input gate 和 output gate 重复选择的问题,还能减少参数量。

从另一个角度讲,给输出层加一个 gate 可以看成是近年来流行的 deep out:即在隐层输出的基础上,再变换个好几层作为模型最终的输出。这里的好几层多出来的变换就可以认为是输出层的 input gate(只是没有文献这么叫罢了),用于提取整合 RNN 的输出中和当前时间步有关的信息。


Nested LSTM

最近有一篇文章([1801.10308] Nested LSTMs)提出了 Nested LSTMs,从信息隐匿的角度讲,我觉得更符合 LSTM 的设计思路。LSTM 不是把 (c, h) 分开了吗?c 和 h 不是地位不对等吗?好,既然 h 构建在 c 的上层,那我就给 c 的底层再加一个更基础的模块。例如,在写一篇文章时,h 用于预测当前词,c 用于存储当前这句话中有用的信息,再定义一个比 c 更底层的模块存储 \tilde{c} 当前段落的主题,再有一个更更底层的模块存储整篇文章的主题,这种设计如何?你不是喜欢藏信息吗,那我让你藏个够!

每一层的 c 都是一层新的抽象,表达时间跨度更大的信息,当模型想做一次预测时,一层一层往深里查询,h 去查 c,c 去查 \tilde{c} ,就好像函数调用一样,层层上报,层层审批,最终把需要的信息从最深层提取出来,而且是糅合了不同深度(对应于不同的时间尺度)的信息。


图示如下:

Nested LSTM


三种结构的计算图对比如下的:

各 LSTM 变体计算图

就作者自己的一些观察而言,也确实是更内层的 cell 变化更慢一些,也即对应了更大时间尺度的信息,和模型设计的初衷相一致。


可能我写这篇文章就是为了安利 Nested LSTM, Orz~

编辑于 2018-05-15

文章被以下专栏收录