深度学习 _ 循环神经网络 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