文档中的词很少是完全独立的,它们的出现会影响文档中的其他词或者收到文档中其他词的影响:
The stolen car sped into the arena.
The clown car sped into the arena.
这两句话可能会产生两种完全不同的情感感受。这两个句子的形容词、名词、动词、介词短语结构式完全相同的,但位于句首的形容词极大地影响了读者的推断。
如果能有一种方式“记忆”之前时刻发生的事情(尤其是当t+1时刻时,t时刻发生的),我们就能捕获当序列中某些词条出现时,其他词条相对应会出现的模式。循环神经网络(RNN)使神经网络能够记住句子中出现过的词。
隐藏层中的单个循环神经元会增加一个循环回路使t时刻隐藏层的输出重新输入到隐藏层中。t时刻的输出会作为t+1时刻隐藏层的输出。而t+1时刻的输出接下来又会被作为t+2时刻的输入,以此类推。
尽管根据时间变化影响状态的思想一开始可能会让人感觉有些困惑,但其基本概念简单明了。对于传入一般前馈完了的每个输入,我们在t时刻得到的网络输入会作为网络的一个额外输入,与下一个t+1时刻的数据一起输入网络。这样,我们就可以告诉前馈网络之前发生了什么和“现在”正在发生什么。
在循环网络中,整个循环是由一个或多个神经元组成的前馈网络层。网络隐藏层的输出和普通输出一样,但它本身会和下一个时刻的正常输入数据一起作为输入回传进网络。这个反馈表示为从隐藏层的输出指向它的输入的箭头。
理解这个过程的更简单的方法是展开这个网络。下图从新的角度,展示了网络随时间变量(t)展开两次的图形,显示了t+1时刻和t+2时刻的网络层。
每个时刻由完全相同的神经网络展开后的一列神经元表示。就像在时刻中查看每个样本的神经网络的剧本或视频帧一样。右侧网络是左侧网络的未来版本。在一个时刻(t)的隐藏层的输出被回传到隐藏层已经用作右侧下一个时刻(t+1)的输入数据,如此循环往复。上图显示了这一展开的两次迭代,因此对于t=0、t=1、t=2,由3列神经元。
这个可视化中的所有垂直路径都是克隆的,或者说是完全相同神经元的视图。它们在时间轴上是单个网络表示的。当讨论信息在反向传播算法中是如何在网络中前向和反向流动时,这种可视化非常有用。但是,当我们观察折3个展开的网络时,要记住它们都是同一个网络的不同快照,只有一组权重。
放大一个循环神经网络展开前的原始表示,可以揭示输入和权重之间的关系,如下图:
处于隐藏状态的每个神经元都有一组权重,它们应用于每个输入向量的每个元素,这和一般的前馈网络一样。但是,现在我们有一组额外的可训练权重,这些权重应用于前一个时刻隐藏层神经元的输出。当我们逐个词条的输入序列时,网络可以学习分配给“过去”的事件多少权重或重要度。
回到数据,假设我们有一组文档,每篇文档都是一个带标签的样本。对于每个样本,不同于一次性将词向量集合传递进卷积神经网络(下左图),这次是从样本中一次取一个词条并将其单独传递到RNN中(下右图)。
在循环神经网络中,我们传入第一个词条的词向量并获得网络的输出,然后传入第二个词条的词向量,同时也传入第一个词条的输出;然后传入第三个词条的词向量以及第二个词条的输出,以此类推。网络中有前后概念和因果关系,以及一些模糊的时间概念。
随时间反向传播算法
循环神经网络也有一个标签(目标变量),但并不是说每个词条都有一个标签,而是每个样本中的所有词条只有一个标签。也就是说,对于样本文档,只有一个标签。
这里,我们开始会在最后一个时刻查看网络的输出,并将该输出与标签进行比较。这就是对于误差的定义,而误差是我们的网络最终想要尽量减小的目标。对于给定的数据样本,我们可以将其分成较小的片段,这些片段按顺序进入网络。但是,我们并不直接处理这些由“子样本”产生的所有输出,而是将其反馈给网络。
我们只关心最终的输出:将序列中的每个词条输入网络,并根据序列中最后一个时刻(词条)的输出计算损失,如下图:
对于给定样本的误差,我们需要确定哪些权重需要更新已经需要更新多少。我们可以输入样本序列中的各个词条,并根据之前时刻的网络输出计算误差,但是这也正是不能在时间序列上引用反向传播算法的原因。
可以这样来考虑:将整个过程视为基于时间的。我们在每个时刻取一个词条,从t=0处的第一个词条开始,将它输入当前的隐藏层神经元(如上图的下一列),当这样做时,网络会展示并揭开下一列,为序列中的下一个词条做好准备。隐藏层的神经元不断展开,一次一个,就像是音乐盒或钢琴的演奏。当我们到达终点,在输入样本的所有片段之后,网络将停止展开并且我们将获得目标变量的最终输出标签。我们可以使用该输出来计算误差并调整权重。这样,我们就完成了这个展开网络计算图的所有环节。
此时可以将整个输入视为静态的。通过计算图我们可以看到各个神经元分别送入了哪个输入。一旦知道各个神经元是如何工作的,我们就可以循着之前的方法,像在标准前馈网络中做的那样运用反向传播。
我们使用链式法则反向传播到前一层。但是,不同于传播到上一层,这里是传播到过去的层,就好像每个展开的网络版本都不同(如下图),数学公式是一样的。
将反向传播在最后一个时刻获得的误差,对于每个“较早”的时刻,都要执行更新时刻的梯度,对于该样本,在计算了所有词条的梯度之后,我们将聚合这些校正值并将它们应用于整套权重的更新,直至回到时刻t=0。
不同时刻的权重更新
通过将看似奇怪的循环神经网络转换为类似于标准前馈网络的东西,权重更新将会变得相当简单。但这里还有个问题:更新过程中棘手的部分是我们正在更新的权重不是神经网络的不同分支,每个分支代表着位于不同时刻的相同网络。各个时刻的权重是相同的。
一个简单的解决方案是计算各个时刻的权重校正值但不立即更新。在前馈网络中,一旦为输入样本计算了所有梯度,所有权重的校正值就会被计算。这对循环神经网络同样适用,但对该输入样本我们必须一直保留这些校正值。直至回到时刻t=0。
梯度计算需要基于权重对误差的贡献值。这里是令人费解的部分:在时刻t一个权重在初次计算时对误差产生了贡献,而该权重在时刻t+1会接收到不同的输入,因此之后对误差的贡献量也会有所不同。
我们可以计算出权重在每个时刻的不同校正值,然后聚合所有校正值并在学习阶段的最后一步将其应用于隐藏层的各个权重。
对于单个数据样本,随时间反向传播算法中的单个权重在一个时刻t可能会在一个方向上进行调整(取决于其在时刻t对输入的反应),然后在时刻t-1在另一个方向上进行调整(取决于其在时刻t-1对输入的反应)。但要记住,不管中间步骤有多复杂,神经网络一般都是通过最小化损失函数来工作的,所以总体来说,它会对这个复杂的函数进行优化。当对每个数据样本应用一次权重更新时,网络将确定对该输入样本来说最适合处理此任务的神经元的权重。
至关重要的输出
有时,还要关心在各个中间时刻生成的整个序列。下图展示了在任意给定时刻捕获误差的路径,并在反向传播期间使用该误差反向调整网络的所有权重。
这个过程类似于在n个时刻执行普通的随时间反向传播。当前例子中,现在正在同时从多个源反向传播误差。权重的校正值是累积的,我们从最后一个时刻一直反向传播到初始时刻,并且对每个权重计算要更新的总数,然后对于在倒数第二个时刻计算出的误差进行同样的处理,并将反向进行处理知道时刻t=0将所有的校正值加起来。重复这个过程,知道回到时刻t=0,然后继续反向传播,此时要聚合的值只有一个。接着,我们可以将更新的总和一次性地应用于相关隐藏层的所有权重。
上图中,可以看到误差从每个输出反向传播到t=0,并在最后对权重应用更新之前进行聚合。与标准的前馈网络一样,对于该输入(或一组输入),只有在计算了整个反向传播步骤中各权重需要更新的校正值之后,我们才会更新权重值。在循环神经网络的情况下,这种反向传播包含了所有时刻到t=0的更新。
较早地更新权重会较早的“污染”反向传播中的梯度计算。要记住梯度是根据特定的权重计算的,所以如果要提前更新它,例如在时刻t,那么当计算时刻t-1的梯度时,权重的值(它在网络中的权重位置是相同的)会发生变化。如果根据时刻t-1的输入计算梯度,计算将是错误的。我们将因为一个权重没有对误差做出贡献而“惩罚”/“奖励”它。
难点
经一个循环神经网络需要学习的权重(参数)可能相对较少,但是从上图中可以看出训练一个循环神经网络的代价很高,尤其是对于较长的序列(如10个词条)。我们拥有的词条越多,每个时刻误差必须反向传播的时间越长。而对于每一时刻,都有更多的导数需要计算。虽然循环神经网络的效果并不比其他网络的效果差,但是计算机的工作里还是非常大的。
神经网络虽然有了基本的记忆能力,但是当它们(网络时刻)变深,就会出现梯度消失问题(及梯度爆炸问题),它们的思想是:随着网络变得更深(更多层)时,误差信号会随着梯度的每一次计算消散或增长。
循环神经网络也面临同样的问题,因为在数学上,时刻的每一次后退都相当于将一个误差反向传播到前馈网络的前一层。由于这个问题,大多数前馈网络往往只有几层深,但是当我们要处理的是5个、10个、甚至更多词条的序列时,要深入到100层网络的底层还是很困难的。不过一个让我们可以继续工作、减轻压力的因素在于:尽管梯度可能会在计算最后一次权重的过程中消失或爆炸,但是实际上只更新了一次权重集,并且每个时刻的权重集都是相同的。仍然有些信息会传递出去,虽然它可能不是我们认为所能创建的理想记忆状态。