循环神经网络简介

循环神经网络(recurrent neural network, RNN)源自1982年由Saratha Sathasivam提出的霍普菲尔德网络。霍普菲尔德网络因为实现困难,在提出时并且没有被合适地应用。该网络结构也于1986年后被全连接神经网络以及一些传统的机器学习算法所取代。然而:

  • 传统的机器学习算法非常依赖于人工提取的特征,使得基于传统机器学习的图像识别、语音识别以及自然语言处理等问题存在特征提取的瓶颈;
  • 基于全连接神经网络的方法也存在参数太多、无法利用数据中时间序列信息等问题。

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

1. 原理

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

下图展示了一个典型的RNN。在每一时刻 t ,RNN会针对该时刻的输入结合当前模型的状态给出一个输出,并更新模型状态。从下图中可以看到,RNN的主体结构 A 的输入除了来自输入深度学习(三):详解循环神经网络RNN,含公式推导,还有一个循环的边来提供上一时刻的隐藏状态(hidden state)深度学习(三):详解循环神经网络RNN,含公式推导。在每一个时刻,RNN的模块A在读取了深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导之后会生成新的隐藏状态深度学习(三):详解循环神经网络RNN,含公式推导,并产生本时刻的输出深度学习(三):详解循环神经网络RNN,含公式推导,由于模块A中的运算和变量在不同时刻是相同的,因此RNN络理论上可以被看作是同一神经网络结构被无限复制的结果。正如卷积神经网络在不同的空间位置共享参数,循环神经网络是在不同时间位置共享参数,从而能够使用有限的参数处理任意长度的序列。

深度学习(三):详解循环神经网络RNN,含公式推导

将完整的输入输出序列展开,可以得到下图所展示的结构。在图中可以更加清楚地看到RNN在每一个时刻会有一个输入深度学习(三):详解循环神经网络RNN,含公式推导,然后根据RNN前一时刻的状态深度学习(三):详解循环神经网络RNN,含公式推导计算新的状态深度学习(三):详解循环神经网络RNN,含公式推导,并输出深度学习(三):详解循环神经网络RNN,含公式推导。RNN当前的状态深度学习(三):详解循环神经网络RNN,含公式推导, 是根据上一时刻的状态深度学习(三):详解循环神经网络RNN,含公式推导和当前的输入深度学习(三):详解循环神经网络RNN,含公式推导共同决定的。在时刻 t,状态深度学习(三):详解循环神经网络RNN,含公式推导浓缩了前面序列深度学习(三):详解循环神经网络RNN,含公式推导的信息,用于作为输出深度学习(三):详解循环神经网络RNN,含公式推导的参考。由于序列的长度可以无限延长,维度有限的h状态不可能将序列的全部信息都保存下来,因此模型必须学习只保留与后面任务深度学习(三):详解循环神经网络RNN,含公式推导相关的最重要的信息。

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

深度学习(三):详解循环神经网络RNN,含公式推导

2. 应用

从RNN的结构特征可以很容易看出它最擅长解决与时间序列相关的问题。RNN也是处理这类问题时最自然的神经网络结构。对于一个序列数据,可以将这个序列上不同时刻的数据依次传入RNN的输入层,而输出可以是对序列中下一个时刻的预测,也可以是对当前时刻信息的处理结果(比如语音识别结果)。循环神经网络要求每一个时刻都有一个输入,但是不一定每个时刻都需要有输出。

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

深度学习(三):详解循环神经网络RNN,含公式推导

3. RNN前向传播

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

深度学习(三):详解循环神经网络RNN,含公式推导

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

假设输入向量的维度为x,隐藏状态的维度为n,那么上图循环体的全连接层神经网络的输入大小为n+x。也就是将上一时刻的状态与当前时刻的输入拼接(这里为了简便描述为拼接,实际情况往往是t-1时刻的隐状态和输入分别乘以一个矩阵,详见代码)成一个大的向量作为循环体中神经网络的输入。因为该全连接层的输出为当前时刻的状态,于是输出层的节点个数也为n,循环体中的参数个数为(n+x)∗n+n 个。从图中可以看到,循环体中的神经网络输出不但提供给了下一时刻作为状态,同时也会提供给当前时刻的输出。注意到循环体状态与最终输出的维度通常不同,因此为了将当前时刻的状态转化为最终的输出,RNN还需要另外一个全连接神经网络来完成这个过程。这和CNN中最后的全连接层的意义是一样的类似的,不同时刻用于输出的全连接神经网络中的参数也是一致的。

4. 前向传播例子

下图直观的展示了一个RNN前向传播的具体计算过程。

深度学习(三):详解循环神经网络RNN,含公式推导

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

深度学习(三):详解循环神经网络RNN,含公式推导

偏置项的大小为深度学习(三):详解循环神经网络RNN,含公式推导=[0.1,−0.1],用于输出的全连接层权重为:

深度学习(三):详解循环神经网络RNN,含公式推导

偏置项的大小为深度学习(三):详解循环神经网络RNN,含公式推导=0.1。那么在时刻深度学习(三):详解循环神经网络RNN,含公式推导,因为没有上一时刻,所以将状态初始化为深度学习(三):详解循环神经网络RNN,含公式推导=[0,0],而当前的输入为1,所以拼接得到的向量为[0,0,1],通过循环体中的全连接层神经网络得到的结果为:

深度学习(三):详解循环神经网络RNN,含公式推导

这个结果将作为下一时刻的输入状态,同时RNN也会使用该状态生成输出。将该向盘作为输入提供给用于输出的全连接神经网络可以得到深度学习(三):详解循环神经网络RNN,含公式推导时刻的最终输出:

深度学习(三):详解循环神经网络RNN,含公式推导

使用深度学习(三):详解循环神经网络RNN,含公式推导时刻的状态可以类似地推导得出深度学习(三):详解循环神经网络RNN,含公式推导时刻的状态为[0.860, 0.884],而深度学习(三):详解循环神经网络RNN,含公式推导时刻的输出为2.73。在得到RNN的前向传播结果之后,可以和其他神经网络类似地定义损失函数。RNN唯一的区别在于因为它每个时刻都有一个输出,所以RNN的总损失为所有时刻(或者部分时刻)上的损失函数的总和。

以下代码实现了这个简单的循环神经网络前向传播的过程:

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)
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]

RNN(BPTT   Back Propagation Through Time)

以上情况只是RNN中最简单的情形,它还有许多变种,我们接下来看一个标准的RNN结构,并推导一下它的数学公式

深度学习(三):详解循环神经网络RNN,含公式推导

下图是一个以时间展开的更为具体的结构,【W,V,U】表示顺序与上图表示略有不同

深度学习(三):详解循环神经网络RNN,含公式推导

RNN的几种常见结构

结构一:

深度学习(三):详解循环神经网络RNN,含公式推导

深度学习(三):详解循环神经网络RNN,含公式推导


结构二: 

深度学习(三):详解循环神经网络RNN,含公式推导

 深度学习(三):详解循环神经网络RNN,含公式推导


结构三:

深度学习(三):详解循环神经网络RNN,含公式推导

深度学习(三):详解循环神经网络RNN,含公式推导


 RNN的一个特点是所有的隐层共享参数(U,V,W),整个网络只用这一套参数。

RNN前向传导

深度学习(三):详解循环神经网络RNN,含公式推导

上图中左边是RNN模型没有按时间展开的图,如果按时间序列展开,则是上图中的右边部分,我们重点观察右边部分的图,注意区分与之前几幅图的角标的不同,以下讨论都以上图角标为准

这幅图描述了在序列索引号 t 附近RNN的模型。其中:

  1. 深度学习(三):详解循环神经网络RNN,含公式推导代表在序列索引号 t 时训练样本的输入。同样的,深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导代表在序列索引号 t−1 和 t+1 时训练样本的输入。
  2. 深度学习(三):详解循环神经网络RNN,含公式推导代表在序列索引号 t 时模型的隐藏状态。深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导共同决定。
  3. 深度学习(三):详解循环神经网络RNN,含公式推导代表在序列索引号 t 时模型的输出。深度学习(三):详解循环神经网络RNN,含公式推导只由模型当前的隐藏状态深度学习(三):详解循环神经网络RNN,含公式推导决定。
  4. 深度学习(三):详解循环神经网络RNN,含公式推导代表在序列索引号 t 时模型的损失函数。
  5. 深度学习(三):详解循环神经网络RNN,含公式推导代表在序列索引号 t 时训练样本序列的真实输出。
  6. 深度学习(三):详解循环神经网络RNN,含公式推导这三个矩阵是我们的模型的线性关系参数,它在整个RNN网络中是共享的。 也正因为是共享了,它体现了RNN的模型的“循环反馈”的思想。  

有了上面的模型,RNN的前向传播算法就很容易得到了。

对于任意一个序列时刻 t ,我们隐藏状态深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导得到:

                                                                  深度学习(三):详解循环神经网络RNN,含公式推导

其中 σ 为RNN的**函数,一般为tanh, b为偏置项。

序列时刻 t ,模型的输出o(t)的表达式比较简单:

                                                                                  深度学习(三):详解循环神经网络RNN,含公式推导

最终在序列时刻 t 时我们的预测输出为:

                                                                                     深度学习(三):详解循环神经网络RNN,含公式推导

通常由于RNN是识别类的分类模型,所以上面这个**函数一般是softmax。

通过损失函数深度学习(三):详解循环神经网络RNN,含公式推导,比如对数似然损失函数、交叉熵损失函数,我们可以量化模型在当前位置的损失,即深度学习(三):详解循环神经网络RNN,含公式推导深度学习(三):详解循环神经网络RNN,含公式推导的差距。

反向传播算法推导

有了RNN前向传播算法的基础,就容易推导出RNN反向传播算法的流程了。通过梯度下降法一轮轮的迭代,得到合适的RNN模型参数 U,W,V,b,c 。由于我们是基于时间反向传播,所以RNN的反向传播有时也叫做BPTT(back-propagation through time)。当然这里的BPTT和DNN也有很大的不同点,即这里所有的 U,W,V,b,c 在序列的各个位置是共享的,反向传播时我们更新的是相同的参数。

为了简化描述,这里的损失函数我们为交叉熵损失函数,输出的**函数为softmax函数,隐藏层的**函数为tanh函数。

对于RNN,由于我们在序列的每个位置都有损失函数,因此最终的损失L为:

                                                                                      深度学习(三):详解循环神经网络RNN,含公式推导

其中,深度学习(三):详解循环神经网络RNN,含公式推导为序列的总时间步

接下来对V,c求梯度,V,c 的梯度计算比较简单

                                                          深度学习(三):详解循环神经网络RNN,含公式推导

                                                  深度学习(三):详解循环神经网络RNN,含公式推导

但是深度学习(三):详解循环神经网络RNN,含公式推导的梯度计算就比较的复杂了。从RNN的模型可以看出,在反向传播时,在某一序列位置 t 的梯度损失由当前位置的输出对应的梯度损失和序列索引位置 t+1 时的梯度损失两部分共同决定。对于 W 在某一序列位置 t 的梯度损失需要反向传播一步步的计算。我们定义序列索引 t 位置的隐藏状态的梯度为: 

                                                                                       深度学习(三):详解循环神经网络RNN,含公式推导

                        深度学习(三):详解循环神经网络RNN,含公式推导

这样我们就可以根据深度学习(三):详解循环神经网络RNN,含公式推导递推深度学习(三):详解循环神经网络RNN,含公式推导

对于深度学习(三):详解循环神经网络RNN,含公式推导,由于它的后面没有其他的序列索引了,因此有:

                                                                  深度学习(三):详解循环神经网络RNN,含公式推导

有了深度学习(三):详解循环神经网络RNN,含公式推导,计算深度学习(三):详解循环神经网络RNN,含公式推导就容易了,这里给出深度学习(三):详解循环神经网络RNN,含公式推导的梯度计算表达式:

                                                深度学习(三):详解循环神经网络RNN,含公式推导

                                                    深度学习(三):详解循环神经网络RNN,含公式推导

                                                        深度学习(三):详解循环神经网络RNN,含公式推导

根据RNN模型会有些不同,自然前向反向传播的公式会有些不一样,但是原理基本类似。

需要特别指出的是,理论上RNN可以支持任意长度的序列,然而在实际训练过程中,如果序列过长:

  • 一方面会导致优化时出现梯度消散和梯度爆炸的问题;
  • 另一方面,展开后的前馈神经网络会占用过大的内存

所以实际中一般会规定一个最大长度,当序列长度超过规定长度之后会对序列进行截断。