一次训练一个自编码器

我们不是一次完成整个栈式自编码器的训练,而是一次训练一个浅自编码器,然后将所有这些自编码器堆叠到一个栈式自编码器(因此名称)中,通常要快得多,如图 15-4 所示。 这对于非常深的自编码器特别有用。

一次训练一个自编码器 - 图1

在训练的第一阶段,第一个自编码器学习重构输入。 在第二阶段,第二个自编码器学习重构第一个自编码器隐藏层的输出。 最后,您只需使用所有这些自编码器来构建一个大三明治,如图 15-4 所示(即,您首先将每个自编码器的隐藏层,然后按相反顺序堆叠输出层)。 这给你最后的栈式自编码器。 您可以用这种方式轻松地训练更多的自编码器,构建一个非常深的栈式自编码器。

为了实现这种多阶段训练算法,最简单的方法是对每个阶段使用不同的 TensorFlow 图。 训练完一个自编码器后,您只需通过它运行训练集并捕获隐藏层的输出。 这个输出作为下一个自编码器的训练集。 一旦所有自编码器都以这种方式进行了训练,您只需复制每个自编码器的权重和偏置,然后使用它们来构建堆叠的自编码器。 实现这种方法非常简单,所以我们不在这里详细说明,但请查阅 Jupyter notebooks 中的代码作为示例。

另一种方法是使用包含整个栈式自编码器的单个图,以及执行每个训练阶段的一些额外操作,如图 15-5 所示。

一次训练一个自编码器 - 图2

这值得解释一下:

  • 图中的中央列是完整的栈式自编码器。这部分可以在训练后使用。

  • 左列是运行第一阶段训练所需的一系列操作。它创建一个绕过隐藏层 2 和 3 的输出层。该输出层与堆叠的自编码器的输出层共享相同的权重和偏置。此外还有旨在使输出尽可能接近输入的训练操作。因此,该阶段将训练隐藏层1和输出层(即,第一自编码器)的权重和偏置。

  • 图中的右列是运行第二阶段训练所需的一组操作。它增加了训练操作,目的是使隐藏层 3 的输出尽可能接近隐藏层 1 的输出。注意,我们必须在运行阶段 2 时冻结隐藏层 1。此阶段将训练隐藏层 2 和 3 的权重和偏置(即第二自编码器)。

TensorFlow 代码如下所示:

  1. [...] # Build the whole stacked autoencoder normally.
  2. # In this example, the weights are not tied.
  3. optimizer = tf.train.AdamOptimizer(learning_rate)
  4. with tf.name_scope("phase1"):
  5. phase1_outputs = tf.matmul(hidden1, weights4) + biases4
  6. phase1_reconstruction_loss = tf.reduce_mean(tf.square(phase1_outputs - X))
  7. phase1_reg_loss = regularizer(weights1) + regularizer(weights4)
  8. phase1_loss = phase1_reconstruction_loss + phase1_reg_loss
  9. phase1_training_op = optimizer.minimize(phase1_loss)
  10. with tf.name_scope("phase2"):
  11. phase2_reconstruction_loss = tf.reduce_mean(tf.square(hidden3 - hidden1))
  12. phase2_reg_loss = regularizer(weights2) + regularizer(weights3)
  13. phase2_loss = phase2_reconstruction_loss + phase2_reg_loss
  14. train_vars = [weights2, biases2, weights3, biases3]
  15. phase2_training_op = optimizer.minimize(phase2_loss, var_list=train_vars)

第一阶段比较简单:我们只创建一个跳过隐藏层 2 和 3 的输出层,然后构建训练操作以最小化输出和输入之间的距离(加上一些正则化)。

第二阶段只是增加了将隐藏层 3 和隐藏层 1 的输出之间的距离最小化的操作(还有一些正则化)。 最重要的是,我们向minim()方法提供可训练变量的列表,确保省略权重 1 和偏差 1;这有效地冻结了阶段 2 期间的隐藏层 1。

在执行阶段,你需要做的就是为阶段 1 一些迭代进行训练操作,然后阶段 2 训练运行更多的迭代。

由于隐藏层 1 在阶段 2 期间被冻结,所以对于任何给定的训练实例其输出将总是相同的。 为了避免在每个时期重新计算隐藏层1的输出,您可以在阶段 1 结束时为整个训练集计算它,然后直接在阶段 2 中输入隐藏层 1 的缓存输出。这可以得到一个不错的性能上的提升。