经典神经网络结构如下图:
从图中可以看出,循环神经网络主体A的输入除了来自输入层的xt,还有一个循环的边来提供上一时刻的隐藏状态(hidden state)ht-1。在每一时刻,循环神经网络的模块A读取了xt和ht-1后,会生成新的隐藏状态ht,并产生本时刻输出ot。因为模块A中运算和变量在不同时刻是相同的,因此RNN理论上可以被看做是同一神经网络结构被无限复制的结果。
正如CNN在不同的空间位置参数共享,RNN是在不同的时间位置参数共享,从而能够使用有限的参数处理任意长度的序列。
将完整的输入输出序列展开,可以得到下图展示的结构:
从图中可以看到RNN对长度为N的序列展开后,可以视为一个有N个中间层的前馈神经网络。这个前馈神经网络没有循环连接,因此可以使用反向传播算法进行训练。
对于一个序列数据,可以将这个序列上不同时刻的数据依次传入RNN的输入层,而输出可以是对序列中下一时刻的预测,也可以是对当前时刻信息的处理结果。RNN要求每个时刻都要有一个输入,但不一定每个时刻都要有输出。
如前面所说,RNN可以看做是同一神经网络结构在时间序列上被复制多次的结果,这个被复制多次的结构被称为循环体。如何设计循环体的网络结构是解决问题的关键。下图展示了一个最简单的循环体结构。
RNN网络中的状态是通过一个向量来表示的,这个向量的维度也称为RNN网络隐藏层的大小,假设其为n。
以下代码简单实现了RNN的前向传播:
import numpy as np
X = [1, 2] # 输入
state = [0.0, 0.0] # 初始h0的状态
# 分别定义不同输入部分的权重
w_cell_state = np.asarrat([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
# 按时间顺序执行RNN的前向传播过程
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
理论上RNN可以支持任意长度的序列,然而在实际中,如果序列过长,一方面会引起梯度弥散,另一方面,展开后的前馈神经网络会占用过大的内存。
LSTM靠一些"门"的结构让信息有选择地影响RNN每个时刻的状态。所谓"门"的结构就是一个使用sigmoid神经网络和一个按位做乘法的操作,这两个操作合在一起就是一个"门"的结构。
遗忘门的的作用是让RNN忘记之前没有用的信息。它会根据当前的输入xt和上一时刻的输出ht-1决定哪一部分记忆需要被遗忘。
假设状态c的维度是n,遗忘门会根据当前的输入xt和上一时刻输出ht-1计算出一个n维向量 f=sigmoid(W1*x + W2*h)
它在每一维度上的值都在(0, 1)范围内,再将上一时刻的状态ct-1与f按位相乘,那么f取值接近0的维度上的信息就会被“忘记”,而接近1的信息会被保留。
在“忘记”了部分之前的状态后,它还需要从当前的输入补充最新的记忆,这个过程就是输入门完成的。
输入门会根据xt和ht-1决定哪些信息加入到状态ct-1中,从而生成新的状态ct。
通过遗忘门和输出门,LSTM结构可以更加有效地决定哪些信息应该被遗忘,那些信息应该得到保留。
LSTM结构在计算得到新的状态ct后,需要产生当前时刻的输出,这个过程是通过输出门完成的。输出门会根据最新的状态ct、上一时刻的输出和当前的输入xt,来决定该时刻的输出ht。
具体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是四个维度为[2n, n]的参数矩阵
下图展示了一个双向循环神经网络:
双向循环神经网络的主体就是两个单向的结合。在每一个时刻t,输入会同时提供给这两个方向相反的RNN。两个网络独立计算,产生各自该时刻的新状态和输出,而双向循环网络的最终输出是这两个单向RNN的输出的简单拼接。
为了增强表达能力,在网络中设置多个循环层,将每层循环网络的输出传给下一层处理。
结构如下图:
在一个L层的深层循环网络中,每一时刻的输入xt到输出ot之间有L个循环体,因此网络可以从输入中抽取更加高层的信息。不同层之间的参数可以不一致。tf中提供了MultiRNNCell类来实现深层循环神经网络的前向传播,它实际是在BasicLSTMCell的基础上再封装一层。
通过dropout可以让神经网络更健壮,RNN一般只在不同层循环体结构之间使用dropout,而不在同一层的循环体结构之间使用。
图中实线箭头表示不适用dropout,虚线箭头表示使用dropout。