深度学习 _ 循环神经网络 RNN 与 LSTM

1. 循环神经网络 RNN

1) 什么是 RNN?

循环神经网络(RNN)是一种节点定向连接成环的人工神经网络。具体应用有语音识别,手写识别,翻译等.

2) 什么时候使用 RNN?

FNN(前馈神经网络,如 BP,CNN 等)效果已经不错了,RNN 还需要更大量的计算,为什么要用 RNN 呢?如果训练 N 次,每次和每次都没什么关系,那就不需要 RNN,但如果每个后一次都可能和前一次训练相关,比如说翻译:一个句子里面N个词,一个词为一次训练(train instance),一个词的意思很可能依赖它的上下文,也就是其前次或后次训练,这个时候就需要 RNN.

3) RNN 与 FNN 有何不同?

如图所示,左边的是前馈神经网络,数据按黑箭头方向从输入层经过隐藏层流入输出层,向前流动,因此叫做前馈网络.右图中,隐藏层中的数据除了传向输出层,还和下次输入一起训练后续的隐藏层,不再是单向,而是包含了循环,则构成了循环神经网络.下图是将各个时间点画在同一图上,左边前馈 FNN 的展开图,右边是 RNN 的展开图.

简单地说,它只是在隐藏层处加了一个"循环",但实际上问题没这么简单.之前说过(详见:深度学习 _BP 神经网络),输入层向隐藏层传数据时,根据权重U计算(为简化说明省略偏置),隐藏层向输出层传数据时,根据权重 V 计算(之前文档用字体 w1,w2 表示),循环神经网络又加了参数 W,用于控制隐藏层的权重.看起来好像只是多了一次矩阵乘法和加法,但实际上 RNN 计算要比前馈网络复杂很多.原因我们看红色箭头,它标记的是误差反向传播,也就是根据实际结果 y 和输出层的预测结果 o 计算出的误差传回网络以调整权重 UVW.由于每个隐藏层都依赖前一隐藏层的结果,因此误差不只要从隐藏层传回输入层,还要一层一层传回上一隐藏层.它使用计算变得很复杂,且无法并行.

于是又有了下图中的变种,使用实际输出 y,和下个 x 一次训练下一隐藏层,因为 y 中信息并不像 h 中那么丰富,因此可能效果会差一些.这里只是循环神经网络的几种情况,其它就不一一列举了,总的来说循环神经网络并无定式,主要指数据的流向中包含循环.

2. LSTM

经常听到 LSTM 神经网络如何如何,其实 LSTM 不是一种网络,而是一种对 RNN 隐藏层的改进算法(改进算法有很多,这个因为效果好,所以比较著名)

LSTM(Long short-term memory) 是长短期记忆的简写.

引自:《深度学习》"花书"

如果不断用隐藏层去计算下一时间隐藏层,当计算隐藏层的特征向量大于1时,经过 N 次迭代后值就会越来越大,最终发生爆炸,如果小于 1,最后越来越小,导致消失.换句话说,过去会给我们启发,所以不能忘记过去,但如果每时每刻都被过去影响,就像滚雪球一样,最后也会悲剧.最好是把重点记住,然后在开始新篇章的时候更多地忘记过去.

更直观的说,原来在输入层和隐藏层间是仿射变换加激活函数,现在用输入门,遗忘门输出门和状态层来代替隐藏单元的生成算法.其中每个门都有非线性变换.这几个门的关系,详见代码:

1
2
3
4
5
6
input_gate = tf.sigmoid(tf.matmul(i, ix) + tf.matmul(o, im) + ib)  #输入门
forget_gate = tf.sigmoid(tf.matmul(i, fx) + tf.matmul(o, fm) + fb) #遗忘门
update = tf.tanh(tf.matmul(i, cx) + tf.matmul(o, cm) + cb)  #更新
state = forget_gate * state + input_gate * update # 更新状态, 遗忘门控制是否忘记旧时状态
output_gate = tf.sigmoid(tf.matmul(i, ox) + tf.matmul(o, om) + ob) #输出门
return output_gate * tf.tanh(state), state #返回输出值

3. 程序

1) 说明

与前篇的 BP 网络和 CNN 网络一样,这次使用的仍然是 MNIST 手写数据识别.在练习了纯 Python 和 Keras 框架之后,此次使用更低层的 TensorFlow 代码实现 RNN.也顺便了解一个高级工具都封装了什么?

每个图片仍然是 28x28 像素,前馈网络把 28x28 共 748 个像素值作为一个输入 x 数据传入输入层,而 RNN 把每张图当成一个序列,序列有 28 个元素(一行为一个元素),以每行的 28 个点为输入 x 传入输入层.简单地说就是切成一行一行训练,每行与下一行有一定联系.

下图是时序图(为简化逻辑,此处只画了一个隐藏层),相对最一般的 RNN,下图是一个变种:整个序列(28 行)的输入对应同一个输出 y(手写对应的数字).

图片.png
2) 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# -*- coding: utf-8 -*-

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
import numpy as np

# 在MINIST_data目录下载 mnist数据
mnist = input_data.read_data_sets("MINIST_data/", one_hot=True)

# RNN学习时使用的参数
learning_rate = 0.001 # 学习率
training_iters = 100000 # 训练实例数
batch_size = 120 # 批大小
display_step = 10 # 训练10批显示一次

n_input = 28 # 每28个作为一个输入层的节点数(行中点)
n_steps = 28 # 28个连续序列(列)
n_hidden = 128 # 隐含层的节点数
n_classes = 10 # 输出的节点数,0~9个数字,这里一共有10个

x = tf.placeholder("float", [None, n_steps, n_input]) # 构建输入节点
istate = tf.placeholder("float", [None, 2 * n_hidden]) # 构建隐藏节点,一个存节点,一个存状态
y = tf.placeholder("float", [None, n_classes]) # 构建输出节点

# 随机初始化各层的权值和偏置
weights = {
'hidden': tf.Variable(tf.random_normal([n_input, n_hidden])), # 输入到隐藏
'out': tf.Variable(tf.random_normal([n_hidden, n_classes])) # 隐藏到输出
}
biases = {
'hidden': tf.Variable(tf.random_normal([n_hidden])),
'out': tf.Variable(tf.random_normal([n_classes]))
}

# 建立RNN模型,_X是批训练数据,_istate为隐藏节点
def RNN(_X, _istate, _weights, _biases):
_X = tf.transpose(_X, [1, 0, 2]) # 把batch_size,n_steps,n_input顺序变为n_steps,batch_size,n_input
_X = tf.reshape(_X, [-1, n_input]) # 再转换为n_steps*batch_size, n_input
# 两个隐藏层:第一层直接计算,第二层用LSTM
_X = tf.matmul(_X, _weights['hidden']) + _biases['hidden'] # 计算隐藏层的节点,此时X从输入节点转为隐藏节点
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0,state_is_tuple=False) # 定义lstm
_X = tf.split(_X, n_steps, 0) # 序列切片,每片是一个(batch_size, n_hidden)
outputs, states = tf.nn.static_rnn(lstm_cell, _X, initial_state=_istate) # 计算lstm rnn,states存状态
return tf.matmul(outputs[-1], _weights['out']) + _biases['out'] #计算输出层

pred = RNN(x, istate, weights, biases)

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y)) # 损失函数为交叉熵
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) # 优化方法为Adam

correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) # 计算错误数
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)) # 计算错误率

init = tf.global_variables_initializer()

sess = tf.InteractiveSession()
sess.run(init)
step = 1
while step * batch_size < training_iters:
batch_xs, batch_ys = mnist.train.next_batch(batch_size) # 随机抽取训练数据
batch_xs = batch_xs.reshape((batch_size, n_steps, n_input))
# 用feed_dict传入数据:输入,输出,隐藏层, 数据由placeholder定义, 运行optimizer
sess.run(optimizer, feed_dict={x: batch_xs, y: batch_ys, istate: np.zeros((batch_size, 2 * n_hidden))})
if step % display_step == 0: # 每display_step次批处理显示一次, 通过run运行accuracy,cost
acc = sess.run(accuracy, feed_dict={x: batch_xs, y: batch_ys, istate: np.zeros((batch_size, 2 * n_hidden))})
loss = sess.run(cost, feed_dict={x: batch_xs, y: batch_ys, istate: np.zeros((batch_size, 2 * n_hidden))})
print("Iter " + str(step * batch_size) + ", Minibatch Loss= " + "{:.6f}".format(loss) + ", Training Accuracy= " + "{:.5f}".format(acc))
step += 1
print("Optimization Finished!")

test_len = 256
test_data = mnist.test.images[:test_len].reshape((-1, n_steps, n_input))
test_label = mnist.test.labels[:test_len]
print("Testing Accuracy:", sess.run(accuracy, feed_dict={x: test_data, y: test_label, istate: np.zeros((test_len, 2 * n_hidden))}))

3) 分析

TensorFlow 涉及了更多具体计算,比如格式转换,矩阵乘法等等,不像 Keras 从外面基本看不到具体的步骤的动作和结果.

从代码中很容易明白为什么说 TensorFlow 是一个"框架",程序的前 50 行,具体数据还没出现,程序就指定了数据的结构和流向,在接下来的 sess 部分,tf 才真正开始运算,把数据切块"喂"给框架,使其运行.它和一般程序调函数,确实不太一样.

这里比较不容易理解的是数据怎么从 feed_dict 转入了 RNN 函数.在 pred = RNN(x, istate, weights, biases) 被运行时,其实里面并没有真正的数据在做转换和乘法,这里定义的是数据的流程.此时的 x,istate 里面还没有数据,而只是定义了数据形式,并告诉 TensorFlow 该数据需要如何处理.在后面 feed_dict 处理时才传入了真正的数据,并通过 run() 间接地调用了 RNN(). sess.run() 调用函数时,函数的各个参数都是从当前环境里取的.我理解这里的 placeholder 意思有点像 C 中定义的数据结构.

4. 问题与解答

1) RNN 的隐藏层是一个还是多个?

隐藏层可以是一个,也可以是多个(多层循环网络),比如说可以有三个隐藏层 h1,h2,h3,其中 h2 将结果转给下一个实例的训练,还可以是双向的(一个传向前一时间点,一个传向后一时间点,即双向循环网络),一般为了简化,例子里都有单层的.

2) RNN 中反向传播梯度是怎么进行的?

对于训练序列来说,如果序列中每个时间点的输入 x 都有对应的输出 y,总损失就是所有时间步的损失之和.如果像 MNIST 中整个序列对应一个结果,则使用该结果与预测的误差.具体方法还是梯度下降,只是计算隐藏层与隐藏层之间权重的方法需要按时间往前推.

5. 参考

1) TensorFlow 学习笔记(8):基于 MNIST 数据的循环神经网络 RNN

https://segmentfault.com/a/1190000008346992

2) 解读 tensorflow 之 rnn

http://weibo.com/p/23041853dd83fd0102x6wc?sudaref=www.baidu.com&display=0&retcode=6102&sudaref=passport.weibo.com

3) TensorFlow 遇到的问题汇总(持续更新中......)

http://www.cnblogs.com/hunttown/p/6866586.html

4) 利用 Keras 下的 LSTM 进行情感分析

http://blog.csdn.net/william_2015/article/details/72978387

5) 基于 Theano 的深度学习 (Deep Learning) 框架 Keras 学习随笔 -02-Example

http://blog.csdn.net/niuwei22007/article/details/49053771

6) 循环神经网络 (RNN, Recurrent Neural Networks) 介绍

http://blog.csdn.net/heyongluoyao8/article/details/48636251

7) 零基础入门深度学习 (6) - 长短时记忆网络 (LSTM)

https://www.zybuluo.com/hanbingtao/note/581764