【翻译】Sklearn与TensorFlow机器学习实用指南 —— 第14章 循环神经网络(中)

浏览: 1753

作者:ApacheCN【翻译】  Python机器学习爱好者

Python爱好者社区专栏作者

GitHub:https://github.com/apachecn/hands_on_Ml_with_Sklearn_and_TF


处理变长输入序列

到目前为止,我们只使用固定大小的输入序列(全部正好两个步长)。 如果输入序列具有可变长度(例如,像句子)呢? 在这种情况下,你应该在调用dynamic_rnn()(或static_rnn())函数时设置sequence_length参数;它必须是一维张量,表示每个实例的输入序列的长度。 例如:

n_steps = 2
n_inputs = 3
n_neurons = 5

reset_graph()

X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)

seq_length = tf.placeholder(tf.int32, [None])
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32,
                                   sequence_length=seq_length)

例如,假设第二个输入序列只包含一个输入而不是两个输入。 为了适应输入张量X,必须填充零向量(因为输入张量的第二维是最长序列的大小,即 2)

X_batch = np.array([
       # step 0     step 1
       [[0, 1, 2], [9, 8, 7]], # instance 1
       [[3, 4, 5], [0, 0, 0]], # instance 2 (padded with zero vectors)
       [[6, 7, 8], [6, 5, 4]], # instance 3
       [[9, 0, 1], [3, 2, 1]], # instance 4
   ])
seq_length_batch = np.array([2, 1, 2, 2])

当然,你现在需要为两个占位符Xseq_length提供值:

with tf.Session() as sess:
   init.run()
   outputs_val, states_val = sess.run(
       [outputs, states], feed_dict={X: X_batch, seq_length: seq_length_batch})

现在,RNN 输出序列长度的每个时间步都会输出零向量(查看第二个时间步的第二个输出):

此外,状态张量包含每个单元的最终状态(不包括零向量):



处理变长输出序列

如果输出序列长度不一样呢? 如果事先知道每个序列的长度(例如,如果知道长度与输入序列的长度相同),那么可以按照上面所述设置sequence_length参数。 不幸的是,通常这是不可能的:例如,翻译后的句子的长度通常与输入句子的长度不同。 在这种情况下,最常见的解决方案是定义一个称为序列结束标记(EOS 标记)的特殊输出。 任何在 EOS 后面的输出应该被忽略(我们将在本章稍后讨论)。

好,现在你知道如何建立一个 RNN 网络(或者更准确地说是一个随着时间的推移而展开的 RNN 网络)。 但是你怎么训练呢?

训练 RNN

为了训练一个 RNN,诀窍是在时间上展开(就像我们刚刚做的那样),然后简单地使用常规反向传播(见图 14-5)。 这个策略被称为时间上的反向传播(BPTT)。


训练序列分类器

我们训练一个 RNN 来分类 MNIST 图像。 卷积神经网络将更适合于图像分类(见第 13 章),但这是一个你已经熟悉的简单例子。 我们将把每个图像视为 28 行 28 像素的序列(因为每个MNIST图像是28×28像素)。 我们将使用 150 个循环神经元的单元,再加上一个全连接层,其中包含连接到上一个时间步的输出的 10 个神经元(每个类一个),然后是一个 softmax 层(见图 14-6)。

建模阶段非常简单, 它和我们在第 10 章中建立的 MNIST 分类器几乎是一样的,只是展开的 RNN 替换了隐层。 注意,全连接层连接到状态张量,其仅包含 RNN 的最终状态(即,第 28 个输出)。 另请注意,y是目标类的占位符。

n_steps = 28
n_inputs = 28
n_neurons = 150
n_outputs = 10

learning_rate = 0.001

X = tf.placeholder(tf.float32, [None, n_steps, n_inputs])
y = tf.placeholder(tf.int32, [None])

basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)

logits = tf.layers.dense(states, n_outputs)
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,
                                                         logits=logits)
loss = tf.reduce_mean(xentropy)
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(loss)
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))

init = tf.global_variables_initializer()

现在让我们加载 MNIST 数据,并按照网络的预期方式将测试数据重塑为[batch_size, n_steps, n_inputs]。 我们之后会关注训练数据的重塑。

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/")
X_test = mnist.test.images.reshape((-1, n_steps, n_inputs))
y_test = mnist.test.labels

现在我们准备训练 RNN 了。 执行阶段与第 10 章中 MNIST 分类器的执行阶段完全相同,不同之处在于我们在将每个训练的批量提供给网络之前要重新调整。

batch_size = 150

with tf.Session() as sess:
   init.run()
   for epoch in range(n_epochs):
       for iteration in range(mnist.train.num_examples // batch_size):
           X_batch, y_batch = mnist.train.next_batch(batch_size)
           X_batch = X_batch.reshape((-1, n_steps, n_inputs))
           sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
       acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch})
       acc_test = accuracy.eval(feed_dict={X: X_test, y: y_test})
       print(epoch, "Train accuracy:", acc_train, "Test accuracy:", acc_test)

输出应该是这样的:

我们获得了超过 98% 的准确性 - 不错! 另外,通过调整超参数,使用 He 初始化初始化 RNN 权重,更长时间训练或添加一些正则化(例如,droupout),你肯定会获得更好的结果。

你可以通过将其构造代码包装在一个变量作用域内(例如,使用variable_scope("rnn", initializer = variance_scaling_initializer())来使用 He 初始化)来为 RNN 指定初始化器。


为预测时间序列而训练

现在让我们来看看如何处理时间序列,如股价,气温,脑电波模式等等。 在本节中,我们将训练一个 RNN 来预测生成的时间序列中的下一个值。 每个训练实例是从时间序列中随机选取的 20 个连续值的序列,目标序列与输入序列相同,除了向后移动一个时间步(参见图14-7)。

首先,我们来创建一个 RNN。 它将包含 100 个循环神经元,并且我们将在 20 个时间步骤上展开它,因为每个训练实例将是 20 个输入那么长。 每个输入将仅包含一个特征(在该时间的值)。 目标也是 20 个输入的序列,每个输入包含一个值。 代码与之前几乎相同:

一般来说,你将不只有一个输入功能。 例如,如果你试图预测股票价格,则你可能在每个时间步骤都会有许多其他输入功能,例如竞争股票的价格,分析师的评级或可能帮助系统进行预测的任何其他功能。

在每个时间步,我们现在有一个大小为 100 的输出向量。但是我们实际需要的是每个时间步的单个输出值。 最简单的解决方法是将单元包装在OutputProjectionWrapper中。 单元包装器就像一个普通的单元,代理每个方法调用一个底层单元,但是它也增加了一些功能。Out putProjectionWrapper在每个输出之上添加一个完全连接的线性神经元层(即没有任何激活函数)(但不影响单元状态)。 所有这些完全连接的层共享相同(可训练)的权重和偏差项。 结果 RNN 如图 14-8 所示。

包装单元是相当容易的。 让我们通过将BasicRNNCell包装到OutputProjectionWrapper中来调整前面的代码:

cell =tf.contrib.rnn.OutputProjectionWrapper(
   tf.contrib.rnn.BasicRNNCell(num_units=n_neurons,activation=tf.nn.relu),
   output_size=n_outputs)

到现在为止还挺好。 现在我们需要定义损失函数。 我们将使用均方误差(MSE),就像我们在之前的回归任务中所做的那样。 接下来,我们将像往常一样创建一个 Adam 优化器,训练操作和变量初始化操作:


生成 RNN

到现在为止,我们已经训练了一个能够预测未来时刻样本值的模型,正如前文所述,可以用模型来生成新的序列。

为模型提供 长度为n_steps的种子序列, 比如全零序列,然后通过模型预测下一时刻的值;把该预测值添加到种子序列的末尾,用最后面 长度为n_steps的序列做为新的种子序列,做下一次预测,以此类推生成预测序列。

如图 14-11 所示,这个过程产生的序列会跟原始时间序列相似。

如果你试图把约翰·列侬的唱片塞给一个 RNN 模型,看它能不能生成下一张《想象》专辑。

约翰·列侬 有一张专辑《Imagine》(1971),这里取其双关的意思

也许你需要一个更强大的 RNN 网络,它有更多的神经元,层数也更多。下面来探究一下深度 RNN。

深度 RNN

一个朴素的想法就是把一层层神经元堆叠起来,正如图 14-12 所示的那样,它呈现了一种深度 RNN。

为了用 TensorFlow 实现深度 RNN,可先创建一些神经单元,然后堆叠进MultiRNNCell

以下代码中创建了 3 个相同的神经单元(当然也可以用不同类别的、包含不同不同数量神经元的单元)

n_neurons = 100
n_layers = 3

basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
multi_layer_cell = tf.contrib.rnn.MultiRNNCell([basic_cell] * n_layers)
outputs, states = tf.nn.dynamic_rnn(multi_layer_cell, X, dtype=tf.float32)

这些代码就完成了这部分堆叠工作。status变量包含了每层的一个张量,这个张量就代表了该层神经单元的最终状态(维度为[batch_size, n_neurons])。

如果在创建MultiRNNCell时设置了state_is_tuple=False,那么status变量就变成了单个张量,它包含了每一层的状态,其在列的方向上进行了聚合,维度为[batch_size, n_layers*n_neurons]

注意在 TensorFlow 版本 0.11.0 之前,status是单个张量是默认设置。

Python爱好者社区历史文章大合集:

Python爱好者社区历史文章列表(每周append更新一次)

福利:文末扫码立刻关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“课程”即可获取:

小编的Python入门免费视频课程!!!

【最新免费微课】小编的Python快速上手matplotlib可视化库!!!

崔老师爬虫实战案例免费学习视频。

陈老师数据分析报告制作免费学习视频。

玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。

推荐 0
本文由 Python爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册