Posts /

A step by step RNN tutorial

Twitter Facebook
02 Nov 2016

最近接到指导老师给的“看一下LSTM”的任务。

虽然之前已经了解过LSTM以及RNN的基本原理和适用范围,但是还没有写过代码,因此通过这个机会,将RNN以及LSTM及其变体,更详细的了解一下,并尽量将其实现。

Below comes from Wikipedia:

递归神经网络RNN)是两种人工神经网络的总称。一种是时间递归神经网络(recurrent neural network),另一种是结构递归神经网络(recursive neural network)。时间递归神经网络的神经元间连接构成有向图,而结构递归神经网络利用相似的神经网络结构递归构造更为复杂的深度网络。

不做特别说明,本文中我使用 RNN 指代 Recurrent Neural Network,或循环神经网路。

RNN 可以描述动态时间行为,因为和前馈神经网络(feedforward neural network)接受较特定结构的输入不同,RNN将状态在自身网络中循环传递,因此可以接受更广泛的时间序列结构输入。手写识别是最早成功利用RNN的研究结果。

Tomas Mikolov 和 Martin Karafiat 在 2010 年提出了一个 rnn based language model。这个语言模型主要被应用在两个方面:

大神 Andrej Karpathy 向我们展示了一个基于 rnn 的 character-level 的语言模型,原文在此:The Unreasonable Effectiveness of Recurrent Neural Networks

1. 什么是 RNNs ?

在传统的神经网络中,输入和输出一般是相互独立的。RNNs 的核心思想就是怎样利用输入的 sequential information。比如,在词序的预测中,我们要预测下一个单词,那就需要根据它前面的单词来推测当前的单词可能是什么。

RNNs 的 recurrent 表示 它对序列中的每个元素都执行同样的 task,每个 element 计算的输出依赖于之前的计算结果。更形象一点地说,RNNs 有了 ’memory‘,它可以捕捉到目前为止已经得到的信息。理论上,RNNs 是可以用于任一长度的序列的,但是实际上,它们只对前几步有比较好的记忆效果。

一个 RNN 的结果如下图所示,把它展开,就是右边的样子,他们的内部结构是完全一样的。箭头表示时间序列/输入序列。图片来源于 Nature

举例来说,如果我们输入了一个长度为 5 个单词的句子,那这个 RNN 展开之后就是一个 5 层的神经网络,每层处理一个 word。

x 表示每一层的输入,s 表示 hidden state,这就是前面说到的 ’memory‘,它通过 x 和 上一层的 s 计算得到,计算的函数是一个非线性函数,比如 tanh 或者 ReLU。第一层的 hidden state 通常被初始化为 0。 o 表示输出。例如如果我要预测一句话的下一个词是什么,那这个输出就是基于自定义的 vocabulary 的概率分布。我们可以使用 softmax 函数来计算得到输出。

Someting to NOTE:

2. RNNs 可用来做什么?

目前 RNNs 被广泛应用于 NLP 领域,而现在绝大多数的 RNNs Model 都是 LSTMs。在了解 LSTM 之前,先看一下 RNNs 在 NLP 领域应用的一些例子:

3. 如何训练 RNNs?

同样是使用 BP 算法,不过有点不同:Backpropagation Through Time (BPTT)。前面提到,由于 RNNs 中的参数是网络中的所有 time steps 共享的,所以我们计算当前步的梯度时,也需要利用前一步的信息。例如,我们计算 t=4 的梯度时,我们需要反向传播到前一步,然后将它们的梯度相加。在更深入的了解之前,需要明确的是, BPTT 在学习 long term dependencies 的时候是有困难的。这个问题被称作 ’Vanishing/Exploding gradient problem‘。这个问题一般是指我们常说的 vanilla RNNs。

vanilla是什么意思呢?

“Vanilla” is a common euphemism(委婉语) for “regular” or “without any fancy stuff.”

类似于 naive

4. A brief overview of RNNs Extensions

5. RNNs 基本实现

在此使用 Python 实现了一个全功能的 RNN,并用 Threano 进行 GPU 加速。完整的代码在我的 Github

现在的模型存在的问题是

vanilla RNN 几乎不能成成有意义的文本,因为它不能学习隔了很多步的依赖关系。

接下来我们先深入了解一下 BPTT 和 vanishing gradient,然后开始介绍 LSTM。

在这一部分,主要了解一下 BPTT 以及它跟传统的反向传播算法有什么区别。然后尝试理解 vanishing gradient 的问题,这个问题也催生出了后来的 LSTMs 和 GRUs,后两者也是当今 NLP 领域最有效的模型之一。

起初,vanishing gradient 问题是在 1991 年被 Sepp Hochreiter 首次发现的,并且在最近由于深层架构的使用而越来越受关注。

这一部分需要偏导以及基本的反向传播理论的基础,如果你并不熟悉的话,可以看这里这里这里

LSTMs & GRUs

LSTMs 最早在 1997 年被提出,它可能是目前为止 NLP 领域应用最广泛的模型。

GRUs 第一次提出是在 2014 年,它是一个简化版本的 LSTMs。

以上两种 RNN 结构都是用来显式地被设计为解决 vanishing gradient 问题,现在让我们正式开始:

在完整的了解 RNNs 的细节之前,我已经学习过了 LSTMs,详情见我的这篇 深入理解LSTM

概括来说,LSTMs 针对 RNNs 的 long-term dependencies 问题,引入了 门机制 gating 来解决这个问题。先来回顾一下上面我这篇 post 中提到的 LSTMs 的主要的计算公式:

其中,o 表示逐点操作。

i, f, o 分别是 输入、遗忘和输出门。从上面我们一刻看到他们的公式是相同的,只不过对应的参数矩阵不同。关于input, forget 和 output 分别代表什么意义,看上面这篇 post。需要注意的一点是,所有这些 gates 有相同的维度 d,d 就是 hidden state size。比如,前面的 RNNs 实现中的 hidden_size = 100。

g 是基于当前输入和前一步隐状态的 hidden state。这跟前面的 vanilla RNNs 是一样求的。只是将 U,W 的名字改了一下。但是,我们没有像在 RNN 中那样将 g 当做 new hidden state,而是用前面的 input gate 挑选出其中一些来。

c_t 是当前 unit 的 ’memory‘。从上面的公式可以看出,它定义了我们怎样将之前的记忆和和新的输入结合在一起。给定了 c_t,我们就可以计算出 hidden_state s_t,使用上面的公式。

不知你是否已经感觉到, plain/vanilla RNNs 可以看做是 LSTMs 的一个特殊变体,你把 input gate 固定位 1,forget gate 固定位 0, output gate 固定位 1,你就几乎得到了一个标准的 RNN。唯一不同的地方是多了个 tanh。

综上,门机制使得 LSTMs 可以显式地解决长距离依赖的问题。通过学习门的参数,网络就知道了如何处理它的 memory。

现在已经有了很多 LSTMs 的变体,比如一种叫做 peephole 的变体:

这里有一篇关于评估 LSTM 的不同结构的 paper: LSTM: A Search Space Odyssey

Reference

学习整个 LSTMs 的过程我主要看了如下五个材料,并查阅了其他相关的术语定义,完成了其中的 RNN 和 LSTM 的两个实验。

其中,最后一篇参考资料引用了如下内容:

Introduction of the LSTM model:

Addition of the forget gate to the LSTM model:

More recent LSTM paper:

Papers related to Theano:

Problems

在写这篇完整的 post 的最后,我想总结一下目前还没有解决的问题:

总而言之,post 虽然写完,但还是有很多坑要填。 —_—


Twitter Facebook