共计 9603 个字符,预计需要花费 25 分钟才能阅读完成。
说到计算机生成的图像必定就会想到 deep fake:将马变成的斑马或者生成一个不存在的猫。在图像生成方面 GAN 仿佛成为了支流,然而只管这些模型在生成真切的图像方面获得了巨大成功,但他们的缺点也是非常显著的,而且并不是生成图像的全副。自编码器(autoencoder)作为生成的图像的传统模型还没有过期并且还在倒退,所以不要忘掉自编码器!
GAN 并不是您所须要的全副
当谈到计算机视觉中的生成建模时,简直都会提到 GAN。应用 GAN 的开发了很多许多惊人的应用程序,并且能够在这些应用程序中生成高保真图像。然而 GAN 的毛病也非常显著:
1、训练不稳固,常常会呈现梯度隐没、模式解体问题(会生成雷同的图像),这使得咱们须要做大量的额定工作来为数据找到适合的架构。
2、GAN 很难反转(不可逆),这意味着没有简略的办法能够从生成的图像反推到产生这个图像的噪声输出。例如:如果应用可逆生成模型进行生成的图像的加强,能够间接取得生成图像的特定输出,而后在正确的方向上略微扰动它这样就能够取得十分类似的图像,然而 GAN 做到这一点很麻烦。
3、GAN 不提供密度估计。也就是说能够生成图像但无奈晓得特定特色呈现在其中的可能性有多大。例如:如果对于异样检测来说密度估计是至关重要的,如果有生成模型能够通知咱们一只可能的猫与一只不太可能的猫的样子,咱们就能够将这些密度估计传递给上游的异样检测工作,然而 GAN 是无奈提供这样的预计的。
自编码器 (AE) 是一种代替计划。它们绝对疾速且易于训练、可逆且具备概率性。AE 生成的图像的保真度可能还没有 GAN 的那么好,但这不是不应用他们的理由!
自编码器还没有过期
有人说:一旦 GAN 呈现,自编码器就曾经过期了。这在某种程度上是正确的,但时代在提高 GAN 的呈现让自编码器的倒退有了更多的能源。在认真地钻研后人们曾经意识到 GAN 的毛病并承受它们并不总是最适宜的模型。,所以目前对自编码器持续进行更加深刻的钻研。
例如,一种被称为矢量量化变分自编码器 (Vector Quantized Variational AutoEncoder / VQ-VAE) 的自回归 AE 宣称能够生成与 GAN 的品质相匹配的图像,同时不会有 GAN 的已知毛病,例如模式解体和不足多样性等问题。
应用 VQ-VAE-2 生成多样化的高保真图像”(链接:arXiv:1906.00446)
在论文中,作者通过生成渔民图像将他们的 AE 模型与 DeepMind 的 BigGAN 进行了比拟。能够看到 AE 生成的图像之间还是有多少变动的。
另外,在自编码器畛域另一个令人兴奋的钻研的例子是 VAE / GAN。这种混合模型应用 GAN 的鉴别器在典型的反抗训练中学到的常识来进步 AE 的生成能力。
“Autoencoding beyond pixels using a learned similarity metric”(arXiv:1512.09300)
在上图中作者应用他们的模型从学习的示意中重建一组图像,这是 GAN 无奈做到的,因为 GAN 不足下面说过的的可逆性。从图上看重建看起来很不错。
尽管 GAN 很重要,然而自编码器还在以某种形式在图像生成中发挥作用(自编码器可能还没被齐全的开发),相熟它们必定是件坏事。
在本文的上面局部,将介绍自编码器的工作原理、有哪些不同类型的自编码器以及如何应用它们。最初还将提供一些 TensorFlow 的代码。
应用自编码器进行示意学习
自编码器都是对于如何无效地示意数据的。他们的工作是找到一个高维输出的低维示意,在不损失内容的状况下重建原始输出。
从下图所示的 quickdraw 数据集中获取“斧头”。图像为 28×28 灰度,这意味着它由 784 个像素组成。自编码器会找到从这个 784 维空间到 2D 空间的映射,这样压缩后的 ax 图像将仅由两个数字形容:地图上的 X 和 Y 坐标。接下来,仅晓得 X-Y 坐标,自编码器将尝试仅从这两个值重建原始的 784 个像素。
自编码器学习其输出的低维度示意。
重建必定不会是完满的,因为在压缩过程中不可避免地会失落一些信息,然而咱们的指标是心愿它足以辨认原始图像。在咱们示例中的”地图“是无效示意数据的潜在空间。尽管咱们应用 2D 进行阐明,但实际上潜在空间通常会更大,但仍比输出图像小得多。
自编码器的工作是创立一个低维示意让它重建原始输出。这确保了这个潜在空间压缩了最相干的输出特色,并且没有噪声和对重建输出不重要的特色。
要点:自编码器的潜在空间压缩了当初相干的输出特色,并且没有噪声和冗余特色。
这个特点使得它在许多方面都具备吸引力。能够应用自编码器进行降维或特征提取(能够构建一个在数学上等同于主成分剖析或 PCA 的自编码器,咱们以前有个相应的文章,有趣味的能够搜寻参考)。所以能够在任何数据管道中用自编码器学习的低维度示意替换高维度数据。
自编码器还有许多其余利用。它们可用于对图像进行去噪:只需输出一张有噪声的图像,自编码器会重建原始的无噪声图像。它们还可用于自监督预训练,其中模型从大量未标记数据中学习图像特色,而后针对一小部分标记数据上的某些监督工作进行微调。最初自编码器能够用作生成模型,这将是本文的重点。
要点:自编码器可用于降维、特征提取、图像去噪、自监督学习和生成模型。
传统的自编码器 AE
这里应用 Google 游戏“Quick, Draw!”的玩家制作的手绘形态的 quickdraw 数据集构建一个简略的自编码器。为了不便演示,咱们将只应用三类图像:狗、猫和树。这是图像的示例。
如何构建一个自编码器呢?它须要由两局部组成:编码器,它接管输出图像并将其压缩为低维示意,以及解码器,它做相同的事件:从潜在示意产生原始大小的图像.
让咱们从编码器开始。因为是解决图像所以在网络中应用卷积层。该模型将输出图像顺次通过卷积层和最大池化层,以将它们压缩成低维示意。
encoder = tf.keras.models.Sequential([tf.keras.layers.Reshape([28, 28, 1], input_shape=[28, 28]),
tf.keras.layers.Conv2D(16, kernel_size=3, padding="same", activation="selu"),
tf.keras.layers.MaxPool2D(pool_size=2),
tf.keras.layers.Conv2D(32, kernel_size=3, padding="same", activation="selu"),
tf.keras.layers.MaxPool2D(pool_size=2),
tf.keras.layers.Conv2D(64, kernel_size=3, padding="same", activation="selu"),
tf.keras.layers.MaxPool2D(pool_size=2)
])
这种非凡的架构基于 Aurélien Géron 在他的书中用于 FashionMNIST 数据集的架构(参见底部的起源)。这里应用 SELU 激活而不是 ReLU,是因为他比拟新,成果也好😉
编码器最终输入 64 个特色图,每个特色图大小为 3×3,这就是对数据的低维示意。上面就须要一个解码器将这些示意解决成原始大小的图像。这里应用转置卷积(能够将其视为与惯例卷积相同的操作)。转置卷积会放大图像,减少其高度和宽度,同时缩小其深度或特色图的数量。
decoder = tf.keras.models.Sequential([
tf.keras.layers.Conv2DTranspose(32, kernel_size=3, strides=2, padding="valid", activation="selu", input_shape=[3, 3, 64]
),
tf.keras.layers.Conv2DTranspose(16, kernel_size=3, strides=2, padding="same", activation="selu"),
tf.keras.layers.Conv2DTranspose(1, kernel_size=3, strides=2, padding="same", activation="sigmoid"),
tf.keras.layers.Reshape([28, 28])
])
剩下要做的就是将编码器与解码器连接起来,并将它们作为一个残缺的自编码器进行联结训练。应用二元穿插熵损失对模型进行了 20 个 epoch 的训练,代码如下:
ae = tf.keras.models.Sequential([encoder, decoder])
ae.compile(
loss="binary_crossentropy",
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
metrics=["accuracy"]
)
history = ae.fit(
X_train, X_train,
epochs=20,
validation_data=(X_val, X_val)
)
损失函数抉择来说:二元穿插熵和 RMSE 都能够被用作损失函数,两者的次要区别在于二元穿插熵对大误差的惩办更强,这能够将重建图像的像素值推入均匀幅度,然而这反过来又会使重建的图像不那么活泼。因为这个数据集是灰度图像,所以损失函数的抉择不会产生任何有意义的差别。
上面看一下测试集中的一些图像,以及自编码器重建它们的成果如何。
测试集的原始图像(上)与它们的重建图像(下)。
看起来不错,然而一些细节含糊(这是自编码器的缺点,也是 GAN 的劣势),但整体重建精度仿佛相当不错。另一种可视化自编码器所学内容的办法是将一些测试图像仅传递给编码器。这将产生它们的潜在示意,本例 (3, 3, 64)。而后应用降维算法(例如 t-SNE)将它们映射到二维并绘制散点图,通过它们的标签(猫、狗或树)为点着色,如下图所示:
能够分明地看到,树与其余图像拆散良好而猫和狗则有点混淆。留神底部的大蓝色区域,这些是带有胡须的猫头的图像这些并没有与狗混同。然而在图的的上半局部都是从动物的侧面,这使得辨别猫和狗变得更加艰难。这里一个十分值得关注的事件是,自编码器在没有给出标签的状况下理解了多少图像类别!(下面说到的自监督学习)
要点:自编码器能够在没有标签的状况下学习很多对于图像分类的常识。
传统的自编码器模型仿佛曾经学会了数据的有意义的潜在示意。上面让咱们回到本文的主题:它能够作为生成模型吗?
传统自编码器作为生成模型
首先明确一下咱们对生成模型的冀望:心愿可能抉择潜在空间中的任何随机点,将其通过解码器取得真切的图像。最重要的是,在潜在空间中抉择不同的随机点应该会产生不同的生成图像,这些图像应该涵盖模型看到的所有类型的数据:猫、狗和树。
从潜在空间采样
当咱们在潜在空间中抉择一个随机点时,第一个问题就呈现了:在这种状况下,“随机”是什么意思?它应该来自正态分布还是均匀分布?散布应该如何参数化?下图显示了对测试数据样本进行编码后潜在空间值的概率密度。
除此以外,我还计算了一些汇总统计数据:最小潜在空间值为 -1.76,最大值为 22.35。对于随机点采样,让潜在空间以零为中心对称中心化会容易得多,或者说至多以某种形式是有界的,须要一个最大值和最小值。
要点:潜在空间值造成不规则的、无界的散布,会使随机点采样变得艰难。
图像多样性
另一个问题波及潜在空间中各个类别的代表区域,这会影响生成图像的多样性。
模型的潜在空间是 3x3x64,它是 576 维的无奈可视化。为了便于解释能够尝试对一个维度进行 3D 切片,其形态为 3x1x1。只思考此切片时,每个图像在潜在空间中由 3D 矢量示意能够将其可视化为散点图。这是测试数据样本的图:
蓝色点云散布在比红色和绿色云小得多的体积上。这意味着如果要从这个空间中随机抽取一个点,最终失去猫或狗的可能性要比失去树的可能性大得多。在极其状况下,思考到潜在空间的所有 576 个维度,可能永远不会对树进行采样,这违反了对生成模型可能笼罩它所看到的数据的整个空间的要求。
要点:不同图像类别的潜在示意可能在大小上有所不同,导致模型生成某些类别的频率比其余类别高得多。
红色和绿色点云中向上突出的尖峰。在这个尖峰外部存在一些图像的潜在示意。但如果从那里向旁边挪动,在尖刺旁边的正上方一个点取样呢?能得出实在的图像吗?
潜在空间中的有意义区域
在潜在空间的 3D 子空间中,图像嵌入通常是良好聚类的——可能除了点云顶部的红绿 尖峰 之外。然而随着咱们增加更多的维度,嵌入式图像之间会呈现更多的空白空间。这使得整个 3x3x64 的潜在空间充斥了真空。当从其中随机采样一个点时,很可能会从任何特定图像中失去一个远离(在当初的维度上)的点。如果通过解码器传递这些随机抉择的点,咱们会失去什么?答案是得不到任何的形态。
猫和狗之间的采样不应该产生一个耳朵和胡须松软的生物吗?
传统自编码器学习的潜在空间不是间断的,所以该空间中的点之间的含意没有平滑的过渡。并且即便是一个小的扰动点也可能会致垃圾输入。
要点:传统的自编码器学习的潜在空间不是间断的。
应用传统自编码器作为生成模型存在三个问题: 不晓得如何从一个不规则的、无界的空间中采样,一些类可能在潜空间中被适度示意,学习空间是不间断的,这使得很难找到一个点将解码成一个良好的图像。所以这时候变分自编码器呈现了。
变分自编码器 VAE
变分自编码器(Variational autoencoder)或称 VAE,通过引入随机性和束缚潜在空间以便更容易从中采样来解决下面探讨的问题。
要点:变分自编码器将随机性引入模型并限度潜在空间。
要将传统自编码器转换为变分自编码器,只须要调整编码器局部和损失函数。让咱们从第一步开始。
变分编码器
变分编码器不是将输出图像映射到潜在空间中的一个点,而是将其映射到一个散布中,精确地说是多元正态分布(multivariate normal distribution)。
多元正态分布是将单变量正态分布扩大到更多维度。就像单变量正态分布由两个参数形容:均值和方差,多元正态分布由两个参数向量形容,每个参数的长度等于维数。例如,2D 法线将有一个蕴含两个均值的向量和一个蕴含两个方差的向量。如果散布的许多维度是相干的,则会呈现额定的协方差参数,但在 VAE 中,假如所有维度都是独立的,这样所有协方差为零。
为了将输出图像编码为潜在空间中的低维度示意,将从多元正态分布中对其进行采样,其参数(均值和方差)将由编码器学习。
这样潜在空间将用两个向量来形容:均值向量和方差向量。本文的例子中将这两个向量都设为 576 维,以匹配之前构建的编码器,后者编码为 3x3x64 = 576 维空间。实际上能够重用下面的编码器代码。只需展平它的输入并将两个向量附加到它下面。
vanilla_encoder = tf.keras.models.clone_model(encoder)
encoder_inputs = tf.keras.layers.Input(shape=[28, 28])
z = vanilla_encoder(encoder_inputs)
z = tf.keras.layers.Flatten()(z)
codings_mean = tf.keras.layers.Dense(576)(z)
codings_log_var = tf.keras.layers.Dense(576)(z)
codings = Sampling()([codings_mean, codings_log_var])
var_encoder = tf.keras.models.Model(inputs=[encoder_inputs],
outputs=[codings_mean, codings_log_var, codings]
)
这里只有两件事须要具体阐明:
1、正如可能从变量名称中猜到的那样,应用方差的对数来形容正态分布,而不是按原样形容方差。这是因为方差须要为正,而对数方差能够是任何值。
2、编码器应用自定义采样层,该层依据均值和对数变量从多元法线中采样一个点。上面就是代码:
class Sampling(tf.keras.layers.Layer):
def call(self, inputs):
mean, log_var = inputs
epsilon = K.random_normal(tf.shape(log_var))
return mean + K.exp(log_var / 2) * epsilon
为什么变分编码器能够工作
与传统编码器相比,VAE 不将输出映射到一个确定性点,而将其映射到某个空间中的一个随机点。为什么这个更好呢?
对于一个雷同的图像,每次都会在潜在空间中失去一个略微不同的点(只管它们都在均值左近)。这使得 VAE 理解该邻域中的所有点在解码时都应该产生相似的输入。这确保了潜在空间是间断的!
要点:编码器中的随机化迫使潜在空间是间断的。
变分解码器
VAE 的解码器不须要太多更改,间接能够重用以前的代码。
vanilla_decoder = tf.keras.models.clone_model(decoder)
decoder_inputs = tf.keras.layers.Input(shape=[576])
x = tf.keras.layers.Reshape([3, 3, 64])(decoder_inputs)
decoder_outputs = vanilla_decoder(x)
var_decoder = tf.keras.models.Model(inputs=[decoder_inputs],
outputs=[decoder_outputs]
)
惟一的区别是当初编码器的输入或潜在空间是一维向量而不是 3D 张量。所以只需增加一个重塑层就能够了。当初能够将变分编码器和解码器组合到 VAE 模型中。
_, _, codings = var_encoder(encoder_inputs)
reconstructions = var_decoder(codings)
vae = tf.keras.models.Model(inputs=[encoder_inputs],
outputs=[reconstructions]
)
变分损失函数
在传统自编码器中,应用了二元穿插熵损失,并提到均方根误差可能是一种代替办法。在 VAE 中损失函数是须要扩大得,因为穿插熵或 RMSE 损失是一种重建损失——它会惩办模型以产生与原始输出不同的重建。
在 VAE 中在损失函数中减少了 KL 散度,惩办模型学习与规范正态有很大不同的多元正态分布。KL 散度是掂量两个散布差别水平的指标,在此能够衡量标准正态分布与模型学习的散布之间的差别。也就是说:如果均值不为零且方差不为 1,则会产生损失。
latent_loss = -0.5 * \
K.sum(1 + codings_log_var - K.exp(codings_log_var) - \
K.square(codings_mean),
axis=-1)
vae.add_loss(K.mean(latent_loss) / (28 * 28))
vae.compile(
loss="binary_crossentropy",
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
metrics=["accuracy"]
)
latent_loss 的公式就是 KL-divergence 公式,并且在这种非凡状况下失去简化:指标散布是规范正态分布并且两者都没有零协方差。另外就是须要将其缩放到输出图像的大小,以确保它与重建损失具备类似的比例并且不会占主导地位。既然不是主导地位,为什么咱们要把这个 KL 局部加到损失中呢?
1、它使潜在空间中的随机点采样变得简略。咱们能够从规范法线中取样,并确保该空间对模型有意义。
2、因为规范法线是圆形的并且围绕其平均值对称,因而潜在空间中存在间隙的危险较小,也就是说解码器产生有效的图像的概率会小。
通过以上形式,VAE 克服了传统自编码器在图像生成方面的所有三个毛病。当初训练一下看看成果。
history = vae.fit(
X_train, X_train,
epochs=100,
batch_size=128,
validation_data=(X_val, X_val),
)
变分自编码器的剖析
原始图像和它们的重建图像。
后者可能看更含糊,这是意料之中的,毕竟咱们调整了损失函数:不仅关注重建精度,还关注产生有意义的潜在空间。
图像之间的变形
先来验证变分自编码器学习到的潜在空间的确是间断的、行为良好且有意义的,那就是抉择两个图像并在它们之间变形。让咱们以这只猫和这棵树为例。
对它们进行编码以取得它们的暗藏示意,并在它们之间进行线性插值。而后将沿插值线的每个点传递给解码器,这样能够在猫和树之间生成图像。
cat = var_encoder(X_train[5930, :, :].reshape(1, 28, 28))[0].numpy()
tree = var_encoder(X_train[17397, :, :].reshape(1, 28, 28))[0].numpy()
linfit = interp1d([1, 10], np.vstack([cat, tree]), axis=0)
将两个潜在示意重叠在一个形态为 2×576 的矩阵中,并利用 scipy 的线性插值函数,如果须要调整,能够批改 linfit ([i + 1 for i in range (10)]) 来取得两头插值。
认真看看猫的嘴是如何变成树干的。以相似的形式,还能够将另猫变成狗。留神猫的尖耳朵是如何逐步变成狗的松软耳朵的。
这个乏味的试验表明,变分自编码器学习的潜在空间是间断的,并确保点之间的平滑过渡。
要点:VAE 潜在空间是间断的,容许在图像之间生成有意义的插值。
如果潜在空间是间断且有意义的,咱们应该可能对图像进行算术运算。
思考这两只猫(图片是重建而不是原始图像)。
如果从右边有胡须的猫中减去左边的无胡须猫,咱们会失去什么?减法必须产生在潜在空间中。
cat_1 = var_encoder(X_train[19015, :, :].reshape(1, 28, 28))[0].numpy()
cat_2 = var_encoder(X_train[7685, :, :].reshape(1, 28, 28))[0].numpy()
result = var_decoder(cat_1 - cat_2)
后果相似于胡须?还真有点像
总结
本文中曾经介绍了自编码器如何学习数据的低维示意,以及这些潜在示意对于新图像的生成是如何不完满的,至多在传统自编码器的状况下:它们学习的空间难以采样且不间断。
还介绍了变分自编码器如何通过向编码器引入随机性并加强损失函数来强制学习间断且有意义的潜在空间来缓解这些问题,从而容许在图像之间进行算术和变形。
下面探讨的示例是在具备现成架构的简略数据集上训练的。设想一下理论利用得时候变分自编码器有如许弱小!
援用:
- Geron A., 2019, 2nd edition, Hands-On Machine Learning with Scikit-Learn and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems, O’Reilly
- Foster D., 2019, Generative Deep Learning. Teaching Machines to Paint, Write, Compose and Play, O’Reilly
- https://www.overfit.cn/post/bc1df57b1f1a499c996e92875ec48923
本文作者:Michał Oleszak