tensorflow-RNN和LSTM

本章主要介绍循环神经网络(recurrent neuralnetwork,RNN)和长短时记忆网络(long short-term memory,LSTM)

一. RNN简介

1.背景

循环神经网络挖掘数据中的时序信息以及语义信息的深度表达能力别充分利用,并在语音识别、语言模型、机器翻译以及时序分析等方面实现了突破。

循环神经网络的主要用途是处理和预测序列数据。在全连接神经网络或卷积神经网络中,网络结构都是从输入层到隐藏层再到输出层,层与层之间是全连接或部分连接的,但每层之间的节点是无连接的。考虑这样一个问题,如果要预测句子的下一个单词是什么,一般需要用到当前单词以及前面的单词,因为句子中前后单词并不是独立的。循环神经网络的来源就是为了刻画一个序列当前的输出与之前信息的关系。从网络结构上,循环神经网络会记忆之前的信息,并利用之前的信息影响后面节点的输出。也就是说,循环神经网络的隐藏层之间的节点是有连接的,隐藏层的输入不仅包括输入层的输出,还包括上一时刻隐藏层的输出。

2.RNN经典结构

如上所示为一个经典的循环神经网络结构。可以看到在每一时刻t,循环神经网络会针对该时刻的输入结合当前模型的状态给出一个输出,并更新模型状态。此结构A的输入除了来自输入层xt,还有一个循环的边来提供上一时刻的隐藏状态(hidden state)ht-1。在每一个时刻,循环神经网络的模块A在读取了xt和ht-1之后会生成新的隐藏状态ht,并产生本时刻的输出ot(在很多模型中隐藏状态ht也被直接用于输出,这类模型可以看作ot == ht的特性。一些资料直接用ht同时代表这两个输出),由于模型A中的运算和变量在不同时刻是相同的,因此循环神经网络理论上可以被看作是同一神经网络结构被无限复制的结果。正如卷积神经网络在不同的空间位置共享参数,循环神经网络是在不同时间位置共享参数,从而能够使用有限的参数处理任意长度的序列。

3.展开输入输出序列

3.1 将完整的输入输出展开,可以得到上面的结构,可以更加清楚地看到循环神经网络在每一个时刻会有一个输入xt,然后根据循环神经网络前一时刻的状态ht-1计算新的状态ht,并输出ot。循环神经网络当前的状态ht是根据上一时刻的状态ht-1和当前的输入xt共同决定的。在时刻t,状态ht-1浓缩了前面序列x0,x1,...,xt-1的信息,用于作为输出ot的参考。由于序列的长度可以无限延长,维度有限的h状态不可能将序列的全部信息都保存下来,因此模型必须学习只保留与后面任务ot,ot+1,...相关的最重要的信息。

3.2 循环网络的展开在模型训练中有重要意义。从上图可以看出,循环神经网络对长度为N的序列展开之后,可以视为一个有N个中间层的前馈神经网络。这个前馈神经网络没有循环链接,因此可以直接使用反向传播算法进行训练,而不需要任何特别的优化算法。这样的训练方法称为“沿时间反向传播”(Back-Propagation Through Time),是训练循环神经网络最常见的方法。

3.3 从上图可以看出循环神经网络的结构特征最擅长解决与时间时序相关的问题。循环神经网络也是处理这类问题时最自然的神经网络结构。对于一个序列数据,可以将这个序列上不同时刻的数据依次传入循环神经网络的输入层,而输出可以是对序列中下一个时刻的预测,也可以是对当前时刻信息的处理结果(比如语音识别结果)。循环神经网络要求每一个时刻都有一个输入。但是不一定每一个时刻都需要有输出。循环神经网络被广泛地应用在语音识别、语言模型、机器翻译以及时序分析等问题上,并取得了巨大的成功。

4. 机器翻译案例

循环神经网络中每一个时刻的输入为需要翻译的句子中的单词,如下图所示,需要翻译的句子为ABCD,那么循环神经网络第一段每一个时刻的输入就分别是A、B、C和D,然后用"_"作为待翻译句子的结束符(句子结束符可以用符号或字符串表示,只要文本正文中没有出现过即可)。在第一段中,循环神经网络没有输出。从结束符”_“开始,循环神经网络进入翻译阶段。该阶段中每一个时刻的输入是上一个时刻的输出,而最终得到的输出就是句子ABCD翻译的结果,当网络输出"_"时翻译结束。

5.循环体介绍

如之前所介绍,循环神经网络可以看作是同一神经网络结构在时间序列上被复制多次的结果,这个被复制多次的结构被称之为循环体。如何设计循环体的网络结构是循环神经网络解决实际问题的关键。下面展示了一个最简单的循环体结构。这个循环体中只使用了一个类似全连接层的神经网络结构。

循环神经网络中的状态是通过一个向量来表示的,这个向量的维度也称为循环神经网络隐藏层的大小,假设其为n,上图可以看出,循环体中的神经网络的输入有两部分,一部分为上一时刻的状态,另一部分为当前时刻的输入样本。对于时间序列数据来说(比如不同时刻商品的销量),每一时刻的输入样例可以是当前时刻的数值(比如销量值);对于语言模型来说,输入样例可以是当前单词对应的单词向量。

假设输入向量的维度为x,隐藏状态的维度为n,那么上图中循环体的全连接层神经网络的输入大小为n+x。也就是将上一时刻的状态与当前时刻的输入拼接成一个大的向量作为循环体中神经网络的输入(有时也将会将上一时刻状态对应的权重和当前时刻输入对应的权重特意分开,但它们的实质是一样的。本节采用了向量拼接的方式)。因为该全连接层的输出为当前时刻的状态,于是输出层的节点个数也为n,循环体中的参数个数为(n+x)xn+n。上图可以看出,循环体中的神经网络输出不但提供给了下一时刻作为状态,同时也会提供给当前时刻的输出。注意到循环体状态与最终输出的维度通常不同,因此为了将当前时刻的状态转化为最终的输出,循环神经网络还需要另外一个全连接神经网络来完成这个过程。这和卷积神经网络中最后的全连接层的意思是一样的。类似的,不同时刻用于输出的全连接神经网络中的参数也是一致的。

6. 循环神经网络的前向传播过程

如下图所示,假设状态的维度为2,输入、输出的维度都为1,而且循环体中的全连接层权重为:wrnn=[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]],偏置项的大小为brnn=[0.1, -0.1]。用于输出的全连接层权重为:woutput=[1.0, 2.0],偏置项大小为boutput=0.1.那么在时刻t0,因为没有上一时刻,所以将状态初始化为hinit=[0, 0],而当前的输入为1,所以拼接得到的向量为[0, 0, 1],通过循环体中的全连接层神经网络得到的结果为:

这个结果将作为下一时刻的输入状态,同时循环神经网络也会使用该状态生成输出。将该向量作为输入提供给用于输出的全连接神经网络可以得到t0时刻的最终输出:[0.537, 0.462]x[1.0, 2.0] + 0.1 = 1.56,使用t0时刻的状态可以类似地推导得出t1时刻的状态为[0.860, 0.884],而t1时刻的输出为2.73。在得到循环神经网络的前向传播结果之后,可以和其他神经网络类似地定义损失函数,循环神经网络唯一的区别在于因为它每个时刻都有一个输出,所以循环神经网络的总损失为所有时刻(或者部分时刻)上的损失函数的总和。

此前向传播代码练习参考:https://github.com/pythonAndAI/tensorflow-exercise/blob/master/com/tensorflow/exercise/RNN/RNN_basis.py

和其他神经网络类似,在定义完损失函数之后,套用前面说的优化框架(指数衰减法、滑动平均模型、优化器、正则化等)Tensorflow就可以自动完成模型训练的过程。这里唯一需要特别指出的是,理论上循环神经网络可以支持任意长度的序列,然而在实际训练过程中,,如果序列过长,一方面会导致优化时出现梯度消散和梯度爆炸的问题,另一方面,展开后的前馈神经网络会占用过大的内存,所以实际中一般会规定一个最大长度,当序列长度超过规定长度之后会对序列进行截断。

二、长短时记忆网络(LSTM)结构

1. LSTM结构详情

在有些问题中,模型仅仅需要短期内的信息来执行当前的任务,在这样的场景中,相关的信息和待预测的词的位置之间的间隔很小,循环神经网络可以比较容易地利用先前的信息。这类问题可以用前面介绍的简单循环体来解决。

但同样也有一些上下文场景更加复杂的情况。比如一个模型仅仅根据短期的信息不能正确的预测正确的结果。这样前面介绍的简单循环神经网络有可能会丧失学习到距离如此远的信息的能力。或者在复杂语言场景中,有用信息的间隔有大有小、长短不一,循环神经网络的性能也会受到限制。

长短时记忆网络(long short-term memory,LSTM)的设计就是为了解决这个问题,在很多任务上,采用LSTM结构的循环神经网络比标准的循环神经网络表现更好。LSTM结构是由Sepp Hochreiter和Jurgen Schmidhuber于1997年提出的,它是一种特殊的循环体结构。如下图所示,与单一tanh循环体结构不同,LSTM是一种拥有三个“门”结构的特殊网络结构。

LSTM靠一些“门”的结构让信息有选择性地影响循环神经网络中每个时刻的状态。所谓“门”的结构就是一个使用sigmoid神经网络和一个按位做乘法的操作,这两个操作合在一起就是一个“门”的结构。之所以该结构叫做“门”是因为使用sigmoid作为激活函数的全连接神经网络层会输出一个0到1之间的数值,描述当前输入有多少信息量可以通过这个结构。于是这个结构的功能就类似于一扇门,当门打开时(sigmoid神经网络层输出为1时),全部信息都可以通过;当,而门关上时(sigmoid神经网络层输出为0时),任何信息都无法通过。

为了使循环神经网络更有效的保存长期记忆,上图中“遗忘门”和“输入门”至关重要,它们是LSTM结构的核心。“遗忘门”的作用是让循环神经网络“忘记”之前没有用的信息。比如一段文章中先介绍了某地原来是绿水蓝天,但后来被污染了。于是在看到被污染了之后,循环神经网络应该“忘记”之前绿水蓝天的状态。这个工作是通过“遗忘门”来完成的。“遗忘门”会根据当前的输入xt和上一时刻输出ht-1决定那一部分记忆需要被遗忘。假设状态c的维度为n,“遗忘门”会根据当前的输入xt和上一时刻输出ht-1计算一个维度为n的向量f=sigmoid(W1x + W2h),它在每一维度上的值都在(0,1)范围内。再将上一时刻的状态ct-1与f向量按位相乘,那么f取值接近0的维度上的信息就会被“忘记”,而f取值接近1的维度上的信息会被保留。

在循环神经网络“忘记”了部分之前的状态后,它还需要从当前的输入补充最新的记忆。这个过程就是“输入门”完成的。如上图所示,“输入门”会根据xt和ht-1决定哪些信息加入到转态ct-1中生成新的状态ct。比如当看到文章中提到环境被污染之后,模型需要将这个信息写入新的状态。这时“输入门”和需要写入的新状态都从xt和ht-1计算产生。通过“遗忘门”和“输入门”,LSTM结构可以更加有效地决定哪些信息应该被遗忘,哪些信息应该得到保留。

LSTM结构在计算得到新的状态ct后需要产生当前时刻的输出,这个过程是通过“输出门”完成的。“输出门”会根据最新的状态ct、上一时刻的输出ht-1和当前的输入xt来决定该时刻的输出ht。比如当前的状态为被污染,那么“天空的颜色”后面的单词很可能就是“灰色的”。

2. LSTM的前向传播

相比1.6介绍的循环神经网络结构,使用LSTM结构的循环神经网络的前向传播是一个相对比较复杂的过程。具体LSTM每个“门”的公式定义如下:

z = tanh(Wz[ht-1,xt])    (输入值)        i = sigmoid(Wi[ht-1,xt])        (输入门)

f = sigmoid((Wf[ht-1,xt])) (遗忘门)  o = sigmoid(Wo[ht-1,xt])    (输出门)

ct = f * ct-1 + i * z   (新状态)    ht = o * tanh ct   (输出)

其中Wz、Wi、Wf、Wo是4个维度为[2n, n]的参数矩阵。下面用流程图的形式表示了上面的公式。

输入:Ct-1、ht-1、Xt,输出:Ct、ht

用tensorflow定义一个LSTM结构:https://github.com/pythonAndAI/tensorflow-exercise/blob/master/com/tensorflow/exercise/RNN/LSTM_test.py

三、循环神经网络的变种

1. 双向循环神经网络

在经典的循环神经网络中,状态的传输是从前往后单向的。然而,在有些问题中,当前时刻的输出不仅和之前的状态有关,也和之后的状态相关。这时就需要使用双向循环神经网络(bidirectional RNN)来解决这类问题。例如预测一个句子中缺失的单词不仅需要根据前文来判断,也需要根据后面的内容,这时双向循环神经网络就可以发挥它的作用。双向循环神经网络是由两个独立的循环神经网络叠加到一起组成的。输出由这两个循环神经网络的输出拼接而成。如下图所示结构。

从上图可以看出,双向循环神经网络的主体结构就是两个单向循环神经网络的结合。在每一个时刻t,输入会同时提供给这两个方向相反的循环神经网络。两个网络独立进行计算,各自产生该时刻的新状态和输出。而双向循环神经网络的最终输出是这两个单向循环神经网络的输出的简单拼装。两个循环神经网络除方向不同以外,其余结构完全对称。每一层网络中的循环体可以自由选用任意结构,如前面说的简单RNN、LSTM均作为双向循环神经网络的循环体。双向循环神经网络的前向传播过程和单向的循环神经网络十分类似。

2. 深层循环神经网络

深层循环神经网络(Deep RNN)是循环神经网络的另外一种变种。为了增强模型的表达能力,可以在网路中设置多个循环层,将每层循环网络的输出传给下一层进行处理。在前面描述的单层循环网络中,每一时刻的输入xt到输出ot之间只有一个全连接层,因此在xt到ot的路径上是一个很浅的神经网络,从输入中提取抽象信息的能力将受到限制。下图给出的深层循环神经网络的结构。可以看出,在一个L层的深层循环网络中,每一个时刻的输入xt到输出ot之间有L个循环体,网络因此可以从输入中抽取更加高层的信息。和CNN类似,每一层(每个时刻的相同层,比如h1init是0时刻到t时刻的第一层)的循环体中参数是一致的,而不同层中的参数可以不同。Tensorflow中提供了MultiRNNCell类来实现深层循环神经网络的前向传播过程。

用Tensorflow实现一个Deep RNN前向传播过程:https://github.com/pythonAndAI/tensorflow-exercise/blob/master/com/tensorflow/exercise/RNN/RNN_Deep.py

四、循环神经网络的dropout

在CNN时有使用过dropout方法。通过dropout方法,可以让卷积神经网络更加健壮。类似的,在循环神经网络中使用dropout也有同样的功能。而且,类似CNN只在最后的全连接层中使用dropout,RNN一般只在不同层循环体结构之间使用dropout,而不再同一层的循环体结构之间使用。如下图,也就是说从时刻t-1传递到时刻t时,RNN不会进行状态的dropout;而在同一时刻t中,不同层循环体之间会使用dropout(下图实线箭头表示不使用dropout,虚线箭头表示使用dropout)。

如上图所示。假设要从t-2时刻的输入xt-2传递到t+1时刻的输入yt+1,那么xt-2将首先传入第一层循环体结构,这个过程会使用dropout。但是从t-2时刻的第一层循环体结构传递到第一层的t-1、t、t+1时刻不会使用dropout。在t+1时刻的第一层循环体结构传递到传递到同一时刻内更高层的循环体结构时,会再次使用dropout,tensorflow中可以用tf.nn.rnn_cell.DropoutWrapper类实现dropout功能。可以参考此地址练习:https://github.com/pythonAndAI/tensorflow-exercise/blob/master/com/tensorflow/exercise/RNN/RNN_dropout.py

 五、RNN样例应用

前面已经介绍了不同循环神经网络的网络结构,并给出了具体的Tensorflow程序来实现这些循环神经网络的前向传播过程。将以时序预测为例,利用循环神经网络实现对函数sinx取值的预测。下面给出了sin函数的函数图像。

下面将用Tensorflow程序来实现预测sin函数。因为循环神经网络模型预测的是离散时刻的取值,所以在程序中需要将连续的sin函数曲线离散化。所谓离散化就是在一个给定的区间[0, MAX]内,通过有限个采样点模拟一个连续的曲线。比如在以下练习中每个SAMPLE_ITERVAL对sin函数进行一次采样,采样得到的序列就是sin函数离散化之后的结果。练习代码地址为:https://github.com/pythonAndAI/tensorflow-exercise/blob/master/com/tensorflow/exercise/RNN/RNN_sin_case.py

猜你喜欢

转载自www.cnblogs.com/bestExpert/p/10633794.html