recurrent neural network

Chapter 10

Sequence Modeling: Recurrent and Recursive Nets

文章来自MIT的Deep Learning未出版版本:

@unpublished{Bengio-et-al-2015-Book,
title={Deep Learning},
author={Yoshua Bengio and Ian J. Goodfellow and Aaron Courville},
note={Book in preparation for MIT Press},
url={http://www.iro.umontreal.ca/~bengioy/dlbook},
year={2015}
}
Deep learning Book: Chapter 10 Sequence Modeling: Recurrentand Recursive Nets

引言讲了为什么要提出RNN这种结构。首先讲了隐马尔可夫模型HMM(我之前看了一下统计学习方法里面的相关章节),我们知道HMM基于两个假设:

  • 任一时刻t的状态只与前一时刻t-1有关,而与再之前时刻无关,相当于一阶马尔可夫链
  • 任一时刻的观测只依赖于该时刻马尔可夫链的状态

但是对于语音识别这样的问题来说,由于声音可以延续,那么当前时刻t的状态可能跟t-2,t-3甚至更之前的时刻都有关系,如此一来如果我们单单像HMM那样只考虑前一时刻t-1,可能效果会变差。

所以我们自然而然地把研究目光放在了RNN上。这里的R是指recurrent,跟时间有关,也就是时序的神经网络。我们知道HMM也是基于时序的,所以直观上我们可以认为RNN是在HMM的基础上,放宽HMM第一个假设,使得某个时刻的状态跟之前所有时刻的状态都有关系。
注意RNN中的R有两种,一个是刚才提到的recurrent,还有一个是recursive。文中提到recursive的结构比recurrent更通用,也就是说我们可以认为recurrent是recursive的一种特例。一般来说我们的RNN指的都是时序的recurrent-Nets。

10.1 Unfolding Flow Graphs and Sharing Parameters

注:这里文章是先讲unfold flow graph,再引出RNN的。而Hinton的lecture和这份入门资料都是先提出RNN模型,再展开到unfold flow graph的。感觉前者更加深入,而后者更加直观。

首先来看看flow graph,也就是流图(6.4节中有更多介绍)。这节只讲述把recursive(循环)部分转化为unfold flow graph(展开为流图)结构的思想。
首先举个最简单的例子,有下面的式子:
$$s_t = f_\theta(s_{t-1}) \quad(10.1)$$
这是一个时序的函数。式子的意思很简单,时刻t的状态s跟前一个时刻t-1的状态s有关,具体的关系是函数f,函数的参数是$\theta$。这个式子可以对应下面这张图:
Alt text

接下来在第一个例子的基础上稍微加点东西,引入第二个例子:
$$s_t = f_\theta(s_{t-1}, x_t)\quad(10.2)$$
这个式子加了$x_t$,也就是说当前时刻t的状态s除了跟前一时刻t-1的状态有关之外,还跟当前状态的输入$x_t$有关,也可以用一张图来表示:
Alt text
但是我们注意到,由于是时序的,其实上面这个等式可以改写:
$$s_t = g_t(x_t, x_{t-1}, x_{t-2}, … , x_2, x_1)\quad(10.3)$$
也就是其实$s_{t-1}, s_{t-2}$这些都是中间状态,最终都可以用$x_i$来表示的。

  • 参数共享
    10.3这个式子其实不太好,因为对于不同长度t的输入,$g_t$函数都不同,那么对于不同长度的输入都需要训练才能都到不同的$g_t$函数,这既麻烦又浪费参数存储空间。所以其实还是10.2式更好,因为它们共享了参数$\theta$
  • 展开为流图
    Alt text
    上图的左边和右边都可以用来表示公式10.2, 左图就是一个简单的基于时序的RNN了,但是不太好理解。而右图就是把这个RNN模型展开成流图,这样就可以方便我们理解了!右图有三列,分别表示时刻t-1, t, t+1时的情况,直观地反映了时序的概念。
    注:左图循环边上面的那个黑色方框表示就表示这条边是有一个时间的延时,体现了时序的意思。

10.2 Recurrent Neural Networks

下图显示了一个vanilla recurrent network:
Alt text
其中U,V,W这三个权重矩阵分别表示输入层到隐藏层、隐藏层到输出层、隐藏层到隐藏层的权重矩阵。
下面的公式可以表示vanilla recurrent network:
Alt text (10.4)
损失函数是把所有时间序列上的损失加起来:
Alt text (10.5)

下面这个图是另外一个RNN的例子:
Alt text
这个模型比刚才那个模型要差一点。因为我们看到,这个模型每次是把当前的$o_t$也就是预测值传到下一时刻,而刚才那个vanilla recurrent network是把$s_t$(相当于这个模型中的$h_t$)传递到下一时刻,这样一来其实是会损失一些重要信息的。
总之呢,RNN需要存储足够时长的序列,才能保证预测下一时刻的准确性。

下面这个图为例子,说我们需要把输出也传递到时序中去(上面两个例子都没有这么做):
Alt text
在训练时,由于我们有标记$y_t$,所以我们把标记$y_t$进行时序的反馈(点线标出);在做预测时,我们是没有标记的,而只有估计值$\widehat{y}_t$,那么我们就把估计值进行时序反馈(虚线表示)。
我们把前面一种叫做teaching forcing(非常形象,因为训练的过程就是teaching的过程),这个阶段我们是把target,也就是$y_t$进行反馈的,而不是我们的预测值(这可能就是forcing的意思)。后面一种叫做generate(认知),测试的过程就是预测的过程,认知的过程。

但是在teaching forcing的时候有一个缺点,就是当网络如果使用了open-loop模式的时候(网络的输出作为下一时刻的输入),那么训练时和测试时网络的输入将会非常不同,这将导致认知的过程变得很差。
有两种解决这个问题的办法:

  • 第一种办法就是同时训练teacher-forced inputs 和free-running inputs。这样子的话网络可以把训练中没有学习到的情况也综合进来,使得认知的过程更准确。
  • 另外一种方法是Bengio在2015年中提出来的,就是尽量减小训练过程和测试(认知)过程的差距。具体来说,在训练过程中随机地挑选预测出来的值或者实际的标记值两者中的一个作为下一时刻的输入,然后通过一个学习策略逐渐地多使用预测出来的值作为输入。

10.2.1 RNN中梯度的计算

使用 Back-Propagation ThroughTime (BPTT) algorithm算法来计算RNN中的梯度。理论上,当我们知道了如何计算梯度,我们就可以用一般的方法来训练RNN了。
这里的公式推导自己看不懂。希望以后能回过头来再看一下。

10.2.2 把RN看成认知的有向无环图模式

讲到目前为止,我们还没有说明跟输出$o_t$相关的损失函数$L_t$是怎样的。因为RNN在具体应用上是多种多样的。在本节中,我们把RNN模型表示为观测序列的概率分布。
如果像公式10.5那样,用log近似预测训练目标,那么我们其实就是建立了一个条件概率分布用过去时刻s的$x_s$和$y_s$来预测当前时刻t的输出$y_t$(跟HMM很像,不过放宽了第一个条件,前文有提到的)。我们可以把RNN模型看成是一个有向图模型。我们要建立$x_t$和$y_t$的联合概率。

为了初学者更好理解,我们把模型弄的简单一点(就是10.1中的第一个图而不是第二个图),每个时刻的输出当作下一个时刻的输入,这样一来就没有额外的输入了。
通过下面这个式子来计算:
Alt text
上面这个式子就是马尔可夫链嘛,可以从前到后递推地计算出来。
然后损失函数也就相应地可以具体来表示了(这里有点像熵的意思):
Alt text
在一般的有向图模型中,$x_t$可以用它前面时刻的一部分来表示(不用到前面的所有时刻),但是在RNN中,我们必须用到前面所有的时刻。在有向图中通过去掉弧可以做到刚才说的,但是在RNN中,我们通过引入状态变量,这个状态变量可以用来表示之前所有时刻的输入信息(其实就是上文的公式10.2和10.3嘛),这样引入$s_t$之后,条件概率里面的参数就跟时间t没有关系了,而$s_t$是只跟维度有关的。这样做的代价就是优化参数的时候会比较困难。
总之,用公式表达就是:
Alt text
把后面那个条件用$s_t$来替代:
Alt text
注意这样一来,函数$f_\theta$会忽略$x_{t-k}$之前的那些x。这样做其实是很合理的。
我们用图来好好理解一下上面的东西:
Alt text
上面这个图显示了一个感知时候的RNN网络模型,每个$x_t$即作为输入,又作为标记。输出$o_t$通过条件概率$P(x_{t+1} | x_1, …, x_t) = P(x_{t+1} | o_t)$来编码生成$x_{t+1}$。损失函数$L_t$跟预测输出$o_t$有关。在训练模式的时候,通过观测序列x,最小化损失函数之和来训练模型。在预测模式时,加入了虚线箭头,也就是把当前时刻的输出作为下一时刻的输入。

最后一个比较重要的事情就是模型要知道什么时候结束(之前Hinton讲从wiki上训练数据来让模型自动预测句子的例子,模型不能让句子一直造下去吧,肯定要在适当地方结束这个句子)。有很多种方法可以来实现。

  • 有一种方法是添加一个特殊的符号标志来标记序列的结束。当这个符号标记生成出来的时候,表示一个完整的序列就生成了(一个句子结束了)。这个特殊符号在一个序列中当然只能出现一次了,而且只能在最后作为$x_T$出现。我们可以通过训练一个二项分布的输出来实现。举例来说可以使用非线性的sigmoid输出和交叉熵损失(不明白具体怎么实现)。
  • 另外一种方法是单独给T建模,使用还剩多少时间t的方式。这样我们就把$P(x_1, …, x_T)$分解成了$P(T)$和$Px_1, …, x_T | T)$。总之呢我们在生成一个完整的序列的时候不仅仅要考虑$x_t$,还要考虑序列的长度T,不管是隐式的还是显式的。

10.2.3 用RNN来表示条件概率分布

这一部分自己也不是能够深刻地理解。

10.3 双向RNN

前面的讨论中,RNN时刻t的状态只跟$x_1, …, x_{t-1}$有关,然而在很多情况下,我们需要当前时刻的状态跟整个序列的状态有关(也就是说要看未来的状态)。例如在语音识别中,由于语法之间是有上下文关联和依赖的,所以对于当前状态的预测我们可能希望通过未来的状态来更好地预测。这在手写识别这个问题中也同样存在。
为了解决这个问题,提出了Bidirectional recurrent neural networks(双向RNN)。这个模型在语音识别,手写识别和生物信息学中获得了成功。
Alt text
双向RNN把前向RNN和后向RNN结合了起来,这就使得输出$o_t$可以把过去的信息和未来的信息都结合起来,而不需要指定关于t的固定窗口大小。
这个想法可以应用到2D的图像中来,通过建立四个不同方向的RNN(上下左右)来计算最能捕捉局部信息的表示,通过也可以考虑到周边输入的影响。

10.4 编码-解码 序列到序列架构

这节我们讲讲如何用RNN来实现输入和输出序列不等长的情况。比如在语音识别,机器翻译,自动问答中就会出现这样的情况。
有两个人几乎同时在2014年各自独立地提出了类似的架构,看下面这个图:
Alt text
下面这个RNN叫做编码RNN,用来处理输入;上面那个是解码RNN,用来处理输出。注意到编码RNN的最后一个隐藏状态会计算出一个固定大小(应该是向量维度的大小)的变量C,这个C里面表示了整个输入序列的信息,然后交给解码RNN进行计算。
这其中蕴含的思想很简单:

  1. 编码RNN处理输入序列,从最后一个隐藏状态中产生C来表示这个输入序列
  2. 解码RNN受限于这个C,然后产生输出序列。

这两个RNN联合起来训练,通过所有的训练样本$(X,Y)$,使得平均的$P(Y=Y|X=X)$最大化。通过这种架构的方式,输入序列和输出序列就不用等长了。
但是这样做有一个缺陷,就是当编码RNN的输出C的维度太小的时候,它不足以表示一个很长的输入序列X。那么一个改进的方法就是让C的维度可变,而不是固定大小。

10.5 Deep RNN

在计算大多数的RNN时,参数都可以分解成为三个部分,进行相互联系的转换:

  1. 从输入状态到隐藏状态
  2. 从前一个隐藏状态到下一个隐藏状态
  3. 从隐藏状态到输出状态
    前两条是输入和先前状态到下一个状态的转换。在之前讲的那个vanilla RNN架构中,这三个部分的每一个部分都各自有一个单独的权重矩阵(之前那个图的U、V、W)。
    那么我们自然就想到,能不能把这三个部分的一个或几个多做几层变成Deep的呢?很多实验证明,这样做的效果是显著的。
    上图:
    Alt text
  • 左图是把隐藏状态多了多层,这里有两个隐藏层,也就是h 和z。两层之间是相互连通的。(但是这里要把连接做一些限制,就像CNN一样,不然复杂度太大了)。跟CNN类似,我们可以认为底层的隐藏层进行了一些底层输入的转换,而高层的隐藏层进行了更加抽象,更加近似的转换。
  • 中图在对上面每一个部分都做了deep(但是这个图看不太懂)。但是这样做有一个很大的缺点:就是最短的那条路径从时间$t$变到了时间$t’$,而时间$t’$远远大于$t$。在依赖关系很大的模型上,计算就变得困难了。
  • 右图是中图的改进,通过隐藏层之间的跳跃连接(skip connections)来改善中图中出现的缺点。

10.6 Recursive Neural Networks

前面已经提到了,recursive NN 是比recurrent NN更加通用的一种模型结构。一言以蔽之,就是之前的recurrent是链表形状的结构,现在我们把结构扩展了一下,recursive就是一种树形的结构。这种结构在自然语言处理和计算机视觉上也获得了成功。
Alt text
上面这个图就是一个树形结构的recursive NN模型了。输入是可变长的,而输出是固定长度的。上图是一个有监督学习的例子,对于整个序列x有一个标签y。
recursive nets有一个好处就是对于同样长度的输入序列,深度从N变成了o(logN),这就跟树的形态有关了。一种方案是用平衡二叉树,另外一种方案是使用其它方法,例如自然语言语法分析器。最理想的情况是我们想让模型自己去推断,对于不同的输入,构建不同的树形。
下面就介绍了不同的人提出的不同的recursive网络的想法。