12.3.1 引入BPTT求解RNN
通过前面对RNN算法的工作原理与基本结构的学习,我们对RNN算法有了初
步的了解。RNN 算法从本质上来说还是一个神经网络,也是由输入层、隐藏层
及输出层组成。因此求解RNN实际上和求解普通的神经网络一样,也是求解参
数如何设置的问题。如图12-10所示,在RNN中我们需要求解U 、V 和W 这三个
参数。
其中,参数W 和U 的求解过程需要用到历史时刻数据,而求解参数V 只关
注目前时刻的数据结果,相对来说比较简单。如果我们要预测t时刻的输出,
就必须利用上一时刻t-1的信息以及当前时刻的输入,这样才能得到t时刻的输
出。为了找出模型最好的参数U 、W 和V 的取值,首先我们要知道当前参数的
结果是怎么样的,因此首先要定?一个损失函数,记作L。
RNN 每一个隐藏层都会有一个损失函数 L,因为对结果产生影响的,肯定
不止一个时刻,因此需要把所有时刻造成的损失都加起来,这样就得出了最终
的损失函数。也就是说我们需要根据每个单元计算出的总输出与目标输出之间
的误差,从网络的最终输出反向逐层回归,利用损失函数的偏导调整每个单元
的权重。现在的问题也就变成了如何求解损失函数的问题。
在第7章我们曾经讲过,通常求解神经网络损失函数的方法是BP反向传播
算法。但是由于神经网络本身的特点导致BP反向传播算法只考虑了层级之间的
关系,没有考虑层级间的纵向传播以及时间上的横向传播,因此我们需要对现
有的BP算法做一些改造,使其能够在两个方向上进行参数优化,以适应RNN模
型。
在RNN中,时间序列反向传播(Back-Propagation Through Time,BPTT)
算法是我们最常用的模型训练算法,这个算法继?了BP反向传播算法的特点,
只是针对时间序列数据做了特殊的改造。BPTT算法的核心思想与BP算法相同,
沿着需要优化的参数的负梯度方向不断寻找更优的点直至收敛。因此,BPTT算
法求解还是使用梯度?降法,我们的目标还是使用梯度?降法迭代优化损失函
数使其达到最小值,因此如何求各个参数的梯度便成了此算法的核心。
如图12-11所示,将RNN的结构展开以后会发现,其实BPTT的思路很简单,
该图中的网络结构是一个RNN的时序展开结构,所有的列表示的是同一个神经
网络,只是按照时间依次排开而已。横向的箭头表示的是时序上的联系。竖向
的箭头表示的是空间上的传播,也就是普通的前向传播过程,而横向的箭头表
示的是上一个时刻隐藏层的输出和当前时刻上一层的输出共同组成的当前隐藏
层的输入。例如t+1时刻的第l+1层,那么这一层的输入是该层的上一个时刻的
输出和当前时刻的上一层的输出。
将RNN展开之后,似乎看到了熟悉的结构。前向传播的过程就是依次按照
时间的顺序计算一次,反向传播的过程就是从最后一个时刻将累积的残差传
递回来,跟普通的神经网络训练并没有本质上的不同 。
由此我们知道,BPTT算法是针对循环层的训练算法,它的基本原理和BP算
法是一样的,可以总结为三个步骤:
(1)前向计算每个神经元的输出值,也就是计算隐藏层S 以及它的矩阵
形式。
(2)反向计算每个神经元的误差项值。BPTT算法将第I层t时刻的误差值
沿两个方向传播,一个是传递到上一层网络,这部分只和权重矩阵 U 有关;
另一个是沿时间线传递到初始时刻,这部分只和权重矩阵W有关。最后计算误
差函数E对神经元j的加权输入的偏导数。
(3)计算每个权重的梯度。最后再用随机梯度?降算法更新权重,这个
计算过程在此我们不展开?述。
12.3.2 梯度消失问题
在BPTT的帮助?,求解RNN算法终于取得了一些突破性的进展。使用梯度
?降法,能够有效避免“维数灾难”的问题,因为梯度?降法通过放大误差或
找到代价函数的局部最小值解决了维数灾难问题。这有助于系统调整分配给各
个单元的权重值,使网络变得更加精确。
但梯度?降法也随之带来了“梯度消失”的问题,什么是梯度消失问题
呢?在训练深层神经网络的背景?,梯度代表斜率,也就是说梯度越大代表坡
度越陡峭,模型越能够快速?滑到终点并完成训练。但是当斜坡太平坦的时
候,没有办法快速训练。所以这对于网络中的第一层造成特别重要的影响,如
果第一层的梯度值为0,则模型就失去了调整方向,不知道该往哪里走了,因
此也无法调整相关的权重使得损失函数最小化,这种现象就称为“梯度消
失”。简单理解就是随着梯度越来越小,训练时间也会越来越长,这类似于物
理学中的沿直线运动,在光滑的表面,小球会一直运动?去。
梯度消失会导致在训练模型时梯度不能在较长序列中传递?去,从而RNN
无法捕捉到长距离的影响。因此我们也经常说梯度消失是一个麻烦又复杂的问
题,因为它很难检测,且很难处理。总的来说,有三种方法可用来应对梯度消
失问题:
(1)合理地初始化权重值。初始化权重,尽可能使每个神经元不取极大
或极小值,以躲开梯度消失的区域。
(2)使用更合理的激活函数,这部分内容在此不展开?述。
(3)使用其他结构的 RNN,比如长短时记?网络(Long Short Term
Memory Network,LSTM),这是目前最流行的做法,?一节讲述该方法。
12.4 RNN的提升
12.4.1 长期依赖问题
RNN 的优势在于它可以将原先获得的信息用到当前任务上,每一时刻的输
出结果并非互相独立。回到上文语言模型的例子,“马儿在小溪旁喝水,一直
以来全靠这条小溪滋养了__。”在这个任务中我们不需要知道其他的句子就可
以推测出来空白处需要填“马儿”,相关信息和预测词之间的间隔非常小,就
在同一句话里,利用RNN可以获取句中开头的信息从而进行推测,如图12-12所
示。
如果我们给任务增加一点难度,使用一个更长的句子,例如“我希望有个
如你一般的人,如山间清爽的风,如古城温暖的光,从清晨到夜晚,由山?到
书房,最后只要是__在我身旁。”在这个长句中,如果我们从头开始读很容易
就知道空白处填的是“你”,但是RNN是从空白处开始向前搜索并判断,例子
中空白处与相关信息之间的间隔非常大,如图12-13所示,在这个间隔不断增
大的过程中,最终RNN会丧失学习到连接如此远的信息的能力。
我们通常称上述问题为“长期依赖”问题,细心的读者会发现,长期依赖
问题就是由于梯度消失所导致的。理论上,RNN 的结构完全可以解决这种长期
依赖问题,我们可以通过仔细挑选参数来解决处于最初级形式的这类问题。但
是在实践中,我们很难挑选到合适的参数,也就是说RNN很难处理长距离的依
赖。
12.4.2 处理长序列能手——LSTM
RNN算法因为会出现梯度消失的现象,导致无法处理长序列的数据,这使
得RNN算法真正在工业上应用还有很长的路要走。在当时,很多学者针对这个
问题对 RNN进行了深入的研究,他们经过不懈的努力,终于提出了长短时记?
网络(Long Short-Term Memory,LSTM)算法。LSTM 的设计初衷是希望解决
神经网络的长期依赖问题,让记住长期信息成为神经网络的默认行为 。LSTM
成功地克服了原始RNN算法的缺陷,一举成了当前最流行的RNN算法,在语音识
别、图片描述、自然语言处理等许多领域应用广泛。
我们都知道,RNN 算法的隐藏层只有一个状态,所以它对于短期的输入非
常敏感,故我们需要增加一个状态,用来保留长期的记?。LSTM在普通RNN算
法的基础上,在隐藏层各个神经元中增加了记?单元,从而使时间序列上的信
息记?变得可控。并且在各隐藏层之间增加了“控制门”,可以选择记?或遗
忘之前的信息和当前的信息,从而使RNN算法具备了长期记?能力。
LSTM算法的结构示意图如图12-14所示,从图中可以看出来,在t时刻,
LSTM的输入有三个,分别是当前时刻网络的输入值、上一时刻 LSTM 的输出值
以及上一时刻的单元状态;LSTM 的输出有两个,分别是当前时刻 LSTM 的输
出值和当前时刻的单元状态。
学习 LSTM 算法的关键,是掌握它如何控制长期状态。在这里,LSTM 的
设计思路也非常简单,既然这个记?单元有两个输入、一个输出,那么就设计
三个开关控制它的信息流转不就可以了吗?第一个开关,负责控制保存的长期
状态;第二个开关,负责控制把即时状态作为长期状态的输入;第三个开关,
负责控制是否把长期状态作为当前的LSTM的输出。三个开关的作用如图12-15
所示。
LSTM算法通过三个“门”来控制是丢弃还是增加信息,从而实现信息遗忘
或记?。“门”是一种控制信息选择性通过的结构,通常由一个“Sigmid函
数”及一个“点乘”操作组成。Sigmoid函数的输出值在[0,1]区间,0代表完
全丢弃,1代表完全通过。一个LSTM单元有三个这样的门,分别是遗忘门、输
入门和输出门。
(1)遗忘门:它决定上一时刻的单元有多少信息保留到当前时刻。它以
上一个单元的输出h t−1 和本单元的输入x t 作为Sigmoid函数的输入,为C t−1
中的每一项产生一个在[0,1]内的值,用于控制上一单元状态被遗忘的程度。
(2)输入门:输入门决定了当前时刻网络的输入有多少保存到单元中。
它和一个“tanh”函数配合控制保存哪些新信息。tanh 函数产生一个新的候
选向量,输入门为候选向量的每一项产生一个在[0,1]内的值,控制加入多少
新信息。这时候遗忘门的输出用于控制上一个单元被遗忘的程度,输入门的输
出用于控制新信息加入后的状态。
(3)输出门:输出门用于控制单元到 LSTM 的输出值。它用来控制当前
的单元状态有多少被过滤掉。先将单元状态激活,输出门为其中每一项产生一
个在[0,1]内的值,控制单元状态被过滤的程度。
通过这样的方式将 LSTM 关于当前的记?和长期的记?组合在一起,形成
了新的单元状态。由于遗忘门的控制,它可以保存很久很久之前的信息,由于
输入门的控制,它又可以避免当前无关紧要的内容进入记?中。
回到语言模型的例子,“我希望有个如你一般的人,如山间清爽的风,如
古城温暖的光,从清晨到夜晚,由山?到书房,最后只要是__在我身旁。”对
于这个长句,我们需要推断的词只与第一小句相关,因此中间一大段排比句的
权值需要降低,减少这一段对结果的影响。
在LSTM中,首先决定从记?单元中丢弃什么信息。这个决定通过遗忘门完
成,该门会读取h t−1 和x t ,输出一个在[0,1]之间的数值,赋给每个细胞状
态C t−1 。1表示全部保留,0表示完全舍弃。从句子的结构可以判断空白处缺少
一个名词,因此句中所有的形容词如“清爽”“温暖”都是首先需要丢弃或降
低权重的信息。
?一步是确定什么样的新信息需要被存放在记?单元中,包含两方面:一
方面是通过输入门决定什么值需要更新;另一方面是通过tanh函数创建一个新
的候选向量,接?来通过这两个信息产生状态更新。在这个长句中,实际上主
语从一开始就确定?来了,需要做的是减少排比句中的名词对主语产生的影
响。对所有的单词计算之后不断调整权值以及记?单元中的过滤值,最终确定
输出值。
由此可见,很多算法的设计思路与产品的设计思路是很相似的。最初可能
是一个为了解决某个特定问题的构想,有一个算法大致的结构。然后随着场景
的扩展再慢慢深入某一个细节去做调整以解决更多的问题。研究算法的时候,
也要根据具体问题的场景,思考这个算法是哪里遇到了问题,是怎样一步步改
进的,这样可以加深我们对算法的理解,也能够在实际项目中,找到最合适的
算法。