![3c36fcff8a20adc7a294e6ab7c2b0c9f.png](https://i-blog.csdnimg.cn/blog_migrate/43147c01cf9e7ca5312fcc5b46401a2a.jpeg)
说明:本文依据《Sklearn 与 TensorFlow 机器学习实用指南》完成,所有版权和解释权均归作者和翻译成员所有,我只是搬运和做注解。
进入第二部分深度学习
第十四章循环神经网络
循环神经网络可以分析时间序列数据,诸如股票价格,并告诉你什么时候买入和卖出。在自动驾驶系统中,他们可以预测行车轨迹,避免发生交通意外。
循环神经网络可以在任意长度的序列上工作,而不是之前讨论的只能在固定长度的输入上工作的网络。
举个例子,它们可以把语句,文件,以及语音范本作为输入,使得它们在诸如自动翻译,语音到文本或者情感分析(例如,读取电影评论并提取评论者关于该电影的感觉)的自然语言处理系统中极为有用。
另外,循环神经网络的预测能力使得它们具备令人惊讶的创造力。
可以要求它们去预测一段旋律的下几个音符,随机选取这些音符的其中之一并演奏它。然后要求网络给出接下来最可能的音符,演奏它,如此周而复始。
同样,循环神经网络可以生成语句,图像标注等。
在本章中,教程介绍以下几点
- 循环神经网络背后的基本概念
- 循环神经网络所面临的主要问题(在第11章中讨论的消失/爆炸的梯度),广泛用于反抗这些问题的方法:LSTM 和 GRU cell(单元)。
- 展示如何用 TensorFlow 实现循环神经网络。最终我们将看看及其翻译系统的架构。
2.时间上的静态展开
static_rnn()函数通过链接单元创建一个展开的RNN网络。在这里构建一个与上一节完全相同的模型。
创建输入占位符X0和X1
n_inputs = 3n_neurons = 5X0 = tf.placeholder(tf.float32, [None, n_inputs])X1 = tf.placeholder(tf.float32, [None, n_inputs])
创建BasicRNNCell,这个函数创建单元的副本来构建展开后的RNN(前面所说每一个单元都有若干时间步长,展开)。
需要注意的是Tensorflow更新的问题,教程中使用的tf.contrib.rnn.BasicRNNCell已经变成了tf.nn.rnn_cell.BasicRNNCell,而我在运行的时候又变了,放在了Keras下的SimpleRNNCell。
然后调用static_rnn(),向它提供单元工厂和输入张量,并告诉它输入的数据类型(用来创建初始状态矩阵,默认情况下是全零)。
static_rnn(basic_cell, [X0, X1], dtype=tf.float32),可以看到static_run()构建了单元的两个副本,每个单元包含有5个循环神经元的循环层。
static_rnn()返回两个对象,一个是包含每个时间步的输出张量的 Python 列表。 一个是包含网络最终状态的张量
# basic_cell = tf.nn.rnn_cell.BasicRNNCell(num_units=n_neurons)basic_cell = tf.keras.layers.SimpleRNNCell(units=n_neurons)output_seqs, states = tf.nn.static_rnn(basic_cell, [X0, X1], dtype=tf.float32)Y0, Y1 = output_seqs
如果有 50 个时间步长,则不得不定义 50 个输入占位符和 50 个输出张量。
简化一下。下面的代码再次构建相同的 RNN,但是这次它需要一个形状为[None,n_steps,n_inputs]的单个输入占位符,其中第一个维度是最小批量大小。
提取每个时间步的输入序列列表。 X_seqs是形状为n_steps的 Python 列表,包含形状为[None,n_inputs]的张量,其中第一个维度同样是最小批量大小。
使用transpose()函数交换前两个维度。
使用unstack()函数沿第一维(即每个时间步的一个张量)提取张量的 Python 列表。
最后,使用stack()函数将所有输出张量合并成一个张量,然后我们交换前两个维度得到最终输出张量,形状为[None, n_steps,n_neurons]。
X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])X_seqs = tf.unstack(tf.transpose(X, perm=[1, 0, 2]))basic_cell=tf.keras.layers.SimpleRNNCell(units=n_neurons)output_seqs, states=tf.nn.static_rnn(basic_cell,X_seqs,dtype=tf.float32)outputs=tf.transpose(tf.stack(output_seqs),perm=[1,0,2])
通过提供一个包含所有小批量序列的张量来运行网络
init = tf.global_variables_initializer()X_batch = np.array([ # t = 0 t = 1 [[0, 1, 2], [9, 8, 7]], # instance 1 [[3, 4, 5], [0, 0, 0]], # instance 2 [[6, 7, 8], [6, 5, 4]], # instance 3 [[9, 0, 1], [3, 2, 1]], # instance 4 ])with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch})print(outputs_val)[[[-0.71007144 -0.5149771 -0.4025803 0.84779686 -0.695326 ] [-0.99820256 -0.9999348 0.9861707 0.90765584 -0.9861588 ]] [[-0.9637711 -0.98081887 0.05506388 0.9819332 -0.96890193] [-0.906498 -0.2699622 0.8120271 -0.3782449 0.07480869]] [[-0.99599314 -0.9994144 0.49068618 0.9979843 -0.99722755] [-0.9924605 -0.9945629 0.99294806 0.62711215 -0.91487855]] [[ 0.9867179 -0.9999985 0.9999859 -0.95983976 0.9942305 ] [ 0.13565563 0.19667651 0.3582377 0.29364806 -0.9558775 ]]]
看起来挺美好的,因为节省了很多代码编写,但是教程认为这种静态展开的方式仍然会建立一个每个时间步包含一个单元的图。 如果有50个时间步,这个图看起来会非常难看。如果使用大图,在反向传播期间(特别是在 GPU 内存有限的情况下),你甚至可能会发生内存不足(OOM)错误,因为它必须在正向传递期间存储所有张量值,以便可以使用它们在反向传播期间计算梯度。
这个问题就需要使用动态展开。
3.时间上的动态展开
Tensorflow提供了dynamic_rnn()函数,使用while_loop()操作,在单元上运行适当次数,反向传播期间将GPU内存交换到CPU内存,避免了内存不足的错误。
动态展开可以在每个时间步接受和输出所有单个张量,不需要堆叠、拆散、转置。
if __name__ == '__main__': n_inputs = 3 n_neurons = 5 n_steps = 2 X=tf.placeholder(tf.float32,[None, n_steps, n_inputs]) basic_cell=tf.keras.layers.SimpleRNNCell(units=n_neurons) outputs, states=tf.nn.dynamic_rnn(basic_cell,X,dtype=tf.float32) init=tf.global_variables_initializer() X_batch=np.array([ [[0, 1, 2], [9, 8, 7]], # instance 1 [[3, 4, 5], [0, 0, 0]], # instance 2 [[6, 7, 8], [6, 5, 4]], # instance 3 [[9, 0, 1], [3, 2, 1]], # instance 4 ]) with tf.Session() as sess: init.run() outputs_val=outputs.eval(feed_dict={X:X_batch}) print(outputs_val)[[[-0.71723616 0.71103394 -0.9259252 0.35195237 0.97944766] [-1. 1. -0.9999448 0.9999934 1. ]] [[-0.99871033 0.99937254 -0.9987622 0.9802172 0.9999999 ] [-0.88610965 0.81995314 -0.6732962 0.7383372 -0.2807465 ]] [[-0.99999505 0.9999989 -0.99998003 0.9995836 1. ] [-0.9999892 0.999996 -0.9973973 0.99972314 1. ]] [[-0.9825746 0.9999997 0.99574494 0.99944705 0.9999318 ] [-0.96585083 0.9917968 -0.84697026 0.99012244 0.9625891 ]]]
在反向传播期间,while_loop()操作会执行相应的步骤:在正向传递期间存储每次迭代的张量值,以便在反向传递期间使用它们来计算梯度。