RNN概述
循环神经网络(RNN)是用于处理序列数据的神经网络,该序列在时刻 t(从1 到 τ)包含向量 x(t)。典型的网络结构如下图所示:
RNN每个时间步都需要将 x 值的输入序列映射到输出值 o 的对应序列。其中 o 是未归一化的对数概率,并且在损失函数 L 内部计算 y^=softmax(x)。损失函数 L 用于衡量每个 o 与相应的训练目标 y 的距离。
RNN输入到隐含单元的连接由权重矩阵 U 参数化,隐含单元与隐含单元连接由权重矩阵 W 参数化,隐含单元到输出节点的连接由权重矩阵 V 参数化。从上面的图中也可以看出,与CNN类似,在RNN中同样有参数共享的思想(共享了参数W,U,V等)。这使得模型的泛化能力更强,因为模型不会依赖于特定位置上的输入x。例如考虑这两句话:“I went to Nepal in 2009’’ 和 “In 2009, I went to Nepal.” ,他们表达的意思完全相同,但是时间2009出现在不同的地方,RNN的参数共享机制可以更有效的提取到这个时间信息。参数共享也允许模型泛化到没有见过的序列长度,并且训练模型所需的训练样本远远少于不带参数共享的模型。
当训练循环网络根据过去的信息去预测未来的信息时,网络通常要使用隐含状态 h (t) 来表示时刻 t 之前的序列信息:
h(t)=g(t)(x(t),x(t−1),x(t−2),…,x(2),x(1))=f(h(t−1),x(t);θ).
函数g(t)将过去的序列(x(t),x(t−1),x(t−2),…,x(2),x(1))作为输入来生成当前的隐含状态h(t)。实际上,我们可以递归调用函数f来完成隐含状态的计算。这么做主要有两个优点:
- 无论序列的长度如何,模型始终具有相同的输入数据。因为它只关注给定输x(t)后,从一种状态h(t−1)到另一种状态h(t)的转移, 而不是在可变长度的历史输入数据x(t−1),x(t−2),…,x(2),x(1)上的操作。
- 我们可以在每个时间步(t时刻)使用相同参数的状态转移函数f。
在不同的NLP场景中,h(t) 需要保存的信息也不同。例如在词性标注任务中,h(t) 更多的是要保存前一个单词的信息;而在机器翻译任务中,则需要保存输入序列的所有信息。
前向传播公式
上面的图中没有指明**函数,假设使用tanh作为**函数,并且假设输出值是离散的,例如用于预测类别。一种表示离散变量的方式是:把输出 o 作为离散变量每种可能值的非标准化对数概率。然后,我们可以应用 softmax 函数获得标准化后概率的输出向量y^。
RNN从特定的初始状态 h (0) 开始前向传播。从 t = 1 到 t = τ 的每个时间步,我们应用以下更新方程:
a(t)=b+Wh(t−1)+Ux(t)h(t)=tanh(a(t))o(t)=c+Vh(t)
y^(t)=softmax(o(t))=∑t=1τeo(t)eo(t)
其中的参数的偏置向量 b 和 c 连同权重矩阵 U、V 和 W,分别对应于输入到隐藏单元、
隐藏单元到输出和隐藏单元到隐藏单元的连接。这个循环网络将一个输入序列映射到相同长度的输出序列。与 x 序列配对的 y 的总损失就是所有时间步的损失之和,例如定义损失L为所有时间步的负对数似然函数:
L({x(1),…,x(τ)},{y(1),…,y(τ)})=t∑L(t)=−t∑logPmodel(y(t)∣{x(1),…,x(τ)})
了解正向传播的计算内容之后,就可以讨论通过时间反向传播算法来更新网络参数了。
通过时间反向传播(BPTT)
回顾之前的计算图:
对于每一个节点 N,我们需要先 N 后面的节点的梯度,然后递归地计算梯度 ∇NL。我们从最后一个节点开始递归:
∂L(t)∂L=1
观察正向传播的计算公式,不难发现 L(t)是关于y^(t)的函数,y^(t)是softmax的结果, 而softmax是关于 o(t)的函数。求L对非标准化对数概率 o(t)求偏导(相关求导过程可以参考Softmax求导),得到:
(∇o(t)L)i=∂oi(t)∂L=∂L(t)∂L∂y^i(t)∂L(t)∂oi(t)∂y^i(t)={y^i(t)−1,yi(t)=labely^i(t)−0,yi(t)=label
从序列的末尾开始,反向进行计算。在最后的时间步 τ,只有 o (τ) 依赖于h (τ) ,不存在h (τ+1)依赖于h (τ) ,因此这个梯度很简单。由o(t)=c+Vh(t),可以知道:
∇h(τ)L=∂h(τ)∂o(τ)∇o(τ)L=VT∇o(τ)L
然后我们可以从时刻 t = τ − 1 到 t = 1 反向迭代,通过时间反向传播梯度。由下面的式子:
a(t)=b+Wh(t−1)+Ux(t)h(t)=tanh(a(t))o(t)=c+Vh(t)
不难发现当t < τ时, o (t) 和 h (t+1) 都依赖于h (t) 。因此,它的梯度由两个部分组成:
∇h(t)L=(∂h(t)∂h(t+1))⊤(∇h(t+1)L)+(∂h(t)∂o(t))⊤(∇o(t)L)=W⊤(∇h(t+1)L)diag(1−(h(t+1))2)+V⊤(∇o(t)L)
其中:
tanh′(x)=1−(tanh(x))2
∂h(t)∂h(t+1)=∂h(t)∂tanh(b+Wh(t)+Ux(t+1))=WTdiag(1−(h(t+1))2)
基于前面的步骤,接下来进行参数的跟新:
∇cL∇bL∇VL∇W∇UL=t∑(∂c∂o(t))⊤∇o(t)L=t∑∇o(t)L=t∑(∂b(t)∂h(t))⊤∇h(t)L=t∑diag(1−(h(t))2)∇h(t)L=t∑i∑(∂oi(t)∂L)∇Voi(t)=t∑(∇o(t)L)h(t)⊤=t∑i∑(∂hi(t)∂L)∇W(t)hi(t)=t∑diag(1−(h(t))2)(∇h(t)L)h(t−1)⊤=t∑i∑(∂hi(t)∂L)∇U(t)hi(t)=t∑diag(1−(h(t))2)(∇h(t)L)x(t)⊤
RNN确定序列长度方式
第一种是添加一个表示序列末端的特殊符号。在训练集中,我们将该符号作为序列的一个额外成员,即紧跟每个训练样本 x (τ) 之后。
第二种选择是在模型中引入一个额外的Bernoulli输出,表示在每个时间步决定继续或停止。相比向词汇表增加一个额外符号,这种方法更普遍,因为它适用于任何RNN。在这种方法中,sigmoid被训练为最大化正确预测的对数似然,即在每个时间步序列决定结束或继续。
第三种是将一个额外的输出添加到模型并预测整数 τ 本身。模型可以预测 τ 的值,然后使用 τ 步有价值的数据。这种方法需要在每个时间步的循环更新中增加一个额外输入,使得循环更新知道它是否是靠近所产生序列的末尾。
其他RNN结构
除了上面介绍的RNN结构,还有一些其他结构:
1, 每个时间步都产生一个输出,但是只有当前时刻的输出 o 与下个时刻的隐藏单元之间有连接的RNN:
这样的RNN没有最开始介绍的 RNN 那样强大,上图中的RNN被训练为将特定输出值放入 o 中,并且 o 是允许传播到未来的唯一信息。此处没有从 h 前向传播的直接连接。之前的 h 仅通过产生的预测间接地连接到当前。o 通常缺乏过去的重要信息,除非它非常高维且内容丰富。这使得该图中的RNN不那么强大,但是它更容易训练,因为每个时间步可以与其他时间步分离训练,允许训练期间更多的并行化。
2, 隐藏单元之间存在循环连接,但读取整个序列后只在最后一个时间步产生单个输出的RNN:
这样的网络可以用于概括序列,并产生一个向量 o 用于表示整个输入序列,然后可以对 o 进行进一步的处理。例如在翻译任务中,先输入原始输入的句子,得到句子的向量表示 o ,然后对 o 展开进一步的翻译任务。
3,包含上下文的RNN:
此RNN包含从前一个输出到当前状态的连接。这些连接允许RNN在给定 x 的序列后,对相同长度的 y 序列上的任意分布建模。
4,双向RNN
在许多应用中,我们要输出的 y (t) 的预测可能依赖于整个输入序列。例如,在语音识别中,当前声音作为音素的正确解释可能取决于未来几个音素,甚至可能取决于未来的几个词,因为词与附近的词之间的存在语义依赖。双向RNN结构如下:
隐藏状态变量 h 在时间上向前传播信息(向右),而隐藏状态变量 g 在时间上向后传播信息(向左)。因此在每个点 t,输出单元 o (t) 可以受益于输入 h (t) 中关于过去的相关信息以及输入 g (t) 中关于未来的相关信息。
5,编码—解码结构(序列到序列)的RNN
编码—解码结构的RNN支持将输入序列映射到不一定等长的输出序列,结构如下图所示:
这在许多场景中都有应用,如语音识别、机器翻译或问答,其中训练集的输入和输出序列的长度通常不相同。它由读取输入序列的编码器RNN以及生成输出序列的解码器RNN组成。编码器RNN的最终隐藏状态用于计算一般为固定大小的上下文向量 C,C 表示输入序列的语义概要信息并且作为解码器RNN的输入。
基于RNN的应用
RNN通常用于处理序列形式的输入数据,如文本,语音等。输出也可以是序列或者某个预测值。常见的应用如下:
1,序列数据的分析
例如情感分析,输入一句话的每个字的向量表示,输出其情感的预测标签。网络结构如下:
2,序列数据的转换
例如机器翻译,输入是某种语言的字序列,输出是翻译后的字序列。网络结构如下:
再如,词性标注,输入是字的向量表示,输出是每个字对应的词性标注信息,常见网络结构如下:
3,序列数据的生成
例如,图片描述生成,输入是一张图片的向量表示,输出图片的描述信息。网络结构如下:
RNN的不足
RNN很难解决长依赖问题,即经过许多个时间步的传播之后,梯度值倾向于0或者无穷大,这样的现象称之为梯度消失和梯度爆炸。
1,从隐藏变量h角度来看
为了简化说明,我们认为:
h(t)=WTh(t−1)
根据递推关系,可以转换为:
h(t)=(Wt)Th(0)
当W 符合下列形式的特征分解:
W=QΣQT
其中Q是正交矩阵。于是有:
h(t)=QTΣtQh(0)
经过多个阶段的传播后,如果$ \boldsymbol \Sigma^t中的特征值小于1,特征值将衰减到零。如果特征值大于1,经过t次相乘后,特征值将激增。任何不与最大特征向量对齐的\boldsymbol h^{(0)}$的部分将最终被丢弃,无法做到长期依赖。
2,从梯度传播角度来看
梯度的反向传播过程如下图所示:
不难得到:
∂y0∂J=∂h3∂J∂h2∂h3∂h1∂h2∂y0∂h1
上面的式子是多个偏导数的累乘,如果每个偏导数的值都小于1,那么传播到 t=0 时刻的梯度几乎等于0了,也就是梯度消失了。梯度消失意味着只有靠近输出的几层才真正起到学习的作用,无法通过加深网络层数来提升预测效果,这样RNN很难学习到输入序列中的长距离依赖关系。
反之,如果每个偏导数的值都大于1,那么传播到 t=0 时刻的梯度被指数倍放大,也就是梯度爆炸了。可以通过梯度裁剪来缓解,即当梯度的范式大于某个给定值的时候,对梯度进行等比缩放。
为了能学习到更长的依赖关系,需要对RNN网络加以改进,下一篇文章提到的LSTM模型可以一定程度上得到改善。
参考文章:
《深度学习》
RNN详解