首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

深度学习第6期:循环神经网络RNN

在深度学习中,有两种结构特殊的神经网络是目前应用最多的,一种是卷积神经网络(CNN),另外一种是循环神经网络(RNN)。在前面,我们已经介绍过CNN的构造,以及其在计算机视觉领域中的应用。今天,我们来介绍RNN的主要思想。

在CNN有关的科普文中,我们提到,图片数据是一种类型特殊的数据,其像素点之间具有一定的空间结构。而CNN正是由于利用了这种空间结构,才使得其在图片型数据的问题上展现出比普通的神经网络更好的效果。而今天我们要介绍的RNN同样适合处理一种特殊类型的数据,那就是时间序列型的数据。RNN的结构正是利用了这种数据在时间维度上的有序性,才得以发挥比普通神经网络更好的效果。

时间序列问题

举个例子,我们有一架大风车,可以用来风力发电,为建设社会主义事业贡献力量。我们有一段时间内各个等间距时刻的风速(x1,x2,...,xn),我们也有相应时刻的风车的发电量(y1,y2,...,yn)。我们需要拟合一个从风速预测发电量的函数。一种最朴素的想法是将每一个时刻的数据当成是独立同分布的样本,即假定每一个时刻的发电量只与该时刻的风速有关。则问题化为一个普通的回归问题,我们只要用一个普通的回归器回归x与y之间的关系即可。

但是事实上,用这种思路建立的回归器往往不能达到好的效果。我们想象有两种情况,一种是在漫长的时间段里风平浪静,而后忽然起风,使得风速达到5;另一种是在很长的一个时间段里狂风骤雨,然后逐渐云淡风轻,风速减慢到5。这两种情况下,风车的发电量会是相同的吗?这启发我们,影响一个时刻的发电量的绝不只有该时刻的风速,还包括上一段时间的风速、上一段时间的发电量。

另外,我们还要考虑到的是,一些我们不可见的变量(即手头没有数据的变量)也有可能会影响发电量,例如风车转动的角加速度等等。在各个样本独立同分布的问题中,我们一般对这些可见的变量是无能为力的,只能简单地将其视为混杂在模型的随机噪声。但是,在时间序列的问题中,我们或许可以根据一些我们可见变量的变化情况,推断出背后不可见的变量的部分信息,并且将这些信息加以利用。

除了风速预测发电之外,天气数据、金融数据、经济数据,都是时间序列类型的数据。由于与我们的生活各方面息息相关,所以有很高的研究价值。目前,专门针对时间序列数据的研究已经从一般的统计学中独立出来,发展成为了一个独特的学科。对于时间序列的问题,已经有十分成熟的线性模型,包括自回归(Auto Regression)、滑动平均(Moving Average)、ARMA等模型。但是,由于线性模型的表示能力较弱,所以其拟合能力较弱。并且,由于线性模型也无法添加隐含变量,这使得其结构过于简单,无法针对复杂的时间序列问题作出充分的解释与足够好的拟合。

对于非线性的时间序列,也有不少传统的模型。例如隐马氏链(HMM)模型。其基本的模型结构如下图所示:

我们还可以将隐马氏链的结构设计得复杂一些:我们假定我们的序列有输入xt,有一个隐含状态zt,有可观测状态yt。模型假定隐含状态之间,根据输入会有某一转移概率,即P(zt+1|zt, xt)。另外,模型还假定可观测状态是由隐含状态与某一观测分布函数P(yt|zt)产生的。对于一组数据xt,yt,t=1,2,3,…,n,为了模拟出一个隐马氏链模型,我们会采用Baum–Welch算法。这事实上是一种EM算法,它用迭代的求期望-求最大似然的步骤求出隐马氏链的参数,以求出能够使得似然函数极大化的模型参数(即同时最大化状态转移概率P(zt+1|zt,xt)与观测概率P(yt|zt)的联合概率)。

对于预测风速发电的问题,如果我们不采取时间序列的观点,而只是采取独立同分布回归的观点,则我们只需要对P(yt|zt)进行极大似然估计,即可得到我们的模型。

如果我们采用隐马氏链模型,则意味着我们要想象模型背后有一个隐含变量zt代表“风车的状态”。某一时刻的风车的状态由前一时刻的状态,以及这个时刻的风速决定。而每一个时刻的发电量,又由该时刻的风车的状态决定。在这种模型设定下,我们就要对P(zt+1|zt,xt)与P(yt|zt)的联合概率进行极大似然估计。这无疑导致了更加复杂的计算量(无法直接用优化求解,而要用到EM算法进行迭代),但由于其运用了时间维度上的有序性,这也将导致模型有更好的解释性与更加强大拟合能力。

除了下面我们将要介绍的RNN则是实现非线性时间序列模型的另一个好方法。正如目前神经网络在其他领域相对传统模型都表现出一定的优势一样,RNN在非线性时间序列这个领域也展示出优于传统模型的性能。

RNN

我们通过一个最简单的RNN与NN的对比,来理解二者的不同。设RNN只有一个隐藏层。其输入为x,输出为o,中间有一个隐藏层s。其一共有三组参数,输入层到隐藏层的参数U,隐藏层到输出层的参数V,以及隐藏层之间循环结构的参数W。一般的神经网络只有前两组参数U与V,而RNN之所以与普通的神经网络不同,正是由于加入了W。其示意图如下:

RNN之间信息传递的运算公式如下所示:

其时间上的相关性主要体现在状态随着时间传递的系数——W上。如果我们去掉了W及状态随时间传递的关系,则RNN变回普通的NN,其运算公式如下:

如果我们采取普通的NN,就相当于是忽略了数据在时间上的相关性。于是,我们将数据中的一个batch拿去训练,与将一个样本拿去训练,其效果是差不多的。故而NN的训练中,假定输入x、输出的标签y给定。总的损失函数为Loss=(y–o)2/2,BP算法求出对各个参数的导数如下:

而在RNN中,由于隐藏层的状态x1,x2,…,xt与输出y1,y2,…,yt一起拿去训练,才能正确地算出对于各个参数的更新方式。我们设总的损失函数为各个预测与真实值的标签的平方误差之和。RNN对于各个参数的更新将会是十分复杂的。

首先,我们求Loss1=(y1–o1)2/2对于各项参数的梯度。它与上面NN给出的梯度没有任何区别:

接下来求Loss2=(y2–o2)2/2对于各项参数的梯度:

可以看到的是,Loss2对于V的梯度与Loss1对于V的梯度形式一样的,但是Loss2对于U的梯度对比Loss2对于U的梯度要更为复杂。这是因为在o2的计算公式中包含了两个U。从x2生成s2时候与U有关,从x1生成s1时候也与U有关,而另一方面,s2的生成与s1有关。所以,Loss2对于U的梯度就需要分成两部分计算,这也使得其更加复杂。

可以想象,Loss3=(y3–o3)2/2对于各项参数的梯度将会更加复杂,其具体的梯度公式推导为:

由于在o3的表达式中出现了3项U与2项W,所以其对于U与W的梯度的表达式也变得比o2更加复杂。我们可以想象,对于Loss4,Loss5,…,其对于U与W的梯度的表达式将会越来越复杂。

对于用风速预测发电的情况而言,每一个时刻的情况只受到过去的影响,而不可能受到未来的影响。这是时间单向流动的性质所决定的。但是,对于其他序列生成问题,如果这个序列不是时间维度的序列,而是其他维度(比如从左到右)的序列,则该序列中每一个位置除了受到之前的影响,也有可能受到之后的影响。这时候就要采用双向RNN,即设定两组参数W1和W2,分别代表隐含层在时间维度上的前传与后传关系。即设定每一个隐含状态st=f(Uxt+W1st−1+W2st+1)。其示意图如下:

不难想到,在双向RNN中,所有的ot与整个序列的输入x1,x2,…,xn都有关系。所以,对于所有Lossi,其对于U的梯度是同样复杂的。

上面,我们只是用了一个结构最为简单的RNN做为例子。在现实中,如果要模拟稍微复杂一些的序列生成机制,则只有两层的RNN显然是不够的。为此,要使用深度的RNN,其示意图如下(以下为一个多层的双向RNN):

不难想到,当我们采用了比较深的RNN结构,如果输入的序列比较长,则我们训练时用BP算法求出的对于各个参数的梯度将会极其复杂。所以,我们在训练的时候尽量不会选取太长的序列。例如,我们拥有的风速-发电量的数据有1000项的长度,但是在训练时候,我们很可能只是将其截取为50项的序列,依次放入RNN进行训练。而另一方面,当我们训练完成后,我们可以用来生产任意长的序列。

RNN的局限性

在早期的NN中,人们习惯采用Sigmoid函数为激活函数。但是Sigmoid有一个特点——其导数无论如何都在0到1/4之间。使用BP算法的时候,我们是从输出端逐渐地往输入端求各项参数的梯度。而由于Sigmoid函数导数在0到1/4之间的特点,当我们后传导数较多次的时候,则有可能会导致梯度过小,无法有效地更新较接近输入端的参数,这就是所谓的“梯度消失”的问题。早期神经网络正是由于受到“梯度消失”的限制,所以无法将层数加深。

而后来由于使用了ReLU函数做为激活函数,神经网络得到了较大的发展。由于ReLU的导数属于[,1],故而能够比较有效地解决梯度消失的问题。正是由于ReLU函数的广泛使用,神经网络才得以往深的方向发展,真正开启了深度学习的热潮。目前,在NN或是CNN的领域,ReLU都是最为常用的激活函数。

但是,在RNN的领域,ReLU却并非是最合适的函数。相比之下,tanh是更加常用的函数。深度学习界的泰斗人物Hinton在其论文《A Simple Way to Initialize Recurrent Networks of Rectified Linear Units》中指出:

在上一节中,我们大致推出了RNN对于各项参数的公式。虽然我们只列出了前三项,但是我们不难推想出,在后面的项中将会出现多个W与f′连乘的部分。如果我们选用ReLU为激活函数,当我们的序列较长,而W的特征值大于1时,则有可能会使得求出的梯度过大。当梯度过大的时候,可能会导致训练十分不稳定,难以收敛到合适的位置,甚至不能真正使得训练损失有效下降。这正是与“梯度消失”完全相反的另一问题——“梯度爆炸”。

为了解决梯度爆炸的问题,我们需要激活函数具有一定的压缩性,例如Sigmoid或是tanh函数都具有这种性质。人们经历实践后发现选用tanh函数能够使得训练效率较高,网络的能力较好。所以,目前在RNN领域,tanh函数是最为常见的激活函数。

RNN在拟合一些较为简单的时间序列数据上表现出了强大的能力,逐渐超过了传统做法,成为了时间序列领域最佳的模型之一。随着人工智能在各个领域的应用普及,人们逐渐将目光转移到更加重要的一个领域——自然语言处理(NLP)。自然语言,即人们平时使用的语言,从本质上看也是一类时间序列数据。因为当我们说话的时候,有意义的各个词语必须要按照一定的语法顺序正确组织起来,才能具有意义。如果将同样的词语调换顺序,则语句很有可能变成完全不同的意思。如何将RNN的技术应用于自然语言处理成为了人们关注的重点。

例如,我们是否能够将刚才预测风力发电的模型(输入为风速,输出为风力发电量)改造成为一个实时机器翻译的模型,使得我们能不断按顺序地输入一个语句中的英文单词,而模型输出中文单词,使得这些中文单词能够连成翻译完成后的句子?

但是,人们发现在自然语言处理的领域中,RNN的表现不尽如人意。其效果甚至还不如一些传统模型,诸如我们前面介绍过的HMM之类的。这由于在RNN中,输入对于输出在时间上的影响是快速下降的。这正是由于使用tanh这样的激活函数所导致的后果。这也导致了某一个时刻的输入在经历了几次时间维度上的传递之后,效果会迅速衰减,以至于其可能无法影响到几个时刻以后的输出。

对于风力发电而言,某个时刻的发电量或许与一分钟之间的风速有重大关系,但是其与十分钟之间的风速可能关系不大。所以对于风力发电这样的数据而言,RNN的结构是合适的。但是对于自然语言这样的序列而言,各个输入可能对于输入有着长度不同的影响。其中有的输入可能未来的输出具有极其深远的影响。例如我们在文章开头申明了故事发生的时间是在三个月以前,这就意味着全篇所有动词要采用过去式等等。而RNN的结构对于这种输入的深远影响是无能无力的。

针对RNN的这个缺憾,人们发明了LSTM模型。

LSTM

LSTM的全称为Long Short Term Memory。由于其具有状态能够随着时间传递的性质,所以它本质上也属于RNN。不过,LSTM的结构与第二节中提到的RNN有较大的不同。所以当LSTM发明之后,人们一般用RNN特指代前面所提及的、结构较为简单朴素的RNN,以将其与结构较为复杂的LSTM分开。

在普通的RNN中,由于循环结构的设计,模型是具有一定的记忆的。但是另一方面,由于隐藏层是由公式st=f(Wst−1+Uxt)计算出来的,信息从st−1到st可能发生了翻天覆地的变化。这也就是说,模型顶多只具有“短期的记忆”。而我们希望发明一种更加稳健的记忆方式,即st−1到st大致上是不变的。输入的xt只能为其带来一定的微小变化。如果我们拥有这样的结构,则我们可以说模型具有了一种“长期的记忆”。

LSTM的主要思想是要为模型加入一个较为长期的记忆。只有拥有长期记忆,在面对自然语言处理这类问题的时候,它才有可能记得住现在的时间、地点、人称,以及语境,还有各个单词的组织顺序,继而才能翻译出合乎人类逻辑的句子。其设计思路如下:

1.增加遗忘机制。例如当一个场景结束是,模型应该重置场景的相关信息,例如位置、时间等。而一个角色死亡,模型也应该记住这一点。所以,我们希望模型学会一个独立的忘记/记忆机制,当有新的输入时,模型应该知道哪些信息应该丢掉。

2.增加保存机制。当模型得到新的输入的时候,需要学会其中是否有值得使用和保存的信息。即面对新的输入,模型首先忘掉哪些用不上的长期记忆信息,然后学习新输入有什么值得使用的信息,然后存入长期记忆中。

3.把长期记忆聚焦到工作记忆中。最后,模型需要学会长期记忆的哪些部分立即能派上用场。不要一直使用完整的长期记忆,而要知道哪些部分是重点。

LSTM的结构示意图如下:

在上图中,我们假定中央的细胞是C,而三个标记 σ 的圆圈表示“三重门”:输入门 i,输出门 o,遗忘门 f,均使用有压缩性的函数(比如Sigmoid或tanh)将取值范围限制到 (0, 1) 之内。计算公式如下,其中σ表示Sigmoid、g表示tanh、Φ表示softmax:

其中W表示权值矩阵(例如Wix表示从输入门到输入的权值矩阵,Wic表示窥连孔连接的对角权值矩阵)。 由于其计算公式十分复杂,所以我们不对其进行详细的一一解释了。并且可以预想到,如果要求给定训练集各个参数的梯度,那无疑会更加复杂,甚至比第二节中我们看到的RNN对各项参数的梯度公式还要复杂很多。所以,我们也不再列出其公式了,有兴趣的读者可以自己上网查阅有关资料。有关LSTM,我们最主要需要记住的就是,它含有长记忆,以及有关遗忘与增添记忆的机制。正是借助这种结构,它可以把序列中一些特殊元素对于序列的影响保持很久。

以下是LSTM的另一种形式的图例。它有助于我们更好地理解LSTM的工作方式:

总的来说,LSTM结构过于复杂。所以作为LSTM的简化,有人提出了GRU(Gated Recurrent Unit)。LSTM有三个门——遗忘门、输入门与输出门。而GRU只有两个门——更新门与重置门。所以,在同样能够长期保留输入的影响的前提下,GRU的结构比起LSTM要相对简单一些。其示意图如下:

GRU的输出、输出与隐藏状态的计算公式如下:

而Jos vander Westhuizen等人目前的工作又进一步指出,如果在LSTM的基础上进行修改,使得其只有一个门——遗忘门,则其结构又会变得比GRU更加简单,并且他们通过实验证明这种简化后的LSTM在许多数据集上表现出比普通的LSTM与GRU更好的结果。它们将这种新式的简化版LSTM称作JANET(Just Another NET)。 JANET的计算公式为:

总的来说,带有长记忆结构的RNN,例如LSTM、GRU、JANET等,目前是自然语言处理领域表现最佳的模型。由于其计算输出的前传公式与计算梯度的后传公式都极其复杂,所以本文不进行详细的介绍。感兴趣的同学可以查阅有关资料。

小结

在本文中,我们首先介绍了序列型的数据与其它普通数据的不同之处,介绍了其不能当做一般独立同分布回归问题用普通NN回归的原因。然后,我们介绍了能够利用序列的特性的RNN结构,并且举了一个简单的例子,以说明其从输入前传计算输出的机制,以及从输出端的误差后传求各个参数的梯度的方法。

接下来,我们介绍了使用RNN时候的一些注意事项(例如不适宜使用CNN领域最火的ReLU做为激活函数)以及其导致的RNN的局限性(输入对输出造成的影响不能持续太久)。为此,我们给出了LSTM结构的简要介绍。LSTM的特殊结构可以使其较长时间保留住一些特殊输入的影响,也正是由于这个特点,它才能在自然语言处理领域取得如此大的成功。

对于RNN以及LSTM,尤其是具有一定深度的结构而言,其前传与后传的表达式都是十分复杂的。所以在本文中我们省略了这个部分。在大多数深度学习框架中,一般也可以有封装好的RNN以及LSTM可以直接调用,不需要从头写起。对于希望能够更加深刻的理解模型的同学,可以查阅有关论文,并且对相关公式进行详细推导。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180601G061IH00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券