第十章 序列建模:循环和递归网络
循环神经网络(RNN)是一大类用于处理序列数据的神经网络的总称,类似于卷积网络是一种专门用来处理网格数据(比如图片)的神经网络,循环神经网络是专门用来处理序列值 (x^{(1)},...,x^{(p)}). 卷积神经网络能很容易扩展到处理有非常大值的长和宽,而且一些网络还能处理变长的图片,与之相对应的循环神经网络也能比一般的没有对序列优化的神经网络处理更长的序列输入。
为了从多层感知机引申到循环网络,我们需要利用在二十世纪八十年代左右在机器学习和统计领域中提出的一个思想:在模型的不同部分共享参数。
参数共享使得模型能够应用和推广到不同格式的数据上(在rnn中,不同的数据是指不同的长度)。如果对每个值在不同时刻都有不同的参数,那么就不能推广到训练集中未见过的序列长度,也不能共享不同长度序列的统计特性,而且也不同共享一个序列上不同位置的特性。 而这种共享对于在一个序列中多次出现的特性至关重要。 比如考虑两个句子:"I went to Nepal in 2009" 和 "In 2009, I went to Nepal",现在的任务是提取出两个句子中去 Nepal的年份,我们希望提取出2009这个信息,但是2009在两个句子中的位置不同,不管是第一位还是第六位我们都希望能够提取出来,如果是用一般的定长的前馈神经网络,其对于不同位置的参数不同,需要学习这句话在不同位置的规则?与之相对应的是,RNN在不同的时间点处共享权重。(这段话中的例子比较费解)
一个相关的想法是用一维的卷积来做,这种卷积方法是 time-delay neural networks的基础。这种卷积操作允许模型在一定的范围内共享参数,但是共享地比较浅。与输入相对应的输出是关于该输入相邻节点的函数,参数共享相当于在在每一个时间点都共享一个卷积核。 RNN使用不同的方法共享参数,每个output都是给定上一步的输出同时用相同的更新规则更新的。这种循环的方式使得参数在一个非常长的计算图中共享。
一种简单的解释是,假设RNNs操作的数据对象是(x^{t}) ,其实(t)是time index,并且(t)的范围是1到( au). 实际上,RNN通常的数据是minibatch大小的这样的sequence,而且其中每个序列的长度可能都不同。这里我们为了简化标记而去除了minibatch的索引。进一步地,序列中的每一步并不一定都代表时间,只有是代表序列中的不同位置就可以了。RNN也可以应用在二维数据中,比如图像,
本章在前面计算图的基础上引入了环,这些环代表了当前参数本身在未来会影响其自身的值。这种计算图允许我们定义了RNN,并且还介绍了多种构建、训练和使用循环神经网络。
10.1 展开计算图
计算图是一种对计算过程的形式化的表示,这一小节主要介绍将循环和递归的计算展开成一个含有重复结构的计算图,并且这个图是链式结构,而且会共享参数。
考虑传统的动态系统: (s^{(t)} = f(s^{(t-1)}; heta)) , (s^{(t)})也称为系统的状态。这个式子可以看作是循环的,因为当前时刻的状态依赖于上一时刻的状态。
对于有限的时间步( au)来说,计算图可以通过递归地应用上述公式从而对图展开,假设( au = 3) , 带入公式得:(s^{(3)} = f(s^{(2)}; heta) = f(f(s^{(1)}; heta); heta)) ,对这个等式递归的调用递归式能获得不包含递归的计算图。
考虑动态系统中还受到外部信号(x^{(t)})的影响
(s^{(t)} = f(s^{(t-1)},x^{(t)}; heta))
现在的状态包含了所有过去的状态。RNN中hidden state的状态转移方程如下:
之所以称为hidden state是因为每一个RNN还要有一个输出层用来提取对应hidden state中的信息。
当RNN在做序列预测任务时,通常用(h^{(t)})作为过去(t)时刻序列中包含的信息。但这个过程通常是有损失的,因为一个固定长度的变量(h^{(t)})就包含了变长序列((x^{(1)},x^{(2)},...,x^{(t)})) 中的信息。取决于具体的训练标准,(h^{(t)})可以选择前面序列中能提高预测精度的那些信息. 比如在利用RNN预测句子中下一个单词时,一篇文章是由很多句子组成,我只需要保存当前句子中前面的单词就行了,而不需要保存前面所有句子的信息。
式10.5可以有两种表示图表示方式,如图10.2,一种是左边的有环图表,另一种是右边的展开形式。其中左图中的黑色方块表示延迟一个time step,
采用展开形式有两个主要的优点:
- 不管输入序列有多长,每个节点的输入大小是固定的,只需要将hidden state一直往后传递就可以了,输入并不是变长的序列长度
- 在每个time step中,可以采用相同的transformation function 和 相同的参数。
在所有的时间点采用相同的函数及其参数有两个主要因素:
- 采用相同的函数使得模型能够应对任意长度的输入序列,如果采用不同的函数则不能推广到训练数据集中没有的序列长度的情境下。
- 而且参数共享能使得模型在训练数据集小的情况下学习到较好的参数。
包含循环的图和展开图都有各自的优点,循环图看起来简洁,展开图能清晰地展示计算过程,而且能很好地说明信息在网络中的流动路径。
10.2 循环神经网络
基于计算图的展开以及参数共享两个特性,我们可以设计各种各样的循环神经网络。
常见的RNN结构:
- RNN 在每个时刻都有一个output,而且hidden states之间循环连接。如图10.3所示。
- RNN在每个时刻都有一个output,但每一时刻的输出与下一时刻的hidden state连接
- RNN中hidden states之间循环连接,但是只有最有一个output
其实大部分RNN都是第一种结构,即每一个time step 都有一个output,而且hidden states之间循环连接。
接下来我们基于图10.3给出RNN的前向传播公式(forward propagation equation),图中并没有指定激活函数和损失函数的种类,这里假设激活函数为 hyperbolic tangent(双曲正切), 假设output类型是离散的,因为RNN经常处理的问题是预测单词,常用的方法是假设output为 unnormalized log probabilities of each possible value of the discrete variable. 最后对output做softmax,从而输出概率最高的单词。网络刚开始初始化一个hidden state (h^{(0)}), 之后对于时刻(t (1<t< au)) 有:
其中(W)是连接上一步hidden state的权重,(U)是当前输入(x^t)的连接权,V是hidden to output, (b)和c是偏置。
这个例子中输入的长度与输出长度相等。总的损失就是各个time step的损失和
其中(p_{model}(y^t|x^1,...,x^t)) 表示在ouput中该样本真实的值(y^t)所对应位置的概率值(经过softmax函数后,y各个位置上的值可以看作概率,这里采用了cross-entropy 损失函数)
在训练过程中,每次都要先forward propagation,然后backward从而计算梯度,这个过程不能并行化,因为它是串行的序列(从展开的计算图可以看出), 而且每一个time step计算的相关参数需要保存,因为backward的过程会用到,因此空间复杂度也是(O( au)),有没有什么方法能降低复杂度呢?
10.2.2 Teacher Forcing and Network with output Recurrence
(Teacher forcing 不太懂怎么翻译)
前面说到还有一种rnn的hidden 与 hidden state之间并不连接,而是output与hidden state连接,但是这种结构相比hidden to hidden 的结构是 不够powerful(怎么翻译?牛逼?)的,我们希望output能包含前面序列中的信息,但是output被训练为与样本的label吻合,因此,output不能包含前面序列的信息,除非用户知道怎样去描述序列的信息并且将信息嵌入到样本的label中。
但优点是每一步的hidden state是相互独立的,更新的时候可以单独更新,因此训练可以并行。而且不需要将(o^t)输入到(h^{t+1})中,可以直接输入true label (y^t).
对于将true label直接输入到模型中的模型可以采用 teacher focing 的方式训练。 Teacher forcing 最早来自于maximum likelihood criterion, 在这个模型中真实标号(y^t)作为(t+1)时刻的输入。这里( au=2)为例说明这个过程。条件最大似然为(conditional maximum likelihood criterion):
在这个例子中我们发现当(t = 2)时,模型的目标是最大化(y^2)给定(x,y^1)的条件概率,最大似然就是要在训练过程中将true label输入到下一步的输入中。
teacher focing 最开始的引入是希望能在不包含hidden 与 hidden 连接的模型中避免 back-propagation, 其实它也可以应用在含有hidden 循环连接的模型中,只要这个模型中存在output 到hidden的循环连接,只不过这候的优化需要结合teacher forcing 和 BPTT.
teacher forcing 的结构如图 10.6所示。
10.2.2 在RNN中计算梯度
已知:
而且: (L = -sum_tlog(hat{y}^t_j)) ,其中(j) 表示(t)时刻真实的label.
而且由softmax函数可得:
所以有:
或者
综上可得:
根据反向传播原理,假设序列的最后一个时刻为( au),(h^{ au})唯一的后继变量是(O^{ au}),因此有:
之后将梯度依时间向前传播(从 t+1 时刻传播到t时刻),由于(h^t)影响了(h^{t+1})和(O^t),因此有:
当关于内部节点的梯度计算好之后,下一步就是求损失函数关于参数的梯度。因为参数在各个time step都是共享的,所以我们在做微分运算时一定要小心谨慎不要出错。
10.2.3 从有向图模型的角度来看待RNN
在之前的例子中,损失(L^{(t)}) 计算的是样本真实标号(y)与模型输出(o)之间的交叉熵。与前馈神经网络类似,循环神经网络理论上也可以使用任意其它损失函数。而且与前馈神经网络类似,我们通常假设样本输出服从一种概率分布,因此可以用交叉熵衡量预测的分布与真实分布之间的差异度。最小平方损失就是假设当输出现呈单位高斯分布时的交叉熵损失。
当我们用对数似然函数作为训练目标时,我们实际上是在训练RNN估计给定t时刻之前的序列来估计(y^t) 的条件概率分布(拗口。。)。这意味着我们要最大化如下对数似然:
当然如果模型中有将前一步的outout输入到下一步的输入中的结构,我们就是要最大化如下对数似然函数:
当我们不把过去(y)值作为下一步预测的条件时,过去的(y^i)当前的(y^t)之间并没有边连接,因此,各个时刻的(y)值在给定(x)值的条件下是条件独立的。
balabala 。。。
将有条件依赖的序列建模成RNN
balabala
感觉这两小节讲的东西有点奇怪,相当于从图模型的角度来解释RNN。有点方
10.3 双向RNN
我们之前讨论的RNN结构都比较“随意”(causal),就是说时刻(t)的状态只含有输入序列(x^1,...,x^{t-1},x^t)的信息。而在双向RNN中,(t)时刻之后输入的序列也能影响到(t)时刻的状态。
在有些场景下,我们希望每个时刻的hidden state都包含了整个序列的信息,而不仅仅是该时刻之前输入序列的信息。比如在语音识别中,如何正确发出当前字符的读音常常也要取决于后面几个字符,因为有些字符单独发的音和与别的字符连在一起发的音是不同的。还有很多其他序列预测任务也是这样需要后续序列的信息的。比如手写字符识别,语音识别等。
如图10.11,RNN的结构包含两个hidden state的序列,一个是forward的,另一个是backward的。
从图中可以看出,计算(o^t)利用到了前向和后向的隐藏状态,所以相当于利用了整个序列的信息。如果利用传统的前馈神经网络想利用未来的信息,一般是设定一个滑动窗口,当前状态的输出利用了滑动窗口中的状态,也就相当于只看到了未来几步的信息,这个步数是固定的。
双向RNN这个想法可以很容易推广到二维输入序列中,比如图像数据,可以称双向RNN在二维数据中的推广模型为四向RNN,当前位置的output取决于上下左右四个方向上序列的数据。
10.4 编码-解码 或 序列-序列的架构
图10.5给出了如何将一个序列映射成一个向量的RNN结构,图10.9给出了如何将固定长度的向量映射成一个序列的RNN结构,我们之前主要介绍的RNN结构是输入序列与输出有相同的长度。这一小节我们主要介绍如何构造网络使得输入序列与输出有不同的长度,在很多应用中输入与输出的长度都不想等,比如语音识别,机器翻译以及智能问答系统,这些系统中输入与输出通常都不是相同的长度(虽然他们的长度是有联系的,比如机器翻译中的输入越长,其输出也应该越长)。
我们通常把输入到RNN的序列称为"context"(上下文),我们希望利用RNN获得这些输入数据的一个表示,这个表示记为C,C可能是一个向量或者是一个向量序列 (X = (x_1,x_2,...,x^{nx}))。
最简单的将变长的输入序列映射成变长的输出序列的RNN结构最早是由Cho(2013)等人提出来的,后来Sutskever等(2014)也独立地发明了这个结构,并第一个利用这个结构在翻译任务中获得state-of-the-art的效果。这个结构通常被称为 编码-解码(encoder-decoder) 或者是 序列-序列(sequence to sequence) 结构,其实这个想法非常简单:(1) 一个encoder 或者是 reader 或者是 input RNN 来处理输入序列, 然后encoder输出一个 context的表示(representation)C, 通常是关于hidden state的一个函数。(2) 一个解码器 或者是 writer 或者是output RNN,能基于固定长度的vector生成输出序列 (Y=(y^1,y^2,...,y^{n_y})), 这个方法的创新点是输入的长度和输出的长度 (n_x,n_y)可以互不相同,而之前介绍的结构中 (n_x = n_y= au),在我们新的sequence-to-sequence 结构中,这两个RNN需要同时训练(jointly training)并最大化在训练集中所有样本(x,y)序列对 的(logP(y^1,..,y^{n_y}|x_1,...,x^{n_x})),encoder的最后一个hidden state (h^{n_x})通常就作为输入序列的representation C,而且C也作为输入输入到decoder中。
如果C是一个向量,那么decoder就是一个vector-to-sequence的RNN,而这种RNN结构由两种实现方式,一种是将C作为初始的hidden state (h_0),另一种这个输入C与所有时刻的hidden state都相连接。
这种结构一种很明显的缺陷是encoder的输出 C的维度太低,从而不足以概括encoder的输入序列中的信息,这种问题最早是Bahdnan(2015)等人在机器翻译任务中发现的。 他们提出对于C学习一个变长的序列,而不是固定长度的向量。 另外,他们还提出了 attention machanism 将C的元素与output中的序列联系起来,更具体的说明请参看 Section 12.4.5.1
10.5 深度循环网络
RNN网络中的计算可以分解成下面三个部分:
- input to hidden
- hidden to hidden
- hidden to output
从图10.3中可以看出,每一个部分只需要学习一个权重矩阵和一个偏置项就可以了,相当于每个部分都做了一个shadow的transformation,相当于一个多层感知机中的一个单隐层。那么能不能将这些操作变深?实验证明是可以的,而且是有效的。
图10.13展示了深度循环网络的一个发展过程,最左边表示将hidden state 层次化,使用了两层的hidden state,高层的hidden state能学习到低层hidden state更高级的表示。中间的结构表示分别在input to hidden , hidden to hidden和hidden to output加了一个单隐层,将三个部分的结构都加深了,这样又产生了一个问题,就是hidden to hidden之间多了一层,这样就导致(h^t)到(h^{t+1})之间多了一层,会导致优化变得困难,因此图10.13的最右边相比于中间的结构多了一个skip-connection操作(其实我没懂的是为什么仅仅hidden to hidden 之间有skip-connecttion,而input to hidden 和 hidden to output都没有)。
10.6 深度递归网络
递归神经网络是循环神经网络的一种推广,递归神经网络的计算图是一颗树,而不是类似于循环神经网络的链状结构,一种典型的递归神经网络结构如图10.14所示,递归神经网络成功地应用在了自然语言处理,计算机视觉等领域中。
递归网络相比于循环网络的一个显著的优势是对于一个长度为( au)的序列,循环网络的深度是( au) ,而递归网络的深度是(O(log au))。这使得递归网络适合处理长序列问题。一个开放的问题是怎样选择树的结构,一种选项是选择一个不取决于数据类型的结构,比如说二叉平衡树。在很多应用领域外部的方法可以帮助确定合适的树结构,比如当处理自然语言处理问题时,递归网络的树结构可以由自然语言处理方法解析获得的语法树决定。理想状态下,我们希望学习器能针对不同的输入自己学习到合适的树结构 (Bottou 2011)。
递归神经网络可以有很多种变种,比如Frasconi(1997,1998)等将数据与树结构联系在一起。(后面这一小段需要结合论文理解,盲目翻译可能会导致理解误差)
Long-term 依赖的挑战
从数学的角度看,long term 依赖的主要问题是在反向传播过程中发生的梯度消失和梯度爆炸,即使我们假设循环网络中的参数是稳定的(stable,即不会发生梯度爆炸),long-term 依赖相比于short-term 依赖的主要问题是指数缩小的权重(因为long-term包括jaconians矩阵的连乘)[感觉这句话是废话,之前不是说了问题主要是梯度消失和梯度爆炸吗?那除了梯度爆炸,剩下的当然是梯度消失了。。]。 很多相关论文提供了如何应对long-term问题的办法。在这一小节中,我们详细地介绍这个问题,下一节再给出解决问题的方法。
循环网络的结构由很多相同的函数组成,每个时间步一个。这种结构会导致模型极度非线性,如图10.15所示。
特别地,RNN结构中包含的函数类似于矩阵乘法,下式是一个简单的不包含激活函数且不包含输入(x)的循环关系式:
我们换一个写法,假设当前时刻是(t),则有:
如果对(W)做特征值分解: (W=QDelta^TQ),其中(Q)是正交矩阵,那么循环方程可以简化成:
那么当特征值小于1时,(h^t)就会渐变成0,当特征值大于1时,(h^t)就会变得非常大,(h^0)中不是与较大特征值对应的成分都将会消失。
这个问题在RNN中尤其显著。先考虑标量情形,当一个标量(omega)乘以自身很多次时,其乘积(omega^t)或者是消失或者是爆炸,这主要取决于(omega)的大小。但是,如果我们构建一个非循环神经网络,这个网络在每个时间点都有不同的(omega^{(t)}),如果初始状态是1,那么在时刻t的状态就是(prod _tomega^{(t)})。假设每一步都随机生成一个当前输入(omega^{(t)}),而且生成的数服从均值为0方差为(v)的分布,因此连乘的的方差为(O(v^n)),为了获得所期望的方差(v^*),我们在每一步生成随机输入时采用的分布的方差为(v = ^{n}sqrt{v^*}). 对于非常深的前馈神经网络,如果每一步都选择合适的分布生成输入,是可以避免梯度消失和梯度爆炸的(Sussillo 2014)。
RNN的梯度消失和爆炸问题是由不同的研究者独立发现的(Hochreiter,1991;Bengio et al. 1994,1994),一些人希望通过使参数一直保持在这样一个参数空间中:参数既不会消失也不会爆炸。不幸的是,为了使得参数对一些扰动鲁棒,RNN的参数必须要进入最终会发生梯度消失的参数空间(Bengio et al. 1993,1994). 特别地,不管是什么模型,只要它能表示long-term的依赖关系,long-term的梯度都会指数小于short-term的梯度。这并不意味着long-term就是不可能学习了,但是学习long-term依赖关系需要很长时间,因为long-term依赖之间的信号非常容易因为序列前面的一些波动而被屏蔽(有点不通。。大致意思是当序列变长时,序列靠后的部分的信号传递不畅). 在实践中的实验(Bengio et al. 1994)表明当我们增大long-term 的依赖范围,使用基于梯度的优化方法优化RNN变得非常困难,当序列长度是10或者20时,使用SGD优化RNN成功的概率就接近于0了(这是怎么解决的呢???)。
本章接下来的部分就介绍使用不同的方法去克服学习long-term 依赖的困难(在一些场景下,RNN的序列长度可以达到上百个)。虽然已经有了一些方法克服这个问题,但是这仍然是深度学习的挑战之一。