Skip to content

Latest commit

 

History

History
1126 lines (653 loc) · 38.7 KB

7.md

File metadata and controls

1126 lines (653 loc) · 38.7 KB

七、CycleGAN - 将绘画变成照片

CycleGAN 是一种生成对抗网络GAN),用于跨域迁移任务,例如更改图像的样式,将绘画转变为照片, 反之亦然,例如照片增强功能,更改照片的季节等等。 CycleGAN 由朱俊彦,Taesung Park,Phillip Isola 和 Alexei A. Efros 在题为《使用循环生成对抗网络的非配对图像到图像转换》中引入。 该产品于 2018 年 2 月在加州大学伯克利分校的 Berkeley AI ResearchBAIR)实验室生产,可通过以下链接获得。 由于其广泛的使用案例,CycleGAN 在 GAN 社区引起了轰动。 在本章中,我们将与 CycleGAN 一起使用,尤其是使用它们将绘画转换为照片。

在本章中,我们将介绍以下主题:

  • CycleGAN 简介
  • CycleGAN 的架构
  • 数据收集与准备
  • Keras 的 CycleGAN 实现
  • 目标函数
  • 训练 CycleGAN
  • CycleGAN 的实际应用

CycleGAN 简介

为了将照片变成一幅画或一幅画,再将它们变成照片,普通 GAN 需要一对图像。 CycleGAN 是一种 GAN 网络,可以将图像从一个域 X 转换为另一个域 Y,而无需配对图像。 CycleGAN 尝试学习生成器网络,而生成器网络又学习了两个映射。 CycleGAN 无需训练大多数 GAN 中使用的单个生成器网络,而是训练两个生成器和两个判别器网络。

CycleGAN 中有两个生成器网络,如下所示:

  1. 生成器A:学习映射G: X -> Y,其中X是源域,Y是目标域。 它从源域A拍摄图像,并将其转换为与目标域B相似的图像。 基本上,网络的目的是学习映射,以使G(X)Y相似。
  2. 生成器B:学习映射F: Y -> X,然后从目标域B中获取图像,并将其转换为与源域A中的图像相似的图像。类似地, 网络要学习另一个映射,以便F(G(X))类似于X)。

这两个网络的架构相同,但我们分别对其进行训练。

CycleGAN 中有两个判别器网络,如下所示:

  1. 判别器A:判别器A的工作是区分由生成器网络A生成的图像,这些图像表示为G(X)和来自源域A的真实图像,它们表示为X
  2. 判别器B:判别器B的工作是区分由生成器网络B生成的图像,这些图像表示为F(Y)以及来自源域B的真实图像,它们表示为Y

两个网络的架构是相同的。 与生成器网络类似,我们分别训练判别器网络。 如下图所示:

具有两个生成器和两个对抗性判别器网络的 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 具有训练目标函数,我们需要将其最小化以训练模型。 损失函数是以下损失的加权总和:

  1. 对抗损失
  2. 循环一致性损失

在以下各节中,让我们详细研究对抗性损失和周期一致性损失。

对抗损失

对抗性损失是实际分布AB的图像与生成器网络生成的图像之间的损失。 我们有两个映射函数,我们将对两个映射应用对抗性损失。

映射G: X -> Y的对抗损失如下所示:

在此,x是来自分布A的一个域的图像,y是来自分布B的另一个域的图像。判别器D[y]试图区分G映射(G(x))生成的图像和来自不同的分布B的真实图像y。判别器D[x]试图区分F映射生成的图像(F(y))和来自分布A的真实图像xG的目的是使对抗损失函数D最小,后者不断尝试使其最大化。

循环一致性损失

仅使用对抗损失的问题在于,网络可以将同一组输入图像映射到目标域中图像的任何随机排列。 因此,任何学习的映射都可以学习类似于目标分布的输出分布。 x[i]y[i]之间可能有许多可能的映射函数。 循环一致性损失通过减少可能的映射数来克服了这个问题。 周期一致映射函数是可以将图像x从域A转换为域B中的另一个图像y并生成原始图像的函数。

前向循环一致性映射函数如下所示:

向后循环一致映射函数如下所示:

循环一致性损失的公式如下:

由于循环一致性损失,由F(G(x))G(F(y))重构的图像分别类似于xy

完整目标函数

完整目标函数是对抗损失和周期一致性损失两者的加权总和,表示如下:

在此,l[GAN](G, D[Y], X, Y)是第一个对抗性损失,l[GAN](F, D[X], Y, X)是第二个对抗性损失。 在生成器A和判别器B上计算第一个对抗损失。在生成器B和判别器A上计算第二个对抗损失。

要训​​练 CycleGAN,我们需要优化以下函数:

前面的等式表明,训练一个 CycleGAN,需要最小化生成器网络的损失,并使判别器网络的损失最大化。 优化之后,我们将获得一组训练有素的网络,能够从绘画中生成照片。

设置项目

如果尚未使用所有章节的完整代码克隆存储库,请立即克隆存储库。 下载的代码有一个名为Chapter07的目录,其中包含本章的全部代码。 执行以下命令来设置项目:

  1. 首先,导航到父目录,如下所示:
cd Generative-Adversarial-Networks-Projects
  1. 现在,将目录从当前目录更改为 Chapter07,如以下示例所示:
cd Chapter07
  1. 接下来,为该项目创建一个 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

我们将为此项目使用此新创建的虚拟环境。 每章都有其自己单独的虚拟环境。

  1. 激活新创建的虚拟环境,如以下代码所示:
source venv/bin/activate

激活虚拟环境后,所有其他命令将在该虚拟环境中执行。

  1. 通过执行以下命令,安装requirements.txt 文件中提供的所有库:
pip install -r requirements.txt

您可以参考 README.md 文件,以获取有关如何设置项目的更多说明。 开发人员经常会遇到依赖关系不匹配的问题。 为每个项目创建一个单独的虚拟环境将解决此问题。

在本节中,我们已经成功设置了项目并安装了必需的依赖项。 在下一节中,我们将处理数据集。

下载数据集

在本章中,我们将使用monet2photo数据集。 该数据集是开放源代码,可以由 UC Berkeley 的 Berkeley AI ResearchBAIR)实验室使用。 您可以从以下链接选择手动下载数据集

下载后,将其解压缩到根目录中。

或者,要自动下载数据集,请执行以下命令:

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 中实现生成器网络:

  1. 首先定义生成器网络所需的超参数,如下所示:
input_shape = (128, 128, 3)
residual_blocks = 6
  1. 接下来,创建一个输入层,将输入馈送到网络,如下所示:
input_layer = Input(shape=input_shape)
  1. 将第一个卷积块与先前在“生成器网络”部分的架构中指定的超参数相加,如下所示:
x = Conv2D(filters=32, kernel_size=7, strides=1, padding="same")(input_layer)
x = InstanceNormalization(axis=1)(x)
x = Activation("relu")(x)
  1. 添加第二个卷积块,如下所示:
x = Conv2D(filters=64, kernel_size=3, strides=2, padding="same")(x)
x = InstanceNormalization(axis=1)(x)
x = Activation("relu")(x)
  1. 添加第三个卷积块,如下所示:
x = Conv2D(filters=128, kernel_size=3, strides=2, padding="same")(x)
x = InstanceNormalization(axis=1)(x)
x = Activation("relu")(x)
  1. 定义残差块,如下所示:
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)
  1. 接下来,添加一个上采样块,如下所示:
x = Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same', use_bias=False)(x)
x = InstanceNormalization(axis=1)(x)
x = Activation("relu")(x)
  1. 添加另一个上采样模块,如下所示:
x = Conv2DTranspose(filters=32, kernel_size=3, strides=2, padding='same', use_bias=False)(x)
x = InstanceNormalization(axis=1)(x)
x = Activation("relu")(x)
  1. 最后,添加输出卷积层,如下所示:
x = Conv2D(filters=3, kernel_size=7, strides=1, padding="same")(x)
output = Activation('tanh')(x)

这是生成器网络的最后一层。 它生成形状为(128, 128, 3)的图像。

  1. 现在,通过为网络指定inputsoutputs来创建 Keras 模型,如下所示:
model = Model(inputs=[input_layer], outputs=[output])

生成器网络的整个代码如下所示:

def build_generator():
    """
 Create a generator network using the hyperparameter values defined below """  input_shape = (128, 128, 3)
    residual_blocks = 6
  input_layer = Input(shape=input_shape)

    # First Convolution block
  x = Conv2D(filters=32, kernel_size=7, strides=1, padding="same")(input_layer)
    x = InstanceNormalization(axis=1)(x)
    x = Activation("relu")(x)

    # 2nd Convolution block
  x = Conv2D(filters=64, kernel_size=3, strides=2, padding="same")(x)
    x = InstanceNormalization(axis=1)(x)
    x = Activation("relu")(x)

    # 3rd Convolution block
  x = Conv2D(filters=128, kernel_size=3, strides=2, padding="same")(x)
    x = InstanceNormalization(axis=1)(x)
    x = Activation("relu")(x)

    # Residual blocks
  for _ in range(residual_blocks):
        x = residual_block(x)

    # Upsampling blocks
     # 1st Upsampling block
  x = Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same', use_bias=False)(x)
    x = InstanceNormalization(axis=1)(x)
    x = Activation("relu")(x)

    # 2nd Upsampling block
  x = Conv2DTranspose(filters=32, kernel_size=3, strides=2, padding='same', use_bias=False)(x)
    x = InstanceNormalization(axis=1)(x)
    x = Activation("relu")(x)

    # Last Convolution layer
  x = Conv2D(filters=3, kernel_size=7, strides=1, padding="same")(x)
    output = Activation('tanh')(x)

    model = Model(inputs=[input_layer], outputs=[output])
    return model

我们已经成功地为生成器网络创建了 Keras 模型。 在下一节中,我们将为判别器网络创建 Keras 模型。

判别器网络

我们已经在“判别器网络的架构”部分中探索了判别器网络的架构。让我们首先在 Keras 框架中编写判别器网络的层,然后使用 Keras 框架的函数式 API 创建 Keras 模型。

执行以下步骤以在 Keras 中实现判别器网络:

  1. 首先定义判别器网络所需的超参数,如下所示:
input_shape = (128, 128, 3)
hidden_layers = 3
  1. 接下来,添加一个输入层,将输入馈送到网络,如下所示:
input_layer = Input(shape=input_shape)
  1. 接下来,添加一个二维零填充层,如下所示:
x = ZeroPadding2D(padding=(1, 1))(input_layer)

该层将在xy轴上为输入张量添加填充。

  1. 接下来,使用先前在“判别器网络”部分的架构中指定的超参数添加卷积块,如下所示:
x = Conv2D(filters=64, kernel_size=4, strides=2, padding="valid")(x)
x = LeakyReLU(alpha=0.2)(x)
  1. 接下来,添加另一个 2D 零填充层,如下所示:
x = ZeroPadding2D(padding=(1, 1))(x)
  1. 接下来,使用先前在“判别器网络”部分中指定的超参数添加三个卷积块,如下所示:
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 零填充层。

  1. 现在,将最终(output)卷积层添加到网络,如下所示:
output = Conv2D(filters=1, kernel_size=4, strides=1, activation="sigmoid")(x)
  1. 最后,通过指定网络的输入和输出来创建 Keras 模型,如下所示:
model = Model(inputs=[input_layer], outputs=[output])

判别器网络的整个代码如下所示:

def build_discriminator():
    """
 Create a discriminator network using the hyperparameter values defined below """  input_shape = (128, 128, 3)
    hidden_layers = 3    input_layer = Input(shape=input_shape)

    x = ZeroPadding2D(padding=(1, 1))(input_layer)

    # 1st Convolutional block
  x = Conv2D(filters=64, kernel_size=4, strides=2, padding="valid")(x)
    x = LeakyReLU(alpha=0.2)(x)

    x = ZeroPadding2D(padding=(1, 1))(x)

    # 3 Hidden Convolution blocks
  for i in range(1, hidden_layers + 1):
        x = Conv2D(filters=2 ** i * 64, kernel_size=4, strides=2, padding="valid")(x)
        x = InstanceNormalization(axis=1)(x)
        x = LeakyReLU(alpha=0.2)(x)

        x = ZeroPadding2D(padding=(1, 1))(x)

    # Last Convolution layer
  output = Conv2D(filters=1, kernel_size=4, strides=1, activation="sigmoid")(x)

    model = Model(inputs=[input_layer], outputs=[output])
    return model

我们也已经成功地为判别器网络创建了 Keras 模型。 在下一部分中,我们将训练网络。

训练 CycleGAN

我们已经在“CycleGANs 简介”部分中介绍了训练目标函数。 我们还为两个网络分别创建了 Keras 模型。 训练 CycleGAN 是一个多步骤的过程。 我们将执行以下步骤来训练网络:

  1. 加载数据集
  2. 创建生成器和判别器网络
  3. 训练网络以达到指定次数
  4. 绘制损失
  5. 生成新图像

让我们在开始训练网络之前定义基本变量,如下所示:

data_dir = "/Path/to/dataset/directory/*.*" batch_size = 1 epochs = 500

加载数据集

在执行其他任何操作之前,请执行以下步骤来加载数据集:

  1. 首先使用glob模块创建图像路径列表,如下所示:
imagesA = glob(data_dir + '/testA/*.*')
imagesB = glob(data_dir + '/testB/*.*')

我们具有来自两个域AB的数据,这就是为什么我们创建了两个列表的原因。

  1. 接下来,遍历列表。 在循环中加载,调整大小和水平翻转图像,如下所示:
allImagesA = []
allImagesB = []
 # Iterate over the lists
for index, filename in enumerate(imagesA):
     # Load images
     imgA = imread(filename, mode='RGB')
     imgB = imread(imagesB[index], mode='RGB')

     # Resize images
     imgA = imresize(imgA, (128, 128))
     imgB = imresize(imgB, (128, 128))

     # Randomly horizontally flip images
     if np.random.random() > 0.5:
         imgA = np.fliplr(imgA)
         imgB = np.fliplr(imgB)

     allImagesA.append(imgA)
     allImagesB.append(imgB)
  1. 现在,对图像进行归一化以使像素值在 -1 和 1 之间的范围内,如下所示:
# Normalize images allImagesA = np.array(allImagesA) / 127.5 - 1. allImagesB = np.array(allImagesB) / 127.5 - 1.

加载数据集的整个代码如下所示:

def load_images(data_dir):
    imagesA = glob(data_dir + '/testA/*.*')
    imagesB = glob(data_dir + '/testB/*.*')

    allImagesA = []
    allImagesB = []

    for index, filename in enumerate(imagesA):
        # Load images
        imgA = imread(filename, mode='RGB')
        imgB = imread(imagesB[index], mode='RGB')

        # Resize images
        imgA = imresize(imgA, (128, 128))
        imgB = imresize(imgB, (128, 128))

        # Randomly horizontally flip images
        if np.random.random() > 0.5:
            imgA = np.fliplr(imgA)
            imgB = np.fliplr(imgB)

        allImagesA.append(imgA)
        allImagesB.append(imgB)

    # Normalize images
  allImagesA = np.array(allImagesA) / 127.5 - 1.
  allImagesB = np.array(allImagesB) / 127.5 - 1.    return allImagesA, allImagesB

前面的函数将返回两个 Numpy ndarray。 在开始训练之前,我们将使用它来加载和预处理图像。

建立和编译网络

在本节中,让我们构建必要的网络并准备进行训练。 执行以下步骤:

  1. 首先定义训练所需的优化器,如以下代码所示:
# Define the common optimizer common_optimizer = Adam(0.0002, 0.5)

我们将使用Adam优化器,其中learning_rate等于 0.0002,并且beta_1值等于 0.5。

  1. 首先创建判别器网络,如以下代码所示:
discriminatorA = build_discriminator()
discriminatorB = build_discriminator()

如“判别器网络的架构”部分所述,CycleGAN 具有两个判别器网络。

  1. 接下来,编译网络,如下所示:
discriminatorA.compile(loss='mse', optimizer=common_optimizer, metrics=['accuracy'])
discriminatorB.compile(loss='mse', optimizer=common_optimizer, metrics=['accuracy'])

使用mse作为损失函数,并使用accuracy作为度量标准来编译网络。

  1. 接下来,创建生成器网络AgeneratorAToB)和BgeneratorBToA)。 生成器网络A的输入是数据集A的真实图像(realA),输出将是重构图像(fakeB)。 生成器网络B的输入是来自数据集B的真实图像(realB),输出将是重构图像(fakeA),如下所示:
generatorAToB = build_generator()
generatorBToA = build_generator()

如“CycleGAN 的架构”部分所述,CycleGAN 具有两个生成器网络。 generatorAToB会将图像从域A转换为域BgeneratorBToA会将图像从域B转换为域A

现在,我们已经创建了两个生成器网络和两个判别器网络。 在下一个小节中,我们将创建并编译一个对抗网络。

创建和编译对抗网络

对抗网络是一个组合网络。 它在单个 Keras 模型中使用所有四个网络。 创建对抗网络的主要目的是训练生成器网络。 当我们训练对抗网络时,它只训练生成器网络,但冻结了判别器网络的训练。 让我们创建一个具有所需功能的对抗模型。

  1. 首先为网络创建两个输入层,如下所示:
inputA = Input(shape=(128, 128, 3))
inputB = Input(shape=(128, 128, 3))

两个输入都将拍摄大小为(128, 128, 3)的图像。 这些是符号输入变量,不包含实际值。 它们用于创建 Keras 模型(TensorFlow 图)。

  1. 接下来,使用生成器网络生成伪造图像,如下所示:
generatedB = generatorAToB(inputA)
generatedA = generatorBToA(inputB)

使用符号输入层生成图像。

  1. 现在,再次使用生成器网络重建原始图像,如下所示:
reconstructedA = generatorBToA(generatedB)
reconstructedB = generatorAToB(generatedA)
  1. 使用生成器网络生成伪造图像,如下所示:
generatedAId = generatorBToA(inputA)
generatedBId = generatorAToB(inputB)

生成器网络AgeneratorAToB)将图像从域A转换为域B。类似地,生成器网络BgeneratorBToA)将图像从域B转换为域A

  1. 接下来,使两个判别器网络均不可训练,如下所示:
discriminatorA.trainable = False discriminatorB.trainable = False

我们不想在我们的对抗网络中训练判别器网络。

  1. 使用判别器网络来预测每个生成的图像是真实的还是伪造的,如下所示:
probsA = discriminatorA(generatedA)
probsB = discriminatorB(generatedB)
  1. 创建 Keras 模型并指定网络的输入和输出,如下所示:
adversarial_model = Model(inputs=[inputA, inputB],outputs=[probsA, probsB, reconstructedA, reconstructedB, generatedAId, generatedBId])

我们的对抗网络将采用两个输入值(即张量),并返回六个输出值(即张量)。

  1. 接下来,按如下所示编译对抗网络:
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。 现在我们已经准备好基本组件,让我们开始训练。

开始训练

要针对指定的周期数训练网络,请执行以下步骤:

  1. 首先加载两个域的数据集,如下所示:
imagesA, imagesB = load_images(data_dir=data_dir)

我们已经在定义了load_images函数。

  1. 接下来,创建一个for循环,该循环应运行由周期数指定的次数,如下所示:
for epoch in range(epochs):
    print("Epoch:{}".format(epoch))
  1. 创建两个列表以存储所有小批量的损失,如下所示:
dis_losses = []
gen_losses = []
  1. 计算epochs循环内的小批量数量,如下所示:
num_batches = int(min(imagesA.shape[0], imagesB.shape[0]) /  
                  batch_size)
print("Number of batches:{}".format(num_batches))
  1. 接下来,在周期循环内创建另一个循环,并使其运行num_batches指定的次数,如下所示:
 for index in range(num_batches):
        print("Batch:{}".format(index))

我们用于判别器网络和对抗网络训练的整个代码将在此循环内。

训练判别器网络

本小节中的代码是上一节中代码的延续。 在这里,您将看到如何训练判别器网络:

  1. 首先对两个域的图像进行小批量采样,如以下代码所示:
        batchA = imagesA[index * batch_size:(index + 1) * batch_size]
        batchB = imagesB[index * batch_size:(index + 1) * batch_size]
  1. 接下来,使用生成器网络生成伪造图像,如下所示:
        generatedB = generatorAToB.predict(batchA)
        generatedA = generatorBToA.predict(batchB)
  1. 然后,对判别器网络A进行真实图像和伪图像(由生成器网络生成)的训练,如下所示:
        dALoss1 = discriminatorA.train_on_batch(batchA, real_labels)
        dALoss2 = discriminatorB.train_on_batch(generatedA, fake_labels)

此步骤将在真实图像和伪图像的微型批量上训练判别器A,并会稍微改善网络。

  1. 接下来,对判别器B进行真实图像和伪图像的训练,如下所示:
dBLoss1 = discriminatorB.train_on_batch(batchB, real_labels)
dbLoss2 = discriminatorB.train_on_batch(generatedB, fake_labels)
  1. 现在,计算判别器网络的总损失值,如下所示:
d_loss = 0.5 * np.add(0.5 * np.add(dALoss1, dALoss2), 0.5 * 
         np.add(dBLoss1, dbLoss2))

到目前为止,我们一直在添加代码来训练判别器网络。 在下一部分中,我们将训练对抗性网络以训练生成器网络。

训练对抗网络

为了训练对抗网络,我们需要输入值和真实值。 网络的输入值为batchAbatchB。 基本真值是real_labelsreal_labelsbatchAbatchBbatchAbatchB,如下所示:

        g_loss = adversarial_model.train_on_batch([batchA, batchB],
  [real_labels, real_labels, batchA, batchB, batchA, batchB])

此步骤将训练生成器网络,而无需训练生成器网络。

在每个微型批量上完成一次迭代(循环)之后,将损失存储在名为dis_lossesgen_losses的列表中,如下所示:

        dis_losses.append(d_loss)
        gen_losses.append(g_loss)

每 10 个周期后,使用生成器网络生成一组图像:

# Sample and save images after every 10 epochs if epoch % 10 == 0:
    # Get a batch of test data
  batchA, batchB = load_test_batch(data_dir=data_dir, batch_size=2)

    # Generate images
  generatedB = generatorAToB.predict(batchA)
    generatedA = generatorBToA.predict(batchB)

    # Get reconstructed images
  reconsA = generatorBToA.predict(generatedB)
    reconsB = generatorAToB.predict(generatedA)

    # Save original, generated and reconstructed images
  for i in range(len(generatedA)):
        save_images(originalA=batchA[i], generatedB=generatedB[i], recosntructedA=reconsA[i],
  originalB=batchB[i], generatedA=generatedA[i], reconstructedB=reconsB[i],
  path="results/gen_{}_{}".format(epoch, i))

将前面的代码块放入epochs循环中。 每隔 10 个时间段,它将生成一批伪图像并将其保存到结果目录。

接下来,将平均损失存储到 TensorBoard 中以进行可视化。 既存储损失,也要存储生成器网络的平均损失和判别器网络的平均损失,如以下示例所示:

write_log(tensorboard, 'discriminator_loss', np.mean(dis_losses), 
          epoch)
write_log(tensorboard, 'generator_loss', np.mean(gen_losses), epoch)

将前面的代码块放入epochs循环中。

保存模型

在 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 个周期后,图像显示如下:

在 20 个周期之后,图像显示如下:

我建议您将网络训练 1000 个周期。 如果一切顺利,则在 1000 个周期之后,生成器网络将开始生成逼真的图像。

可视化损失

为了可视化训练的损失,请按以下方式启动 TensorBoard 服务器:

tensorboard --logdir=logs

现在,在浏览器中打开localhost:6006。 TensorBoard 的标量部分包含两个损失的图,如以下示例所示:

判别器网络的损失图如下所示:

生成器网络的损失图如下所示:

这些图将帮助您决定是继续还是停止训练。 如果损失不再减少,您就可以停止训练,因为没有改善的机会。 如果损失持续增加,则必须停止训练。 使用超参数,然后选择一组您认为可以提供更好结果的超参数。 如果损失逐渐减少,请继续训练模型。

可视化图

TensorBoard 的GRAPHS部分包含两个网络的图。 如果网络表现不佳,这些图可以帮助您调试网络。 它们还显示了每个图中的张量流和不同的操作,如以下示例所示:

CycleGAN 的实际应用

CycleGAN 有许多应用。 在本章中,我们使用了 CycleGAN 将画作转换为照片。 它们还可以在以下情况下使用:

  • 样式迁移:例如,将照片转换为绘画,反之亦然,将马的图像转换为斑马,反之亦然,将橘子的图像转换为苹果,反之亦然
  • 照片增强:CycleGAN 可用于增强图片质量
  • 季节转换:例如,将冬天的图片变成夏天的图片,反之亦然
  • 游戏风格迁移:CycleGAN 可用于将游戏 A 的风格迁移到游戏 B

总结

在本章中,我们学习了如何使用 CycleGAN 将绘画转换为照片。 我们从介绍 CyleGAN 入手,并探讨了 CycleGAN 所涉及的网络架构。 我们还探讨了训练 CycleGAN 所需的不同损失函数。 接下来是在 Keras 框架中实现 CycleGAN。 我们在monet2photo数据集上训练了 CycleGAN,并可视化了生成的图像,损失和不同网络的图。 在结束本章之前,我们探讨了 CycleGAN 的实际用例。

在下一章中,我们将在 pix2pix 网络上进行图像到图像的翻译。 在 pix2pix 中,我们将探索条件 GAN 进行图像翻译。

进一步阅读

CycleGAN 有许多已知的用例。 使用以下文章寻求帮助,探索新用途: