【翻译】Sklearn与TensorFlow机器学习实用指南 —— 第15章 自编码器(上)

浏览: 1628

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

Python爱好者社区专栏作者

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


自编码器是能够在无监督的情况下学习输入数据的有效表示(叫做编码)的人工神经网络(即,训练集是未标记)。这些编码通常具有比输入数据低得多的维度,使得自编码器对降维有用(参见第 8 章)。更重要的是,自编码器可以作为强大的特征检测器,它们可以用于无监督的深度神经网络预训练(正如我们在第 11 章中讨论过的)。最后,他们能够随机生成与训练数据非常相似的新数据;这被称为生成模型。例如,您可以在脸部图片上训练自编码器,然后可以生成新脸部。

令人惊讶的是,自编码器只需学习将输入复制到其输出即可工作。 这听起来像是一件小事,但我们会看到以各种方式约束网络可能会让它变得相当困难。例如,您可以限制内部表示的大小,或者可以向输入添加噪声并训练网络以恢复原始输入。这些约束防止自编码器将输入直接复制到输出,这迫使它学习表示数据的有效方法。 简言之,编码是自编码器在某些限制条件下尝试学习恒等函数的副产品。

在本章中,我们将更深入地解释自编码器如何工作,可以施加什么类型的约束以及如何使用 TensorFlow 实现它们,无论是用来降维,特征提取,无监督预训练还是作为生成式模型。

有效的数据表示

您发现以下哪一个数字序列最容易记忆?

  • 40, 27, 25, 36, 81, 57, 10, 73, 19, 68

  • 50, 25, 76, 38, 19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20

乍一看,第一个序列似乎应该更容易,因为它要短得多。 但是,如果仔细观察第二个序列,则可能会注意到它遵循两条简单规则:偶数是前面数的一半,奇数是前面数的三倍加一(这是一个着名的序列,称为雹石序列)。一旦你注意到这种模式,第二个序列比第一个更容易记忆,因为你只需要记住两个规则,第一个数字和序列的长度。 请注意,如果您可以快速轻松地记住非常长的序列,则您不会在意第二个序列中存在的模式。 你只需要了解每一个数字,就是这样。 事实上,很难记住长序列,因此识别模式非常有用,并且希望能够澄清为什么在训练过程中限制自编码器会促使它发现并利用数据中的模式。

记忆,感知和模式匹配之间的关系在 20 世纪 70 年代早期由 William Chase 和 Herbert Simon 着名研究。 他们观察到,专家棋手能够通过观看棋盘5秒钟来记忆所有棋子的位置,这是大多数人认为不可能完成的任务。 然而,只有当这些棋子被放置在现实位置(来自实际比赛)时才是这种情况,而不是随机放置棋子。 国际象棋专家没有比你更好的记忆,他们只是更容易看到国际象棋模式,这要归功于他们对比赛的经验。 注意模式有助于他们有效地存储信息。

就像这个记忆实验中的象棋棋手一样,一个自编码器会查看输入信息,将它们转换为高效的内部表示形式,然后吐出一些(希望)看起来非常接近输入的东西。 自编码器总是由两部分组成:将输入转换为内部表示的编码器(或识别网络),然后是将内部表示转换为输出的解码器(或生成网络)(见图 15-1)。

如您所见,自编码器通常具有与多层感知器(MLP,请参阅第 10 章)相同的体系结构,但输出层中的神经元数量必须等于输入数量。 在这个例子中,只有一个由两个神经元(编码器)组成的隐藏层和一个由三个神经元(解码器)组成的输出层。 由于自编码器试图重构输入,所以输出通常被称为重建,并且损失函数包含重建损失,当重建与输入不同时,重建损失会对模型进行惩罚。

由于内部表示具有比输入数据更低的维度(它是 2D 而不是 3D),所以自编码器被认为是不完整的。 不完整的自编码器不能简单地将其输入复制到编码,但它必须找到一种方法来输出其输入的副本。 它被迫学习输入数据中最重要的特征(并删除不重要的特征)。

我们来看看如何实现一个非常简单的不完整的自编码器,以降低维度。

用不完整的线性自编码器执行 PCA

如果自编码器仅使用线性激活并且损失函数是均方误差(MSE),则可以显示它最终执行主成分分析(参见第 8 章)。

以下代码构建了一个简单的线性自编码器,以在 3D 数据集上执行 PCA,并将其投影到 2D:

import tensorflow as tf
from tensorflow.contrib.layers import fully_connected
n_inputs = 3 # 3D inputs
n_hidden = 2 # 2D codings
n_outputs = n_inputs
learning_rate = 0.01
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
hidden = fully_connected(X, n_hidden, activation_fn=None)
outputs = fully_connected(hidden, n_outputs, activation_fn=None)
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(reconstruction_loss)
init = tf.global_variables_initializer()

这段代码与我们在过去章节中建立的所有 MLP 没有什么不同。 需要注意的两件事是:

  • 输出的数量等于输入的数量。

  • 为了执行简单的 PCA,我们设置activation_fn = None(即,所有神经元都是线性的)

而损失函数是 MSE。 我们很快会看到更复杂的自编码器。

现在让我们加载数据集,在训练集上训练模型,并使用它来对测试集进行编码(即将其投影到 2D):

X_train, X_test = [...] # load the dataset
n_iterations = 1000
codings = hidden # the output of the hidden layer provides the codings
with tf.Session() as sess:
   init.run()
   for iteration in range(n_iterations):
       training_op.run(feed_dict={X: X_train}) # no labels (unsupervised)
   codings_val = codings.eval(feed_dict={X: X_test})

图 15-2 显示了原始 3D 数据集(左侧)和自编码器隐藏层的输出(即编码层,右侧)。 正如您所看到的,自编码器找到了将数据投影到数据上的最佳二维平面,保留了数据的尽可能多的差异(就像 PCA 一样)。

栈式自编码器(SAE)

就像我们讨论过的其他神经网络一样,自编码器可以有多个隐藏层。 在这种情况下,它们被称为栈式自编码器(或深度自编码器)。 添加更多层有助于自编码器了解更复杂的编码。 但是,必须注意不要让自编码器功能太强大。 设想一个编码器非常强大,只需学习将每个输入映射到一个任意数字(并且解码器学习反向映射)即可。 很明显,这样的自编码器将完美地重构训练数据,但它不会在过程中学习到任何有用的数据表示(并且它不可能很好地推广到新的实例)。

栈式自编码器的架构关于中央隐藏层(编码层)通常是对称的。 简单来说,它看起来像一个三明治。 例如,一个用于 MNIST 的自编码器(在第 3 章中介绍)可能有 784 个输入,其次是一个隐藏层,有 300 个神经元,然后是一个中央隐藏层,有 150 个神经元,然后是另一个隐藏层,有 300 个神经元,输出层有 784 神经元。 这个栈式自编码器如图 15-3 所示。

TensorFlow实现

您可以像常规深度 MLP 一样实现栈式自编码器。 特别是,我们在第 11 章中用于训练深度网络的技术也可以应用。例如,下面的代码使用 He 初始化,ELU 激活函数和 l2 正则化为 MNIST 构建一个栈式自编码器。 代码应该看起来很熟悉,除了没有标签(没有y):

n_inputs = 28 * 28 # for MNIST
n_hidden1 = 300
n_hidden2 = 150 # codings
n_hidden3 = n_hidden1
n_outputs = n_inputs

learning_rate = 0.01
l2_reg = 0.001

X = tf.placeholder(tf.float32, shape=[None, n_inputs])
with tf.contrib.framework.arg_scope(
       [fully_connected],
       activation_fn=tf.nn.elu,
       weights_initializer=tf.contrib.layers.variance_scaling_initializer(),
       weights_regularizer=tf.contrib.layers.l2_regularizer(l2_reg)):
   hidden1 = fully_connected(X, n_hidden1)
   hidden2 = fully_connected(hidden1, n_hidden2) # codings
   hidden3 = fully_connected(hidden2, n_hidden3)
   outputs = fully_connected(hidden3, n_outputs, activation_fn=None)
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE

reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
loss = tf.add_n([reconstruction_loss] + reg_losses)

optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(loss)

init = tf.global_variables_initializer()

然后可以正常训练模型。 请注意,数字标签(y_batch)未使用:

n_epochs = 5
batch_size = 150
with tf.Session() as sess:
   init.run()
   for epoch in range(n_epochs):
       n_batches = mnist.train.num_examples // batch_size
       for iteration in range(n_batches):
           X_batch, y_batch = mnist.train.next_batch(batch_size)
           sess.run(training_op, feed_dict={X: X_batch})


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

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

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

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

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

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

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

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

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

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

0 个评论

要回复文章请先登录注册