1. 8.1 循环神经网络简介 (1)

循环神经网络(recurrent neural network,RNN)源自于1982年由Saratha Sathasivam提出的霍普菲尔德网络 (2) 。霍普菲尔德网络因为实现困难,在其提出时并且没有被合适地应用。该网络结构也于1986年后被全连接神经网络以及一些传统的机器学习算法所取代。然而,传统的机器学习算法非常依赖于人工提取的特征,使得基于传统机器学习的图像识别、语音识别以及自然语言处理等问题存在特征提取的瓶颈。而基于全连接神经网络的方法也存在参数太多、无法利用数据中时间序列信息等问题。随着更加有效的循环神经网络结构被不断提出,循环神经网络挖掘数据中的时序信息以及语义信息的深度表达能力被充分利用,并在语音识别、语言模型、机器翻译以及时序分析等方面实现了突破。

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

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

图8-1 循环神经网络经典结构示意图 (3)

在图8-2中可以更加清楚的看到循环神经网络在每一个时刻会有一个输入X t ,然后根据循环神经网络当前的状态A t 提供一个输出h t 。而循环神经网络当前的状态A t 是根据上一时刻的状态A t -1 和当前的输入X t 共同决定的。从循环神经网络的结构特征可以很容易得出它最擅长解决的问题是与时间序列相关的。循环神经网络也是处理这类问题时最自然的神经网络结构。对于一个序列数据,可以将这个序列上不同时刻的数据依次传入循环神经网络的输入层,而输出可以是对序列中下一个时刻的预测,也可以是对当前时刻信息的处理结果(比如语音识别结果)。循环神经网络要求每一个时刻都有一个输入,但是不一定每个时刻都需要有输出。在过去几年中,循环神经网络已经被广泛地应用在语音识别、语言模型、机器翻译以及时序分析等问题上,并取得了巨大的成功。

图8-2 循环神经网络按时间展开后的结构

以机器翻译为例来介绍循环神经网络是如何解决实际问题的。循环神经网络中每一个时刻的输入为需要翻译的句子中的单词。如图8-3所示,需要翻译的句子为ABCD,那么循环神经网络第一段每一个时刻的输入就分别是A、B、C和D,然后用“”作为待翻译句子的结束符。在第一段中,循环神经网络没有输出。从结束符“”开始,循环神经网络进入翻译阶段。该阶段中每一个时刻的输入是上一个时刻的输出,而最终得到的输出就是句子ABCD翻译的结果。从图8-3中可以看到句子ABCD对应的翻译结果就是XYZ,而Q是代表翻译结束的结束符。

图8-3 循环神经网络实现序列预测示意图

如之前所介绍,循环神经网络可以被看做是同一神经网络结构在时间序列上被复制多次的结果,这个被复制多次的结构被称之为循环体。如何设计循环体的网络结构是循环神经网络解决实际问题的关键。和卷积神经网络过滤器中参数是共享的类似,在循环神经网络中,循环体网络结构中的参数在不同时刻也是共享的。

图8-4展示了一个使用最简单的循环体结构的循环神经网络,在这个循环体中只使用了一个类似全连接层的神经网络结构。下面将通过图8-4中所展示的神经网络来介绍循环神经网络前向传播的完整流程。循环神经网络中的状态是通过一个向量来表示的,这个向量的维度也称为循环神经网络隐藏层的大小,假设其为h 。从图8-4中可以看出,循环体中的神经网络的输入有两部分,一部分为上一时刻的状态,另一部分为当前时刻的输入样本。对于时间序列数据来说(比如不同时刻商品的销量),每一时刻的输入样例可以是当前时刻的数值(比如销量值);对于语言模型来说,输入样例可以是当前单词对应的单词向量(word embedding) (4) (5)

图8-4 使用单层全连接神经网络作为循环体的循环神经网络结构图 (6)

假设输入向量的维度为x,那么图8-4中循环体的全连接层神经网络的输入大小为h+x 。也就是将上一时刻的状态与当前时刻的输入拼接成一个大的向量作为循环体中神经网络的输入 (7) 。因为该神经网络的输出为当前时刻的状态,于是输出层的节点个数也为h ,循环体中的参数个数为(h+x)×h+h 个。从图8-4中可以看到,循环体中的神经网络输出不但提供给了下一时刻作为状态,同时也会提供给当前时刻的输出。为了将当前时刻的状态转化为最终的输出,循环神经网络还需要另外一个全连接神经网络来完成这个过程。这和卷积神经网络中最后的全连接层的意义是一样的。类似的,不同时刻用于输出的全连接神经网络中的参数也是一致的。为了让读者对循环神经网络的前向传播有一个更加直观的认识,图8-5展示了一个循环神经网络前向传播的具体计算过程。

图8-5 循环神经网络的前向传播的计算过程示意图

在图8-5中,假设状态的维度为2,输入、输出的维度都为1,而且循环体中的全连接层中权重为:

偏置项的大小为 ,用于输出的全连接层权重为:

偏置项大小为 。那么在时刻t 0 ,因为没有上一时刻,所以将状态初始化为[0,0],而当前的输入为1,所以拼接得到的向量为[0,0,1],通过循环体中的全连接层神经网络得到的结果为:

这个结果将作为下一时刻的输入状态,同时循环神经网络也会使用该状态生成输出。将该向量作为输入提供给用于输出的全连接神经网络可以得到t 0 时刻的最终输出:

使用t 0 时刻的状态可以类似地推导得出t 1 时刻的状态为[0.860, 0.884],而t1时刻的输出为2.73。在得到循环神经网络的前向传播结果之后,可以和其他神经网络类似地定义损失函数。循环神经网络唯一的区别在于因为它每个时刻都有一个输出,所以循环神经网络的总损失为所有时刻(或者部分时刻)上的损失函数的总和。以下代码实现了这个简单的循环神经网络前向传播的过程。

import numpy as np

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

'''

和其他神经网络类似,在定义完损失函数之后,套用第4章中介绍的优化框架TensorFlow就可以自动完成模型训练的过程。这里唯一需要特别指出的是,理论上循环神经网络可以支持任意长度的序列,然而在实际中,如果序列过长会导致优化时出现梯度消散的问题(the vanishing gradient problem) (8) ,所以实际中一般会规定一个最大长度,当序列长度超过规定长度之后会对序列进行截断。

Copyright & copy 7dtime.com 2014-2018 all right reserved,powered by Gitbook该文件修订时间: 2018-06-23 09:57:04

results matching ""

    No results matching ""