循环神经网络基础介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lilong117194/article/details/82958326

在应用循环神经网络的过程中,还是会有些地方疑惑,所以还是要回归下问题的本质。
学而不思则惘,思而不学则怠。。

1. 循环神经网路简介

首先循环神经网络的主要用途是处理和预测序列数据。在之前的全链接神经网络或卷积神经网络模型中,网络的结构都是从输入层到隐藏层再到输出层,层与层之间是全链接或者部分连接的,但每层之间的节点是无法连接的。而循环神经网络的隐藏层之间的节点是有连接的,隐藏层的输入不仅包括输入层的输出,还包括上一时刻隐藏层的输出。
在这里插入图片描述
图1.1 RNN-rolled

上图是一个典型的循环神经网络。对于循环神经网络,一个非常重要的概念就是时刻。循环神经网络会对于每一个时刻的输入结合当前模型的状态给出一个输出。从图中可以看到,循环神经网络的主体结构A的输入除了来自输入层Xt,还有一个循环的边来提供当前时刻的状态。在每一个时刻,循环神经网络的模块A会读取t时刻的输入Xt,并输出一个值Ht。同时A的状态会从当前步传递到下一步。
因此,循环神经网络理论上可以被看作是同一神经网络结构被无限复制的结果。但出于优化的考虑,目前循环神经网络无法做到真正的无限循环,所以,现实中一般会将循环体展开,于是可以得到下图所示的展示结构。
在这里插入图片描述
图1.2 按时间展开后的RNN结构

在上图中可以更加清楚的看到循环神经网络在每一个时刻会有一个输入 X t X_t ,然后根据循环神经网络当前的状态 A t A_t ,提供一个输出 h t h_t 。而循环神经网络的当前状态 A t A_t 是根据上一时刻的的状态 A t 1 A_{t-1} 和当前的输入 X t X_t 共同决定的。从循环神经网络的结构特征可以很容易得出它最擅长解决的问题是与时间序列相关的。循环神经网络也是处理这类问题时最自然的神经网络结构。对于一个序列数据,可以将这个序列上不同时刻的数据依次传入循环神经网络的输入层,而输出可以是对序列中下一个时刻的预测,也可以是对当前时刻信息的处理结果(比如语音识别结果)。循环神经网络要求每一个时刻都有一个输入,但是不一定每一个时刻都需要有输出。

下面是RNN的应用场景:
RNN相对于传统的神经网络,它允许我们对向量序列进行操作:输入序列、输出序列、或大部分的输入输出序列。如下图所示,每一个矩形是一个向量,箭头则表示函数(比如矩阵相乘)。输入向量用红色标出,输出向量用蓝色标出,绿色的矩形是RNN的状态。
在这里插入图片描述
图1.3 RNN的模式分类

从左到右:
(1)没有使用RNN的Vanilla模型,从固定大小的输入得到固定大小输出(比如图像分类)
(2)序列输出(比如图片字幕,输入一张图片输出一段文字序列)
(3)序列输入(比如情感分析,输入一段文字然后将它分类成积极或者消极情感)
(4)序列输入和序列输出(比如机器翻译:一个RNN读取一条英文语句然后将它以法语形式输出)
(5)同步序列输入输出(比如视频分类,对视频中每一帧打标签)。我们注意到在每一个案例中,都没有对序列长度进行预先特定约束,因为递归变换(绿色部分)是固定的,而且我们可以多次使用。

举例子:
在这里插入图片描述
图1.4 循环神经网络实现序列预测

以机器翻译为例来简单介绍循环神经网络是如何使用的,循环神经网络的每一个时刻的输入为需要翻译的句子中的单词,如上所示,需要翻译的句子为ABCD,那么循环神经网络的第一段中的每一时刻的输入数据分别是A、B、C、和D。然后用“_”作为翻译的结束符号。在第一个阶段中,循环神经网络中没有输出(句子还没有读完,不能进行翻译),只有每一个时刻的输入,而从结束符号开始有输出,也就是进入了翻译阶段。在翻译阶段每一个时刻的输入是上一个时刻的输出,而最终的的输出XYZ就是ABCD的翻译结果,而Q是代表翻译结束的标志。
在上述实现的过程中可以看作同一个循环体在时间序列上被复制多次的结果,而这个循环体的设计网络结构的是问题的关键。和卷积神经网络结构中过滤器的参数共享类似,在循环神经网络中的循环体的参数在不同时刻也是共享的。

2. 简单的单层全连接神经网络作为循环体的例子

在这里插入图片描述
图2.1 使用单层全连接神经网络作为循环体的循环神经网络结构图

上图展示了一个使用最简单的循环体结构的循环神经网络,在这个循环体中只使用了一个类似全连接层的神经网络结构。下面将通过上图中所展示的神经网络来介绍循环神经网络前向传播的完整流程。
循环神经网络中的状态是通过一个向量来表示的,这个向量的维度也称为神经网络隐藏层的大小,假设其为h。
从上图种可以看出,循环体中的神经网络的输入有两部分,一部分为上一时刻的状态,另一部分为当前时刻的输入样本。对于时间序列数据来说,每一时刻的输入样例可以是当前时刻的数据;对于语言模型来说,输入样例可以是当前单词对应的单词向量(word embedding)。
假设输入向量的维度为x,那么上图中循环体的全连接层神经网络的输入大小为h+x。也就是将上一时刻的状态与当前时刻的输入拼接成一个大的向量作为循环体中神经网络的输入。因为该神经网络的输出为当前时刻的状态,于是输出层的节点个数也为h(节点个数就是向量的维度,或者是隐藏层的大小),循环体中的参数个数为 ( h + x ) h + h (h+x)*h+h 个(这里可以理解为输入层有h+x个神经元,输出层有h个神经元,从而形成一个全连接的前馈神经网络,有(h+x)*h个权值,有h个偏置)。
同时循环体的神经网络输出不但提供给下一个时刻作为状态,同时还提供给当前的时刻作为输出。为了将当前时刻的状态转化为最终的输出,循环体还需要另外一个全连接神经网络来完成这个过程。这和卷积神经网络中最后的全连接层的意义是一样的。类似的,不同时刻用于输出的全连接神经网络中的参数也是共享的(参数一致)。

下面是一个具体的例子,当然了数值都是假设的,意在理解原理:
在这里插入图片描述
图2.2 RNN的前向传播的计算过程

如图2所示,假设节点状态的维度为2,节点的输入和输出维度为1,那么在循环体的全连接层神经网络的输入维度为3,也就是将上一时刻的状态与当前时刻的输入拼接成一维向量作为循环体的全连接层神经网络的输入,在这里t0时刻的节点状态初始化为[0.0, 0.0]t0时刻的节点输入为[1.0],拼接之后循环体的全连接层神经网络的输入为[0.0, 0.0, 1.0],循环体中的全连接层的权重表示为二维矩阵[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]],偏置项为[0.1, -0.1],我们可以看到权重矩阵和偏置项在t0t1时刻的循环体中是一样的,这也说明了RNN结构中的参数在不同时刻中也是共享的。经过循环体中的全连接层神经网络后节点的状态改变为tanh([0.6, 0.5]) = [0.537, 0.462],当前节点状态的输出作为下一个节点状态的输入。
为了将当前时刻的状态转变为节点的最终输出,RNN中还有另外一个全连接神经网络来计算节点输出,在图2中被表示为[0.537, 0.462] * [1.0, 2.0] + [0.1] = [1.56],用于输出的全连接层权重为[1.0, 2.0],偏置项为[0.1]1.56表示为t0时刻节点的最终输出。
得到RNN的前向传播结果之后,和其他神经网络类似,定义损失函数,使用反向传播算法和梯度下降算法训练模型,但RNN唯一的区别在于:由于它每个时刻的节点都有一个输出,所以RNN的总损失为所有时刻(或部分时刻)上的损失和。

下面是自己假设数据的代码实现:

import numpy as np

"""
定义RNN的参数
"""

X = [1,2]    # 输入序列
state = [0.0, 0.0]  # 初始状态下的状态
# 状态层的权重和输入层的权重分开定义,方便操作
w_cell_state = np.asarray([[0.1, 0.2], [0.3, 0.4]]) # 定义状态层权重
w_cell_input = np.asarray([0.5, 0.6])  # 定义输入层的权重
b_cell = np.asarray([0.1, -0.1])  # 状态层的偏置
w_output = np.asarray([[1.0], [2.0]]) # 输出层的权重
b_output = 0.1 # 输出层的偏置

"""
执行前向传播过程
"""

for i in range(len(X)):
    before_activation = np.dot(state, w_cell_state) + X[i] * w_cell_input + b_cell
    state = np.tanh(before_activation)
    final_output = np.dot(state, w_output) + b_output
    print ("before activation: ", before_activation)
    print ("state: ", state)
    print ("output: ", final_output)

运行结果:

before activation:  [0.6 0.5]
state:  [0.53704957 0.46211716]
output:  [1.56128388]
before activation:  [1.2923401  1.39225678]
state:  [0.85973818 0.88366641]
output:  [2.72707101]

3. 长短时记忆网络(LSTM)结构

循环神经网络工作的关键点就是使用历史的信息来帮助当前的决策。例如使用之前出现的单词来加强对当前文字的理解。循环神经网络可以更好地利用传统神经网络结构所不能建模的信息,但同时,这也带来了更大的技术挑战——长期依赖(long-term dependencies)问题。
因此,当前预测位置和相关信息之间的文本间隔就有可能变得很大。当这个间隔不断增大时,类似图2.1中给出的简单循环神经网络有可能丧失学习到距离如此远的信息的能力。或者在复杂语言场景中,有用信息的间隔有大有小、长短不一,循环神经网络的性能也会受到限制。

3.1 LSTM简介

长短记忆网络(long short term memory, LSTM)的设计正是为了解决上述RNN的依赖问题,即为了解决RNN有时依赖的间隔短,有时依赖的间隔长的问题。其中循环神经网络被成功应用的关键就是LSTM。
在这里插入图片描述
图3.1 LSTM单元结构图

它是一种特殊的循环体结构,与单一的tanh循环体结构不同,LSTM是一种拥有三个“门”结构的特殊网络结构。
所谓的“门”结构就是使用了sigmoid激活函数的全连接神经网络和一个按做乘法的操作,sigmoid激活函数会输出一个0~1之间的数值,这个数值描述的是当前有多少信息能通过“门”0表示任何信息都无法通过,1表示全部信息都可以通过。其中,“遗忘门”和“输入门”是LSTM单元结构的核心。下面我们来详细分析下三种“门”结构。
遗忘门:用来让RNN“忘记”之前没有用的信息。比如“十年前,北京的天空是蓝色的”,但当看到“空气污染开始变得越来越严重”后,RNN应该忘记“北京的天空是蓝色的”这个信息。遗忘门会根据当前时刻节点的输入Xt、上一时刻节点的状态C(t-1)和上一时刻节点的输出h(t-1)来决定哪些信息将被遗忘。

输入门:用来让RNN决定当前输入数据中哪些信息将被留下来。在RNN使用遗忘门“忘记”部分之前的信息后,还需要从当前的输入补充最新的记忆。输入门会根据当前时刻节点的输入Xt、上一时刻节点的状态C(t-1)和上一时刻节点的输出h(t-1)来决定哪些信息将进入当前时刻节点的状态Ct,比如看到“空气污染开始变得越来越严重”后,模型需要记忆这个最新的信息。

输出门:LSTM在得到最新节点状态Ct后,结合上一时刻节点的输出h(t-1)和当前时刻节点的输入Xt来决定当前时刻节点的输出。比如当前时刻节点状态为被污染,那么“天空的颜色”后面的单词应该是“灰色”。
在TensorFlow中可以使用lstm = rnn_cell.BasicLSTMCell(lstm_hidden_size)来声明一个LSTM结构。

3.2 LSTM详解

具体的LSTM每个门的公式如下:
在这里插入图片描述
图3.2 LSTM单元详细结构

所有循环神经网络都有一个重复结构的模型形式,在标准的RNN中,重复的结构是一个简单的循环体,如图3.2所示的A循环体。然而LSTM的循环体是一个拥有四个相互关联的全连接前馈神经网络的复制结构。

符号介绍:
在这里插入图片描述
图3.3 符号说明

  • Neural NetWork Layer:该图表示一个神经网络层(自己可以定义大小)
  • Pointwise Operation:该图表示一种操作,如加号表示矩阵或向量的求和、乘号表示向量的乘法操作;
  • Vector Tansfer:每一条线表示一个向量,从一个节点输出到另一个节点;
  • Concatenate:该图表示两个向量的合并,即由两个向量合并为一个向量,如有X1和X2两向量合并后为[X1,X2]向量;
  • Copy:该图表示一个向量复制了两个向量,其中两个向量值相同。

门设计:

遗忘门:LSTM的第一步是决定要从上一个时刻的状态中丢弃什么信息,其是由一个sigmoid全连接的前馈神经网络的输出管理,将这种操作称为遗忘门(forget get layer)。如图3.4所示。这个全连接的前馈神经网络的输入是 h t 1 h_{t-1} X t X_t 组成的向量,输出是 f t f_t 向量。 f t f_t 向量是由1和0组成,1表示能够通过,0表示不能通过。
在这里插入图片描述
图3.4 遗忘门

输入门:第二步决定哪些输入信息要保存到神经元的状态中。这由两个前馈神经网络,如图3.5所示。首先是一个sigmoid层的全连接前馈神经网络,称为输入门(input gate layer),其决定了哪些值将被更新;然后是一个tanh层的全连接前馈神经网络,其输出是一个向量 C t ~ \tilde{C_t} C t ~ \tilde{C_t} 向量可以被添加到当前时刻的神经元状态中;最后根据两个神经网络的结果创建一个新的神经元状态。
在这里插入图片描述
图3.5 输入门

状态控制:第三步就可以更新上一时刻的状态 C t 1 C_{t-1} 为当前时刻的状态 C t C_t 了。上述的第一步的遗忘门计算了一个控制向量,此时可通过这个向量过滤了一部分 C t 1 C_{t-1} 状态,如图3.6所示的乘法操作;上述第二步的输入门根据输入向量计算了新状态,此时可以通过这个新状态和 C t 1 C_{t-1}状态根据一个新的状态 C_t$。
在这里插入图片描述
图3.6 状态控制

输出门:最后一步就是决定神经元的输出向量 h t h_t 是什么,此时的输出是根据上述第三步的 C t C_t 状态进行计算的,即根据一个sigmoid层的全连接前馈神经网络过滤到一部分 C t C_t 状态作为当前时刻神经元的输出,如图3.7所示。这个计算过程是:首先通过sigmoid层生成一个过滤向量;然后通过一个tanh函数计算当前时刻的 C t C_t 状态向量(即将向量每个值的范围变换到[-1,1]之间);接着通过sigmoid层的输出向量过滤tanh函数结果,即为当前时刻神经元的输出。
在这里插入图片描述
图3.7 输出门

LSTM有很多种变体:
Peephole connections:一种流行的LSTM变体是由Gers&Schmidhuber(2000)提出的网络结构,如图 29所示。通过将上一时刻的状态Ct-1合并到各个门上,从而更详细控制各个门的管理。
在这里插入图片描述

Coupled forget and input gates:另一种变体是使用耦合的遗忘门和输入门
在这里插入图片描述

Gated Recurrent Unit:另一种变体是Gated Recurrrent Unit
在这里插入图片描述

3.3 循环神经网络的变体

双向循环神经网络(BRNN):RNN和LSTM都只能依据之前时刻的时序信息来预测下一时刻的输出,但在有些问题中,当前时刻的输出不仅和之前的状态有关,还可能和未来的状态有关系。比如预测一句话中缺失的单词不仅需要根据前文来判断,还需要考虑它后面的内容,真正做到基于上下文判断。BRNN(bidirectional
RNN)有两个RNN上下叠加在一起组成的,输出由这两个RNN的状态共同决定。BRNN结构图如图3.8所示:
在这里插入图片描述
图3.8 BRNN结构图

从上图可以看出,双向循环神经网络就是两个单向循环神经网络的结合。在每一个时刻t,输入会同时提供给这两个方向相反的循环神经网络,而输出则是由这两个单向循环神经网路共同决定的。

深层循环神经网络(DRNN:DRNN可以增强模型的表达能力,相比图3.2介绍的循环神经网络,深层循环神经网络主要是将每个时刻上的循环体重复多次,每一层循环体中参数是共享的,但不同层之间的参数可以不同。DRNN结构如图3.9所示:

在这里插入图片描述
图3.9 DRNN结构图

TensorFlow中可以通过rnn_cell.MultiRNNCell([lstm] * number_of_layer)来构建DRNN,其中number_of_layer表示了有多少层。

3.4 循环神经网络的fropout

在构建实际的任务模型时,往往会设置dropout来让构建的网络模型更加健壮,类似在卷积神经网络只在最后全连接层使用dropout,DRNN一般只在不同层循环体结构中使用dropout,而不在同一层的循环体结构中使用。即从时刻t-1传递到t时刻时,RNN不进行状态的dropout,但在同一时刻t中,不同层循环体之间会使用dropout,图3.10展示了DRNN中使用dropout,其中实线箭头表示不使用dropout,虚线箭头表示使用dropout。
在这里插入图片描述
图3.10 DRNN中使用dropout

TensorFlow中可以使用tf.nn.rnn_cell.DropoutWrapper类来实现dropout功能。

在实际编程中,可以通过TensorFlow的高层封装工具TFLearn来自定义模型,TFLearn封装了一些常用的神经网络模型,这里不再介绍。

参考:https://www.cnblogs.com/huliangwen/p/7464813.html

猜你喜欢

转载自blog.csdn.net/lilong117194/article/details/82958326