RNN介绍

2017-07-01

递归神经网络RNN最近两年在NLP问题上显示出巨大威力,但是鲜有中文相关资料能把RNN的工作解释清楚的,所以我翻译了一系列的博客,希望能把这个问题解释清楚。我们将从以下几个部分来解释RNN是如何工作的。

1.RNN介绍(本文)
2.用Python和Theano实现RNN
3.理解BPTT算法和梯度消失问题
4.实现LSTM/GRU结构的RNN

这篇文章假设你对神经网络有了基本的了解,如果不了解,请参阅用Python实现神经网络

什么是RNN?

RNN的主要思想是利用顺序信息。在传统的神经网络中,我们假设所有的输入 (包括输出)都是相互独立的。但是在很多任务中,这种假设非常致命。例如,我们想预测一个句子中的下一个单词,我们需要知道这个单词前面的内容。RNN之所以称为递归神经网络,是因为它对序列的每一个元素(比如单词)执行相同的操作,RNN每次的输出取决于前面序列的计算结果。可以从另一个角度理解RNN,RNN有一个“记忆体”,这个“记忆体”里存储了序列开头到当前位置计算得到的所有信息,这样在预测下一个单词的任务中,我们就可以把单词前面的信息利用上了。理论上,RNN可以处理任意长度的序列,但是在实践中它只能“记住”几个步骤的信息(文后提到)。这儿展示了一个典型的RNN:

上图展示了RNN打开以后的样子,这儿注意,展开网络并不是意味着所有的元素同时处理的,而是一个元素一个元素进入网络中处理的,展开是为了更好的展示神经网络如何处理序列。从这儿可以看出,RNN是一个空间上的浅层,时间上的深层网络。举个例子,“A boy loves a girl.” 这句话一共五个单词,网络就会展开成五层,每层处理一个单词,这些单词是一个一个进入同一个网络处理的,它们进入的是时间上的不同的层。上图的解释如下:

1.$x_t$是$t$时刻阶段的输入,比如句子中的A对应$x_0$,boy对应$x_1$等等。
2.$s_t$是$t$时刻阶段隐藏层的状态,即RNN的“记忆体”。$s_t$由前一层的隐藏状态还有本层的输入计算得来,计算公式: $s_t=f(U*x_t+W*s_{t-1})$ 。此处的函数$f$通常是非线性的,比如tanh或者ReLU。$s_{-1}$用来计算第一个隐藏层状态$s_0$,通常被初始化为0。
3.$o_t$是$t$时刻阶段的输出。比如,我们想预测一个句子里接下来的单词是什么,此时的输出就是一个基于词汇表的概率向量,即:$o_t=softmax(V*s_t)$。

这儿我们有一些要注意的地方:

1.你可以把隐藏状态$s_t$看成RNN的“记忆体”。$s_t$可以捕获到$t$阶段之前的所有信息。此时的$o_t$只基于当前的“记忆体”($s_t$)计算得到。正如之前提到的,在实践中RNN不能捕获到太“早”之前的信息。
2.在传统的神经网络中,我们在网络的不同层使用不同的参数,但是RNN不一样,RNN在所有的阶段共享同一组参数(图中的$U, V, W$),这个就表示我们在每一个步骤中执行了相同的计算,只是使用了不同的输入,这大大减少了我们需要学习的参数总数。
3.在上图中,我们在每一个时刻都有输出,但是,这个并不是必须的,视我们的任务而定。比如情感分析问题中,需要我们判断整句话的情感倾向,我们并不用给每个单词一个情感的输出,只要在一句话结束的时候,给出判断即可。RNN最主要的特征就是它的隐藏状态,因为隐藏状态中包含了序列的信息。

RNN可以做什么?

RNN已经在NLP任务中取得了巨大的成功。最常用的RNN是LSTM,它能够比普通的RNN捕获更“早”的信息。LSTM与本教程中将要开发的RNN根本上是一样的,区别只是计算隐藏状态的方法不同。我们会在以后的文章中更详细的介绍LSTM。下面是RNN在NLP领域应用的一些例子。

语言建模和文本生成

给定一系列的单词,我们希望能够根据前面的词汇,预测出接着的每一个词汇的概率。语言模型允许我们衡量句子的可能性,这个在机器翻译中非常有用,因为高概率的句子通常是正确的。能够预测下一个单词的能力还可以被用在生成模型中。根据我们的训练数据,我们可以生成各种各样的东西。在语言模型中,我们的输入通常是一系列单词(例如可以把每一个单词变成一个one-hot vector),我们的输出是预测的单词的序列。训练网络时,我们设置$o_t=x_{t+1}$,因为我们希望$t$阶段的输出是实际的下一个单词。
语言模型和文本生成的相关论文:

Recurrent neural network based language model
Extensions of Recurrent neural network based language model
Generating Text with Recurrent Neural Networks

机器翻译

机器翻译跟语言建模有点像,我们同样需要输入来自源语言的词汇序列(比如中文“一个男孩喜欢一个女孩”),我们期望能够输出目标语言的词汇序列(比如英文“A boy loves a girl”)。一个关键的区别是我们的输出需要在我们看过完整的输入以后开始,因为翻译完的的句子的第一个词汇可能需要从完整的序列里获取信息。下面就是处理机器翻译的RNN的样式:

机器翻译的相关论文:

A Recursive Recurrent Neural Network for Statistical Machine Translation
Sequence to Sequence Learning with Neural Networks
Joint Language and Translation Modeling with Recurrent Neural Networks

语音识别

给定声音信号的输入序列,我们可以输出语音片段的相关概率。
语音识别的相关论文:

Towards End-to-End Speech Recognition with Recurrent Neural Networks

生成图片描述

配合CNN,将RNN作为模型的一部分,我们可以用来生成未标记过的图片的描述。这个组合而成的模型甚至可以将生成的单词与图像中发现的特征对应上。

RNN训练

RNN的训练类似传统神经网络的训练,都是使用反向传播算法,但是会有一些改变。因为参数在所有的不同的时间阶段共享,每一个输出的梯度不仅取决于当前阶段的状态,还取决于以前阶段的状态。例如为了计算$t=4$时刻的梯度,我们需要反向传播3个“时间层”,然后把梯度累加起来。这个就是“时间上的反向传播”(BPTT)。如果这儿不太理解,不用担心,我们以后会详细地讲这边。需要注意的是,由于梯度消失和梯度爆炸问题,使用BPTT训练RNN会有一些困难,所以会有一些特定的机制,比如特定类型的RNN(e.g. LSTM,GRU)被专门设计出来绕过它们。

RNN扩展

多年来,为了应对普通RNN模型的短板,研究人员们开发了很多复杂的RNN变种模型。我们将在以后的文章中更详细的讲解。本节作为一个简单概述,希望您可以通过本节了解模型的大概分类。

双向RNN

双向RNN基于这样一个想法,模型在时间$t$上的输出,不仅取决于先前的元素,也取决于将来的元素。例如,我们想预测一个句子中缺失的单词,我们要同时查看缺失单词左侧和右侧的内容。双向RNN很简单,它只是两个RNN堆叠起来。然后基于两个RNN的隐藏状态来计算输出。

深度(双向)RNN

深度(双向)RNN跟双向RNN非常类似,仅仅在每个时间阶段含有多个隐层。这给了我们更高的学习能力,当然,同时也需要更多的训练数剧。

LSTM

LSTM最近几年非常火,我们在上文也简单的提到了它。它跟普通RNN架构上没有根本的区别,但是在计算隐层状态上采取了不同的方法。“记忆体”在LSTM中被称为“cell”,你可以把它看成一个黑盒,这个黑盒接收前一层的状态$h_{t-1}$和本层的输入$x_t$作为输入(两个输入)。“cell”内部决定哪些内容被保存(抹去)。然后结合先前的状态,当前的输入和状态产生输出,下一层黑盒又要用到本层的状态和下一层的输入,如此往复。结果表明这种结构在捕获长期依赖(记得更早的事情)方面效果非常好。LSTM的结构解释起来有点复杂,如果您有兴趣,这篇文章可以作为不错的参考。

结论

到这儿,我希望您对RNN是什么以及它能做什么有一个大概的了解。在下一篇文章中,我们讲使用Python和Theano来实现我们的RNN的第一个版本。