如何使用 Keras 从头开始实现 Pix2Pix GAN 模型

Pix2Pix GAN 是一种生成模型,用于在配对样本上训练图像到图像的转换。

例如,该模型可用于将白天图像转换为夜晚图像,或将产品(如鞋子)的草图转换为产品照片。

Pix2Pix 模型的好处是,与用于条件图像生成的其他 GAN 相比,它相对简单,并且能够跨各种图像转换任务生成大量高质量图像。

该模型非常出色,但其架构对初学者来说似乎有些复杂。

在本教程中,您将学习如何使用 Keras 深度学习框架从头开始实现 Pix2Pix GAN 架构。

完成本教程后,您将了解:

  • 如何为 Pix2Pix GAN 开发 PatchGAN 判别器模型。
  • 如何为 Pix2Pix GAN 开发 U-Net 编码器-解码器生成器模型。
  • 如何实现用于更新生成器的复合模型,以及如何训练这两个模型。

开启您的项目,阅读我的新书《Python 生成对抗网络》,其中包含分步教程和所有示例的Python 源代码文件。

让我们开始吧。

  • 2021 年 1 月更新:已更新,以便层冻结与批处理归一化一起使用。
How to Implement Pix2Pix GAN Models From Scratch With Keras

如何使用 Keras 从头开始实现 Pix2Pix GAN 模型
照片由 Ray in Manila 拍摄,保留部分权利。

教程概述

本教程分为五个部分;它们是:

  1. 什么是 Pix2Pix GAN?
  2. 如何实现 PatchGAN 判别器模型
  3. 如何实现 U-Net 生成器模型
  4. 如何实现对抗损失和 L1 损失
  5. 如何更新模型权重

什么是 Pix2Pix GAN?

Pix2Pix 是一种生成对抗网络 (GAN) 模型,专为通用图像到图像翻译而设计。

该方法由 Phillip Isola 等人在其 2016 年题为“Image-to-Image Translation with Conditional Adversarial Networks”(条件对抗网络中的图像到图像翻译)的论文中提出,并于 2017 年在 CVPR 上发表。

GAN 架构包含一个用于输出新的逼真合成图像的生成器模型和一个用于将图像分类为真实(来自数据集)或伪造(生成)的判别器模型。判别器模型直接更新,而生成器模型通过判别器模型更新。因此,这两个模型以对抗过程同时训练,其中生成器试图更好地欺骗判别器,判别器试图更好地识别伪造图像。

Pix2Pix 模型是一种条件 GAN,或 cGAN,其中输出图像的生成取决于输入,在此情况下为源图像。判别器同时接收源图像和目标图像,并必须确定目标图像是否是源图像的合理转换。

同样,判别器模型直接更新,生成器模型通过判别器模型更新,尽管损失函数会更新。生成器通过对抗损失进行训练,该损失鼓励生成器生成目标域中逼真的图像。生成器还通过在生成的图像和期望的输出图像之间测量的 L1 损失进行更新。此额外损失鼓励生成器模型创建源图像的逼真翻译。

Pix2Pix GAN 已在多种图像到图像转换任务中得到证明,例如将地图转换为卫星照片、将黑白照片转换为彩色以及将产品草图转换为产品照片。

现在我们熟悉了 Pix2Pix GAN,让我们探讨如何使用 Keras 深度学习库来实现它。

想从零开始开发GAN吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

如何实现 PatchGAN 判别器模型

Pix2Pix GAN 中的判别器模型实现为 PatchGAN。

PatchGAN 的设计基于感受野的大小,有时也称为有效感受野。感受野是模型的一个输出激活与输入图像(实际上是输入通道的体积)区域之间的关系。

使用的是 70×70 的 PatchGAN,这意味着模型的输出(或每个输出)映射到输入图像的 70×70 的方形区域。实际上,70×70 的 PatchGAN 将对输入图像的 70×70 的块进行真实或伪造的分类。

……我们设计了一个判别器架构——我们称之为 PatchGAN——它仅在块的尺度上进行惩罚。此判别器尝试对图像中的每个 NxN 块进行真实或伪造的分类。我们跨图像卷积地运行此判别器,并平均所有响应以提供 D 的最终输出。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

在我们深入研究 PatchGAN 的配置细节之前,了解感受野的计算非常重要。

感受野不是判别器模型的输出大小,例如,它不指模型输出的激活图的形状。它是模型的一种定义,指输出激活图中的一个像素与输入图像的关系。模型输出可以是一个单一值,也可以是一个方形激活图,其中值表示输入图像的每个块是真实还是伪造。

传统上,感受野是指单个卷积层相对于该层的输入、滤波器大小和步长计算的激活图大小。有效感受野推广了这一想法,并计算了堆叠卷积层相对于原始图像输入的输出的感受野。这两个术语经常互换使用。

Pix2Pix GAN 的作者提供了一个 Matlab 脚本来计算脚本 receptive_field_sizes.m 中不同模型配置的有效感受野大小。通过举例说明 70×70 PatchGAN 的感受野计算会很有帮助。

70×70 PatchGAN 拥有固定的三层(不包括输出层和倒数第二层),无论输入图像的大小如何。感受野的计算(以一维为例)为:

  • 感受野 = (输出尺寸 – 1) * 步长 + 核尺寸

其中输出尺寸是前一层激活图的大小,步长是应用滤波器到激活时滤波器移动的像素数,核尺寸是应用的滤波器的大小。

PatchGAN 使用固定的步长 2×2(输出层和倒数第二层除外)和固定的核尺寸 4×4。因此,我们可以从模型输出的一个像素开始,向后追溯到输入图像,来计算感受野大小。

我们可以开发一个名为 `receptive_field()` 的 Python 函数来计算感受野,然后计算并打印 Pix2Pix PatchGAN 模型中每一层的感受野。完整示例列在下面。

运行示例将打印模型中从输出层到输入层的每一层的感受野大小。

我们可以看到,输出层中的每个 1×1 像素映射到输入层中 70×70 的感受野。

Pix2Pix 论文的作者探讨了不同的 PatchGAN 配置,包括一个 1×1 感受野(称为 PixelGAN)和一个与模型输入的 256×256 像素图像匹配(重采样为 286×286)的感受野(称为 ImageGAN)。他们发现 70×70 的 PatchGAN 在性能和图像质量之间取得了最佳的折衷。

70×70 的 PatchGAN [...] 获得了略好的分数。扩展到完整的 286×286 ImageGAN 并未显示出结果视觉质量的提升。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

PatchGAN 的配置在论文附录中提供,并且可以通过查看官方 Torch 实现中的 defineD_n_layers() 函数来确认。

该模型接受两个图像作为输入,具体来说是源图像和目标图像。这些图像在通道级别串联起来,例如,每个图像的 3 个彩色通道变为输入的 6 个通道。

令 Ck 表示一个带有 k 个滤波器的卷积-批归一化-ReLU 层。 […] 所有卷积都是 4×4 的空间滤波器,使用步长为 2。 […] 70×70 的判别器架构是:C64-C128-C256-C512。最后一层之后,应用一个卷积层将特征图映射到一维输出,然后是 Sigmoid 函数。作为上述约定的例外,批归一化未应用于第一个 C64 层。所有 ReLU 都是 leaky ReLU,斜率为 0.2。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

PatchGAN 配置使用简写符号定义为:C64-C128-C256-C512,其中 C 指的是一个卷积-批归一化-LeakyReLU 层块,数字表示滤波器的数量。批归一化不用于第一层。如前所述,核尺寸固定为 4×4,除了模型的最后 2 层外,所有层都使用 2×2 的步长。LeakyReLU 的斜率设置为 0.2,输出层使用 sigmoid 激活函数。

通过将 256×256 的输入图像缩放到 286×286,然后随机裁剪回 256×256 来应用随机抖动。权重从均值为 0、标准差为 0.02 的高斯分布初始化。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

模型权重是通过均值为 0.0、标准差为 0.02 的随机高斯分布初始化的。输入到模型图像为 256×256。

……我们在优化 D 时将目标除以 2,这减慢了 D 相对于 G 的学习速度。我们使用小批量 SGD 并应用 Adam 求解器,学习率为 0.0002,动量参数 β1 = 0.5,β2 = 0.999。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

模型以一个图像的批次大小进行训练,并使用随机梯度下降的 Adam 版本,具有较小的学习范围和适中的动量。在每次模型更新时,判别器的损失加权 50%。

将所有这些结合起来,我们可以定义一个名为 `define_discriminator()` 的函数,该函数创建 70×70 的 PatchGAN 判别器模型。

下面列出了定义模型的完整示例。

运行示例首先会总结模型,从而让我们了解输入形状如何在各层之间转换以及模型的参数数量。

我们可以看到,两个输入图像被串联起来,形成一个 256x256x6 的输入,进入第一个隐藏的卷积层。这种输入图像的级联可以在模型输入层之前完成,但让模型执行级联操作可以使模型的行为更加清晰。

我们可以看到,模型的输出将是一个 16×16 像素(或激活)的激活图,具有一个通道,图中的每个值对应于输入 256×256 图像的 70×70 像素块。如果输入图像尺寸减半为 128×128,则输出特征图也将减半为 8×8。

该模型是一个二分类模型,意味着它预测的输出是一个在 [0,1] 范围内的概率,即输入图像是真实图像还是来自目标数据集的图像的可能性。可以通过对这些值块求平均来给出模型的真实/伪造预测。在训练时,目标值与目标值矩阵进行比较,伪造为 0,真实为 1。

模型图被创建,显示了许多相同的图形信息。模型并不复杂,它有一个线性的路径,有两个输入图像和一个输出预测。

注意:创建图表假设已安装 pydot 和 pygraphviz 库。如果存在问题,可以注释掉 `plot_model()` 函数的导入和调用。

Plot of the PatchGAN Model Used in the Pix2Pix GAN Architecture

Pix2Pix GAN 架构中使用的 PatchGAN 模型图

现在我们知道如何实现 PatchGAN 判别器模型,接下来我们可以研究实现 U-Net 生成器模型。

如何实现 U-Net 生成器模型

Pix2Pix GAN 的生成器模型实现为 U-Net。

U-Net 模型是一种用于图像翻译的编码器-解码器模型,其中使用跳跃连接将编码器中的层与解码器中具有相同大小特征图的相应层连接起来。

模型编码器部分由卷积层组成,使用 2×2 的步长将输入源图像下采样到瓶颈层。模型解码器部分读取瓶颈输出,并使用转置卷积层上采样到所需的输出图像大小。

……输入通过一系列逐渐下采样的层,直到到达瓶颈层,此时过程反转。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

Architecture of the U-Net Generator Model

U-Net 生成器模型架构
摘自《条件对抗网络中的图像到图像翻译》。

在具有相同大小特征图的层之间添加了跳跃连接,因此第一个下采样层与最后一个上采样层连接,第二个下采样层与倒数第二个上采样层连接,依此类推。连接将下采样层中的特征图通道与上采样层中的特征图进行级联。

具体来说,我们在每层 i 和层 n-i 之间添加跳跃连接,其中 n 是总层数。每个跳跃连接仅将层 i 的所有通道与层 n-i 的通道进行级联。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

与 GAN 架构中的传统生成器模型不同,U-Net 生成器不从潜在空间获取输入。相反,dropout 层被用作训练期间和模型用于进行预测(例如,在推理时生成图像)时的随机性来源。

同样,在训练和推理过程中,批量归一化(batch normalization)的使用方式也相同,这意味着为每个批次计算统计数据,而不是在训练过程结束时固定。这被称为实例归一化(instance normalization),尤其是在批次大小设置为1时,就像Pix2Pix模型那样。

在推理时,我们以与训练阶段完全相同的方式运行生成器网络。这与通常的协议不同之处在于,我们在测试时应用 dropout,并使用测试批次的统计数据来应用批量归一化,而不是聚合训练批次的统计数据。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

在Keras中,像 DropoutBatchNormalization 这样的层在训练和推理模型中操作方式不同。我们可以在调用这些层时将“training”参数设置为“True”,以确保它们即使在推理时也始终以训练模式运行。

例如,可以按如下方式将一个在推理和训练时都会进行 dropout 的 Dropout 层添加到模型中:

与判别器模型一样,生成器模型的配置细节定义在 论文附录 中,并且在与 官方Torch实现中的defineG_unet()函数 进行比较时可以确认。

编码器使用类似判别器模型的卷积-批量归一化-LeakyReLU块,而解码器模型使用卷积-批量归一化-Dropout-ReLU块,dropout率为50%。所有卷积层都使用4x4的滤波器大小和2x2的步幅。

令Ck表示一个具有k个滤波器的卷积-批量归一化-ReLU层。CDk表示一个具有50% dropout率的卷积-批量归一化-Dropout-ReLU层。所有卷积都是4x4的空间滤波器,以步幅2应用。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

U-Net模型的架构使用简写表示法定义为:

  • 编码器: C64-C128-C256-C512-C512-C512-C512-C512
  • 解码器: CD512-CD1024-CD1024-C1024-C1024-C512-C256-C128

编码器的最后一层是瓶颈层,根据论文的修正和代码中的确认,该层不使用批量归一化,而是使用 ReLU 激活 而不是 LeakyRelu。

……瓶颈层的激活被批量归一化操作归零,有效地跳过了最内层。这个问题可以通过从该层移除批量归一化来修复,正如在公开代码中所做的那样。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

U-Net解码器中的滤波器数量有点误导,因为它是与编码器中相应层连接后的滤波器的数量。当我们创建一个模型图时,这可能会更清楚。

模型的输出使用一个单一的卷积层,具有三个通道,并且在输出层使用tanh激活函数,这对于GAN生成器模型来说是常见的。编码器的第一层不使用批量归一化。

在解码器的最后一层之后,应用一个卷积来映射到输出通道的数量(通常是3 [...]),然后是一个Tanh函数 [...] 编码器的第一个C64层不应用BatchNorm。编码器中的所有ReLU都是 leaky 的,斜率为0.2,而解码器中的ReLU不是 leaky 的。

——《使用条件对抗网络进行图像到图像翻译》,2016年。

总而言之,我们可以定义一个名为define_generator()的函数,该函数定义了U-Net编码器-解码器生成器模型。还提供了两个辅助函数来定义编码器层块和解码器层块。

下面列出了定义模型的完整示例。

运行示例首先总结了模型。

该模型有一个输入和一个输出,但跳跃连接使得摘要难以阅读。

创建了模型的图,显示了很大程度上相同的信息。模型很复杂,而图有助于理解跳跃连接及其对解码器滤波器数量的影响。

注意:创建图表假设已安装 pydot 和 pygraphviz 库。如果存在问题,可以注释掉 `plot_model()` 函数的导入和调用。

从输出层倒推,如果我们查看 Concatenate 层和解码器的第一个 Conv2DTranspose 层,我们可以看到通道数量为:

  • [128, 256, 512, 1024, 1024, 1024, 1024, 512].

反转此列表即可得到论文中提到的解码器各层滤波器数量的配置:

  • CD512-CD1024-CD1024-C1024-C1024-C512-C256-C128
Plot of the U-Net Encoder-Decoder Model Used in the Pix2Pix GAN Architecture

Pix2Pix GAN 架构中使用的 U-Net 编码器-解码器模型图

现在我们已经定义了两个模型,我们可以看看生成器模型是如何通过判别器模型进行更新的。

如何实现对抗损失和 L1 损失

判别器模型可以直接更新,而生成器模型必须通过判别器模型更新。

这可以通过在Keras中定义一个新的复合模型来实现,该模型将生成器模型的输出连接到判别器模型的输入。然后,判别器模型可以预测生成的图像是真实的还是伪造的。我们可以以一种方式更新复合模型的权重,使生成的图像具有“真实”的标签而不是“伪造”的标签,这将促使生成器权重更新以生成更好的伪造图像。在此上下文中,我们还可以将判别器权重标记为不可训练,以避免误导性更新。

此外,还需要更新生成器以更好地匹配目标翻译。这意味着复合模型还必须直接输出生成的图像,以便将其与目标图像进行比较。

因此,我们可以将此复合模型的输入和输出总结如下:

  • 输入: 源图像
  • 输出: 真实/伪造分类,生成的目标图像。

生成器的权重将通过判别器输出的对抗损失和直接图像输出的L1损失进行更新。损失分数被加在一起,其中L1损失被视为一个正则化项,并通过一个称为lambda的超参数加权,该超参数设置为100。

  • loss = 对抗损失 + lambda * L1 损失

下面的define_gan()函数实现了这一点,它将定义的生成器和判别器模型作为输入,并创建可用于更新生成器模型权重的复合GAN模型。

源图像输入同时提供给生成器和判别器作为输入,生成器的输出也连接到判别器作为输入。

在模型编译时,为判别器和生成器输出分别指定了两个损失函数。loss_weights参数用于定义当它们加在一起以更新生成器模型权重时每个损失的权重。

将前面章节的模型定义与此结合,完整的示例列在下面。

运行示例,首先会总结复合模型,展示 256×256 的图像输入,来自 *model_2*(生成器)的相同形状的输出,以及来自 *model_1*(判别器)的 PatchGAN 分类预测。

复合模型的图也会被创建,展示输入图像如何流经生成器和判别器,并且模型有两个输出或终结点,分别来自两个模型。

注意:创建图表假定已安装 pydot 和 pygraphviz 库。如果存在问题,可以注释掉 plot_model() 函数的导入和调用。

Plot of the Composite GAN Model Used to Train the Generator in the Pix2Pix GAN Architecture

Pix2Pix GAN 架构中用于训练生成器的复合 GAN 模型图

如何更新模型权重

训练定义的模型相对直接。

首先,我们必须定义一个辅助函数,该函数将选择一批真实的源图像和目标图像以及相关的输出(1.0)。在这里,数据集是两个图像数组的列表。

同样,我们需要一个函数来生成一批假图像和相关的输出(0.0)。在这里,样本是源图像的数组,将为其生成目标图像。

现在,我们可以定义单次训练迭代的步骤。

首先,我们必须调用 *generate_real_samples()* 来选择一批源图像和目标图像。

通常,批次大小 (*n_batch*) 设置为 1。在这种情况下,我们将假设输入图像为 256×256,这意味着 PatchGAN 判别器的 *n_patch* 将为 16,表示 16×16 的输出特征图。

接下来,我们可以使用选定的真实源图像批次来生成相应的生成或假目标图像批次。

然后,我们可以使用真实和虚假图像以及它们的目标来更新独立的判别器模型。

到目前为止,这与在 Keras 中更新 GAN 是一样的。

接下来,我们可以通过对抗损失和 L1 损失来更新生成器模型。回想一下,复合 GAN 模型以源图像批次作为输入,首先预测真实/虚假分类,然后生成的目标。在这里,我们向复合模型的判别器输出提供一个目标,以指示生成的图像是“*真实*”(类别=1)。真实的目标图像用于计算它们与生成的目标图像之间的 L1 损失。

我们有两个损失函数,但为批次更新计算了三个损失值,其中只有第一个损失值是相关的,因为它是在批次上对抗损失和 L1 损失的加权和。

就是这样。

我们可以将所有这些定义在一个名为 *train()* 的函数中,该函数接受定义的模型和一个加载的数据集(作为两个 NumPy 数组),并训练模型。

然后可以直接调用 train 函数,传入定义的模型和加载的数据集。

进一步阅读

如果您想深入了解,本节提供了更多关于该主题的资源。

官方

API

文章

总结

在本教程中,您学习了如何使用 Keras 深度学习框架从头开始实现 Pix2Pix GAN 架构。

具体来说,你学到了:

  • 如何为 Pix2Pix GAN 开发 PatchGAN 判别器模型。
  • 如何为 Pix2Pix GAN 开发 U-Net 编码器-解码器生成器模型。
  • 如何实现用于更新生成器的复合模型,以及如何训练这两个模型。

你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。

立即开发生成对抗网络!

Generative Adversarial Networks with Python

在几分钟内开发您的GAN模型

...只需几行python代码

在我的新电子书中探索如何实现
使用 Python 构建生成对抗网络

它提供了关于以下内容的自学教程端到端项目
DCGAN条件GAN图像翻译Pix2PixCycleGAN
以及更多...

最终将GAN模型引入您的视觉项目

跳过学术理论。只看结果。

查看内容

103 条回复“如何从零开始实现 Pix2Pix GAN 模型(Keras)”

  1. Gemadou 2019 年 8 月 2 日 12:32 am #

    这本书是否涵盖了 TensorFlow 2.0?

    • Jason Brownlee 2019 年 8 月 2 日 6:50 am #

      不,TensorFlow 2.0 尚未发布,仍处于 beta 版。

  2. Lulu 2019 年 8 月 2 日 10:11 am #

    好????

  3. Shravankumar 2019 年 8 月 2 日 4:04 pm #

    你是最棒的。

  4. Pau 2019 年 8 月 3 日 2:32 am #

    假设我想保存最好的模型。应该考虑哪个损失来保存模型,或者进行提前停止?

    • Jason Brownlee 2019 年 8 月 3 日 8:13 am #

      哎哟。很好的问题!

      都不是。损失不是衡量 GAN 生成图像质量的好指标。WGAN 损失可以是,LS 损失也可以是,但我不会在实践中依赖它们。

      相反,一直保存模型——例如每个周期,用它们生成样本,并根据生成样本的质量来选择模型。

      使用 Incept Score 或 FID 等指标评估样本(教程即将推出)。

  5. can 2019 年 8 月 4 日 4:08 am #

    感谢您的宝贵贡献。

    您说解压数据集。但我无法弄清楚如何加载数据集。据我所知,我们应该有两个数据集,一个是要翻译成预期图像的源数据,另一个是用于应用于源图像的真实图像。但我们如何加载这些数据集呢?

    # 加载图像数据
    dataset = … (这里应该输入两个数据集的目录吗?但我无法弄清楚)

    如果我的问题很奇怪,请原谅。

  6. Shane Strunk 2019 年 8 月 13 日 2:05 am #

    当您将判别器的 Trainable 设置为 False 时,它在所有后续训练批次中不都是不可训练的吗?在训练判别器之前,您不应该每次都将其设置为 True,并在训练整个 GAN 模型时将其更改为 False 吗?

    • Jason Brownlee 2019 年 8 月 13 日 6:13 am #

      是的,但这仅在复合模型中。

      独立的判别器不受影响。

  7. Salman Sajd 2019 年 9 月 7 日 4:09 am #

    为什么 GAN 的批次大小通常是 1?

    • Jason Brownlee 2019 年 9 月 7 日 5:37 am #

      不总是,但在这种情况下是因为我们为每个图像进行更新。

  8. Vasudevakrishna 2019 年 9 月 9 日 6:51 pm #

    您在哪里使用了 lambda?
    我找不到那个参数。

    • Jason Brownlee 2019 年 9 月 10 日 5:39 am #

      好问题,在 compile() 的 loss_weights 参数中。

  9. Majid 2019 年 9 月 12 日 2:59 pm #

    你好 Adrian,

    感谢您提供如此精彩的文章。

    有没有办法通过负的损失权重来编写代码,而不是交换标签?

    • Jason Brownlee 2019 年 9 月 13 日 5:38 am #

      是的,但我没有可运行的示例,抱歉。

  10. Majid 2019 年 9 月 12 日 3:02 pm #

    抱歉,我本来想评论“如何编写 GAN 训练算法和损失函数”。是我的错。

  11. Elaheh Amini 2019 年 9 月 25 日 9:44 pm #

    亲爱的 Jason,

    您写的这个判别器的有效感受野不是 142 吗?因为我们有 4 个卷积层,核大小为 4,步幅为 2,最后两个层核大小为 4,步幅为 1,这使得感受野为 142。

    • Jason Brownlee 2019 年 9 月 26 日 6:37 am #

      不,可运行示例从第一个卷积层开始,而不是从像素开始。

      • mohammadhosseinashoori 2020 年 2 月 11 日 7:19 pm #

        亲爱的Jason
        在“Image-to-Image Translation with Conditional Adversarial Networks”论文中

        70×70 判别器架构是
        C64-C128-C256-C512

        在最后一层之后,应用卷积将其映射到
        一个一维输出,然后是 Sigmoid 函数。

        但您的实现中有两个 C512 层
        我不明白……

        感谢您的精彩网站

        • Jason Brownlee 2020 年 2 月 12 日 5:44 am #

          是的,该实现与他们提供的代码匹配,其中最后的 C512 “解释”了 70×70 的输出。

      • Qidong Yang 2020 年 2 月 26 日 7:32 pm #

        嗨,Jason,
        我有一个关于感受野计算的问题。我仍然不明白为什么 70X70 PatchGAN 的实现代码中有 6 个卷积层,但在计算感受野时只使用了 5 个卷积层。有一个(filter size = 4, stride = 2)卷积层是为了什么目的而被遗漏的?您能详细解释一下吗?谢谢。

        • Jason Brownlee 2020 年 2 月 27 日 5:41 am #

          最后一层解释了感受野,与论文和官方实现匹配。

          这也在代码注释中有所说明。

          • Jagannath Krishnan 2023 年 6 月 23 日 2:07 am #

            感谢这个出色的资源!

            如果我在作者的原始实现中使用 torch_summary 打印模型,它似乎没有这个额外的 C512 层。

            —————————————————————-
            层(类型) 输出形状 参数 #
            ================================================================
            Conv2d-1 [-1, 64, 128, 128] 6,208
            LeakyReLU-2 [-1, 64, 128, 128] 0
            Conv2d-3 [-1, 128, 64, 64] 131,072
            BatchNorm2d-4 [-1, 128, 64, 64] 256
            LeakyReLU-5 [-1, 128, 64, 64] 0
            Conv2d-6 [-1, 256, 32, 32] 524,288
            BatchNorm2d-7 [-1, 256, 32, 32] 512
            LeakyReLU-8 [-1, 256, 32, 32] 0
            Conv2d-9 [-1, 512, 31, 31] 2,097,152
            BatchNorm2d-10 [-1, 512, 31, 31] 1,024
            LeakyReLU-11 [-1, 512, 31, 31] 0
            Conv2d-12 [-1, 1, 30, 30] 8,193
            ================================================================
            总参数:2,768,705

            请注意,它比添加该额外 conv2d 层的模型少了约 400 万个参数。

          • James Carmichael 2023 年 6 月 23 日 8:10 am #

            感谢您的反馈,Jagannath!

  12. Yasmeen 2019 年 9 月 26 日 12:28 pm #

    亲爱的 Jason,

    感谢这篇文章。它非常有帮助,我从中理解了 pix2pix 模型。是否有关于 Star GAN 的 Keras 文章?
    https://arxiv.org/abs/1711.09020

    谢谢你

    • Jason Brownlee 2019 年 9 月 26 日 1:41 pm #

      感谢您的建议,我将来可能会涵盖它。

      • YD 2019 年 11 月 15 日 8:19 pm #

        你好 Jason,

        再次感谢您出色的教程。
        我想知道是否可以保存判别器、生成器和 GAN 的状态,稍后加载它们并继续训练?

        您能否为我指明方向?

        谢谢你

        • Jason Brownlee 2019 年 11 月 16 日 7:22 am #

          是的,您可以使用每个模型的 save() 方法。
          https://machinelearning.org.cn/save-load-keras-deep-learning-models/

          • YD 2019 年 11 月 20 日 4:03 am #

            嗨,Jason,

            感谢您的建议。您是否有保存所有三个模型、加载它们然后再次训练的代码示例?我尝试保存和加载所有三个模型,但尝试再次训练时,判别器和生成器的损失为 0 且保持为 0。我搜索了很多论坛但都没有找到解决方案。

            关于这个特定主题是否有任何示例?我认为这将对很多人有所帮助

          • Jason Brownlee 2019年11月20日 晚上6:22 #

            我在这里有一个关于训练、保存、加载和使用模型的例子
            https://machinelearning.org.cn/how-to-develop-a-pix2pix-gan-for-image-to-image-translation/

          • YD 2019年11月20日 晚上8:21 #

            在所有例子中,生成器模型仅在训练后保存和加载,用于预测/生成新图像。不过,还是要感谢 Jason 的教程。

  13. Nina 2019年10月12日 凌晨12:22 #

    亲爱的 Jason,

    非常感谢。非常有帮助。那 pix2pixHD 呢?

  14. Aman Krishna 2019年11月12日 下午2:27 #

    你好,
    感谢这篇精彩的文章!
    我有一个关于这个段落的疑问
    “我们可以看到,模型输出将是一个激活图,大小为 16×16 像素或激活,以及一个通道,图中的每个值对应于输入 256×256 图像的 70×70 像素块。如果输入图像的尺寸减半到 128×128,那么输出特征图也会减半到 8×8。”

    -> 16×16 中的每个值对应输入图像的 70×70 像素块是如何确定的?
    -> 如果我想将其应用于输入图像的 16×16 块,输出尺寸应该是多少?
    -> 输入图像是指 256x256x6(连接后)的图像还是连接前的图像?

    非常感谢!

    • Jason Brownlee 2019年11月13日 上午5:34 #

      谢谢。

      请参阅标题为“如何实现 PatchGAN 判别器模型”的部分,了解 PatchGAN 的计算方法。您可以插入任何您想要的数字。

  15. Sarvesh 2019年11月18日 晚上9:21 #

    您好,在您的代码中,您只采用了一个输入层。您是如何处理两个图像 A 和 B 的?我无法理解。我一直在尝试构建自己的模型,我所做的是:
    self.discriminator = self.build_discriminator()
    self.discriminator.compile(loss=”binary_crossentropy”,
    optimizer=optimizer,
    loss_weights=[0.5])
    print(“## DISCRIMINATOR ##”)
    self.discriminator.summary()
    # plot_model(self.discriminator, to_file=’discriminator.png’, show_shapes=True, show_layer_names=True)

    self.generator = self.build_generator()
    print(“## GENERATOR ##”)
    self.generator.summary()
    # plot_model(self.generator, to_file=”generator.png”, show_shapes=True, show_layer_names=True)

    lr = Input(shape=self.img_shape)
    hr = Input(shape=self.img_shape)

    sr = self.generator(lr)

    valid = self.discriminator([sr,hr])
    self.discriminator.traininable = False

    self.combined = Model(inputs=[lr,hr], outputs=[valid,sr])
    self.combined.compile(loss=[‘binary_crossentropy’,’mae’],
    loss_weights=[1,100],
    optimizer=optimizer)
    self.combined.summary()

    这是我的 GAN 的摘要
    Model: “model_2”
    __________________________________________________________________________________________________
    层(类型)输出形状参数 # 连接到
    ==================================================================================================
    input_4 (InputLayer) [(None, 512, 512, 3) 0
    __________________________________________________________________________________________________
    model_1 (Model) (None, 512, 512, 3) 54429315 input_4[0][0]
    __________________________________________________________________________________________________
    input_5 (InputLayer) [(None, 512, 512, 3) 0
    __________________________________________________________________________________________________
    model (Model) (None, 32, 32, 1) 6968257 model_1[1][0]
    input_5[0][0]
    ==================================================================================================
    总参数:61,397,572
    可训练参数:61,384,900
    不可训练参数:12,672

    我不明白为什么使用两个输入层是错误的?为什么我的判别器不接受 trainable=False?

    您能帮我解答这些疑问吗?

  16. vandana kushwaha 2019年12月10日 晚上10:17 #

    感谢这篇精彩的文章。

    是否必须输入 256x256x3 的图像?

    我们可以输入 480x480x3 等不同尺寸的图像吗?

    是否可以将非方形图像输入网络,而不是方形图像?

    • Jason Brownlee 2019年12月11日 上午6:58 #

      是的,但您需要更改模型。

      • vandana kushwaha 2019年12月11日 下午5:41 #

        请解释一下对于方形或非方形图像,在模型-网络方面需要进行哪些更改。

  17. Scott 2019年12月31日 上午9:58 #

    非常有用的信息!

    根据下面这行代码,我对我的黑白数据集做了一个小的修改:

    “在解码器的最后一层之后,应用一个卷积来映射到输出通道数(通常是 3 […])”

    我正在训练一些黑白数据,形状为 [256,256,1],注意到在 define_generator 的末尾有一个硬编码的“3”
    在 define_generator 的末尾

    g = Conv2DTranspose(3, (4,4), strides=(2,2), padding=’same’, kernel_initializer=init)(d7)

    您默认使用 3 通道图像,但您的 input_shape 可以处理其他变体,只需进行此修改:

    g = Conv2DTranspose(input_shape[2], (4,4), strides=(2,2), padding=’same’, kernel_initializer=init)(d7)

    谢谢你做这个

  18. Matheus 2020年1月29日 上午3:17 #

    您好,我想问一个问题。

    我正在尝试使用 PyTorch 复现 pix2pix,但在实现判别器时,如果我将最后两个层的核大小设置为 4,步幅设置为 1,那么输出必须是一个 14×14 的矩阵(考虑到官方 github 项目中的 padding=1)。我不明白 padding='same' 如何使其保持 16×16。

    • Jason Brownlee 2020年1月29日 上午6:45 #

      也许可以使用现有 PyTorch 实现的模型?

  19. Pallavi 2020年3月4日 上午10:37 #

    将真实图像和生成图像连接起来作为输入传递给判别器的目的是什么?

    • Jason Brownlee 2020年3月4日 下午1:33 #

      为了在更新权重时,批量中有两种图像类型。

  20. Dang Tuan Hoang 2020年3月5日 晚上10:16 #

    Jason 您好,我有一个关于 70×70 PatchGAN 感受野的问题。在 pix2pix 论文中,他们的架构是 C64-C128-C256-C512-C1,所有卷积层都有 4×4 滤波器和步幅 2(第 16 页),这与您的实现略有不同。根据感受野计算,我得到

    receptive_field(1, 4, 2)
    Out[3]: 4
    receptive_field(4, 4, 2)
    Out[4]: 10
    receptive_field(10, 4, 2)
    Out[5]: 22
    receptive_field(22, 4, 2)
    Out[6]: 46
    receptive_field(46, 4, 2)
    Out[7]: 94

    他们改变了架构吗?

    • Jason Brownlee 2020年3月6日 上午5:32 #

      没有,但如果您查看代码,他们使用了一个 patchgan,并且在论文描述中不包含输出层之前的块。我提出的实现是基于他们发布的代码。

  21. Autumm 2020年3月8日 上午1:43 #

    尊敬的作者,我在加载和保存 GAN 进行研究时遇到了困难。您能否回答我关于所有类型的 GAN 都可以保存和加载模型的问题?非常感谢您给我这个留言的机会。

  22. Vedansh Dwivedi 2020年3月11日 上午2:44 #

    您能分享一个可以下载此模型的链接(Github 或某个在线存储链接)吗?

    • Jason Brownlee 2020年3月11日 上午5:27 #

      不。我不分享训练好的模型。我的重点是帮助开发人员构建模型和类似的模型。

  23. Ali 2020年5月3日 下午6:00 #

    为什么您的网站屏蔽伊朗的 IP?
    我们只是渴望学习的学生,您的网站非常有帮助。

  24. SG 2020年5月6日 上午3:29 #

    如何处理大于 256×256 且不一定是方形的图像?

    • Jason Brownlee 2020年5月6日 上午6:30 #

      要么将图像缩放到匹配模型,要么更改模型来匹配图像。

  25. Ujjayant Sinha 2020年5月7日 晚上10:15 #

    Jason 您好。在“如何实现对抗和 L1 损失”部分,我们不应该使用自定义损失函数而不是 mae 吗?

    • Ujjayant Sinha 2020年5月7日 晚上10:28 #

      def L1(y_true, y_pred)

      x = K.abs(y_true – y_pred)

      return K.sum(x)

      类似于这个,而不是 mae。

      • Jason Brownlee 2020年5月8日 上午6:34 #

        是的,我们这样做,请参阅“组合模型中使用的 mae”损失。

    • Jason Brownlee 2020年5月8日 上午6:33 #

      在这种情况下,我们使用 L1 对抗损失,正如论文中所述。

  26. Neal 2020年5月18日 晚上8:12 #

    嗨,Jason,

    组合模型如何知道对哪个输出应用哪个损失?是损失的顺序吗?那么第一个损失(“二元交叉熵”)将为输出的第一个参数计算,mae 将为第二个计算?
    如果我想为一个输出计算多个损失,我该怎么做?

  27. Shubham Shekhar 2020年6月10日 下午6:29 #

    嗨,Jason,
    您能否在 TensorFlow 中使用 ResNet 块实现非配对图像到图像的翻译?我想学习这个。如果您能从头开始构建模型,那将很有帮助。

    原始论文:https://arxiv.org/abs/1703.10593

  28. Mahdi 2020年6月21日 上午2:07 #

    嗨,Jason,

    感谢这篇信息丰富的教程。我正尝试将此用于 81*81 的图像而不是 256*256,但我遇到了一些问题。最主要的问题是 Conv2DTranspose 的维度不一致。
    是否需要针对此特定尺寸进行任何调整?

    谢谢你

    • Jason Brownlee 2020年6月21日 上午6:28 #

      您需要根据新尺寸调整模型。这可能需要一些试验和错误。

  29. Ahmed Tarek 2020年6月22日 上午7:27 #

    你好 Jason,

    我正在尝试将 UNet CNN 应用于与图像到图像翻译非常相似的任务。网络的输入是大小为 (64,256) 的二进制矩阵,输出是大小为 (64,32)。训练数据的准确率约为 90%,而测试数据的准确率约为 50%。这里的准确率是指每张图像中正确条目的平均百分比。此外,在训练期间,验证损失增加而损失减少,这是过拟合的明显迹象。我已经尝试了大多数正则化技术,也尝试减小模型的容量,但这只会减小训练误差而不会改善泛化误差。有什么建议或想法吗?

  30. Abdulaziz Alhefdhi 2020年8月11日 下午12:18 #

    非常感谢您的课程。我尝试绘制组合(GAN)模型,但没有更改您的任何代码,但遇到了以下错误:
    AttributeError: ‘ListWrapper’ object has no attribute ‘name’

  31. Ujjayant 2020年8月27日 晚上11:03 #

    Jason 您好。我正在尝试根据 https://jleinonen.github.io/2019/11/07/gan-elements-2.html 添加梯度惩罚,以及 Wasserstein 损失(来自您的一篇文章)。我不太清楚如何修改判别器的 train_on_batch。您能给我一些指导吗?

    • Jason Brownlee 2020年8月28日 上午6:48 #

      抱歉,我不熟悉那篇文章,也许直接问作者?

  32. Abhi Bhagat 2020年9月27日 下午4:09 #

    .
    非常重要的问题。

    在章节:“如何更新模型权重”
    在最终代码中
    def train ()

    g_loss, _, _ = gan_model.train_on_batch(X_realA, [y_real, X_realB])
    .
    .
    现在在 gan_model() 中

    model = Model(in_src, [dis_out, gen_out])
    .
    .
    它的 gen_out == generator 的输出

    但 g_loss, _, _ = gan_model.train_on_batch(X_realA, [y_real, X_realB])

    它被分配为
    gen_out = X_realB。这让我感到困惑,因为 X_realB 是“目标”真实图像。
    .
    gen_out 是生成器的输出。
    在 train 函数中
    X_fakeB 是生成器生成的“目标”图像。

    那么为什么使用 X_realB 而不是 X_fakeB?
    .

    • Jason Brownlee 2020年9月28日 上午6:14 #

      您具体遇到了什么问题?也许您可以详细说明一下?

  33. Nirisha Manandhar 2021年3月3日 上午6:40 #

    嗨!
    感谢这篇精彩的文章!

    我有一个小小的困惑,在此文章和下一篇文章之间:

    https://machinelearning.org.cn/how-to-develop-a-pix2pix-gan-for-image-to-image-translation/

    在这里,您提到‘patch_shape’参数在‘generate_fake_samples’和‘generate_real_samples’中应为 16,在“如何更新模型权重”部分,但您在另一篇文章中将 patch_shape 传递为 1。您能否解释一下两个示例的区别?这将非常有帮助。
    据我所知,Patch GAN 判别器的架构在这两篇文章中是相同的。Patch_shape 为 16 在这里有意义,正如您在此解释的那样,但 patch_shape 为 1 有点令人困惑。

    • Jason Brownlee 2021年3月3日 上午8:08 #

      不客气。

      也许上面我们一起计算 PatchGAN 感受野的示例会对此有所帮助?尝试完成教程的这部分。

  34. Nirisha Manandhar 2021年3月3日 晚上7:36 #

    是的,我确实遵循了那个示例计算,但是在这里,我对‘patch_shape’参数在‘generate_fake_samples’和‘generate_real_samples’中取什么值感到困惑?16×16 的输出形状在这里这篇文章中有意义,但在您之前的这篇(https://machinelearning.org.cn/how-to-develop-a-pix2pix-gan-for-image-to-image-translation/

    您将相同的 patch_shape 参数设置为 1。(在 summarize_performance() 函数中,代码的第一行和第二行)。是不是我错过了什么地方?

  35. Jackarles 2021年3月26日 上午1:26 #

    你好,Jason。
    我审查了几乎所有不同教程中的问题,但没有找到我的答案。
    我想使用大小为 52*52*1 的图像,无法将其调整为 256*256,所以我猜我必须自己更改模型。
    如果我只更改输入图像大小来运行它,我在连接时会遇到一些问题:一边是 (None, 2, 2, 512),另一边是 (None, 1, 1, 512)。我认为我必须删除一些层,所以这是我天真地尝试的方法,但仍然存在类似的错误,直到我只保留了一个编码层和一个解码层。根据较小的输入图像尺寸修改模型,这是正确的方法吗?
    我还必须更改什么?瓶颈处的“512”?

    提前感谢您的回复,尽可能准确!

    • Jason Brownlee 2021年3月26日 上午6:27 #

      是的,您可能(将!)需要自定义层数和大小来实现您想要的输入和输出。

  36. enexs 2021年4月29日 下午6:51 #

    我非常感谢您提供的这些精彩教程。

    我只想纠正一个小的排版错误。

    在定义 U-net 生成器的第一个解码器块时,您说:

    “解码器的第一层不使用批归一化。”

    但是,解码器的第一层使用了批归一化。我认为应该是编码器,而不是解码器,因为编码器的第一层 C64 不使用批归一化。

    谢谢!

  37. Saumya 2021年7月27日上午9:06 #

    嗨,Jason!
    这是一个非常有用的教程。我学习并根据我的需求修改了代码,但在训练模型时,速度非常慢。我有 380 个训练样本和 100 个 epoch,批次大小为 1,这使得迭代次数高达 38000。
    我看了您的图像翻译代码,您训练了大约 10 万次迭代。您的训练时间是多少?也许我犯了什么错误。

    • Jason Brownlee 2021年7月28日上午5:21 #

      我不记得了,抱歉,我想我在 AWS EC2 实例上训练了一夜。

  38. Saumya 2021年7月27日下午12:23 #

    我的 g_loss 太高了,大约 17000。之前我是在 CPU 上运行模型的,但现在我切换到了 GPU,速度快了很多。

    • Jason Brownlee 2021年7月28日上午5:22 #

      损失可能不是 GAN 模型性能的好指标。请查看生成的图像。

  39. Jordan 2021年9月29日上午11:58 #

    嗨,Jason,

    感谢您提供的精彩教程。我从中学到了很多。

    我已经实现了您的代码,并添加了一行代码来保存整个 GAN 模型(在 summarize_performance 中添加 gan_name = ‘GAN_%06d.h5’ % (step+1) 和 gan_model.save(gan_name)),但当我加载模型并继续训练时,判别器的损失很快就降到了 0。如果我不加载模型只进行训练,那么一切正常。如果我们只加载 d 和 g 的权重,我认为 Adam 的衰减会重置回 beta_1 参数的初始值。

    我保存了 GAN 模型,以便它能记住 Adam 衰减的状态,但不确定为什么这会导致模型在加载后停止学习。我可能犯了错误,但找不到哪里。

    您能否稍微扩展一下这个教程,并向我们展示如何特别保存和恢复此模型的训练?我相信这会极大地帮助所有人,因为 Google Colab 和许多人的计算机无法长时间保持开启状态,必须稍后恢复。

    我相信这将非常受大家欢迎。
    谢谢!

    • Adrian Tam
      Adrian Tam 2021年9月30日凌晨1:23 #

      感谢您的建议。我们计划撰写这方面的内容。

      • Jordan 2021年11月6日凌晨1:31 #

        你好 Adrian,

        我发现使用 TensorFlow Checkpoints 可以保存所有超参数的状态,并在实例化后恢复优化器。

        checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer, discriminator_optimizer=discriminator_optimizer, generator=generator, discriminator=discriminator)

        然后

        checkpoint.save(…)

  40. Mostafa 2021年11月4日晚上9:27 #

    嗨,Jason,

    感谢这篇教程。
    我想使用 pix2pix 来去除图像水印。
    我的数据集有 60K 个样本,包含 160 种不同的彩色水印。
    我训练了 pix2pix 大约 40000 步,但没有任何效果,生成器的输出与输入相同,没有任何变化。
    我想知道您是否能帮助我。

    • Adrian Tam
      Adrian Tam 2021年11月7日早上7:50 #

      我不太可能提供详细的帮助。但 pix2pix 是一个 GAN,您应该检查您的编码器和解码器部分是否构建正确。另外,为了验证输入和输出不相同,请验证数值而不是视觉外观。您可能只是训练得不够。

    • Pruthvi 2022年12月2日凌晨5:21 #

      您好,我也在尝试使用 pix2pix gan 来去除水印。我刚开始。您能告诉我是否应该继续使用 pix2pix gan,还是应该更换它?

  41. Vinoj John Hosan 2022年1月25日凌晨4:02 #

    你好 Adrian,

    您详细的解释太棒了。我使用了您的代码结构,但用我自己的方式在 pytorch 中进行了编码。效果非常好。您的教程帮助我取得了出色的成果。
    谢谢你

    • Vinoj John Hosan 2022年1月25日凌晨4:03 #

      不是 Adrian,抱歉有错别字。功劳归于 Jason Brownlee。

  42. YooJC 2022年11月18日凌晨12:15 #

    您的解释非常简单易懂。

    我想运行上面的代码。

    您能告诉我上面的代码是否有效吗?

    谢谢你。

    • James Carmichael 2022年11月18日凌晨6:06 #

      您好 YooJC……感谢您的反馈!请随时执行代码并告知您的发现。

留下回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。