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

浏览: 1669

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

Python爱好者社区专栏作者

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


稀疏自编码器

通常良好特征提取的另一种约束是稀疏性:通过向损失函数添加适当的项,自编码器被推动以减少编码层中活动神经元的数量。 例如,它可能被推到编码层中平均只有 5% 的显着活跃的神经元。 这迫使自编码器将每个输入表示为少量激活的组合。 因此,编码层中的每个神经元通常都会代表一个有用的特征(如果您每个月只能说几个字,您可能会试着让它们值得一听)。

为了支持稀疏模型,我们必须首先在每次训练迭代中测量编码层的实际稀疏度。 我们通过计算整个训练批次中编码层中每个神经元的平均激活来实现。 批量大小不能太小,否则平均数不准确。

一旦我们对每个神经元进行平均激活,我们希望通过向损失函数添加稀疏损失来惩罚太活跃的神经元。 例如,如果我们测量一个神经元的平均激活值为 0.3,但目标稀疏度为 0.1,那么它必须受到惩罚才能激活更少。 一种方法可以简单地将平方误差(0.3-0.1)^2添加到损失函数中,但实际上更好的方法是使用 Kullback-Leibler 散度(在第 4 章中简要讨论),其具有比均方误差更强的梯度,如图 15-10 所示。


给定两个离散的概率分布PQ,这些分布之间的 KL 散度,记为Dkl(P || Q),可以使用公式 15-1 计算。

在我们的例子中,我们想要测量编码层中的神经元将激活的目标概率p与实际概率q(即,训练批次上的平均激活)之间的差异。 所以KL散度简化为公式 15-2。

一旦我们已经计算了编码层中每个神经元的稀疏损失,我们就总结这些损失,并将结果添加到损失函数中。 为了控制稀疏损失和重构损失的相对重要性,我们可以用稀疏权重超参数乘以稀疏损失。 如果这个权重太高,模型会紧贴目标稀疏度,但它可能无法正确重建输入,导致模型无用。 相反,如果它太低,模型将大多忽略稀疏目标,它不会学习任何有趣的功能。

TensorFlow 实现

我们现在拥有了使用 TensorFlow 实现稀疏自编码器所需的全部功能:

def kl_divergence(p, q):
   return p * tf.log(p / q) + (1 - p) * tf.log((1 - p) / (1 - q))

learning_rate = 0.01
sparsity_target = 0.1
sparsity_weight = 0.2

[...] # Build a normal autoencoder (in this example the coding layer is hidden1)

optimizer = tf.train.AdamOptimizer(learning_rate)

hidden1_mean = tf.reduce_mean(hidden1, axis=0) # batch mean
sparsity_loss = tf.reduce_sum(kl_divergence(sparsity_target, hidden1_mean))
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X)) # MSE
loss = reconstruction_loss + sparsity_weight * sparsity_loss
training_op = optimizer.minimize(loss)

一个重要的细节是编码层的激活必须介于 0 和 1 之间(但不等于 0 或 1),否则 KL 散度将返回NaN(非数字)。 一个简单的解决方案是对编码层使用逻辑激活功能:

hidden1 = tf.nn.sigmoid(tf.matmul(X, weights1) + biases1)

一个简单的技巧可以加速收敛:不是使用 MSE,我们可以选择一个具有较大梯度的重建损失。 交叉熵通常是一个不错的选择。 要使用它,我们必须对输入进行规范化处理,使它们的取值范围为 0 到 1,并在输出层中使用逻辑激活函数,以便输出也取值为 0 到 1。TensorFlow 的sigmoid_cross_entropy_with_logits()函数负责 有效地将 logistic(sigmoid)激活函数应用于输出并计算交叉熵:

[...]
logits = tf.matmul(hidden1, weights2) + biases2)
   outputs = tf.nn.sigmoid(logits)

reconstruction_loss = tf.reduce_sum(
   tf.nn.sigmoid_cross_entropy_with_logits(labels=X, logits=logits))

请注意,训练期间不需要输出操作(我们仅在我们想要查看重建时才使用它)。

变分自编码器(VAE)

Diederik Kingma 和 Max Welling 于 2014 年推出了另一类重要的自编码器,并迅速成为最受欢迎的自编码器类型之一:变分自编码器。

它们与我们迄今为止讨论的所有自编码器完全不同,特别是:

  • 它们是概率自编码器,意味着即使在训练之后,它们的输出部分也是偶然确定的(相对于仅在训练过程中使用随机性的自编码器的去噪)。

  • 最重要的是,它们是生成自编码器,这意味着它们可以生成看起来像从训练集中采样的新实例。

这两个属性使它们与 RBM 非常相似(见附录 E),但它们更容易训练,并且取样过程更快(在 RBM 之前,您需要等待网络稳定在“热平衡”之后才能进行取样一个新的实例)

我们来看看他们是如何工作的。 图 15-11(左)显示了一个变分自编码器。 当然,您可以认识到所有自编码器的基本结构,编码器后跟解码器(在本例中,它们都有两个隐藏层),但有一个转折点:不是直接为给定的输入生成编码 ,编码器产生平均编码μ和标准差σ。 然后从平均值μ和标准差σ的高斯分布随机采样实际编码。 之后,解码器正常解码采样的编码。 该图的右侧部分显示了一个训练实例通过此自编码器。 首先,编码器产生μσ,随后对编码进行随机采样(注意它不是完全位于μ处),最后对编码进行解码,最终的输出与训练实例类似。


从图中可以看出,尽管输入可能具有非常复杂的分布,但变分自编码器倾向于产生编码,看起来好像它们是从简单的高斯分布采样的:在训练期间,损失函数(将在下面讨论)推动 编码在编码空间(也称为潜在空间)内逐渐迁移以占据看起来像高斯点集成的云的大致(超)球形区域。 一个重要的结果是,在训练了一个变分自编码器之后,你可以很容易地生成一个新的实例:只需从高斯分布中抽取一个随机编码,对它进行解码就可以了!

那么让我们看看损失函数。 它由两部分组成。 首先是通常的重建损失,推动自编码器重现其输入(我们可以使用交叉熵来解决这个问题,如前所述)。 第二种是潜在的损失,推动自编码器使编码看起来像是从简单的高斯分布中采样,为此我们使用目标分布(高斯分布)与编码实际分布之间的 KL 散度。 数学比以前复杂一点,特别是因为高斯噪声,它限制了可以传输到编码层的信息量(从而推动自编码器学习有用的特征)。 幸运的是,这些方程简化为下面的潜在损失代码:

eps = 1e-10 # smoothing term to avoid computing log(0) which is NaN
latent_loss = 0.5 * tf.reduce_sum(
   tf.square(hidden3_sigma) + tf.square(hidden3_mean)
   - 1 - tf.log(eps + tf.square(hidden3_sigma)))

一种常见的变体是训练编码器输出γ= log(σ^2)而不是σ。 只要我们需要σ,我们就可以计算σ= exp(2/γ)。 这使得编码器可以更轻松地捕获不同比例的σ,从而有助于加快收敛速度。 潜在损失结束会变得更简单一些:

latent_loss = 0.5 * tf.reduce_sum(
   tf.exp(hidden3_gamma) + tf.square(hidden3_mean) - 1 - hidden3_gamma)

以下代码使用log(σ^2)变体构建图 15-11(左)所示的变分自编码器:

n_inputs = 28 * 28 # for MNIST
n_hidden1 = 500
n_hidden2 = 500
n_hidden3 = 20 # codings
n_hidden4 = n_hidden2
n_hidden5 = n_hidden1
n_outputs = n_inputs

learning_rate = 0.001

with tf.contrib.framework.arg_scope(    
       [fully_connected],
       activation_fn=tf.nn.elu,
       weights_initializer=tf.contrib.layers.variance_scaling_initializer()):
   X = tf.placeholder(tf.float32, [None, n_inputs])
   hidden1 = fully_connected(X, n_hidden1)
   hidden2 = fully_connected(hidden1, n_hidden2)
   hidden3_mean = fully_connected(hidden2, n_hidden3, activation_fn=None)
   hidden3_gamma = fully_connected(hidden2, n_hidden3, activation_fn=None)
   hidden3_sigma = tf.exp(0.5 * hidden3_gamma)
   noise = tf.random_normal(tf.shape(hidden3_sigma), dtype=tf.float32)
   hidden3 = hidden3_mean + hidden3_sigma * noise
   hidden4 = fully_connected(hidden3, n_hidden4)
   hidden5 = fully_connected(hidden4, n_hidden5)
   logits = fully_connected(hidden5, n_outputs, activation_fn=None)
   outputs = tf.sigmoid(logits)
reconstruction_loss = tf.reduce_sum(
   tf.nn.sigmoid_cross_entropy_with_logits(labels=X, logits=logits))
latent_loss = 0.5 * tf.reduce_sum(
   tf.exp(hidden3_gamma) + tf.square(hidden3_mean) - 1 - hidden3_gamma)
cost = reconstruction_loss + latent_loss

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

init = tf.global_variables_initializer()

生成数字

现在让我们使用这个变分自编码器来生成看起来像手写数字的图像。 我们所需要做的就是训练模型,然后从高斯分布中对随机编码进行采样并对它们进行解码。

import numpy as np
n_digits = 60
n_epochs = 50
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})

   codings_rnd = np.random.normal(size=[n_digits, n_hidden3])
   outputs_val = outputs.eval(feed_dict={hidden3: codings_rnd})

现在我们可以看到由autoencoder生成的“手写”数字是什么样的(参见图15-12):

for iteration in range(n_digits):
   plt.subplot(n_digits, 10, iteration + 1)
   plot_image(outputs_val[iteration])



其他自编码器

监督式学习在图像识别,语音识别,文本翻译等方面取得的惊人成就在某种程度上掩盖了无监督学习的局面,但它实际上正在蓬勃发展。 自编码器和其他无监督学习算法的新体系结构定期发明,以至于我们无法在本书中全面介绍它们。 以下是您可能想要查看的几种类型的自编码器的简要说明(绝非详尽无遗):

压缩自编码器(CAE)

自编码器在训练过程中受到约束,因此与输入有关的编码的导数很小。 换句话说,两个类似的输入必须具有相似的编码。

栈式卷积自编码器(SCAE)

学习通过重构通过卷积层处理的图像来提取视觉特征的自编码器。

生成随机网络(GSN)

消除自编码器的泛化,增加了生成数据的能力。

赢家通吃(WTA)的自编码

在训练期间,在计算编码层中所有神经元的激活之后,只保留训练批次上每个神经元的前 k% 激活,其余部分设为零。 自然这导致稀疏的编码。 而且,可以使用类似的 WTA 方法来产生稀疏卷积自编码器。

对抗自编码器(AAE)

一个网络被训练来重现它的输入,同时另一个网络被训练去找到第一个网络不能正确重建的输入。 这推动了第一个自编码器学习健壮的编码。

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

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

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

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

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

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

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

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

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

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

0 个评论

要回复文章请先登录注册