生成对抗网络项目(二)
四、使用 DCGAN 生成动漫角色
众所周知,卷积层确实擅长处理图像。 他们能够学习重要的特征,例如边缘,形状和复杂的对象,有效的, ,例如神经网络,例如 Inception,AlexNet, 视觉几何组(VGG)和 ResNet。 Ian Goodfellow 等人在其名为《生成对抗网络》的论文中提出了具有密集层的生成对抗网络(GAN),该网络可在以下链接获得。 复杂的神经网络,例如卷积神经网络(CNN),循环神经网络(RNN)和长短期记忆(LSTM)最初并未在 GAN 中进行测试。 深度卷积生成对抗网络(DCGAN)的发展是使用 CNN 进行图像生成的重要一步。 DCGAN 使用卷积层而不是密集层。 它们是由研究人员 Alec Radford , Luke Metz , Soumith Chintala 等,在其论文《使用深度卷积生成对抗网络的无监督表示学习》中提出的,可以在以下链接中找到。 从那时起,DCGAN 被广泛用于各种图像生成任务。 在本章中,我们将使用 DCGAN 架构生成动漫角色。
在本章中,我们将介绍以下主题:
- DCGAN 简介
- GAN 网络的架构细节
- 建立项目
- 为训练准备数据集
- DCGAN 的 Keras 实现以生成动画角色
- 在动漫角色数据集上训练 DCGAN
- 评估训练好的模型
- 通过优化超参数优化网络
- DCGAN 的实际应用
DCGAN 简介
CNN 在计算机视觉任务中非常出色,无论是用于分类图像还是检测图像中的对象。 CNN 善于理解图像,以至于激发研究人员在 GAN 网络中使用 CNN。 最初,GAN 官方论文的作者介绍了仅具有密集层的深层神经网络(DNN)。 在 GAN 网络的原始实现中未使用卷积层。 在以前的 GAN 中,生成器和判别器网络仅使用密集的隐藏层。 相反,作者建议在 GAN 设置中可以使用不同的神经网络架构。
DCGAN 扩展了在判别器和生成器网络中使用卷积层的思想。 DCGAN 的设置类似于朴素 GAN。 它由两个网络组成:生成器和判别器。 生成器是具有卷积层的 DNN,而判别器是具有卷积层的 DNN。 训练 DCGAN 类似于训练普通 GAN 网络。 在第一章中,我们了解到网络参与了非合作博弈,其中判别器网络将其误差反向传播到生成器网络,生成器网络使用此误差来提高其权重。
在下一部分中,我们将探索两个网络的架构。
DCGAN 的架构细节
如前所述,DCGAN 网络在两个网络中都使用卷积层。 重申一下,CNN 是一个具有卷积层,紧随其后的归一化或池化层以及紧随其后的激活函数的网络。 在 DCGAN 中,判别器网络会拍摄图像,在卷积和池化层的帮助下对图像进行降采样,然后使用密集的分类层将图像分类为真实图像或伪图像。 生成器网络从潜在空间中获取随机噪声向量,使用上采样机制对其进行上采样,最后生成图像。 我们使用 Leaky ReLU 作为隐藏层的激活函数,并在 0.4 和 0.7 之间进行滤除以避免过拟合。
让我们看一下两个网络的配置。
配置生成器网络
在继续之前,让我们看一下生成器网络的架构:
来源:arXiv:1511.06434 [cs.LG]
上图包含了生成器网络架构中的不同层,并显示了它如何生成分辨率为64 x 64 x 3的图像。
DCGAN 的生成器网络包含 10 层。 它执行跨步卷积以增加张量的空间分辨率。 在 Keras 中,上采样和卷积层的组合等于跨步卷积层。 基本上,生成器网络会从均匀分布中获取采样的噪声向量,并不断对其进行转换,直到生成最终图像为止。 换句话说,它采取形状的张量(batch_size, 100),并输出形状的张量(batch_size, 64, 64, 3)。
让我们看一下生成器网络中的不同层:
| 编号 | 层名称 | 配置 |
|---|---|---|
| 1 | 输入层 | input_shape=(batch_size, 100),output_shape=(batch_size, 100) |
| 2 | 密集层 | neurons=2048,input_shape=(batch_size, 100),output_shape=(batch_size, 2048),activation='relu' |
| 3. | 密集层 | neurons=16384,input_shape=(batch_size, 100),output_shape=(batch_size, 2048),batch_normalization=Yes,activation='relu' |
| 4. | 重塑层 | input_shape=(batch_size=16384),outp ut_shape=(batch_size, 8, 8, 256) |
| 5. | 上采样层 | size=(2, 2),input_shape=(batch_size, 8, 8, 256),output_shape=(batch_size, 16, 16, 256) |
| 6. | 2D 卷积层 | filters=128,kernel_size=(5, 5),strides=(1, 1),padding='same',input_shape=(batch_size, 16, 16, 256),output_shape=(batch_size, 16, 16, 128), activation='relu' |
| 7. | 上采样层 | size=(2, 2),input_shape=(batch_size, 16, 16, 128),output_shape=(batch_size, 32, 32, 128) |
| 8. | 2D 卷积层 | filters=64,kernel_size=(5, 5),strides=(1, 1),padding='same',activation=ReLU,input_shape=(batch_size, 32, 32, 128),output_shape=(batch_size, 32, 32, 64),activation='relu' |
| 9. | 上采样层 | size=(2, 2),input_shape=(batch_size, 32, 32, 64),output_shape=(batch_size, 64, 64, 64) |
| 10. | 2D 卷积层 | filters=3,kernel_size=(5, 5),strides=(1, 1),padding='same',activation=ReLU,input_shape=(batch_size, 64, 64, 64),output_shape=(batch_size, 64, 64, 3),activation='tanh' |
L 等人研究了张量如何从第一层流到最后一层。 下图显示了不同层的输入和输出形状:
该配置对具有 TensorFlow 后端和channels_last格式的 Keras API 有效。
配置判别器网络
在继续之前,让我们看一下判别器网络的架构:
上图给出了生成器网络架构的顶层概述。
如前所述,判别器网络是一个包含 10 层的 CNN(您可以根据需要向网络添加更多层)。 基本上,它会拍摄大小为64 x 64 x 3的图像,使用 2D 卷积层对其进行下采样,然后将其传递到完全连接的层进行分类。 它的输出是对给定图像是伪图像还是真实图像的预测。 可以为 0 或 1;可以为 0。 如果输出为 1,则传递到判别器的图像是真实的;如果输出为 0,则传递的图像是伪图像。
让我们看一下判别器网络中的各层:
| 编号 | 层名称 | 配置 |
|---|---|---|
| 1. | 输入层 | input_shape=(batch_size, 64, 64, 3),output_shape=(batch_size, 64, 64, 3) |
| 2. | 2D 卷积层 | filters=128,kernel_size=(5, 5),strides=(1, 1),padding='valid',input_shape=(batch_size, 64, 64, 3),output_shape=(batch_size, 64, 64, 128), activation='leakyrelu',leaky_relu_alpha=0.2 |
| 3. | 2D 最大池化 | pool_size=(2, 2),input_shape=(batch_size, 64, 64, 128),output_shape=(batch_size, 32, 32, 128) |
| 4. | 2D 卷积层 | filters=256,kernel_size=(3, 3),strides=(1, 1),padding='valid',input_shape=(batch_size, 32, 32, 128),output_shape=(batch_size, 30, 30, 256), activation='leakyrelu',leaky_relu_alpha=0.2 |
| 5. | 2D 最大池化 | pool_size=(2, 2),input_shape=(batch_size, 30, 30, 256),output_shape=(batch_size, 15, 15, 256) |
| 6. | 2D 卷积层 | filters=512,kernel_size=(3, 3),strides=(1, 1),padding='valid',input_shape=(batch_size, 15, 15, 256),output_shape=(batch_size, 13, 13, 512), activation='leakyrelu',leaky_relu_alpha=0.2 |
| 7. | 2D 最大池化 | pool_size=(2, 2),input_shape=(batch_size, 13, 13, 512),output_shape=(batch_size, 6, 6, 512) |
| 8. | 展开层 | input_shape=(batch_size, 6, 6, 512),output_shape=(batch_size, 18432) |
| 9. | 密集层 | neurons=1024,input_shape=(batch_size, 18432),output_shape=(batch_size, 1024),activation='leakyrelu','leakyrelu_alpha'=0.2 |
| 10. | 密集层 | neurons=1,input_shape=(batch_size, 1024),output_shape=(batch_size, 1),activation='sigmoid' |
L 等人研究了张量如何从第一层流到最后一层。 下图显示了不同层的输入和输出形状:
该配置对具有 TensorFlow 后端和channels_last格式的 Keras API 有效。
设置项目
我们已经克隆/下载了所有章节的完整代码。 下载的代码包含一个名为Chapter04的目录,该目录包含本章的全部代码。 执行以下命令来设置项目:
- 首先,导航到父目录,如下所示:
cd Generative-Adversarial-Networks-Projects
- 现在,将目录从当前目录更改为
Chapter04:
cd Chapter04
- 接下来,为该项目创建一个 Python 虚拟环境:
virtualenv venv
virtualenv venv -p python3 # Create a virtual environment using
python3 interpreter
virtualenv venv -p python2 # Create a virtual environment using
python2 interpreter
我们将为此项目使用此新创建的虚拟环境。 每章都有其自己单独的虚拟环境。
- 接下来,激活虚拟环境:
source venv/bin/activate
激活虚拟环境后,所有其他命令将在此虚拟环境中执行。
- 接下来,通过执行以下命令来安装
requirements.txt文件中给出的所有要求:
pip install -r requirements.txt
您可以参考 README.md 文件,以获取有关如何设置项目的更多说明。 开发人员经常会遇到依赖关系不匹配的问题。 为每个项目创建一个单独的虚拟环境将解决此问题。
在本节中,我们已成功设置项目并安装了所需的依赖项。 在下一部分中,我们将使用数据集,包括下载和清理数据集。
下载并准备动漫角色数据集
要训练 DCGAN 网络,我们需要一个动漫人物数据集,其中包含人物的裁剪面孔。 收集数据集有多种方法。 我们可以使用公开可用的数据集,也可以抓取一个,只要不违反网站的抓取策略即可。 在本章中,我们将仅出于教育和演示目的刮取图像。 我们使用名为 gallery-dl 的搜寻器工具从 pixiv.net 抓取了图像。 这是一个命令行工具,可用于从网站下载图像集,例如 pixiv.net,exhentai.org,danbooru.donmai.us 和更多。 它可通过以下链接获得。
下载数据集
在本节中,我们将介绍安装依赖项和下载数据集所需的不同步骤。 在执行以下命令之前,激活为此项目创建的虚拟环境:
- 执行以下命令安装
gallery-dl:
pip install --upgrade gallery-dl
- 或者,您可以使用以下命令安装
gallery-dl的最新开发版本:
pip install --upgrade https://github.com/mikf/gallery-dl/archive/master.zip
- 如果上述命令不起作用,请按照官方存储库中的说明进行操作:
# Official gallery-dl Github repo
https://github.com/mikf/gallery-dl
- 最后,执行以下命令以使用
gallery-dl.从danbooru.donmai.us下载图像:
gallery-dl https://danbooru.donmai.us/posts?tags=face
下载图像需要您自担风险。 所提供的信息仅用于教育目的,我们不支持非法刮取。 我们没有图像的版权,因为图像由其各自所有者托管。 出于商业目的,请联系网站的各自所有者或您所使用的内容。
探索数据集
在裁剪或调整图像大小之前,请查看下载的图像:
如您所见,有些图像还包含其他身体部位,我们在训练图像中不需要这些部位。 在下一部分中,我们将仅从这些图像中裁剪出人脸。 此外,我们会将所有图像调整为训练所需的大小。
裁剪数据集中的图像并调整其大小
在本节中,我们将从图像中裁剪出面孔。 我们将使用python-animeface从图像中裁剪出面孔。 这是一个开源 GitHub 存储库,可从命令行的图像中自动裁剪人脸。 它可以通过以下链接公开获得。
执行以下步骤来裁剪图像并调整其大小:
- 首先,下载
python-animeface:
pip install animeface
- 接下来,导入任务所需的模块:
import glob
import os
import animeface
from PIL import Image
- 接下来,定义参数:
total_num_faces = 0
- 接下来,遍历所有图像以进行裁剪并一一调整其大小:
for index, filename in
enumerate(glob.glob('/path/to/directory/containing/images/*.*')):
- 在循环内,打开当前图像并检测其中的人脸:
try:
# Open image
im = Image.open(filename)
# Detect faces
faces = animeface.detect(im)
except Exception as e:
print("Exception:{}".format(e))
continue
- 接下来,获取图像中检测到的人脸坐标:
fp = faces[0].face.pos
# Get coordinates of the face detected in the image
coordinates = (fp.x, fp.y, fp.x+fp.width, fp.y+fp.height)
- 现在,将人脸裁剪出图像:
# Crop image cropped_image = im.crop(coordinates)
- 接下来,将裁剪后的人脸图像调整为
(64, 64)的大小:
# Resize image cropped_image = cropped_image.resize((64, 64), Image.ANTIALIAS)
- 最后,将裁剪并调整大小的图像保存到所需目录:
cropped_image.save("/path/to/directory/to/store/cropped/images/filename.png"))
包装在 Python 函数中的完整代码如下所示:
import glob
import os
import animeface
from PIL import Image
total_num_faces = 0 for index, filename in enumerate(glob.glob('/path/to/directory/containing/images/*.*')):
# Open image and detect faces
try:
im = Image.open(filename)
faces = animeface.detect(im)
except Exception as e:
print("Exception:{}".format(e))
continue
# If no faces found in the current image if len(faces) == 0:
print("No faces found in the image")
continue fp = faces[0].face.pos
# Get coordinates of the face detected in the image
coordinates = (fp.x, fp.y, fp.x+fp.width, fp.y+fp.height)
# Crop image
cropped_image = im.crop(coordinates)
# Resize image
cropped_image = cropped_image.resize((64, 64), Image.ANTIALIAS)
# Show cropped and resized image
# cropped_image.show() # Save it in the output directory cropped_image.save("/path/to/directory/to/store/cropped/images/filename.png"))
print("Cropped image saved successfully")
total_num_faces += 1 print("Number of faces detected till now:{}".format(total_num_faces))
print("Total number of faces:{}".format(total_num_faces))
前面的脚本将从包含下载图像的文件夹中加载所有图像,使用python-animeface库检测人脸,然后从初始图像中裁剪出人脸。 然后,裁切后的图像将被调整为64 x 64的大小。如果要更改图像的大小,请相应地更改生成器和判别器的架构。 我们现在准备在我们的网络上工作。
使用 Keras 实现 DCGAN
在本节中,我们将在 Keras 框架中编写 DCGAN 的实现。 Keras 是一个使用 TensorFlow 或 Teano 作为后端的元框架。 它提供了用于神经网络的高级 API。 与低级框架(如 TensorFlow)相比,它还具有预构建的神经网络层,优化器,正则化器,初始化器和数据预处理层,可轻松进行原型制作。 让我们开始编写生成器网络的实现。
生成器
如 DCGAN 部分的“架构”中所述,生成器网络由一些 2D 卷积层,上采样层,整形层和批归一化层组成 。 在 Keras 中,每个操作都可以指定为一个层。 甚至激活函数也是 Keras 中的层,可以像正常的密集层一样添加到模型中。
执行以下步骤来创建生成器网络:
- 让我们开始创建一个
SequentialKeras 模型:
gen_model = Sequential()
- 接下来,添加一个具有 2,048 个节点的密集层,然后添加一个激活层
tanh:
gen_model.add(Dense(units=2048))
gen_model.add(Activation('tanh'))
- 接下来,添加第二层,它也是一个具有 16,384 个神经元的密集层。 接下来是
batch normalization层,其中default hyperparameters和tanh作为激活函数:
gen_model.add(Dense(256`8`8))
gen_model.add(BatchNormalization())
gen_model.add(Activation('tanh'))
第二密集层的输出是大小为16384的张量。 此处,(256, 8, 8)是密集层中神经元的数量。
- 接下来,向网络中添加一个重塑层,以将张量从最后一层重塑为(
batch_size, 8, 8, 256)**:**形状的张量
# Reshape layer
gen_model.add(Reshape((8, 8, 256), input_shape=(256`8`8,)))
- 接下来,添加 2D 上采样层以将形状从
(8, 8, 256)更改为(16, 16, 256)。 上采样大小为(2, 2),这将张量的大小增加到其原始大小的两倍。 在这里,我们有 256 个张量为16 x 16:的张量。
gen_model.add(UpSampling2D(size=(2, 2)))
- 接下来,添加一个 2D 卷积层。 这使用指定数量的过滤器在张量上应用 2D 卷积。 在这里,我们使用 64 个过滤器和一个
(5, 5)形状的内核:
gen_model.add(Conv2D(128, (5, 5), padding='same'))
gen_model.add(Activation('tanh'))
- 接下来,添加 2D 向上采样层以将张量的形状从
(batch_size, 16, 16, 64)更改为(batch_size, 32, 32, 64):
gen_model.add(UpSampling2D(size=(2, 2)))
2D 上采样层将张量的行和列分别以[0]和[1]的大小重复 。
- 接下来,在第二个 2D 卷积层上添加
64过滤器和,将(5, 5)的核大小tanh作为激活函数:
gen_model.add(Conv2D(64, (5, 5), padding='same'))
gen_model.add(Activation('tanh'))
- 接下来,添加 2D 上采样层,将形状从
(batch_size, 32, 32, 64)更改为(batch_size, 64, 64, 64):
gen_model.add(UpSampling2D(size=(2, 2)))
- 最后,在第三个 2D 卷积层上添加三个过滤器,核大小
(5, 5),然后是tanh作为激活函数:
gen_model.add(Conv2D(3, (5, 5), padding='same'))
gen_model.add(Activation('tanh'))
生成器网络将输出(batch_size, 64, 64, 3)形状的张量。 这批张量中的一个图像张量类似于具有三个通道的大小为64 x 64的图像: 红色,绿色和蓝色(RGB)。
用 Python 方法包装的生成器网络的完整代码如下所示:
def get_generator():
gen_model = Sequential()
gen_model.add(Dense(input_dim=100, output_dim=2048))
gen_model.add(LeakyReLU(alpha=0.2))
gen_model.add(Dense(256 * 8 * 8))
gen_model.add(BatchNormalization())
gen_model.add(LeakyReLU(alpha=0.2))
gen_model.add(Reshape((8, 8, 256), input_shape=(256 * 8 * 8,)))
gen_model.add(UpSampling2D(size=(2, 2)))
gen_model.add(Conv2D(128, (5, 5), padding='same'))
gen_model.add(LeakyReLU(alpha=0.2))
gen_model.add(UpSampling2D(size=(2, 2)))
gen_model.add(Conv2D(64, (5, 5), padding='same'))
gen_model.add(LeakyReLU(alpha=0.2))
gen_model.add(UpSampling2D(size=(2, 2)))
gen_model.add(Conv2D(3, (5, 5), padding='same'))
gen_model.add(LeakyReLU(alpha=0.2))
return gen_model
现在我们已经创建了生成器网络,让我们开始创建判别器网络。
判别器
如 DCGAN 的架构中所述,判别器网络具有三个 2D 卷积层,每个层均具有激活函数,后跟两个最大合并层。 网络的尾部包含两个完全连接的(密集)层,用作分类层。 首先,让我们看一下判别器网络中的不同层:
- 所有卷积层都具有
LeakyReLU作为激活函数,其alpha值为 0.2 - 卷积层分别具有 128、256 和 512 个过滤器。 它们的核大小分别为
(5, 5),(3, 3)和(3, 3)。 - 在卷积层之后,我们有一个展开层,它将输入平坦化为一维张量。
- 此后,网络具有两个密集层,分别具有 1,024 个神经元和一个神经元。
- 第一密集层具有
LeakyReLU作为激活函数,而第二层具有 Sigmoid 作为激活函数。 Sigmoid 激活用于二分类。 我们正在训练辨别器网络,以区分真实图像还是伪图像。
执行以下步骤来创建判别器网络:
- 让我们开始创建一个
SequentialKeras 模型:
dis_model = Sequential()
- 添加一个 2D 卷积层,该层采用形状为
(64, 64, 3)的输入图像。 该层的超参数如下。 另外,添加具有0.2的alpha值的LeakyReLU作为激活函数:- 过滤器:128
- 核大小:
(5, 5) - 填充:相同
dis_model.add(Conv2D(filters=128, kernel_size=5, padding='same',
input_shape=(64, 64, 3)))
dis_model.add(LeakyReLU(alpha=0.2))
- 接下来,添加池大小为
(2, 2)的 2D 最大池化层。 最大池用于对图像表示进行下采样,并通过在表示的非重叠子区域上使用最大过滤来应用它:
dis_model.add(MaxPooling2D(pool_size=(2, 2)))
来自第一层的输出张量的形状将为(batch_size, 32, 32, 128)。
- 接下来,添加具有以下配置的另一个 2D 卷积层:
- 过滤器:256
- 核大小:
(3, 3) - 激活函数:
LeakyReLU,具有alpha0.2 - 2D 最大池中的池大小:
(2, 2)
dis_model.add(Conv2D(filters=256, kernel_size=3))
dis_model.add(LeakyReLU(alpha=0.2))
dis_model.add(MaxPooling2D(pool_size=(2, 2)))
该层的输出张量的形状将为 (batch_size, 30, 30, 256)。
- 接下来,添加具有以下配置的第三个 2D 卷积层:
- 过滤器: 512
- 核大小:
(3, 3) - 激活函数:
LeakyReLU,带有alpha0.2 - 2D 最大池中的池大小:
(2, 2)
dis_model.add(Conv2D(512, (3, 3)))
dis_model.add(LeakyReLU(alpha=0.2))
dis_model.add(MaxPooling2D(pool_size=(2, 2)))
该层的输出张量的形状将为 (batch_size, 13, 13, 512)。
- 接下来,添加一个展开层。 这将使输入变平而不影响批量大小。 它产生一个二维张量:
dis_model.add(Flatten())
来自平坦化层的张量的输出形状将为(batch_size, 18432,)。
- 接下来,添加具有
1024神经元和alpha0.2 作为激活函数的LeakyReLU的密集层:
dis_model.add(Dense(1024))
dis_model.add(LeakyReLU(alpha=0.2))
- 最后,添加带有一个神经元的密集层以进行二分类。 Sigmoid 函数最适合二分类,因为它给出了分类的可能性:
dis_model.add(Dense(1))
dis_model.add(Activation('tanh'))
网络将生成形状为(batch_size, 1)的输出张量。 输出张量包含类的概率。
包裹在 Python 方法中的判别器网络的完整代码如下:
def get_discriminator():
dis_model = Sequential()
dis_model.add(
Conv2D(128, (5, 5),
padding='same',
input_shape=(64, 64, 3))
)
dis_model.add(LeakyReLU(alpha=0.2))
dis_model.add(MaxPooling2D(pool_size=(2, 2)))
dis_model.add(Conv2D(256, (3, 3)))
dis_model.add(LeakyReLU(alpha=0.2))
dis_model.add(MaxPooling2D(pool_size=(2, 2)))
dis_model.add(Conv2D(512, (3, 3)))
dis_model.add(LeakyReLU(alpha=0.2))
dis_model.add(MaxPooling2D(pool_size=(2, 2)))
dis_model.add(Flatten())
dis_model.add(Dense(1024))
dis_model.add(LeakyReLU(alpha=0.2))
dis_model.add(Dense(1))
dis_model.add(Activation('sigmoid'))
return dis_model
在本节中,我们已成功实现了判别器和生成器网络。 在下一部分中,我们将在“下载和准备动漫角色数据集”部分中准备的数据集上训练模型。
训练 DCGAN
同样,训练 DCGAN 类似于训练朴素 GAN 网络。 这是一个四步过程:
- 加载数据集。
- 构建和编译网络。
- 训练判别器网络。
- 训练生成器网络。
我们将在本节中一步一步地进行这些步骤。
让我们从定义变量和超参数开始:
dataset_dir = "/Path/to/dataset/directory/*.*" batch_size = 128 z_shape = 100 epochs = 10000 dis_learning_rate = 0.0005 gen_learning_rate = 0.0005 dis_momentum = 0.9 gen_momentum = 0.9 dis_nesterov = True gen_nesterov = True
在这里,我们为训练指定了不同的超参数。 现在,我们将看到如何为训练加载数据集。
加载样本
要训练 DCGAN 网络,我们需要将数据集加载到内存中,并且需要定义一种机制来加载成批的内存。 执行以下步骤以加载数据集:
- 首先加载所有裁剪,调整大小并保存在
cropped文件夹中的图像。 正确指定目录的路径,以便glob.glob方法可以创建其中所有文件的列表。 要读取图像,请使用scipy.misc模块中的imread方法。 以下代码显示了加载目录中所有图像的不同步骤:
# Loading images all_images = []
for index, filename in enumerate(glob.glob('/Path/to/cropped/images/directory/*.*')):
image = imread(filename, flatten=False, mode='RGB')
all_images.append(image)
- 接下来,为所有图像创建一个
ndarray。 最终ndarray的形状将为(total_num_images, 64, 64, 3)。 此外,标准化所有图像:
# Convert to Numpy ndarray
X = np.array(all_images)
X = (X - 127.5) / 127.5
现在我们已经加载了数据集,接下来我们将看到如何构建和编译网络。
建立和编译网络
在本节中,我们将构建和编译训练所需的网络:
- 首先定义训练所需的优化器,如下所示:
# Define optimizers dis_optimizer = SGD(lr=dis_learning_rate, momentum=dis_momentum, nesterov=dis_nesterov)
gen_optimizer = SGD(lr=gen_learning_rate, momentum=gen_momentum, nesterov=gen_nesterov)
- 接下来,创建生成器模型的实例,并编译生成器模型(编译将初始化权重参数,优化器算法,损失函数以及使用网络所需的其他必要步骤):
gen_model = build_generator()
gen_model.compile(loss='binary_crossentropy', optimizer=gen_optimizer)
使用binary_crossentropy作为生成器网络的loss函数,并使用gen_optimizer作为优化器。
- 接下来,创建判别模型的实例,并对其进行编译,如下所示:
dis_model = build_discriminator()
dis_model.compile(loss='binary_crossentropy', optimizer=dis_optimizer)
同样,使用binary_crossentropy作为判别器网络的损失函数,并使用dis_optimizer作为优化器。
- 接下来,创建一个对抗模型。 一个对抗者将两个网络都包含在一个模型中。 对抗模型的架构如下:
- 输入 -> 生成器 -> 判别器 -> 输出
创建和编译对抗模型的代码如下:
adversarial_model = Sequential()
adversarial_model.add(gen_model)
dis_model.trainable = False adversarial_model.add(dis_model)
当我们训练该网络时,我们不想训练判别器网络,因此在将其添加到对抗模型之前,使其变为不可训练的。
编译对抗模型,如下所示:
adversarial_model.compile(loss='binary_crossentropy', optimizer=gen_optimizer)
使用binary_crossentropy作为损失函数,使用gen_optimizer作为对抗模型的优化器。
在开始训练之前,添加 TensorBoard 以可视化显示损失,如下所示:
tensorboard = TensorBoard(log_dir="logs/{}".format(time.time()), write_images=True, write_grads=True, write_graph=True)
tensorboard.set_model(gen_model)
tensorboard.set_model(dis_model)
我们将针对指定的迭代次数训练网络,因此创建一个应运行指定次数的循环。 在每个周期内,我们将在大小为 128 的微型批量上训练网络。 计算需要处理的批量数量:
for epoch in range(epcohs):
print("Epoch is", epoch)
number_of_batches = int(X.shape[0] / batch_size)
print("Number of batches", number_of_batches)
for index in range(number_of_batches):
现在,我们将仔细研究训练过程。 以下几点说明了 DCGAN 训练中涉及的不同步骤:
- 最初,这两个网络都是幼稚的并且具有随机权重。
- 训练 DCGAN 网络的标准过程是首先对一批样本进行判别器训练。
- 为此,我们需要假样本和真实样本。 我们已经有了真实的样本,因此现在需要生成伪样本。
- 要生成伪样本,请在均匀分布上创建形状为
(100, )的潜向量。 将此潜向量馈入未经训练的生成器网络。 生成器网络将生成伪样本,我们将其用于训练判别器网络。 - 合并真实图像和伪图像以创建一组新的样本图像。 我们还需要创建一个标签数组:真实图像使用标签 1,伪图像使用标签 0。
训练判别器网络
执行以下步骤来训练判别器网络:
- 首先从正态分布中采样一批噪声向量,如下所示:
z_noise = np.random.normal(0, 1, size=(batch_size, z_shape))
要对值进行采样,请使用 Numpy 库中np.random模块中的normal()方法。
- 接下来,从所有图像集中采样一批真实图像:
image_batch = X[index * batch_size:(index + 1) * batch_size]
- 接下来,使用生成器网络生成一批伪图像:
generated_images = gen_model.predict_on_batch(z_noise)
- 接下来,创建真实标签和伪标签:
y_real = np.ones(batch_size) - np.random.random_sample(batch_size) * 0.2 y_fake = np.random.random_sample(batch_size) * 0.2
- 接下来,在真实图像和真实标签上训练判别器网络:
dis_loss_real = dis_model.train_on_batch(image_batch, y_real)
- 同样,在伪造图像和伪造标签上对其进行训练:
dis_loss_fake = dis_model.train_on_batch(generated_images, y_fake)
- 接下来,计算平均损失并将其打印到控制台:
d_loss = (dis_loss_real+dis_loss_fake)/2 print("d_loss:", d_loss)
到目前为止,我们一直在训练判别器网络。 在下一部分中,让我们训练生成器网络。
训练生成器网络
为了训练生成器网络,我们必须训练对抗模型。 当我们训练对抗模型时,它只训练生成器网络,而冻结判别器网络。 由于我们已经训练过判别器网络,因此我们不会对其进行训练。 执行以下步骤来训练对抗模型:
- 首先重新创建一批噪声向量。 从高斯/正态分布中采样以下噪声向量:
z_noise = np.random.normal(0, 1, size=(batch_size, z_shape))
- 接下来,对这批噪声向量训练对抗模型,如下所示:
g_loss = adversarial_model.train_on_batch(z_noise, [1] * batch_size)
我们在一批噪声向量和实数标签上训练对抗模型。 在这里,实数标签是一个所有值均等于 1 的向量。我们还在训练生成器,以欺骗判别器网络。 为此,我们为它提供一个向量,该向量的所有值均等于 1。在此步骤中,生成器将接收来自生成器网络的反馈,并相应地进行改进。
- 最后,将生成器损失打印到控制台以跟踪损失:
print("g_loss:", g_loss)
有一种被动的方法可以评估训练过程。 每 10 个周期后,生成伪造图像并手动检查图像质量:
if epoch % 10 == 0:
z_noise = np.random.normal(0, 1, size=(batch_size, z_shape))
gen_images1 = gen_model.predict_on_batch(z_noise)
for img in gen_images1[:2]:
save_rgb_img(img, "results/one_{}.png".format(epoch))
这些图像将帮助您决定是继续训练还是尽早停止训练。 如果生成的高分辨率图像的质量良好,请停止训练。 或者继续训练,直到您的模型变好为止。
我们已经成功地在动画角色数据集上训练了 DCGAN 网络。 现在我们可以使用该模型生成动漫人物图像。
生成图像
为了生成图像,我们需要一个从潜在空间采样的噪声向量。 Numpy 有一种称为uniform()的方法,可以根据均匀分布生成向量。 让我们看看如何在以下步骤中生成图像:
- 通过添加以下代码行,创建大小为
(batch_size, 100)的噪声向量:
z_noise = np.random.normal(0, 1, size=(batch_size, z_shape))
- 然后,使用生成器模型的
predict_on_batch方法生成图像。 将上一步中创建的噪声向量馈入其中:
gen_images = gen_model.predict_on_batch(z_noise)
- 现在我们已经生成了图像,通过添加以下代码行将其保存。 创建一个名为
results的目录来存储生成的图像:
imsave('results/image_{}.jpg'.format(epoch),gen_images[0])
现在,您可以打开这些生成的图像以测量生成的模型的质量。 这是一种评估模型表现的被动方法。
保存模型
在 Keras 中保存模型只需要一行代码。 要保存生成器模型,请添加以下行:
# Specify the path for the generator model
gen_model.save("directory/for/the/generator/model.h5")
同样,通过添加以下行来保存判别器模型:
# Specify the path for the discriminator model
dis_model.save("directory/for/the/discriminator/model.h5")
可视化生成的图像
在将网络训练了 100 个时间段后,生成器将开始生成合理的图像。 让我们看一下生成的图像。
在 100 个周期之后,图像显示如下:
在 200 个周期之后,图像显示如下:
要生成非常好的图像,请将网络训练 10,000 个周期。
可视化损失
为了可视化训练的损失,请按以下方式启动 TensorBoard 服务器:
tensorboard --logdir=logs
现在,在浏览器中打开localhost:6006。 Tensorboard 的标量部分包含两种损失的图:
这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 使用超参数,然后选择一组您认为可以提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。
可视化图
Tensorboard 的GRAPHS部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的操作:
调整超参数
超参数是模型的属性,在模型训练期间是固定的。 不同的参数可以具有不同的精度。 让我们看一下一些常用的超参数:
- 学习率
- 批量大小
- 周期数
- 生成器优化器
- 判别器优化器
- 层数
- 密集层中的单元数
- 激活函数
- 损失函数
在“使用 Keras 实现 DCGAN”的部分中,学习率是固定的:生成器模型为 0.0005,判别器模型为 0.0005。 批量大小为 128。调整这些值可能会导致我们创建更好的模型。 如果您的模型没有生成合理的图像,请尝试更改这些值,然后再次运行模型。
DCGAN 的实际应用
可以为不同的使用案例定制 DCGAN。 DCGAN 的各种实际应用包括:
-
动画角色的生成:目前,动画师使用计算机软件手动绘制字符,有时还绘制在纸上。 这是一个手动过程,通常需要很多时间。 使用 DCGAN,可以在更短的时间内生成新的动漫角色,从而改善了创作过程。
-
数据集的扩充:如果您想训练一个监督的机器学习模型,要训练一个好的模型,您将需要一个大的数据集。 DCGAN 可以通过扩展现有数据集来提供帮助,因此可以增加监督模型训练所需的数据集大小。
-
MNIST 字符的生成:MNIST 数据集包含 60,000 张手写数字图像。 要训练复杂的监督学习模型,MNIST 数据集是不够的。 DCGAN 一旦受过训练,将生成可以添加到原始数据集中的新数字。
-
人脸生成:DCGAN 使用卷积神经网络,非常擅长生成逼真的图像。
-
特征提取器:训练后,可以使用判别器从中间层提取特征。 这些提取的特征在样式迁移和人脸识别等任务中很有用。 样式迁移涉及生成图像的内部表示,用于计算样式和内容损失。 请参阅以下论文,以了解有关样式迁移的更多信息。
总结
在本章中,我们介绍了深度卷积生成对抗网络。 我们从基本介绍 DCGAN 开始,然后深入探讨了 DCGAN 网络的架构。 之后,我们设置项目并安装必要的依赖项。 然后,我们研究了下载和准备数据集所需的不同步骤。 然后,我们准备了网络的 Keras 实现,并在我们的数据集中对其进行了训练。 经过训练后,我们将其用于生成新的动漫角色。 我们还探讨了 DCGAN 在实际用例中的不同应用。
在下一章中,我们将研究用于高分辨率图像生成的 SRGAN。
五、使用 SRGAN 生成逼真的图像
超分辨率生成对抗网络或 SRGAN ,是生成对抗网络(GAN), 低分辨率图像中的高分辨率图像,具有更好的细节和更高的质量 。 CNN 较早用于产生高分辨率图像,该图像可以更快地训练并达到高水平的精度。 但是,在某些情况下,它们无法恢复更精细的细节,并且通常会生成模糊的图像。 在本章中,我们将在 Keras 框架中实现一个 SRGAN 网络,该网络将能够生成高分辨率图像。 SRGAN 在标题为《使用生成对抗网络的逼真的单图像超分辨率》的论文中引入,作者是 Christian Ledig,Lucas Theis,Ferenc Huszar, Jose Caballero,Andrew Cunningham 等,可以在以下链接中找到。
在本章中,将涵盖以下主题:
- SRGAN 简介
- 建立项目
- 下载 CelebA 数据集
- SRGAN 的 Keras 实现
- 训练和优化 SRGAN 网络
- SRGAN 的实际应用
SRGAN 简介
与其他 GAN 一样,SRGAN 包含一个生成器网络和一个判别器网络。 两个网络都很深。 这两个网络的功能指定如下:
- 生成器:生成器网络拍摄大小为
64x64x3的低分辨率图像,并且在一系列卷积和上采样层之后, 生成形状为256x256x3的超分辨率图片 - 判别器:判别器网络拍摄高分辨率图像,并尝试识别给定的图像是真实的(来自真实数据样本)还是(由生成器)伪造的
SRGAN 的架构
在 SRGAN 中,这两个网络都是深度卷积神经网络。 它们包含卷积层和上采样层。 每个卷积层之后是批量归一化操作和一个激活层。 我们将在以下各节中详细探讨网络。 下图显示了 SRGAN 的架构:
在以下各节中,让我们详细了解网络的架构。
生成器网络的架构
如前一节所述,生成器网络是深度卷积神经网络。 生成器网络包含以下块:
- 前残差块
- 残差块
- 后残差块
- 上采样块
- 最后的卷积层
让我们一一探讨这些块:
- 前残差块:前残差块包含单个 2D 卷积层和 relu 作为激活函数。 块的配置如下:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 2D 卷积层 | Filters=64, kernel_size=3, strides=1, padding='same', activation='relu' | (64, 64, 3) | (64, 64, 64) |
- 残差块:残差块包含两个 2D 卷积层。 两层之后是动量值等于 0.8 的批归一化层。 每个残差块的配置如下:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 2D 卷积层 | Filters=64, kernel_size=3, strides=1, padding='same', activation='relu' | (64, 64, 64) | (64, 64, 64) |
| 批量规范化层 | Momentum=0.8 | (64, 64, 64) | (64, 64, 64) |
| 2D 卷积层 | Filters=64, kernel_size=3, strides=1, padding='same' | (64, 64, 64) | (64, 64, 64) |
| 批量规范化层 | Momentum=0.8 | (64, 64, 64) | (64, 64, 64) |
| 加法层 | None | (64, 64, 64) | (64, 64, 64) |
加法层计算输入到块的张量和最后一批归一化层的输出之和。 生成器网络包含 16 个具有上述配置的残差块。
- 后残差块:后残差块还包含单个 2D 卷积层和
relu作为激活函数。 卷积层之后是批量归一化层,其动量值为 0.8。 后残差块的配置如下:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 2D 卷积层 | Filters=64, kernel_size=3, strides=1, padding='same' | (64, 64, 64) | (64, 64, 64) |
| 批量规范化层 | Momentum=0.8 | (64, 64, 64) | (64, 64, 64) |
- 上采样块:上采样块包含一个上采样层和一个 2D 卷积层,并使用 relu 作为激活函数。 生成器网络中有两个上采样模块。 第一个上采样模块的配置如下:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 2D 上采样层 | Size=(2, 2) | (64, 64, 64) | (128, 128, 64) |
| 2D 卷积层 | Filters=256, kernel_size=3, strides=1, padding='same', activation='relu' | (128, 128, 256) | (128, 128, 256) |
第二个上采样模块的配置如下:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 2D 上采样层 | Size=(2, 2) | (128, 128, 256) | (256, 256, 256) |
| 2D 卷积层 | Filters=256, kernel_size=3, strides=1, padding='same', activation='relu' | (256, 256, 256) | (256, 256, 256) |
- 最后的卷积层:最后一层是使用 tanh 作为激活函数的 2D 卷积层。 它生成形状为
(256, 256, 3)的图像。 最后一层的配置如下:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 2D 卷积层 | Filters=3, kernel_size=9, strides=1, padding='same', activation='tanh' | (256, 256, 256) | (256, 256, 3) |
这些超参数最适合 Keras 框架。 如果使用任何其他框架,请相应地对其进行修改。
判别器网络的架构
判别器网络也是一个深度卷积网络。 它包含八个卷积块,后跟两个密集(完全连接)层。 每个卷积块后面都有一个批量归一化层。 网络的末端有两个密集层,它们充当分类块。 最后一层预测图像属于真实数据集或伪数据集的概率。 判别器网络的详细配置如下表所示:
| 层名称 | 超参数 | 输入形状 | 输出形状 |
|---|---|---|---|
| 输入层 | 没有 | (256, 256, 3) | (256, 256, 3) |
| 2D 卷积层 | filter= 64, kernel_size = 3, stride= 1, padding='same', activcation='leakyrelu' | (256, 256, 3) | (256, 256, 64) |
| 2D 卷积层 | filter= 64, kernel_size = 3, stride= 2, padding='same', activcation='leakyrelu' | (256, 256, 64) | (128, 128, 64) |
| 批量规范化层 | momentum= 0.8 | (128, 128, 64) | (128, 128, 64) |
| 2D 卷积层 | filter= 128, kernel_size = 3, stride= 1, padding='same', activcation='leakyrelu' | (128, 128, 64) | (128, 128, 128) |
| 批量规范化层 | momentum= 0.8 | (128, 128, 128) | (128, 128, 128) |
| 2D 卷积层 | filter= 128, kernel_size = 3, stride= 2, padding='same', activcation='leakyrelu' | (128, 128, 128) | (64, 64, 128) |
| 批量规范化层 | momentum= 0.8 | (64, 64, 128) | (64, 64, 128) |
| 2D 卷积层 | filter= 256, kernel_size = 3, stride= 1, padding='same', activcation='leakyrelu' | (64, 64, 128) | (64, 64, 256) |
| 批量规范化层 | momentum= 0.8 | (64, 64, 256) | (64, 64, 256) |
| 2D 卷积层 | filter= 256, kernel_size = 3, stride= 2, padding='same', activcation='leakyrelu' | (64, 64, 256) | (32, 32, 256) |
现在,我们对这两个网络的架构有了清晰的了解。 在下一部分中,让我们看一下训练 SRGAN 所需的目标函数。
训练目标函数
要训练 SRGAN,有一个目标函数或损失函数,我们需要将其最小化以训练模型。 SRGAN 的目标函数称为感知损失函数,它是两个损失函数的加权和,如下所示:
- 内容损失
- 对抗损失
在以下各节中,让我们详细研究内容损失和对抗损失。
内容损失
内容损失有两种类型,如下所示:
- MSE 像素损失
- VGG 损失
让我们详细讨论这些损失。
MSE 像素损失
内容损失是在真实图像的每个像素值与生成的图像的每个像素值之间计算的均方误差。 像素级 MSE 损失计算出生成的图像与实际图像有多大差异。 像素级 MSE 损失的计算公式如下:
在此,G[θ[G]](I^(LR))表示由所生成的网络所生成的高分辨率图像。 I^(HR)代表从真实数据集中采样的高分辨率图像。
VGG 损失
VGG 损失是另一个内存损失函数,可应用于生成的图像和真实图像。 VGG19 是一种非常流行的深度神经网络,主要用于图像分类。 VGG19 由 Simonyan 和 Zisserman 在他们的论文《用于大规模图像识别的超深度卷积网络》中引入,可在这里获得。 预训练的 VGG19 网络的中间层用作特征提取器,可用于提取生成的图像和真实图像的特征映射。 VGG 损失基于这些提取的特征映射。 计算为生成的图像和真实图像的特征映射之间的欧几里得距离。 VGG 损失的公式如下:
在此,φ[i, j]表示由 VGG19 网络生成的特征映射。 φ[i, j](I^(HR))代表提取的真实图像特征映射,φ[i, j](G[θ[G]](I^(LR)))代表提取的高分辨率图像生成的特征映射。 整个方程表示生成图像和真实图像的特征映射之间的欧式距离。
前述内容损失中的任何一种都可以用于训练 SRGAN。 对于我们的实现,我们将使用 VGG 损失。
对抗损失
根据判别器网络返回的概率计算对抗损失。 在对抗模型中,判别器网络被馈送有由生成的网络生成的生成的图像。 对抗损失可以用以下等式表示:
这里,G[θG](I^(LR))是所生成的图像 , D[θD](G[θG](I^(LR)))表示所生成的图像是真实图像的概率。
知觉损失函数是内容损失和对抗损失的加权和,表示为以下方程式:
在此,总的感知损失由l^(SR)表示。 l^(SR)[X]是内容损失,可以是像素级 MSE 损失或 VGG 损失。
通过最小化感知损失值,生成器网络试图欺骗判别器。 随着感知损失的值减小,生成器网络开始生成更逼真的图像。
现在开始进行该项目。
设置项目
如果尚未使用所有章节的完整代码克隆存储库,请立即克隆存储库。 下载的代码有一个名为Chapter05的目录,其中包含本章的全部代码。 执行以下命令来设置项目:
- 首先,导航到父目录,如下所示:
cd Generative-Adversarial-Networks-Projects
- 现在,将目录从当前目录更改为
Chapter05:
cd Chapter05
- 接下来,为该项目创建一个 Python 虚拟环境:
virtualenv venv
virtualenv venv -p python3 # Create a virtual environment using
python3 interpreter
virtualenv venv -p python2 # Create a virtual environment using
python2 interpreter
我们将为此项目使用此新创建的虚拟环境。 每章都有其自己单独的虚拟环境。
- 接下来,激活新创建的虚拟环境:
source venv/bin/activate
激活虚拟环境后,所有其他命令将在此虚拟环境中执行。
- 接下来,通过执行以下命令,安装
requirements.txt文件中提供的所有库:
pip install -r requirements.txt
您可以参考 README.md 文件,以获取有关如何设置项目的更多说明。 开发人员经常会遇到依赖关系不匹配的问题。 为每个项目创建一个单独的虚拟环境将解决此问题。
在本节中,我们已成功设置项目并安装了所需的依赖项。 在下一节中,让我们研究数据集。 我们将探索下载和格式化数据集的各种步骤。
下载 CelebA 数据集
在本章中,我们将使用大型 CelebFaces 属性(CelebA)数据集,该数据集可从这里获得。 数据集包含 202、599 名名人的人脸图像。
该数据集仅可用于非商业研究目的,不能用于商业目的。 如果您打算将数据集用于商业目的,请寻求图像所有者的许可。
我们将使用 CelebA 数据集训练我们的 SRGAN 网络。 执行以下步骤下载和提取数据集:
- 从以下链接下载数据集:
https://www.dropbox.com/sh/8oqt9vytwxb3s4r/AAB06FXaQRUNtjW9ntaoPGvCa?dl=0
- 通过执行以下命令从下载的
img_align_celeba.zip中提取图像:
unzip img_align_celeba.zip
现在,我们已经下载并提取了数据集。 现在,我们可以开始进行 SRGAN 的 Keras 实现。
SRGAN 的 Keras 实现
正如我们所讨论的,SRGAN 在 Imagenet 数据集上具有三个神经网络,一个生成器,一个判别器和一个预训练的 VGG19 网络。 在本节中,我们将编写所有网络的实现。 让我们从实现生成器网络开始。
在开始编写实现之前,创建一个名为main.py的 Python 文件并导入基本模块,如下所示:
import glob
import os
import numpy as np
import tensorflow as tf
from keras import Input
from keras.applications import VGG19
from keras.callbacks import TensorBoard
from keras.layers import BatchNormalization, Activation, LeakyReLU, Add, Dense, PReLU, Flatten
from keras.layers.convolutional import Conv2D, UpSampling2D
from keras.models import Model
from keras.optimizers import Adam
from keras_preprocessing.image import img_to_array, load_img
from scipy.misc import imsave
生成器网络
我们已经在“生成器网络的架构”中探讨了生成器网络的架构。 让我们首先在 Keras 框架中编写生成器网络的层,然后使用 Keras 框架的函数式 API 创建 Keras 模型。
执行以下步骤以在 Keras 中实现生成器网络:
- 首先定义生成器网络所需的超参数:
residual_blocks = 16 momentum = 0.8 input_shape = (64, 64, 3)
- 接下来,创建一个输入层,以将输入提供给网络,如下所示:
input_layer = Input(shape=input_shape)
输入层获取形状为(64, 64, 3)的输入图像,并将其传递到网络中的下一层。
- 接下来,如下所示添加残差前块(2D 卷积层):
配置:
- 过滤器:
64 - 核大小:
9 - 步幅:
1 - 填充:
same - 激活:
relu:
- 过滤器:
gen1 = Conv2D(filters=64, kernel_size=9, strides=1, padding='same', activation='relu')(input_layer)
- 接下来,编写一个包含残差块完整代码的方法,如下所示:
def residual_block(x):
"""
Residual block """ filters = [64, 64]
kernel_size = 3
strides = 1
padding = "same"
momentum = 0.8
activation = "relu" res = Conv2D(filters=filters[0], kernel_size=kernel_size,
strides=strides, padding=padding)(x)
res = Activation(activation=activation)(res)
res = BatchNormalization(momentum=momentum)(res)
res = Conv2D(filters=filters[1], kernel_size=kernel_size,
strides=strides, padding=padding)(res)
res = BatchNormalization(momentum=momentum)(res)
# Add res and x
res = Add()([res, x])
return res
- 现在,使用在最后一步中定义的
residual_block函数添加 16 个残差块:
res = residual_block(gen1)
for i in range(residual_blocks - 1):
res = residual_block(res)
前残差块的输出进入第一个残差块。 第一个残差块的输出将到达第二个残差块,依此类推,直到第 16 个残差块。
- 接下来,添加后残差块(2D 卷积层,然后是批量归一化层),如下所示:
配置:
- 过滤器:
64 - 核大小:
3 - 步幅:
1 - 填充:
same - 批量规范化:是(动量为 0.8):
- 过滤器:
gen2 = Conv2D(filters=64, kernel_size=3, strides=1, padding='same')(res)
gen2 = BatchNormalization(momentum=momentum)(gen2)
- 现在,添加
Add层,以获取残留前块的输出gen1和后残差块的输出gen2之和。 该层生成另一个类似形状的张量。 有关更多详细信息,请参考“ 生成器网络的架构”部分。
gen3 = Add()([gen2, gen1])
- 接下来,添加一个上采样块,如下所示:
配置:
- 上采样大小:
2 - 过滤器:
256 - 核大小:
3 - 步幅:
1 - 填充:
same - 激活:
PReLU:
- 上采样大小:
gen4 = UpSampling2D(size=2)(gen3)
gen4 = Conv2D(filters=256, kernel_size=3, strides=1, padding='same')(gen4)
gen4 = Activation('relu')(gen4)
- 接下来,添加另一个上采样块,如下所示:
配置:
- 上采样大小:
2 - 过滤器:
256 - 核大小:
3 - 步幅:
1 - 填充:
same - 激活:
PReLU:
- 上采样大小:
gen5 = UpSampling2D(size=2)(gen4)
gen5 = Conv2D(filters=256, kernel_size=3, strides=1, padding='same')(gen5)
gen5 = Activation('relu')(gen5)
- 最后,添加输出卷积层:
配置:
* 过滤器:
3(等于通道数) * 核大小:9* 步幅:1* 填充:same* 激活:tanh:
gen6 = Conv2D(filters=3, kernel_size=9, strides=1, padding='same')(gen5)
output = Activation('tanh')(gen6)
一旦定义了网络中的所有层,就可以创建 Keras 模型。 我们已经使用 Keras 的函数式 API 定义了 Keras 顺序图。 让我们通过指定网络的输入和输出来创建 Keras 模型。
- 现在,创建 Keras 模型并指定模型的输入和输出,如下所示:
model = Model(inputs=[input_layer], outputs=[output], name='generator')
我们已经成功地为生成器网络创建了 Keras 模型。 现在,将生成器网络的整个代码包装在 Python 函数中,如下所示:
def build_generator():
"""
Create a generator network using the hyperparameter values defined below :return:
""" residual_blocks = 16
momentum = 0.8
input_shape = (64, 64, 3)
# Input Layer of the generator network
input_layer = Input(shape=input_shape)
# Add the pre-residual block
gen1 = Conv2D(filters=64, kernel_size=9, strides=1, padding='same',
activation='relu')(input_layer)
# Add 16 residual blocks
res = residual_block(gen1)
for i in range(residual_blocks - 1):
res = residual_block(res)
# Add the post-residual block
gen2 = Conv2D(filters=64, kernel_size=3, strides=1, padding='same')(res)
gen2 = BatchNormalization(momentum=momentum)(gen2)
# Take the sum of the output from the pre-residual block(gen1) and
the post-residual block(gen2)
gen3 = Add()([gen2, gen1])
# Add an upsampling block
gen4 = UpSampling2D(size=2)(gen3)
gen4 = Conv2D(filters=256, kernel_size=3, strides=1, padding='same')(gen4)
gen4 = Activation('relu')(gen4)
# Add another upsampling block
gen5 = UpSampling2D(size=2)(gen4)
gen5 = Conv2D(filters=256, kernel_size=3, strides=1,
padding='same')(gen5)
gen5 = Activation('relu')(gen5)
# Output convolution layer
gen6 = Conv2D(filters=3, kernel_size=9, strides=1, padding='same')(gen5)
output = Activation('tanh')(gen6)
# Keras model
model = Model(inputs=[input_layer], outputs=[output],
name='generator')
return model
我们已经成功地为生成器网络创建了 Keras 模型。 在下一节中,我们将为判别器网络创建 Keras 模型。
判别器网络
我们已经在“使用 Keras 框架的函数式 API 创建 Keras 模型”中探讨了判别器网络的架构。 让我们首先在 Keras 框架中编写判别器网络的层。
执行以下步骤以在 Keras 中实现判别器网络:
- 首先定义判别器网络所需的超参数:
leakyrelu_alpha = 0.2 momentum = 0.8 input_shape = (256, 256, 3)
- 接下来,创建一个输入层,以将输入提供给网络,如下所示:
input_layer = Input(shape=input_shape)
- 接下来,添加一个卷积块,如下所示:
配置:
- 过滤器:
64 - 核大小:
3 - 步幅:
1 - 填充:
same - 激活:
LeakyReLU,alpha等于 0.2:
- 过滤器:
dis1 = Conv2D(filters=64, kernel_size=3, strides=1, padding='same')(input_layer)
dis1 = LeakyReLU(alpha=leakyrelu_alpha)(dis1)
- 接下来,添加另外七个卷积块,如下所示:
配置:
- 过滤器:
64,128,128,256,256,512,512 - 核大小:
3,3,3,3,3,3,3 - 步幅:
2,1,2,1,2,1,2 - 每个卷积层的填充:
same - 激活:
LealyReLU,每个卷积层的alpha等于 0.2:
- 过滤器:
# Add the 2nd convolution block dis2 = Conv2D(filters=64, kernel_size=3, strides=2, padding='same')(dis1)
dis2 = LeakyReLU(alpha=leakyrelu_alpha)(dis2)
dis2 = BatchNormalization(momentum=momentum)(dis2)
# Add the third convolution block dis3 = Conv2D(filters=128, kernel_size=3, strides=1, padding='same')(dis2)
dis3 = LeakyReLU(alpha=leakyrelu_alpha)(dis3)
dis3 = BatchNormalization(momentum=momentum)(dis3)
# Add the fourth convolution block dis4 = Conv2D(filters=128, kernel_size=3, strides=2, padding='same')(dis3)
dis4 = LeakyReLU(alpha=leakyrelu_alpha)(dis4)
dis4 = BatchNormalization(momentum=0.8)(dis4)
# Add the fifth convolution block dis5 = Conv2D(256, kernel_size=3, strides=1, padding='same')(dis4)
dis5 = LeakyReLU(alpha=leakyrelu_alpha)(dis5)
dis5 = BatchNormalization(momentum=momentum)(dis5)
# Add the sixth convolution block dis6 = Conv2D(filters=256, kernel_size=3, strides=2, padding='same')(dis5)
dis6 = LeakyReLU(alpha=leakyrelu_alpha)(dis6)
dis6 = BatchNormalization(momentum=momentum)(dis6)
# Add the seventh convolution block dis7 = Conv2D(filters=512, kernel_size=3, strides=1, padding='same')(dis6)
dis7 = LeakyReLU(alpha=leakyrelu_alpha)(dis7)
dis7 = BatchNormalization(momentum=momentum)(dis7)
# Add the eight convolution block dis8 = Conv2D(filters=512, kernel_size=3, strides=2, padding='same')(dis7)
dis8 = LeakyReLU(alpha=leakyrelu_alpha)(dis8)
dis8 = BatchNormalization(momentum=momentum)(dis8)
- 接下来,添加具有 1,024 个节点的密集层,如下所示:
配置:
- 节点:
1024 - 激活:
LeakyReLU,alpha等于 0.2:
- 节点:
dis9 = Dense(units=1024)(dis8)
dis9 = LeakyReLU(alpha=0.2)(dis9)
- 然后,添加一个密集层以返回概率,如下所示:
output = Dense(units=1, activation='sigmoid')(dis9)
- 最后,创建 Keras 模型并指定网络的输入和输出:
model = Model(inputs=[input_layer], outputs=[output],
name='discriminator')
将判别器网络的整个代码如下包装在函数中:
def build_discriminator():
"""
Create a discriminator network using the hyperparameter values defined below :return:
""" leakyrelu_alpha = 0.2
momentum = 0.8
input_shape = (256, 256, 3)
input_layer = Input(shape=input_shape)
# Add the first convolution block
dis1 = Conv2D(filters=64, kernel_size=3, strides=1, padding='same')(input_layer)
dis1 = LeakyReLU(alpha=leakyrelu_alpha)(dis1)
# Add the 2nd convolution block
dis2 = Conv2D(filters=64, kernel_size=3, strides=2, padding='same')(dis1)
dis2 = LeakyReLU(alpha=leakyrelu_alpha)(dis2)
dis2 = BatchNormalization(momentum=momentum)(dis2)
# Add the third convolution block
dis3 = Conv2D(filters=128, kernel_size=3, strides=1, padding='same')(dis2)
dis3 = LeakyReLU(alpha=leakyrelu_alpha)(dis3)
dis3 = BatchNormalization(momentum=momentum)(dis3)
# Add the fourth convolution block
dis4 = Conv2D(filters=128, kernel_size=3, strides=2, padding='same')(dis3)
dis4 = LeakyReLU(alpha=leakyrelu_alpha)(dis4)
dis4 = BatchNormalization(momentum=0.8)(dis4)
# Add the fifth convolution block
dis5 = Conv2D(256, kernel_size=3, strides=1, padding='same')(dis4)
dis5 = LeakyReLU(alpha=leakyrelu_alpha)(dis5)
dis5 = BatchNormalization(momentum=momentum)(dis5)
# Add the sixth convolution block
dis6 = Conv2D(filters=256, kernel_size=3, strides=2, padding='same')(dis5)
dis6 = LeakyReLU(alpha=leakyrelu_alpha)(dis6)
dis6 = BatchNormalization(momentum=momentum)(dis6)
# Add the seventh convolution block
dis7 = Conv2D(filters=512, kernel_size=3, strides=1, padding='same')(dis6)
dis7 = LeakyReLU(alpha=leakyrelu_alpha)(dis7)
dis7 = BatchNormalization(momentum=momentum)(dis7)
# Add the eight convolution block
dis8 = Conv2D(filters=512, kernel_size=3, strides=2, padding='same')(dis7)
dis8 = LeakyReLU(alpha=leakyrelu_alpha)(dis8)
dis8 = BatchNormalization(momentum=momentum)(dis8)
# Add a dense layer
dis9 = Dense(units=1024)(dis8)
dis9 = LeakyReLU(alpha=0.2)(dis9)
# Last dense layer - for classification
output = Dense(units=1, activation='sigmoid')(dis9)
model = Model(inputs=[input_layer], outputs=[output], name='discriminator')
return model
在这一部分,我们已经成功地为判别器网络创建了 Keras 模型。 在下一节中,我们将构建 VGG19 网络,如“SRGAN 简介”中所示。
VGG19 网络
我们将使用预训练的 VGG19 网络。 VGG19 网络的目的是提取生成的图像和真实图像的特征映射。 在本节中,我们将在 Keras 中使用预训练的权重构建和编译 VGG19 网络:
- 首先指定输入形状:
input_shape = (256, 256, 3)
- 接下来,加载预训练的 VGG19 并指定模型的输出:
vgg = VGG19(weights="imagenet")
vgg.outputs = [vgg.layers[9].output]
- 接下来,创建符号
input_tensor,这将是我们对 VGG19 网络的符号输入,如下所示:
input_layer = Input(shape=input_shape)
- 接下来,使用 VGG19 网络提取特征:
features = vgg(input_layer)
- 最后,创建 Keras
model并为网络指定inputs和outputs:
model = Model(inputs=[input_layer], outputs=[features])
最后,将 VGG19 模型的整个代码包装在一个函数中,如下所示:
def build_vgg():
"""
Build the VGG network to extract image features """ input_shape = (256, 256, 3)
# Load a pre-trained VGG19 model trained on 'Imagenet' dataset
vgg = VGG19(weights="imagenet")
vgg.outputs = [vgg.layers[9].output]
input_layer = Input(shape=input_shape)
# Extract features
features = vgg(input_layer)
# Create a Keras model
model = Model(inputs=[input_layer], outputs=[features])
return model
对抗网络
对抗网络是使用生成器,判别器和 VGG19 的组合网络。 在本节中,我们将创建一个对抗网络。
执行以下步骤来创建对抗网络:
- 首先为网络创建输入层:
input_low_resolution = Input(shape=(64, 64, 3))
对抗网络将收到(64, 64, 3)形状的图像,这就是我们创建输入层的原因。
- 接下来,使用生成器网络生成伪造的高分辨率图像,如下所示:
fake_hr_images = generator(input_low_resolution)
- 接下来,使用 VGG19 网络提取伪造图像的特征,如下所示:
fake_features = vgg(fake_hr_images)
- 接下来,使判别器网络在对抗网络中不可训练:
discriminator.trainable = False
我们使判别器网络不可训练,因为我们不想在训练生成器网络时训练判别器网络。
- 接下来,将伪造的图像传递到判别器网络:
output = discriminator(fake_hr_images)
- 最后,创建一个 Keras 模型,这将是我们的对抗模型:
model = Model(inputs=[input_low_resolution], outputs=[output,
fake_features])
- 将对抗模型的整个代码包装在 Python 函数中:
def build_adversarial_model(generator, discriminator, vgg):
input_low_resolution = Input(shape=(64, 64, 3))
fake_hr_images = generator(input_low_resolution)
fake_features = vgg(fake_hr_images)
discriminator.trainable = False output = discriminator(fake_hr_images)
model = Model(inputs=[input_low_resolution],
outputs=[output, fake_features])
for layer in model.layers:
print(layer.name, layer.trainable)
print(model.summary())
return model
我们现在已经成功地在 Keras 中实现了网络。 接下来,我们将在“数据准备”部分中下载的数据集上训练网络。
训练 SRGAN
训练 SRGAN 网络是一个分为两个步骤的过程。 第一步,我们训练判别器网络。 在第二步中,我们训练对抗网络,最终训练生成器网络。 让我们开始训练网络。
执行以下步骤来训练 SRGAN 网络:
- 首先定义训练所需的超参数:
# Define hyperparameters data_dir = "Paht/to/the/dataset/img_align_celeba/*.*" epochs = 20000 batch_size = 1 # Shape of low-resolution and high-resolution images low_resolution_shape = (64, 64, 3)
high_resolution_shape = (256, 256, 3)
- 接下来,定义训练优化器。 对于所有网络,我们将使用学习率等于 0.0002 且
beta_1等于0.5的 Adam 优化器:
# Common optimizer for all networks common_optimizer = Adam(0.0002, 0.5)
建立和编译网络
在本节中,我们将完成构建和编译网络所需的不同步骤:
- 构建和编译 VGG19 网络:
vgg = build_vgg()
vgg.trainable = False vgg.compile(loss='mse', optimizer=common_optimizer, metrics=
['accuracy'])
要编译 VGG19,请使用mse作为损失,使用accuracy作为度量,并使用common_optimizer作为优化器。 编译网络之前,请先禁用训练,因为我们不想训练 VGG19 网络。
- 接下来,构建并编译
discriminator网络,如下所示:
discriminator = build_discriminator()
discriminator.compile(loss='mse', optimizer=common_optimizer,
metrics=['accuracy'])
要编译判别器网络,使用 mse 作为损失, accuracy 作为度量,并使用 common_optimizer 作为优化器。
- 接下来,构建生成器网络:
generator = build_generator()
- 接下来,创建一个对抗模型。 首先创建两个输入层:
input_high_resolution = Input(shape=high_resolution_shape)
input_low_resolution = Input(shape=low_resolution_shape)
- 接下来,使用生成器网络从低分辨率图像中象征性地生成高分辨率图像:
generated_high_resolution_images = generator(input_low_resolution)
使用 VGG19 提取生成图像的特征映射:
features = vgg(generated_high_resolution_images)
使判别器网络不可训练,因为我们不想在对抗模型训练期间训练判别器模型 :
discriminator.trainable = False
- 接下来,使用判别器网络获取生成的高分辨率伪图像的概率:
probs = discriminator(generated_high_resolution_images)
在此,probs表示所生成的图像属于真实数据集的概率。
- 最后,创建并编译对抗网络:
adversarial_model = Model([input_low_resolution, input_high_resolution], [probs, features])
adversarial_model.compile(loss=['binary_crossentropy', 'mse'],
loss_weights=[1e-3, 1], optimizer=common_optimizer)
要编译对抗模型,请使用binary_crossentropy和mse作为损失函数,common_optimizer作为优化器,并使用[0.001, 1]作为损失权重。
- 添加
Tensorboard以可视化训练损失并可视化网络图:
tensorboard = TensorBoard(log_dir="logs/".format(time.time()))
tensorboard.set_model(generator)
tensorboard.set_model(discriminator)
- 创建一个循环,该循环应运行指定的周期数:
for epoch in range(epochs):
print("Epoch:{}".format(epoch))
完成此步骤后,所有代码都将在此for循环内。
- 接下来,对一批高分辨率和低分辨率图像进行采样,如下所示:
high_resolution_images, low_resolution_images =
sample_images(data_dir=data_dir,
batch_size=batch_size,low_resolution_shape=low_resolution_shape, high_resolution_shape=high_resolution_shape)
sample_images函数的代码如下。 它具有很强的描述性,通过阅读可以理解。 它包含加载和调整图像大小并生成高分辨率和低分辨率图像的不同步骤:
def sample_images(data_dir, batch_size, high_resolution_shape, low_resolution_shape):
# Make a list of all images inside the data directory
all_images = glob.glob(data_dir)
# Choose a random batch of images
images_batch = np.random.choice(all_images, size=batch_size)
low_resolution_images = []
high_resolution_images = []
for img in images_batch:
# Get an ndarray of the current image
img1 = imread(img, mode='RGB')
img1 = img1.astype(np.float32)
# Resize the image
img1_high_resolution = imresize(img1, high_resolution_shape)
img1_low_resolution = imresize(img1, low_resolution_shape)
# Do a random flip
if np.random.random() < 0.5:
img1_high_resolution = np.fliplr(img1_high_resolution)
img1_low_resolution = np.fliplr(img1_low_resolution)
high_resolution_images.append(img1_high_resolution)
low_resolution_images.append(img1_low_resolution)
return np.array(high_resolution_images),
np.array(low_resolution_images)
- 接下来,对图像进行归一化以将像素值转换为
[-1, 1]之间的范围,如下所示:
high_resolution_images = high_resolution_images / 127.5 - 1.
low_resolution_images = low_resolution_images / 127.5 - 1.
将像素值转换为 -1 到 1 的范围非常重要。我们的生成器网络的末尾有tanh。 tanh激活函数将值压缩到相同范围。 在计算损失时,必须使所有值都在同一范围内。
训练判别器网络
本节中给出的步骤显示了如何训练判别器网络。 这是最后一系列步骤的延续:
- 使用
generator网络生成伪造的高分辨率图像:
generated_high_resolution_images =
generator.predict(low_resolution_images)
- 创建一批真实标签和伪标签:
real_labels = np.ones((batch_size, 16, 16, 1))
fake_labels = np.zeros((batch_size, 16, 16, 1))
- 在真实图像和真实标签上训练判别器网络:
d_loss_real = discriminator.train_on_batch(high_resolution_images,
real_labels)
- 在生成的图像和伪造标签上训练判别器:
d_loss_fake = discriminator.train_on_batch(generated_high_resolution_images, fake_labels)
- 最后,计算总的判别器损失:
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
现在,我们添加了代码来训练判别器网络。 接下来,添加代码以训练对抗模型,从而训练生成器网络。
训练生成器网络
本节中给出的步骤显示了如何训练生成器网络。 这是最后一系列步骤的延续:
- 再次,对一批高分辨率和低分辨率图像进行采样并对其进行归一化:
high_resolution_images, low_resolution_images = sample_images(data_dir=data_dir, batch_size=batch_size,low_resolution_shape=low_resolution_shape, high_resolution_shape=high_resolution_shape)
# Normalize images high_resolution_images = high_resolution_images / 127.5 - 1.
low_resolution_images = low_resolution_images / 127.5 - 1.
- 使用 VGG19 网络提取真实高分辨率图像的特征映射(内部表示):
image_features = vgg.predict(high_resolution_images)
- 最后,训练对抗模型并为其提供适当的输入,如下所示:
g_loss = adversarial_model.train_on_batch([low_resolution_images, high_resolution_images],
[real_labels, image_features])
- 在每个周期完成之后,将损失写入 TensorBoard 以将其可视化:
write_log(tensorboard, 'g_loss', g_loss[0], epoch)
write_log(tensorboard, 'd_loss', d_loss[0], epoch)
- 每隔 100 个周期后,使用生成器网络生成高分辨率的伪图像并保存以使其可视化:
if epoch % 100 == 0:
high_resolution_images, low_resolution_images = sample_images(data_dir=data_dir, batch_size=batch_size,low_resolution_shape=low_resolution_shape,
high_resolution_shape=high_resolution_shape)
# Normalize images
high_resolution_images = high_resolution_images / 127.5 - 1.
low_resolution_images = low_resolution_images / 127.5 - 1.
# Generate fake high-resolution images generated_images = generator.predict_on_batch(low_resolution_images)
# Save
for index, img in enumerate(generated_images):
save_images(low_resolution_images[index], high_resolution_images[index], img,
path="results/img_{}_{}".format(epoch, index))
这些图像将帮助您决定是继续训练还是尽早停止训练。 如果生成的高分辨率图像的质量良好,请停止训练。 或者,继续训练,直到您的模型变好为止。
我们现在已经成功地在CelebA数据集上训练了 SRGAN 网络。 训练完成后,生成高分辨率图像非常容易。 拍摄大小为64 x 64 x 3的低分辨率图像,并将其传递给generator.predict()函数,该函数将生成高分辨率图像。
保存模型
在 Keras 中保存模型只需要一行代码。 要保存generator模型,请添加以下行:
# Specify the path for the generator model
gen_model.save("directory/for/the/generator/model.h5")
同样,通过添加以下行来保存discriminator模型:
# Specify the path for the discriminator model
dis_model.save("directory/for/the/discriminator/model.h5")
可视化生成的图像
经过大量时间后,生成器将开始生成良好的图像。 让我们看一下生成的图像:
- 在 1,000 个周期之后,图像显示如下:
- 5,000 个周期后,图像显示如下:
- 10,000 个周期后,图像显示如下:
- 15,000 个周期后,图像显示如下:
- 经过 20,000 个周期后,图像显示如下:
要生成非常好的图像,请将网络训练 30,000-50,000 个周期。
可视化损失
要显示训练损失,请启动,,tensorboard,,服务器,如下所示:
tensorboard --logdir=logs
现在,在浏览器中打开 localhost:6006 。 TensorBoard 的标量部分包含两种损失的图:
这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 尝试使用超参数,然后选择一组您认为可能会提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。
可视化图
TensorBoard 的 GRAPHS 部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的运算:
SRGAN 的实际应用
现在,让我们看一下 SRGAN 的实际应用:
- 恢复旧照片
- 行业应用,例如自动提高徽标,横幅和小册子的分辨率
- 自动为用户增加社交媒体图像的分辨率
- 拍摄时自动增强相机上的照片
- 提高医学图像的分辨率
总结
在本章中,我们首先介绍 SRGAN。 然后,我们研究了生成器和判别器网络的架构。 后来,我们执行了该项目所需的设置。 然后,我们收集并探索了数据集。 之后,我们在训练 SRGAN 之前先在 Keras 中实现了该项目,评估了训练后的 SRGAN 网络,并使用超参数优化技术对训练后的模型进行了优化。 最后,我们简要介绍了 SRGAN 的一些不同应用。
在下一章中,我们将介绍 StackGAN 及其不同的应用。
六、StackGAN - 逼真的文本到图像合成
文本到图像的合成是生成对抗网络(GAN)的用例之一,它具有许多工业应用,就像前面章节中描述的 GAN 一样。 从文本描述中合成图像非常困难,因为要构建可以生成反映文本含义的图像的模型非常困难。 一个试图解决这个问题的网络是 StackGAN。 在本章中,我们将使用 TensorFlow 作为后端在 Keras 框架中实现 StackGAN。
在本章中,我们将介绍以下主题:
- StackGAN 简介
- StackGAN 的架构
- 数据收集与准备
- StackGAN 的 Keras 实现
- 训练 StackGAN
- 评估模型
- pix2pix 网络的实际应用
StackGAN 简介
之所以这样称呼 StackGAN,是因为它具有两个 GAN,这些 GAN 堆叠在一起形成了一个能够生成高分辨率图像的网络。 它分为两个阶段,第一阶段和第二阶段。第一阶段网络生成具有基本颜色和粗略草图的低分辨率图像,并以文本嵌入为条件;而第二阶段网络获取由第一阶段网络生成的图像,并生成以文字嵌入为条件的高分辨率图像。 基本上,第二个网络会纠正缺陷并添加引人注目的细节,从而产生更逼真的高分辨率图像。
我们可以将 StackGAN 网络与画家的作品进行比较。 当画家开始工作时,他们会绘制原始形状,例如线条,圆形和矩形。 然后,他们尝试填充颜色。 随着绘画的进行,越来越多的细节被添加。 在 StackGAN 中,第一阶段与绘制基本形状有关,而第二阶段与校正由第一阶段网络生成的图像中的缺陷有关。第二阶段还添加了更多细节,以使图像看起来更逼真。 这两个阶段的生成器网络都是条件生成对抗网络(CGAN)。 第一个 GAN 以文本描述为条件,而第二网络以文本描述和第一个 GAN 生成的图像为条件。
StackGAN 的架构
StackGAN 是一个两阶段的网络。 每个阶段都有两个生成器和两个判别器。 StackGAN 由许多网络组成,这些网络如下:
- 阶段 1 GAN :文本编码器,条件增强网络,生成器网络,判别器网络,嵌入压缩器网络
- 阶段 2 GAN :文本编码器,条件增强网络,生成器网络,判别器网络,嵌入压缩器网络
来源:arXiv:1612.03242 [cs.CV]
上图是不言自明的。 它代表了 StackGAN 网络的两个阶段。 如您所见,第一步是生成大小为64x64的图像。 然后,第二阶段拍摄这些低分辨率图像,并生成大小为256x256的高分辨率图像。 在接下来的几节中,我们将探讨 StackGAN 网络中的不同组件。 但是,在进行此操作之前,让我们熟悉本章中使用的符号:
| 表示法 | 说明 |
|---|---|
t | 这是真实数据分发的文本描述。 |
z | 这是来自高斯分布的随机采样噪声向量。 |
φ[t] | 这是预训练编码器生成的给定文本描述的文本嵌入。 |
c_hat[0] | 此文本条件变量是从分布中采样的高斯条件变量。 它抓住了的不同含义。 |
N(μ(φ[t]), ∑(φ[t])) | 这是条件高斯分布。 |
N(0, I) | 这是正态分布。 |
∑(φ[t]) | 这是一个对角协方差矩阵。 |
pdata | 这是真正的数据分配。 |
pz | 这就是高斯分布。 |
D1 | 这是第一阶段的判别器。 |
G1 | 这是第一阶段生成器。 |
D2 | 这是第二阶段的判别器。 |
G2 | 这是第二阶段生成器。 |
N2 | 这些是随机噪声变量的大小。 |
c_hat | 这些是第二阶段 GAN 的高斯潜在变量。 |
文字编码器网络
文本编码器网络的唯一目的是将文本描述(t)转换为文本嵌入(φ[t])。 在本章中,我们不会训练文本编码器网络。 我们将使用经过预训练的文本嵌入。 按照“数据准备”部分中给出的步骤下载预训练的文本嵌入。 如果您想训练自己的文本编码器,请参阅《学习细粒度视觉描述的深度表示》,该文章可在这里找到。 文本编码器网络将句子编码为 1,024 维文本嵌入。 文本编码器网络在两个阶段都是相同的。
条件增强块
条件增强(CA)网络从表示为N(μ(φ[t]), ∑(φ[t]))的分布中采样随机潜在变量c_hat。 我们将在后面的部分中详细了解这种分布。 添加 CA 块有很多优点,如下所示:
- 它为网络增加了随机性。
- 通过捕获具有各种姿势和外观的各种对象,它使生成器网络变得强大。
- 它产生更多的图像-文本对。 使用大量的图文对,我们可以训练一个可以处理干扰的健壮网络。
获取条件增强变量
从文本编码器获得文本嵌入(φ[t])后,将它们馈送到完全连接的层以生成诸如平均值μ[0]和标准差σ[0]的值。然后使用这些值,通过将σ[0]放置在矩阵(∑(φ[t]))的对角线上来创建对角协方差矩阵。 最后,我们使用μ[0]和∑[0]创建高斯分布,可以表示为:
然后,我们从刚创建的高斯分布中采样c_hat[0]。 计算c_hat[0]的公式如下:
前面的方程式很不言自明。 为了对c_hat[0]进行采样,我们首先将σ[0]和进行元素逐个相乘,然后将输出添加到μ[0]中。 我们将在“StackGAN 的 Keras 实现”部分中详细介绍如何计算 CA 变量c_hat[0]。
第一阶段
StackGAN 网络的主要组成部分是生成器网络和判别器网络。 在本节中,我们将详细探讨这两个网络。
生成器网络
第一阶段生成器网络是具有几个上采样层的深度卷积神经网络。 生成器网络是 CGAN ,其条件是处于c_hat[0]和随机变量z。 生成器网络采用高斯条件变量c_hat[0]和随机噪声变量z,并生成大小为64x64x3 的图像。 生成的低分辨率图像可能具有原始形状和基本颜色,但会存在各种缺陷。 这里,z是从维数为N[z]的高斯分布P[z]采样的随机噪声变量。 生成器网络生成的图像可以表示为s[0] = G[0](z, c_hat[0])。 让我们看一下生成器网络的架构,如以下屏幕截图所示:
第一阶段的生成器网络架构
如您所见,生成器网络包含几个卷积层,其中每个卷积层后跟一个批量规范化层或一个激活层。 它的唯一目的是生成大小为64x64x3的图像。 现在我们有了生成器网络的基本概念,让我们探索判别器网络。
判别器网络
类似于生成器网络,判别器网络是一个深度卷积神经网络,其中包含一系列下采样卷积层。 下采样层从图像生成特征映射,无论它们是真实数据分布P_data的真实图像还是生成器网络生成的图像。 然后,我们将特征映射连接到文本嵌入。 我们使用压缩和空间复制将嵌入的文本转换为连接所需的格式。 空间压缩和复制包括一个完全连接的层,该层用于压缩嵌入到N[d]维输出的文本,然后通过空间复制将其转换为M[d] × M[d] × N[d]维张量。 然后将特征映射以及压缩的和空间复制的文本嵌入沿通道维级联。 最后,我们具有一个节点的完全连接层,该层用于二分类。 让我们看一下判别器网络的架构,如以下屏幕截图所示:
第一阶段判别器网络的架构
如您所见,生成器网络包含几个卷积层。 判别器网络的唯一目的是区分来自真实数据分布的图像和生成器网络生成的图像。 现在,我们来看看 StackGAN 的第一阶段中使用的损失。
StackGAN 第一阶段的损失
StackGAN 的第一阶段中使用了两个损失,如下所示:
- 生成器损失
- 判别器损失
判别器损失l[D]可以表示为:
公式前面的很不言自明。 它代表了判别器网络的损失函数,其中两个网络都以文本嵌入为条件。
生成器损失l[G]可以表示为:
公式前面的也很容易解释。 它代表了生成器网络的损失函数,其中两个网络都以文本嵌入为条件。 此外,它还包括损失函数的 KL 发散项。
第二阶段
第二阶段 StackGAN 的主要组件是生成器网络和判别器网络。 生成器网络是编码器-解码器类型的网络。 在此阶段不使用随机噪声z,假设s[0]已保留了随机性,其中s[0]是第一阶段的生成器网络生成的图像。
我们首先使用预训练的文本编码器生成高斯条件变量c_hat。 这将生成嵌入φ[t]的相同文本。 第一阶段和第二阶段条件增强具有不同的完全连接层,用于生成不同的均值和标准差。 这意味着第二阶段 GAN 学会了在文本嵌入中捕获有用的信息,而这些信息被第一阶段 GAN 省略了。
第一阶段 GAN 生成的图像存在的问题是,它们可能缺少生动的对象部分,它们可能包含形状失真,并且它们可能会忽略对于生成逼真的图像非常重要的重要细节。 第二阶段 GAN 建立在第一阶段 GAN 的输出上。 第二阶段 GAN 取决于第一阶段 GAN 生成的低分辨率图像和文本描述。 它通过校正缺陷产生高分辨率的图像。
生成器网络
生成器网络还是深层卷积神经网络。 第一阶段的结果是低分辨率图像,它经过几个下采样层以生成图像特征。 然后,将图像特征和文本条件变量沿通道大小连接在一起。 之后,将级联张量馈入一些残差块,这些残差块学习跨图像和文本特征的多峰表示。 最后,最后一个操作的输出被馈送到一组上采样层,这些上采样层生成大小为256x256x3的高分辨率图像。 让我们看一下生成器网络的架构,如下图所示:
第二阶段生成器的架构
该生成器网络的唯一目的是从低分辨率图像生成高分辨率图像。 低分辨率图像首先由第一阶段的生成器网络生成,然后馈送到第二阶段的生成器网络,后者生成高分辨率图像。
判别器网络
类似于生成器网络,判别器网络是一个深度卷积神经网络,并且包含额外的下采样层,因为图像的大小比第一阶段中的判别器网络大。 判别器是一个可识别匹配的判别器(有关更多信息,请参见以下链接,它使我们可以更好地实现图片和条件文本的对齐。 在训练期间,判别器将真实图像及其对应的文本描述作为正样本对,而负样本对则由两组组成。 第一组是具有不匹配的文本嵌入的真实图像,而第二组是具有相应的文本嵌入的合成图像。 让我们看一下判别器网络的架构,如下图所示:
第二阶段判别器网络的架构
关于判别器网络架构的更多信息可以在“StackGAN 的 Keras 实现”部分中找到。
StackGAN 第二阶段的损失
与任何其他 GAN 相似,第二阶段 GAN 中的生成器G和判别器D也可以通过最大化判别器的损失并将生成器网络的损失最小化来训练 。
生成器损失l[G]可以表示为:
前面的方程式非常不言自明。 它代表了判别器网络的损失函数,其中两个网络都以文本嵌入为条件。 一个主要区别是生成器网络以s[0]和c_hat作为输入,其中s[0]是第一阶段生成的图像,c_hat是 CA 变量。
判别器损失l[D]可以表示为:
前面的方程式也很容易解释。 它代表了生成器网络的损失函数,其中两个网络都以文本嵌入为条件。 它还包括对损失函数的 Kullback-Leibler(KL)发散项。
设置项目
如果尚未使用所有章节的完整代码克隆存储库,请立即克隆存储库。 下载的代码有一个名为Chapter06的目录,其中包含本章的全部代码。 执行以下命令来设置项目:
- 首先,导航到父目录,如下所示:
cd Generative-Adversarial-Networks-Projects
- 现在,将目录从当前目录更改为
Chapter06:
cd Chapter06
- 接下来,为该项目创建一个 Python 虚拟环境:
virtualenv venv
virtualenv venv -p python3 # Create a virtual environment using
python3 interpreter
virtualenv venv -p python2 # Create a virtual environment using
python2 interpreter
我们将为此项目使用此新创建的虚拟环境。 每章都有其自己单独的虚拟环境。
- 激活新创建的虚拟环境:
source venv/bin/activate
激活虚拟环境后,将在其中执行所有其他命令。
- 通过执行以下命令,安装
requirements.txt文件中提供的所有库:
pip install -r requirements.txt
您可以参考 README.md 文件,以获取有关如何设置项目的更多说明。 开发人员经常会遇到依赖关系不匹配的问题。 为每个项目创建一个单独的虚拟环境将解决此问题。
在本节中,我们已成功设置项目并安装了所需的依赖项。 在下一节中,我们将处理数据集。
数据准备
在本节中,我们将使用 CUB 数据集,该数据集是不同鸟类的图像数据集,可以在以下链接中找到。 CUB 数据集包含 11,788 个高分辨率图像。 我们还将需要字符 CNN-RNN 文本嵌入,可以在以下链接中找到它们。 这些是预训练的文本嵌入。 按照以下几节中给出的说明下载和提取数据集。
下载数据集
可以从这里手动下载CUB数据集。 另外,我们可以执行以下命令来下载数据集:
wget http://www.vision.caltech.edu/visipedia-data/CUB-200-2011/CUB_200_2011.tgz
下载数据集后,我们可以将其提取并放在data/birds/目录中。
提取数据集
CUB数据集是压缩文件,需要提取。 使用以下命令提取CUB数据集:
tar -xvzf CUB_200_2011.tgz
使用以下命令提取字符 CNN-RNN 嵌入:
unzip birds.zip
最后,将CUB_200_2011放在data/birds目录中。 现在可以使用我们的数据集了。
探索数据集
CUB 数据集总共包含 200 种不同鸟类的 11,788 张图像。 CUB 数据集中的图像包括以下内容:
这四张图片显示了黑脚信天翁,长尾小鹦鹉,bobolink 和 Brandt's cormorant。
在设计网络之前,了解数据集非常重要。 确保您仔细浏览 CUB 数据集中的图像。
StackGAN 的 Keras 实现
StackGAN 的 Keras 实现分为两部分:第一阶段和第二阶段。 我们将在以下各节中实现这些阶段。
第一阶段
第一阶段 StackGAN 包含生成器网络和判别器网络。 它还具有一个文本编码器网络和一个条件增强网络(CA 网络),下面将对此进行详细说明。 生成器网络获取文本条件变量(c_hat[0])以及噪声向量(x)。 经过一组上采样层后,它会生成大小为64x64x3的低分辨率图像。 判别器网络拍摄此低分辨率图像,并尝试识别图像是真实的还是伪造的。 生成器网络是具有一组下采样层的网络,其后是连接,然后是分类层。 在以下各节中,我们将详细探讨 StackGAN 的架构。
第一阶段 StackGAN 网络中使用的网络如下:
- 文本编码器网络
- 条件增强网络
- 生成器网络
- 判别器网络
但是,在开始编写实现之前,请创建一个 Python 文件 main.py ,然后按如下所示导入基本模块:
import os
import pickle
import random
import time
import PIL
import numpy as np
import pandas as pd
import tensorflow as tf
from PIL import Image
from keras import Input, Model
from keras import backend as K
from keras.callbacks import TensorBoard
from keras.layers import Dense, LeakyReLU, BatchNormalization, ReLU, Reshape, UpSampling2D, Conv2D, Activation, \
concatenate, Flatten, Lambda, Concatenate
from keras.optimizers import Adam
from keras_preprocessing.image import ImageDataGenerator
from matplotlib import pyplot as plt
文字编码器网络
文本编码器网络的唯一目的是将文本描述(t)转换为文本嵌入(φ[t])。 该网络将句子编码为 1,024 维文本嵌入。 我们已经下载了预训练的字符 CNN-RNN 文本嵌入。 我们将使用它们来训练我们的网络。
条件增强网络
CA 网络的目的是将文本嵌入向量(φ[t])转换为条件潜在变量(c_hat[0])。 在 CA 网络中,文本嵌入向量穿过具有非线性的完全连接层,从而产生均值μ(φ[t])和对角协方差矩阵∑(φ[t])。
以下代码显示了如何创建 CA 网络:
- 首先创建一个具有 256 个节点的完整连接层,并使用
LeakyReLU作为激活函数:
input_layer = Input(shape=(1024,))
x = Dense(256)(input_layer)
mean_logsigma = LeakyReLU(alpha=0.2)(x)
输入形状为(batch_size,1024),输出形状为(batch_size,256)。
- 接下来,将
mean_logsigma分为mean和log_sigma张量:
mean = x[:, :128]
log_sigma = x[:, 128:]
此操作将创建两个张量的张量(batch_size,128)和(batch_size和128)。
- 接下来,使用以下代码计算文本条件变量。 有关如何生成文本条件变量的更多信息,请参见“StackGAN 架构”小节中的“条件增强(CA)块”部分:
stddev = K.exp(log_sigma)
epsilon = K.random_normal(shape=K.constant((mean.shape[1], ), dtype='int32'))
c = stddev * epsilon + mean
这将产生一个张量为(batch_size,128)的张量,这是我们的文本条件变量。 CA 网络的完整代码如下:
def generate_c(x):
mean = x[:, :128]
log_sigma = x[:, 128:]
stddev = K.exp(log_sigma)
epsilon = K.random_normal(shape=K.constant((mean.shape[1], ), dtype='int32'))
c = stddev * epsilon + mean
return c
条件块的整个代码如下所示:
def build_ca_model():
input_layer = Input(shape=(1024,))
x = Dense(256)(input_layer)
mean_logsigma = LeakyReLU(alpha=0.2)(x)
c = Lambda(generate_c)(mean_logsigma)
return Model(inputs=[input_layer], outputs=[c])
在代码前面的中,build_ca_model()方法创建一个 Keras 模型,其中具有一个完全连接的层并且以LeakyReLU作为激活函数。
生成器网络
生成器网络是条件生成对抗网络(CGAN)。 我们将要创建的生成器网络以文本条件变量为条件。 它采用从潜在空间采样的随机噪声向量,并生成形状为64x64x3的图像。
让我们从编写生成器网络的代码开始:
- 首先创建一个输入层,以将输入(噪声变量)馈送到网络:
input_layer2 = Input(shape=(100, ))
- 接下来,将文本条件变量与噪声变量沿维度 1 连接:
gen_input = Concatenate(axis=1)([c, input_layer2])
此处,c是文本条件变量。 在上一步中,我们编写了代码来生成文本条件变量,gen_input将成为我们对生成器网络的输入。
- 接下来,创建具有
12884*4 (16,384)节点的dense layer和ReLU激活层,如下所示:
x = Dense(128 * 8 * 4 * 4, use_bias=False)(gen_input)
x = ReLU()(x)
- 之后,将最后一层的输出重塑为大小为
(batch_size, 4, 4, 128*8)的张量:
x = Reshape((4, 4, 128 * 8), input_shape=(128 * 8 * 4 * 4,))(x)
该操作将二维张量整形为二维张量。
- 接下来,创建一个二维向上采样卷积块。 该块包含一个上采样层,一个卷积层和一个批归一化层。 批量规范化后,使用
ReLU作为此块的激活函数:
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(512, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
- 之后,再创建三个 3D 上采样卷积块,如下所示:
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(256, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(128, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(64, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
- 最后,创建一个卷积层,它将生成一个低分辨率图像:
x = Conv2D(3, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = Activation(activation='tanh')(x)
- 现在,通过如下指定网络的输入和输出来创建 Keras 模型:
stage1_gen = Model(inputs=[input_layer, input_layer2], outputs=[x, mean_logsigma])
这里,x将是模型的输出,其形状将是(batch_size, 64, 64, 3)。
生成器网络的整个代码如下所示:
def build_stage1_generator():
"""
Builds a generator model """ input_layer = Input(shape=(1024,))
x = Dense(256)(input_layer)
mean_logsigma = LeakyReLU(alpha=0.2)(x)
c = Lambda(generate_c)(mean_logsigma)
input_layer2 = Input(shape=(100,))
gen_input = Concatenate(axis=1)([c, input_layer2])
x = Dense(128 * 8 * 4 * 4, use_bias=False)(gen_input)
x = ReLU()(x)
x = Reshape((4, 4, 128 * 8), input_shape=(128 * 8 * 4 * 4,))(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(512, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(256, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(128, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(64, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = Conv2D(3, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = Activation(activation='tanh')(x)
stage1_gen = Model(inputs=[input_layer, input_layer2], outputs=[x, mean_logsigma])
return stage1_gen
此模型在单个网络中同时包含 CA 网络和生成器网络。 它需要两个输入并返回两个输出。 输入是文本嵌入和噪声变量,而输出是生成的图像和mean_logsigma。
我们现在已经成功实现了生成器网络。 让我们进入判别器网络。
判别器网络
判别器网络是分类器网络。 它包含一组下采样层,并对给定图像是真实的还是伪造的进行分类。
让我们从编写网络代码开始:
- 首先创建一个输入层以将输入提供给网络:
input_layer = Input(shape=(64, 64, 3))
- 接下来,添加具有以下参数的二维卷积层:
- 过滤器:
64 - 核大小:
(4, 4) - 步幅:
2 - 填充:
'same' - 使用偏差:
False - 激活:
LeakyReLU,alpha=0.2
- 过滤器:
stage1_dis = Conv2D(64, (4, 4),
padding='same', strides=2,
input_shape=(64, 64, 3), use_bias=False)(input_layer)
stage1_dis = LeakyReLU(alpha=0.2)(stage1_dis)
- 之后,添加两个卷积层,每个卷积层之后是一个批量归一化层和一个
LeakyReLU激活函数,具有以下参数:- 过滤器:
128 - 核大小:
(4, 4) - 步幅:
2 - 填充:
'same' - 使用偏差:
False - 激活:
LeakyReLU,alpha=0.2
- 过滤器:
x = Conv2D(128, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
- 接下来,再添加一个 2D 卷积层,然后添加批量规范化层和
LeakyReLU激活函数,并具有以下参数:- 过滤器:
256 - 核大小:
(4, 4) - 步幅:
2 - 填充:
'same' - 使用偏差:
False - 激活:
LeakyReLU,alpha=0.2
- 过滤器:
x = Conv2D(256, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
- 之后,再添加一个 2D 卷积层,然后添加批量规范化层和 LeakyReLU 激活函数,并具有以下参数:
- 过滤器:
512 - 核大小:
(4, 4) - 步幅:
2 - 填充:
'same' - 使用偏差:
False - 激活:
LeakyReLU和alpha=0.2
- 过滤器:
x = Conv2D(512, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
- 然后,创建另一个输入层以接收空间复制和压缩的文本嵌入:
input_layer2 = Input(shape=(4, 4, 128))
- 添加一个连接层以连接
x和input_layer2:
merged_input = concatenate([x, input_layer2])
- 之后,添加另一个 2D 卷积层,然后添加批量归一化层,并使用以下参数将
LeakyReLU作为激活函数:- 过滤器:
512 - 核大小:
1 - 步幅:
1 - 填充:
'same' - 批量规范化:是
- 激活:
LeakyReLU,alpha 0.2
- 过滤器:
x2 = Conv2D(64 * 8, kernel_size=1,
padding="same", strides=1)(merged_input)
x2 = BatchNormalization()(x2)
x2 = LeakyReLU(alpha=0.2)(x2)
- 现在,展开张量并添加一个密集的分类器层:
# Flatten the tensor
x2 = Flatten()(x2)
# Classifier layer
x2 = Dense(1)(x2)
x2 = Activation('sigmoid')(x2)
- 最后,创建 Keras 模型:
stage1_dis = Model(inputs=[input_layer, input_layer2], outputs=[x2])
该模型输出输入图像属于真实类别或伪类别的概率。 判别器网络的完整代码如下:
def build_stage1_discriminator(): input_layer = Input(shape=(64, 64, 3))
x = Conv2D(64, (4, 4),
padding='same', strides=2,
input_shape=(64, 64, 3), use_bias=False)(input_layer)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(128, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(256, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(512, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
input_layer2 = Input(shape=(4, 4, 128))
merged_input = concatenate([x, input_layer2])
x2 = Conv2D(64 * 8, kernel_size=1,
padding="same", strides=1)(merged_input)
x2 = BatchNormalization()(x2)
x2 = LeakyReLU(alpha=0.2)(x2)
x2 = Flatten()(x2)
x2 = Dense(1)(x2)
x2 = Activation('sigmoid')(x2)
stage1_dis = Model(inputs=[input_layer, input_layer2], outputs=[x2])
return stage1_dis
该模型有两个输入和一个输出。 输入是低分辨率图像和压缩文本嵌入,而输出是概率。 既然我们已经成功地编写了判别器网络的实现,那么让我们创建对抗网络。
对抗模型
要创建对抗模型,请同时使用生成器网络和判别器网络,并创建一个新的 Keras 模型。
- 首先创建三个输入层以将输入馈送到网络:
def build_adversarial_model(gen_model, dis_model):
input_layer = Input(shape=(1024,))
input_layer2 = Input(shape=(100,))
input_layer3 = Input(shape=(4, 4, 128))
- 然后,使用生成器网络生成低分辨率图像:
# Get output of the generator model
x, mean_logsigma = gen_model([input_layer, input_layer2])
# Make discriminator trainable false
dis_model.trainable = False
- 接下来,使用判别器网络获得概率:
# Get output of the discriminator models valid = dis_model([x, input_layer3])
- 最后,创建对抗模型,获取三个输入并返回两个输出。
model = Model(inputs=[input_layer, input_layer2, input_layer3], outputs=[valid, mean_logsigma])
return model
现在,我们的对抗模型已经准备就绪。 这种对抗模型是一种端到端的可训练模型。 在本节中,我们研究了 StackGAN 模型的第一阶段中涉及的网络。 在下一部分中,我们将研究 StackGAN 的第二阶段所涉及的网络的实现。
第二阶段
第二阶段 StackGAN 与第一阶段 StackGAN 略有不同。 生成器模型的输入是条件变量(c_hat[0])和生成器网络在第一阶段中生成的低分辨率图像。
它包含五个组成部分:
- 文字编码器
- 条件增强网络
- 下采样块
- 残差块
- 上采样块
文本编码器和 CA 网络与之前在第一阶段部分中使用的相似。 现在,我们将介绍生成器网络的三个组件,分别是下采样块,残差块和上采样块。
生成器网络
生成器网络由三个不同的模块组成。 我们将逐一编写每个模块的代码。 让我们从下采样模块开始。
下采样块
该块从第一阶段的生成器获取大小为64x64x3的低分辨率图像,并将其下采样以生成形状为16x16x512的张量。 图像经过一系列 2D 卷积块。
在本节中,我们将为降采样模块编写实现。
- 首先创建第一个下采样块。 该块包含一个以
ReLU作为激活函数的 2D 卷积层。 在应用 2D 卷积之前,请在各侧用零填充输入。 该块中不同层的配置如下:- 填充大小:
(1, 1) - 过滤器:
128 - 核大小:
(3, 3) - 步幅:
1 - 激活:
ReLU
- 填充大小:
x = ZeroPadding2D(padding=(1, 1))(input_lr_images)
x = Conv2D(128, kernel_size=(3, 3), strides=1, use_bias=False)(x)
x = ReLU()(x)
- 接下来,添加具有以下配置的第二个卷积块:
- 填充大小:
(1, 1) - 过滤器:
256 - 核大小:
(4, 4) - 步幅:
2 - 批量规范化:是
- 激活:
ReLU
- 填充大小:
x = ZeroPadding2D(padding=(1, 1))(x)
x = Conv2D(256, kernel_size=(4, 4), strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
- 之后,添加另一个具有以下配置的卷积块:
- 填充大小:
(1, 1) - 过滤器:
512 - 核大小:
(4, 4) - 步幅:
2 - 批量规范化:是
- 激活:
ReLU
- 填充大小:
x = ZeroPadding2D(padding=(1, 1))(x)
x = Conv2D(512, kernel_size=(4, 4), strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
下采样块生成形状为16x16x512的张量。 之后,我们有一系列残差块。 在将该张量馈送到残差块之前,我们需要将其连接到文本条件变量。 执行此操作的代码如下:
# This block will extend the text conditioning variable and concatenate it to the encoded images tensor.
def joint_block(inputs):
c = inputs[0]
x = inputs[1]
c = K.expand_dims(c, axis=1)
c = K.expand_dims(c, axis=1)
c = K.tile(c, [1, 16, 16, 1])
return K.concatenate([c, x], axis=3)
# This is the lambda layer which we will add to the generator network
c_code = Lambda(joint_block)([c, x])
在此,c的形状为(batch_size, 228),x的形状为(batch_size, 16, 16, 512)。 c_code的形状为(batch_size, 640)。
残差块
残差块包含两个 2D 卷积层,每个层之后是批量归一化层和一个激活函数。
- 让我们定义残差块。 此代码完全描述了残差块:
def residual_block(input):
"""
Residual block in the generator network :return:
""" x = Conv2D(128 * 4, kernel_size=(3, 3), padding='same', strides=1)(input)
x = BatchNormalization()(x)
x = ReLU()(x)
x = Conv2D(128 * 4, kernel_size=(3, 3), strides=1, padding='same')(x)
x = BatchNormalization()(x)
x = add([x, input])
x = ReLU()(x)
return x
初始输入被添加到第二个 2D 卷积层的输出中。 结果张量将是块的输出。
- 接下来,添加具有以下超参数的 2D 卷积块:
- 填充大小:
(1, 1) - 过滤器:
512 - 核大小:
(3, 3) - 步幅:
1 - 批量规范化:
Yes - 激活:
ReLU
- 填充大小:
x = ZeroPadding2D(padding=(1, 1))(c_code)
x = Conv2D(512, kernel_size=(3, 3), strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
- 之后,依次添加四个残差块:
x = residual_block(x)
x = residual_block(x)
x = residual_block(x)
x = residual_block(x)
上采样块将从残差块接收该输出张量。 让我们为上采样块编写代码。
上采样块
上采样块包含可提高图像空间分辨率并生成大小256x256x3的高分辨率图像的层。
让我们为上采样块编写代码:
- 首先,添加一个包含 2D 上采样层,2D 卷积层,批归一化和激活函数的上采样块。 块中使用的不同参数如下:
- 上采样大小:
(2, 2) - 过滤器:
512 - 核大小:
3 - 填充:
"same" - 步幅:
1 - 批量规范化:
Yes - 激活:
ReLU
- 上采样大小:
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(512, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
- 接下来,再添加三个上采样块。 该块中使用的超参数可以很容易地从下面给出的代码中推断出来:
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(256, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(128, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(64, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
- 添加最后的卷积层。 该层是最后一层,它负责生成高分辨率图像。
x = Conv2D(3, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = Activation('tanh')(x)
最后,使用部分前面的创建生成器模型:
model = Model(inputs=[input_layer, input_lr_images], outputs=[x, mean_logsigma])
现在,我们已经准备好生成器模型,并使用该模型来生成高分辨率图像。 以下是用于生成器网络的完整代码,用于清晰 :
def build_stage2_generator():
"""
Create a generator network for Stage-II StackGAN
""" # 1\. CA Augementation Network
input_layer = Input(shape=(1024,))
input_lr_images = Input(shape=(64, 64, 3))
ca = Dense(256)(input_layer)
mean_logsigma = LeakyReLU(alpha=0.2)(ca)
c = Lambda(generate_c)(mean_logsigma)
# 2\. Image Encoder
x = ZeroPadding2D(padding=(1, 1))(input_lr_images)
x = Conv2D(128, kernel_size=(3, 3), strides=1, use_bias=False)(x)
x = ReLU()(x)
x = ZeroPadding2D(padding=(1, 1))(x)
x = Conv2D(256, kernel_size=(4, 4), strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = ZeroPadding2D(padding=(1, 1))(x)
x = Conv2D(512, kernel_size=(4, 4), strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
# Concatenation block
c_code = Lambda(joint_block)([c, x])
# 3\. Residual Blocks
x = ZeroPadding2D(padding=(1, 1))(c_code)
x = Conv2D(512, kernel_size=(3, 3), strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = residual_block(x)
x = residual_block(x)
x = residual_block(x)
x = residual_block(x)
# 4\. Upsampling blocks
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(512, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(256, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(128, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(64, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = ReLU()(x)
x = Conv2D(3, kernel_size=3, padding="same", strides=1, use_bias=False)(x)
x = Activation('tanh')(x)
model = Model(inputs=[input_layer, input_lr_images], outputs=[x, mean_logsigma])
return model
判别器网络
第二阶段 StackGAN 的判别器网络是一系列下采样层,然后是连接块,然后是分类器。 让我们为每个块编写代码。
首先创建输入层,如下所示:
input_layer = Input(shape=(256, 256, 3))
下采样块
下采样块具有对图像进行下采样的几层。
首先在下采样模块中添加不同的层。 本节中的代码是非常说明性的,可以轻松理解:
x = Conv2D(64, (4, 4), padding='same', strides=2, input_shape=(256, 256, 3), use_bias=False)(input_layer)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(128, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(256, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(512, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(1024, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(2048, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(1024, (1, 1), padding='same', strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(512, (1, 1), padding='same', strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x2 = Conv2D(128, (1, 1), padding='same', strides=1, use_bias=False)(x)
x2 = BatchNormalization()(x2)
x2 = LeakyReLU(alpha=0.2)(x2)
x2 = Conv2D(128, (3, 3), padding='same', strides=1, use_bias=False)(x2)
x2 = BatchNormalization()(x2)
x2 = LeakyReLU(alpha=0.2)(x2)
x2 = Conv2D(512, (3, 3), padding='same', strides=1, use_bias=False)(x2)
x2 = BatchNormalization()(x2)
之后,我们有两个输出,分别是x和x2。 将这两个张量相加以创建相同形状的张量。 我们还需要应用LeakyReLU激活函数:
added_x = add([x, x2])
added_x = LeakyReLU(alpha=0.2)(added_x)
连接块
为空间复制和压缩的嵌入创建另一个输入层:
input_layer2 = Input(shape=(4, 4, 128))
将下采样块的输出连接到空间压缩的嵌入:
input_layer2 = Input(shape=(4, 4, 128))
merged_input = concatenate([added_x, input_layer2])
全连接分类器
然后将合并后的输入馈送到具有一个卷积层和一个密集层的块中,以进行分类:
x3 = Conv2D(64 * 8, kernel_size=1, padding="same", strides=1)(merged_input)
x3 = BatchNormalization()(x3)
x3 = LeakyReLU(alpha=0.2)(x3)
x3 = Flatten()(x3)
x3 = Dense(1)(x3)
x3 = Activation('sigmoid')(x3)
x3是此判别器网络的输出。 这将输出通过的图像是真实的还是伪造的概率。
最后,创建一个模型:
stage2_dis = Model(inputs=[input_layer, input_layer2], outputs=[x3])
如您所见,此模型采用两个输入并返回一个输出。
判别器网络的完整代码如下:
def build_stage2_discriminator():
input_layer = Input(shape=(256, 256, 3))
x = Conv2D(64, (4, 4), padding='same', strides=2, input_shape=(256, 256, 3), use_bias=False)(input_layer)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(128, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(256, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(512, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(1024, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(2048, (4, 4), padding='same', strides=2, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(1024, (1, 1), padding='same', strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(512, (1, 1), padding='same', strides=1, use_bias=False)(x)
x = BatchNormalization()(x)
x2 = Conv2D(128, (1, 1), padding='same', strides=1, use_bias=False)(x)
x2 = BatchNormalization()(x2)
x2 = LeakyReLU(alpha=0.2)(x2)
x2 = Conv2D(128, (3, 3), padding='same', strides=1, use_bias=False)(x2)
x2 = BatchNormalization()(x2)
x2 = LeakyReLU(alpha=0.2)(x2)
x2 = Conv2D(512, (3, 3), padding='same', strides=1, use_bias=False)(x2)
x2 = BatchNormalization()(x2)
added_x = add([x, x2])
added_x = LeakyReLU(alpha=0.2)(added_x)
input_layer2 = Input(shape=(4, 4, 128))
# Concatenation block
merged_input = concatenate([added_x, input_layer2])
x3 = Conv2D(64 * 8, kernel_size=1, padding="same", strides=1)(merged_input)
x3 = BatchNormalization()(x3)
x3 = LeakyReLU(alpha=0.2)(x3)
x3 = Flatten()(x3)
x3 = Dense(1)(x3)
x3 = Activation('sigmoid')(x3)
stage2_dis = Model(inputs=[input_layer, input_layer2], outputs=[x3])
return stage2_dis
我们现在已经成功地为两个 StackGAN 创建了模型:第一阶段和第二阶段。 让我们继续训练模型。
训练 StackGAN
在本节中,我们将学习如何训练这两个 StackGAN。 在第一小节中,我们将训练第一阶段 StackGAN。 在第二小节中,我们将训练第二阶段 StackGAN。
训练第一阶段 StackGAN
在开始训练之前,我们需要指定基本的超参数。 超参数是在训练过程中不会改变的值。 让我们先这样做:
data_dir = "Specify your dataset directory here/Data/birds" train_dir = data_dir + "/train" test_dir = data_dir + "/test" image_size = 64 batch_size = 64 z_dim = 100 stage1_generator_lr = 0.0002 stage1_discriminator_lr = 0.0002 stage1_lr_decay_step = 600 epochs = 1000 condition_dim = 128 embeddings_file_path_train = train_dir + "/char-CNN-RNN-embeddings.pickle" embeddings_file_path_test = test_dir + "/char-CNN-RNN-embeddings.pickle" filenames_file_path_train = train_dir + "/filenames.pickle" filenames_file_path_test = test_dir + "/filenames.pickle" class_info_file_path_train = train_dir + "/class_info.pickle" class_info_file_path_test = test_dir + "/class_info.pickle" cub_dataset_dir = data_dir + "/CUB_200_2011"
然后,我们需要加载数据集。
加载数据集
加载数据集是一个需要几个步骤的过程。 让我们一步一步地探索每个步骤。
- 第一步是加载存储在 pickle 文件中的类 ID。 以下代码将加载类 ID 并返回所有 ID 的列表:
def load_class_ids(class_info_file_path):
"""
Load class ids from class_info.pickle file """ with open(class_info_file_path, 'rb') as f:
class_ids = pickle.load(f, encoding='latin1')
return class_ids
- 接下来,加载文件名,这些文件名也存储在 pickle 文件中。 可以按照以下步骤进行:
def load_filenames(filenames_file_path):
"""
Load filenames.pickle file and return a list of all file names """ with open(filenames_file_path, 'rb') as f:
filenames = pickle.load(f, encoding='latin1')
return filenames
- 之后,我们需要加载文本嵌入,这些嵌入也位于 pickle 文件中。 加载文件并检索文本嵌入,如下所示:
def load_embeddings(embeddings_file_path):
"""
Load embeddings """ with open(embeddings_file_path, 'rb') as f:
embeddings = pickle.load(f, encoding='latin1')
embeddings = np.array(embeddings)
print('embeddings: ', embeddings.shape)
return embeddings
- 接下来,获取边界框,该边界框用于从原始图像提取对象。 下面的不言自明的代码显示了如何检索边界框:
def load_bounding_boxes(dataset_dir):
"""
Load bounding boxes and return a dictionary of file names and corresponding bounding boxes """ # Paths bounding_boxes_path = os.path.join(dataset_dir, 'bounding_boxes.txt')
file_paths_path = os.path.join(dataset_dir, 'images.txt')
# Read bounding_boxes.txt and images.txt file
df_bounding_boxes = pd.read_csv(bounding_boxes_path,
delim_whitespace=True, header=None).astype(int)
df_file_names = pd.read_csv(file_paths_path, delim_whitespace=True, header=None)
# Create a list of file names
file_names = df_file_names[1].tolist()
# Create a dictionary of file_names and bounding boxes
filename_boundingbox_dict = {img_file[:-4]: [] for img_file in file_names[:2]}
# Assign a bounding box to the corresponding image
for i in range(0, len(file_names)):
# Get the bounding box
bounding_box = df_bounding_boxes.iloc[i][1:].tolist()
key = file_names[i][:-4]
filename_boundingbox_dict[key] = bounding_box
return filename_boundingbox_dict
- 接下来,编写一种加载和裁剪图像的方法。 以下代码加载图像并将其裁剪在提供的边界框周围。 它还将图像调整为指定大小:
def get_img(img_path, bbox, image_size):
"""
Load and resize image """ img = Image.open(img_path).convert('RGB')
width, height = img.size
if bbox is not None:
R = int(np.maximum(bbox[2], bbox[3]) * 0.75)
center_x = int((2 * bbox[0] + bbox[2]) / 2)
center_y = int((2 * bbox[1] + bbox[3]) / 2)
y1 = np.maximum(0, center_y - R)
y2 = np.minimum(height, center_y + R)
x1 = np.maximum(0, center_x - R)
x2 = np.minimum(width, center_x + R)
img = img.crop([x1, y1, x2, y2])
img = img.resize(image_size, PIL.Image.BILINEAR)
return img
- 最后,结合前面所有的方法来获取数据集,这是我们训练所需的。 此代码返回所有图像,其标签和相应的嵌入。 我们需要这些来进行训练:
def load_dataset(filenames_file_path, class_info_file_path, cub_dataset_dir, embeddings_file_path, image_size):
filenames = load_filenames(filenames_file_path)
class_ids = load_class_ids(class_info_file_path)
bounding_boxes = load_bounding_boxes(cub_dataset_dir)
all_embeddings = load_embeddings(embeddings_file_path)
X, y, embeddings = [], [], []
# TODO: Change filenames indexing
for index, filename in enumerate(filenames[:500]):
# print(class_ids[index], filenames[index])
bounding_box = bounding_boxes[filename]
try:
# Load images
img_name = '{}/images/{}.jpg'.format(cub_dataset_dir, filename)
img = get_img(img_name, bounding_box, image_size)
all_embeddings1 = all_embeddings[index, :, :]
embedding_ix = random.randint(0, all_embeddings1.shape[0] - 1)
embedding = all_embeddings1[embedding_ix, :]
X.append(np.array(img))
y.append(class_ids[index])
embeddings.append(embedding)
except Exception as e:
print(e)
X = np.array(X)
y = np.array(y)
embeddings = np.array(embeddings)
return X, y, embeddings
- 最后,加载数据集并将其用于训练:
X_train, y_train, embeddings_train = load_dataset(filenames_file_path=filenames_file_path_train,
class_info_file_path=class_info_file_path_train,
cub_dataset_dir=cub_dataset_dir,
embeddings_file_path=embeddings_file_path_train,
image_size=(64, 64))
X_test, y_test, embeddings_test = load_dataset(filenames_file_path=filenames_file_path_test,
class_info_file_path=class_info_file_path_test,
cub_dataset_dir=cub_dataset_dir,
embeddings_file_path=embeddings_file_path_test,
image_size=(64, 64))
现在我们已经成功加载了数据集进行训练,让我们创建一些模型。
建立模型
让我们使用“StackGAN 的 Keras 实现”下的“第一阶段 StackGAN”部分中的方法创建模型。 我们将使用四个模型:生成器模型,判别器模型,压缩文本嵌入的压缩器模型以及同时包含生成器和判别器的对抗模型:
- 首先定义训练所需的优化器:
dis_optimizer = Adam(lr=stage1_discriminator_lr, beta_1=0.5, beta_2=0.999)
gen_optimizer = Adam(lr=stage1_generator_lr, beta_1=0.5, beta_2=0.999)
- 然后按如下所示构建和编译不同的网络:
ca_model = build_ca_model()
ca_model.compile(loss="binary_crossentropy", optimizer="adam")
stage1_dis = build_stage1_discriminator()
stage1_dis.compile(loss='binary_crossentropy', optimizer=dis_optimizer)
stage1_gen = build_stage1_generator()
stage1_gen.compile(loss="mse", optimizer=gen_optimizer)
embedding_compressor_model = build_embedding_compressor_model()
embedding_compressor_model.compile(loss="binary_crossentropy", optimizer="adam")
adversarial_model = build_adversarial_model(gen_model=stage1_gen, dis_model=stage1_dis)
adversarial_model.compile(loss=['binary_crossentropy', KL_loss], loss_weights=[1, 2.0],
optimizer=gen_optimizer, metrics=None)
此处,KL_loss是自定义损失函数,定义如下:
def KL_loss(y_true, y_pred):
mean = y_pred[:, :128]
logsigma = y_pred[:, :128]
loss = -logsigma + .5 * (-1 + K.exp(2\. * logsigma) + K.square(mean))
loss = K.mean(loss)
return loss
现在我们已经准备好数据集和模型,因此我们可以开始训练模型。
另外,添加 TensorBoard 以存储损失以进行可视化,如下所示:
tensorboard = TensorBoard(log_dir="logs/".format(time.time()))
tensorboard.set_model(stage1_gen)
tensorboard.set_model(stage1_dis)
tensorboard.set_model(ca_model)
tensorboard.set_model(embedding_compressor_model)
训练模型
训练模型需要几个步骤:
- 用真实和假标签创建两个张量。 在训练生成器和判别器时将需要这些。 使用标签平滑处理,该内容在第 1 章,“生成对抗网络”中介绍:
real_labels = np.ones((batch_size, 1), dtype=float) * 0.9 fake_labels = np.zeros((batch_size, 1), dtype=float) * 0.1
- 接下来,创建一个
for循环,该循环应运行的次数由周期数指定,如下所示:
for epoch in range(epochs):
print("========================================")
print("Epoch is:", epoch)
print("Number of batches", int(X_train.shape[0] / batch_size))
gen_losses = []
dis_losses = []
- 之后,计算一批批量并编写一个
for循环,该循环将针对指定数量的批量运行:
number_of_batches = int(X_train.shape[0] / batch_size)
for index in range(number_of_batches):
print("Batch:{}".format(index+1))
- 为当前迭代采样一批数据(一个小批量)。 创建噪声向量,选择一批图像和一批嵌入物,并对图像进行规范化:
# Create a batch of noise vectors
z_noise = np.random.normal(0, 1, size=(batch_size, z_dim))
image_batch = X_train[index * batch_size:(index + 1) * batch_size]
embedding_batch = embeddings_train[index * batch_size:(index + 1) * batch_size]
# Normalize images
image_batch = (image_batch - 127.5) / 127.5
- 接下来,使用生成器模型通过传递
embedding_batch和z_noise来生成伪图像:
fake_images, _ = stage1_gen.predict([embedding_batch, z_noise], verbose=3)
这将生成以一组嵌入和一组噪声向量为条件的一组伪图像。
- 使用压缩器模型压缩嵌入。 空间复制它以将其转换为形状为
(batch_size, 4, 4, 128)的张量:
compressed_embedding = embedding_compressor_model.predict_on_batch(embedding_batch)
compressed_embedding = np.reshape(compressed_embedding, (-1, 1, 1, condition_dim))
compressed_embedding = np.tile(compressed_embedding, (1, 4, 4, 1))
- 接下来,在生成器生成的伪图像,真实数据集中的真实图像和错误图像上训练判别器模型:
dis_loss_real = stage1_dis.train_on_batch([image_batch, compressed_embedding],
np.reshape(real_labels, (batch_size, 1)))
dis_loss_fake = stage1_dis.train_on_batch([fake_images, compressed_embedding],
np.reshape(fake_labels, (batch_size, 1)))
dis_loss_wrong = stage1_dis.train_on_batch([image_batch[:(batch_size - 1)], compressed_embedding[1:]],
np.reshape(fake_labels[1:], (batch_size-1, 1)))
现在,我们已经成功地在三组数据上训练了判别器:真实图像,伪图像和错误图像。 现在让我们训练对抗模型:
- 接下来,训练对抗模型。 为它提供三个输入和相应的真值。 此操作将计算梯度并更新一批数据的权重。
g_loss = adversarial_model.train_on_batch([embedding_batch, z_noise, compressed_embedding],[K.ones((batch_size, 1)) * 0.9, K.ones((batch_size, 256)) * 0.9])
- 接下来,计算损失并将其存储以进行评估。 最好不断打印出不同的损失以跟踪训练:
d_loss = 0.5 * np.add(dis_loss_real, 0.5 * np.add(dis_loss_wrong, dis_loss_fake))
print("d_loss:{}".format(d_loss))
print("g_loss:{}".format(g_loss))
dis_losses.append(d_loss)
gen_losses.append(g_loss)
- 在每个周期完成之后,将任何损失存储到 TensorBoard 中:
write_log(tensorboard, 'discriminator_loss', np.mean(dis_losses), epoch)
write_log(tensorboard, 'generator_loss', np.mean(gen_losses[0]), epoch)
- 在每个周期之后,要评估进度,生成图像并将其保存在结果目录中。
z_noise2 = np.random.normal(0, 1, size=(batch_size, z_dim))
embedding_batch = embeddings_test[0:batch_size]
fake_images, _ = stage1_gen.predict_on_batch([embedding_batch, z_noise2])
# Save images for i, img in enumerate(fake_images[:10]):
save_rgb_img(img, "results/gen_{}_{}.png".format(epoch, i))
此处,save_rgb_img()是一个实用函数,定义如下:
def save_rgb_img(img, path):
"""
Save a rgb image """ fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(img)
ax.axis("off")
ax.set_title("Image")
plt.savefig(path)
plt.close()
- 将每个模型的权重保存在 StackGAN 的第一阶段中。
stage1_gen.save_weights("stage1_gen.h5")
stage1_dis.save_weights("stage1_dis.h5")
恭喜,我们已经成功训练了 StackGAN 的第一阶段。 现在,我们拥有训练有素的生成器网络,可以生成大小为64x64x3的图像。 这些图像将具有基本颜色和原始形状。 在下一部分中,我们将训练第二阶段 StackGAN。
训练第二阶段 StackGAN
请执行以下步骤来训练第二阶段 StackGAN。
首先指定用于训练第二阶段 StackGAN 的超参数:
# Specify hyperparamters data_dir = "Path to the dataset/Data/birds" train_dir = data_dir + "/train" test_dir = data_dir + "/test" hr_image_size = (256, 256)
lr_image_size = (64, 64)
batch_size = 8 z_dim = 100 stage1_generator_lr = 0.0002 stage1_discriminator_lr = 0.0002 stage1_lr_decay_step = 600 epochs = 10 condition_dim = 128 embeddings_file_path_train = train_dir + "/char-CNN-RNN-embeddings.pickle" embeddings_file_path_test = test_dir + "/char-CNN-RNN-embeddings.pickle" filenames_file_path_train = train_dir + "/filenames.pickle" filenames_file_path_test = test_dir + "/filenames.pickle" class_info_file_path_train = train_dir + "/class_info.pickle" class_info_file_path_test = test_dir + "/class_info.pickle" cub_dataset_dir = data_dir + "/CUB_200_2011"
加载数据集
使用创建第一阶段 StackGAN 时在“加载数据集”部分中定义的方法。 分别加载高分辨率和低分辨率数据集。 另外,分别加载训练和测试数据集:
X_hr_train, y_hr_train, embeddings_train = load_dataset(filenames_file_path=filenames_file_path_train,
class_info_file_path=class_info_file_path_train,
cub_dataset_dir=cub_dataset_dir,
embeddings_file_path=embeddings_file_path_train,
image_size=(256, 256))
X_hr_test, y_hr_test, embeddings_test = load_dataset(filenames_file_path=filenames_file_path_test,
class_info_file_path=class_info_file_path_test,
cub_dataset_dir=cub_dataset_dir,
embeddings_file_path=embeddings_file_path_test,
image_size=(256, 256))
X_lr_train, y_lr_train, _ = load_dataset(filenames_file_path=filenames_file_path_train,
class_info_file_path=class_info_file_path_train,
cub_dataset_dir=cub_dataset_dir,
embeddings_file_path=embeddings_file_path_train,
image_size=(64, 64))
X_lr_test, y_lr_test, _ = load_dataset(filenames_file_path=filenames_file_path_test,
class_info_file_path=class_info_file_path_test,
cub_dataset_dir=cub_dataset_dir,
embeddings_file_path=embeddings_file_path_test,
image_size=(64, 64))
建立模型
与之前一样创建 Keras 模型,这些模型在“StackGAN 的 Keras 实现”的“第一阶段 StackGAN”部分中指定:
首先定义训练所需的优化器:
dis_optimizer = Adam(lr=stage1_discriminator_lr, beta_1=0.5, beta_2=0.999)
gen_optimizer = Adam(lr=stage1_generator_lr, beta_1=0.5, beta_2=0.999)
我们将使用 Adam optimizer,其学习率等于0.0002,beta_1值等于0.5,beta_2等于0.999。
现在构建和创建模型:
stage2_dis = build_stage2_discriminator()
stage2_dis.compile(loss='binary_crossentropy', optimizer=dis_optimizer)
stage1_gen = build_stage1_generator()
stage1_gen.compile(loss="binary_crossentropy", optimizer=gen_optimizer)
stage1_gen.load_weights("stage1_gen.h5")
stage2_gen = build_stage2_generator()
stage2_gen.compile(loss="binary_crossentropy", optimizer=gen_optimizer)
embedding_compressor_model = build_embedding_compressor_model()
embedding_compressor_model.compile(loss='binary_crossentropy', optimizer='adam')
adversarial_model = build_adversarial_model(stage2_gen, stage2_dis, stage1_gen)
adversarial_model.compile(loss=['binary_crossentropy', KL_loss], loss_weights=[1.0, 2.0],
optimizer=gen_optimizer, metrics=None)
KL_loss是自定义损失函数,在“训练第一阶段 StackGAN”部分中指定。
现在,我们已经为第二阶段 StackGAN 准备了数据集和模型。 让我们训练模型。
训练模型
让我们逐步进行此过程。
- 首先添加 TensorBoard 来存储损失:
tensorboard = TensorBoard(log_dir="logs/".format(time.time()))
tensorboard.set_model(stage2_gen)
tensorboard.set_model(stage2_dis)
- 然后,创建两个张量分别为
real和fake的张量。 在训练生成器和判别器时将需要这些。 使用标签平滑处理,第 1 章,“生成对抗网络”对此进行了介绍:
real_labels = np.ones((batch_size, 1), dtype=float) * 0.9 fake_labels = np.zeros((batch_size, 1), dtype=float) * 0.1
- 接下来,创建一个
for循环,该循环应运行由周期数指定的次数,如下所示:
for epoch in range(epochs):
print("========================================")
print("Epoch is:", epoch)
gen_losses = []
dis_losses = []
- 在周期循环内创建另一个循环,该循环将运行指定的批量数量:
print("Number of batches:{}".format(number_of_batches))
for index in range(number_of_batches):
print("Batch:{}".format(index))
- 采样训练所需的数据:
# Create a mini-batch of noise vectors z_noise = np.random.normal(0, 1, size=(batch_size, z_dim))
X_hr_train_batch = X_hr_train[index * batch_size:(index + 1) * batch_size]
embedding_batch = embeddings_train[index * batch_size:(index + 1) * batch_size]
# Normalize images
X_hr_train_batch = (X_hr_train_batch - 127.5) / 127.5
- 接下来,使用生成器网络生成大小为
256x256x2的伪图像。 在此步骤中,我们首先使用第一阶段的生成器网络生成低分辨率的伪图像。 然后,我们在第二阶段中使用生成器网络生成以低分辨率图像为条件的高分辨率图像。
lr_fake_images, _ = stage1_gen.predict([embedding_batch, z_noise], verbose=3)
hr_fake_images, _ = stage2_gen.predict([embedding_batch, lr_fake_images], verbose=3)
- 使用压缩器模型压缩嵌入。 空间复制它以将其转换为形状为
(batch_size, 4, 4, 128)的张量
compressed_embedding = embedding_compressor_model.predict_on_batch(embedding_batch)
compressed_embedding = np.reshape(compressed_embedding, (-1, 1, 1, condition_dim))
compressed_embedding = np.tile(compressed_embedding, (1, 4, 4, 1))
- 之后,在伪图像,真实图像和错误图像上训练判别器模型:
dis_loss_real = stage2_dis.train_on_batch([X_hr_train_batch, compressed_embedding],
np.reshape(real_labels, (batch_size, 1)))
dis_loss_fake = stage2_dis.train_on_batch([hr_fake_images, compressed_embedding],
np.reshape(fake_labels, (batch_size, 1)))
dis_loss_wrong = stage2_dis.train_on_batch([X_hr_train_batch[:(batch_size - 1)], compressed_embedding[1:]],
np.reshape(fake_labels[1:], (batch_size-1, 1)))
- 接下来,训练对抗模型。 这是生成器模型和判别器模型的组合。 我们为它提供三个输入和相应的真值:
g_loss = adversarial_model.train_on_batch([embedding_batch, z_noise, compressed_embedding],[K.ones((batch_size, 1)) * 0.9, K.ones((batch_size, 256)) * 0.9])
- 计算损失并将其保存以进行评估:
d_loss = 0.5 * np.add(dis_loss_real, 0.5 * np.add(dis_loss_wrong, dis_loss_fake))
print("d_loss:{}".format(d_loss))
print("g_loss:{}".format(g_loss))
在每个周期之后,将损失保存到 TensorBoard 中:
write_log(tensorboard, 'discriminator_loss', np.mean(dis_losses), epoch)
write_log(tensorboard, 'generator_loss', np.mean(gen_losses)[0], epoch)
- 在每个周期之后,要评估进度,生成图像并将其保存在结果目录中。 在以下代码中,我们仅保存第一个生成的图像。 相应地更改此设置以适当保存图像。
# Generate and save images after every 2nd epoch if epoch % 2 == 0:
# z_noise2 = np.random.uniform(-1, 1, size=(batch_size, z_dim))
z_noise2 = np.random.normal(0, 1, size=(batch_size, z_dim))
embedding_batch = embeddings_test[0:batch_size]
lr_fake_images, _ = stage1_gen.predict([embedding_batch, z_noise2], verbose=3)
hr_fake_images, _ = stage2_gen.predict([embedding_batch, lr_fake_images], verbose=3)
# Save images
for i, img in enumerate(hr_fake_images[:10]):
save_rgb_img(img, "results2/gen_{}_{}.png".format(epoch, i))
此处,save_rgb_img()是工具函数,在“训练第一阶段 StackGAN 部分”中定义。
- 最后,保存模型或其权重值:
# Saving the models stage2_gen.save_weights("stage2_gen.h5")
stage2_dis.save_weights("stage2_dis.h5")
恭喜,我们现在已经成功完成了第二阶段 StackGAN 的训练。 现在,我们有了一个生成器网络,可以生成大小 256x256x3的逼真的图像。 如果为生成器网络提供文本嵌入和噪声变量,它将生成 256x256x3分辨率图像。 让我们可视化网络的损失图。
可视化生成的图像
在将网络训练了 500 个时间段后,网络将开始生成如下所示的体面图像。
由 StackGAN 网络的第一阶段和第二阶段生成的图像
我建议您将网络训练 1000 个周期。 如果一切顺利,则在 1000 个周期之后,生成器网络将开始生成逼真的图像。
可视化损失
为了可视化训练的损失,请按以下方式启动 TensorBoard 服务器:
tensorboard --logdir=logs
现在,在浏览器中打开localhost:6006。 TensorBoard 的标量部分包含两个损失的曲线图,如下所示:
第一阶段的判别器网络的损失图如下所示:
第一阶段的生成器网络的损失图如下所示:
可以类似地从 Tensorboard 获得第二阶段的生成器网络和判别器网络的损失图。
这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 使用超参数,然后选择一组您认为可以提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。
可视化图
TensorBoard 的GRAPHS部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的操作:
StackGAN 的实际应用
StackGAN 的行业应用包括:
- 自动生成高分辨率图像以用于娱乐或教育目的
- 创建漫画:使用 StackGAN 可以将漫画创建过程缩短至几天,因为 StackGAN 可以自动生成漫画并协助创作过程
- 电影创作:StackGAN 可以通过根据文本描述生成帧来协助电影创作者
- 艺术创作:StackGAN 可以通过根据文字描述生成草图来协助艺术家
总结
在本章中,我们了解并实现了 StackGAN 网络,该网络可从文本描述生成高分辨率图像。 我们从对 StackGAN 的基本介绍开始,在其中我们探讨了 StackGAN 的架构细节,并发现了用于训练 StackGAN 的损失。 然后,我们下载并准备了数据集。 之后,我们开始在 Keras 框架中实现 StackGAN。 实现之后,我们依次训练了第一阶段和第二阶段 StackGANS。 成功训练网络后,我们评估了模型并将其保存以备将来使用。
在下一章中,我们将与 CycleGAN 合作,该网络可以将绘画转换为照片。