什么!你竟然还不懂变分自编码机?这个16岁的OpenAI天才实习生讲得可透彻了

3,423 阅读7分钟


编译 | AI科技大本营(rgznai100)
参与 | 史天,胡永波,鸽子


我的天啊,这些少年们,让身为多年程序猿,却还在吃草的我们,情何以堪,情何以堪...AI哥也只剩下最后一点自信了,那就是...



说点正事......那个......

你知道啥叫变分自编码机吗?

你知道为啥你需要懂变分自编码机呢?

你知道如何以最快的速度搞懂变分自编码机吗?

啥也不说了,还是让这位16岁的天才少年讲给你听吧。

Kevin Frans是加州Palo Alto的一名高中生,他年级轻轻便已写出两篇论文,并且对生成式模型颇有研究。他的成名作是一个名为deepcolor的项目。

现在,他正在以实习生的身份在OpenAI做强化学习方面的研究。

本文是Kevin Frans用自己写的实例来讲解变分自编码机,对于自编码机与变分自编码机的工作原理、使用变分自编码机时的优缺点,他都做了特别细心的解释,是了解变分自编码机不可多得的一篇好文。

下面,我们就来看看这个高中生的实力到底有几何:


我曾经讲解过一次生成式对抗网络(GAN),谈的是用它来生成逼真图像的一个简单例子。

但这中间有些问题,即单纯使用GAN存在两大不利因素。

首先,这里的图像生成自某些随机的噪点。如果你想生成的是一张特定细节的图像,除了遍历初始噪点的整个分布范围,你没有别的办法来找出它们的值。

其次,生成式对抗模型只能区分出图像的“真”、“假”。你没有办法强制它所生成猫图必须看起来像猫。这就造成了一个问题,就是它所生成的图像不是参照真实的物体,而是参照的他们在图片中的样子,风格上不会特别写实。

如何解决这两个问题呢?

我会在本文中介绍另一种神经网络——变分自编码机,来解决这两个问题。

什么是变分自编码机?


要理解变分自编码机(VAE),我们要先从一个简单的网络开始,一步一步添加部件。

描述神经网络的常见方法,是把它解释成我们想要建模的功能的某种近似。然而,它们还能被理解为储存信息的某种数据结构。

假设有一个由数层解卷积层构成的神经网络,我们把输入设定为单位向量,然后训练该网络去降低其与目标图像之间的均方误差。这样,该图像的“数据”就包含在神经网络当前的参数之中了。



现在,我们用多张图像来尝试这一步骤。此时,输入不再是单位向量,而要改用独热向量。比如,输入 [1, 0, 0, 0] 可能是生成一张猫的图像,而输入 [0, 1, 0, 0] 则可能生成一张狗的图像。这是可行的,不过这样我们只能存储最多4张图像。让网络记住更多的图像则要使用更长的向量,同时也意味着越来越多的参数。

为此,我们需要使用实向量,而非独热向量。我们可以把它视为某个图像所对应的编码,比如用向量 [3.3, 4.5, 2.1, 9.8] 来表示猫的图像,而用向量 [3.4, 2.1, 6.7, 4.2] 来表示狗的图像,这就是 编码/解码 这一术语的来源。这一初始向量便是我们的潜在变量。

像我前面那样随机选择潜在变量,明显是个糟糕的做法。在自编码机中,我们加入了一个能自动把原始图像编码成向量的组件。上述解卷积层则能把这些向量“解码”回原始图像。



这样,我们的模型终于到了一个能有用武之地的阶段。根据需要,我们可以用尽可能多的图像来训练网络。如果保存了某张图像的编码向量,我们随时就能用解码组件来重建该图像,整个过程仅需一个标准的自编码机。

不过,这里我们想要的是构建一个生成式模型,而非仅仅是“记忆”图像数据的模糊结构。除了像前面那样从已有图像中编码出潜在向量,我们还不知道如何创造这些向量,也就无法凭空生成任何图像。

这里有个简单的办法。我们给编码网络增加一个约束,迫使它所生成的潜在向量大体上服从于单位高斯分布。该约束条件使得变分自编码机不同于标准自编码机。

现在,生成新的图像就变得容易了:我们只需从单位高斯分布中采样出一个潜在向量,并将其传到解码器即可。

实际操作中,我们需要仔细权衡网络的精确度与潜在变量在单位高斯分布上的契合程度。

神经网络可以自行决定这里的取舍。对于其中的误差项,我们归纳出独立的两种:生成误差,用以衡量网络重构图像精确度的均方误差;潜在误差,用以衡量潜在变量在单位高斯分布上的契合程度的KL散度。

generation_loss = mean(square(generated_image - real_image))
latent_loss = KL-Divergence(latent_variable, unit_gaussian)
loss = generation_loss + latent_loss

为了优化KL散度,我们要用到重新参数化的一个简单技巧:生成一个均值向量一个标准差向量,而非直接生成实值向量。



我们的KL散度计算就变成这样:

# z_mean and z_stddev are two vectors generated by encoder network
latent_loss = 0.5 * tf.reduce_sum(tf.square(z_mean) + tf.square(z_stddev) - tf.log(tf.square(z_stddev)) - 1,1)

在计算解码网络的误差时,我们只需从标准差中取样,再加上均值向量,就能得到我们的潜在向量:

samples = tf.random_normal([batchsize,n_z],0,1,dtype=tf.float32)
sampled_z = z_mean + (z_stddev * samples)

除了能让我们生成随机的潜在变量,该约束还能提高VAE网络的泛化能力。

形象地说,我们可以把潜在变量视为数据的变换系数。

在[ 0, 10 ]的区间内,假定你有一系列的实数-名称对,一个实数代表一个物体的名字。例如,5.43表示苹果,5.44表示香蕉。当有人给你数字5.43时,你肯定知道他们是在谈论苹果。本质上,采用这一方式可以编码无限多的信息,毕竟[ 0, 10 ]之间的实数是有无数个。

然而,如果每当有人给告诉你一个新数字,它的高斯噪点也会增加一个时,情况会变成怎样?比如说,你收到数字是5.43,其原始数值则应在[4.4 ~ 6.4]之间,那其他人所说的真实数字就有可能是5.44(香蕉)。

所增噪点的标准差越大,其均值变量所能传递的信息就越少。

用此相同的逻辑,我们就能在编码器和解码器之间传递潜在变量。对原始图像的编码越有效,我们在高斯分布上所能取样的标准差就越大,直至为1(标准正态分布)。

这一约束迫使编码器变得非常高效,从而能创造出信息丰富的潜在变量。它所提升的泛化能力,让我们随机生成或从非训练图像编码而来的潜在变量,在解码时将能产生更好的结果。

VAE的效果有多好?

我在MNIST手写数据集上做了一些测试,从中可以看出变分自编码机的效果有多好。


左:第1世代,中:第9世代,右:原始图像
看起来很不错!在我那没有显卡的笔记本上运行15分钟后,它就生成了一些很好的MNIST结果。

VAE的优点:

由于它们所遵循的是一种 编码-解码 模式,我们能直接把生成的图像同原始图像进行对比,这在使用GAN时是不可能的。

VAE的不足:

由于它是直接采用均方误差而非对抗网络,其神经网络倾向于生成更为模糊的图像。

也有一些需要结合VAE和GAN的研究工作:采用相同的 编码器-解码器 配置,但使用对抗网络来训练解码器。

研究详情参考论文
https://arxiv.org/pdf/1512.09300.pdf
http://blog.otoro.net/2016/04/01/generating-large-images-from-latent-vectors/

本文代码
https://github.com/kvfrans/variational-autoencoder
https://jmetzen.github.io/2015-11-27/vae.html


原文链接
http://kvfrans.com/variational-autoencoders-explained/