深度学习3:循环神经网络Recurrent Neural Network(基于Python MXNet.Gluon框架)

循环神经网络概述

  在前馈神经网络中,信息的传递是单向的,这种限制虽然使得网络变得更容易学习,但在一定程度上也减弱了神经网络模型的能力1

  在生物神经网络中,神经元之间的连接关系要复杂得多。前馈神经网络不能解决的两个问题:

  1. 前馈神经网络每次输入都是独立的,但是在很多现实任务中,网络的输入不仅和当前时刻的输入相关,也和其过去一段时间的输出相关

  2. 前馈网络难以处理时序数据(比如视频、语音、文本等)。时序数据的长度一般是不固定的,而前馈神经网络要求输入和输出的维数都是固定的,不能任意改变。

  因此,当处理这一类和时序数据相关的问题时,就需要一种能力更强的模型。

  循环神经网络(Recurrent Neural Network,RNN)是一类具有短期记忆能力的神经网络。在循环神经网络中,神经元不但可以接受其他神经元的信息,也可以接受自身的信息,形成具有环路的网络结构。

  循环神经网络的参数学习可以通过随时间反向传播算法来学习。随时间反向传播算法即按照时间的逆序将错误信息一步步地往前传递。当输入序列比较长时,会存在梯度爆炸和消失问题 ,也称为长程依赖问题。为了解决这个问题,人们对循环神经网络进行了很多的改进,其中最有效的改进方式引入门控机制(Gating Mechanism)。

  此外,循环神经网络可以很容易地扩展到两种更广义的记忆网络模型:递归神经网络图网络

给网络增加记忆能力

  为了处理这些时序数据并利用其历史信息,我们需要让网络具有短期记忆能力。而前馈网络是一种静态网络,不具备这种记忆能力。

  一般来讲,我们可以通过以下三种方法来给网络增加短期记忆能力。

延时神经网络

  一种简单的利用历史信息的方法是建立一个额外的延时单元,用来存储网络的历史信息(可以包括输入、输出、隐状态等)。比较有代表性的模型是延时神经网络(Time Delay Neural Network,TDNN)。

  延时神经网络是在前馈网络中的非输出层都添加一个延时器,记录神经元的最近几次活性值。在第 t t 个时刻,第 l l 层神经元的活性值依赖于第 l 1 l-1 层神经元的最近 K K 个时刻的活性值,即

h t ( l ) = f ( h t ( l 1 ) , h t 1 ( l 1 ) , , h t K ( l 1 ) ) , h_t^{(l)}=f(h_t^{(l-1)},h_{t-1}^{(l-1)},\cdots,h_{t-K}^{(l-1)}),

其中 h t ( l ) R M l h_t^{(l)}\in\mathbb{R}^{M_l} 表示第 l l 层神经元在时刻 t t 的活性值, M l M_l 为第 l l 层神经元的数量。通过延时器,前馈网络就具有了短期记忆的能力。

有外部输入的非线性自回归模型

  自回归模型(AutoRegressive Model,AR)是统计学上常用的一类时间序列模型,用一个变量 y t y_t 的历史信息来预测自己。

y t = ω 0 + k = 1 K ω k y t k + ϵ t , y_t=\omega_0+\sum_{k=1}^K\omega_k\textbf{y}_{t-k}+\epsilon_t,

其中 K K 为超参数, ω 0 , , ω K \omega_0,\cdots,\omega_K 为可学习参数, ϵ t N ( 0 , σ 2 ) \epsilon_t\sim N(0,\sigma^2) 为第 t t 个时刻的噪声,方差 σ 2 \sigma^2 和时间无关。

  有外部输入的非线性自回归模型(Nonlinear AutoRegressive with Exoge-nous Inputs Model,NARX)是自回归模型的扩展,在每个时刻 t t 都有一个外部输入 x t x_t ,产生一个输出 y t y_t 。NARX 通过一个延时器记录最近 K x K_x 次的外部输入和最近 K y K_y 次的输出,第 t t 个时刻的输出 y t y_t

y t = f ( x t , x t 1 , , x t K x , y t 1 , y t 2 , , y t K y ) , y_t=f(x_t,x_{t-1},\cdots,x_{t-K_x},y_{t-1},y_{t-2},\cdots,y_{t-K_y}),

其中 f ( ) f(\cdot) 表示非线性函数,可以是一个前馈网络, K x K_x K y K_y 为超参数。

循环神经网络

  循环神经网络(Recurrent Neural Network,RNN)通过使用带自反馈的神经元,能够处理任意长度的时序数据

  给定一个输入序列 x 1 : T = ( x 1 , x 2 , , x t , , x T ) \textbf{x}_{1:T}=(\textbf{x}_1,\textbf{x}_2,\cdots,\textbf{x}_t,\cdots,\textbf{x}_T) ,循环神经网络通过下面公式更新带反馈边的隐藏层的活性值 h t \textbf{h}_t

h t = f ( h t 1 , x t ) \textbf{h}_t=f(\textbf{h}_{t-1},\textbf{x}_t)

其中 h 0 = 0 , f ( ) \textbf{h}_0=0,f(\cdot) 为一个非线性函数,可以是一个前馈网络。

  从数学上讲,公式 h t = f ( h t 1 , x t ) \textbf{h}_t=f(\textbf{h}_{t-1},\textbf{x}_t) 可以看成一个动力系统。因此,隐藏层的活性值 h t \textbf{h}_t 在很多文献上也称为状态(State)或隐状态(Hidden State)。理论上,循环神经网络可以近似任意的非线性动力系统。

简单循环网络

  简单循环网络(Simple Recurrent Network,SRN)是一个非常简单的循环神经网络,只有一个隐藏层的神经网络。在一个两层的前馈神经网络中,连接存在相邻的层与层之间,隐藏层的节点之间是无连接的。而简单循环网络增加了从隐藏层到隐藏层的反馈连接。

  令向量 x t R M \textbf{x}_t\in\mathbb{R}^M 表示在时刻 t t 时网络的输入, h t R D \textbf{h}_t\in\mathbb{R}^D 表示隐藏层的状态(即隐藏层神经元活性值),则 h t \textbf{h}_t 不仅和当前时刻的输入 x t \textbf{x}_t 相关,也和上一个时刻的隐藏层状态 h t 1 \textbf{h}_{t-1} 相关。 简单循环网络在时刻 t t 的状态更新公式为

z t =   U h t 1 + W x t + b h t =   f ( z t ) \begin{aligned} \textbf{z}_t=&\ \textbf{U}\textbf{h}_{t-1}+\textbf{W}\textbf{x}_t+\textbf{b}\\ \textbf{h}_t=&\ f(\textbf{z}_t) \end{aligned}

其中 z t \textbf{z}_t 为隐藏层的净输入, U R D × D \textbf{U}\in\mathbb{R}^{D\times D} 状态-状态权重矩阵 W R D × M \textbf{W}\in\mathbb{R}^{D\times M} 状态-输入权重矩阵 b R D \textbf{b}\in\mathbb{R}^D 偏置向量 f ( ) f(\cdot) 非线性激活函数,通常为 L o g i s t i c Logistic 函数或 T a n h Tanh 函数。

在这里插入图片描述
  上面两个公式也经常直接写为

h t = f ( U h t 1 + W x t + b ) \textbf{h}_t=f(\textbf{U}\textbf{h}_{t-1}+\textbf{W}\textbf{x}_t+\textbf{b})

  如果我们把每个时刻的状态都看作是前馈神经网络的一层,循环神经网络可以看作是在时间维度上权值共享的神经网络。下图给出了按时间展开的循环神经网络。

解释:

循环神经网络的计算能力

  由于循环神经网络具有短期记忆能力,相当于存储装置,因此其计算能力十分强大。前馈神经网络可以模拟任何连续函数,而循环神经网络可以模拟任何程序

  我们先定义一个完全连接的循环神经网络,其输入为 x t \textbf{x}_t ,输出为 y t \textbf{y}_t

h t =   f ( U h t 1 + W x t + b ) y t =   V h t \begin{aligned} \textbf{h}_t=&\ f(\textbf{U}\textbf{h}_{t-1}+\textbf{W}\textbf{x}_t+\textbf{b})\\ \textbf{y}_t=&\ \textbf{V}\textbf{h}_t \end{aligned}

其中 h \textbf{h} 为隐状态, f ( ) f(\cdot) 为非线性激活函数, U , W , b \textbf{U},\textbf{W},\textbf{b} V \textbf{V} 为网络参数。

循环神经网络的通用近似定理

  循环神经网络的拟合能力也十分强大。一个完全连接的循环网络是任何非线性动力系统的近似器。

图灵完备

  图灵完备(Turing Completeness)是指一种数据操作规则,比如一种计算机编程语言,可以实现图灵机(Turing Machine)的所有功能,解决所有的可计算问题。

  目前主流的编程语言(比如 C++、Java、Python 等)都是图灵完备的。

应用到机器学习

  循环神经网络可以应用到很多不同类型的机器学习任务。根据这些任务的特点可以分为以下几种模式:序列到类别模式、同步的序列到序列模式、异步的序列到序列模式。

序列到类别模式

  序列到类别模式主要用于序列数据的分类问题:输入为序列,输出为类别。

  比如在文本分类中,输入数据为单词的序列,输出为该文本的类别。

  假设一个样本 x 1 : T = ( x 1 , x 2 , , x t , , x T ) \textbf{x}_{1:T}=(\textbf{x}_1,\textbf{x}_2,\cdots,\textbf{x}_t,\cdots,\textbf{x}_T) 为一个长度为 T T 的序列,输出为一个类别 y { 1 , 2 , , C } y\in\{1,2,\cdots,C\} 。我们可以将样本 x \textbf{x} 按不同时刻输入到循环神经网络中,并得到不同时刻的隐藏状态 h 1 , , h T \textbf{h}_1,\cdots,\textbf{h}_T 。我们可以将 h T \textbf{h}_T 看作整个序列的最终表示(或特征),并输入给分类器 g ( ) g(\cdot) 进行分类。

y ^ = g ( h T ) \hat{y}=g(\textbf{h}_T)

其中 g ( ) g(\cdot) 可以是简单的线性分类器(比如 Logistic 回归)或复杂的分类器(比如多层前馈神经网络)。

  除了将最后时刻的状态作为整个序列的表示之外,我们还可以对整个序列的所有状态进行平均,并用这个平均状态来作为整个序列的表示

y ^ = g ( 1 T t = 1 T h t ) \hat{y}=g(\frac{1}{T}\sum_{t=1}^T\textbf{h}_t)

同步的序列到序列模式

  同步的序列到序列模式主要用于序列标注(Sequence Labeling)任务,即每一时刻都有输入和输出,输入序列和输出序列的长度相同。比如在词性标注(Part-of-Speech Tagging)中,每一个单词都需要标注其对应的词性标签。

  在同步的序列到序列模式(如图6.4所示)中,输入为一个长度为 T T 的序列 x 1 : T = ( x 1 , x 2 , , x t , , x T ) \textbf{x}_{1:T}=(\textbf{x}_1,\textbf{x}_2,\cdots,\textbf{x}_t,\cdots,\textbf{x}_T) ,输出为序列 y 1 : T = ( y 1 , y 2 , , y t , , y T ) y_{1:T}=(y_1,y_2,\cdots,y_t,\cdots,y_T) 。样本 x \textbf{x} 按不同时刻输入到循环神经网络中,并得到不同时刻的隐状态 h 1 , , h T \textbf{h}_1,\cdots,\textbf{h}_T 每个时刻的隐状态 h t \textbf{h}_t 代表了当前时刻和历史的信息,并输入给分类器 g ( ) g(\cdot) 得到当前时刻的标签 y ^ \hat{y}

y ^ t = g ( h t ) , t [ 1 , T ] \hat{y}_t=g(\textbf{h}_t),t\in[1,T]

异步的序列到序列模式

  异步的序列到序列模式也称为编码器-解码器(Encoder-Decoder)模型,即输入序列和输出序列不需要有严格的对应关系,也不需要保持相同的长度

  比如在机器翻译中,输入为源语言的单词序列,输出为目标语言的单词序列。

  出为长度为 M M 的序列 y 1 : M = ( y 1 , y 2 , , y t , , y M ) y_{1:M}=(y_1,y_2,\cdots,y_t,\cdots,y_M) 。异步的序列到序列模式一般通过先编码后解码的方式来实现。先将样本 x \textbf{x} 按不同时刻输入到一个循环神经网络(编码器)中,并得到其编码 h T \textbf{h}_T 。然后再使用另一个循环神经网络(解码器),得到输出序列 y ^ 1 : M \hat{y}_{1:M} 。为了建立输出序列之间的依赖关系,在解码器中通常使用非线性的自回归模型

h t =   f 1 ( h t 1 , x t ) , t [ 1 , T ] h T + t =   f 2 ( h T + t 1 , y ^ t 1 ) , t [ 1 , M ] y ^ t =   g ( h T + t ) , t [ 1 , M ] \begin{aligned} \textbf{h}_t=&\ f_1(\textbf{h}_{t-1},\textbf{x}_t),t\in[1,T] \\ \textbf{h}_{T+t}=&\ f_2(\textbf{h}_{T+t-1},\hat{y}_{t-1}) ,t\in[1,M]\\ \hat{y}_t=&\ g(\textbf{h}_{T+t}),t\in[1,M] \end{aligned}

其中 f 1 ( ) f_1(\cdot) f 2 ( ) f_2(\cdot) 分别为用作编码器和解码器的循环神经网络, g ( ) g(\cdot) 为分类器, y ^ t \hat{y}_t 为预测输出 y ^ t \hat{y}_t 的向量表示.

参数学习

  循环神经网络的参数可以通过梯度下降方法来进行学习。

  以随机梯度下降为例,给定一个训练样本 ( x , y ) (\textbf{x},\textbf{y}) ,其中 x 1 : T = ( x 1 , x 2 , , x t , , x T ) \textbf{x}_{1:T}=(\textbf{x}_1,\textbf{x}_2,\cdots,\textbf{x}_t,\cdots,\textbf{x}_T) 为长度是 T T 的输入序列, y 1 : T = ( y 1 , y 2 , , y t , , y T ) y_{1:T}=(y_1,y_2,\cdots,y_t,\cdots,y_T) 是长度为 T T 的标签序列。即在每个时刻 t t ,都有一个监督信息 y t y_t ,我们定义时刻 t t 的损失函数为

L t = L ( y t , g ( h t ) ) \mathcal{L}_t=\mathcal{L}(y_t,g(\textbf{h}_t))

其中 g ( h t ) g(\textbf{h}_t) t t 时刻的输出, L \mathcal{L} 为可微分的损失函数,比如交叉熵。那么整个序列的损失函数为

L = t = 1 T L t \mathcal{L}=\sum_{t=1}^T\mathcal{L}_t

  整个序列的损失函数 L \mathcal{L} 关于参数 U \textbf{U} 的梯度为

L U = t = 1 T L t U \frac{\partial{\mathcal{L}}}{\partial{\textbf{U}}}=\sum_{t=1}^T\frac{\partial{\mathcal{L}_t}}{\partial{\textbf{U}}}

即每个时刻损失 L t \mathcal{L}_t 为参数 U \textbf{U} 的偏导数之和。

  循环神经网络中存在一个递归调用的函数 f ( ) f(\cdot) ,因此其计算参数梯度的方式和前馈神经网络不太相同。在循环神经网络中主要有两种计算梯度的方式:随时间反向传播(BPTT)算法和实时循环学习(RTRL)算法。

随时间反向传播算法

  随时间反向传播(BackPropagation Through Time,BPTT)算法的主要思想是通过类似前馈神经网络的错误反向传播算法来计算梯度。

  BPTT 算法将循环神经网络看作是一个展开的多层前馈网络,其中“每一层”对应循环网络中的“每个时刻”这样,如下图所示

循环神经网络就可以按照前馈网络中的反向传播算法计算参数梯度。在“展开”的前馈网络中,所有层的参数是共享的,因此参数的真实梯度是所有“展开层”的参数梯度之和。

  介绍一个手动算的例子2

实时循环学习算法

  与反向传播的 BPTT 算法不同的是,实时循环学习(Real-Time Recurrent Learning,RTRL)是通过前向传播的方式来计算梯度。

两种算法的比较

  两种算法比较 RTRL 算法和 BPTT 算法都是基于梯度下降的算法,分别通过前向模式和反向模式应用链式法则来计算梯度。在循环神经网络中,一般网络输出维度远低于输入维度,因此 BPTT 算法的计算量会更小,但是 BPTT 算法需要保存所有时刻的中间梯度,空间复杂度较高。RTRL 算法不需要梯度回传,因此非常适合用于需要在线学习或无限序列的任务中。

RTRL 算法和 BPTT 算法比较
BPTT 算法 RTRL 算法
反向模式应用链式法则来计算梯度 前向模式应用链式法则来计算梯度
BPTT 算法的计算量会更小,但是 BPTT 算法需要保存所有时刻的中间梯度,空间复杂度较高 RTRL 算法不需要梯度回传,因此非常适合用于需要在线学习或无限序列的任务中

手动计算的例子

z 1 =   U h 0 + W x 1 + b =   [ 1 0 2 1 ] [ 1 2 ] + [ 1 2 3 4 5 6 ] [ 1 0 0 ] + [ 2 2 ] =   [ 2 10 ] \begin{aligned} z_1=&\ Uh_0+Wx_1+b \\ =&\ \begin{bmatrix}-1 & 0\\2 & 1\end{bmatrix}\cdot\begin{bmatrix}1\\2\end{bmatrix}+\begin{bmatrix}1 & 2 & 3\\4 & 5 & 6\end{bmatrix}\cdot\begin{bmatrix}1\\0\\0\end{bmatrix}+\begin{bmatrix}-2\\2\end{bmatrix}\\ =&\ \begin{bmatrix}-2\\10\end{bmatrix} \end{aligned}

h 1 = R e l u ( z 1 ) =   [ 0 10 ] h_1=Relu(z_1)=\ \begin{bmatrix}0\\10\end{bmatrix}

y 1 = V h 1 = [ 1 0 2 1 3 1 ] [ 0 10 ] = [ 0 10 10 ] y_1=Vh_1=\begin{bmatrix}1 & 0\\2 & -1\\3 & 1\end{bmatrix}\cdot\begin{bmatrix}0\\10\end{bmatrix}=\begin{bmatrix}0\\-10\\10\end{bmatrix}

z 2 =   U h 1 + W x 2 + b =   [ 1 0 2 1 ] [ 0 10 ] + [ 1 2 3 4 5 6 ] [ 0 1 0 ] + [ 2 2 ] =   [ 0 17 ] \begin{aligned}z_2=&\ Uh_1+Wx_2+b \\=&\ \begin{bmatrix}-1 & 0\\2 & 1\end{bmatrix}\cdot\begin{bmatrix}0\\10\end{bmatrix}+\begin{bmatrix}1 & 2 & 3\\4 & 5 & 6\end{bmatrix}\cdot\begin{bmatrix}0\\1\\0\end{bmatrix}+\begin{bmatrix}-2\\2\end{bmatrix}\\=&\ \begin{bmatrix}0\\17\end{bmatrix}\end{aligned}

h 2 = R e l u ( z 2 ) =   [ 0 17 ] h_2=Relu(z_2)=\ \begin{bmatrix}0\\17\end{bmatrix}

y 2 = V h 2 = [ 1 0 2 1 3 1 ] [ 0 17 ] = [ 0 17 17 ] y_2=Vh_2=\begin{bmatrix}1 & 0\\2 & -1\\3 & 1\end{bmatrix}\cdot\begin{bmatrix}0\\17\end{bmatrix}=\begin{bmatrix}0\\-17\\17\end{bmatrix}

在这里插入图片描述

在这里插入图片描述

长程依赖问题

  循环神经网络在学习过程中的主要问题是由于梯度消失或爆炸问题,很难建模长时间间隔(Long Range)的状态之间的依赖关系。

  虽然简单循环网络理论上可以建立长时间间隔的状态之间的依赖关系,但是由于梯度爆炸或消失问题,实际上只能学习到短期的依赖关系。这样,如果时刻 t t 的输出 y t y_t 依赖于时刻 k k 的输入 x k \textbf{x}_k ,当间隔 t k t-k 比较大时,简单神经网络很难建模这种长距离的依赖关系,称为长程依赖问题(Long-Term Dependencies Problem)。

改进方案

  为了避免梯度爆炸或消失问题,一种最直接的方式就是选取合适的参数,同时使用非饱和的激活函数,尽量使得 d i a g ( f ( z ) ) U T 1 diag(f'(\textbf{z}))\textbf{U}^T\approx1 ,这种方式需要足够的人工调参经验,限制了模型的广泛应用。比较有效的方式是通过改进模型或优化方法来缓解循环网络的梯度爆炸和梯度消失问题。

梯度爆炸

  一般而言,循环网络的梯度爆炸问题比较容易解决,一般通过权重衰减梯度截断来避免。

  权重衰减是通过给参数增加 l 1 l_1 l 2 l_2 范数的正则化项来限制参数的取值范围,从而使得 γ 1 \gamma\leq1

  梯度截断是另一种有效的启发式方法,当梯度的模大于一定阈值时,就将它截断成为一个较小的数。

梯度消失

  梯度消失是循环网络的主要问题。除了使用一些优化技巧外,更有效的方式就是改变模型,比如让 U = I \textbf{U}=\textbf{I} ,同时令 h t h t 1 = I \frac{\partial{\textbf{h}_t}}{\partial{\textbf{h}_{t-1}}}=\textbf{I} 为单位矩阵,即

h t = h t 1 + g ( x t ; θ ) \textbf{h}_t=\textbf{h}_{t-1}+g(\textbf{x}_t;\theta)

其中 g ( ) g(\cdot) 是一个非线性函数, θ \theta 为参数。

  上式中, h t \textbf{h}_t h t 1 \textbf{h}_{t-1} 之间为线性依赖关系,且权重系数为 1,这样就不存在梯度爆炸或消失问题。但是,这种改变也丢失了神经元在反馈边上的非线性激 活的性质,因此也降低了模型的表示能力。

  为了避免这个缺点,我们可以采用一种更加有效的改进策略:

h t = h t 1 + g ( x t , h t 1 ; θ ) \textbf{h}_t=\textbf{h}_{t-1}+g(\textbf{x}_t,\textbf{h}_{t-1};\theta)

这样 h t \textbf{h}_t h t 1 \textbf{h}_{t-1} 之间为线性依赖关系,也有非线性关系,并且可以缓解梯度消失问题。但这种改进依然存在两个问题:

参考资料


  1. 邱锡鹏. 神经网络与深度学习[M]:13-14
    https://nndl.github.io/. ↩︎

  2. 博客园博主:Determined22. 机器学习 —— 基础整理(八)循环神经网络的BPTT算法步骤整理;梯度消失与梯度爆炸
    https://www.cnblogs.com/Determined22/p/6562555.html. ↩︎

发布了14 篇原创文章 · 获赞 7 · 访问量 3249

猜你喜欢

转载自blog.csdn.net/sunsimiaofromsh/article/details/105119059
今日推荐