「这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战」
使用VAE生成人脸图片
变分自编码器(VAE)的基础知识参考博文《变分自编码器(VAE)详解与实现(采用TensorFlow2实现)》。作为VAE的应用,我们将使用VAE生成一些可控制属性的人脸图片。可用的人脸数据集包括:
- Celeb A: 这是在学术界很流行的数据集,因为它包含面部特征的注释,不能用于商业用途。
- Flickr-Faces-HQ Dataset, FFHQ: 该数据集可免费用于商业用途,并包含高分辨率图像。
网络架构
网络的输入形状为(112,112,3),可以在数据预处理时调整图片尺寸。由于数据集较复杂,可以增加滤波器数量以增加网络容量。因此,编码器中的卷积层如下:
a) Conv2D(filters = 32, kernel_size=(3,3), strides = 2)
b) Conv2D(filters = 32, kernel_size=(3,3), strides = 2)
c) Conv2D(filters = 64, kernel_size=(3,3), strides = 2)
d) Conv2D(filters = 64, kernel_size=(3,3), strides = 2)
复制代码
人脸重建
让我们先看一下VAE的重构图像效果:
尽管重建的图片并不完美,但它们至少看起来不错。 VAE设法从输入图像中学习了一些特征,并使用它们来绘制新的面孔。可以看出,VAE可以更好地重建女性面孔。这是由于Celeb_A数据集
中女性的比例较高。这也就是为什么男性的肤色更趋向年轻、女性化。
观察图像背景,由于图像背景的多样性,因此编码器无法将每个细节编码至低维度,因此我们可以看到VAE对背景颜色进行编码,而解码器则基于这些颜色创建模糊的背景。
生成新面孔
为了生成新图像,我们从标准的高斯分布中采样随机数,并将传递给解码器:
z_samples = np.random.normal(loc=0., scale=1, size=(image_num, z_dim))
images = vae.decoder(z_samples.astype(np.float32))
复制代码
但,某些生成的面孔看起来太恐怖了!
我们可以使用采样技巧来提高图像保真度。
采样技巧
可以看到,训练后的VAE可以很好地重建人脸。但,随机抽样潜变量生成的图像中存在问题。为了调试该问题,将数据集中图像输入到VAE解码器中,以获取潜在空间的均值和方差。然后,绘制了每个潜在空间变量的均值:
从理论上讲,它们应该以0为均值且方差为1,但随机采样的样本并不总是与解码器期望的分布匹配。这是采样技巧技巧的地方,收集潜在变量的平均标准差(一个标量值),该标准差用于生成正态分布的样本(200维)。然后,在其中添加了平均均值(200个维度)。
yep,现在生成的图片看起来好多了!
接下来,将介绍如何进行面部属性编辑,而不是生成随机的面孔。
控制人脸属性
潜在空间
本质上,潜在空间意味着潜在变量的每个可能值。在我们的VAE中,它是200个维度的向量(或者称200个变量)。我们希望每个变量都包含独特的语义,例如z[0]代表眼睛,z[1]代表眼睛的颜色,依此类推,事情从来没有那么简单。假设信息是在所有潜在向量中编码的,就可以使用向量算术探索潜在空间。
属性控制
使用一个二维示例解释属性控制的原理。假设现在在地图上的(0,0)点,而目的地位于(x, y)。因此,朝目的地的方向是(x-0, y-0)除以(x, y)的L2范数,可以将方向表示为(x_dot, y_dot)。因此,每次移动(x_dot, y_dot)时,都在朝着目的地移动。每次移动(-2 * x_dot, -2 * y_dot)时,将以两倍的步幅远离目的地。
类似的,如果我们知道了微笑属性的方向向量,则可以将其添加到潜在变量中以使人脸附加微笑属性:
new_z_samples = z_samples + smiling_magnitude*smiling_vector
复制代码
smile_magnitude是我们设置的标量值,因此下一步是找出获取smile_vector的方法。
查找属性向量
Celeb A数据集附带每个图像的面部属性注释。标签是二进制的,指示图像中是否存在特定属性。我们将使用标签和编码的潜在变量来找到我们的方向向量:
- 使用测试数据集或训练数据集中的样本,并使用VAE解码器生成潜矢量。
- 将潜在向量分为两组:具有(正向量)或不具有(负向量)的我们感兴趣的一个属性。
- 分别计算正向量和负向量的平均值。
- 通过从平均正向量中减去平均负向量来获取属性方向向量。
在预处理函数中,返回我们感兴趣的属性的标签。然后,使用lambda函数映射到数据管道:
def preprocess_attrib(sample, attribute):
image = sample['image']
image = tf.image.resize(image, [112,112])
image = tf.cast(image, tf.float32)/255.
return image, sample['attributes'][attribute]
ds = ds.map(lambda x: preprocess_attrib(x, attribute))
复制代码
人脸属性编辑
提取属性向量后,进行以下操作:
- 首先,我们从数据集中获取图像,将其放在首位,作为对比。
- 将人脸编码为潜在变量,然后对其进行解码以生成新人脸,并将其放置在中间。
- 然后,我们向右逐渐增加属性向量。
- 同样,我们向左逐渐减少属性向量。
下图显示了通过内插潜在向量生成的图像:
接下来,我们可以尝试一起更改多个面部属性。在下图中,左侧的图像是随机生成的,并用作基准。右侧是经过一些潜在空间运算后的新图像:
该小部件可在Jupyter notebook中使用。