原文:Generative Adversarial Networks Projects
协议:CC BY-NC-SA 4.0
译者:飞龙
本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。
不要担心自己的形象,只关心如何实现目标。——《原则》,生活原则 2.3.c
六、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 :文本编码器,条件增强网络,生成器网络,判别器网络,嵌入压缩器网络
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYOTYhCZ-1681652906140)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/6f2f1522-849c-4ee4-9c08-57884ea1b6b1.png)]
来源: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]
创建高斯分布,可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQZiOqEK-1681652906141)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/bcc5921a-1e7e-497a-b134-61a55d04cecb.png)]
然后,我们从刚创建的高斯分布中采样c_hat[0]
。 计算c_hat[0]
的公式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OlYIe3mt-1681652906142)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/5bbaa568-0f1e-431b-b8d1-f8bacae72282.png)]
前面的方程式很不言自明。 为了对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])
。 让我们看一下生成器网络的架构,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1cEebkh-1681652906142)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/04c840a1-a6ae-4b3d-a141-ed58f9dae21b.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CoTJMrlO-1681652906142)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/c4ba3b4c-3d2b-4156-8e94-aea9a2e8c781.png)]
第一阶段的生成器网络架构
如您所见,生成器网络包含几个卷积层,其中每个卷积层后跟一个批量规范化层或一个激活层。 它的唯一目的是生成大小为64x64x3
的图像。 现在我们有了生成器网络的基本概念,让我们探索判别器网络。
判别器网络
类似于生成器网络,判别器网络是一个深度卷积神经网络,其中包含一系列下采样卷积层。 下采样层从图像生成特征映射,无论它们是真实数据分布P_data
的真实图像还是生成器网络生成的图像。 然后,我们将特征映射连接到文本嵌入。 我们使用压缩和空间复制将嵌入的文本转换为连接所需的格式。 空间压缩和复制包括一个完全连接的层,该层用于压缩嵌入到N[d]
维输出的文本,然后通过空间复制将其转换为M[d] × M[d] × N[d]
维张量。 然后将特征映射以及压缩的和空间复制的文本嵌入沿通道维级联。 最后,我们具有一个节点的完全连接层,该层用于二分类。 让我们看一下判别器网络的架构,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKCcidP7-1681652906142)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/fc0175ae-38b7-42aa-99ec-1bdbe36a8edf.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ET7sHCuY-1681652906143)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/3fd25fcc-55fa-4bce-b1d7-4f4d801ba076.png)]
第一阶段判别器网络的架构
如您所见,生成器网络包含几个卷积层。 判别器网络的唯一目的是区分来自真实数据分布的图像和生成器网络生成的图像。 现在,我们来看看 StackGAN 的第一阶段中使用的损失。
StackGAN 第一阶段的损失
StackGAN 的第一阶段中使用了两个损失,如下所示:
- 生成器损失
- 判别器损失
判别器损失l[D]
可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pTn0xKKZ-1681652906143)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/01fddc7c-38fb-4dc6-ac3b-3c73b448e65a.png)]
公式前面的很不言自明。 它代表了判别器网络的损失函数,其中两个网络都以文本嵌入为条件。
生成器损失l[G]
可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-atNnJmrc-1681652906143)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/6567c751-fa0d-4009-b4bd-b864d4ecab7c.png)]
公式前面的也很容易解释。 它代表了生成器网络的损失函数,其中两个网络都以文本嵌入为条件。 此外,它还包括损失函数的 KL 发散项。
第二阶段
第二阶段 StackGAN 的主要组件是生成器网络和判别器网络。 生成器网络是编码器-解码器类型的网络。 在此阶段不使用随机噪声z
,假设s[0]
已保留了随机性,其中s[0]
是第一阶段的生成器网络生成的图像。
我们首先使用预训练的文本编码器生成高斯条件变量c_hat
。 这将生成嵌入φ[t]
的相同文本。 第一阶段和第二阶段条件增强具有不同的完全连接层,用于生成不同的均值和标准差。 这意味着第二阶段 GAN 学会了在文本嵌入中捕获有用的信息,而这些信息被第一阶段 GAN 省略了。
第一阶段 GAN 生成的图像存在的问题是,它们可能缺少生动的对象部分,它们可能包含形状失真,并且它们可能会忽略对于生成逼真的图像非常重要的重要细节。 第二阶段 GAN 建立在第一阶段 GAN 的输出上。 第二阶段 GAN 取决于第一阶段 GAN 生成的低分辨率图像和文本描述。 它通过校正缺陷产生高分辨率的图像。
生成器网络
生成器网络还是深层卷积神经网络。 第一阶段的结果是低分辨率图像,它经过几个下采样层以生成图像特征。 然后,将图像特征和文本条件变量沿通道大小连接在一起。 之后,将级联张量馈入一些残差块,这些残差块学习跨图像和文本特征的多峰表示。 最后,最后一个操作的输出被馈送到一组上采样层,这些上采样层生成大小为256x256x3
的高分辨率图像。 让我们看一下生成器网络的架构,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EbUF29p-1681652906143)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/028e54b4-8184-45bd-8edf-abc7b7e8de97.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6KINxt6-1681652906144)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/cd2083c4-1490-4eef-a931-c924f6366e86.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6hnwcF7-1681652906144)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/f0da2f12-07bd-477a-97a6-dc21589a0b1f.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-niCj5rCH-1681652906144)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/6b5db762-e527-4194-85ab-83cc4ff9a1b5.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zttgkj77-1681652906144)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/e196749b-1617-4fcb-bbc5-14c392c944f1.png)]
第二阶段生成器的架构
该生成器网络的唯一目的是从低分辨率图像生成高分辨率图像。 低分辨率图像首先由第一阶段的生成器网络生成,然后馈送到第二阶段的生成器网络,后者生成高分辨率图像。
判别器网络
类似于生成器网络,判别器网络是一个深度卷积神经网络,并且包含额外的下采样层,因为图像的大小比第一阶段中的判别器网络大。 判别器是一个可识别匹配的判别器(有关更多信息,请参见以下链接,它使我们可以更好地实现图片和条件文本的对齐。 在训练期间,判别器将真实图像及其对应的文本描述作为正样本对,而负样本对则由两组组成。 第一组是具有不匹配的文本嵌入的真实图像,而第二组是具有相应的文本嵌入的合成图像。 让我们看一下判别器网络的架构,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYVqGM9i-1681652906144)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/a8c04bff-bd5a-4350-8b08-bd2ef6ae193b.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePIrahCy-1681652906145)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/73371faa-5c7e-475f-a169-4229e9d04b24.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmSUa3IH-1681652906145)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/42eb238c-1bba-415e-b416-469f04e29932.png)]
第二阶段判别器网络的架构
关于判别器网络架构的更多信息可以在“StackGAN 的 Keras 实现”部分中找到。
StackGAN 第二阶段的损失
与任何其他 GAN 相似,第二阶段 GAN 中的生成器G
和判别器D
也可以通过最大化判别器的损失并将生成器网络的损失最小化来训练 。
生成器损失l[G]
可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHHAR54a-1681652906145)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/24684f0c-814c-4b33-bfe7-c177cfe19a46.png)]
前面的方程式非常不言自明。 它代表了判别器网络的损失函数,其中两个网络都以文本嵌入为条件。 一个主要区别是生成器网络以s[0]
和c_hat
作为输入,其中s[0]
是第一阶段生成的图像,c_hat
是 CA 变量。
判别器损失l[D]
可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pnDyMxQA-1681652906145)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/8d494e68-1020-471d-91de-e2d6acf9552c.png)]
前面的方程式也很容易解释。 它代表了生成器网络的损失函数,其中两个网络都以文本嵌入为条件。 它还包括对损失函数的 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/
目录中。
从以下链接下载字符 CNN-RNN 嵌入。
提取数据集
CUB
数据集是压缩文件,需要提取。 使用以下命令提取CUB
数据集:
tar -xvzf CUB_200_2011.tgz
使用以下命令提取字符 CNN-RNN 嵌入:
unzip birds.zip
最后,将CUB_200_2011
放在data/birds
目录中。 现在可以使用我们的数据集了。
探索数据集
CUB 数据集总共包含 200 种不同鸟类的 11,788 张图像。 CUB 数据集中的图像包括以下内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-09QRcWKe-1681652906145)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/65c94f59-5ff4-47de-91ea-301d83e34811.png)]
这四张图片显示了黑脚信天翁,长尾小鹦鹉,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 timeimport 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 + meanreturn 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
将成为我们对生成器网络的输入。
- 接下来,创建具有
128
84*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 modelx, mean_logsigma = gen_model([input_layer, input_layer2])# Make discriminator trainable falsedis_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 Networkinput_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 Encoderx = 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 blockc_code = Lambda(joint_block)([c, x])# 3\. Residual Blocksx = 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 blocksx = 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 blockmerged_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 filedf_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 namesfile_names = df_file_names[1].tolist()# Create a dictionary of file_names and bounding boxesfilename_boundingbox_dict = {img_file[:-4]: [] for img_file in file_names[:2]}# Assign a bounding box to the corresponding imagefor i in range(0, len(file_names)):# Get the bounding boxbounding_box = df_bounding_boxes.iloc[i][1:].tolist()key = file_names[i][:-4]filename_boundingbox_dict[key] = bounding_boxreturn 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.sizeif 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 indexingfor index, filename in enumerate(filenames[:500]):# print(class_ids[index], filenames[index])bounding_box = bounding_boxes[filename]try:# Load imagesimg_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 vectorsz_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 imagesimage_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 imagesX_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 imagesfor 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 个时间段后,网络将开始生成如下所示的体面图像。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bja8c9Vd-1681652906146)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/030151f2-b1cf-4a2a-9d44-d55bc65f9c7c.png)]
由 StackGAN 网络的第一阶段和第二阶段生成的图像
我建议您将网络训练 1000 个周期。 如果一切顺利,则在 1000 个周期之后,生成器网络将开始生成逼真的图像。
可视化损失
为了可视化训练的损失,请按以下方式启动 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 = 6input_layer = Input(shape=input_shape)# First Convolution blockx = Conv2D(filters=32, kernel_size=7, strides=1, padding="same")(input_layer)x = InstanceNormalization(axis=1)(x)x = Activation("relu")(x)# 2nd Convolution blockx = Conv2D(filters=64, kernel_size=3, strides=2, padding="same")(x)x = InstanceNormalization(axis=1)(x)x = Activation("relu")(x)# 3rd Convolution blockx = Conv2D(filters=128, kernel_size=3, strides=2, padding="same")(x)x = InstanceNormalization(axis=1)(x)x = Activation("relu")(x)# Residual blocksfor _ in range(residual_blocks):x = residual_block(x)# Upsampling blocks# 1st Upsampling blockx = 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 blockx = 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 layerx = 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 blockx = 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 blocksfor 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 layeroutput = 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 imagesimgA = imread(filename, mode='RGB')imgB = imread(imagesB[index], mode='RGB')# Resize imagesimgA = imresize(imgA, (128, 128))imgB = imresize(imgB, (128, 128))# Randomly horizontally flip imagesif 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 imagesimgA = imread(filename, mode='RGB')imgB = imread(imagesB[index], mode='RGB')# Resize imagesimgA = imresize(imgA, (128, 128))imgB = imresize(imgB, (128, 128))# Randomly horizontally flip imagesif np.random.random() > 0.5:imgA = np.fliplr(imgA)imgB = np.fliplr(imgB)allImagesA.append(imgA)allImagesB.append(imgB)# Normalize imagesallImagesA = 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 databatchA, batchB = load_test_batch(data_dir=data_dir, batch_size=2)# Generate imagesgeneratedB = generatorAToB.predict(batchA)generatedA = generatorBToA.predict(batchB)# Get reconstructed imagesreconsA = generatorBToA.predict(generatedB)reconsB = generatorAToB.predict(generatedA)# Save original, generated and reconstructed imagesfor 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
循环中。
保存模型
在 Keras 中保存模型只需要一行代码。 要保存生成器模型,请添加以下行:
# Specify the path for the generator A model
generatorAToB.save("directory/for/the/generatorAToB/model.h5") # Specify the path for the generator B model
generatorBToA.save("directory/for/the/generatorBToA/model.h5")
同样,通过添加以下行来保存判别器模型:
# Specify the path for the discriminator A model
discriminatorA.save("directory/for/the/discriminatorA/model.h5") # Specify the path for the discriminator B model
discriminatorB.save("directory/for/the/discriminatorB/model.h5")
可视化生成的图像
在将网络训练了 100 个周期之后,网络将开始生成体面的图像。 让我们看一下生成器网络生成的图像。
10 个周期后,图像显示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MhE7i8VN-1681652906149)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/50a98c0c-f195-40b5-9025-436b7d52523f.png)]
在 20 个周期之后,图像显示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lv1r3tim-1681652906149)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/34d03920-182c-4630-bba6-25d7f728c1f6.png)]
我建议您将网络训练 1000 个周期。 如果一切顺利,则在 1000 个周期之后,生成器网络将开始生成逼真的图像。
可视化损失
为了可视化训练的损失,请按以下方式启动 TensorBoard 服务器:
tensorboard --logdir=logs
现在,在浏览器中打开localhost:6006
。 TensorBoard 的标量部分包含两个损失的图,如以下示例所示:
判别器网络的损失图如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybfNF6V9-1681652906149)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/69b11dd7-e38d-46ad-a293-69d3827daf0d.png)]
生成器网络的损失图如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfJF8ojg-1681652906149)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/4b6517ec-d185-4ec3-a4f9-acfbba2ee45f.png)]
这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 使用超参数,然后选择一组您认为可以提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。
可视化图
TensorBoard 的GRAPHS
部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的操作,如以下示例所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2QlW7EE-1681652906149)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/9bd3d80d-905f-4e80-8eb0-ee3827a69a8c.png)]
CycleGAN 的实际应用
CycleGAN 有许多应用。 在本章中,我们使用了 CycleGAN 将画作转换为照片。 它们还可以在以下情况下使用:
- 样式迁移:例如,将照片转换为绘画,反之亦然,将马的图像转换为斑马,反之亦然,将橘子的图像转换为苹果,反之亦然
- 照片增强:CycleGAN 可用于增强图片质量
- 季节转换:例如,将冬天的图片变成夏天的图片,反之亦然
- 游戏风格迁移:CycleGAN 可用于将游戏 A 的风格迁移到游戏 B
总结
在本章中,我们学习了如何使用 CycleGAN 将绘画转换为照片。 我们从介绍 CyleGAN 入手,并探讨了 CycleGAN 所涉及的网络架构。 我们还探讨了训练 CycleGAN 所需的不同损失函数。 接下来是在 Keras 框架中实现 CycleGAN。 我们在monet2photo
数据集上训练了 CycleGAN,并可视化了生成的图像,损失和不同网络的图。 在结束本章之前,我们探讨了 CycleGAN 的实际用例。
在下一章中,我们将在 pix2pix 网络上进行图像到图像的翻译。 在 pix2pix 中,我们将探索条件 GAN 进行图像翻译。
进一步阅读
CycleGAN 有许多已知的用例。 使用以下文章寻求帮助,探索新用途:
- 通过深度学习(CycleGAN)将 Fortnite 变成 PUBG
- GAN-CycleGAN(用图片玩魔术)
- GAN 和 CycleGAN
- CycleGANs 简介
- 在 TensorFlow 中理解和实现 CycleGAN
八、条件 GAN - 使用条件对抗网络的图像到图像翻译
Pix2pix 是一种生成对抗网络(GAN),用于图像到图像的翻译。 图像到图像转换是一种将图像的一种表示形式转换为另一种表示形式的方法。 Pix2pix 学习从输入图像到输出图像的映射。 它可用于将黑白图像转换为彩色图像,将草图转换为照片,将白天图像转换为夜间图像,将卫星图像转换为地图图像。 pix2pix 网络最早是由菲利普·伊索拉(Phillip Isola),朱俊彦,周婷慧,阿列克谢·埃弗罗斯(Alexei A. Efros)在名为《使用条件对抗网络的图像到图像转换》中引入的。 可以在以下链接中找到。
在本章中,我们将介绍以下主题:
- Pix2pix 网络介绍
- Pix2pix 网络的架构
- 数据收集与准备
- Pix2pix 的 Keras 实现
- 目标函数
- 训练 Pix2pix
- 评估训练好的模型
- Pix2pix 网络的实际应用
Pix2pix 介绍
Pix2pix 是条件 GAN 的变体。 我们已经在第 3 章,“使用条件 GAN(cGAN)进行人脸老化处理”中介绍。 在继续之前,请确保您了解什么是 cGAN。 一旦熟悉了 cGAN,就可以继续本章。 Pix2pix 是一种 GAN,能够使用机器学习(ML)的无监督方法执行图像到图像的翻译。 经过训练后,pix2pix 可以将图像从域 A 转换为域 B。朴素 CNN 也可以用于图像到图像的转换,但是它们不会生成逼真的图像。 另一方面,pix2pix 显示出巨大的潜力,能够生成逼真的图像。 我们将训练 pix2pix 将立面的标签转换为立面的图像。 让我们从了解 pix2pix 的架构开始。
pix2pix 的架构
与其他 GAN 相似,pix2pix 由两个网络组成:生成器网络和判别器网络。 生成器网络的架构受 U-Net 的架构的启发。 同样,判别器网络的架构也受到 PatchGAN 架构的启发。 这两个网络都是深度卷积神经网络。 在本节中,我们将详细探讨 pix2pix。
生成器网络
正如我们在上一节中提到的,生成器网络在很大程度上受到 U-Net 架构的启发。 U-Net 的架构与自编码器网络的架构几乎相同。 它们之间的主要区别在于,U-Net 网络在编码器中的各层之间具有跳跃连接,并且生成器网络和自编码器的解码器部分不具有跳跃连接。 U-Net 网络由两个网络组成:编码器网络和解码器网络。 下图从基本层面说明了 U-Net 的架构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BPPrPykB-1681652906150)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/ba22d993-cf32-42a1-86b9-94c2b37311e8.png)]
上图应使您了解 U-Net 的架构。 如您所见,第一层的输出直接与最后一层合并。 第二层的输出与第二层的第二层合并,依此类推。 如果n
是层的总数,则在编码器网络的第i
层与解码器网络的第n - i
层之间存在跳跃连接。 第i
层可以是这些层中的任何层。 让我们一个一个地仔细研究两个网络。
编码器网络
编码器网络是生成器网络的初始网络,包含八个卷积块,其配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
---|---|---|---|
第一个 2D 卷积层 | filter= 64, kernel_size = 4, stride= 2, padding ='same' | (256, 256, 1) | (128, 128, 64) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (128, 128, 64) | (128, 128, 64) |
第二个 2D 卷积层 | filter= 128, kernel_size = 4, stride= 2, padding ="same" | (128, 128, 64) | (64, 64, 128) |
批量规范化层 | 没有 | (64, 64, 128) | (64, 64, 128) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (64, 64, 128) | (64, 64, 128) |
第三个 2D 卷积层 | filter= 256, kernel_size = 4, stride= 2, padding ='same' | (64, 64, 128) | (32, 32, 256) |
批量规范化层 | 没有 | (32, 32, 256) | (32, 32, 256) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (32, 32, 256) | (32, 32, 256) |
第四个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (32, 32, 256) | (16, 16, 512) |
批量规范化层 | 没有 | (16, 16, 512) | (16, 16, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (16, 16, 512) | (16, 16, 512) |
第五个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (16, 16, 512) | (8, 8, 512) |
批量规范化层 | 没有 | (8, 8, 512) | (8, 8, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (8, 8, 512) | (8, 8, 512) |
第六个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (8, 8, 512) | (4, 4, 512) |
批量规范化层 | 没有 | (4, 4, 512) | (4, 4, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (4, 4, 512) | (4, 4, 512) |
第七个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (4, 4, 512) | (2, 2, 512) |
批量规范化层 | 没有 | (2, 2, 512) | (2, 2, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (2, 2, 512) | (2, 2, 512) |
第八个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (2, 2, 512) | (1, 1, 512) |
批量规范化层 | 没有 | (1, 1, 512) | (1, 1, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (1, 1, 512) | (1, 1, 512) |
编码器网络之后是解码器网络。 我们将在下一节中了解解码器网络的架构。
解码器网络
生成器网络中的解码器网络还包括八个上采样卷积块。 八个上采样卷积块的配置如下:
层名称 | 超参数 | 输入形状 | 输出形状 |
---|---|---|---|
第一个 2D 上采样层 | size=(2, 2) | (1, 1, 512) | (2, 2, 512) |
2D 卷积层 | filter= 512, kernel_size = 4, stride= 1, padding="same" | (2, 2, 512) | (2, 2, 512) |
批量规范化层 | 没有 | (2, 2, 512) | (2, 2, 512) |
丢弃层 | 丢弃= 0.5 | (2, 2, 512) | (2, 2, 512) |
级联层(编码器网络中的第 7 个卷积层) | axis=3 | (2, 2, 512) | (2, 2, 1024) |
激活层 | activation="relu" | (2, 2, 1024) | (2, 2, 1024) |
第二个 2D 上采样层 | size=(2, 2) | (2, 2, 1024) | (4, 4, 1024) |
2D 卷积层 | filter= 1024, kernel_size = 4, stride= 1, padding="same" | (4, 4, 1024) | (4, 4, 1024) |
批量规范化层 | 没有 | (4, 4, 1024) | (4, 4, 1024) |
丢弃层 | 丢弃= 0.5 | (4, 4, 1024) | (4, 4, 1024) |
级联层(编码器网络中的第 6 个卷积层) | axis=3 | (4, 4, 1024) | (4, 4, 1536) |
激活层 | activation="relu" | (4, 4, 1536) | (4, 4, 1536) |
第三个 2D 上采样层 | size=(2, 2) | (4, 4, 1536) | (8, 8, 1536) |
2D 卷积层 | filter= 1024, kernel_size = 4, stride= 1, padding="same" | (8, 8, 1536) | (8, 8, 1024) |
批量规范化层 | 没有 | (8, 8, 1024) | (8, 8, 1024) |
丢弃层 | 丢弃= 0.5 | (8, 8, 1024) | (8, 8, 1024) |
级联层(编码器网络中的第 5 个卷积层) | axis=3 | (8, 8, 1024) | (8, 8, 1536) |
激活层 | activation="relu" | (8, 8, 1536) | (8, 8, 1536) |
第四个 2D 上采样层 | size=(2, 2) | (8, 8, 1536) | (16, 16, 1536) |
2D 卷积层 | filter= 1024, kernel_size = 4, stride= 1, padding="same" | (16, 16, 1536) | (16, 16, 1024) |
批量规范化层 | 没有 | (16, 16, 1024) | (16, 16, 1024) |
连接层(编码器网络的第 4 个卷积层) | axis=3 | (16, 16, 1024) | (16, 16, 1536) |
激活层 | activation="relu" | (16, 16, 1536) | (16, 16, 1536) |
第五个 2D 上采样层 | size=(2, 2) | (16, 16, 1536) | (32, 32, 1536) |
2D 卷积层 | filter= 1024, kernel_size = 4, stride= 1, padding="same" | (32, 32, 1536) | (32, 32, 1024) |
批量规范化层 | 没有 | (32, 32, 1024) | (32, 32, 1024) |
连接层(编码器网络的第 3 个卷积层) | axis=3 | (32, 32, 1024) | (32, 32, 1280) |
激活层 | activation="relu" | (32, 32, 1280) | (32, 32, 1280) |
第六个 2D 上采样层 | size=(2, 2) | (64, 64, 1280) | (64, 64, 1280) |
2D 卷积层 | filter= 512, kernel_size = 4, stride= 1, padding="same" | (64, 64, 1280) | (64, 64, 512) |
批量规范化层 | 没有 | (64, 64, 512) | (64, 64, 512) |
连接层(编码器网络中的第二个卷积层) | axis=3 | (64, 64, 512) | (64, 64, 640) |
激活层 | activation="relu" | (64, 64, 640) | (64, 64, 640) |
第七个 2D 上采样层 | size=(2, 2) | (64, 64, 640) | (128, 128, 640) |
2D 卷积层 | filter= 256, kernel_size = 4, stride= 1, padding="same" | (128, 128, 640) | (128, 128, 256) |
批量规范化层 | 没有 | (128, 128, 256) | (128, 128, 256) |
连接层(编码器网络中的第一个卷积层) | axis=3 | (128, 128, 256) | (128, 128, 320) |
激活层 | activation="relu" | (128, 128, 320) | (128, 128, 320) |
第八个 2D 上采样层 | size=(2, 2) | (128, 128, 320) | (256, 256, 320) |
2D 卷积层 | filter= 1, kernel_size = 4, stride= 1, padding="same" | (256, 256, 320) | (256, 256, 1) |
激活层 | activation="tanh" | (256, 256, 1) | (256, 256, 1) |
生成器网络具有七个跳跃连接,可以将其定义如下:
- 从第 1 个编码器模块到第 7 个解码器模块的输出的连接
- 从第 2 个编码器模块到第 6 个解码器模块的输出的连接
- 从第 3 个编码器模块到第 5 个解码器模块的输出的连接
- 从第 4 个编码器模块到第 4 个解码器模块的输出的连接
- 从第 5 个编码器模块到第 3 个解码器模块的输出的连接
- 从第 6 个编码器模块到第 2 个解码器模块的输出的连接
- 从第 7 个编码器模块到第 1 个解码器模块的输出的连接
连接沿着通道轴发生。 编码器网络的最后一层将张量传递到解码器网络的第一层。 在编码器网络的最后一块和解码器网络的最后一块没有连接。
生成器网络由这两个网络组成。 基本上,编码器网络是下采样器,而解码器网络是上采样器。 编码器网络将大小为(256, 256, 1)
的图像采样为大小为(1, 1, 1, 512)
的内部表示。 另一方面,解码器网络将大小为(1, 1, 1, 512)
的内部表示采样为大小为(256, 256, 1)
的输出图像。
在“pix2pix 的 Keras 实现”中,我们将详细介绍该架构。
判别器网络
pix2pix 中判别器网络的架构受到 PatchGAN 网络架构的启发。 PatchGAN 网络包含八个卷积块,如下所示:
层名称 | 超参数 | 输入形状 | 输出形状 |
---|---|---|---|
第一个 2D 卷积层 | filter= 64, kernel_size = 4, stride= 2, padding ='same' | (256, 256, 1) | (256, 256, 64) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (128, 128, 64) | (128, 128, 64) |
第二个 2D 卷积层 | filter= 128, kernel_size = 4, stride= 2, padding ="same" | (128, 128, 64) | (64, 64, 128) |
批量规范化层 | 没有 | (64, 64, 128) | (64, 64, 128) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (64, 64, 128) [ | (64, 64, 128) |
第三个 2D 卷积层 | filter= 256, kernel_size = 4, stride= 2, padding ='same' | (64, 64, 128) | (32, 32, 256) |
批量规范化层 | 没有 | (32, 32, 256) | (32, 32, 256) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (32, 32, 256) | (32, 32, 256) |
第四个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (32, 32, 256) | (16, 16, 512) |
批量规范化层 | 没有 | (16, 16, 512) | (16, 16, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (16, 16, 512) | (16, 16, 512) |
第五个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (16, 16, 512) | (8, 8, 512) |
批量规范化层 | 没有 | (8, 8, 512) | (8, 8, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (8, 8, 512) | (8, 8, 512) |
第六个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (8, 8, 512) | (4, 4, 512) |
批量规范化层 | 没有 | (4, 4, 512) | (4, 4, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (4, 4, 512) | (4, 4, 512) |
第七个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (4, 4, 512) | (2, 2, 512) |
批量规范化层 | 没有 | (2, 2, 512) | (2, 2, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (2, 2, 512) | (2, 2, 512) |
第八个 2D 卷积层 | filter= 512, kernel_size = 4, stride= 2, padding ="same" | (4, 4, 512) | (1, 1, 512) |
批量规范化层 | 没有 | (1, 1, 512) | (1, 1, 512) |
激活层 | Activation ='leakyrelu', alpha = 0.2 | (1, 1, 512) | (1, 1, 512) |
展开层 | 没有 | (1, 1, 512) | (512, ) |
密集层 | unit= 2, activation='softmax' | (1, 1, 512) | (2, ) |
下表突出显示了判别器网络的架构和配置。 展开层将张量展开为一维数组。
判别器网络中的其余层将在本章的“Keras pix2pix 实现”部分中介绍。
现在,我们已经探索了这两个网络的架构和配置。 现在,我们将探讨训练 pix2pix 所需的训练目标函数。
训练目标函数
Pix2pix 是一个条件生成对抗网络,并且用于条件 GAN 的目标函数可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDGZszln-1681652906150)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/a2a19bc3-64c1-48bd-b2d8-4b1303293dce.png)]
在这里,网络G
(生成器)正试图使针对对手D
(判别器)的先前函数最小化,而对手D
则试图使先前的函数最大化。
如果必须比较朴素 GAN 和条件 GAN 的目标函数,则朴素 GAN 的目标函数如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DOO6jUsJ-1681652906150)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/b2a6e013-9ffb-4a51-93fb-91454976c9c4.png)]
为了减少图像的模糊,我们可以向目标函数添加一个 L1 损失函数。 L1 损失函数可以表示为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EG1yBzAb-1681652906151)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/e5f5e27a-31d3-4fcc-a69c-3fd555c755b4.png)]
在该等式中,y
是原始图像, G(x, z)
是生成器网络生成的图像。 L1 损失是由原始图像的所有像素值与生成的图像的所有像素值之间的所有绝对差值的总和来计算的。
pix2pix 的最终目标函数如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T5ZM39Y7-1681652906151)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/aea66002-edd3-4c8f-bea7-91ebc8e0aaf4.png)]
这是条件 GAN 的损失函数和 L1 损失函数的加权和。
现在我们对 pix2pix 网络有了基本的了解。 在开始在 Keras 中实现 pix2pix 之前,让我们设置项目。
设置项目
如果尚未使用所有章节的完整代码克隆存储库,请立即克隆存储库。 克隆的存储库有一个名为Chapter09
的目录,其中包含本章的全部代码。 执行以下命令来设置项目:
- 首先,导航到父目录,如下所示:
cd Generative-Adversarial-Networks-Projects
- 现在,将目录从当前目录更改为
Chapter09
:
cd Chapter09
- 接下来,为该项目创建一个 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
文件,以获取有关如何设置项目的更多说明。 开发人员经常会遇到依赖关系不匹配的问题。 为每个项目创建一个单独的虚拟环境将解决此问题。
在本节中,我们已成功设置项目并安装了所需的依赖项。 在下一部分中,我们将处理数据集。 现在,我们将探讨下载和格式化数据集所需的各个步骤。
准备数据
在本章中,我们将使用 Facades 数据集,该数据集可从以下链接获得。
该数据集包含立面标签和真实立面图像。 外观通常是建筑物的正面,外观标签是外观图像的建筑标签。 下载数据集后,我们将了解有关立面的更多信息。 执行以下命令以下载和提取数据集:
- 通过执行以下命令下载数据集:
# Before downloading the dataset navigate to data directory
cd data# Download the dataset
wget http://efrosgans.eecs.berkeley.edu/pix2pix/datasets/facades.tar.gz
- 下载数据集后,使用以下命令提取数据集:
tar -xvzf facades.tar.gz
下载的数据集的文件结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8OcUSW7G-1681652906151)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/dd18e210-40f0-4f5e-b898-37d023af1add.png)]
数据集分为训练,测试和验证数据集。 让我们来提取图像。
执行以下步骤以加载数据集:
- 首先创建包含外观标签的
.h5
文件列表和包含外观图像的.h5
另一个列表,如下所示:
data_dir_path = os.path.join(data_dir, data_type)# Get all .h5 files containing training images facade_photos_h5 = [f for f in os.listdir(os.path.join(data_dir_path, 'images')) if '.h5' in f]
facade_labels_h5 = [f for f in os.listdir(os.path.join(data_dir_path, 'facades')) if '.h5' in f]
- 接下来,遍历列表以依次加载每个图像:
final_facade_photos = None final_facade_labels = None for index in range(len(facade_photos_h5)):
此步骤之后的所有代码都将位于前面的for
循环中。
- 接下来,加载包含图像的
h5
文件,并检索实际图像的 Numpy N 维数组:
facade_photos_path = data_dir_path + '/images/' + facade_photos_h5[index]
facade_labels_path = data_dir_path + '/facades/' + facade_labels_h5[index]facade_photos = h5py.File(facade_photos_path, 'r')
facade_labels = h5py.File(facade_labels_path, 'r')
- 接下来,将图像调整为所需的图像大小,如下所示:
# Resize and normalize images num_photos = facade_photos['data'].shape[0]
num_labels = facade_labels['data'].shape[0]all_facades_photos = np.array(facade_photos['data'], dtype=np.float32)
all_facades_photos = all_facades_photos.reshape((num_photos, img_width, img_height, 1)) / 255.0 all_facades_labels = np.array(facade_labels['data'], dtype=np.float32)
all_facades_labels = all_facades_labels.reshape((num_labels, img_width, img_height, 1)) / 255.0
- 接下来,将调整大小的图像添加到最终的 N 维数组中:
if final_facade_photos is not None and final_facade_labels is not None:
final_facade_photos = np.concatenate([final_facade_photos, all_facades_photos], axis=0)
final_facade_labels = np.concatenate([final_facade_labels, all_facades_labels], axis=0)else:final_facade_photos = all_facades_photosfinal_facade_labels = all_facades_labels
加载和调整图像大小的整个代码如下所示:
def load_dataset(data_dir, data_type, img_width, img_height):data_dir_path = os.path.join(data_dir, data_type)# Get all .h5 files containing training imagesfacade_photos_h5 = [f for f in os.listdir(os.path.join(data_dir_path, 'images')) if '.h5' in f]facade_labels_h5 = [f for f in os.listdir(os.path.join(data_dir_path, 'facades')) if '.h5' in f]final_facade_photos = Nonefinal_facade_labels = None for index in range(len(facade_photos_h5)):facade_photos_path = data_dir_path + '/images/' + facade_photos_h5[index]facade_labels_path = data_dir_path + '/facades/' + facade_labels_h5[index]facade_photos = h5py.File(facade_photos_path, 'r')facade_labels = h5py.File(facade_labels_path, 'r')# Resize and normalize imagesnum_photos = facade_photos['data'].shape[0]num_labels = facade_labels['data'].shape[0]all_facades_photos = np.array(facade_photos['data'], dtype=np.float32)all_facades_photos = all_facades_photos.reshape((num_photos, img_width, img_height, 1)) / 255.0 all_facades_labels = np.array(facade_labels['data'], dtype=np.float32)all_facades_labels = all_facades_labels.reshape((num_labels, img_width, img_height, 1)) / 255.0 if final_facade_photos is not None and final_facade_labels is not None:final_facade_photos = np.concatenate([final_facade_photos, all_facades_photos], axis=0)final_facade_labels = np.concatenate([final_facade_labels, all_facades_labels], axis=0)else:final_facade_photos = all_facades_photosfinal_facade_labels = all_facades_labelsreturn final_facade_photos, final_facade_labels
先前的函数将从训练,测试和验证目录中的.h5
文件加载图像。
可视化图像
可视化外观标签和外观图像的 Python 函数如下所示:
def visualize_bw_image(img):"""Visualize a black and white image """ fig = plt.figure()ax = fig.add_subplot(1, 1, 1)ax.imshow(img, cmap='gray', interpolation='nearest')ax.axis("off")ax.set_title("Image")plt.show()
使用前面的函数可以使外观标签或外观照片可视化,如下所示:
visualize_bw_image(image)
visualize_bw_image(image)
建筑物外墙的图像示例如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n788O55k-1681652906151)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/3af97dd6-cd57-48b8-92bb-cd83f75125d4.png)]
下图表示前面的门面图像的建筑标签:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eEU9Pq3h-1681652906151)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/1afafd90-be55-4633-97c5-a1d1eb7fd6c3.png)]
我们将训练一个 pix2pix 网络,该网络能够从立面标签生成立面图像。 让我们开始使用生成器和判别器的 Keras 实现。
pix2pix 的 Keras 实现
如前所述,pix2pix 具有两个网络:一个生成器和一个判别器。 该生成器受 U-Net 架构的启发。 同样,判别器网络也受到 PatchGAN 架构的启发。 我们将在以下部分中实现这两个网络。
在开始编写实现之前,创建一个 Python 文件 main.py
,然后按以下方式导入基本模块:
import os
import timeimport h5py
import keras.backend as K
import matplotlib.pyplot as plt
import numpy as np
from cv2 import imwrite
from keras import Input, Model
from keras.layers import Convolution2D, LeakyReLU, BatchNormalization, UpSampling2D, Dropout, Activation, Flatten, Dense, Lambda, Reshape, concatenate
from keras.optimizers import Adam
生成器网络
生成器网络从源域 A 拍摄大小为(256, 256, 1)
的图像,并将其转换为目标域 B 中大小为(256, 256, 1)
的图像。 基本上,它将图像从源域 A 转换为目标域 B。让我们在 Keras 框架中实现生成器网络。
执行以下步骤来创建生成器网络:
- 首先定义生成器网络所需的超参数:
kernel_size = 4 strides = 2 leakyrelu_alpha = 0.2 upsampling_size = 2 dropout = 0.5 output_channels = 1 input_shape = (256, 256, 1)
- 现在创建一个输入层,将输入输入到网络,如下所示:
input_layer = Input(shape=input_shape)
输入层获取形状为(256, 256, 1)
的输入图像,并将其传递到网络中的下一层。
如上所述,生成器网络具有两个部分:编码器和解码器。 在接下来的几个步骤中,我们将编写编码器部分的代码。
- 使用先前在“pix2pix”部分中指示的参数,将第一个卷积块添加到生成器网络:
# 1st Convolutional block in the encoder network encoder1 = Convolution2D(filters=64, kernel_size=kernel_size, padding='same', strides=strides)(input_layer)
encoder1 = LeakyReLU(alpha=leakyrelu_alpha)(encoder1)
第一卷积块包含具有激活函数的 2D 卷积层。 与其他七个卷积块不同,它没有批量规范化层。
- 将其他七个卷积块添加到生成器网络:
# 2nd Convolutional block in the encoder network encoder2 = Convolution2D(filters=128, kernel_size=kernel_size, padding='same',strides=strides)(encoder1)
encoder2 = BatchNormalization()(encoder2)
encoder2 = LeakyReLU(alpha=leakyrelu_alpha)(encoder2)# 3rd Convolutional block in the encoder network encoder3 = Convolution2D(filters=256, kernel_size=kernel_size, padding='same',strides=strides)(encoder2)
encoder3 = BatchNormalization()(encoder3)
encoder3 = LeakyReLU(alpha=leakyrelu_alpha)(encoder3)# 4th Convolutional block in the encoder network encoder4 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder3)
encoder4 = BatchNormalization()(encoder4)
encoder4 = LeakyReLU(alpha=leakyrelu_alpha)(encoder4)# 5th Convolutional block in the encoder network encoder5 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder4)
encoder5 = BatchNormalization()(encoder5)
encoder5 = LeakyReLU(alpha=leakyrelu_alpha)(encoder5)# 6th Convolutional block in the encoder network encoder6 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder5)
encoder6 = BatchNormalization()(encoder6)
encoder6 = LeakyReLU(alpha=leakyrelu_alpha)(encoder6)# 7th Convolutional block in the encoder network encoder7 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder6)
encoder7 = BatchNormalization()(encoder7)
encoder7 = LeakyReLU(alpha=leakyrelu_alpha)(encoder7)# 8th Convolutional block in the encoder network encoder8 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder7)
encoder8 = BatchNormalization()(encoder8)
encoder8 = LeakyReLU(alpha=leakyrelu_alpha)(encoder8)
这是生成器网络中编码器部分的末端。 生成器网络中的第二部分是解码器。 在接下来的几个步骤中,让我们为解码器编写代码。
- 将第一个上采样卷积块添加到先前在“pix2pix”部分的架构中指示的参数中:
# 1st Upsampling Convolutional Block in the decoder network decoder1 = UpSampling2D(size=upsampling_size)(encoder8)
decoder1 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same')(decoder1)
decoder1 = BatchNormalization()(decoder1)
decoder1 = Dropout(dropout)(decoder1)
decoder1 = concatenate([decoder1, encoder7], axis=3)
decoder1 = Activation('relu')(decoder1)
第一个上采样模块从编码器部分的最后一层获取输入。 它具有一个 2D 上采样层,一个 2D 卷积层,一个批量归一化层,一个脱落层,一个连接操作和一个激活函数。 请参阅 Keras 文档以找到有关这些层的更多信息,该文档可从这里获得。
- 同样,添加以下七个卷积块,如下所示:
# 2nd Upsampling Convolutional block in the decoder network decoder2 = UpSampling2D(size=upsampling_size)(decoder1)
decoder2 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder2)
decoder2 = BatchNormalization()(decoder2)
decoder2 = Dropout(dropout)(decoder2)
decoder2 = concatenate([decoder2, encoder6])
decoder2 = Activation('relu')(decoder2)# 3rd Upsampling Convolutional block in the decoder network decoder3 = UpSampling2D(size=upsampling_size)(decoder2)
decoder3 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder3)
decoder3 = BatchNormalization()(decoder3)
decoder3 = Dropout(dropout)(decoder3)
decoder3 = concatenate([decoder3, encoder5])
decoder3 = Activation('relu')(decoder3)# 4th Upsampling Convolutional block in the decoder network decoder4 = UpSampling2D(size=upsampling_size)(decoder3)
decoder4 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder4)
decoder4 = BatchNormalization()(decoder4)
decoder4 = concatenate([decoder4, encoder4])
decoder4 = Activation('relu')(decoder4)# 5th Upsampling Convolutional block in the decoder network decoder5 = UpSampling2D(size=upsampling_size)(decoder4)
decoder5 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder5)
decoder5 = BatchNormalization()(decoder5)
decoder5 = concatenate([decoder5, encoder3])
decoder5 = Activation('relu')(decoder5)# 6th Upsampling Convolutional block in the decoder network decoder6 = UpSampling2D(size=upsampling_size)(decoder5)
decoder6 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same')(decoder6)
decoder6 = BatchNormalization()(decoder6)
decoder6 = concatenate([decoder6, encoder2])
decoder6 = Activation('relu')(decoder6)# 7th Upsampling Convolutional block in the decoder network decoder7 = UpSampling2D(size=upsampling_size)(decoder6)
decoder7 = Convolution2D(filters=256, kernel_size=kernel_size, padding='same')(decoder7)
decoder7 = BatchNormalization()(decoder7)
decoder7 = concatenate([decoder7, encoder1])
decoder7 = Activation('relu')(decoder7)# Last Convolutional layer decoder8 = UpSampling2D(size=upsampling_size)(decoder7)
decoder8 = Convolution2D(filters=output_channels, kernel_size=kernel_size, padding='same')(decoder8)
decoder8 = Activation('tanh')(decoder8)
最后一层的激活函数为'tanh'
,因为我们打算让生成器生成介于 -1 到 1 之间的值。'concatenate'
层用于添加跳跃连接。 最后一层将生成张量为(256, 256, 1)
的张量。
The ‘concatenate’ layer concatenates tensors along the channel dimension. You can provide a value for the axis, along which you want your tensors to be concatenated.
- 最后,通过指定生成器网络的输入和输出来创建 Keras 模型:
# Create a Keras model
model = Model(inputs=[input_layer], outputs=[decoder8])
Python 函数内部的生成器网络的整个代码如下所示:
def build_unet_generator():"""Create U-Net Generator using the hyperparameter values defined below """ kernel_size = 4strides = 2leakyrelu_alpha = 0.2upsampling_size = 2dropout = 0.5output_channels = 1input_shape = (256, 256, 1)input_layer = Input(shape=input_shape)# Encoder Network # 1st Convolutional block in the encoder network encoder1 = Convolution2D(filters=64, kernel_size=kernel_size, padding='same',strides=strides)(input_layer)encoder1 = LeakyReLU(alpha=leakyrelu_alpha)(encoder1)# 2nd Convolutional block in the encoder networkencoder2 = Convolution2D(filters=128, kernel_size=kernel_size, padding='same',strides=strides)(encoder1)encoder2 = BatchNormalization()(encoder2)encoder2 = LeakyReLU(alpha=leakyrelu_alpha)(encoder2)# 3rd Convolutional block in the encoder networkencoder3 = Convolution2D(filters=256, kernel_size=kernel_size, padding='same',strides=strides)(encoder2)encoder3 = BatchNormalization()(encoder3)encoder3 = LeakyReLU(alpha=leakyrelu_alpha)(encoder3)# 4th Convolutional block in the encoder networkencoder4 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder3)encoder4 = BatchNormalization()(encoder4)encoder4 = LeakyReLU(alpha=leakyrelu_alpha)(encoder4)# 5th Convolutional block in the encoder networkencoder5 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder4)encoder5 = BatchNormalization()(encoder5)encoder5 = LeakyReLU(alpha=leakyrelu_alpha)(encoder5)# 6th Convolutional block in the encoder networkencoder6 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder5)encoder6 = BatchNormalization()(encoder6)encoder6 = LeakyReLU(alpha=leakyrelu_alpha)(encoder6)# 7th Convolutional block in the encoder networkencoder7 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder6)encoder7 = BatchNormalization()(encoder7)encoder7 = LeakyReLU(alpha=leakyrelu_alpha)(encoder7)# 8th Convolutional block in the encoder networkencoder8 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same',strides=strides)(encoder7)encoder8 = BatchNormalization()(encoder8)encoder8 = LeakyReLU(alpha=leakyrelu_alpha)(encoder8)# Decoder Network # 1st Upsampling Convolutional Block in the decoder network decoder1 = UpSampling2D(size=upsampling_size)(encoder8)decoder1 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same')(decoder1)decoder1 = BatchNormalization()(decoder1)decoder1 = Dropout(dropout)(decoder1)decoder1 = concatenate([decoder1, encoder7], axis=3)decoder1 = Activation('relu')(decoder1)# 2nd Upsampling Convolutional block in the decoder networkdecoder2 = UpSampling2D(size=upsampling_size)(decoder1)decoder2 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder2)decoder2 = BatchNormalization()(decoder2)decoder2 = Dropout(dropout)(decoder2)decoder2 = concatenate([decoder2, encoder6])decoder2 = Activation('relu')(decoder2)# 3rd Upsampling Convolutional block in the decoder networkdecoder3 = UpSampling2D(size=upsampling_size)(decoder2)decoder3 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder3)decoder3 = BatchNormalization()(decoder3)decoder3 = Dropout(dropout)(decoder3)decoder3 = concatenate([decoder3, encoder5])decoder3 = Activation('relu')(decoder3)# 4th Upsampling Convolutional block in the decoder networkdecoder4 = UpSampling2D(size=upsampling_size)(decoder3)decoder4 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder4)decoder4 = BatchNormalization()(decoder4)decoder4 = concatenate([decoder4, encoder4])decoder4 = Activation('relu')(decoder4)# 5th Upsampling Convolutional block in the decoder networkdecoder5 = UpSampling2D(size=upsampling_size)(decoder4)decoder5 = Convolution2D(filters=1024, kernel_size=kernel_size, padding='same')(decoder5)decoder5 = BatchNormalization()(decoder5)decoder5 = concatenate([decoder5, encoder3])decoder5 = Activation('relu')(decoder5)# 6th Upsampling Convolutional block in the decoder networkdecoder6 = UpSampling2D(size=upsampling_size)(decoder5)decoder6 = Convolution2D(filters=512, kernel_size=kernel_size, padding='same')(decoder6)decoder6 = BatchNormalization()(decoder6)decoder6 = concatenate([decoder6, encoder2])decoder6 = Activation('relu')(decoder6)# 7th Upsampling Convolutional block in the decoder networkdecoder7 = UpSampling2D(size=upsampling_size)(decoder6)decoder7 = Convolution2D(filters=256, kernel_size=kernel_size, padding='same')(decoder7)decoder7 = BatchNormalization()(decoder7)decoder7 = concatenate([decoder7, encoder1])decoder7 = Activation('relu')(decoder7)# Last Convolutional layerdecoder8 = UpSampling2D(size=upsampling_size)(decoder7)decoder8 = Convolution2D(filters=output_channels, kernel_size=kernel_size, padding='same')(decoder8)decoder8 = Activation('tanh')(decoder8)model = Model(inputs=[input_layer], outputs=[decoder8])return model
我们现在已经成功地为生成器网络创建了 Keras 模型。 在下一节中,我们将为判别器网络创建 Keras 模型。
判别器网络
判别器网络受 PatchGAN 架构的启发。 它包含八个卷积块,一个密集层和一个展开层。 判别器网络获取从大小为(256, 256, 1)
的图像中提取的一组补丁,并预测给定补丁的概率。 让我们在 Keras 中实现判别器。
- 首先初始化生成器网络所需的超参数:
kernel_size = 4 strides = 2 leakyrelu_alpha = 0.2 padding = 'same' num_filters_start = 64 # Number of filters to start with num_kernels = 100 kernel_dim = 5 patchgan_output_dim = (256, 256, 1)
patchgan_patch_dim = (256, 256, 1)# Calculate number of patches
number_patches = int((patchgan_output_dim[0] / patchgan_patch_dim[0]) * (patchgan_output_dim[1] / patchgan_patch_dim[1]))
- 让我们向网络添加一个输入层。 这需要一个张量为
patchgan_patch_dim
的张量的补丁:
input_layer = Input(shape=patchgan_patch_dim)
- 接下来,如下所示将卷积层添加到网络。 “pix2pix 的架构”部分中提供了该块的配置:
des = Convolution2D(filters=64, kernel_size=kernel_size, padding=padding, strides=strides)(input_layer)
des = LeakyReLU(alpha=leakyrelu_alpha)(des)
- 接下来,使用以下代码计算卷积块的数量:
# Calculate the number of convolutional layers total_conv_layers = int(np.floor(np.log(patchgan_output_dim[1]) / np.log(2)))
list_filters = [num_filters_start * min(total_conv_layers, (2 ** i)) for i in range(total_conv_layers)]
- 接下来,使用先前在“pix2pix”部分的架构中指示的超参数值添加另外七个卷积块:
# Next 7 Convolutional blocks for filters in list_filters[1:]:des = Convolution2D(filters=filters, kernel_size=kernel_size, padding=padding, strides=strides)(des)des = BatchNormalization()(des)des = LeakyReLU(alpha=leakyrelu_alpha)(des)
- 接下来,将展开层添加到网络,如下所示:
flatten_layer = Flatten()(des)
展开层将n
维张量转换为一维张量。
- 类似地,添加具有两个节点/神经元的密集层,并添加
softmax
作为激活函数。 这需要一个来自Flatten
层的张量,并将其转换为大小为(batch_size, 2)
的张量:
dense_layer = Dense(units=2, activation='softmax')(flatten_layer)
softmax
函数将向量转换为概率分布。
- 接下来,为 PatchGAN 网络创建 Keras 模型,如下所示:
model_patch_gan = Model(inputs=[input_layer], outputs=[dense_layer, flatten_layer])
PatchGAN 模型将输入张量作为输入,并输出两个张量,一个来自密集层,另一个来自展开层。 我们的 PatchGAN 网络现已准备就绪。 但是,它本身不能用作判别器。 而是将单个补丁分类为真实或伪造类别。 要创建完整的判别器,请按照下列步骤操作:
- 我们将从输入图像中提取色块,并将它们逐一馈送到 PatchGAN。 创建一个等于补丁数量的输入层列表,如下所示:
# Create a list of input layers equal to number of patches list_input_layers = [Input(shape=patchgan_patch_dim) for _ in range(number_patches)]
- 接下来,将补丁传递到 PatchGAN 网络并获得概率分布:
# Pass the patches to the PatchGAN and get probability distribution output1 = [model_patch_gan(patch)[0] for patch in list_input_layers]
output2 = [model_patch_gan(patch)[1] for patch in list_input_layers]
如果我们有多个面片,则output1
和output2
都是张量的列表。 我们现在应该有两个张量列表。
- 如果您有多个补丁,请沿着渠道维度将其连接起来以计算永久损失:
# In case of multiple patches, concatenate them along the channel dimension to calculate perceptual loss if len(output1) > 1:output1 = concatenate(output1)
else:output1 = output1[0]# In case of multiple patches, merge output2 as well if len(output2) > 1:output2 = concatenate(output2)
else:output2 = output2[0]
- 接下来,创建一个密集层,如下所示:
dense_layer2 = Dense(num_kernels * kernel_dim, use_bias=False, activation=None)
- 接下来,添加一个自定义损失层。 该层计算馈入该层的张量的最小批判别:
custom_loss_layer = Lambda(lambda x: K.sum(K.exp(-K.sum(K.abs(K.expand_dims(x, 3) - K.expand_dims(K.permute_dimensions(x, pattern=(1, 2, 0)), 0)), 2)), 2))
- 接下来,使
output2
张量通过dense_layer2
:
output2 = dense_layer2(output2)
- 接下来,将
output2
重塑为(num_kernels, kernel_dim)
的张量:
output2 = Reshape((num_kernels, kernel_dim))(output2)
- 接下来,将
output2
张量传递到custom_loss_layer
:
output2 = custom_loss_layer(output2)
- 接下来,连接
output1
和output2
以创建一个张量,并将其通过密集层:
output1 = concatenate([output1, output2])
final_output = Dense(2, activation="softmax")(output1)
将"softmax"
用作最后一个密集层的激活函数。 这将返回概率分布。
- 最后,通过如下指定网络的输入和输出来创建判别器模型:
discriminator = Model(inputs=list_input_layers, outputs=[final_output])
判别器网络的完整代码如下:
def build_patchgan_discriminator():"""Create PatchGAN discriminator using the hyperparameter values defined below """ kernel_size = 4strides = 2leakyrelu_alpha = 0.2padding = 'same'num_filters_start = 64 # Number of filters to start withnum_kernels = 100kernel_dim = 5patchgan_output_dim = (256, 256, 1)patchgan_patch_dim = (256, 256, 1)number_patches = int((patchgan_output_dim[0] / patchgan_patch_dim[0]) * (patchgan_output_dim[1] / patchgan_patch_dim[1]))input_layer = Input(shape=patchgan_patch_dim)des = Convolution2D(filters=64, kernel_size=kernel_size, padding=padding, strides=strides)(input_layer)des = LeakyReLU(alpha=leakyrelu_alpha)(des)# Calculate the number of convolutional layerstotal_conv_layers = int(np.floor(np.log(patchgan_output_dim[1]) / np.log(2)))list_filters = [num_filters_start * min(total_conv_layers, (2 ** i)) for i in range(total_conv_layers)]# Next 7 Convolutional blocksfor filters in list_filters[1:]:des = Convolution2D(filters=filters, kernel_size=kernel_size, padding=padding, strides=strides)(des)des = BatchNormalization()(des)des = LeakyReLU(alpha=leakyrelu_alpha)(des)# Add a flatten layerflatten_layer = Flatten()(des)# Add the final dense layerdense_layer = Dense(units=2, activation='softmax')(flatten_layer)# Create the PatchGAN modelmodel_patch_gan = Model(inputs=[input_layer], outputs=[dense_layer, flatten_layer])# Create a list of input layers equal to the number of patcheslist_input_layers = [Input(shape=patchgan_patch_dim) for _ in range(number_patches)]# Pass the patches through the PatchGAN networkoutput1 = [model_patch_gan(patch)[0] for patch in list_input_layers]output2 = [model_patch_gan(patch)[1] for patch in list_input_layers]# In case of multiple patches, concatenate outputs to calculate perceptual lossif len(output1) > 1:output1 = concatenate(output1)else:output1 = output1[0]# In case of multiple patches, merge output2 as wellif len(output2) > 1:output2 = concatenate(output2)else:output2 = output2[0]# Add a dense layerdense_layer2 = Dense(num_kernels * kernel_dim, use_bias=False, activation=None)# Add a lambda layercustom_loss_layer = Lambda(lambda x: K.sum(K.exp(-K.sum(K.abs(K.expand_dims(x, 3) - K.expand_dims(K.permute_dimensions(x, pattern=(1, 2, 0)), 0)), 2)), 2))# Pass the output2 tensor through dense_layer2output2 = dense_layer2(output2)# Reshape the output2 tensoroutput2 = Reshape((num_kernels, kernel_dim))(output2)# Pass the output2 tensor through the custom_loss_layeroutput2 = custom_loss_layer(output2)# Finally concatenate output1 and output2output1 = concatenate([output1, output2])final_output = Dense(2, activation="softmax")(output1)# Create a discriminator modeldiscriminator = Model(inputs=list_input_layers, outputs=[final_output])return discriminator
我们现在已经成功创建了判别器网络。 接下来,让我们创建一个对抗网络。
对抗网络
在本节中,我们将创建一个对抗网络,其中包含 U-Net 生成器网络和 PatchGAN 判别器网络。 执行以下步骤来创建对抗网络:
- 首先初始化超参数:
input_image_dim = (256, 256, 1)
patch_dim = (256, 256)
- 接下来,创建一个输入层,将输入馈送到网络,如下所示:
input_layer = Input(shape=input_image_dim)
- 接下来,使用生成器网络生成伪造的图像:
generated_images = generator(input_layer)
- 接下来,从生成的图像中提取补丁:
# Chop the generated images into patches img_height, img_width = input_img_dim[:2]
patch_height, patch_width = patch_dimrow_idx_list = [(i * patch_height, (i + 1) * patch_height) for i in range(int(img_height / patch_height))]
column_idx_list = [(i * patch_width, (i + 1) * patch_width) for i in range(int(img_width / patch_width))]generated_patches_list = []
for row_idx in row_idx_list:for column_idx in column_idx_list:generated_patches_list.append(Lambda(lambda z: z[:, column_idx[0]:column_idx[1], row_idx[0]:row_idx[1], :],output_shape=input_img_dim)(generated_images))
- 冻结了判别器网络的训练,因为我们不想训练判别器网络:
discriminator.trainable = False
- 现在,我们应该有一个补丁列表。 通过 PatchGAN 判别器网络传递它们:
dis_output = discriminator(generated_patches_list)
- 最后,通过如下指定网络的输入和输出来创建 Keras 模型:
model = Model(inputs=[input_layer], outputs=[generated_images, dis_output])
这些步骤使用两个网络(生成器网络和判别器网络)创建对抗模型。 对抗模型的整个代码如下:
def build_adversarial_model(generator, discriminator):"""Create an adversarial model """ input_image_dim = (256, 256, 1)patch_dim = (256, 256)# Create an input layerinput_layer = Input(shape=input_image_dim)# Use the generator network to generate imagesgenerated_images = generator(input_layer)# Extract patches from the generated imagesimg_height, img_width = input_img_dim[:2]patch_height, patch_width = patch_dimrow_idx_list = [(i * patch_height, (i + 1) * patch_height) for i in range(int(img_height / patch_height))]column_idx_list = [(i * patch_width, (i + 1) * patch_width) for i in range(int(img_width / patch_width))]generated_patches_list = []for row_idx in row_idx_list:for column_idx in column_idx_list:generated_patches_list.append(Lambda(lambda z: z[:, column_idx[0]:column_idx[1], row_idx[0]:row_idx[1], :],output_shape=input_img_dim)(generated_images))discriminator.trainable = False # Pass the generated patches through the discriminator networkdis_output = discriminator(generated_patches_list)# Create a modelmodel = Model(inputs=[input_layer], outputs=[generated_images, dis_output])return model
现在,我们已经成功地为生成器网络,判别器网络和对抗模型创建了模型。 我们准备训练 pix2pix。 在下一部分中,我们将在 Facades 数据集中训练 pix2pix 网络。
训练 pix2pix 网络
像任何其他 GAN 一样,训练 pix2pix 网络是一个两步过程。 第一步,我们训练判别器网络。 在第二步中,我们训练对抗网络,最终训练生成器网络。 让我们开始训练网络。
执行以下步骤来训练 SRGAN 网络:
- 首先定义训练所需的超参数:
epochs = 500 num_images_per_epoch = 400 batch_size = 1 img_width = 256 img_height = 256 num_channels = 1 input_img_dim = (256, 256, 1)
patch_dim = (256, 256)# Specify dataset directory path
dataset_dir = "pix2pix-keras/pix2pix/data/facades_bw"
- 接下来,定义通用优化器,如下所示:
common_optimizer = Adam(lr=1E-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
对于所有网络,我们将使用Adam
优化器,其中learning rate
等于1e-4
, beta_1
等于 0.9, beta_2
等于 0.999,并且epsilon
等于1e-08
。
- 接下来,构建并编译 PatchGAN 判别器网络,如下所示:
patchgan_discriminator = build_patchgan_discriminator()
patchgan_discriminator.compile(loss='binary_crossentropy', optimizer=common_optimizer)
要编译判别器模型,请使用binary_crossentropy
作为损失函数,并使用common_optimizer
作为训练优化器。
- 现在,构建并编译生成器网络,如下所示:
unet_generator = build_unet_generator()
unet_generator.compile(loss='mae', optimizer=common_optimizer)
要编译判别器模型,请使用 mse
作为损失函数,并使用 common_optimizer
作为训练优化器。
- 接下来,构建并编译对抗模型,如下所示:
adversarial_model = build_adversarial_model(unet_generator, patchgan_discriminator)
adversarial_model.compile(loss=['mae', 'binary_crossentropy'], loss_weights=[1E2, 1], optimizer=common_optimizer)
要编译对抗模型,请使用损失列表 ['mse', 'binary_crossentropy']
和 common_optimizer
作为训练优化器。
- 现在,按如下所示加载训练,验证和测试数据集:
training_facade_photos, training_facade_labels = load_dataset(data_dir=dataset_dir, data_type='training',img_width=img_width, img_height=img_height)test_facade_photos, test_facade_labels = load_dataset(data_dir=dataset_dir, data_type='testing',img_width=img_width, img_height=img_height)validation_facade_photos, validation_facade_labels = load_dataset(data_dir=dataset_dir, data_type='validation',img_width=img_width, img_height=img_height)
load_dataset
函数是“数据准备”部分中定义的 。 每一组包含所有图像的一组ndarray
。 每组的大小将为(#total_images, 256, 256, 1)
。
- 添加
tensorboard
以可视化训练损失并可视化网络图:
tensorboard = TensorBoard(log_dir="logs/".format(time.time()))
tensorboard.set_model(unet_generator)
tensorboard.set_model(patchgan_discriminator)
- 接下来,创建一个
for
循环,该循环应运行的次数由周期数指定,如下所示:
for epoch in range(epochs):print("Epoch:{}".format(epoch))
- 创建两个列表来存储所有小批量的损失:
dis_losses = []
gen_losses = []# Initialize a variable
batch_counter = 1
- 接下来,在周期循环内创建另一个循环,并使它运行
num_batches
指定的次数,如下所示:
num_batches = int(training_facade_photos.shape[0] / batch_size) for index in range(int(training_facade_photos.shape[0] / batch_size)):
print("Batch:{}".format(index))
我们用于判别器网络和对抗网络训练的整个代码将在此循环内。
- 接下来,对训练和验证数据进行小批量采样,如下所示:
train_facades_batch = training_facade_labels[index * batch_size:(index + 1) * batch_size]train_images_batch = training_facade_photos[index * batch_size:(index + 1) * batch_size]val_facades_batch = validation_facade_labels[index * batch_size:(index + 1) * batch_size]val_images_batch = validation_facade_photos[index * batch_size:(index + 1) * batch_size]
- 接下来,生成一批假图像并从中提取补丁。 如下使用
generate_and_extract_patches
函数:
patches, labels = generate_and_extract_patches(train_images_batch, train_facades_batch, unet_generator,batch_counter, patch_dim)
generate_and_extract_patches
函数定义如下:
def generate_and_extract_patches(images, facades, generator_model, batch_counter, patch_dim):# Alternatively, train the discriminator network on real and generated imagesif batch_counter % 2 == 0:# Generate fake imagesoutput_images = generator_model.predict(facades)# Create a batch of ground truth labelslabels = np.zeros((output_images.shape[0], 2), dtype=np.uint8)labels[:, 0] = 1 else:# Take real imagesoutput_images = images# Create a batch of ground truth labelslabels = np.zeros((output_images.shape[0], 2), dtype=np.uint8)labels[:, 1] = 1 patches = []for y in range(0, output_images.shape[0], patch_dim[0]):for x in range(0, output_images.shape[1], patch_dim[1]):image_patches = output_images[:, y: y + patch_dim[0], x: x + patch_dim[1], :]patches.append(np.asarray(image_patches, dtype=np.float32))return patches, labels
前面的函数使用生成器网络生成伪图像,然后从生成的图像中提取补丁。 现在,我们应该有一个补丁列表及其基本真理值。
- 现在,在生成的补丁上训练判别器网络:
d_loss = patchgan_discriminator.train_on_batch(patches, labels)
这将在提取的补丁和地面真相标签上训练判别器网络。
- 接下来,训练对抗模型。 对抗性模型将训练生成器网络,但冻结判别器网络的训练。 使用以下代码:
labels = np.zeros((train_images_batch.shape[0], 2), dtype=np.uint8)labels[:, 1] = 1 # Train the adversarial model g_loss = adversarial_model.train_on_batch(train_facades_batch, [train_images_batch, labels])
- 每个小批量完成后增加批计数器:
batch_counter += 1
- 在每个微型批量上完成一次迭代(循环)后,将损失存储在名为
dis_losses
和gen_losses
的列表中:
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), epoch)
- 每 10 个周期后,使用生成器网络生成一组图像:
# After every 10th epoch, generate and save images for visualization if epoch % 10 == 0:# Sample a batch of validation datasetsval_facades_batch = validation_facade_labels[0:5]val_images_batch = validation_facade_photos[0:5]# Generate imagesvalidation_generated_images = unet_generator.predict(val_facades_batch)# Save imagessave_images(val_images_batch, val_facades_batch, validation_generated_images, epoch, 'validation', limit=5)
将前面的代码块放入周期循环中。 每隔 10 个时间段,它将生成一批伪图像并将其保存到结果目录。 这里,save_images()
是如下定义的效用函数:
def save_images(real_images, real_sketches, generated_images, num_epoch, dataset_name, limit):real_sketches = real_sketches * 255.0real_images = real_images * 255.0generated_images = generated_images * 255.0 # Save some images onlyreal_sketches = real_sketches[:limit]generated_images = generated_images[:limit]real_images = real_images[:limit]# Create a stack of imagesX = np.hstack((real_sketches, generated_images, real_images))# Save stack of imagesimwrite('results/X_full_{}_{}.png'.format(dataset_name, num_epoch), X[0])
现在,我们已经成功地在立面数据集上训练了 pix2pix 网络。 对网络进行 1000 个周期的训练,以获取高质量的生成器网络。
保存模型
在 Keras 中保存模型只需要一行代码。 要保存生成器模型,请添加以下行:
# Specify the path for the generator model
unet_generator.save_weights("generator.h5")
同样,通过添加以下行来保存判别器模型:
# Specify the path for the discriminator model
patchgan_discriminator.save_weights("discriminator.h5")
可视化生成的图像
在将网络训练了 20 个时间段后,网络将开始生成体面的图像:让我们看一下由生成器网络生成的图像。
在 20、50、150 和 200 个周期(从左到右) 之后,图像如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8HrdlmdA-1681652906152)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/81830986-deb9-4db9-9dba-518aa6313466.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVbB29zD-1681652906152)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/515bff70-925a-4f3b-a995-470637fd676e.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZEsITef0-1681652906152)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/550e5193-b6a1-49ba-81eb-cd1fe71c7634.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uU7kghsi-1681652906153)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/277d3877-16f5-4ce5-b5f0-2703bc2d3075.png)]
每个块均包含垂直堆叠的外观标签,生成的照片和实际图像。 我建议您将网络训练 1000 个周期。 如果一切顺利,则在 1000 个周期之后,生成器网络将开始生成逼真的图像。
可视化损失
要可视化训练损失,请启动 TensorBoard 服务器,如下所示:
tensorboard --logdir=logs
现在,在浏览器中打开localhost:6006
。 TensorBoard 的标量部分包含两种损失的图表,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ok2VTMOc-1681652906153)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/ff705572-5fb8-4216-883c-63bbfb809286.png)]
TensorBoard 的标量部分
这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 尝试使用超参数,然后选择一组您认为可以提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。
可视化图
TensorBoard 的GRAPHS
部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMvwcI7i-1681652906153)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/7c0476a9-d31c-4ef3-b3b3-2193680e7c0c.png)]
张量流和每个图内部的不同操作
pix2pix 网络的实际应用
pix2pix 网络有很多应用。 其中包括:
- 将像素级分割转换为真实图像
- 要将白天图像转换为夜间图像,反之亦然
- 将卫星平面图像转换为地图图像
- 将草图转换为照片
- 将黑白图像转换为彩色图像,反之亦然
以下是从指定的官方文件中拍摄的图像。 它显示了 pix2pix 网络的不同用例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9ZXLqxl-1681652906153)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/beb77a0a-2316-4a3e-9bf9-dead211714b4.png)]
使用条件对抗网络的图像到图像翻译,来源:arXiv:1611.07004 [cs.CV]
总结
在本章中,我们了解了 pix2pix 网络是什么,并探讨了其架构。 我们从数据加载开始,准备要训练的数据集,然后准备了项目,并研究了 pix2pix 网络的 Keras 实现。 之后,我们研究了训练 pix2pix 网络的目标函数。 然后,我们在立面数据集上训练了 pix2pix 网络,并探索了 pix2pix 网络的一些实际应用。
在下一章中,我们将预测 GAN 的未来。 我们将研究 GAN 领域在不久的将来会发生什么,以及它将如何改变我们的行业和我们的日常生活。
九、预测 GAN 的未来
如果您已完成本书各章中的所有练习,则为学习和编码生成对抗网络(GAN)进行了很长的路要走。 世界应用。 GAN 有可能在许多不同行业中造成破坏。 科学家和研究人员开发了各种可用于构建商业应用的 GAN。 在本书中,我们探索并实现了一些最著名的 GAN 架构。
因此,让我们回顾一下到目前为止所学到的东西:
-
我们从对 GAN 的简要介绍开始,学习了各种重要概念。
-
然后,我们探索了 3D-GAN,这是一种可以生成 3D 图像的 GAN。 我们训练了 3D-GAN,以生成现实世界对象的 3D 模型,例如飞机或桌子。
-
在第三章中,我们探索了用于人脸老化的条件 GAN。 我们学习了如何使用条件 GAN 将处于年龄的的人脸图像转换为同一张脸的图像。 我们还讨论了 Age-cGAN 的各种实际应用。
-
之后,我们探索了深度卷积生成对抗网络(DCGANs),该网络用于生成动漫人物的面孔。
-
在第五章中,我们探讨了超分辨率生成对抗网络(SRGAN),该网络可用于从低分辨率图像生成高分辨率图像。 之后,我们讨论了 SRGAN 如何解决一些非常有趣的现实问题。
-
然后,我们探索了 StackGAN,我们将其用于执行文本到图像的合成任务。 我们在训练 StackGAN 之前先探索了一个数据集,然后通过讨论 StackGAN 的实际应用来结束本章。
-
在第七章中,我们探索了 CycleGAN,这是一次图像到图像的翻译任务。 我们的目标是将绘画变成照片。 我们还讨论了 CycleGAN 的实际应用
-
最后,在第八章中,我们探讨了 pix2pix 网络,这是一种条件 GAN。 我们训练了 pix2pix 网络,以根据建筑标签生成立面图像。 像其他章节一样,我们通过讨论 pix2pix 网络的实际应用来结束本章。
在本章中,我们将介绍以下主题:
- 我们对 GAN 未来的预测
- GAN 的潜在未来应用
- 可以探索 GAN 的其他区域
我们对 GAN 未来的预测
我认为,GAN 的未来将具有以下特点:
- 研究团体公开接受 GAN 及其应用。
- 令人印象深刻的结果-到目前为止,GAN 在使用常规方法难以执行的任务上显示出非常令人印象深刻的结果。 例如,将低分辨率图像转换为高分辨率图像以前是一项艰巨的任务,通常是使用 CNN 进行的。 GAN 架构(例如 SRGAN 或 pix2pix)显示了 GAN 在此应用中的潜力,而 StackGAN 网络已被证明对文本到图像的合成任务很有用。 如今,任何人都可以创建 SRGAN 网络并将其训练在自己的图像上。
- 深度学习技术的进步。
- GAN 用于商业应用。
- GAN 训练过程的成熟。
改善现有的深度学习方法
监督式深度学习方法需要大量数据来训练模型。 获取该数据既昂贵又费时。 有时,无法获得数据,因为它不是公开可用的,或者如果它是公开可用的,则数据集的大小可能很小。 这是 GAN 可以营救的地方。 一旦使用相当小的数据集进行了训练,便可以部署 GAN 来从同一域生成新数据。 例如,假设您正在处理图像分类任务。 您有一个数据集,但是它不足以完成您的任务。 我们可以在现有映像上训练 GAN,然后将其部署到同一域中以生成新映像。 尽管 GAN 目前存在训练不稳定性的问题,但一些研究人员表明,可以生成逼真的图像。
GAN 商业应用的演变
未来几年,我们将看到 GAN 的更多商业应用。 GAN 的许多商业应用已经开发,并给人留下了积极的印象。 例如,移动应用 Prisma 是 GAN 最早获得广泛成功的应用之一。 我们可能会在不久的将来看到 GAN 的民主化,一旦我们这样做了,我们将开始看到 GAN 改善了我们的日常生活。
GAN 训练过程的成熟
自 2014 年成立以来,四年后,GAN 仍然遭受训练不稳定问题的困扰。 有时,GAN 根本无法收敛,因为两个网络都偏离了它们的训练路径。 在编写本书时,我多次遭受这个问题的困扰。 研究人员为稳定 GAN 的训练做出了许多努力。 我预测,随着深度学习领域的进步,该过程将日趋成熟,并且我们很快将能够毫无问题地训练模型。
GAN 的潜在未来应用
GAN 的未来是光明的! 我认为在不久的将来有可能会使用 GAN:
- 从文本创建图表
- 生成网站设计
- 压缩数据
- 药物发现与开发
- 生成文字
- 生成音乐
从文本创建图表
设计图表是一个漫长的过程。 这需要数小时的劳动并且需要特定的技能。 在市场营销和社会促销中,信息图表的魅力十足。 它们是社交媒体营销的主要成分。 有时,由于漫长的创建过程,公司不得不采用效率较低的策略来解决。 AI 和 GAN 可以帮助设计师进行创作。
生成网站设计
同样,设计网站是一个手动的,创造性的过程,需要熟练的手动工作,并且需要很长时间。 GAN 可以通过提供可以用作灵感的初始设计来协助设计师,从而节省大量金钱和时间。
压缩数据
互联网使我们能够将大量数据传输到任何位置,但这是有代价的。 GAN 使我们能够提高图像和视频的分辨率。 我们可以将低分辨率的图像和视频传输到它们所需的位置,然后可以使用 GAN 来提高数据质量,而这需要更少的带宽。 这带来了很多可能性。
药物发现与开发
使用 GAN 进行药物开发可能听起来像是一个梦想,但考虑到所需的化学和生物学特性,GAN 已被用于生成分子结构。 制药公司在新药的研发上花费了数十亿美元。 用于药物开发的 GAN 可以大大降低该成本。
用于生成文本的 GAN
GAN 已被证明对图像生成任务很有用。 GAN 中的大部分研究目前集中在高分辨率图像生成,文本到图像合成,样式迁移,图像到图像翻译以及其他类似任务上。 目前,对于使用 GAN 生成文本的研究还不多。 这是因为 GAN 旨在生成连续的值,因此训练 GAN 以获得离散值确实具有挑战性。 将来, 预测将在文本生成任务中进行更多的研究。
用于生成音乐的 GAN
使用 GAN 进行音乐生成是另一个尚未得到充分探索的领域。 音乐创作的过程充满创造力,非常复杂。 GAN 具有改变音乐产业的潜力,如果发生这种情况,我们可能很快就会聆听 GAN 创建的曲目。
探索 GAN
您可以探索的其他 GAN 架构包括:
-
BigGAN:《用于高保真自然图像合成的大规模 GAN 训练》
-
WaveGAN:《使用生成的对抗网络合成音频》
-
BEGAN:《BEGAN:边界均衡生成对抗网络》
-
AC-GAN:《使用辅助分类器 GAN 的条件图像合成》
-
AdaGAN:《AdaGAN:增强生成模型》
-
ArtGAN:《ArtGAN:带有条件分类 GAN 的艺术品合成》
-
BAGAN:《BAGAN:平衡 GAN 的数据扩充》
-
BicycleGAN:《从多模态图像迈向图像翻译》
-
CapsGAN:《CapsGAN:为生成对抗网络使用动态路由》
-
E-GAN:《进化生成对抗网络》
-
WGAN:《Wasserstein GAN》
研究人员还开发了数百种其他 GAN 架构。
总结
在本书中,我的目的是让您了解 GAN 及其在世界上的应用。 你的想象力是唯一的限制。 有大量可用的不同 GAN 架构,并且它们正在变得越来越成熟。 GAN 仍有很长的路要走,因为它们仍然存在诸如训练不稳定性和模式崩溃之类的问题,但是现在已经提出了各种解决方案,包括标签平滑,实例规范化和小批量区分。 我希望这本书对您实现 GAN 有所帮助。 如果您有任何疑问,请给我发送电子邮件到 ahikailash1@gmail.com。