相信大家对浩瀚的宇宙、漫天繁星都充满过极大的兴趣,我们对夜晚漫天眨眼的星星充满了无限的向往,在某个夏日夜晚里,我们也是那个“数星星的孩子”。在本文中,我们将使用变分自编码器(VAE)这项深度学习技术,生成星系的图片,生成我们的“星辰大海”。


一、变分自编码器

自编码器(AutoEncoder)是一种表示学习技术,是深度学习中的一个重要分支,也是无监督学习的重要技术之一。一个典型的自编码器结构如Figure1所示,包括两个主要部分:编码器(Encoder)和解码器(Decoder)。编码器将输入图像不断压缩,最后形成一个特征表示向量,而解码器用这个表示向量将原图片重构出来。学习过程是最小化输出与输入之间的差异,这个差异用一个损失函数表示,比如均方误差。自编码器起到了降维的作用,用一个低维的向量空间来表示原始的高维空间。

生成星辰大海——变分自编码器(VAE)实践_编码器

而变分自编码器是怎么回事呢?先看一下典型的变分自编码的结构,如图2所示。

生成星辰大海——变分自编码器(VAE)实践_损失函数_02

看上去很复杂,在编码器和解码器之间已经不是那个简单的特征表示向量了。作为生成式模型,VAE和生成式对抗网络GAN比较相似:都是希望通过一个隐变量Z生成数据x,使生成数据的分布 p ( x ∣ z ) p(x|z) p(x∣z)接近于真实分布 p ( x ) p(x) p(x)。假设 z z z服从某种分布,比如正态分布。GAN直接由Z生成数据,和真实数据进行对抗训练。而VAE直接从输入数据中学习这种分布。我们现在假设Z服从 标准正态分布,它有两个参数,均值 μ {\boldsymbol \mu} μ和方差 σ 2 {\boldsymbol \sigma}^2 σ2。从图2中可看出,编码器就是来干这个事情的,它会拟合出均值 μ {\boldsymbol \mu} μ和方差的对数 log ⁡ σ 2 \log{\boldsymbol \sigma}^2 logσ2。这里不直接拟合 σ 2 {\boldsymbol \sigma}^2 σ2是因为 σ 2 {\boldsymbol \sigma}^2 σ2是非负的,需要加激活函数,而 log ⁡ σ 2 \log{\boldsymbol \sigma}^2 logσ2就可正可负了,不需要加激活函数。

由于Z要从 N ( μ , σ 2 ) \mathcal N(\boldsymbol \mu, {\boldsymbol \sigma}^2) N(μ,σ2)中采样,但这个采样的过程是不可导的,无法使用反向传播算法。对于这个问题,这里采用了reparameterization的技巧,即从 N ( 0 , I ) \mathcal N(\boldsymbol 0, \boldsymbol I) N(0,I)中采样一个 ϵ \boldsymbol\epsilon ϵ,然后令 Z = σ 2 × ϵ + μ \boldsymbol Z={\boldsymbol \sigma}^2 \times {\boldsymbol \epsilon} + \boldsymbol\mu Z=σ2×ϵ+μ,这样Z依然服从 N ( μ , σ 2 ) \mathcal N(\boldsymbol \mu, {\boldsymbol \sigma}^2) N(μ,σ2),而这个过程是线性的,可以正常使用反向传播算法。

现在Z服从正态分布 N ( μ , σ 2 ) \mathcal N(\boldsymbol \mu, {\boldsymbol \sigma}^2) N(μ,σ2),为了使 Z Z Z服从标准正态分布 N ( 0 , I ) \mathcal N(\boldsymbol 0, \boldsymbol I) N(0,I),我们将 N ( μ , σ 2 ) \mathcal N(\boldsymbol \mu, {\boldsymbol \sigma}^2) N(μ,σ2)与 N ( 0 , I ) \mathcal N(\boldsymbol 0, \boldsymbol I) N(0,I)的KL散度作为损失函数的一项,即正则化项,使 Z Z Z趋向于标准正态分布。损失函数的另一项就是重构损失了,由于要使生成数据的分布 p ( x ∣ z ) p(x|z) p(x∣z)接近于真实分布 p ( x ) p(x) p(x),所以这里使用两者的交叉熵损失作为重构损失。最后整个损失函数为:

生成星辰大海——变分自编码器(VAE)实践_正态分布_03

那么说了这么多,VAE和普通的自编码器相比有什么优点呢?普通的自编码器更关注对原始数据的重构,生成的特征表示也是不连续的,那么如果从一片未学到的隐空间进行采样再进过解码器后,生成的数据可能是不真实的。而VAE学习到的是数据的分布,隐空间是连续的,可以从这个空间随意采样。下图是两个用VAE生成图片的例子。左边的图片是生成人脸,第一行从左往右看,人脸从朝右慢慢变成了朝左,往下看,人脸从面无表情慢慢变成了微笑。右边是生成的手写数字,从第一行向右看,数字从6慢慢变成了0,往下看,数字6慢慢变成了9,整个生成的图片都是渐变的。所以用VAE生成的图片可以有渐变的特性。

生成星辰大海——变分自编码器(VAE)实践_编码器_04

二、使用星系数据集实践VAE

这里我们想要生成星系的图片,但没有这样的公开数据集,所以我自己从网上爬取了一些星系的图片,经过筛选与处理,最后得到了600多张图片。600多张说实话确实太少了,但质量比较高的关于星系的图片比较少,网上爬取的图片很多也都是重复的。这里我们用这个星系数据集为例,能使模型work起来,结果看得过去就算成功。

我收集到的星系数据集的一些样例如下图所示:

生成星辰大海——变分自编码器(VAE)实践_编码器_05

星系本身是非常漂亮的~

下面是模型设计,这里我们使用全卷积网络(FCN)作为编码器,解码器结构与之对称。全卷积网络没有池化层,池化层使用带步长的卷积代替,这样可以让网络自己学习到合适的下采样策略。编码器encoder与解码器decoder的结构如表1和表2所示。编码器中有4层卷积,滤波器通道数目逐渐增加,已学到更丰富的特征。然后最后一层卷积的输出被展开成一维向量,分成两个支路,一个支路表示均值向量 μ {\boldsymbol \mu} μ,另一个支路表示 log ⁡ σ 2 \log{\boldsymbol \sigma}^2 logσ2。这里设它们是4维的。对于解码器,基本上使编码器的对称。由于编码器中图像经过卷积后尺寸是慢慢变小的,在解码器中用上采用层将图片慢慢变大。而通道数目是逐渐减少的,将丰富的信息逐渐整合成RGB三通道的图像。最后一层的激活函数是tanh函数,值在-1和1之间,会将其线性变换至0到255之间的整数。整个网络的输入输出均为256 * 256 * 3的图像。

生成星辰大海——变分自编码器(VAE)实践_数据集_06

生成星辰大海——变分自编码器(VAE)实践_正态分布_07

下面是代码部分,整个代码是基于keras实现的。



数据读取部分:这里使用keras的ImageDataGenerator,它将读取数据、生成mini-batch、预处理、数据增强等都结合在了一起,非常方便。
生成星辰大海——变分自编码器(VAE)实践_损失函数_08



编码器部分:
生成星辰大海——变分自编码器(VAE)实践_数据集_09



潜在空间采样函数:
生成星辰大海——变分自编码器(VAE)实践_损失函数_10



解码器部分:
生成星辰大海——变分自编码器(VAE)实践_损失函数_11



损失函数,一般keras的损失函数的定义形式为loss(input, target),但VAE的损失函数不符合这种形式,所以我们写一个自定义层,使用其内置的add_loss方法来创建自己想要的损失。
生成星辰大海——变分自编码器(VAE)实践_编码器_12



训练部分则是从ImageDataGenerator里获得mini-batch,用train_on_batch方法训练。这里batch size为64,一共训练10000轮。学习率设置为0.001,每100轮迭代减小一次学习率,优化器为Adam
生成星辰大海——变分自编码器(VAE)实践_编码器_13



最后是实验结果,下图是训练结束后生成的图像:

生成星辰大海——变分自编码器(VAE)实践_数据集_14

从上图中可以看出VAE已经学习到了星系相关的形状,从左上角向右看,形状由棒形变为不规则形状,向下看变为团状,颜色也是渐变的。不过缺点也是很明显的:不够清晰。这里有两个原因吧:第一是VAE生成图片的清晰度不如GAN,依靠对抗训练GAN可以获得高清晰度的图片。第二是数据量不够,只有600多张图片,模型能学习到的信息很有限,增加数据量可以进一步提高效果。

下一步的研究与工作:

1、 关于VAE的原理这里只是作了简单介绍,下一步会对VAE作详细的理论介绍

2、 在这个模型的基础上进一步想办法提高效果,尝试VAE的其他变种

3、增加数据量。希望对本项目和星系数据集感兴趣的小伙伴们,能收集更多的数据,在本项目的github地址上提交,一起来提升图片生成的质量~


生成星辰大海——变分自编码器(VAE)实践_数据集_15