一、循环神经网络的定义:
循环神经网络的单个神经元模型如下图所示,与以往的神经元相比它包含了一个反馈输入,如果将其按照时间变化展开可以看到循环神经网络单个神经元类似一系列权值共享前馈神经元的依次连接,连接后同传统神经元相同随着时间的变化输入和输出会发生变化,但不同的是循环神经网络上一时刻神经元的”历史信息”会通过权值与下一时刻的神经元相连接,这样循环神经网络在t时刻的输入完成与输出的映射且参考了t之前所有输入数据对网络的影响,形成了反馈网络结构。
二、网络训练学习算法
1、反馈网络结构存在的问题
反馈结构的循环神经网络能够参考背景信号但常见的信号所需要参考的背景信息与目标信息时间相隔可能非常的宽泛,理论上循环神经网络可以参考距离背景信息任意范围的参考信息,但实际应用过程中对于较长时间间隔的参考信息通常无法参考。
2、解决办法
上述问题的解决关键在于网络训练时需要计算的网络代价函数梯度,而梯度计算与神经元之间连接的权值密切相关,在训练学习过程中很容易造成梯度爆炸或者梯度消失问题。常见的网络训练学习算法以反向传播算法或者实时递归学习算法为主。
3、缺陷
随着时间推移数据量逐步增大以及网络隐层神经元自身循环问题,这些算法的误差在按照时间反向传播时会产生指数增长或者消失问题。
(1)由于时间延迟越来越长从而需要参考的信号也越来越多,这样权值数量也会出现激增,最终,很小的误差经过大量的权值加和之后出现指数式增长,导致无法训练或者训练时间过长。
(2)梯度消失问题指网络刚开始输入的具有参考价值的数据,随着时间变化新输入网络的数据会取代网络先前的隐层参数导致最初的有效信息逐步被”忘记”,如果以颜色深浅代表数据信息的有用程度,那么随着时间的推移数据信息的有用性将逐步被淡化。
这两种问题都会导致网络的实际建模缺陷,无法参考时间间隔较远的序列状态,最终在与网络相关的分类识别类似的应用中仍旧无法获得好的实践效果。
三、最简单的循环神经网络
1、网络结构
神经网络一共有3层,分别是输入层x,隐藏层h和输出层y。定义每一层的节点下标如下:k表示的是输出层的节点下标,j表示的是当前时间节点隐藏层的节点下标,l表示的是上一时间节点隐藏层的节点下标,i表示的是输入层的节点下标。
四、长短时间记忆单元
1、出现原因:
为了解决循环神经网络在训练过程中的梯度问题,循环神经网络神经元在以往的循环神经元结构基础上进行改进,提出了一种称作长短时间记忆单元(Long Short-Term Memory,LSTM)的特殊结构循环神经网络。
2、应用效果
该网络结构基于梯度学习算法能够避免上述提及的梯度问题且对于存在噪声或不可压缩的输入序列数据依然可以参考时间间隔在1000 时间步长以上的数据信息。经过大量的实验结论证明LSTM网络已经解决了传统循环神经网络无法解决的问题,在蛋白质结构预测,语音识别及手写字符识别等常见研究方面取得了新的突破。
3、LSTM分析
LSTM网络由一个一个的单元模块组成,每个单元模块一般包含一个或者多个反馈连接的神经元及三个乘法单元,正是由于这些乘法单元的存在我们可以用这些乘法单元实现数据是否输入、输出及遗忘摒弃。常用取值为’0’或’1’的输入门,输出门和遗忘门与对应数据相乘实现如下图所示,输入门控制是否允许输入信息输入到当前网络隐层节点中,如果门取值为’1’则输入门打开允许输入数据,相反若门取值为’0’,则输入门关闭不允许数据输入;输出门控制经过当前节点的数据是否传递给下一个节点,如果门取值为’1’,则当前节点的数据会传递给下一个节点,相反若门取值为’0’,则不传递该节点信息;遗忘门控制神经元是否摒弃当前节点所保存的历史时刻信息,该门通过一种称为”peephole”的连接方式和内部神经元相连这种连接方式可以提高LSTM在时间精度及计算内部状态相关研究的学习能力,若门取值为’1’则保留以往的历史信息,相反若门取值为’0’,则清除当前节点所保存的历史信息。除了多增加的三个信息输入控制门以外,模块内部的神经元连接方式也有所不同,线性性质的内部神经元以一个权值固定为1的循环自连接方式连接称为”Constant Error Carousel”(CEC),CEC连接保证了误差或梯度随着时间的传播不会发生消失现象。当没有新的输入或者误差信号进入神经元时,CEC的局部误差既不增长也不下降保持不变状态,最终能够在前向传播和反向传播时都能通过输入输出门保证不必要的信息进入到网络。对较远时间步长的信号参考而言,更远时间步长的有效数据信息可以通过各个门的组合开或关保存下来,而无效的数据信息可以被摒弃其对应的参数无需保存,最终前面提及的梯度消失问题得到遏制。例如,当有参考价值的有效信息出现时,我们打开输入门使有效数据可以输入,当有效数据输入网络后输入门再保持关闭状态,随着时间的推移如果输入门一直保持关闭状态,那么,网络新输入的数据将无法替换单元以前的激励输出,在任意时刻只要网络输出门打开该有效数据信息都可以随时参考,从而网络实现了时间距离步长更远的有效数据信息参考。
单个LSTM单元如下图:
单个LSTM网络与最初的循环神经网络除了隐层的非线性单元被替换成为了记忆模块以外别的方面都非常相似,但LSTM网络内部隐层连接方面却有了非常大的变化。
五、双向LSTM网络
双向循环神经网络前向传播层和后向传播层相互分离,如果没有后向传播层那么该网络结构和我们最早提到的前馈网络结构完全相同。网络输入节点同时和前向后向传播单元连接,对于前向传播层而言,上一时刻的输出状态作为下一时刻的输入状态;对于后向传播层而言,上一时刻的输入为下一时刻的输出状态,最终,前向传播输出和输入同时输出到网络输出节点。对于这样的网络组成形式,我们在同一网络结构中,可以直接用两个相反时间方向的输入信息来减少代价函数的误差而不需要额外的算法处理”未来”数据信息,较前面提及的传统循环神经网络方法更为简便。虽然双向循环神经网络结构发生了很大的变化,但是网络隐层中的两个传播层之间仍然互不连接,单个隐层网络完全可以看作是前馈网络结构,若仍旧采用传统网络训练时的反向传播算法,由于传播方向不同,前向传播层和后向传播层的神经元状态及输出与以往相比将不能同时进行。此外,对于t = 1时的前向层和t = T时的后向传播层它们的初始状态必须在训练之前给出。所以,网络训练方面我们可以参考部分以往循环神经网络的训练算法,只是训练算法因网络结构改变有所增加。
六、循环神经网络基础实践
1、案例描述:
例子: 输入3行4列的矩阵数据,如
[1, 2, 5, 6],
[5, 7, 7, 8],
[3, 4, 5, 7]
输出:
[1, 3, 7, 11],
[5, 12, 14, 15],
[3, 7, 9, 12]
分析规律: 输出第1列为输入的第1列, 输出第2列为输入的第1列和第2列之和, 输出的第3列为输入的第2列和第3列之和,依次类推。
2、通过神经网络来训练模型
可以看到里面有LSTM(长短文本分析)的特征,所以可以用循环神经网络来训练。
(1)导入依赖
import tensorflow as tf
from tensorflow.contrib import rnn
tf.reset_default_graph()
(2)创建一个模型类, 这个类中包含RNN训练参数初始化,损失函数及优化器的构建,模型生成方法,模型训练方法,以及测试方法。
class SeriesPredictor:
def __init__(self, input_dim, seq_size, hidden_dim=10):
# 网络参数
self.input_dim = input_dim # 输入维度
self.seq_size = seq_size # 时序长度
self.hidden_dim = hidden_dim # 隐藏层维度
# 权重参数W与输入X及标签Y
self.W_out = tf.Variable(tf.random_normal([hidden_dim, 1]), name="W_out")
self.b_out = tf.Variable(tf.random_normal([1]), name='b_out')
self.x = tf.placeholder(tf.float32, [None, seq_size, input_dim])
self.y = tf.placeholder(tf.float32, [None, seq_size])
# 均方误差求损失值,并使用梯度下降
self.cost = tf.reduce_mean(tf.square(self.model() - self.y))
self.train_op = tf.train.AdamOptimizer().minimize(self.cost)
self.saver = tf.train.Saver()
def model(self):
'''
:param x: inpouts of size [T, batch_size, input_size]
:param W: matrix of fully-connected output layer weights
:param b: vector of fully-connected output layer biases
'''
# BasicLSTMCell基本的RNN类, 建立hidden_dim个CELL
cell = rnn.BasicLSTMCell(self.hidden_dim)
# dynamic_rnn 动态RNN, cell生成好的cell类对象, self.x是一个张量, 一般是三维张量[Batch_size, max_time(序列时间X0-Xt), X具体输入]
outputs, states = tf.nn.dynamic_rnn(cell, self.x, dtype=tf.float32) # (?, seq_size, hidden_dim)
num_examples = tf.shape(self.x)[0]
tf_expand = tf.expand_dims(self.W_out, 0)
tf_tile = tf.tile(tf_expand, [num_examples, 1, 1]) # 将第一维扩大为num_examples维 (?, hidden_dim, 1)
out = tf.matmul(outputs, tf_tile) + self.b_out # (?, seq_size, 1)
print(out)
out = tf.squeeze(out)
return out
def train(self, train_x, train_y):
with tf.Session() as sess:
tf.get_variable_scope().reuse_variables() # 变量可重复利用
sess.run(tf.global_variables_initializer())
for i in range(1000):
_, mse = sess.run([self.train_op, self.cost], feed_dict={self.x: train_x, self.y: train_y})
if i % 100 == 0:
print(i, mse)
save_path = self.saver.save(sess, './model')
print('Model saved to {}'.format(save_path))
def test(self, test_x):
with tf.Session() as sess:
tf.get_variable_scope().reuse_variables()
self.saver.restore(sess, './model')
output = sess.run(self.model(), feed_dict={self.x: test_x})
return output
(3)最后写一个main函数,用训练数据训练网络,并用测试数据测试。
if __name__ == '__main__':
predictor = SeriesPredictor(input_dim=1, seq_size=4, hidden_dim=10)
train_x = [[[1], [2], [5], [6]],
[[5], [7], [7], [8]],
[[3], [4], [5], [7]]]
train_y = [[1, 3, 7, 11],
[5, 12, 14, 15],
[3, 7, 9, 12]]
predictor.train(train_x, train_y)
test_x = [[[1], [2], [3], [4]],
[[4], [5], [6], [7]]]
test_y = [[[1], [3], [5], [7]],
[[4], [9], [11], [13]]]
pred_y = predictor.test(test_x)
print("\n开始测试!\n")
for i, x in enumerate(test_x):
print("当前输入{}".format(x))
print("应该输出{}".format(test_y[i]))
print("训练模型的输出{}".format(pred_y[i]))
(4)运行结果