生成对抗网络项目:6~9(2)https://developer.aliyun.com/article/1426898
可视化损失
为了可视化训练的损失,请按以下方式启动 TensorBoard 服务器:
tensorboard --logdir=logs
现在,在浏览器中打开localhost:6006
。 TensorBoard 的标量部分包含两个损失的曲线图,如下所示:
第一阶段的判别器网络的损失图如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UY1cSkeq-1681652906146)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/8d391386-69b9-4890-bd5a-a078ba996754.png)]
第一阶段的生成器网络的损失图如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3IfsDoq-1681652906147)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/905056c0-7994-444e-b42b-4ebce2f093d9.png)]
可以类似地从 Tensorboard 获得第二阶段的生成器网络和判别器网络的损失图。
这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 使用超参数,然后选择一组您认为可以提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。
可视化图
TensorBoard 的GRAPHS
部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3361gjcm-1681652906147)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/b2df0366-0c95-4e61-a490-7efa0da76e73.png)]
StackGAN 的实际应用
StackGAN 的行业应用包括:
- 自动生成高分辨率图像以用于娱乐或教育目的
- 创建漫画:使用 StackGAN 可以将漫画创建过程缩短至几天,因为 StackGAN 可以自动生成漫画并协助创作过程
- 电影创作:StackGAN 可以通过根据文本描述生成帧来协助电影创作者
- 艺术创作:StackGAN 可以通过根据文字描述生成草图来协助艺术家
总结
在本章中,我们了解并实现了 StackGAN 网络,该网络可从文本描述生成高分辨率图像。 我们从对 StackGAN 的基本介绍开始,在其中我们探讨了 StackGAN 的架构细节,并发现了用于训练 StackGAN 的损失。 然后,我们下载并准备了数据集。 之后,我们开始在 Keras 框架中实现 StackGAN。 实现之后,我们依次训练了第一阶段和第二阶段 StackGANS。 成功训练网络后,我们评估了模型并将其保存以备将来使用。
在下一章中,我们将与 CycleGAN 合作,该网络可以将绘画转换为照片。
七、CycleGAN - 将绘画变成照片
CycleGAN 是一种生成对抗网络(GAN),用于跨域迁移任务,例如更改图像的样式,将绘画转变为照片, 反之亦然,例如照片增强功能,更改照片的季节等等。 CycleGAN 由朱俊彦,Taesung Park,Phillip Isola 和 Alexei A. Efros 在题为《使用循环生成对抗网络的非配对图像到图像转换》中引入。 该产品于 2018 年 2 月在加州大学伯克利分校的 Berkeley AI Research(BAIR)实验室生产,可通过以下链接获得。 由于其广泛的使用案例,CycleGAN 在 GAN 社区引起了轰动。 在本章中,我们将与 CycleGAN 一起使用,尤其是使用它们将绘画转换为照片。
在本章中,我们将介绍以下主题:
- CycleGAN 简介
- CycleGAN 的架构
- 数据收集与准备
- Keras 的 CycleGAN 实现
- 目标函数
- 训练 CycleGAN
- CycleGAN 的实际应用
CycleGAN 简介
为了将照片变成一幅画或一幅画,再将它们变成照片,普通 GAN 需要一对图像。 CycleGAN 是一种 GAN 网络,可以将图像从一个域 X 转换为另一个域 Y,而无需配对图像。 CycleGAN 尝试学习生成器网络,而生成器网络又学习了两个映射。 CycleGAN 无需训练大多数 GAN 中使用的单个生成器网络,而是训练两个生成器和两个判别器网络。
CycleGAN 中有两个生成器网络,如下所示:
- 生成器
A
:学习映射G: X -> Y
,其中X
是源域,Y
是目标域。 它从源域A
拍摄图像,并将其转换为与目标域B
相似的图像。 基本上,网络的目的是学习映射,以使G(X)
与Y
相似。 - 生成器
B
:学习映射F: Y -> X
,然后从目标域B
中获取图像,并将其转换为与源域A
中的图像相似的图像。类似地, 网络要学习另一个映射,以便F(G(X))
类似于X
)。
这两个网络的架构相同,但我们分别对其进行训练。
CycleGAN 中有两个判别器网络,如下所示:
- 判别器
A
:判别器A
的工作是区分由生成器网络A
生成的图像,这些图像表示为G(X)
和来自源域A
的真实图像,它们表示为X
。 - 判别器
B
:判别器B
的工作是区分由生成器网络B
生成的图像,这些图像表示为F(Y)
以及来自源域B
的真实图像,它们表示为Y
。
两个网络的架构是相同的。 与生成器网络类似,我们分别训练判别器网络。 如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FlevXRLf-1681652906147)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/969f8518-7d6b-42ce-9030-fdc7ca31818b.png)]
具有两个生成器和两个对抗性判别器网络的 CycleGAN 的插图。来源:arxiv:1703.10593
在下一节中,让我们详细了解 CycleGAN 的架构。
CycleGAN 的架构
CycleGAN 总体上由两种架构组成:生成器和判别器。 生成器架构用于创建两个模型,生成器 A 和生成器 B。判别器架构用于创建另外两个模型,判别器 A 和判别器 B。我们现在将在接下来的两节中介绍两个网络的架构。
生成器的架构
生成器网络是自编码器类型的网络。 它以图像作为输入并输出另一个图像。 它由两部分组成:编码器和解码器。 编码器包含具有下采样功能的卷积层,并将128x128x3
形状的输入转换为内部表示。 解码器包含两个上采样块和最后一个卷积层,该层将内部表示形式转换为128x128x3
形状的输出。
生成器网络包含以下块:
- 卷积块
- 残差块
- 上采样块
- 最后的卷积层
让我们逐一介绍每个组件:
- 卷积块:卷积块包含 2D 卷积层,然后是实例规范化层和 relu 作为激活函数。 请参阅第 1 章,“生成对抗网络简介”,以了解有关“实例规范化”的更多信息。
生成器网络包含三个卷积块,其配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
2D 卷积层 | filters=32, kernel_size=7, strides=1, padding='same' |
(128, 128, 3) |
(128, 128, 32) |
实例规范化层 | axis=1 |
(128, 128, 32) |
(128, 128, 32) |
激活层 | activation='relu' |
(128, 128, 32) |
(128, 128, 32) |
2D 卷积层 | filters=64, kernel_size=3, strides=2, padding='same' |
(128, 128, 32) |
(64, 64, 64) |
实例规范化层 | axis=1 |
(64, 64, 64) |
(64, 64, 64) |
激活层 | activation='relu' |
(64, 64, 64) |
(64, 64, 64) |
2D 卷积层 | filters=128, kernel_size=3, strides=2, padding='same' |
(64, 64, 64) |
(32, 32, 128) |
实例规范化层 | axis=1 |
(32, 32, 128) |
(32, 32, 128) |
激活层 | activation='relu' |
(32, 32, 128) |
(32, 32, 128) |
- 残差块:残差块包含两个 2D 卷积层。 两层之后是动量值等于 0.8 的批归一化层。 生成器网络包含六个残差块,其配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
2D 卷积层 | filters=128, kernel_size=3, strides=1, padding='same' |
(32, 32, 128) |
(32, 32, 128) |
批量规范化层 | axis=3, momentum=0.9, epsilon=1e-5 |
(32, 32, 128) |
(32, 32, 128) |
2D 卷积层 | filters=138, kernel_size=3, strides=1, padding='same' |
(32, 32, 128) |
((32, 32, 128) |
批量规范化层 | axis=3, momentum=0.9, epsilon=1e-5 |
(32, 32, 128) |
(32, 32, 128) |
加法层 | None |
(32, 32, 128) |
(32, 32, 128) |
加法层计算输入到块的张量与最后一个批量归一化层的输出之和。
- 上采样块:上采样块包含 2D 转置卷积层,并使用
relu
作为激活函数。 生成器网络中有两个上采样模块。 第一个上采样模块的配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
2D 转置卷积层 | filters=64, kernel_size=3, strides=2, padding='same', use_bias=False |
(32, 32, 128) |
(64, 64, 64) |
实例规范化层 | axis=1 |
(64, 64, 64) |
(64, 64, 64) |
激活层 | activation='relu' |
(64, 64, 64) |
(64, 64, 64) |
第二个上采样模块的配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
2D 转置卷积层 | filters=32, kernel_size=3, strides=2, padding='same', use_bias=False |
(64, 64, 64) |
(128, 128, 32) |
实例规范化层 | axis=1 |
(128, 128, 32) |
(128, 128, 32) |
激活层 | activation='relu' |
(128, 128, 32) |
(128, 128, 32) |
- 最后的卷积层:最后一层是使用
tanh
作为激活函数的 2D 卷积层。 它生成形状为(256, 256, 3)
的图像。 最后一层的配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
2D 卷积层 | filters=3, kernel_size=7, strides=1, padding='same', activation='tanh' |
(128, 128, 32) |
(128, 128, 3) |
这些超参数最适合 Keras 框架。 如果使用任何其他框架,请进行相应的修改。
判别器的架构
判别器网络的架构类似于 PatchGAN 网络中的判别器架构。 它是一个深度卷积神经网络,包含多个卷积块。 基本上,它会拍摄形状为(128, 128, 3)
的图像,并预测该图像是真实的还是假的。 它包含几个 2D 零填充,可以在以下链接中找到其文档。 下表详细显示了判别器网络的架构:
层名称 | 超参数 | 输入形状 | 输出形状 |
输入层 | none |
(128, 128, 3) |
(128, 128, 3) |
2D 零填充层 | padding(1, 1) |
(128, 128, 3) |
(130, 130, 3) |
2D 卷积层 | filters=64, kernel_size=4, strides=2, padding='valid' |
(130, 130, 3) |
(64, 64, 64) |
激活层 | activation='leakyrelu', alpha=0.2 |
(64, 64, 64) |
(64, 64, 64) |
2D 零填充层 | padding(1, 1) |
(64, 64, 64) |
(66, 66, 64) |
2D 卷积层 | filters=128, kernel_size=4, strides=2, padding='valid' |
(66, 66, 64) |
(32, 32, 128) |
实例规范化层 | axis=1 |
(32, 32, 128) |
(32, 32, 128) |
激活层 | activation='leakyrelu', alpha=0.2 |
(32, 32, 128) |
(32, 32, 128) |
2D 零填充层 | padding(1, 1) |
(32, 32, 128) |
(34, 34, 128) |
2D 卷积层 | filters=256, kernel_size=4, strides=2, padding='valid' |
(34, 34, 128) |
(16, 16, 256) |
实例规范化层 | axis=1 |
(16, 16, 256) |
(16, 16, 256) |
激活层 | activation='leakyrelu', alpha=0.2 |
(16, 16, 256) |
(16, 16, 256) |
2D 零填充层 | padding(1, 1) |
(16, 16, 256) |
(18, 18, 256) |
2D 卷积层 | filters=512, kernel_size=4, strides=2, padding='valid' |
(18, 18, 256) |
(8, 8, 512) |
实例规范化层 | axis=1 |
(8, 8, 512) |
(8, 8, 512) |
激活层 | activation='leakyrelu', alpha=0.2 |
(8, 8, 512) |
(8, 8, 512) |
2D 零填充层 | padding(1, 1) |
(8, 8, 512) |
(10, 10, 512) |
2D 卷积层 | filters=1, kernel_size=4, strides=1, padding='valid', activation='sigmoid' |
(10, 10, 512) |
(7, 7, 1) |
判别器网络返回形状为(7, 7, 1)
的张量。 现在,我们已经介绍了这两个网络的详细架构。 在下一节中,我们将介绍训练 CycleGAN 所需的目标函数。
ZeroPadding2D
层在图像张量的顶部,底部,左侧和右侧添加零行和零列。
训练目标函数
与其他 GAN 相似,CycleGAN 具有训练目标函数,我们需要将其最小化以训练模型。 损失函数是以下损失的加权总和:
- 对抗损失
- 循环一致性损失
在以下各节中,让我们详细研究对抗性损失和周期一致性损失。
对抗损失
对抗性损失是实际分布A
或B
的图像与生成器网络生成的图像之间的损失。 我们有两个映射函数,我们将对两个映射应用对抗性损失。
映射G: X -> Y
的对抗损失如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsyP7TIx-1681652906147)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/f278fd84-eb49-4d88-a98e-31396fe4ea3a.png)]
在此,x
是来自分布A
的一个域的图像,y
是来自分布B
的另一个域的图像。判别器D[y]
试图区分G
映射(G(x)
)生成的图像和来自不同的分布B
的真实图像y
。判别器D[x]
试图区分F
映射生成的图像(F(y)
)和来自分布A
的真实图像x
。G
的目的是使对抗损失函数D
最小,后者不断尝试使其最大化。
循环一致性损失
仅使用对抗损失的问题在于,网络可以将同一组输入图像映射到目标域中图像的任何随机排列。 因此,任何学习的映射都可以学习类似于目标分布的输出分布。 x[i]
和y[i]
之间可能有许多可能的映射函数。 循环一致性损失通过减少可能的映射数来克服了这个问题。 周期一致映射函数是可以将图像x
从域A
转换为域B
中的另一个图像y
并生成原始图像的函数。
前向循环一致性映射函数如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7sMRtBE-1681652906147)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/b6e02c98-294c-42d4-b687-3c2c7e84644a.png)]
向后循环一致映射函数如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YDzyWD46-1681652906148)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/bf736fcf-a789-4a17-b1a9-a452b23e07f9.png)]
循环一致性损失的公式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ca5ZOSzP-1681652906148)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/bae8d146-d70d-479e-bc91-801a8140d502.png)]
由于循环一致性损失,由F(G(x))
和G(F(y))
重构的图像分别类似于x
和y
。
完整目标函数
完整目标函数是对抗损失和周期一致性损失两者的加权总和,表示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1dK6Hco-1681652906148)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/b72c39e8-2d9b-487b-83df-3b73ab2c550f.png)]
在此,l[GAN](G, D[Y], X, Y)
是第一个对抗性损失,l[GAN](F, D[X], Y, X)
是第二个对抗性损失。 在生成器A
和判别器B
上计算第一个对抗损失。在生成器B
和判别器A
上计算第二个对抗损失。
要训练 CycleGAN,我们需要优化以下函数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KFnvJcaO-1681652906148)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/b0e30420-c79d-434a-b6d9-6fcaaa2a61ad.png)]
前面的等式表明,训练一个 CycleGAN,需要最小化生成器网络的损失,并使判别器网络的损失最大化。 优化之后,我们将获得一组训练有素的网络,能够从绘画中生成照片。
设置项目
如果尚未使用所有章节的完整代码克隆存储库,请立即克隆存储库。 下载的代码有一个名为Chapter07
的目录,其中包含本章的全部代码。 执行以下命令来设置项目:
- 首先,导航到父目录,如下所示:
cd Generative-Adversarial-Networks-Projects
- 现在,将目录从当前目录更改为
Chapter07
,如以下示例所示:
cd Chapter07
- 接下来,为该项目创建一个 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
文件,以获取有关如何设置项目的更多说明。 开发人员经常会遇到依赖关系不匹配的问题。 为每个项目创建一个单独的虚拟环境将解决此问题。
在本节中,我们已经成功设置了项目并安装了必需的依赖项。 在下一节中,我们将处理数据集。
下载数据集
在本章中,我们将使用monet2photo
数据集。 该数据集是开放源代码,可以由 UC Berkeley 的 Berkeley AI Research(BAIR)实验室使用。 您可以从以下链接选择手动下载数据集。
下载后,将其解压缩到根目录中。
或者,要自动下载数据集,请执行以下命令:
wget https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/monet2photo.zip upzip monet2photo.zip
这些命令将下载数据集并将其解压缩到项目的根目录中。
monet2photo
数据集仅可用于教育目的。 要将其用于商业项目,您必须获得 BAIR 实验室 UC Berkeley 的许可。 我们不拥有数据集中可用图像的版权。
CycleGAN 的 Keras 实现
如本章前面“CycleGAN 简介”部分中所述,CycleGAN 具有两种网络架构,即生成器网络和判别器网络。 在本节中,我们将编写所有网络的实现。
但是,在开始编写实现之前,请创建一个 Python 文件main.py
并导入基本模块,如下所示:
from glob import glob import matplotlib.pyplot as plt import numpy as np import tensorflow as tf from keras import Input, Model from keras.layers import Conv2D, BatchNormalization, Activation, Add, Conv2DTranspose, \ ZeroPadding2D, LeakyReLU from keras.optimizers import Adam from keras_contrib.layers import InstanceNormalization from scipy.misc import imread, imresize
生成器网络
在本章前面的“生成器网络的架构”部分中,我们已经探讨了生成器网络的架构。 让我们首先在 Keras 框架中编写生成器网络的层,然后使用 Keras 框架的函数式 API 创建 Keras 模型。
执行以下步骤以在 Keras 中实现生成器网络:
- 首先定义生成器网络所需的超参数,如下所示:
input_shape = (128, 128, 3) residual_blocks = 6
- 接下来,创建一个输入层,将输入馈送到网络,如下所示:
input_layer = Input(shape=input_shape)
- 将第一个卷积块与先前在“生成器网络”部分的架构中指定的超参数相加,如下所示:
x = Conv2D(filters=32, kernel_size=7, strides=1, padding="same")(input_layer) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x)
- 添加第二个卷积块,如下所示:
x = Conv2D(filters=64, kernel_size=3, strides=2, padding="same")(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x)
- 添加第三个卷积块,如下所示:
x = Conv2D(filters=128, kernel_size=3, strides=2, padding="same")(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x)
- 定义残差块,如下所示:
def residual_block(x): """ Residual block """ res = Conv2D(filters=128, kernel_size=3, strides=1, padding="same")(x) res = BatchNormalization(axis=3, momentum=0.9, epsilon=1e-5)(res) res = Activation('relu')(res) res = Conv2D(filters=128, kernel_size=3, strides=1, padding="same")(res) res = BatchNormalization(axis=3, momentum=0.9, epsilon=1e-5)(res) return Add()([res, x])
现在,使用residual_block()
函数向模型添加六个残差块,如以下示例所示:
for _ in range(residual_blocks): x = residual_block(x)
- 接下来,添加一个上采样块,如下所示:
x = Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same', use_bias=False)(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x)
- 添加另一个上采样模块,如下所示:
x = Conv2DTranspose(filters=32, kernel_size=3, strides=2, padding='same', use_bias=False)(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x)
- 最后,添加输出卷积层,如下所示:
x = Conv2D(filters=3, kernel_size=7, strides=1, padding="same")(x) output = Activation('tanh')(x)
这是生成器网络的最后一层。 它生成形状为(128, 128, 3)
的图像。
- 现在,通过为网络指定
inputs
和outputs
来创建 Keras 模型,如下所示:
model = Model(inputs=[input_layer], outputs=[output])
生成器网络的整个代码如下所示:
def build_generator(): """ Create a generator network using the hyperparameter values defined below """ input_shape = (128, 128, 3) residual_blocks = 6 input_layer = Input(shape=input_shape) # First Convolution block x = Conv2D(filters=32, kernel_size=7, strides=1, padding="same")(input_layer) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x) # 2nd Convolution block x = Conv2D(filters=64, kernel_size=3, strides=2, padding="same")(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x) # 3rd Convolution block x = Conv2D(filters=128, kernel_size=3, strides=2, padding="same")(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x) # Residual blocks for _ in range(residual_blocks): x = residual_block(x) # Upsampling blocks # 1st Upsampling block x = Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same', use_bias=False)(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x) # 2nd Upsampling block x = Conv2DTranspose(filters=32, kernel_size=3, strides=2, padding='same', use_bias=False)(x) x = InstanceNormalization(axis=1)(x) x = Activation("relu")(x) # Last Convolution layer x = Conv2D(filters=3, kernel_size=7, strides=1, padding="same")(x) output = Activation('tanh')(x) model = Model(inputs=[input_layer], outputs=[output]) return model
我们已经成功地为生成器网络创建了 Keras 模型。 在下一节中,我们将为判别器网络创建 Keras 模型。
判别器网络
我们已经在“判别器网络的架构”部分中探索了判别器网络的架构。让我们首先在 Keras 框架中编写判别器网络的层,然后使用 Keras 框架的函数式 API 创建 Keras 模型。
执行以下步骤以在 Keras 中实现判别器网络:
- 首先定义判别器网络所需的超参数,如下所示:
input_shape = (128, 128, 3) hidden_layers = 3
- 接下来,添加一个输入层,将输入馈送到网络,如下所示:
input_layer = Input(shape=input_shape)
- 接下来,添加一个二维零填充层,如下所示:
x = ZeroPadding2D(padding=(1, 1))(input_layer)
该层将在x
和y
轴上为输入张量添加填充。
- 接下来,使用先前在“判别器网络”部分的架构中指定的超参数添加卷积块,如下所示:
x = Conv2D(filters=64, kernel_size=4, strides=2, padding="valid")(x) x = LeakyReLU(alpha=0.2)(x)
- 接下来,添加另一个 2D 零填充层,如下所示:
x = ZeroPadding2D(padding=(1, 1))(x)
- 接下来,使用先前在“判别器网络”部分中指定的超参数添加三个卷积块,如下所示:
for i in range(1, hidden_layers + 1): x = Conv2D(filters=2 ** i * 64, kernel_size=4, strides=2, padding="valid")(x) x = InstanceNormalization(axis=1)(x) x = LeakyReLU(alpha=0.2)(x) x = ZeroPadding2D(padding=(1, 1))(x)
每个卷积块都有两个卷积层,实例规范化层,激活层和 2D 零填充层。
- 现在,将最终(
output
)卷积层添加到网络,如下所示:
output = Conv2D(filters=1, kernel_size=4, strides=1, activation="sigmoid")(x)
- 最后,通过指定网络的输入和输出来创建 Keras 模型,如下所示:
model = Model(inputs=[input_layer], outputs=[output])
判别器网络的整个代码如下所示:
def build_discriminator(): """ Create a discriminator network using the hyperparameter values defined below """ input_shape = (128, 128, 3) hidden_layers = 3 input_layer = Input(shape=input_shape) x = ZeroPadding2D(padding=(1, 1))(input_layer) # 1st Convolutional block x = Conv2D(filters=64, kernel_size=4, strides=2, padding="valid")(x) x = LeakyReLU(alpha=0.2)(x) x = ZeroPadding2D(padding=(1, 1))(x) # 3 Hidden Convolution blocks for i in range(1, hidden_layers + 1): x = Conv2D(filters=2 ** i * 64, kernel_size=4, strides=2, padding="valid")(x) x = InstanceNormalization(axis=1)(x) x = LeakyReLU(alpha=0.2)(x) x = ZeroPadding2D(padding=(1, 1))(x) # Last Convolution layer output = Conv2D(filters=1, kernel_size=4, strides=1, activation="sigmoid")(x) model = Model(inputs=[input_layer], outputs=[output]) return model
我们也已经成功地为判别器网络创建了 Keras 模型。 在下一部分中,我们将训练网络。
训练 CycleGAN
我们已经在“CycleGANs 简介”部分中介绍了训练目标函数。 我们还为两个网络分别创建了 Keras 模型。 训练 CycleGAN 是一个多步骤的过程。 我们将执行以下步骤来训练网络:
- 加载数据集
- 创建生成器和判别器网络
- 训练网络以达到指定次数
- 绘制损失
- 生成新图像
让我们在开始训练网络之前定义基本变量,如下所示:
data_dir = "/Path/to/dataset/directory/*.*" batch_size = 1 epochs = 500
加载数据集
在执行其他任何操作之前,请执行以下步骤来加载数据集:
- 首先使用
glob
模块创建图像路径列表,如下所示:
imagesA = glob(data_dir + '/testA/*.*') imagesB = glob(data_dir + '/testB/*.*')
我们具有来自两个域A
和B
的数据,这就是为什么我们创建了两个列表的原因。
- 接下来,遍历列表。 在循环中加载,调整大小和水平翻转图像,如下所示:
allImagesA = [] allImagesB = [] # Iterate over the lists for index, filename in enumerate(imagesA): # Load images imgA = imread(filename, mode='RGB') imgB = imread(imagesB[index], mode='RGB') # Resize images imgA = imresize(imgA, (128, 128)) imgB = imresize(imgB, (128, 128)) # Randomly horizontally flip images if np.random.random() > 0.5: imgA = np.fliplr(imgA) imgB = np.fliplr(imgB) allImagesA.append(imgA) allImagesB.append(imgB)
- 现在,对图像进行归一化以使像素值在 -1 和 1 之间的范围内,如下所示:
# Normalize images allImagesA = np.array(allImagesA) / 127.5 - 1. allImagesB = np.array(allImagesB) / 127.5 - 1.
加载数据集的整个代码如下所示:
def load_images(data_dir): imagesA = glob(data_dir + '/testA/*.*') imagesB = glob(data_dir + '/testB/*.*') allImagesA = [] allImagesB = [] for index, filename in enumerate(imagesA): # Load images imgA = imread(filename, mode='RGB') imgB = imread(imagesB[index], mode='RGB') # Resize images imgA = imresize(imgA, (128, 128)) imgB = imresize(imgB, (128, 128)) # Randomly horizontally flip images if np.random.random() > 0.5: imgA = np.fliplr(imgA) imgB = np.fliplr(imgB) allImagesA.append(imgA) allImagesB.append(imgB) # Normalize images allImagesA = np.array(allImagesA) / 127.5 - 1. allImagesB = np.array(allImagesB) / 127.5 - 1. return allImagesA, allImagesB
前面的函数将返回两个 Numpy ndarray
。 在开始训练之前,我们将使用它来加载和预处理图像。
建立和编译网络
在本节中,让我们构建必要的网络并准备进行训练。 执行以下步骤:
- 首先定义训练所需的优化器,如以下代码所示:
# Define the common optimizer common_optimizer = Adam(0.0002, 0.5)
我们将使用Adam
优化器,其中learning_rate
等于 0.0002,并且beta_1
值等于 0.5。
- 首先创建判别器网络,如以下代码所示:
discriminatorA = build_discriminator() discriminatorB = build_discriminator()
如“判别器网络的架构”部分所述,CycleGAN 具有两个判别器网络。
- 接下来,编译网络,如下所示:
discriminatorA.compile(loss='mse', optimizer=common_optimizer, metrics=['accuracy']) discriminatorB.compile(loss='mse', optimizer=common_optimizer, metrics=['accuracy'])
使用mse
作为损失函数,并使用accuracy
作为度量标准来编译网络。
- 接下来,创建生成器网络
A
(generatorAToB
)和B
(generatorBToA
)。 生成器网络A
的输入是数据集A
的真实图像(realA
),输出将是重构图像(fakeB
)。 生成器网络B
的输入是来自数据集B
的真实图像(realB
),输出将是重构图像(fakeA
),如下所示:
generatorAToB = build_generator() generatorBToA = build_generator()
如“CycleGAN 的架构”部分所述,CycleGAN 具有两个生成器网络。 generatorAToB
会将图像从域A
转换为域B
。generatorBToA
会将图像从域B
转换为域A
。
现在,我们已经创建了两个生成器网络和两个判别器网络。 在下一个小节中,我们将创建并编译一个对抗网络。
创建和编译对抗网络
对抗网络是一个组合网络。 它在单个 Keras 模型中使用所有四个网络。 创建对抗网络的主要目的是训练生成器网络。 当我们训练对抗网络时,它只训练生成器网络,但冻结了判别器网络的训练。 让我们创建一个具有所需功能的对抗模型。
- 首先为网络创建两个输入层,如下所示:
inputA = Input(shape=(128, 128, 3)) inputB = Input(shape=(128, 128, 3))
两个输入都将拍摄大小为(128, 128, 3)
的图像。 这些是符号输入变量,不包含实际值。 它们用于创建 Keras 模型(TensorFlow 图)。
- 接下来,使用生成器网络生成伪造图像,如下所示:
generatedB = generatorAToB(inputA) generatedA = generatorBToA(inputB)
使用符号输入层生成图像。
- 现在,再次使用生成器网络重建原始图像,如下所示:
reconstructedA = generatorBToA(generatedB) reconstructedB = generatorAToB(generatedA)
- 使用生成器网络生成伪造图像,如下所示:
generatedAId = generatorBToA(inputA) generatedBId = generatorAToB(inputB)
生成器网络A
(generatorAToB
)将图像从域A
转换为域B
。类似地,生成器网络B
(generatorBToA
)将图像从域B
转换为域A
。
- 接下来,使两个判别器网络均不可训练,如下所示:
discriminatorA.trainable = False discriminatorB.trainable = False
我们不想在我们的对抗网络中训练判别器网络。
- 使用判别器网络来预测每个生成的图像是真实的还是伪造的,如下所示:
probsA = discriminatorA(generatedA) probsB = discriminatorB(generatedB)
- 创建 Keras 模型并指定网络的输入和输出,如下所示:
adversarial_model = Model(inputs=[inputA, inputB],outputs=[probsA, probsB, reconstructedA, reconstructedB, generatedAId, generatedBId])
我们的对抗网络将采用两个输入值(即张量),并返回六个输出值(即张量)。
- 接下来,按如下所示编译对抗网络:
adversarial_model.compile(loss=['mse', 'mse', 'mae', 'mae', 'mae', 'mae'], loss_weights=[1, 1, 10.0, 10.0, 1.0, 1.0], optimizer=common_optimizer)
对抗网络返回六个值,我们需要为每个输出值指定损失函数。 对于前两个值,我们使用均方误差损失,因为这是对抗性损失的一部分。 对于接下来的四个值,我们使用平均绝对误差损失,这是周期一致性损失的一部分。 六个不同损失的权重值为 1,1,10.0,10.0,1.0,1.0。 我们正在使用common_optimizer
训练网络。
现在,我们已经成功为对抗网络创建了 Keras 模型。 如果您在理解 Keras 模型的工作方式时遇到困难,请查看 TensorFlow 图及其函数的文档。
在开始训练之前,请执行以下两个基本步骤。 TensorBoard 将在后面的部分中使用:
添加 TensorBoard 来存储损失和图以用于可视化目的,如下所示:
tensorboard = TensorBoard(log_dir="logs/{}".format(time.time()), write_images=True, write_grads=True, write_graph=True) tensorboard.set_model(generatorAToB) tensorboard.set_model(generatorBToA) tensorboard.set_model(discriminatorA) tensorboard.set_model(discriminatorB)
创建一个包含所有等于 1 的值的 4 维数组,该数组表示真实标签。 同样,创建另一个所有值均等于零的三维数组,代表伪标签,如下所示:
real_labels = np.ones((batch_size, 7, 7, 1)) fake_labels = np.zeros((batch_size, 7, 7, 1))
使用 numpy 的ones()
和zeros()
函数创建所需的ndarray
。 现在我们已经准备好基本组件,让我们开始训练。
开始训练
要针对指定的周期数训练网络,请执行以下步骤:
- 首先加载两个域的数据集,如下所示:
imagesA, imagesB = load_images(data_dir=data_dir)
我们已经在定义了load_images
函数。
- 接下来,创建一个
for
循环,该循环应运行由周期数指定的次数,如下所示:
for epoch in range(epochs): print("Epoch:{}".format(epoch))
- 创建两个列表以存储所有小批量的损失,如下所示:
dis_losses = [] gen_losses = []
- 计算
epochs
循环内的小批量数量,如下所示:
num_batches = int(min(imagesA.shape[0], imagesB.shape[0]) / batch_size) print("Number of batches:{}".format(num_batches))
- 接下来,在周期循环内创建另一个循环,并使其运行
num_batches
指定的次数,如下所示:
for index in range(num_batches): print("Batch:{}".format(index))
我们用于判别器网络和对抗网络训练的整个代码将在此循环内。
训练判别器网络
本小节中的代码是上一节中代码的延续。 在这里,您将看到如何训练判别器网络:
- 首先对两个域的图像进行小批量采样,如以下代码所示:
batchA = imagesA[index * batch_size:(index + 1) * batch_size] batchB = imagesB[index * batch_size:(index + 1) * batch_size]
- 接下来,使用生成器网络生成伪造图像,如下所示:
generatedB = generatorAToB.predict(batchA) generatedA = generatorBToA.predict(batchB)
- 然后,对判别器网络
A
进行真实图像和伪图像(由生成器网络生成)的训练,如下所示:
dALoss1 = discriminatorA.train_on_batch(batchA, real_labels) dALoss2 = discriminatorB.train_on_batch(generatedA, fake_labels)
此步骤将在真实图像和伪图像的微型批量上训练判别器A
,并会稍微改善网络。
- 接下来,对判别器
B
进行真实图像和伪图像的训练,如下所示:
dBLoss1 = discriminatorB.train_on_batch(batchB, real_labels) dbLoss2 = discriminatorB.train_on_batch(generatedB, fake_labels)
- 现在,计算判别器网络的总损失值,如下所示:
d_loss = 0.5 * np.add(0.5 * np.add(dALoss1, dALoss2), 0.5 * np.add(dBLoss1, dbLoss2))
到目前为止,我们一直在添加代码来训练判别器网络。 在下一部分中,我们将训练对抗性网络以训练生成器网络。
训练对抗网络
为了训练对抗网络,我们需要输入值和真实值。 网络的输入值为batchA
和batchB
。 基本真值是real_labels
,real_labels
,batchA
,batchB
,batchA
和batchB
,如下所示:
g_loss = adversarial_model.train_on_batch([batchA, batchB], [real_labels, real_labels, batchA, batchB, batchA, batchB])
此步骤将训练生成器网络,而无需训练生成器网络。
在每个微型批量上完成一次迭代(循环)之后,将损失存储在名为dis_losses
和gen_losses
的列表中,如下所示:
dis_losses.append(d_loss) gen_losses.append(g_loss)
每 10 个周期后,使用生成器网络生成一组图像:
# Sample and save images after every 10 epochs if epoch % 10 == 0: # Get a batch of test data batchA, batchB = load_test_batch(data_dir=data_dir, batch_size=2) # Generate images generatedB = generatorAToB.predict(batchA) generatedA = generatorBToA.predict(batchB) # Get reconstructed images reconsA = generatorBToA.predict(generatedB) reconsB = generatorAToB.predict(generatedA) # Save original, generated and reconstructed images for i in range(len(generatedA)): save_images(originalA=batchA[i], generatedB=generatedB[i], recosntructedA=reconsA[i], originalB=batchB[i], generatedA=generatedA[i], reconstructedB=reconsB[i], path="results/gen_{}_{}".format(epoch, i))
将前面的代码块放入epochs
循环中。 每隔 10 个时间段,它将生成一批伪图像并将其保存到结果目录。
接下来,将平均损失存储到 TensorBoard 中以进行可视化。 既存储损失,也要存储生成器网络的平均损失和判别器网络的平均损失,如以下示例所示:
write_log(tensorboard, 'discriminator_loss', np.mean(dis_losses), epoch) write_log(tensorboard, 'generator_loss', np.mean(gen_losses), epoch)
将前面的代码块放入epochs
循环中。
生成对抗网络项目:6~9(4)https://developer.aliyun.com/article/1426900