循环神经网络是一个可以具有短期记忆力的神经网络,神经元不但能接受其它神经元的信息,也可以接受自身信息,形成具有环路的网络结构。通过随时间反向传播算法学习,即按照时间的逆序将错误信息一步步地往前传递。
为了处理时序任务,我们必须让网络具有短期记忆能力,一般可通过以下三种方式增加记忆能力。
延时神经网络
建立一个额外的延时单元,用来存储历史信息。延时神经网络就是在前馈网络的非输出层都建立一个延时单元,存储最近几次该神经元的输出。在t时刻,第l+1层神经元和第l层的神经元最近p次输出相关:
有外部输入的非线性自相关模型
自相关模型就是以历史变量y来预测自己,其中w为参数,p为超参数:
有外部输入的非线性自相关模型(Nonlinear AutoRegressive with Exogenous Inputs Model,NARX)是自相关模型的拓展,在每个时刻t都有一个外部输入xt,产生一个输出yt。NARX通过一个延时器记录最近几次的外部输入和输出,在t时刻的输出为:
f为非线性函数,可以为一个前馈神经网络,p.q为超参数。
循环神经网络
循环神经网络通过使用带自反馈的神经元,能够处理任何长度的时序数据。给定一个序列:x1:T = (x1, x2, . . . , xt, . . . , xT ),RNN中带反馈边隐藏层的活性值ht:
其中h0=0,f(·)为一个非线性函数,也可以为一个前馈神经网络。
循环神经网络可以看成在时间维度上权值共享的神经网络:
Elman Network vs. Jordan Network:
序列到类别模式主要用于序列数据的分类问题:输入为序列,输出为类别。比如在文本分类中,输入数据为单词的序列,输出为该文本的类别。(以及:文档的关键字提取)
假设输入为x1:T = (x1, · · · , xT ),输出为y ∈ {1, · · · ,C},可以送入到循环神经网络中,得到不同时刻的隐藏状态h1,h2,…hT,如图(a)所示,将hT作为整个序列的最终表示(特征)送入分类器g(·)进行分类,g(·)可以为简单的线性分类器,也可以为一个前馈神经网络:
除了将最后时刻的状态作为序列表示之外,我们还可以对整个序列的所有状态进行平均,并用这个平均状态来作为整个序列的表示,如图(b)所示,式子如下:
同步的序列到序列模式主要用于序列标注(Sequence Labeling)任务,即每一时刻都有输入和输出,输入序列和输出序列的长度相同。比如词性标注(Part-of-Speech Tagging)中,每一个单词都需要标注其对应的词性标签。
输入为x1:T = (x1, · · · , xT ),输出为序列y1:T = (y1, · · · , yT )。样本x按不同时刻输入到循环神经网络中,并得到不同时刻的隐状态h1, · · · ,hT 。如上图所示,每个时刻的隐状态ht代表了当前时刻和历史的信息,并输入给分类器g(·) 得到当前时刻的标签ˆy:
异步的序列到序列模式也称为编码器-解码器(Encoder-Decoder)模型,即输入序列和输出序列不需要有严格的对应关系,也不需要保持相同的长度。比如在机器翻译中,输入为源语言的单词序列,输出为目标语言的单词序列。(以及:语音识别)
输入x1:T = (x1, · · · , xT ),输出为y1:M = (y1, · · · , yM),异步的序列到序列模式一般通过先编码后解码的方式来实现。如上图所示(< EOS >表示输入序列结束),先将样本x按不同时刻输入到一个循环神经网络(编码器)中,并得到其编码hT 。然后再使用另一个循环神经网络(解码器),得到输出序列ˆy1:M。为了建立输出序列之间的依赖关系,在解码器中通常使用非线性自回归模型。
其中f1(·) 和f2(·)分别为用作编码器和解码器的循环神经网络,g(·) 为分类器,ˆyt为预测输出ˆyt 的向量表示。
不失一般性,以同步序列到序列模式来介绍RNN的参数学习,由于每个时刻t都偶一个监督信息yt,我们定义此刻的损失函数为:
其中g(ht)为t时刻的输出,L为可微分的损失函数,整个序列的损失函数L关于参数U的梯度为:
循环神经网络中存在一个递归调用的函数f(·),因此其计算参数梯度的方式和前馈神经网络不太相同。在循环神经网络中主要有两种计算梯度的方式:随时间反向传播(BPTT)算法和实时循环学习(RTRL)算法。
BPTT算法将循环神经网络看作是一个展开的多层前馈网络,其中“每一层”对应循环网络中的“每个时刻”(见下图)。这样循环神经网络就可以按照前馈网络中的反向传播算法计算参数梯度:
其中,
zk = Uhk−1 +Wxk + b
与反向传播的BPTT算法不同的是,实时循环学习(Real-Time Recurrent Learning,RTRL)是通过前向传播的方式来计算梯度。
假设第t个时刻存在一个监督信息,其损失函数为Lt,就可以同时计算损失函数对uij的偏导数,这样在第t时刻,可以实时地计算损失Lt关于参数U的梯度,并更新参数。参数W和b的梯度也可以同样按上述方法实时计算。
RTRL算法和BPTT算法都是基于梯度下降的算法,分别通过前向模式和反向模式应用链式法则来计算梯度。
在循环神经网络中,一般网络输出维度远低于输入维度,因此BPTT算法的计算量会更小,但是BPTT算法需要保存所有时刻的中间梯度,空间复杂度较高。
RTRL算法不需要梯度回传,因此非常适合用于需要在线学习或无限序列的任务中。
在有些任务中,一个时刻的输出不但和过去时刻的信息有关,也和后续时刻的信息有关。比如给定一个句子,其中一个词的词性由它的上下文决定,即包含左右两边的信息。因此,在这些任务中,我们可以增加一个按照时间的逆序来传递信息的网络层,来增强网络的能力。
RNN在产生yt的时候,这就相当于在看了整个句子。
上面提到的RNN只是最简单的版本,并没有对memory的管理多加约束,可以随时进行读取,但现在RNN最常用的memory管理方式叫做长短期记忆(Long Short-term Memory),简称LSTM。
但现实生活中有很多情况,只根据最近的信息是无法进行预测,还要结合更早输入的信息。比如输入的序列是“I grew up in France… I speak fluent ”,我希望预测出“French”,即“I grew up in France… I speak fluent French”。由于France和French之间有很多其他的句子,普通的RNN无法处理这个问题(后面会提到,是由于RNN在训练时出现的梯度消失/梯度爆炸问题)
在LSTM中,有三个门控机制(Gate):
上面介绍的是一个单独的LSTM模块,那么它与RNN有什么联系呢?其实,LSTM就是作为RNN的一个神经元,门控是由神经元的输入分别乘上矩阵得到的,这些矩阵参数都是可学习的。
下图是单个LSTM的运算情景,每个LSTM的cell所得到的input都是各不相同的,但它们却是可以一起共同运算的,整个运算流程如下图左侧所示:
上述的过程反复进行下去,就得到下图中各个时间点的LSTM值的变化情况,其中与上面的描述略有不同的是,这里还需要把hidden layer的输出ht以及当前cell的值ct都连接到下一个时间点的输入上。
上面是单层的LSTM,实际上不可能只有一个hidden layer,往往会叠很多层,下图就是一个两层叠加的LSTM。
目前,使用LSTM的RNN就是标准的RNN(除LSTM之外,还有Simple RNN、GRU)。
基于RNN的模型往往是很难训练的,如下图所示,模型的error surface是很崎岖的,要么很平坦要么很陡峭,甚至会导致训练的loss无法收敛。
无法收敛的问题可以通过一个很简单的手段解决,就是clipping,例如当前的gradient>>15,但我只把它等效为15,这样在陡峭的地方,梯度就会比较理想地下降,而不是直接飞出去(上图中蓝色虚线部分)。
有人认为是RNN用了Sigmoid function导致的训练困难,实际上与activation function无关,因为有人将Sigmoid替换为ReLU之后,效果更差。
实际上,这是由于普通RNN在训练时,会出现梯度消失/梯度爆炸的现象,从下面这张图的简单例子可以看出,由于时间维度上w被循环利用,y1000的梯度对于t1时刻的w来说经过了太多层,这样很容易出现梯度爆炸/消失,导致学习率很难控制。
P.S 由于RNN 中同样的权重在各个时间上共享,最终的梯度 g = 各个时间步的梯度 g_t 的和,RNN中的梯度消失并不是真正地消失,RNN 所谓梯度消失的真正含义是,梯度被近距离梯度主导,导致模型难以学到远距离的依赖关系。
使用LSTM可以缓解这个问题,因为它可以解决掉梯度消失的问题(但无法解决梯度爆炸:这可以通过cliping来解决)
(由于上面两个公式不能直接从笔记里copy过来,所以直接用的图片了)
Gated Recurrent Unit(GRU):比LSTM更简单(参数量变少,不容易overfitting),只有2个gate,即forget gate和input gate,这两者有一个联动:
[1] https://www.zhihu.com/question/34878706/answer/665429718 知乎:LSTM如何来避免梯度弥散和梯度爆炸? 作者:Towser
[2] https://www.bilibili.com/video/BV1JE411g7XF?p=21 李宏毅:深度学习-RNN
[3] https://nndl.github.io/ 邱锡鹏:《神经网络与深度学习》