[翻译] WILDML RNN系列教程 第一部分 RNN简介

翻译自WILDML博客文章: Recurrent Neural Networks Tutorial, Part 1 - Introduction to RNNs

这份教程是比较通俗易懂的RNN教程,从基本知识到RNN的实现,再到GRU/LSTM等变种均有详述。但是原帖中由于渲染的问题,很多LaTeX的公式都显示不了。本文初衷是为了归档这个系列的教程,并解决公式显示的问题。而后索性将其译为中文,方便以后重新回顾。

笔者才疏学浅,翻译过程中难免有误,请见谅,亦烦请勘误。

文中部分术语会按照笔者所认为之惯用词语进行翻译,英文原词亦包括其中,以供参考。

Recurrent Neural Networks (RNNs) 是一类比较流行的模型,并且在很多NLP任务中有着不错的效果。尽管它很流行,但现在并没有太多资源详述RNN的工作原理与具体的实现方法,故本教程应运而生。这是一个系列教程,我计划包括下面几个方面:

  1. 介绍RNN(亦即本文)
  2. 使用Python和Threano实现一个RNN
  3. 理解Backpropagation Through Time (BPTT)算法和梯度消失(vanishing gradient problem)问题
  4. 实现一个GRU/LSTM

作为本教程的一部分,我们将会实现一个基于RNN的语言模型(recurrent neural network based language model)。语言模型主要包含了两部分:首先,它能够允许我们去根据任意句子在现实世界中出现的可能性给出一个概率上的分数。这能够成为衡量句子在语法和语义层面上的正确程度的标准(译注:越像“人话”分越高,反之亦然)。这些语言模型被用作机器翻译系统的一部分。其次,一个语言模型能够让我们生成新的文本(我认为这个应用厉害得多!)。利用莎士比亚的语料训练一个语言模型,可以被用来产生“莎士比亚风格”的文本。Andrej Karpathy的这篇论文 显示了基于RNN的字符级(character-level)语言模型的威力。

我假设你已经稍微了解过基本的关于神经网络的知识。如果对它很陌生,你或许可以看一看 Implementing A Neural Network From Scratch这篇文章,这能够让你对如何实现一个非递归的神经网络有一些基本概念。

什么是RNNs?

RNN的核心观点在于利用序列化信息。在传统的神经网络中,我们假设所有的输入(和输出)都是相互孤立的(译注:原词是independent,为与“条件独立”的“独立”相区别,此处用孤立)。对很多任务来说,这个非常假设不合适。如果你想预测句子的下一个词是什么,你最好需要知道之前的词序列(word sequence)是哪些。RNN被称为“循环的”是因为:一个序列中的每一个元素都进行相同的工作,并且当前的输出依赖于之前的计算。也可以用“记忆(Memory)”来从另一个角度理解RNN: 它记住了所有到目前为止所有被计算过的信息。理论上RNNs能够利用任意长序列的信息,但在实际使用中,他们经常被限制在有限的几步之内(后面还会提到这点)。下面是一个典型的RNN结构:

RNN 一个随时间展开的RNN,图片来源:Nature

上面的这幅图显示了被展开(unrolled or unfolded)成一个完整网络的的RNN. 通过展开,我们能够很容易地得到一个完整序列的网络结构。比如说,如果一个序列正在处理一个由5个单词组成的句子,网络将会被展开成为一个5层的神经网络,每一层对应一个单词。描述计算过程的公式如下:

  • \(x_t\)是在\(t\)时刻的输入。例如,\(x_1\)可以是一个用来表示句子中第二个单词的one-hot向量。
  • \(s_t\)是在\(t\)时刻的隐状态(hidden state),也就是网络的“记忆”。\(s_t\)是根据之前的隐状态和当前时刻输入所计算得到的,既:\(s_t=f(Ux_t+Ws_{t-1})\)。函数\(f\)通常是非线性函数,例如tanh或者ReLU

  • \(o_t\)是在\(t\)时刻输出。例如,如果我们想要预测句子中的下一个词,那这个输出值将会是一个词表长度的向量,表示整个词表中每一个单词的概率,既\(o_t=\mathrm{softmax}(Vs_t)\)

此外,还有些需要注意的地方:

  • 可以认为隐状态\(s_t\)是网络的记忆。\(s_t\)能够捕获之前所有时间步内发生的信息(译注:既\(s_{t-1}\)和\(x_t\)的信息)。输出节点\(o_t\)的值仅仅基于在时间\(t\)时的记忆(译注:也就是\(s_t\)的信息)。正如前面所提到的,由于\(s_t\)一般不能捕捉到太早前时间步的信息,所以在实际应用中有些复杂。
  • 传统深度神经网络的每层参数各不相同,而RNN在所有时间步中共享相同的参数(前述\(U,V,W\))。这样就说明我们在每一个时间步进行的任务都是一样的,区别只有不同的输入。这将会显著减少网络的需要学习的参数的数量。
  • 上面的图中表明,在每一个时间步都会有一个输出,但是否需要输出,会根据具体任务需求有所变化。例如,我们预测一个句子的情感倾向,如果输出表示情感倾向,我们只在乎输入整句话之后的最终输出,而不是输入每一个单词后的输出。类似地,我们或许不需要在每个时间步都提供一个输入。RNN的主要特性就是隐状态,它能捕捉到序列的一些信息。

RNN能做些啥?

RNNs在很多NLP任务中都有过成功的应用。在此我应该提及RNNs最常见的变种LSTMs,这种变种相对于普通的RNN更容易捕获长序列。不过别担心,本教程也会像实现RNN一样实现LSTMs。它们只是用一种不同的方式去计算隐状态。我们将会在后面的文章中详细介绍LSTMs。下面列举了一些RNNs在NLP领域应用的例子(并不是很全面)。

语言建模和文本生成(Language Modeling and Generating Text)

给定一个单词序列,我们想要预测在给定前面词的情况下,当前词应该是哪个词的概率。语言模型允许我们去衡量一个句子的“像句子”的可能性。这个功能是机器翻译的一个重要的输入信息(概率更高的句子,往往是正确的“句子”)。能够预测下一个词的模型的副产品是生成模型(generative model),它能让我们很容易地利用输出概率来生成新的文本。而且利用不同的训练数据,我们能够得到不同的“文风”(译注:此处原词为stuff,本意是基础,此处译为文风)。语言模型的输入一般都是一个词序列(例如编码成one-hot向量),输出通常是预测的词序列。训练网络是目标是\(o_t=x_{t+1}\),也就是说我们希望\(t\)时刻的输出最好是序列中的下一个词。

关于语言建模和文本生成有这样几篇论文:

机器翻译

机器翻译是一个与语言建模相似的任务。它的输入是源语言的词序列(比如说德语)。我们想要让输出是目标语言序列(比如说英语)。而最大的区别(译注:与语言模型相比)是现在的输出要在所有的输入完成之后才能开始。因为翻译后的目标语言序列的第一个词可能与整个输入的语言序列相关。

Machine Translation 利用RNN进行机器翻译,图片来源:http://cs224d.stanford.edu/lectures/CS224d-Lecture8.pdf

关于机器翻译的一些文章:

语音识别

给定一个从声音波形中得到的语音信号序列,我们能够预测一个语音段(phonetic segments)序列和它们的概率。

关于语音识别的一些文章: * Towards End-to-End Speech Recognition with Recurrent Neural Networks

产生图像描述(Generating Image Descriptions)

RNN经常与CNN结合使用来对未标注的图像生成描述。这个工作看起来非常令人惊叹。结合的模型能够针对图片中发现的一些特点产生文字。

Generating Image Description 利用深度视觉语义对齐产生图片描述(Deep Visual-Semantic Alignments for Generating Image Descriptions),来源:http://cs.stanford.edu/people/karpathy/deepimagesent/

训练RNN

训练一个RNN的方法与训练传统的神经网络类似,仍然使用BP(Backpropagation)算法,但仍然有一些细微的区别。因为网络中的参数在所有训练时间步内是共享的,而每个输出节点的梯度不但依赖于当前时间步的计算结果,也依赖于之前时间步。例如,为了计算\(t=4\)时刻的梯度,我们需要使用3步BP算法,并且将梯度相加。这个方法被称为Backpropagation Through Time (BPTT)。如果还没能完整理解也没关系,我们将会有一整篇文章来介绍个中细节。现在我们只需要认识到,由于梯度消失/梯度爆炸问题的出现,即便使用BPTT训练一个最基本的RNN,也很难学习到长序列的信息(译注:原文为“long-term dependencies”)。现在已经有一些方法能够处理这些问题,例如特殊的RNNs(比如说LSTMs)就是被设计用来解决它们的。

扩展RNN

多年以来,研究者们已经开发出各种各样复杂又精巧的RNNs来弥补基础RNN模型的缺点。在随后的文章中,我们会更加详细地介绍它们。但是现在我打算做一个简短的介绍,使你能够熟悉一下这些模型的分类。

双向RNN(Bidirectional RNNs) 是这样考虑的:在时间\(t\)时刻的输出有可能不止依赖于该序列之前的元素,也可能依赖于后续输入的元素。例如,我们想要预测一个序列中丢失的词,我们同时需要考虑到单词左边和右边的上下文。双向RNN非常简单,它只是将两个RNN相互叠加在一起。输出则同时基于两个RNN的隐状态。

Bidirectional RNN

深度RNN(Deep (Bidirectional) RNNs)与双向RNN相似,唯一的不同是我们在每个时间步都会有多层。它将带来更好的学习能力(但是我们需要更多的数据去训练它) Deep Bidirectional RNNs

LSTM网络是最近非常流行的模型,我们也曾在前文中提到过它。LSTMs与RNNs并没有本质上的差异,但它们使用不同的方式去计算隐状态。LSTMs中的“记忆”被称为“cells”,你可以把它想象成为一些黑盒子,以之前的隐状态状态\(h_{t-1}\)和现在输入\(x_t\)作为输入。这些cells在内部决定和是否应该写入或删除记忆,然后他们将之前的状态、现在的记忆和当前输入结合在一起。这些单元能够非常有效地捕捉到长期以来的信息。LSTMs在刚开始学习时比较复杂,但如果你感兴趣想要多了解一下,这篇文章讲解的非常清楚。

结论

到目前为止,我希望你已经对RNNs有一个基本的了解,并且知道他们能用来干些什么。在下一篇文章中,我们将会利用Python和Theano去实现我们第一个一个基于RNN的语言模型。如有疑问请留言。

Friskit

继续阅读此作者的更多文章