从隐图像空间进行采样以创建全新的图像或编辑现有图像是目前创作AI最受欢迎和最成功的应用方式。
图像生成的关键思想是开发表示的低维潜在空间(自然是矢量空间),其中任何点都可以映射到逼真的图像上。 能够实现该映射的模块,将潜在点作为输入并输出图像(像素网格),被称为生成器(在GAN的情况下)或解码器(在VAE的情况下)。一旦开发出这样的潜在空间,可以有意或无意地从中采样点,并通过将它们映射到图像空间,生成以前从未见过的图像。
GAN和VAE是用于学习图像表示的潜在空间的两种不同策略,每种都具有其自身的特征。VAE非常适合学习结构良好的潜在空间,其中特定方向编码数据能产生有意义的变化轴。GAN生成的图像可能非常逼真,但它们来自潜在的空间可能没有那么多的结构和连续性。
给定潜在的表示空间或嵌入空间,空间中的某些方向可以编码原始数据中有趣的变化轴。例如,在面部图像的潜在空间中,可能存在微笑矢量s,使得如果潜在点z是某个面部的嵌入表示,则潜在点z+s是同一面部的嵌入表示,面带微笑。一旦确定了这样的矢量,就可以通过将图像投影到潜在空间中来编辑图像,以有意义的方式移动它们的表示,然后将它们解码回图像空间。
变分自动编码器,是一种生成模型,特别适用于通过概念向量进行图像编辑的任务。它们是自动编码器的现代版本 - 一种旨在将输入编码到低维潜在空间然后将其解码回来的网络 - 将来自深度学习的想法与贝叶斯推理混合在一起. 经典图像自动编码器通过编码器模块拍摄图像,将其映射到潜在的矢量空间,然后通过解码器模块将其解码回与原始图像具有相同尺寸的输出。然后通过使用与输入图像相同的图像作为目标数据来训练,这意味着自动编码器学习重建原始输入。通过对代码(编码器的输出)施加各种约束,可以使自动编码器学习或多或少有趣的数据潜在表示。最常见的是,将限制代码为低维和稀疏(大多数为零),在这种情况下,编码器可以将输入数据压缩为更少的信息位。
在实践中,这种经典的自动编码器不会导致特别有用或结构良好的潜在空间,也不太擅长数据压缩。由于这些原因,他们已经基本上不再流行。然而,VAE用统计方法增强了自动编码器,迫使他们学习连续的,高度结构化的潜在空间。它们已成为图像生成的强大工具。
VAE不是将其输入图像压缩为潜在空间中的固定代码,而是将图像转换为统计分布的参数:均值和方差。从本质上讲,这意味着假设输入图像是由统计过程生成的,并且此过程的随机性应在编码和解码期间用于计算。然后,VAE使用均值和方差参数随机采样分布的一个元素,并将该元素解码回原始输入。该过程的随机性提高了鲁棒性并迫使潜在空间在任何地方编码有意义的表示:在潜在空间中采样的每个点被解码为有效输出。
数学描述,VAE工作过程:
因为epsilon是随机的,所以该过程确保接近编码input_img(z-mean)的潜在位置的每个点都可以被解码为类似于input_img的东西,从而迫使潜在空间持续有意义。潜在空间中的任何两个闭合点将解码为高度相似的图像。连续性与潜在空间的低维度相结合,迫使潜在空间中的每个方向编码有意义的数据变化轴,使得潜在空间非常结构化,因此非常适合通过概念向量进行操纵。
VAE的参数通过两个损失函数进行训练:强制解码样本与初始输入匹配的重建损失函数,以及有助于学习良好的隐空间并减少过度拟合训练数据的正则化损失函数。让我们快速了解一下VAE的Keras实现。原理上,它看起来像这样:
z_mean,z_log_variance = encoder(input_img)#输入编码成均值、方法参数
z = z_mean + exp(z_log_variance)*epsilon#隐空间通过epsilon取样
reconstructed_img = decoder(z)#取样点生成新图片
model = Model(input_img,reconstructed_img)#实例化模型:输入图片映射到新建图片上,之后训练
模型定义后,使用重建损失函数和正则损失训练模型。 使用一个简单的convnet将输入图片映射到隐空间的概率分布上,得到两个向量z_mean,z_log_var。
VAE Encoder网络
import keras
from keras import layers
from keras import backend as K
from keras.models import Model
import numpy as np
img_shape = (28,28,1)
batch_size = 16
latent_dim = 2
input_img = keras.Input(shape=img_shape)
x = layers.Conv2D(32,3,padding='same',activation='relu')(input_img)
x = layers.Conv2D(64,3,padding='same',activation='relu',stride=(2,2))(x)
x = layers.Conv2D(64,3,padding='same',activation='relu')(x)
x = layers.Conv2D(64,3,padding='same',activation='relu')(x)
shape_before_flattening = K.int_shape(x)
x = layers.Flatten()(x)
x = layers.Dense(32,activation='relu')(x)
#输入图片最终 编码成 两个参数
z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)
之后使用输入图片的假设空间分布特征z_mean和z_log_var得到隐空间取样点z。在这里,将一些任意代码(构建在Keras后端基元之上)包装到Lambda层中。在Keras中,一切都需要是一个层,因此不属于内置层的代码应该包装在Lambda(或自定义层)中.
隐空间取样函数
def sampling(args):
z_mean,z_log_var = args
epsilon=K.random_normal(shape=(K.shape(z_mean)[0],
latent_dim),mean=0.,stddev=1.)
return z_mean + K.exp(z_log_var)*epsilon
z = layers.Lambda(sampling)([z_mean,z_log_var])
解码器部分实现。将向量z reshape到图片尺寸,最后经过几个卷积层得到最终的图片输出。 VAE decoder网络:隐变量空间到图片
decoder_input = layers.Input(K.int_shape(z)[1:])#输入z向量
x = layers.Dense(np.prod(shape_before_flattening[1:]),activation='relu')(decoder_input)
x = layers.Reshape(shape_before_flattening[1:])(x)
x = layers.Conv2DTranspose(32,3,padding='same',activation='relu',strides=(2,2))(x)
x = layers.Conv2D(1,3,padding='same',activation='sigmoid')(x)
decoder = Model(decoder_input, x)#实例化模型,模型将输入decoder_input转换成图片
z_decoded = decoder(z)#输入z,得到最终转换后的输出图片
VAE的双重损失函数不符合传统形式损失函数(输入,目标)的预期。因此,将通过编写内部使用内置add_loss图层方法来创建任意损失的自定义图层来设置损失函数。 定义图层计算损失函数
class CustomVariationalLayer(keras.layers.Layer):
def vae_loss(self, x, z_decoded):
x = K.flatten(x)
z_decoded = K.flatten(z_decoded)
xent_loss = keras.metrics.binary_crossentropy(x, z_decoded)#重构损失
kl_loss = -5e-4 * K.mean(1+z_log_var-K.square(z_mean)-
K.exp(z_log_var), axis=-1)#encoder损失
return K.mean(xent_loss + kl_loss)
def call(self,inputs):
x = inputs[0]
z_decoded = inputs[1]
loss = self.vae_loss(x,z_decoded)
self.add_loss(loss,inputs=inputs)
return x
y = CustomVariationalLayer()([input_img, z_decoded])
最后,实例化模型并训练。由于损失函数是在自定义层中处理的,因此不会在编译时指定外部损失(loss=None),这反过来意味着不会在训练期间传递目标数据(如所见,只能将x_train传递给模型在fit函数中)。 VAE训练
from keras.datasets import mnist
vae = Model(input_img,y)#通过定义输入和输出 Model模型
vae.compile(optimizer='rmsprop', loss=None)
vae.summary()
(x_train, _), (x_test, y_test) = mnist.load_data()
x_train = x_train.astype('float32') / 255.
x_train = x_train.reshape(x_train.shape + (1,))
x_test = x_test.astype('float32') / 255.
x_test = x_test.reshape(x_test.shape + (1,))
vae.fit(x=x_train, y=None,shuffle=True,epochs=10,batch_size=batch_size,
validation_data=(x_test, None))
模型训练完成后,可以使用decoder模块将任意隐变量空间点转换生成图片。 2D隐变量空间点取样,生成图片
import matplotlib.pyplot as plt
from scipy.stats import norm
n = 15#15*15 225个数字图片
digit_size = 28
figure = np.zeros((digit_size*n,digit_sie*n))#最终图片
grid_x = norm.ppf(np.linspace(0.05,0.95,n))#假设隐变量空间符合高斯分布
grid_y = norm.ppf(np.linspace(0.05,0.95,n))#ppf随机取样
for i,yi in enumerate(grid_x):
for j, xi in enumerate(grid_y):
z_sample = np.array([[xi, yi]])
#重复z_sample多次,形成一个完整的batch
z_sample = np.tile(z_sample, batch_size).reshape(batch_size, 2)
x_decoded = decoder.predict(z_sample, batch_size=batch_size)
digit=x_decoded[0].reshape(digit_size, digit_size)#28*28*1->28*28
figure[i*digit_size:(i+1)*digit_size,j*digit_size:(j+1)*digit_size] = digit
plt.figure(figsize=(10, 10))
plt.imshow(figure, cmap='Greys_r')
plt.show()