第2章 GAN初步:2.4 生成人脸图像

2.4 生成人脸图像

在本节中,我们将尝试训练GAN,使它可以生成人脸图像。与生成单色的手写数字图像相比,我们将面临以下两个全新的挑战。

  • 使用彩色图像作为训练数据,并学习生成全彩色图像。
  • 训练数据集中的图像更加多样化,也包含更多容易使人分心的 细节。

2.4.1 彩色图像

在数字图像中,有多种表示颜色的方法。最普遍的一种方法是将色彩描述为红、绿和蓝三种光的混合。我们在图像编辑器中选取颜色时,应该经常用到这种色彩表示方法。所有的数码显示器,比如笔记本电脑或智能手机屏幕,几乎都是由红色、绿色、蓝色的微小像素组成的。
从下图中我们可以看到,红、绿、蓝三色的图层组成了一幅全彩色的美味沙拉图像。
在这里插入图片描述
上图对我们很有帮助,它表明我们可以把红、绿、蓝三色信息分别放在相同大小的数组中。我们知道,一幅大小为28像素× 28像素的单色图像可以用一个28 × 28的数组来表示。
同样大小的彩色图像需要用3个单独的数组来表示,每个数组的大小都是28 × 28。其中一个用于表示红光值,一个用于表示绿光值,另一个用于表示蓝光值。

下图解释了如何用一个三维数组或张量表示一幅全彩色图像。一个维度是图像的高;另一个维度是图像的宽;第3个维度是固定值3,因为这里有3个层,分别对应红、绿、蓝三色值。在这里插入图片描述
很方便的是,很多Python库使用这种数据格式来处理彩色图像,包括matplotlib和它提供的imshow()函数。

2.4.2 CelebA图像数据集

训练GAN的挑战之一是,确保有足够多的训练数据。我们不能指望用几十张或者几百张照片来训练GAN生成人脸图像。
值得庆幸的是,我们可以使用流行的CelebA数据集,其中包含202 599幅名人脸部的图像。所有图像都经过对齐和裁剪,使眼睛和嘴巴在图像中的大概位置居中。
我们可以从以下CelebA的官方主页了解更多关于这个数据集的信息,并查看样本图像。

  • http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html

由于该数据集仅适用于非商业研究以及教育用途,我们在本书中不会直接使用数据集中的图像,而是只展示由GAN生成的图像。我们暂且用两张卡通图像来表示GAN生成的图像。在这里插入图片描述

2.4.3 分层数据格式

CelebA数据集包含数万张JPEG格式的独立图像文件。我们可以将它们解压到一个文件夹中,然后在训练GAN的代码中逐个读取、关闭所有图像文件。这样做虽然没错,但是整体运行速度会很慢。这是因为逐个读取、关闭成千上万个图像文件的效率非常低。如果我们使用Google Drive,问题就更糟糕了。由于使用云端存储,代码和数据之间的距离更远了。

为了提高读取性能,我们可以将数据打包成另外一种格式,以便更有效地支持这种重复读取。我们将使用一种名为HDF5的压缩格式

分层数据格式(hierarchical data format)是一种成熟、开源的压缩数据格式,专门用于存储非常大量的数据,并实现对数据的高效读取。它被普遍应用于科学计算和工程领域中。

之所以称它为分层数据格式,是因为一个HDF5文件可以包含一个或多个组,每个组内又包含一个或多个数据集,甚至包含更多的组。这种管理数据的方式与我们常见的文件夹和文件之间的关系很相似。在这里插入图片描述

HDF5格式和用于访问该格式数据的库有许多实用的特性,可以确保读取性能。其中之一就是通过数据压缩减少从较慢的存储器的数据传输量。其二是将数据智能地映射到较快的RAM(random access memory,随机存储器)内存中,以减少对存储器的请求量。如果没有这些功能,在Google Drive中处理成千上万幅图像文件的速度会慢得无法想象。

即使我们使用的是自己的存储设备,而不是Google Drive,也不妨尝试一下像HDF5这样的格式是否能提高机器学习的性能。尤其是那些需要重复访问大量的数据,而数据又无法被全部装进RAM的任务。

2.4.4 获取数据

下载CelebA数据集,解压20 000幅图像,并把它们打包成HDF5文件。

解压20 000幅图像应该需要4分钟左右。包含这些图像的HDF5文件应该被保存到celeba_dataset文件夹中,占用的存储空间不超过2GB。

2.4.5 查看数据

现在,让我们从HDF5文件中读取数据,并查看文件中的名人脸部图像。
在下一个单元格里,导入支持HDF5文件的库h5py。同时导入numpy和matplotlib。在这里插入图片描述
h5py使用类似字典(dictionary)的对象,管理组和数据集的分层结构。它允许我们以Python的方式访问HDF5包。接下来我们具体解释它的意义。

h5py允许我们使用一个文件对象(file_object),以只读方式打开一个HDF5文件。这与我们在Python中使用open()访问文件的方法很类似。接着,我们通过循环访问文件对象,打印出分层结构顶部的组别名称。
在这里插入图片描述

看来它只有一个名为img_align_celeba的组。我们可以按照访问Python字典的方式来访问它,如
file_object[‘img_align_celeba’]。这样一来,我们就有了一个变量dataset,它包含所有图像数据。

同样,可以使用类似字典的方法读取单幅图像,比如dataset[‘7.jpg’]。图像本身仍是HDF5格式,不过它可以很方便地转换成numpy数组。 我们已经学过如何使用matplotlib的imshow()函数画出numpy数组的图形。

我们看到的应该是全彩色图像。让我们检查一下numpy数组image的大小,看它是否包含所有颜色的信息。
在这里插入图片描述
正如所料,它的高是218像素,宽是178像素,且有3层,分别代表红、绿、蓝三色值。

图像的文件名为0.jpg、1.jpg、2.jpg……以此类推,一直到19999.jpg。为了了解数据集的多样性,我们可以多查看几幅图像。数据集中的人脸图像有不同的发型、肤色、服饰以及姿态等。我们也看到,有些图像的角度异常,或图像中包括可能造成干扰的细节,这些都使机器学习任务变得更具有挑战性。

2.4.6 数据集类

在使用CelebA数据集之前,我们需要改变之前用于MNIST图像数据集的Dataset类。

我们已经知道如何打开一个HDF5文件并从中读取图像,因此只需要进行很简单的改动。让我们看看下面的CelebADataset类。在这里插入图片描述
构造函数__init__() 打开HDF5文件,并打开名为img_align_celeba的组别,以便逐个读取图像。

  • len() 方法很容易,通过len()函数可以很方便地获取一个HDF5组别中样本的数量。
  • getitem()方法通过将索引index转换成图像文件名来检索图像数据。numpy数组的所有值被除以255.0,结果的范围为0~1。

getitem() 中的代码检查索引index是否大于或等于数据集的大小,并抛出(raise)一个IndexError异常(exception)。在之前的例子中,我们不需要这样做。这是因为,如果我们试图访问一个索引超出边界的数组或DataFrame,无论如何都会抛出一个IndexError异常。由于我们访问的是HDF5数据集,超出边界的访问会造成一个错误,但不会是PyTorch所预期的IndexError异常。

我们推荐用可视化方法检查数据。这里,我们使用plot_image()函数。该函数与MNIST数据中的画图函数相似,不过代码更简单,因为我们不需要将数据变形,在这里它的形状已经是(218, 178, 3)。

2.4.7 鉴别器

鉴别器需要读取一幅图像,并试图将它分类为真实图像或生成图像。该功能与MNIST鉴别器相同,所以我们可以复制MNIST鉴别器的代码并重复使用。

MNIST图像与CelebA图像的区别在于它们的大小和色彩深度。大小的不同,意味着我们需要改变鉴别器神经网络的输入层节点数。MNIST图像是28像素× 28像素,所以我们有28×28=784个输入节点。CelebA图像大小是218像素× 178像素,输入节点数也可以通过计算得出。

但是,不要忘了每个彩色像素都包含3个值,分别代表红、绿、蓝光。这意味着,每个图像总共由218×178×3个值组成。因为不希望遗漏任何有价值的信息,我们需要将这些值全部输入鉴别器。这
也意味着鉴别器需要218×178×3=116 412个输入节点。

一个值得讨论的问题是,我们应该如何排列这116 412个值。 在使用MNIST分类器和鉴别器时,我们直接按照在数据集中出现的顺序将像素值输入网络。它们的顺序与图像中的像素一致,沿着每一行从左向右。当到达每一行的右端时,绕到下一行继续。只要排列方式保持一致,排列的顺序实际上并不重要。由于神经网络层是全连接的,因此一层中的每一个节点都与下一层中的每一个节点相通。这意味着一个像素值出现在输入张量中的任何位置都没有区别。

我们可以对彩色图像进行同样的处理。我们可以把大小为218×178×3的图像张量展开或重塑,变成一个长度为116 412的一维张量,然后将该张量输入一个全连接的神经网络。其实,如何展开或重塑并不重要,只要我们保证每次输入鉴别器时以同样的方式处理即可。在这里插入图片描述
我们对以上大部分的代码并不陌生。其中,有一个线性层,以全部3218178个输入节点为输入,连接一个只有100个节点的中间层。在连接到下一个线性层之前,我们使用LeakyReLU激活函数和一个层标准化。接着,一个线性层将100个节点连接到一个输出节点。最后,在输出结果上应用S型激活函数。

一开始的View(2181783)是新代码。它的作用是将大小为(218, 178, 3) 的三维图像张量重塑成一个长度为218×178×3的一维张量。

通过使用View,我们可以方便地重塑Sequential列表中的张量。PyTorch还没有提供实现这一功能的模块。我们可以使用下面的自定义类,它被分享在PyTorch的github问题讨论区。值得注意的是,它继承了nn.Module类。因此,它可以像其他在Sequential列表中使用的模块一样工作。在这里插入图片描述

2.4.8 测试鉴别器

让我们测试一下鉴别器,以确定它至少可以将真实数据和随机生成的像素值区分开。在这里插入图片描述
相比于生成MNIST数字,这段代码更简单。这是因为我们不需要考虑标签和目标值。

从鉴别器的损失图看,网络的确可以学会从随机生成的图像中识别出真实的图像。在这里插入图片描述
运行这个实验需要较长时间。以我的笔记本为例,访问20 000幅图像总共需要1小时19分钟。MNIST数据集有60 000幅图像,全部访问只需要4分钟。之所以有如此大的差别,是因为CelebA图像包括
116 412像素,要比MNIST图像的784像素大得多。具体来说,两者间的差距约为150倍。

2.4.10 生成器

我们需要对生成器进行同样的改动,因为现在我们需要生成的图像不仅更大,而且是彩色的。这意味着输出需要是一个三维张量,大小为(218,178,3)。
看一下改动后的生成器神经网络代码。在这里插入图片描述
输入与之前一样,是一个大小为 100 100 100的种子。输入先与一个包含 3 ∗ 10 ∗ 10 = 300 3*10*10 = 300 31010=300个节点的中间层完全连接。中间层再与包含 3 ∗ 218 ∗ 178 3*218*178 3218178个节点的输出层完全连接。最后,我们将长度为 3 ∗ 218 ∗ 178 3*218*178 3218178 的一维张量重塑成大小为 ( 218 , 178 , 3 ) (218, 178, 3) 218,178,3的三维张量,与彩色图像的大小相同。

2.4.11 检查生成器输出

在开始训练之前,我们最好检查一下生成器的输出大小,并确保运行没有错误。在这里插入图片描述
我们对这段代码应该不陌生。我们先创建一个新的生成器对象并将它存入GPU。接着,我们以随机种子作为生成器输入,计算一个输出。在显示输出图像之前,我们需要用detach()将它与PyTorch的
计算图分离,存回CPU并转换成numpy数组。在这里插入图片描述
由上图可见,输出的大小是正确的,也包含随机颜色。如果图像只有单一颜色或包含特定图案,说明某一部分代码出现错误,需要修改。一个未经训练的生成器应该生成随机数据。

2.4.12 训练GAN

终于可以开始训练GAN了。训练循环总体上与之前一样,只是现在我们将鉴别器和生成器存入GPU,并使用torch.cuda.FloatTensor作为目标值。在这里插入图片描述
代码不需要进行太多修改,这说明我们之前编写的代码的可重用性很高。运行训练循环一个周期需要差不多10分钟。不用GPU加速则可能需要花上3小时!

让我们看一下损失图。鉴别器损失值如下图所示。在这里插入图片描述
看起来训练效果不错,并没有出现不稳定和混乱的情况。大致上,损失值收敛于一个不太大的值。

我们再看一下生成器损失值。在这里插入图片描述
损失图看起来也不错,训练相当平稳。损失值收敛于与鉴别器损失值相近的值。
实际上,两个损失值都很接近理想的二元交叉熵损失ln 2 =0.693,正如附录A所述。这是一个非常好的结果。

让我们查看生成器在训练后生成的一些图像。以下代码在3×2的网格中生成6幅图像,分别由生成器输出。在这里插入图片描述
把多幅生成的图像放在一起显示,是为了检查生成图像的多样性。下图是上面的代码运行两次的结果,所以一共有12幅图像。在这里插入图片描述
赞!我们能看到人脸了。不仅如此,这些图像还相当多样化。生成器已经学会了制作出不同发型、不同脸型甚至不同姿势的人像。尽管有些图像的质量还不尽如人意,但仅用非常简单的代码就能得到这种程度的效果,还是很令人惊讶的。在这里插入图片描述
损失值在理论最优值附近保持稳定。这是一个很好的现象,说明训练没有变得不稳定。让我们再看看生成器损失值。在这里插入图片描述
同样地,损失值在最优值ln 2 = 0.693附近保持稳定。额外训练这6个周期后,让我们再看看12幅图像的效果。在这里插入图片描述
图像的质量有了一些提高。面部特征和细节更加明显了,奇怪的绿脸或蓝脸也不多见了。同时,我们开始看到有明显男性或女性特征的人脸。值得注意的是,发型也是多样化的。

读者可以自己尝试一下使用大一些的神经网络、不同的优化方法以及不同的损失函数,看看能不能进一步提高图像质量。

2.4.13 学习要点

  • 颜色可以用红、绿、蓝三种色光表示。因此,彩色图像常被表示为3层像素值的数组,每层对应三原色之 一,且大小为(长,宽,3)。
  • 在处理由多个文件组成的数据集时,逐个读取和关闭每个文件的方法效率很低,特别是在虚拟环境中。一种推荐的做法是,将数据重新包装成一种为方便频繁、随机访问大量数据而设计的格式。成熟的HDF格式是科学计算中常见的格式。
  • 一个GAN不会记忆训练数据中的样本,也不会复制和粘贴训练样本中的元素。它学习的是训练数据中特征的概率分布,并生成与训练数据看似来自同一分布的数据。
  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值