邱锡鹏《神经网络与深度学习》学习笔记。
神经网络与深度学习(五)—— 循环神经网络
1.循环神经网络
前馈神经网络的输出只依赖当前的输入,但在很多现实任务(如视频、语音、文本)中,网络的输出不仅和当前时刻的输入相关,也和其过去一段时间的输出相关。此外时序数据的长度一般是不固定的,而前馈神经网络要求输入和输出的维数都是固定的。因此处理这类时序数据相关的问题时,就需要一种能力更强的模型。
循环神经网络(recurrent neural network,RNN)是一种具有短期记忆能力的神经网络。在 RNN 中,神经元不但可以接受其他神经元的信息,也可以接受自身的信息,形成具有环路的网络结构。
RNN 的参数可以通过随时间反向传播算法来学习。将误差信息按照时间逆序一步一步的往前传递。当输入序列比较长时,会存在梯度爆炸和梯度消失问题,也称为长程依赖问题。解决这个问题最有效的方法是门控机制(Gating Mechanism)。
RNN 已被广泛应用于语音识别、语言模型以及自然语言生成等任务上。
1.1 简单循环神经网络
1.1.1 循环神经网络:给网络增加记忆能力
循环神经网络通过使用带自反馈的神经元,使网络具有短期记忆能力。因此,能够处理任意长度的时序数据。
给定一个输入序列 x 1 : T = ( x 1 , x 2 , … , x t , … , x T ) \boldsymbol{x}_{1: T}=\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{2}, \ldots, \boldsymbol{x}_{t}, \ldots, \boldsymbol{x}_{T}\right) x1:T=(x1,x2,…,xt,…,xT),循环神经网络通过下面的公式更新带反馈边的隐藏层的活性值 h t \boldsymbol{h}_t ht:
h t = f ( h t − 1 , x t ) \boldsymbol{h}_t=f(\boldsymbol{h}_{t-1},\boldsymbol{x}_t) ht=f(ht−1,xt) 其中, h 0 = 0 \boldsymbol{h}_0=0 h0=0, f ( ⋅ ) f(·) f(⋅)为一个非线性函数,可以是一个前馈网络。
从数学上讲,上式可以看成一个动力系统。因此隐藏层的活性值 h t \boldsymbol{h}_t ht也称为状态(state)或隐状态(Hidden State)。
动力系统(Dynamical System)是一个数学上的概念,指系统状态按照一定的规律随时间变化的系统。
下图给出了循环神经网络的示例,其‘延时器’是一个虚拟单元,记录神经元的最近一次(或几次)活性值。
理论上,前馈神经网络可以模拟任何连续函数,循环神经网络可以模拟任何程序。
1.1.2 简单循环网络
简单循环网络(Simple Recurrent Network,SRN,1990)是一个非常简单的循环神经网络,在只有一个隐藏层的前馈神经网络中增加了从隐藏层到隐藏层的反馈连接。
令向量 x t ∈ R M \boldsymbol{x}_{t} \in \mathbb{R}^{M} xt∈RM表示在时刻 t t t时网络的输入, h t ∈ R D \boldsymbol{h}_{t} \in \mathbb{R}^{D} ht∈RD表示 t t t时刻的隐藏层状态(即隐藏层神经元活性值),$\boldsymbol{y}_{t} $表示输出。则一个完全连接的简单循环神经网络定义为:
z t = U h t − 1 + W x t + b h t = f ( z t ) y t = V h t \begin{array}{l}\boldsymbol{z}_{t}=\boldsymbol{U} \boldsymbol{h}_{t-1}+\boldsymbol{W} \boldsymbol{x}_{t}+\boldsymbol{b}\\ \boldsymbol{h}_{t}=f\left(\boldsymbol{z}_{t}\right) \\ \boldsymbol{y}_{t}=\boldsymbol{V} \boldsymbol{h}_{t}\end{array} zt=Uht−1+Wxt+bht=f(zt)yt=Vht
z t \boldsymbol{z}_{t} zt为隐藏层的净输入, U ∈ R D × D \boldsymbol{U} \in \mathbb{R}^{D \times D} U∈RD×D为状态-状态权重矩阵, W ∈ R D × M \boldsymbol{W} \in \mathbb{R}^{D \times M} W∈RD×M为状态-输入权重矩阵。 b ∈ R D \boldsymbol{b} \in \mathbb{R}^D b∈RD为偏置向量, f ( ⋅ ) f(·) f(⋅)为非线性激活函数,通常为 Logistic 函数或 Tanh 函数。 V \boldsymbol{V} V为状态-输出权重矩阵。 U \boldsymbol{U} U、 W \boldsymbol{W} W、 b \boldsymbol{b} b 和 V \boldsymbol{V} V 都是网络参数。
如果我们把每个时刻的状态都看做前馈神经网络的一层,循环神经网络可以看做时间维度上权值共享的神经网络。下图给出了按时间展开的循环神经网络。
循环神经网络的拟合能力也十分强大。一个完全连接的循环神经网络是任何非线性动力系统的近似器,可以近似解决所有的可计算问题。
1.2 RNN 应用于机器学习的三种任务模式
RNN可以应用到很多不同类型的机器学习任务。根据这些任务的特点可以分为以下几种模式:序列到类别模式、同步的序列到序列模式、异步的序列到序列模式。
1.2.1 序列到类别模式
输入为序列,输出为类别。主要用于序列数据的分类问题。
假设输入样本为一个长度为 T T T的序列 x 1 : T = ( x 1 , ⋯ , x T ) \boldsymbol{x}_{1: T}=\left(\boldsymbol{x}_{1}, \cdots, \boldsymbol{x}_{T}\right) x1:T=(x1,⋯,xT),输出为一个类别 y ∈ { 1 , . . . , C } y\in\{1,...,C\} y∈{
1,...,C}.将样本按不同时刻输入到循环神经网络中,得到不同时刻的隐藏状态 h 1 , ⋯ , h T \boldsymbol{h}_{1}, \cdots, \boldsymbol{h}_{T} h1,⋯,hT,我们可以将 h T \boldsymbol{h}_{T} hT看作整个序列的最终状态(或特征),并输入给分类器 g ( ⋅ ) g(·) g(⋅)进行分类,即
y ^ = g ( h T ) \hat{y}=g\left(\boldsymbol{h}_{T}\right) y^=g(hT)
其中 g ( ⋅ ) g(·) g(⋅)可以是简单的线性分类器(如 Logistic回归)或复杂的分类器(如多层全连接网络)。
也可以对整个序列的所有状态进行平均,并用这个平均状态来作为整个序列的表示,即
y ^ = g ( 1 T ∑ t = 1 T h t ) \hat{y}=g\left(\frac{1}{T} \sum_{t=1}^{T} \boldsymbol{h}_{t}\right) y^=g(T1t=1∑Tht)
1.2.2 同步的序列到序列模式
每一时刻都有输入和输出,输入序列和输出序列长度相同。主要用于序列标注(Sequence Labeling)任务。
这种模式的输出序列为 y 1 : T = ( y 1 , ⋯ , y T ) y_{1: T}=\left(y_{1}, \cdots, y_{T}\right) y1:T=(y1,⋯,yT).每个时刻的隐状态 h t \boldsymbol{h}_{t} ht代表了当前时刻和历史的信息,输入给分类器,得到当前时刻的标签 y ^ t \hat{y}_{t} y^t,即
y ^ t = g ( h t ) , ∀ t ∈ [ 1 , T ] \hat{y}_{t}=g\left(\boldsymbol{h}_{t}\right), \quad \forall t \in[1, T] y^t=g(ht),∀t∈[1,T]
同步的序列到序列模式图示如下
1.2.3 异步的序列到序列模式
异步的序列到序列模式也称为编码器-解码器(Encoder-Decoder)模型。输入序列和输出序列不需要严格的对应关系,也不需要保持相同的长度。比如在机器翻译中,输入为源语言的单词序列,输出为目标语言的单词序列。
异步的序列到序列模式一般通过先编码后解码的方式来实现。现将样本按不同时刻输入到一个循环神经网络(编码器)中,并得到其编码 h T \boldsymbol{h}_{T} hT,然后再使用另一个循环神经网络(解码器)得到输出序列 y ^ 1 : M \hat{y}_{1:M} y^1:M。
令 f 1 ( ⋅ ) f_1(·) f1(⋅)和 f 2 ( ⋅ ) f_2(·) f2(⋅)分别用作编码器和解码器的循环神经网络,则编码器-解码器模型可以写为
h t = f 1 ( h t − 1 , x t ) h T + t = f 2 ( h T + t − 1 , y ^ t − 1 ) y ^ t = g ( h T + t ) \begin{aligned} \boldsymbol{h}_{t} &=f_{1}\left(\boldsymbol{h}_{t-1}, \boldsymbol{x}_{t}\right) \\ \boldsymbol{h}_{T+t} &=f_{2}\left(\boldsymbol{h}_{T+t-1}, \hat{\boldsymbol{y}}_{t-1}\right) \\ \hat{y}_{t} &=g\left(\boldsymbol{h}_{T+t}\right) \end{aligned} hthT+ty^t=f1(ht−1,xt)=f2(hT+t−1,y^t−1)=g(hT+t)
下图给出了异步的序列到序列模式示例,其中 < E O S > <EOS> <EOS>表示输入序列的结束。
1.3 参数学习
1.3.1 梯度下降
RNN 的参数可以通过梯度下降法来进行学习。
以随机梯度下降为例,给定一个训练样本 ( x , y ) (\boldsymbol{x},\boldsymbol{y}) (x,y),其中 x 1 : T = ( x 1 , . . . , x T ) \boldsymbol{x}_{1:T}=(\boldsymbol{x}_1,...,\boldsymbol{x}_T) x1:T=(x1,...,xT)为长度为 T T T的输入序列, y 1 : T = ( y 1 , ⋯ , y T ) y_{1: T}=\left(y_{1}, \cdots, y_{T}\right) y1:T=(y1,⋯,yT)是长度为 T T T的标签序列。即在每个时刻 t t t都有一个监督信息 y t y_t yt,我们定义时刻 t t t的损失函数为
L t = L ( y t , g ( h t ) ) \mathcal{L}_{t}=\mathcal{L}\left(y_{t}, g\left(\boldsymbol{h}_{t}\right)\right) Lt=L(yt,g(ht))
其中 g ( h t ) g\left(\boldsymbol{h}_{t}\right) g(ht)为第 t t t时刻的输出, L \mathcal{L} Lw为可微分的损失函数,比如交叉熵。那么整个序列的损失函数为
L = ∑ t = 1 T L t \mathcal{L}=\sum_{t=1}^{T} \mathcal{L}_{t} L=t=1∑TLt
整个序列的损失函数 L \mathcal{L} L关于参数 U \boldsymbol{U} U的梯度为
∂ L ∂ U = ∑ t = 1 T ∂ L t ∂ U \frac{\partial \mathcal{L}}{\partial \boldsymbol{U}}=\sum_{t=1}^{T} \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{U}} ∂U∂L=t=1∑T∂U∂Lt
即每个时刻损失 L t \mathcal{L}_{t} Lt对参数 U \boldsymbol{U} U的偏导数之和。
循环神经网络中存在一个递归调用的函数 f ( ⋅ ) f(·) f(⋅),因此其计算梯度的方式和前馈神经网络不同一样。RNN 主要有两种计算梯度的方式:随时间反向传播(BPTT)算法和实时循环学习(RTRL)算法。
1.3.2 随时间反向传播算法
BackPropagation Through Time,BPTT 算法的主要思想是通过类似前馈神经网络的误差反向传播算法来计算梯度。
BPTT算法将循环神经网络看做展开的多层前馈神经网络,其中“每一层”对应循环神经网络中的“每个时刻”。这样,循环神经网络就可以按照前馈网络中的反向传播算法计算参数梯度。在“展开”的前馈网络中,所有层的参数是共享的,因此参数的真实梯度是所有“展开层”的参数梯度之和。
计算偏导数 ∂ L t ∂ U \frac{\partial \mathcal{L}_t}{\partial \boldsymbol{U}} ∂U∂Lt
因为参数 U \boldsymbol{U} U和隐藏层在每个时刻 k k k的净输入 z k = U h k − 1 + W x k + b \boldsymbol{z}_{k}=\boldsymbol{U} \boldsymbol{h}_{k-1}+\boldsymbol{W} \boldsymbol{x}_{k}+\boldsymbol{b} zk=Uhk−1+Wxk+b有关,因此第 t t t时刻的损失函数 L t \mathcal{L}_t Lt关于参数 u i j u_{ij} uij的梯度为:
∂ L t ∂ u i j = ∑ k = 1 t ∂ + z k ∂ u i j ∂ L t ∂ z k \frac{\partial \mathcal{L}_{t}}{\partial u_{i j}}=\sum_{k=1}^{t} \frac{\partial^{+} \boldsymbol{z}_{k}}{\partial u_{i j}} \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{z}_{k}} ∂uij∂Lt=k=1∑t∂uij∂+zk∂zk∂Lt
其中 ∂ + z k ∂ u i j \frac{\partial^{+} \boldsymbol{z}_{k}}{\partial u_{i j}} ∂uij∂+zk表示“直接”偏导数,即公式 z k = U h k − 1 + W x k + b \boldsymbol{z}_{k}=\boldsymbol{U} \boldsymbol{h}_{k-1}+\boldsymbol{W} \boldsymbol{x}_{k}+\boldsymbol{b} zk=Uhk−1+Wxk+b中保持 h k − 1 \boldsymbol{h}_{k-1} hk−1不变,对 u i j u_{ij} uij进行求偏导数,得到
其中 [ h k − 1 ] j \left[\boldsymbol{h}_{k-1}\right]_{j} [hk−1]j为第 k − 1 k-1 k−1 时刻隐状态的第 j j j维; I i ( x ) \mathbb{I}_{i}(x) Ii(x)除了第 i i i行值为 x x x外,其余都为 0 的行向量。
定义误差项 δ t , k = ∂ L t ∂ z k \delta_{t, k} =\frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{z}_{k}} δt,k=∂zk∂Lt为第 t t t时刻的损失对第 t t t时刻隐藏神经层的净输入 z k \boldsymbol{z}_{k} zk的导数,则当 1 ≤ k < t 1 \leq k<t 1≤k<t时:
δ t , k = ∂ L t ∂ z k = ∂ h k ∂ z k ∂ z k + 1 ∂ h k ∂ L t ∂ z k + 1 = diag ( f ′ ( z k ) ) U ⊤ δ t , k + 1 \begin{aligned} \delta_{t, k} &=\frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{z}_{k}} \\ &=\frac{\partial \boldsymbol{h}_{k}}{\partial \boldsymbol{z}_{k}} \frac{\partial \boldsymbol{z}_{k+1}}{\partial \boldsymbol{h}_{k}} \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{z}_{k+1}} \\ &=\operatorname{diag}\left(f^{\prime}\left(\boldsymbol{z}_{k}\right)\right) \boldsymbol{U}^{\top} \delta_{t, k+1} \end{aligned} δt,k=∂zk∂Lt=∂zk∂hk∂hk∂zk+1∂zk+1∂Lt=diag(f′(zk))U⊤δt,k+1
综合上述两式可得
∂ L t ∂ u i j = ∑ k = 1 t [ δ t , k ] i [ h k − 1 ] j \frac{\partial \mathcal{L}_{t}}{\partial u_{i j}}=\sum_{k=1}^{t}\left[\delta_{t, k}\right]_{i}\left[\boldsymbol{h}_{k-1}\right]_{j} ∂uij∂Lt=k=1∑t[δt,k]i[hk−1]j
写成矩阵形式为
∂ L t ∂ U = ∑ k = 1 t δ t , k h k − 1 ⊤ \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{U}}=\sum_{k=1}^{t} \delta_{t, k} \boldsymbol{h}_{k-1}^{\top} ∂U∂Lt=k=1∑tδt,khk−1⊤
同理可得
∂ L ∂ W = ∑ t = 1 T ∑ k = 1 t δ t , k x k ⊤ ∂ L ∂ b = ∑ t = 1 T ∑ k = 1 t δ t , k \begin{aligned} \frac{\partial \mathcal{L}}{\partial \boldsymbol{W}} &=\sum_{t=1}^{T} \sum_{k=1}^{t} \delta_{t, k} \boldsymbol{x}_{k}^{\top} \\ \frac{\partial \mathcal{L}}{\partial \boldsymbol{b}} &=\sum_{t=1}^{T} \sum_{k=1}^{t} \delta_{t, k} \end{aligned} ∂W∂L∂b∂L=t=1∑Tk=1∑tδt,kxk⊤=t=1∑Tk=1∑tδt,k
下图为误差项随时间进行反向传播的示例:
1.3.3 实时循环学习算法
Real-Time Tecurrent Learning,RTRL,通过前向传播方式来计算梯度。
假设循环神经网络中第 t + 1 t+1 t+1时刻状态
h t + 1 = f ( z t + 1 ) = f ( U h t + W x t + 1 + b ) \boldsymbol{h}_{t+1}=f\left(\boldsymbol{z}_{t+1}\right)=f\left(\boldsymbol{U} \boldsymbol{h}_{t}+\boldsymbol{W} \boldsymbol{x}_{t+1}+\boldsymbol{b}\right) ht+1=f(zt+1)=f(Uht+Wxt+1+b)
其关于参数 u i j u_{ij} uij的偏导数为
∂ h t + 1 ∂ u i j = ( ∂ + z t + 1 ∂ u i j + ∂ h t ∂ u i j U ⊤ ) ∂ h t + 1 ∂ z t + 1 = ( I i ( [ h t ] j ) + ∂ h t ∂ u i j U ⊤ ) diag ( f ′ ( z t + 1 ) ) = ( I i ( [ h t ] j ) + ∂ h t ∂ u i j U ⊤ ) ⊙ ( f ′ ( z t + 1 ) ) ⊤ \begin{aligned} \frac{\partial \boldsymbol{h}_{t+1}}{\partial u_{i j}} &=\left(\frac{\partial^{+} \boldsymbol{z}_{t+1}}{\partial u_{i j}}+\frac{\partial \boldsymbol{h}_{t}}{\partial u_{i j}} \boldsymbol{U}^{\top}\right) \frac{\partial \boldsymbol{h}_{t+1}}{\partial \boldsymbol{z}_{t+1}} \\ &=\left(\mathbb{I}_{i}\left(\left[\boldsymbol{h}_{t}\right]_{j}\right)+\frac{\partial \boldsymbol{h}_{t}}{\partial u_{i j}} \boldsymbol{U}^{\top}\right) \operatorname{diag}\left(f^{\prime}\left(\boldsymbol{z}_{t+1}\right)\right) \\ &=\left(\mathbb{I}_{i}\left(\left[\boldsymbol{h}_{t}\right]_{j}\right)+\frac{\partial \boldsymbol{h}_{t}}{\partial u_{i j}} \boldsymbol{U}^{\top}\right) \odot\left(f^{\prime}\left(\boldsymbol{z}_{t+1}\right)\right)^{\top}\end{aligned} ∂uij∂ht+1=(∂uij∂+zt+1+∂uij∂htU⊤)∂zt+1∂ht+1=(Ii([ht]j)+∂uij∂htU⊤)diag(f′(zt+1))=(Ii([ht]j)+∂uij∂htU⊤)⊙(f′(zt+1))⊤
其中 I i ( x ) \mathbb{I}_{i}(x) Ii(x)是除了第 i i i行值为 x x x外,其余都为 0 的行向量。
RTRL 算法从第 1 个时刻开始,除了计算循环神经网络的隐状态之外,还利用上式依次前向计算偏导数 ∂ h 1 ∂ u i j , ∂ h 2 ∂ u i j , ∂ h 3 ∂ u i j , ⋯ \frac{\partial \boldsymbol{h}_{1}}{\partial u_{i j}}, \frac{\partial \boldsymbol{h}_{2}}{\partial u_{i j}}, \frac{\partial \boldsymbol{h}_{3}}{\partial u_{i j}}, \cdots ∂uij∂h1,∂uij∂h2,∂uij∂h3,⋯。
这样,假设第 t t t个时刻存在一个监督信息,其损失函数为 L t \mathcal{L}_{t} Lt,就可以同时计算损失函数对 u i j u_{ij} uij的偏导数 ∂ L t ∂ u i j = ∂ h t ∂ u i j ∂ L t ∂ h t \frac{\partial \mathcal{L}_{t}}{\partial u_{i j}}=\frac{\partial \boldsymbol{h}_{t}}{\partial u_{i j}} \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{h}_{t}} ∂uij∂Lt=∂uij∂ht∂ht∂Lt
这样在 t t t时刻,可以实时计算损失 L t \mathcal{L}_{t} Lt关于参数 U \boldsymbol{U} U的梯度,并更新参数。参数 W \boldsymbol{W} W和 b \boldsymbol{b} b的梯度也可以统一按上述方法实时计算。
两种算法比较
RTRL 和 BPTT 都是基于梯度下降的算法,分别通过前向模式和反向模式嘤嘤链式法则来计算梯度。在 RNN中,一般网络输出维度远低于输入维度,因此 BPTT 算法的计算量会更小,但 BPTT 算法需要保存所有时刻的中间梯度,空间复杂度较高。RTRL 算法不需要梯度回传,因此非常适合熊需要在线学习或无线序列的任务中。
1.4 基于门控的循环神经网络
1.4.1 长程依赖问题
RNN 在学习过程中的主要问题是由于梯度消失或爆炸问题,很难建模长时间间隔(Long Range)的状态之间的依赖关系。称为长程依赖问题(Long-Term Dependencies Problem)
在 BPTT 算法中, δ t , k \delta_{t, k} δt,k展开得到
δ t , k = ∏ τ = k t − 1 ( diag ( f ′ ( z τ ) ) U ⊤ ) δ t , t \delta_{t, k}=\prod_{\tau=k}^{t-1}\left(\operatorname{diag}\left(f^{\prime}\left(z_{\tau}\right)\right) U^{\top}\right) \delta_{t, t} δt,k=τ=k∏t−1(diag(f′(zτ))U⊤)δt,t
如果定义 γ ≅ ∥ diag ( f ′ ( z τ ) ) U ⊤ ∥ \gamma \cong\left\|\operatorname{diag}\left(f^{\prime}\left(\boldsymbol{z}_{\tau}\right)\right) \boldsymbol{U}^{\top}\right\| γ≅∥∥∥diag(f′(zτ))U⊤∥∥∥,则
δ t , k ≅ γ t − k δ t , t \delta_{t, k} \cong \gamma^{t-k} \delta_{t, t} δt,k≅γt−kδt,t
若 γ > 1 \gamma>1 γ>1,当 t − k → ∞ t-k \rightarrow \infty t−k→∞ 时 γ t − k → ∞ \gamma^{t-k} \rightarrow \infty γt−k→∞,当间隔 t − k t-k t−k比较大时,梯度也变得比较大,会造成系统不稳定,称为梯度爆炸问题(gradient exploding problem)。
相反若 γ < 1 \gamma<1 γ<1,当 t − k → ∞ t-k \rightarrow \infty t−k→∞ 时 γ t − k → 0 \gamma^{t-k} \rightarrow 0 γt−k→0,当间隔 t − k t-k t−k比较大时,梯度也变得非常小,会出现和深层前馈神经网络类似的梯度消失问题(vanishing gradient problem)。
要注意的是RNN 中的梯度消失不是说 ∂ L t ∂ U \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{U}} ∂U∂Lt的梯度消失了,而是 ∂ L t ∂ h k \frac{\partial \mathcal{L}_{t}}{\partial \boldsymbol{h}_{k}} ∂hk∂Lt的梯度消失了(当 t − k t-k t−k间隔比较大时)。也就是说,参数 U \boldsymbol{U} U的更新注意靠当前时刻 t t t的几个相邻状态 h k \boldsymbol{h}_{k} hk来更新,长距离的状态对参数 U \boldsymbol{U} U没有影响。
由于 RNN 经常使用 Logistic 或 Tanh 作为激活函数,其导数都小于 1,并且权重矩阵 ∥ U ∥ \|\boldsymbol{U}\| ∥U∥也不会太大,因此如果时间间隔 t − k t-k t−k过大, δ t , k \delta_{t, k} δt,k会趋向于 0,因而经常会出现梯度消失现象。
一般而言,RNN 的梯度爆炸问题比较容易解决,一般通过权重衰减或梯度截断来避免。
而解决梯度消失问题除了需要一些优化技巧外,还需要改变模型。比如让 U = I \boldsymbol{U}=\boldsymbol{I} U=I,同时为了不丢失 h t \boldsymbol{h}_{t} ht和 h t − 1 \boldsymbol{h}_{t-1} ht−1之间的非线性激活性质(否则会降低模型的表现力),可以将模型改为
h t = h t − 1 + g ( x t , h t − 1 ; θ ) \boldsymbol{h}_{t}=\boldsymbol{h}_{t-1}+g\left(\boldsymbol{x}_{t}, \boldsymbol{h}_{t-1} ; \theta\right) ht=ht−1+g(xt,ht−1;θ)
,其中 g ( ⋅ ) g(·) g(⋅)是一个非线性函数, θ \theta θ为参数。这样 h t \boldsymbol{h}_{t} ht和 h t − 1 \boldsymbol{h}_{t-1} ht−1之间既有线性关系,也有非线性关系,并且可以缓解梯度消失问题。但这种改进依然存在两个问题:
- 梯度爆炸问题:令 z k = U h k − 1 + W x k + b \boldsymbol{z}_{k}=\boldsymbol{U} \boldsymbol{h}_{k-1}+\boldsymbol{W} \boldsymbol{x}_{k}+\boldsymbol{b} zk=Uhk−1+Wxk+b为第 k k k时刻函数 g ( ⋅ ) g(·) g(⋅)的输入,计算误差项 δ t , k = ∂ L t ∂ z k \delta_{t, k}=\frac{\partial \mathcal{L}_{t}}{\partial z_{k}} δt,k=∂zk∂Lt项时,梯度可能会过大,从而导致梯度爆炸问题。
- 记忆容量问题:随着 h t \boldsymbol{h}_{t} ht不断累积存储新的输入信息,会发生饱和现象。假设 g ( ⋅ ) g(·) g(⋅)为 Logistic 函数,随时间 t t t的增长, h t \boldsymbol{h}_{t} ht会变得越来越大,从而导致 h \boldsymbol{h} h变得饱和。也就是说,隐状态 h t \boldsymbol{h}_{t} ht可以存储的信息有限,随着记忆单元存储的内容越来越多,其丢失的信息也越来越多。
为了解决这个问题,我们引入门控机制来控制信息的累积速度。包括有选择的加入新信息,并有选择的遗忘之前的累积信息。
1.4.2 长短期记忆网络
Long Short-Term Memory Network,LSTM,1997。可以有效的解决简单循环神经网络的梯度爆炸和消失问题。
在 h t = h t − 1 + g ( x t , h t − 1 ; θ ) \boldsymbol{h}_{t}=\boldsymbol{h}_{t-1}+g\left(\boldsymbol{x}_{t}, \boldsymbol{h}_{t-1} ; \theta\right) ht=ht−1+g(xt,ht−1;θ)的基础上,LSTM 网络的主要改进有两个方面:新的内部状态和门控机制。
1.4.2.1 新的内部状态
LSTM 引入了一个新的内部状态(internal state) c t ∈ R D \boldsymbol{c}_{t} \in \mathbb{R}^{D} ct∈RD专门进行线性的循环信息传递。同时(非线性地)输出信息给隐藏层的外部状态 h t ∈ R D \boldsymbol{h}_{t} \in \mathbb{R}^{D} ht∈RD。内部状态 c t \boldsymbol{c}_{t} ct通过下面的公式计算:
c t = f t ⊙ c t − 1 + i t ⊙ c t ~ h t = o t ⊙ tanh ( c t ) \begin{array}{l}\boldsymbol{c}_{t}=\boldsymbol{f}_{t} \odot \boldsymbol{c}_{t-1}+\boldsymbol{i}_{t} \odot \tilde{\boldsymbol{c}_{t}} \\ \boldsymbol{h}_{t}=\boldsymbol{o}_{t} \odot \tanh \left(\boldsymbol{c}_{t}\right)\end{array} ct=ft⊙ct−1+it⊙ct~ht=ot⊙tanh(ct)
其中 f t ∈ [ 0 , 1 ] D , i t ∈ [ 0 , 1 ] D \boldsymbol{f}_{t} \in[0,1]^{D}, \boldsymbol{i}_{t} \in[0,1]^{D} ft∈[0,1]D,it∈[0,1]D 和 o t ∈ [ 0 , 1 ] D \boldsymbol{o}_{t} \in[0,1]^{D} ot∈[0,1]D为三个门(gate)来控制信息传递的路径; ⊙ \odot ⊙为向量元素乘积; c t − 1 \boldsymbol{c}_{t-1} ct−1为上一时刻的记忆单元; c ~ t ∈ R D \tilde{\boldsymbol{c}}_{t} \in \mathbb{R}^{D} c~t∈RD是通过非线性函数得到的候选状态: c ~ t = tanh ( W c x t + U c h t − 1 + b c ) \tilde{\boldsymbol{c}}_{t}=\tanh \left(\boldsymbol{W}_{c} \boldsymbol{x}_{t}+\boldsymbol{U}_{c} \boldsymbol{h}_{t-1}+\boldsymbol{b}_{c}\right) c~t=tanh(Wcxt+Ucht−1+bc)
在每个时刻 t t t,LSTM 网络的内部状态 c t \boldsymbol{c}_{t} ct 记录了到当前时刻为止的历史信息。
1.4.2.2 门控机制
在数字电路中,门(gate)为一个二值变量{0,1}分别代表关闭和开放状态。
LSTM 引入门控机制来控制信息的传递路径。上节所三个门分别为:
-
遗忘门 f t ∈ [ 0 , 1 ] D \boldsymbol{f}_{t} \in[0,1]^{D} ft∈[0,1]D,控制上一时刻的内部状态 c t − 1 \boldsymbol{c}_{t-1} ct−1需要遗忘多少信息。
-
输入门 i t ∈ [ 0 , 1 ] D \boldsymbol{i}_{t} \in[0,1]^{D} it∈[0,1]D 控制当前时刻的候选状态 c ~ t \tilde{\boldsymbol{c}}_{t} c~t有多少信息需要保存。
-
输出门 o t ∈ [ 0 , 1 ] D \boldsymbol{o}_{t} \in[0,1]^{D} ot∈[0,1]D,控制当前时刻的内部状态 c t \boldsymbol{c}_{t} ct有多少信息需要输出给外部状态 h t \boldsymbol{h}_{t} ht。
当 f t = 0 , i t = 1 \boldsymbol{f}_{t}=\mathbf{0}, \boldsymbol{i}_{t}=\mathbf{1} ft=0,it=1时,记忆单元将历史信息清空,并将候选状态向量 c ~ t \tilde{\boldsymbol{c}}_{t} c~t写入。但此时记忆单元 c t \boldsymbol{c}_{t} ct依然和上一时刻的历史信息相关。当 f t = 1 , i t = 0 \boldsymbol{f}_{t}=\mathbf{1}, \boldsymbol{i}_{t}=\mathbf{0} ft=1,it=0时,记忆单元将复制上一时刻的内容,不写入新信息。
LSTM 的门是一种“软”门,取值在(0,1)之间,表示以一定的比例允许信息通过,三个门的计算方式为:
i t = σ ( W i x t + U i h t − 1 + b i ) f t = σ ( W f x t + U f h t − 1 + b f ) o t = σ ( W o x t + U o h t − 1 + b o ) \begin{aligned} \boldsymbol{i}_{t} &=\sigma\left(\boldsymbol{W}_{i} \boldsymbol{x}_{t}+\boldsymbol{U}_{i} \boldsymbol{h}_{t-1}+\boldsymbol{b}_{i}\right) \\ \boldsymbol{f}_{t} &=\sigma\left(\boldsymbol{W}_{f} \boldsymbol{x}_{t}+\boldsymbol{U}_{f} \boldsymbol{h}_{t-1}+\boldsymbol{b}_{f}\right) \\ \boldsymbol{o}_{t} &=\sigma\left(\boldsymbol{W}_{o} \boldsymbol{x}_{t}+\boldsymbol{U}_{o} \boldsymbol{h}_{t-1}+\boldsymbol{b}_{o}\right) \end{aligned} itftot=σ(Wixt+Uiht−1+bi)=σ(Wfxt+Ufht−1+bf)=σ(Woxt+Uoht−1+bo)
其中 σ ( ⋅ ) \sigma(·) σ(⋅)为 Logistic 函数,其输出区间为(0,1), x t \boldsymbol{x}_{t} xt为当前时刻的输入, h t − 1 \boldsymbol{h}_{t-1} ht−1为上一时刻的外部状态。
1.4.2.3 LSTM
下图给出了 LSTM 网络的循环单元结构,其计算过程为:
1)利用上一时刻的外部状态 h t − 1 \boldsymbol{h}_{t-1} ht−1和当前时刻的输入 x t \boldsymbol{x}_{t} xt计算出三个门,以及候选状态 c ~ t \tilde{\boldsymbol{c}}_{t} c~t;
[ c ~ t o t i t f t ] = [ tanh σ σ σ ] ( W [ x t h t − 1 ] + b ) \left[\begin{array}{c}\tilde{\boldsymbol{c}}_{t} \\ \boldsymbol{o}_{t} \\ \boldsymbol{i}_{t} \\ \boldsymbol{f}_{t}\end{array}\right]=\left[\begin{array}{c}\tanh \\ \sigma \\ \sigma \\ \sigma\end{array}\right]\left(\boldsymbol{W}\left[\begin{array}{c}\boldsymbol{x}_{t} \\ \boldsymbol{h}_{t-1}\end{array}\right]+\boldsymbol{b}\right) ⎣⎢⎢⎡c~totitft⎦⎥⎥⎤=⎣⎢⎢⎡tanhσσσ⎦⎥⎥⎤(W[xtht−1]+b)
其中 σ ( ⋅ ) \sigma(·) σ(⋅)为 Logistic 函数,其输出区间为 ( 0 , 1 ) (0,1) (0,1)。
2)结合遗忘门 f t \boldsymbol{f}_{t} ft和输入门 i t \boldsymbol{i}_{t} it来更新记忆单元 c t \boldsymbol{c}_{t} ct;
c t = f t ⊙ c t − 1 + i t ⊙ c t ~ \boldsymbol{c}_{t}=\boldsymbol{f}_{t} \odot \boldsymbol{c}_{t-1}+\boldsymbol{i}_{t} \odot \tilde{\boldsymbol{c}_{t}} ct=ft⊙ct−1+it⊙ct~
3)结合输出门 o t \boldsymbol{o}_{t} ot,将内部状态信息传递给外部状态 h t \boldsymbol{h}_{t} ht。
h t = o t ⊙ tanh ( c t ) \boldsymbol{h}_{t}=\boldsymbol{o}_{t} \odot \tanh \left(\boldsymbol{c}_{t}\right) ht=ot⊙tanh(ct)
通过 LSTM 循环单元,整个网络可以建立较长距离的时序依赖关系。
遗忘的参数初始值一般都设置的比较大,其偏置向量 b f \boldsymbol{b}_f bf设置为 1 或 2。因为在训练 LSTM 网络时,过小的值会使得遗忘门的值比较小。这意味着前一时刻的信息大部分都丢失了,这样网络很难捕捉到长距离的依赖信息。并且相邻时间间隔的梯度会非常小,这会导致梯度弥散问题。
记忆
RNN 中的隐状态 h \boldsymbol{h} h存储了历史信息,可以看做一种记忆(memory)。在简单循环网络中,隐状态每个时刻都会被重写,因此可以看做一种短期记忆(short-term memory)。在神经网络中,长期记忆(long-term memory)可以看做网络参数,隐含了从训练数据中学到的经验,其更新周期要远远慢于短期记忆。而在 LSTM 中,记忆单元 c \boldsymbol{c} c可以在某个时刻捕捉到某个关键信息,并有能力将此关键信息保存一定的时间间隔。记忆单元 c \boldsymbol{c} c中保持信息的生命周期芽哟长于短期记忆 h \boldsymbol{h} h,但又短于长期记忆,因此称为长短期记忆(long short-term memory)即长的“短期记忆”。
1.4.3 门控循环单元网络
Gated Recurrent Unit,GRU,2014.是一种比 LSTM 更加简单的循环神经网络。
和 LSTM 不同,GRU不引入额外的记忆单元,而是在公式 h t = h t − 1 + g ( x t , h t − 1 ; θ ) \boldsymbol{h}_{t}=\boldsymbol{h}_{t-1}+g\left(\boldsymbol{x}_{t}, \boldsymbol{h}_{t-1} ; \theta\right) ht=ht−1+g(xt,ht−1;θ)的基础上引入一个更新门(Up-date Gate)来控制当前状态需要从历史状态中保留多少信(不经过非线性变换)息,以及需要从候选状态中接受多少新信息,即
h t = z t ⊙ h t − 1 + ( 1 − z t ) ⊙ h ~ t \boldsymbol{h}_{t}=\boldsymbol{z}_{t} \odot \boldsymbol{h}_{t-1}+\left(1-\boldsymbol{z}_{t}\right) \odot \tilde{\boldsymbol{h}}_{t} ht=zt⊙ht−1+(1−zt)⊙h~t 其中, z t ∈ [ 0 , 1 ] D \boldsymbol{z}_{t} \in[0,1]^{D} zt∈[0,1]D为更新门:
z t = σ ( W z x t + U z h t − 1 + b z ) \boldsymbol{z}_{t}=\sigma\left(\boldsymbol{W}_{z} \boldsymbol{x}_{t}+\boldsymbol{U}_{z} \boldsymbol{h}_{t-1}+\boldsymbol{b}_{z}\right) zt=σ(Wzxt+Uzht−1+bz) 在LSTM 中,输入门和遗忘门是互补关系,具有一定的冗余性,GRU 网络直接使用一个更新门来控制输入和遗忘之间的平衡。
h ~ t \tilde{\boldsymbol{h}}_{t} h~t为当前时刻的候选状态(即函数 g ( x t h t − 1 ; θ ) g\left(\boldsymbol{x}_{t}\boldsymbol{h}_{t-1} ; \theta\right) g(xtht−1;θ)的定义):
h ~ t = tanh ( W h x t + U h ( r t ⊙ h t − 1 ) + b h ) \tilde{\boldsymbol{h}}_{t}=\tanh \left(\boldsymbol{W}_{h} \boldsymbol{x}_{t}+\boldsymbol{U}_{h}\left(\boldsymbol{r}_{t} \odot \boldsymbol{h}_{t-1}\right)+\boldsymbol{b}_{h}\right) h~t=tanh(Whxt+Uh(rt⊙ht−1)+bh)
r t ∈ [ 0 , 1 ] D \boldsymbol{r}_{t} \in[0,1]^{D} rt∈[0,1]D为重置门(reset gate):
r t = σ ( W r x t + U r h t − 1 + b r ) \boldsymbol{r}_{t}=\sigma\left(\boldsymbol{W}_{r} \boldsymbol{x}_{t}+\boldsymbol{U}_{r} \boldsymbol{h}_{t-1}+\boldsymbol{b}_{r}\right) rt=σ(Wrxt+Urht−1+br)
用来控制候选状态 h ~ t \tilde{\boldsymbol{h}}_{t} h~t的计算是否依赖上一时刻的状态 h t − 1 \boldsymbol{h}_{t-1} ht−1。
可以看出,当 z t = 0 , r = 1 \boldsymbol{z}_{t}=\boldsymbol{0}, \boldsymbol{r}=\boldsymbol{1} zt=0,r=1 时,GRU 网络退化为简单循环神经网络;若 z t = 0 , r = 0 \boldsymbol{z}_{t}=\boldsymbol{0}, \boldsymbol{r}=\boldsymbol{0} zt=0,r=0时,当前状态 h t \boldsymbol{h}_{t} ht只和当前输入 x t \boldsymbol{x}_t xt相关,和历史状态 h t − 1 \boldsymbol{h}_{t-1} ht−1无关。当 z t = 1 \boldsymbol{z}_{t}=1 zt=1时,当前状态 h t = h t − 1 \boldsymbol{h}_{t}=\boldsymbol{h}_{t-1} ht=ht−1,和当前输入 x t \boldsymbol{x}_{t} xt无关。
GRU网络的循环单元结构:
1.5 深层循环神经网络
增加循环神经网络的深度可以增强网络的能力。此处的“深度”主要是增加同一时刻网络输入到输出之间的路径 x t → y t \boldsymbol{x}_{t} \rightarrow \boldsymbol{y}_{t} xt→yt,比如增加输入到隐状态 x t → h t \boldsymbol{x}_{t} \rightarrow \boldsymbol{h}_{t} xt→ht以及隐状态到输出 h t → y t \boldsymbol{h}_{t} \rightarrow \boldsymbol{y}_{t} ht→yt之间的路径的深度。
1.5.1 堆叠循环神经网络
一种常见的增加 RNN 深度的做法是将多个循环网络堆叠起来,称为堆叠循环神经网络(Stacked Recurrent Neural Network,SRNN)。一个堆叠的简单循网络(Stacked SRN)也称为循环多层感知机(Recurrent Multi-Layer Perceptron,RMLP)
下图给出了按时间展开的堆叠循环神经网络,第 l l l层网络的输入是第 l − 1 l-1 l−1层网络的输出。
1.5.2 双向循环神经网络
在有些任务中,一个时刻的输出不但和过去时刻的信息有关,也和后续时刻的信息有关。如一个句子的词的词性由它的上下文决定。因此这些任务中,我们可以增加一个按照时间的逆序来传递信息的网络层,来增强网络的能力。
双向循环神经网络(Bidirectional Recurrent Neural Network,Bi-RNN)由两层循环神经网络组成,他们的输入相同,只是信息传递的方向不同。
假设第 1 层按时间顺序,第2层按时间逆序,在时刻 t t t时的隐状态定义为 h t ( 1 ) \boldsymbol{h}_{t}^{(1)} ht(1)和 h t ( 2 ) \boldsymbol{h}_{t}^{(2)} ht(2),则
h t ( 1 ) = f ( U ( 1 ) h t − 1 ( 1 ) + W ( 1 ) x t + b ( 1 ) ) h t ( 2 ) = f ( U ( 2 ) h t + 1 ( 2 ) + W ( 2 ) x t + b ( 2 ) ) h t = h t ( 1 ) ⊕ h t ( 2 ) \begin{aligned} \boldsymbol{h}_{t}^{(1)} &=f\left(\boldsymbol{U}^{(1)} \boldsymbol{h}_{t-1}^{(1)}+\boldsymbol{W}^{(1)} \boldsymbol{x}_{t}+\boldsymbol{b}^{(1)}\right) \\ \boldsymbol{h}_{t}^{(2)} &=f\left(\boldsymbol{U}^{(2)} \boldsymbol{h}_{t+1}^{(2)}+\boldsymbol{W}^{(2)} \boldsymbol{x}_{t}+\boldsymbol{b}^{(2)}\right) \\ \boldsymbol{h}_{t} &=\boldsymbol{h}_{t}^{(1)} \oplus \boldsymbol{h}_{t}^{(2)} \end{aligned} ht(1)ht(2)ht=f(U(1)ht−1(1)+W(1)xt+b(1))=f(U(2)ht+1(2)+W(2)xt+b(2))=ht(1)⊕ht(2)
其中 ⊕ \oplus ⊕为向量拼接操作。
下图为按时间展开的双向循环神经网络: