1.VAE和GAN
- 变分自编码器(VAE,variatinal autoencoder)
- 生成式对抗网络(GAN,generative adversarial network)
两者不仅适用于图像,还可以探索声音、音乐甚至文本的潜在空间;
- VAE非常适合用于学习具有良好结构的潜在空间,其中特定方向表示数据中有意义的变化轴;
- GAN生成的图像可能非常逼真,但它的潜在空间可能没有良好结构,也没有足够的连续型。
自编码,简单来说就是把输入数据进行一个压缩和解压缩的过程。 原来有很多 Feature,压缩成几个来代表原来的数据,解压之后恢复成原来的维度,再和原数据进行比较。它是一种非监督算法,只需要输入数据,解压缩之后的结果与原数据本身进行比较。
在实践中,这种经典的自编码器不会得到特别有用或具有良好结构的潜在空间。它们也没有对数据做多少压缩。因此,它们已经基本上过时了(Keras 0.x版本还有AutoEncoder这个层,后来直接都删了)。但是,VAE向自编码器添加了一点统计魔法,迫使其学习连续的、高度结构化的潜在空间。这使得VAE已成为图像生成的强大工具。变分编码器和自动编码器的区别就在于,传统自动编码器的隐变量z的分布是不知道的,因此我们无法采样得到新的z,也就无法通过解码器得到新的x。下面我们来变分,我们现在不要从x中直接得到z,而是得到z的均值和方差,然后再迫使它逼近正态分布的均值和方差,则网络变成下面的样子:
然而上面这个网络最大的问题是,它是断开的。前半截是从数据集估计z的分布,后半截是从一个z的样本重构输入。最关键的采样这一步,恰好不是一个我们传统意义上的操作。这个网络没法求导,因为梯度传到f(z)以后没办法往前走了。为了使得整个网络得以训练,使用一种叫reparemerization的trick,使得网络对均值和方差可导,把网络连起来。这个trick的idea见下图:
实际上,这是将原来的单输入模型改为二输入模型了。因为服从标准正态分布,所以它乘以估计的方差加上估计的均值,效果跟上上图直接从高斯分布里抽样本结果是一样的。这样,梯度就可以通上图红线的方向回传,整个网络就变的可训练了。
VAE的工作原理:
(1)一个编码器模块将输入样本input_img转换为表示潜在空间中的两个参数z_mean和z_log_variance;
(2)我们假定潜在正态分布能够生成输入图像,并从这个分布中随机采样一个点:z=z_mean + exp(z_log_variance)*epsilon,其中epsilon是取值很小的随机张量;
(3)一个解码器模块将潜在空间的这个点映射回原始输入图像。
因为epsilon是随机的,所以这个过程可以确保,与input_img编码的潜在位置(即z-mean)靠近的每个点都能被解码为与input_img类似的图像,从而迫使潜在空间能够连续地有意义。潜在空间中任意两个相邻的点都会被解码为高度相似的图像。连续性以及潜在空间的低维度,将迫使潜在空间中的每个方向都表示数据中一个有意义的变化轴,这使得潜在空间具有非常良好的结构,因此非常适合通过概率向量来进行操作。
VAE的参数通过两个损失函数来进行训练:一个是重构损失(reconstruction loss),它迫使解码后的样本匹配初始输入;另一个是正则化损失(regularization loss),它有助于学习具有良好结构的潜在空间,并可以降低训练数据上的过拟合。
实现代码如下:
编码自编码器是更现代和有趣的一种自动编码器,它为码字施加约束,使得编码器学习到输入数据的隐变量模型。 | |
import keras
| 卷积层的输入必须是3维的(长,宽,1或者3) keras不需要输入batch的大小,fit时候再设置 shape_before_flattening (None, 14, 14, 64) |
encoded = layers.Flatten()(encoded)
| z_mean ---> <tf.Tensor 'dense_5/BiasAdd:0' shape=(?, 2) dtype=float32> K.shape(z_mean) ---> |
#潜在空间采样的函数 | 在keras中,任何对象都应该是一个层,如果代码不是内置层的一部分, 我们应该将其包装到一个Lambda层(或自定义层)中 Keras的Lambda层以一个张量函数为参数,对输入的数据按照张量函数的要求做映射。 本质上就是Keras layer中.call()的快捷方式。先定义运算逻辑 K.int_shape(z) ---> (None,2) None应该是batch_size |
#VAE解码器网络,将潜在空间点映射为图像 | |
#用于计算VAE损失的自定义层 | 正则化损失 + 重构损失 我们一般认为采样函数的形式为loss(input,target),VAE的双重损失不符合这种形式。 因此,损失的设置方法为:编写一个自定义层,并在其内部使用内置的add_loss层方法 来创建一个你想要的损失 |
#训练VAE
| |
from keras.datasets import mnist
| x_train.shape (600, 28, 28) |
vae.fit(x_train,None, | 一旦训练好了这样的模型,我们就可以使用decoder网络将任意潜在空间向量 转换为图像 |
#从二维潜在空间中采样一组点的网络,并将其解码为图像
| 因为训练时候就用了600个数据,所以效果很差....电脑实在带不动,┭┮﹏┭┮ 以后有服务器再试试,7777777 |
小结: 用深度学习进行图像生成,就是通过对潜在空间进行学习来实现的,这个潜在空间能够捕捉到关于图像数据集的统计信息。 通过对潜在空间中的点进行采样和编码,我们可以生成前所未见的图像。
网上的代码大部分都是关于mnist数据集的,直接load_dataset就完事了,我找到了名人头像的数据集celebrity_data,用这个数据集做vae更有趣一点。
import keras
|
|
train_imgs = glob.glob('./celebrity_data/train/*.jpg') | Image读出来的是PIL的类型,而skimage.io读出来的数据是numpy格式的 import Image as img 输出可以看出Img读图片的大小是图片的(width, height);而skimage的是(height,width, channel) |
height,width = imageio.imread(train_imgs[0]).shape[:2]
| 训练集里面的图片都是218*178*3的,训练的时候我也没有改大小,直接放进去训练的 |
def imread(f):
| train_data_generator是训练集图片生成器,每次生成一个图片 |
img_shape = (img_xdim,img_ydim,3)
| 这部分和上面基于minist数据集的encoder部分一样 |
#将图片转换为二维向量 nxf_image = nxf_image.reshape((1,)+nxf_image.shape)
| 这里是我在测试encoder,随机输入一张图片,输出了二维的一个值,一个是均值,一个是方差,encoder没有编译, 也没有fit,就相当于将多维图片降维成二维的一组 |
# 潜在空间采样的函数
| 这部分也是一样的,解码操作,随机生成一个点(均值,方差)放入decoder中,看看生成的图片能不能和原来的图片一样 |
# 用于计算VAE损失的自定义层
| VAE的两个损失,由于keras自带的损失函数没有同时有正则损失和重构损失,所以需要自定义一个损失层, 使用call函数来定义该损失层的功能 |
def sample(path):
| sample函数,我就随机输入两个值(encoder的输出值),看看能不能生成一个相似的图片 |
参考文献:
【2】变分自编码器(Variational Autoencoder, VAE)通俗教程
【5】vae 名人数据集的使用