RNN概述

循环神经网络(RNN)是用于处理序列数据的神经网络,该序列在时刻 t(从1 到 τ)包含向量 x(t)x^{(t) }。典型的网络结构如下图所示:

循环神经网络(RNN)相关知识

RNN每个时间步都需要将 x 值的输入序列映射到输出值 o 的对应序列。其中 o 是未归一化的对数概率,并且在损失函数 L 内部计算 y^=softmax(x)hat y = softmax(x)。损失函数 L 用于衡量每个 o 与相应的训练目标 y 的距离。

RNN输入到隐含单元的连接由权重矩阵 U 参数化,隐含单元与隐含单元连接由权重矩阵 W 参数化,隐含单元到输出节点的连接由权重矩阵 V 参数化。从上面的图中也可以看出,与CNN类似,在RNN中同样有参数共享的思想(共享了参数WUVW,U,V等)。这使得模型的泛化能力更强,因为模型不会依赖于特定位置上的输入xx。例如考虑这两句话:“I went to Nepal in 2009’’ 和 “In 2009, I went to Nepal.” ,他们表达的意思完全相同,但是时间2009出现在不同的地方,RNN的参数共享机制可以更有效的提取到这个时间信息。参数共享也允许模型泛化到没有见过的序列长度,并且训练模型所需的训练样本远远少于不带参数共享的模型。

当训练循环网络根据过去的信息去预测未来的信息时,网络通常要使用隐含状态 h (t) 来表示时刻 t 之前的序列信息:
h(t)=g(t)(x(t),x(t1),x(t2),,x(2),x(1))=f(h(t1),x(t);θ).begin{aligned}h^{(t)} &=g^{(t)}(x^{(t)},x^{(t-1)},x^{(t-2)},dots,x^{(2)}, x^{(1)}) \& = f(h^{(t-1)}, x^{(t)} ; theta) .end{aligned}
函数g(t)g^{(t)}将过去的序列(x(t),x(t1),x(t2),,x(2),x(1))(x^{(t)},x^{(t-1)}, x^{(t-2)},dots,x^{(2)}, x^{(1)})作为输入来生成当前的隐含状态h(t)h(t)。实际上,我们可以递归调用函数ff来完成隐含状态的计算。这么做主要有两个优点:

  1. 无论序列的长度如何,模型始终具有相同的输入数据。因为它只关注给定输x(t)x^{(t)}后,从一种状态h(t1)h(t-1)到另一种状态h(t)h(t)的转移, 而不是在可变长度的历史输入数据x(t1),x(t2),,x(2),x(1)x^{(t-1)},x^{(t-2)},dots,x^{(2)}, x^{(1)}上的操作。
  2. 我们可以在每个时间步(tt时刻)使用相同参数的状态转移函数ff

在不同的NLP场景中,h(t) 需要保存的信息也不同。例如在词性标注任务中,h(t) 更多的是要保存前一个单词的信息;而在机器翻译任务中,则需要保存输入序列的所有信息。

前向传播公式

上面的图中没有指明**函数,假设使用tanhtanh作为**函数,并且假设输出值是离散的,例如用于预测类别。一种表示离散变量的方式是:把输出 o 作为离散变量每种可能值的非标准化对数概率。然后,我们可以应用 softmax 函数获得标准化后概率的输出向量y^hat y

RNN从特定的初始状态 h (0) 开始前向传播。从 t = 1 到 t = τ 的每个时间步,我们应用以下更新方程:
a(t)=b+Wh(t1)+Ux(t)h(t)=tanh(a(t))o(t)=c+Vh(t)begin{aligned}&mathbf a^{(t)} = mathbf b + mathbf W mathbf h^{(t-1)} + mathbf U mathbf x^{(t)} \&mathbf h^{(t)} = tanh(mathbf a^{(t)}) \&mathbf o^{(t)} = mathbf c + mathbf V mathbf h^{(t)} \end{aligned}

y^(t)=softmax(o(t))=eo(t)t=1τeo(t)hat {mathbf y} ^{(t)} = softmax(mathbf o^{(t)}) = frac{e^{o^{(t)}}}{sum_{t=1}^tau e^{o^{(t)}}} \

其中的参数的偏置向量 b 和 c 连同权重矩阵 U、V 和 W,分别对应于输入到隐藏单元、
隐藏单元到输出和隐藏单元到隐藏单元的连接。这个循环网络将一个输入序列映射到相同长度的输出序列。与 x 序列配对的 y 的总损失就是所有时间步的损失之和,例如定义损失L为所有时间步的负对数似然函数:
L({x(1),,x(τ)},{y(1),,y(τ)})=tL(t)=tlogPmodel(y(t){x(1),,x(τ)})begin{aligned}&L({x^{(1)},ldots,x^{(tau)}},{y^{(1)},ldots,y^{(tau)}}) \&= sum_{t} L^{(t)} \&= -sum_{t} log P_{model}(y^{(t)}|{x^{(1)},ldots,x^{(tau)}})end{aligned}
了解正向传播的计算内容之后,就可以讨论通过时间反向传播算法来更新网络参数了。

通过时间反向传播(BPTT)

回顾之前的计算图:

循环神经网络(RNN)相关知识

对于每一个节点 N,我们需要先 N 后面的节点的梯度,然后递归地计算梯度 NL∇ _N L。我们从最后一个节点开始递归:
LL(t)=1frac{partial L}{partial L^{(t)}} = 1
观察正向传播的计算公式,不难发现 L(t)L^{(t)}是关于y^(t)hat {mathbf y} ^{(t)}的函数,y^(t)hat {mathbf y} ^{(t)}softmaxsoftmax的结果, 而softmaxsoftmax是关于 o(t)o^{(t)}的函数。求LL对非标准化对数概率 o(t)o^{(t)}求偏导(相关求导过程可以参考Softmax求导),得到:
(o(t)L)i=Loi(t)=LL(t)L(t)y^i(t)y^i(t)oi(t)={y^i(t)1,yi(t)=labely^i(t)0,yi(t)labelbegin{aligned}(nabla_{mathbf o^{(t)}} L)_i &= frac{partial L}{partial o_{i}^{(t)}}\&= frac{partial L}{partial L^{(t)}} frac{partial L^{(t)}}{partial hat {mathbf y} ^{(t)}_i} frac{partial hat {mathbf y} ^{(t)}_i}{partial o_{i}^{(t)}} \&=begin{cases}hat {y}_i ^{(t)} - 1 , qquad {y}_i ^{(t)} =label\hat {y}_i ^{(t)} - 0, qquad {y}_i ^{(t)} ne labelend{cases}end{aligned}
从序列的末尾开始,反向进行计算。在最后的时间步 τ,只有 o (τ) 依赖于h (τ) ,不存在h (τ+1)依赖于h (τ) ,因此这个梯度很简单。由o(t)=c+Vh(t)mathbf o^{(t)} = mathbf c + mathbf V mathbf h^{(t)},可以知道:
h(τ)L=o(τ)h(τ)o(τ)L=VTo(τ)Lnabla_{mathbf h^{(tau)}}L = frac{partial o^{(tau)}}{partial h^{(tau)}}nabla_{mathbf o^{(tau)}} L = mathbf V^T nabla_{mathbf o^{(tau)}} L
然后我们可以从时刻 t = τ − 1 到 t = 1 反向迭代,通过时间反向传播梯度。由下面的式子:
a(t)=b+Wh(t1)+Ux(t)h(t)=tanh(a(t))o(t)=c+Vh(t)mathbf a^{(t)} = mathbf b + mathbf W mathbf h^{(t-1)} + mathbf U mathbf x^{(t)} \mathbf h^{(t)} = tanh(mathbf a^{(t)}) \mathbf o^{(t)} = mathbf c + mathbf V mathbf h^{(t)}
不难发现当t < τ时, o (t) 和 h (t+1) 都依赖于h (t) 。因此,它的梯度由两个部分组成:
h(t)L=(h(t+1)h(t))(h(t+1)L)+(o(t)h(t))(o(t)L)=W(h(t+1)L)diag(1(h(t+1))2)+V(o(t)L)begin{array}{l}nabla_{h^{(t)}} L=left(frac{partial boldsymbol{h}^{(t+1)}}{partial boldsymbol{h}^{(t)}}right)^{top}left(nabla_{boldsymbol{h}^{(t+1)}} Lright)+left(frac{partial boldsymbol{o}^{(t)}}{partial boldsymbol{h}^{(t)}}right)^{top}left(nabla_{boldsymbol{o}^{(t)}} Lright) \=boldsymbol{W}^{top}left(nabla_{boldsymbol{h}^{(t+1)}} Lright) operatorname{diag}left(1-left(boldsymbol{h}^{(t+1)}right)^{2}right)+boldsymbol{V}^{top}left(nabla_{boldsymbol{o}^{(t)}} Lright)end{array}
其中:
tanh(x)=1(tanh(x))2tanh'(x) = 1- (tanh(x))^2

h(t+1)h(t)=tanh(b+Wh(t)+Ux(t+1))h(t)=WTdiag(1(h(t+1))2)begin{aligned}frac{partial boldsymbol{h}^{(t+1)}}{partialboldsymbol{h}^{(t)}}&= frac{partial tanh(mathbf b + mathbf W boldsymbol h^{(t)} + mathbf U mathbf x^{(t+1)} )}{partialboldsymbol{h}^{(t)}} \&= mathbf W^T diag(1-(boldsymbol h^{(t+1)})^2)end{aligned}

基于前面的步骤,接下来进行参数的跟新:
cL=t(o(t)c)o(t)L=to(t)LbL=t(h(t)b(t))h(t)L=tdiag(1(h(t))2)h(t)LVL=ti(Loi(t))Voi(t)=t(o(t)L)h(t)W=ti(Lhi(t))W(t)hi(t)=tdiag(1(h(t))2)(h(t)L)h(t1)UL=ti(Lhi(t))U(t)hi(t)=tdiag(1(h(t))2)(h(t)L)x(t)begin{aligned}nabla_{c} L &=sum_{t}left(frac{partial boldsymbol{o}^{(t)}}{partial boldsymbol{c}}right)^{top} nabla_{boldsymbol{o}^{(t)}} L=sum_{t} nabla_{boldsymbol{o}^{(t)}} L \nabla_{boldsymbol{b}} L &=sum_{t}left(frac{partial boldsymbol{h}^{(t)}}{partial boldsymbol{b}^{(t)}}right)^{top} nabla_{boldsymbol{h}^{(t)}} L=sum_{t} operatorname{diag}left(1-left(boldsymbol{h}^{(t)}right)^{2}right) nabla_{boldsymbol{h}^{(t)}} L \nabla_{boldsymbol{V}} L &=sum_{t} sum_{i}left(frac{partial L}{partial o_{i}^{(t)}}right) nabla_{boldsymbol{V}} o_{i}^{(t)}=sum_{t}left(nabla_{o^{(t)}} Lright) boldsymbol{h}^{(t)^{top}} \nabla_{boldsymbol{W}} &=sum_{t} sum_{i}left(frac{partial L}{partial h_{i}^{(t)}}right) nabla_{boldsymbol{W}^{(t)}} h_{i}^{(t)} \&=sum_{t} operatorname{diag}left(1-left(boldsymbol{h}^{(t)}right)^{2}right)left(nabla_{boldsymbol{h}^{(t)}} Lright) boldsymbol{h}^{(t-1)^{top}} \nabla_{U} L &=sum_{t} sum_{i}left(frac{partial L}{partial h_{i}^{(t)}}right) nabla_{boldsymbol{U}^{(t)}} h_{i}^{(t)} \&=sum_{t} operatorname{diag}left(1-left(boldsymbol{h}^{(t)}right)^{2}right)left(nabla_{boldsymbol{h}^{(t)}} Lright) boldsymbol{x}^{(t)^{top}}end{aligned}

RNN确定序列长度方式

第一种是添加一个表示序列末端的特殊符号。在训练集中,我们将该符号作为序列的一个额外成员,即紧跟每个训练样本 x (τ) 之后。

第二种选择是在模型中引入一个额外的Bernoulli输出,表示在每个时间步决定继续或停止。相比向词汇表增加一个额外符号,这种方法更普遍,因为它适用于任何RNN。在这种方法中,sigmoid被训练为最大化正确预测的对数似然,即在每个时间步序列决定结束或继续。

第三种是将一个额外的输出添加到模型并预测整数 τ 本身。模型可以预测 τ 的值,然后使用 τ 步有价值的数据。这种方法需要在每个时间步的循环更新中增加一个额外输入,使得循环更新知道它是否是靠近所产生序列的末尾。

其他RNN结构

除了上面介绍的RNN结构,还有一些其他结构:

1, 每个时间步都产生一个输出,但是只有当前时刻的输出 o 与下个时刻的隐藏单元之间有连接的RNN:

循环神经网络(RNN)相关知识

这样的RNN没有最开始介绍的 RNN 那样强大,上图中的RNN被训练为将特定输出值放入 o 中,并且 o 是允许传播到未来的唯一信息。此处没有从 h 前向传播的直接连接。之前的 h 仅通过产生的预测间接地连接到当前。o 通常缺乏过去的重要信息,除非它非常高维且内容丰富。这使得该图中的RNN不那么强大,但是它更容易训练,因为每个时间步可以与其他时间步分离训练,允许训练期间更多的并行化。

2, 隐藏单元之间存在循环连接,但读取整个序列后只在最后一个时间步产生单个输出的RNN:

循环神经网络(RNN)相关知识

这样的网络可以用于概括序列,并产生一个向量 o 用于表示整个输入序列,然后可以对 o 进行进一步的处理。例如在翻译任务中,先输入原始输入的句子,得到句子的向量表示 o ,然后对 o 展开进一步的翻译任务。

3,包含上下文的RNN:

循环神经网络(RNN)相关知识

此RNN包含从前一个输出到当前状态的连接。这些连接允许RNN在给定 x 的序列后,对相同长度的 y 序列上的任意分布建模。

4,双向RNN

在许多应用中,我们要输出的 y (t) 的预测可能依赖于整个输入序列。例如,在语音识别中,当前声音作为音素的正确解释可能取决于未来几个音素,甚至可能取决于未来的几个词,因为词与附近的词之间的存在语义依赖。双向RNN结构如下:

循环神经网络(RNN)相关知识

隐藏状态变量 h 在时间上向前传播信息(向右),而隐藏状态变量 g 在时间上向后传播信息(向左)。因此在每个点 t,输出单元 o (t) 可以受益于输入 h (t) 中关于过去的相关信息以及输入 g (t) 中关于未来的相关信息。

5,编码—解码结构(序列到序列)的RNN

编码—解码结构的RNN支持将输入序列映射到不一定等长的输出序列,结构如下图所示:

循环神经网络(RNN)相关知识

这在许多场景中都有应用,如语音识别、机器翻译或问答,其中训练集的输入和输出序列的长度通常不相同。它由读取输入序列的编码器RNN以及生成输出序列的解码器RNN组成。编码器RNN的最终隐藏状态用于计算一般为固定大小的上下文向量 C,C 表示输入序列的语义概要信息并且作为解码器RNN的输入。

基于RNN的应用

RNN通常用于处理序列形式的输入数据,如文本,语音等。输出也可以是序列或者某个预测值。常见的应用如下:

1,序列数据的分析

例如情感分析,输入一句话的每个字的向量表示,输出其情感的预测标签。网络结构如下:

循环神经网络(RNN)相关知识

2,序列数据的转换

例如机器翻译,输入是某种语言的字序列,输出是翻译后的字序列。网络结构如下:

循环神经网络(RNN)相关知识

再如,词性标注,输入是字的向量表示,输出是每个字对应的词性标注信息,常见网络结构如下:

循环神经网络(RNN)相关知识

3,序列数据的生成

例如,图片描述生成,输入是一张图片的向量表示,输出图片的描述信息。网络结构如下:

循环神经网络(RNN)相关知识

RNN的不足

RNN很难解决长依赖问题,即经过许多个时间步的传播之后,梯度值倾向于0或者无穷大,这样的现象称之为梯度消失和梯度爆炸。

1,从隐藏变量h角度来看

为了简化说明,我们认为:
h(t)=WTh(t1)boldsymbol h^{(t)} = boldsymbol W^T boldsymbol h^{(t-1)}
根据递推关系,可以转换为:
h(t)=(Wt)Th(0)boldsymbol h^{(t)} = (boldsymbol W^t)^T boldsymbol h^{(0)}
Wboldsymbol W 符合下列形式的特征分解:
W=QΣQTboldsymbol W = boldsymbol Q boldsymbol Sigma boldsymbol Q^T
其中Qboldsymbol Q是正交矩阵。于是有:
h(t)=QTΣtQh(0)boldsymbol h^{(t)} = boldsymbol Q^T boldsymbol Sigma^t boldsymbol Q boldsymbol h^{(0)}
经过多个阶段的传播后,如果$ boldsymbol Sigma^t11中的特征值小于1,特征值将衰减到零。如果特征值大于1,经过t次相乘后,特征值将激增。任何不与最大特征向量对齐的boldsymbol h^{(0)}$的部分将最终被丢弃,无法做到长期依赖。

2,从梯度传播角度来看

梯度的反向传播过程如下图所示:

循环神经网络(RNN)相关知识

不难得到:
Jy0=Jh3h3h2h2h1h1y0frac{partial J}{partial y_0} = frac{partial J}{partial h_3}frac{partial h_3}{partial h_2} frac{partial h_2}{partial h_1} frac{partial h_1}{partial y_0}
上面的式子是多个偏导数的累乘,如果每个偏导数的值都小于1,那么传播到 t=0t=0 时刻的梯度几乎等于0了,也就是梯度消失了。梯度消失意味着只有靠近输出的几层才真正起到学习的作用,无法通过加深网络层数来提升预测效果,这样RNN很难学习到输入序列中的长距离依赖关系。

反之,如果每个偏导数的值都大于1,那么传播到 t=0t=0 时刻的梯度被指数倍放大,也就是梯度爆炸了。可以通过梯度裁剪来缓解,即当梯度的范式大于某个给定值的时候,对梯度进行等比缩放。

为了能学习到更长的依赖关系,需要对RNN网络加以改进,下一篇文章提到的LSTM模型可以一定程度上得到改善。

参考文章:

《深度学习》

RNN详解