深度学习算法(第31期)----变分自编码器及其实现

2019-10-17 15:16:06 浏览数 (1)

上期我们一起学习了深度学习中的降噪自编码器和稀疏自编码器及其实现的相关知识,

深度学习算法(第30期)----降噪自编码器和稀疏自编码器及其实现 今天我们一起学一下变分自编码器及其实现方面的知识。

变分自编码器

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

它和我们之前讨论的降噪自编码器,稀疏自编码器有些不同,特别是:

  • 它们是概率自编码器,意味着即使在训练之后,它们的输出部分也是概率上的(相对于降噪自编码器仅在训练过程中增加随机性的噪声)。
  • 最重要的是,它们是生成式的自编码器,这意味着它们可以产生看起来像从训练集中采样的新实例。

这些特性使得它跟RBMs(Restricted Boltzmann Machine)有些相似,但是变分自编码器更容易训练,并且取样过程更快(在 RBM 之前,您需要等待网络稳定在“热平衡”之后才能进行取样一个新的实例)。 现在我们来一起看下,变分自编码器是如何工作的,如下图:

其中左侧显示了一个变分自编码器,基本结构跟之前学的降噪自编码器和稀疏自编码器差不多,都是编码器后面跟着解码器,在这个例子中,编码器和解码器都有两个隐藏层。不同的是:编码器并不是直接为给定的输入生成编码 ,而是编码器产生平均编码μ和标准差σ。然后以平均值μ和标准差σ的高斯分布随机采样实际编码。之后,解码器正常解码采样的编码。

该右侧部分显示了一个训练实例如何通过此自编码器。首先,编码器产生μ和σ,随后对编码进行随机采样(注意它不是完全位于μ处),最后对编码进行解码,最终的输出与训练实例类似。

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

接下来,我们看一下损失函数,它有两部分组成:

  • 第一部分是通常的重建损失,迫使自编码器重构其输入(我们可以使用交叉熵来解决这个问题,如上期讨论的一样)。
  • 第二部分是隐藏损失,推动自编码器使编码看起来像是从简单的高斯分布中采样,这里我们使用目标分布(高斯分布)与编码实际分布之间的KL散度。 从数学角度来讲,这个比之前的复杂一点,特别是因为高斯噪声,它限制了可以传输到编码层的信息量(从而推动自编码器学习有用的特征)。幸运的是,这些损失方程可以简化为下面的代码:
代码语言:javascript复制
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/γ)。 这使得编码器可以更轻松地捕获不同比例的σ,从而有助于加快收敛速度。这样隐藏损失会变得更简单一些:

代码语言:javascript复制
latent_loss = 0.5 * tf.reduce_sum(
    tf.exp(hidden3_gamma)   tf.square(hidden3_mean) - 1 - hidden3_gamma)

下面代码创建了一个上图左侧先显示的变分自编码器,用log(σ^2)变体:

代码语言:javascript复制
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()
数字生成

接下来,我们用这变分自编码器生成一些看起来像手写数字的图片,我们所需要做的就是训练模型,然后从高斯分布中随机采样编码,并进行解码:

代码语言:javascript复制
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})

就这么简单,现在我们看下自编码器生成的手写数字长什么样子:

代码语言:javascript复制
for iteration in range(n_digits):
    plt.subplot(n_digits, 10, iteration   1)
    plot_image(outputs_val[iteration])

生成手写数字如下:

从上图看,感觉这些数字还挺像回事儿,虽然有一些看起来比较有“创意”,但是在这种自编码器上已经很不错了,而且这只是简单的训练,训练时间少于一小时,如果给更多的训练时间的话,这些数字应该看起来更完美。

其他自编码器

监督学习在图像识别,语音识别,文本翻译等方面取得的惊人成就在某种程度上掩盖了无监督学习发展,但它实际上也正在蓬勃发展。自编码器和其他无监督学习算法的新体系结构也在不断的被创造,篇幅原因,我们并不能全面的介绍它们。 以下是几种常见类型的自编码器的简要说明:

  • 压缩自编码器(CAE),自编码器在训练过程中受到约束,因此与输入有关的编码的导数很小。 换句话说,两个类似的输入必须具有相似的编码。
  • 栈式卷积自编码器(SCAE),学习通过重构卷积层的图像来提取视觉特征的自编码器。
  • 生成随机网络(GSN),降噪自编码器的生成版,增加了生成数据的能力。
  • 赢家通吃(WTA)的自编码,训练期间,在计算编码层中所有神经元的激活之后,只保留训练batch上每个神经元的前k%激活,其余部分设为零。当然,这导致稀疏编码。 而且,可以使用类似的WTA方法来产生稀疏卷积自编码器。
  • 对抗自编码器(AAE),一个网络被训练来重现它的输入,同时另一个网络被训练去找到第一个网络不能正确重建的输入。这推动了第一个自编码器学习健壮的编码。

好了,至此,今天我们简单学习了变分编码器的原理及其tensorflow实现的相关知识,希望有些收获,下期我们将学习强化学习的相关知识,欢迎留言或进社区共同交流。

0 人点赞