Vanilla RNN
参考 RNN wiki 的描述,根据隐层 ht 接受的是上时刻的隐层(hidden layer) ht−1 还是上时刻的输出(output layer) yt−1 ,分成了两种 RNN,定义如下:
- Elman network 接受上时刻的隐层 ht−1
- Jordan network 接受上时刻的输出 yt−1
但是看了很多的教程,感觉应用最多的还是 Elman network 的做法。比如 WILDML: RECURRENT NEURAL NETWORKS TUTORIAL 画出来的示意图:
还有 Andrej Karpathy 的博客 The Unreasonable Effectiveness of Recurrent Neural Networks 的实现,也是接收上一时刻隐层的结果,图就不贴了。
Bidirectional RNNs
双向的 RNN 是同时考虑“过去”和“未来”的信息,考虑上图,正常情况下,输入(黑色点)沿着黑色的实线箭头传输到隐层(黄色点),再沿着红色实线传到输出(红色点)。黑色实线做完前向传播后,在 Bidirectional RNNs 却先不急着后向传播,而是从末尾的时刻沿着虚线的方向再回传回来。最后把两个方向得到的激活值拼在一起(concatenate),当做最后的激活值。那么后向传播也是类似,要转一圈回来。
Stacked Bidirectional RNNs
堆多层的 recurrent layer,如上图所示,可以增加模型的参数,提高模型的学习能力。每层的 hidden state 不仅要输给下一时刻,还是当做是此时刻下一层的输入。上图展示了双向的三层 RNNs,那么 hidden state 的维度是 hidden_dim * 6,输出的维度为 hidden_dim * 2,因为是两个方向最有一层 hidden state 拼接的结果。
原始的 RNN 很难训练,主要是因为存在梯度消失(gradient vanishing problem)和梯度爆炸问题(gradient explosion problem)。梯度消失导致无法抓住长时刻依赖,因此效果不好,后面的 LSTM 和 GRU 的新结构,就是为了对付这个问题。而梯度爆炸问题虽然不是每次都出现,但是一旦出现就很致命。一般会选择用截断的梯度(clipped gradient)来更新参数,或者直接把梯度 rescale 到一个固定模大小的范围。
LSTM
由于 Vanilla RNN 具有梯度消失问题,对长关系的依赖(Long-Term Dependencies)的建模能力不够强大。这句话是什么意思呢?就是说,原来的 RNN,由于结构上的限制,很长的时刻以前的输入,对现在的网络影响非常小,后向传播时那些梯度,也很难影响很早以前的输入,即会出现梯度消失的问题。而 LSTM 通过构建一些门(Gate),让网络能记住那些非常重要的信息,而这个核心的结构,就是 cell state。比如遗忘门,来选择性清空过去的记忆和更新较新的信息。
上面讲的比较迷糊,如果我有新的理解会更新这个博客。另外可以参考大神的博客 Understanding LSTM Networks,把 LSTM 讲的深入浅出,并且提到了很多的变种和展望。
有两种常见的 LSTM 结构,如 LSTM wiki 总结的,第一种是带遗忘门的 Traditional LSTM,公式如下:
前三行是三个门,分别是遗忘门 ft ,输入门 it ,输出门 ot ,输入都是 [xt,ht−1] ,只是参数不同,然后要经过一个激活函数,把值放缩到 [0,1] 附近。第四行 ct 是 cell state,由上一时刻的