作者 | 李理
目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能。
写在前面
由于工作太忙,这个系列文章有一年多没有更新了。最近在整理资料时用到了里面的一些内容,觉得做事情应该有始有终,所以打算把它继续完成。下面的系列文章会首先会介绍 vanilla RNN 的代码,希望读者能够通过代码更加深入的了解RNN的原理。代码会着重于 forward 的介绍,而对 BPTT 一带而过。之前的文章为了让读者了解原理,我们都是自己来实现梯度的计算和各种优化算法。但是在实际的工作中,我们一般使用一些成熟的深度学习框架。因为框架把常用的算法都做了封装,我们的代码会更加简单而不易出错;此外框架的实现效率一般会比我们的更高,会利用 GPU 来加速训练。
我们之前在 CNN 的地方介绍了 theano,但是深度学习的发展变化也很快,theano目前已是一个死掉的项目。目前用户最多的深度学习框架是TensorFlow,但是在 RNN 方面,基于动态图的 PyTorch 更加方便,所以这个系列文章会使用 PyTorch。因此介绍过 vanilla RNN 之后会简单的介绍一下 PyTorch,尤其是 PyTorch 在 RNN 方面相关模块。然后会介绍一些 PyTorch 的例子,接下来会介绍 seq2seq(encoder-decoder) 模型和注意力机制,包括它在机器翻译里的应用,我们会自己实现一个简单的汉语-英语的翻译系统。
最后一部分就是我们的主题—— Image Caption Generation,有了前面 CNN 和 RNN 的基础,实现它就非常轻松了。
本章会介绍循环神经网络的基本概念。
基本概念
▌RNN
RNN 的特点是利用序列的信息。之前我们介绍的神经网络假设所有的输入是相互独立的。但是对于许多任务来说这不是一个好的假设。如果你想预测一个句子的下一个词,知道之前的词是有帮助的。RNN 被成为递归的 (recurrent) 原因就是它会对一个序列的每一个元素执行同样的操作,并且之后的输出依赖于之前的计算。另外一种看待 RNN 的方法是可以认为它有一些“记忆”能捕获之前计算过的一些信息。理论上 RNN 能够利用任意长序列的信息,但是实际中它能记忆的长度是有限的。
图5.1显示了怎么把一个 RNN 展开成一个完整的网络。比如我们考虑一个包含5个词的句子,我们可以把它展开成 5 层的神经网络,每个词是一层。RNN 的计算公式如下:
1. 是 t 时刻的输入。
2. 是 t 时刻的隐状态。
它是网络的“记忆”。 的计算依赖于前一个时刻的状态和当前时刻的输入:
。函数 f 通常是诸如 tanh 或者 ReLU 的非线性函数。
,这是用来计算第一个隐状态,通常我们可以初始化成0。
图5.1: RNN 展开图
3. 是 t 时刻的输出。
有一些事情值得注意:
1. 你可以把 看成是网络的“记忆”。
捕获了从开始到前一个时刻的所有(感兴趣) 的信息。输出
只基于当前时刻的记忆。不过实际应用中
很难记住很久以前的信息。
2. 参数共享
和传统的深度神经网络不同,这些传统的网络每层使用不同的参数,RNN 的参数(上文的 U, V, W ) 是在所有时刻共享(一样) 的。这反映这样一个事实:我们每一步都在执行同样的操作,只不过输入不同而已。这种结构极大的减少了我们需要学习的参数【同时也让信息得以共享,是的训练变得可能】
3. 每一个时刻都有输出
上图每一个时刻都有输出,但我们不一定都要使用。比如我们预测一个句子的情感倾向是我们只关注最后的输出,而不是每一个词的情感。类似的,我们也不一定每个时刻都有输入。RNN 最主要的特点是它有隐状态(记忆),它能捕获一个序列的信息。
▌RNN 的扩展
1. 双向 RNN (Bidirectional RNNs)
它的思想是 t 时刻的输出不但依赖于之前的元素,而且还依赖之后的元素。比如,我们做完形填空,在句子中“挖”掉一个词,我们想预测这个词,我们不但会看之前的词,也会分析之后的词。双向 RNN 很简单,它就是两个 RNN 堆叠在一起。输出依赖两个 RNN 的隐状态。
2. 深度(双向) RNN (Deep (Bidirectional) RNNs)