Machine-Learning-Mastery-生成对抗网络教程-一-

149 阅读1小时+

Machine Learning Mastery 生成对抗网络教程(一)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

Pix2Pix 生成对抗网络的温和介绍

原文:machinelearningmastery.com/a-gentle-in…

最后更新于 2019 年 12 月 6 日

图像到图像的转换是给定源图像到目标图像的受控转换。

一个例子可能是黑白照片到彩色照片的转换。

图像到图像的翻译是一个具有挑战性的问题,对于给定的翻译任务或数据集,通常需要专门的模型和损失函数。

Pix2Pix GAN 是一种通用的图像到图像转换方法。它基于条件生成对抗网络,在该网络中,以给定的输入图像为条件生成目标图像。在这种情况下,Pix2Pix GAN 改变损失函数,使得生成的图像在目标域的内容中是可信的,并且是输入图像的可信翻译。

在这篇文章中,你将发现图像到图像转换的 Pix2Pix 条件生成对抗网络。

看完这篇文章,你会知道:

  • 图像到图像的翻译通常需要专门的模型和手工制作的损失函数。
  • Pix2Pix GAN 为图像到图像的翻译提供了通用模型和损失函数。
  • Pix2Pix GAN 在各种各样的图像生成任务中进行了演示,包括将照片从白天翻译成黑夜,将产品草图翻译成照片。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

概观

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

  1. 图像到图像的翻译问题
  2. 用于图像到图像转换的 Pix2Pix GAN
  3. Pix2Pix 建筑细节
  4. Pix2Pix GAN 的应用
  5. 对 Pix2Pix 架构选择的洞察

图像到图像的翻译问题

图像到图像的转换是以特定或受控的方式改变给定图像的问题。

例如,将风景照片从白天转换为黑夜,或将分割图像转换为照片。

与自动语言翻译类似,我们将自动图像到图像转换定义为在给定足够的训练数据的情况下,将场景的一种可能表示翻译成另一种表示的任务。

——条件对抗网络下的图像到图像转换,2016。

这是一个具有挑战性的问题,通常需要为所执行的翻译任务类型开发专门的模型和手工制作的损失函数。

经典的方法使用每像素分类或回归模型,其问题是每个预测的像素独立于它之前预测的像素,并且图像的更广泛的结构可能会丢失。

图像到图像的转换问题通常被表述为每像素分类或回归。这些公式将输出空间视为“非结构化的”,即每个输出像素都被视为有条件地独立于给定输入图像的所有其他像素。

——条件对抗网络下的图像到图像转换,2016。

理想情况下,需要一种通用的技术,这意味着相同的通用模型和损失函数可以用于多个不同的图像到图像转换任务。

用于图像到图像转换的 Pix2Pix GAN

Pix2Pix 是一个为通用图像到图像转换而设计的生成对抗网络模型。

该方法由菲利普·伊索拉(Phillip Isola)等人在 2016 年发表的论文《条件对抗网络下的 T2 图像到图像转换》(T3)中提出,并于 2017 年在 CVPR 的 T4 会议上提出。

GAN 架构是一种训练生成器模型的方法,通常用于生成图像。鉴别器模型被训练为将图像分类为真实的(来自数据集)或虚假的(生成的),生成器被训练为欺骗鉴别器模型。

有条件的 GAN,或称 cGAN,是 GAN 体系结构的扩展,它提供了对生成的图像的控制,例如允许生成给定类的图像。Pix2Pix GAN 是 cGAN 的一种实现,其中图像的生成取决于给定的图像。

正如条件生成元学习数据的生成模型一样,条件生成元也学习条件生成模型。这使得 cGANs 适用于图像到图像的转换任务,在这种任务中,我们对输入图像进行调节并生成相应的输出图像。

——条件对抗网络下的图像到图像转换,2016。

生成器模型以给定的图像作为输入,并生成图像的翻译版本。鉴别器模型被给予一个输入图像和一个真实的或生成的配对图像,并且必须确定配对图像是真实的还是伪造的。最后,生成器模型被训练成既欺骗鉴别器模型又最小化生成图像和预期目标图像之间的损失。

因此,Pix2Pix GAN 必须在由输入图像(翻译前)和输出或目标图像(翻译后)组成的图像数据集上进行训练。

这种通用架构允许针对一系列图像到图像的转换任务来训练 Pix2Pix 模型。

Pix2Pix 建筑细节

Pix2Pix GAN 架构涉及生成器模型、鉴别器模型和模型优化过程的仔细规范。

生成器和鉴别器模型都使用标准的卷积-BatchNormalization-ReLU 层块,这在深度卷积神经网络中很常见。论文附录中提供了具体的层配置。

生成器和鉴别器都使用卷积形式的模块。

——条件对抗网络下的图像到图像转换,2016。

让我们仔细看看这两种模型架构以及用于优化模型权重的损失函数。

网络生成器模型

生成器模型以图像作为输入,与传统的 GAN 模型不同,它不从潜在空间中取一点作为输入。

取而代之的是,随机性的来源来自于在训练期间和进行预测时使用的丢失层。

相反,对于我们的最终模型,我们只以下降的形式提供噪声,在训练和测试时应用于发电机的几个层。

——条件对抗网络下的图像到图像转换,2016。

  • 输入:来自源域的图像
  • 输出:目标域的图像

生成器使用了一个 U-Net 模型架构,而不是通用的编码器-解码器模型。

编码器-解码器生成器体系结构包括将图像作为输入,并在几个层上对其进行下采样,直到瓶颈层,然后在输出具有期望大小的最终图像之前,在几个层上再次对表示进行上采样。

U-Net 模型架构非常相似,它涉及到向下采样到瓶颈,然后再次向上采样到输出图像,但是在编码器和解码器中相同大小的层之间建立了链接或跳过连接,从而绕过了瓶颈。

对于许多图像转换问题,在输入和输出之间共享了大量的低级信息,并且希望将这些信息直接传送到网络上。[……]为了给生成器提供一种方法来绕过像这样的信息瓶颈,我们按照“U-Net”的一般形状添加了跳过连接。

——条件对抗网络下的图像到图像转换,2016。

例如,编码器的第一层具有与解码器的最后一层相同大小的特征映射,并与解码器合并。对编码器中的每一层和解码器的相应层重复这一过程,形成一个 U 形模型。

Depiction of the Encoder-Decoder Generator and U-Net Generator Models

编码器-解码器生成器和 U 网生成器模型的描述。 摘自:条件对抗网络下的图像转图像转换。

帕奇根鉴别器模型

鉴别器模型获取来自源域的图像和来自目标域的图像,并预测来自目标域的图像是源图像的真实版本还是生成版本的可能性。

  • 输入:源域的图像,目标域的图像。
  • 输出:来自目标域的图像是源图像的真实翻译的概率。

鉴别器模型的输入强调了在训练模型时需要一个由成对的源图像和目标图像组成的图像数据集。

与使用深度卷积神经网络对图像进行分类的传统 GAN 模型不同,Pix2Pix 模型使用 PatchGAN。这是一个深度卷积神经网络,旨在将输入图像的斑块分类为真实或虚假,而不是整个图像。

……我们设计了一个鉴别器架构——我们称之为 PatchGAN——它只在补丁的规模上惩罚结构。这个鉴别器试图分类图像中的每个 NxN 补丁是真的还是假的。我们在图像上运行这个鉴别器卷积,平均所有响应,以提供 d 的最终输出。

——条件对抗网络下的图像到图像转换,2016。

PatchGAN 鉴别器模型被实现为深度卷积神经网络,但是层数被配置为使得网络的每个输出的有效感受野映射到输入图像中的特定大小。网络的输出是真实/虚假预测的单个特征图,可以对其进行平均以给出单个分数。

发现 70×70 的补丁大小在一系列图像到图像的翻译任务中是有效的。

复合对抗性损失和 L1 损失

鉴别器模型以独立的方式训练,与传统的 GAN 模型相同,最小化了识别真实和虚假图像的负对数可能性,尽管以源图像为条件。

与发生器相比,鉴别器的训练太快,因此鉴别器损耗减半,以便减慢训练过程。

  • 鉴别器损耗= 0.5 *鉴别器损耗

使用鉴别器模型的对抗损失和源图像和预期目标图像的生成平移之间的 L1 或平均绝对像素差来训练生成器模型。

对抗性损失和 L1 损失合并成一个复合损失函数,用于更新发电机模型。L2 损失也进行了评估,发现导致图像模糊。

鉴别器的工作保持不变,但是生成器的任务不仅是愚弄鉴别器,而且是在 L2 意义上接近地面真实输出。我们也探索了这个选项,使用 L1 距离而不是 L2,因为 L1 鼓励减少模糊

——条件对抗网络下的图像到图像转换,2016。

对抗性损失影响发生器模型是否能够输出在目标域中似是而非的图像,而 L1 损失使发生器模型正则化以输出作为源图像的似是而非的翻译的图像。因此,L1 损失与对抗损失的组合由新的超参数λ控制,该超参数λ被设置为 10,例如在训练期间给予发生器的 L1 损失的重要性是对抗损失的 10 倍。

  • 发电机损耗=对抗性损耗+λ* L1 损耗

Pix2Pix GAN 的应用

Pix2Pix GAN 在一系列有趣的图像到图像转换任务中进行了演示。

例如,论文列出了 9 个应用;它们是:

  • 语义标签照片,在 Cityscapes 数据集上训练。
  • 建筑标签->照片,在立面上训练。
  • 地图航拍照片,基于谷歌地图刮取的数据进行训练。
  • 黑白->彩色照片。
  • 边缘->照片。
  • 素描->照片。
  • 白天->晚上拍照。
  • 热感->彩色照片。
  • 缺少像素的照片->修复照片,在巴黎街景上训练。

在这一节中,我们将回顾一些论文中的例子。

照片的语义标签

下面的例子演示了语义标签图像到街景照片的转换。

Pix2Pix GAN Translation of Semantic Images to Photographs of a Cityscape

将语义图像转换成城市景观照片。 摘自:条件对抗网络下的图像转图像转换。

另一个例子是展示照片的建筑立面的语义标记图像。

Pix2Pix GAN Translation of Semantic Images to Photographs of Building Facades

Pix2Pix GAN 将语义图像转换成建筑物正面的照片。 摘自:条件对抗网络下的图像转图像转换。

白天到晚上的照片

下面的例子演示了白天到夜间照片的转换。

Pix2Pix GAN Translation of Daytime Photographs to Nighttime

Pix2Pix GAN 白天照片到夜间的翻译。 摘自:条件对抗网络下的图像转图像转换。

要拍摄的产品草图

下面的例子演示了如何将包的产品草图翻译成照片。

Pix2Pix GAN Translation of Product Sketches of Bags to Photographs

Pix2Pix GAN 将包的产品草图翻译成照片。 摘自:条件对抗网络下的图像转图像转换。

类似的例子也被用来把鞋的草图翻译成照片。

Pix2Pix GAN Translation of Product Sketches of Shoes to Photographs

Pix2Pix GAN 将鞋子的产品草图翻译成照片。 摘自:条件对抗网络下的图像转图像转换。

照片修复

下面的例子展示了修复巴黎街景的照片。

Pix2Pix GAN Inpainting Photographs of Paris

Pix2Pix GAN 修复巴黎照片。 摘自:条件对抗网络下的图像转图像转换。

热像仪到彩色照片

下面的例子演示了热图像到街景彩色照片的转换。

Pix2Pix GAN Translation of Thermal Images to Color Photographs

热图像到彩色照片的 Pix2Pix GAN 转换。 摘自:条件对抗网络下的图像转图像转换。

对 Pix2Pix 架构选择的洞察

作者探索和分析了不同模型配置和损失函数对图像质量的影响,支持架构选择。

这些实验的发现也许揭示了为什么 Pix2Pix 方法在广泛的图像转换任务中是有效的。

损失函数分析

进行实验以比较用于训练发电机模型的不同损失函数。

其中包括仅使用 L1 损失,仅使用有条件的对抗性损失,仅使用无条件的对抗性损失,以及 L1 与每一种对抗性损失的组合。

结果很有趣,显示了 L1 和条件性对抗损失可以单独产生合理的图像,尽管 L1 图像是模糊的,而 cGAN 图像引入了伪影。两者的结合给出了最清晰的结果。

只有 L1 导致合理但模糊的结果。仅 cGAN】就给出了更清晰的结果,但在某些应用程序中引入了视觉伪像。将这两个项相加(λ= 100)可以减少这些伪像。

——条件对抗网络下的图像到图像转换,2016。

Generated Images Using L1, Conditional Adversarial (cGAN) and Composite Loss Functions

使用 L1、条件对抗和复合损失函数生成的图像。 摘自:条件对抗网络下的图像转图像转换。

发电机模型分析

将 U-Net 生成器模型体系结构与更常见的编码器-解码器生成器模型体系结构进行了比较。

将这两种方法与仅 L1 损失和 L1 +条件对抗损失进行了比较,表明编码器-解码器能够在两种情况下生成图像,但是当使用 U-Net 架构时,图像要清晰得多。

在我们的实验中,编码器-解码器无法学习生成逼真的图像。U-Net 的优势似乎并不局限于条件 GANs:当 U-Net 和编码器-解码器都以 L1 损失进行训练时,U-Net 再次获得了优越的结果。

——条件对抗网络下的图像到图像转换,2016。

Generated Images Using the Encoder-Decoder and U-Net Generator Models Under Different Loss

在不同损失下使用编码器-解码器和 U-Net 生成器模型生成的图像。 摘自:条件对抗网络下的图像转图像转换。

鉴别器模型分析

进行实验来比较具有不同大小的有效感受野的 PatchGAN 鉴别器。

从 1×1 感受野(PixelGAN)到全尺寸的 286×286 或 ImageGAN,以及更小的 16×16 和 70×70 patch gan,对该模型的不同版本进行了测试。

感受野越大,网络越深。这意味着 1×1 PixelGAN 是最浅的模型,286×286 ImageGAN 是最深的模型。

结果表明,非常小的感受野可以生成有效的图像,尽管全尺寸的 ImageGAN 提供了更清晰的结果,但更难训练。使用较小的 70×70 感受野提供了表现(模型深度)和图像质量的良好折衷。

70×70 的 PatchGAN […]表现稍好。超出此范围,扩展到完整的 286×286 ImageGAN,似乎并不能提高视觉质量[……]这可能是因为 ImageGAN 比 70 × 70 PatchGAN 具有更多的参数和更大的深度,并且可能更难训练。

——条件对抗网络下的图像到图像转换,2016。

Generated Images Using PatchGANs with Different Sized Receptive Fields

使用具有不同大小感受野的补丁生成图像。 摘自:条件对抗网络下的图像转图像转换。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

摘要

在这篇文章中,你发现了图像到图像转换的 Pix2Pix 条件生成对抗网络。

具体来说,您了解到:

  • 图像到图像的翻译通常需要专门的模型和手工制作的损失函数。
  • Pix2Pix GAN 为图像到图像的翻译提供了通用模型和损失函数。
  • Pix2Pix GAN 在各种各样的图像生成任务中进行了演示,包括将照片从白天翻译成黑夜,将产品草图翻译成照片。

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

大型生成对抗网络 BigGAN 的温和介绍

原文:machinelearningmastery.com/a-gentle-in…

生成对抗网络,或 GANs ,可能是最有效的图像合成生成模型。

然而,它们通常局限于生成小图像,并且训练过程仍然脆弱,依赖于特定的增强和超参数以获得良好的结果。

BigGAN 是一种方法,它池化了一套最近在训练类条件图像和按比例增加批量大小和模型参数数量方面的最佳实践。结果是常规生成高分辨率(大)和高质量(高保真)图像。

在这篇文章中,你将发现用于放大类条件图像合成的 BigGAN 模型。

看完这篇文章,你会知道:

  • 图像大小和训练脆性仍然是 GANs 的大问题。
  • 放大模型尺寸和批次尺寸可以产生更大更高质量的图像。
  • 扩大 GANs 规模所需的特定模型架构和培训配置。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

A Gentle Introduction to the BigGAN

雷伊·佩雷佐索拍摄的《大人物简介》图片,版权所有。

概观

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

  1. GAN 训练的脆性
  2. 通过扩大规模开发更好的 GANs
  3. 如何使用 BigGAN 扩展 GANs
  4. BigGAN 生成的图像示例

GAN 训练的脆性

生成对抗网络,简称 GANs,能够生成高质量的合成图像。

然而,生成的图像的尺寸保持相对较小,例如 64×64 或 128×128 像素。

此外,尽管进行了大量研究并提出了改进建议,模型训练过程仍然脆弱。

没有辅助稳定技术,这种训练过程是出了名的脆弱,需要微调超参数和架构选择才能工作。

——高保真自然图像合成的大规模 GAN 训练,2018。

对训练过程的大部分改进都集中在目标函数的改变或训练过程中对鉴别器模型的约束上。

许多最近的研究相应地集中在对香草甘程序的修改上,以赋予稳定性,利用越来越多的经验和理论见解。一个工作重点是改变目标函数[……]以鼓励趋同。另一条线侧重于通过梯度惩罚[…]或归一化[…]来约束 D,以抵消无界损失函数的使用,并确保 D 在任何地方都向 g 提供梯度。

——高保真自然图像合成的大规模 GAN 训练,2018。

最近,工作集中在有效应用 GAN 生成高质量和更大的图像。

一种方法是尝试扩大已经运行良好的 GAN 模型。

通过扩大规模开发更好的 GANs

BigGAN 是 GAN 架构的一个实现,旨在利用已报道的更普遍的最佳工作方式。

Andrew Brock 等人在他们 2018 年发表的题为“用于高保真自然图像合成的大规模 GAN 训练”的论文中进行了描述,并在 ICLR 2019 会议上进行了介绍。

具体来说,BigGAN 是为类条件图像生成而设计的。即,使用来自潜在空间的点和图像类别信息作为输入来生成图像。用于训练类条件 GANs 的示例数据集包括具有几十个、几百个或几千个图像类的 CIFARImageNet 图像类别数据集。

顾名思义,BigGAN 专注于放大 GAN 模型。

这包括具有以下特点的 GAN 型号:

  • 更多模型参数(例如,更多要素图)。
  • 更大的批量
  • 建筑变化

我们证明,与现有技术相比,GANs 从扩展中受益匪浅,训练模型的参数数量是现有技术的 2 到 4 倍,批处理大小是现有技术的 8 倍。我们引入了两个简单、通用的架构更改来提高可扩展性,并修改正则化方案来改善条件,从而显著提高表现。

——高保真自然图像合成的大规模 GAN 训练,2018。

BigGAN 架构还引入了在图像生成期间使用的“截断技巧”,从而提高了图像质量,并引入了相应的正则化技术来更好地支持这一技巧。

结果是一种能够生成更大和更高质量图像的方法,例如 256×256 和 512×512 图像。

当在 ImageNet 上以 128×128 的分辨率进行训练时,我们的模型(BigGANs)提高了最先进的水平……我们还成功地在 ImageNet 上以 256×256 和 512×512 的分辨率训练了 BigGANs…

——高保真自然图像合成的大规模 GAN 训练,2018。

如何使用 BigGAN 扩展 GANs

BigGAN 模型的贡献在于模型和训练过程的设计决策。

这些设计决策不仅对重新实现 BigGAN 很重要,而且对深入了解配置选项也很重要,这些选项可能对更广泛的 GANs 有益。

BigGAN 模型的重点是增加模型参数的数量和批量,然后配置模型和训练过程以达到最佳效果。

在本节中,我们将回顾 BigGAN 中的具体设计决策。

1.自关注模块和铰链损耗

该模型的基础是张寒等人在 2018 年的论文《倾斜的”自我注意生成对抗网络中描述的自我注意 GAN,简称 SAGAN 这包括引入应用于特征图的注意力图,允许生成器和鉴别器模型关注图像的不同部分。

这包括在深度卷积模型架构中添加一个注意力模块。

Summary of the Self-Attention Module Used in the Self-Attention GAN

自我关注 GAN 中使用的自我关注模块概述。 摘自:自我注意生成对抗网络。

此外,模型通过铰链损失进行训练,常用于训练支持向量机。

在 SAGAN 中,所提出的注意模块被应用于发生器和鉴别器,它们通过最小化对抗性损失的铰链版本以交替的方式被训练

——自我注意生成对抗网络,2018。

BigGAN 使用的模型架构带有来自 SAGAN 的注意力模块,并通过铰链损失进行训练。

标题为架构细节的论文附录 B 提供了生成器和鉴别器模型中使用的模块及其配置的摘要。有两个版本的模型分别描述了 BigGAN 和 BigGAN-deep,后者涉及更深的 resnet 模块,进而获得更好的结果。

2.类条件信息

类信息通过类条件批处理规范化提供给生成器模型。

文森特·杜穆林等人在 2016 年的论文《T2:艺术风格的学术表现》中对此进行了描述在本文中,该技术被称为“T4”条件实例规范化,其涉及基于来自给定风格的图像的统计来规范化激活,或者在 BigGAN 的情况下,给定类别的图像。

我们称这种方法为条件实例规范化。该过程的目标是将层的激活 x 转换为特定于绘画风格 s 的规范化激活 z。

——艺术风格的学术表达,2016 年。

类别信息通过投影提供给鉴别器。

这是由takentu Miyato等人在他们 2018 年的论文《生成对抗网络的谱归一化中描述的这包括使用类值的整数嵌入,该类值被连接到网络的中间层。

条件 GANs 的鉴别器。为了便于计算,我们将整数标签 y 嵌入到{0,.。。,1000)转换为 128 维,然后将向量连接到中间层的输出。

——生成对抗网络的谱归一化,2018。

为了减少权重的数量,使用了共享嵌入,而不是每个类标签使用一个类嵌入。

我们选择使用共享嵌入,而不是每个嵌入都有一个单独的层,共享嵌入是线性投影到每个层的增益和偏差。这降低了计算和内存成本,并将训练速度(达到给定表现所需的迭代次数)提高了 37%。

——高保真自然图像合成的大规模 GAN 训练,2018。

3.光谱归一化

使用频谱归一化来归一化生成器的权重。

takentu Miyato等人在他们 2018 年发表的题为“生成对抗网络的光谱归一化的论文中描述了用于 GANs 的光谱归一化具体来说,它包括归一化权重矩阵的谱范数。

我们的谱归一化将权重矩阵 W 的谱范数归一化,使得它满足李普希茨约束σ(W)= 1:

——生成对抗网络的谱归一化,2018。

高效的实现要求在小批量随机梯度下降过程中改变权重更新,如光谱归一化论文附录一所述。

Algorithm for SGD with Spectral Normalization

带谱归一化的 SGD 算法 摘自:生成对抗网络的谱归一化

4.更新鉴别器多于生成器

在 GAN 训练算法中,通常先更新鉴别器模型,再更新生成器模型。

BigGAN 对此稍作修改,并在每次训练迭代中更新生成器模型之前更新鉴别器模型两次。

5.模型权重的移动平均值

基于生成的图像评估生成器模型。

在生成用于评估的图像之前,使用移动平均值在先前的训练迭代中平均模型权重。

Tero Karras 等人在 2017 年发表的题为“T2”的论文中描述并使用了发电机评估的加权移动平均模型,以提高质量、稳定性和变化性

……为了在训练期间可视化任意给定点的生成器输出,我们使用衰减为 0.999 的生成器权重的指数运行平均值。

——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。

6.正交权重初始化

使用正交初始化初始化模型权重。

安德鲁·萨克斯(Andrew Saxe)等人在 2013 年发表的题为“深度线性神经网络中学习的非线性动力学的 T2 精确解”的论文中对此进行了描述这包括将权重设置为随机正交矩阵。

…每一层中的初始权重为随机正交矩阵(满足 w^t w = I)…

——深度线性神经网络中学习的非线性动力学的精确解,2013。

注意,Keras 直接支持正交权重初始化

7.更大的批量

测试和评估了非常大的批次尺寸。

这包括批量大小为 256、512、1024 和 2,048 的图像。

批量越大,图像质量通常越好,批量为 2,048 张时图像质量最好。

…只需将批量增加 8 倍,就可以将最先进的信息系统提高 46%。

——高保真自然图像合成的大规模 GAN 训练,2018。

直觉是批量越大,提供的“模式越多”,反过来也为更新模型提供了更好的梯度信息。

我们推测这是每批覆盖更多模式的结果,为两个网络提供了更好的梯度。

——高保真自然图像合成的大规模 GAN 训练,2018。

8.更多模型参数

模型参数的数量也大幅增加。

这是通过将每个层中的通道或要素图(过滤器)的数量增加一倍来实现的。

然后,我们将每一层的宽度(通道数)增加 50%,大约是两个模型中参数数量的两倍。这导致信息系统进一步提高了 21%,我们认为这是由于相对于数据集的复杂性,模型的容量增加了。

——高保真自然图像合成的大规模 GAN 训练,2018。

9.跳过 z 连接

生成器模型中添加了跳过连接,将输入潜在点直接连接到网络深处的特定层。

这些被称为 skip-z 连接,其中 z 指的是输入潜在向量。

接下来,我们将噪声向量 z 中的直接跳过连接(skip-z)添加到 G 的多个层,而不仅仅是初始层。这种设计背后的直觉是允许 G 使用潜在空间来直接影响不同分辨率和层次的特征。[……]Skip-z 提供了大约 4%的适度表现提升,并将训练速度进一步提高了 18%。

——高保真自然图像合成的大规模 GAN 训练,2018。

10.截断技巧

截断技巧包括在训练期间对生成器的潜在空间使用不同于推理或图像合成期间的分布。

训练时使用高斯分布,推理时使用截断高斯。这被称为“截断绝招

我们称之为截断技巧:通过对幅度高于所选阈值的值进行重新采样来截断 z 向量,以减少总体样本种类为代价来提高单个样本的质量。

——高保真自然图像合成的大规模 GAN 训练,2018。

截断技巧提供了图像质量或保真度与图像多样性之间的权衡。采样范围越窄,质量越好,而采样范围越大,采样图像越多样。

对于给定的样品,这种技术允许在样品质量和品种之间进行精细的事后选择

——高保真自然图像合成的大规模 GAN 训练,2018。

11.正交正则化

并非所有的模型都能很好地响应截断技巧。

当使用截断技巧时,一些更深层次的模型会提供饱和伪影。

为了更好地鼓励更广泛的模型很好地使用截断技巧,使用了正交正则化。

这是由安德鲁·布洛克等人在 2016 年发表的题为“带有内省对抗网络的神经照片编辑的论文中介绍的

这与正交权重初始化相关,并且引入了权重正则化项来鼓励权重保持其正交属性。

正交性是 ConvNet 滤波器的一个理想品质,部分原因是与正交矩阵相乘会保持原始矩阵的范数不变。[……]我们提出了一种简单的权重正则化技术——正交正则化,通过将权重推向最近的正交流形来鼓励权重正交。

——内省对抗网络神经照片编辑,2016。

BigGAN 生成的图像示例

BigGAN 能够生成大而高质量的图像。

在这一节中,我们将回顾论文中介绍的几个示例。

下面是一些由 BigGAN 生成的高质量图像的例子。

Examples of High-Quality Class-Conditional Images Generated by BigGAN

BigGAN 生成的高质量类条件图像示例。 取自:高保真自然图像合成的大规模 GAN 训练。

下面是 BigGAN 生成的大而高质量的图像示例。

Examples of Large High-Quality Class-Conditional Images Generated by BigGAN

BigGAN 生成的大型高质量类条件图像示例。 取自:高保真自然图像合成的大规模 GAN 训练。

训练 BigGAN 发电机时描述的问题之一是“类泄漏”的想法,这是一种新型的故障模式。

下面是一个部分训练的比格的漏课例子,显示了一个网球和一只狗之间的交叉。

Examples of Class Leakage in an Image Generated by Partially Trained BigGAN

部分训练的 BigGAN 生成的图像中的类泄漏示例。 取自:高保真自然图像合成的大规模 GAN 训练。

下面是一些由 BigGAN 以 256×256 分辨率生成的附加图像。

Examples of Large High-Quality 256x256 Class-Conditional Images Generated by BigGAN

BigGAN 生成的大型高质量 256×256 类条件图像示例。 取自:高保真自然图像合成的大规模 GAN 训练。

下面是一些由 BigGAN 以 512×512 分辨率生成的更多图像。

Examples of Large High-Quality 512x512 Class-Conditional Images Generated by BigGAN

BigGAN 生成的大型高质量 512×512 类条件图像示例。 取自:高保真自然图像合成的大规模 GAN 训练。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

密码

文章

摘要

在这篇文章中,你发现了放大类条件图像合成的 BigGAN 模型。

具体来说,您了解到:

  • 图像大小和训练脆性仍然是 GANs 的大问题。
  • 放大模型尺寸和批次尺寸可以产生更大更高质量的图像。
  • 扩大 GANs 规模所需的特定模型架构和培训配置。

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

9 本关于生成对抗网络的书

原文:machinelearningmastery.com/books-on-ge…

最后更新于 2019 年 8 月 21 日

生成对抗网络,简称 GANs,是伊恩·古德费勒等人在 2014 年发表的题为“生成对抗网络”的论文中首次描述的

从那以后,GANs 受到了很多关注,因为它们可能是生成大型高质量合成图像的最有效技术之一。

因此,已经有很多关于 GANs 的书,主要集中在如何在实践中开发和使用模型。

在这篇文章中,你会发现关于生成对抗网络的书籍。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

GAN 书籍

大部分的书都是在 Packt 出版公司的指导下编写和发行的。

几乎所有的书都有同样的问题:即质量普遍较低,总结了第三方代码在 GitHub 上的使用情况,几乎没有原创内容。这尤其适用于来自 Packt 的书籍。

尽管如此,了解一下有哪些书以及涵盖的主题还是很有用的。这既有助于选择自学书籍,也有助于了解开始使用 GANs 时您可能想要探索的主题类型。

我们将回顾以下七本书:

  1. GANs 正在运行
  2. 生成性深度学习
  3. 用 Keras 高级深度学习
  4. 学习生成对抗网络
  5. 生成对抗网络项目
  6. 生成对抗网络秘籍
  7. 与 Keras 的动手生成对抗网络

此外,我们还将复习两本热门深度学习书籍的 GAN 部分。

  1. 深度学习
  2. Python 深度学习

如果我错过了一本关于 GANs 的书,请在下面的评论中告诉我。

这些书似乎大多涵盖了相同的 GAN 架构,例如:

  • 标准 : GAN、DCGAN。
  • 条件 : cGAN、SS-GAN、InfoGAN、ACGAN。
  • 损失 : WGAN,WGAN-GP,LSGAN。
  • 图像转换 : Pix2Pix,CycleGAN。
  • 高级 GANs : BigGAN,PG-GAN,StyleGAN。
  • 其他 : StackGAN、3DGAN、BEGAN、SRGAN、DiscoGAN、SEGAN。

让我们仔细看看每本书涵盖的主题。

1.行动中的甘斯

标题:行动中的甘斯:生成对抗网络的深度学习

雅库布·朗格弗拉基米尔·博克撰写,2019 年出版。

这本书温和地介绍了使用 Keras 深度学习库的 GANs。

GANs in Action

行动中的甘斯

目录

  • 第一章:GANs 简介
  • 第二章:自动编码器作为 GANs 的路径
  • 第三章:你的第一个 GAN:生成手写数字
  • 第四章:深度卷积神经网络
  • 第五章:培训和常见挑战:为成功而组织
  • 第六章:与 GANs 一起进步
  • 第七章:半监督 GAN
  • 第八章:条件 GAN
  • 第九章:循环干
  • 第十章:对抗性例子
  • 第十一章:GANs 的实际应用
  • 第十二章:展望未来

2.生成性深度学习

标题 : 生成性深度学习:教机器绘画、写作、作曲和演奏

大卫·福斯特执笔,2019 年出版。

Generative Deep Learning

生成性深度学习

这本书侧重于更一般的问题,生成建模与深度学习,允许变分自动编码器进行讨论。它确实涵盖了一系列的 GAN 模型,但也包括使用 LSTMs 的语言建模。

目录

  • 第一部分:生成性深度学习导论
    • 第一章。生成建模
    • 第二章。深度学习
    • 第三章。可变自动编码器
    • 第四章。生成对抗网络
  • 第二部分:教机器绘画、写作、作曲和演奏
    • 第五章。颜料
    • 第六章。写
    • 第七章。构成
    • 第八章。玩
    • 第九章。生成建模的未来

3.通过 Keras 进行高级深度学习

标题:利用 Keras 进行高级深度学习:应用深度学习技术、自动编码器、GANs、变分自动编码器、深度强化学习、策略梯度等

作者罗威尔·阿蒂恩萨,2018 年出版。

这本书是关于更一般的主题的高级深度学习与 Keras,允许覆盖自动编码器,变分自动编码器,和深度强化学习。然而,这本书有四章是关于 GAN 的,我认为这是一本 GAN 的书。

Advanced Deep Learning with Keras

通过 Keras 进行高级深度学习

目录

  • 第一章:用 Kera 介绍高级深度学习
  • 第二章:深度神经网络
  • 第三章:自动编码器
  • 第四章:生成对抗网络
  • 第五章:改进的 GANs
  • 第六章:解开代理问题
  • 第七章:跨域 GANs
  • 第八章:可变自动编码器
  • 第九章:深度强化学习
  • 第十章:政策梯度方法

4.学习生成对抗网络

标题:学习生成对抗网络:下一代深度学习简化版。

Kuntal Ganguly 执笔,2017 年出版。

这本书对 GANs 做了非常简单的介绍。这本书可能已经被 Packt 删除或取消出版,并被视频课程取代。

Learning Generative Adversarial Networks

学习生成对抗网络

目录

  • 第一章:深度学习介绍
  • 第二章:无监督学习
  • 第三章:跨不同领域传递图像风格
  • 第四章:从你的文本中构建真实的图像
  • 第五章:使用各种生成模型生成图像
  • 第六章:将机器学习带入生产

5.生成对抗网络项目

标题:生成对抗网络项目:使用 TensorFlow 和 Keras 构建下一代生成模型。

Kailash Ahirwar 执笔,2019 年出版。

这本书用 Keras 中的代码示例总结了一系列 GANs。

Generative Adversarial Networks Projects

生成对抗网络项目

目录

  • 第一章:生成对抗网络导论
  • 第二章:三维甘-使用甘生成形状
  • 第三章:基于条件遗传神经网络的人脸衰老
  • 第四章:使用 DCGANs 生成动漫角色
  • 第五章:使用 SRGANs 生成照片级真实感图像
  • 第六章:层叠–文本到照片的真实感图像合成
  • 第七章:循环干——把绘画变成照片
  • 第八章:条件 GAN——使用条件性对抗网络的图像到图像转换
  • 第九章:预测 GANs 的未来

6.生成对抗网络秘籍

标题:生成对抗网络秘籍:100 多种使用 Python、TensorFlow 和 Keras 构建生成模型的秘籍

作者乔希·卡琳,2018 年出版。

Generative Adversarial Networks Cookbook

生成对抗网络秘籍

目录

  • 第一章:什么是生成对抗网络
  • 第二章:数据优先、轻松的环境和数据准备
  • 第三章:我在百行以下的第一个 GAN
  • 第四章:梦想用 DCGAN 建造新的户外建筑
  • 第五章:Pix2Pix 图像到图像的翻译
  • 第六章:用 CycleGAN 风格传递你的形象
  • 第七章:用模拟图像创建逼真的眼球
  • 第八章:使用 GANs 从图像到三维模型

7.用 Keras 实践生成对抗网络

标题:用 Keras 实践生成对抗网络:实现下一代生成对抗网络的指南

作者拉斐尔·瓦莱,2019 年出版。

这可能是最好的 Packt 出版的书籍之一,因为代码看起来质量更好,覆盖了更广泛的 GANs。

Hands-On Generative Adversarial Networks with Keras

用 Keras 实践生成对抗网络

目录

  • 第一部分:简介和环境设置
    • 第一章:深度学习基础和环境设置
    • 第二章:生成模型介绍
  • 第二部分:培训全球网络
    • 第三章:培训甘斯
    • 第四章:评估你的第一个 GAN
    • 第五章:提高你的第一个 GAN
  • 第三部分:GANS 在计算机视觉、自然语言处理和音频中的应用
    • 第六章:用 GANs 合成和操作图像
    • 第七章:肝的渐进式增长
    • 第八章:使用 GANs 生成离散序列
    • 第九章:用 GANs 进行文本到图像的合成
    • 第十章:使用 GANs 进行语音增强
    • 第十一章:龙舌兰酒——鉴别 GAN 样品
    • 第十二章:GANs 的下一步是什么

其他书中的甘斯

关于深度学习的其他现代书籍中也涉及到了 GANs 的主题。

下面列出了两个重要的例子。

8.深度学习

伊恩·古德费勒(Ian Goodfellow)等人在 2016 年出版的名为《深度学习》的教科书中描述了 GANs,具体如下:

  • 第二十章:深度生成模型。

第 20.10.4 节标题为“生成对抗网络”提供了在撰写本文时对 GANs 的简短介绍,即在原始论文发表两年后。

如果能看到古德费勒在未来某个时候就这个话题写一本专门的教科书,那就太好了。

Deep Learning

深度学习

9.使用 Python 进行深度学习

Francois Chollet 在 2017 年出版的名为《用 Python 进行深度学习》的书中也谈到了 GANs,具体来说:

  • 第八章:生成性深度学习。

在标题为“生成对抗网络简介”的第 8.5 节中,介绍了 GANs 的主题,并介绍了为 CIFAR-10 数据集中的一个图像类(青蛙)开发 GAN 的工作示例。这里提供了源代码:

Deep Learning with Python

使用 Python 进行深度学习

摘要

在这篇文章中,你发现了一套关于生成对抗网络的书籍。

你读过列出的书吗? 在下面的评论中让我知道你对它的看法。

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

如何用 Keras 开发用于图像到图像转换的 CycleGAN

原文:machinelearningmastery.com/cyclegan-tu…

最后更新于 2020 年 9 月 1 日

循环生成对抗网络是一种训练深度卷积神经网络的方法,用于图像到图像的翻译任务。

与其他用于图像转换的 GAN 模型不同,CycleGAN 不需要成对图像的数据集。例如,如果我们对将橙子的照片转换成苹果感兴趣,我们不需要手动转换成苹果的橙子训练数据集。这允许在训练数据集可能不存在的问题上开发翻译模型,例如将绘画翻译成照片。

在本教程中,您将发现如何开发一个 CycleGAN 模型来将马的照片翻译成斑马,然后再翻译回来。

完成本教程后,您将知道:

  • 如何加载和准备马到斑马的图像转换数据集进行建模。
  • 如何训练一对 CycleGAN 发电机模型,将马翻译成斑马,斑马翻译成马。
  • 如何加载保存的 CycleGAN 模型,并使用它们来翻译照片。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

How to Develop a CycleGAN for Image-to-Image Translation with Keras

如何使用 Keras 开发用于图像到图像转换的 cycle gana . Munar摄,保留部分权利。

教程概述

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

  1. 什么是循环干?
  2. 如何为斑马数据集准备马
  3. 如何开发将马翻译成斑马的自行车
  4. 如何使用循环生成器执行图像转换

什么是循环干?

朱俊彦等人在 2017 年的论文《使用循环一致对抗网络的不成对图像到图像的翻译》中描述了循环根模型

CycleGAN 模型的好处是可以在没有成对例子的情况下进行训练。也就是说,为了训练模型,它不需要翻译前后的照片示例,例如白天和晚上同一城市景观的照片。相反,该模型能够使用来自每个领域的照片集合,并提取和利用集合中图像的潜在风格,以便执行翻译。

模型架构由两个生成器模型组成:一个生成器(生成器-A)用于为第一个域(域-A)生成图像,第二个生成器(生成器-B)用于为第二个域(域-B)生成图像。

  • 生成器-A ->域-A
  • 生成器-B ->域-B

生成器模型执行图像转换,这意味着图像生成过程取决于输入图像,特别是来自另一个域的图像。生成器-A 从域-B 获取图像作为输入,生成器-B 从域-A 获取图像作为输入。

  • 域-B ->生成器-A ->域-A
  • 域-A ->生成器-B ->域-B

每个生成器都有相应的鉴别器模型。第一个鉴别器模型(鉴别器-A)从域-A 获取真实图像,从生成器-A 获取生成的图像,并预测它们是真实的还是伪造的。第二个鉴别器模型(Discriminator-B)从 Domain-B 获取真实图像,从 Generator-B 获取生成的图像,并预测它们是真实的还是伪造的。

  • 域-A ->鉴别器-A->[真/假]
  • 域-B ->生成器-A ->鉴别器-A->[真/假]
  • 域-B ->鉴别器-B->[真/假]
  • 域-A ->生成器-B ->鉴别器-B->[真/假]

鉴别器和生成器模型是在一个对抗性的零和过程中训练的,就像正常的 GAN 模型一样。生成器学会更好地欺骗鉴别器,鉴别器学会更好地检测假图像。模型一起在训练过程中找到平衡。

此外,生成器模型被正则化以不仅在目标域中创建新图像,而且从源域翻译输入图像的更多重构版本。这是通过将生成的图像用作相应生成器模型的输入并将输出图像与原始图像进行比较来实现的。将图像通过两个发生器称为一个循环。每对生成器模型一起被训练以更好地再现原始源图像,这被称为周期一致性。

  • 域-B ->生成器-A ->域-A ->生成器-B ->域-B
  • 域-A ->生成器-B ->域-B ->生成器-A ->域-A

该架构还有一个元素,称为身份映射。这是发生器被提供有作为来自目标域的输入的图像的地方,并且期望生成相同的图像而不改变。对体系结构的这种添加是可选的,尽管会导致输入图像的颜色轮廓更好地匹配。

  • 领域-A ->生成器-A ->领域-A
  • 域-B ->生成器-B ->域-B

既然我们已经熟悉了模型架构,我们就可以依次仔细看看每个模型,以及它们是如何实现的。

论文很好地描述了模型和训练过程,尽管官方火炬实现被用作每个模型和训练过程的最终描述,并为下面描述的模型实现提供了基础。

如何为斑马数据集准备马

报纸上令人印象深刻的骑自行车的例子之一是把马的照片变成斑马,反之,斑马变成马。

该论文的作者称之为“对象变形的问题,苹果和橘子的照片也证明了这一点。

在本教程中,我们将从零开始开发一个 CycleGAN,用于从马到斑马的图像到图像的转换(或对象变形),反之亦然。

我们将这个数据集称为“马 2 斑马”。该数据集的 zip 文件约为 111 兆字节,可从 CycleGAN 网页下载:

将数据集下载到当前工作目录中。

您将看到以下目录结构:

horse2zebra
├── testA
├── testB
├── trainA
└── trainB

A ”类别指的是马, B 类别指的是斑马,数据集由列车和测试元素组成。我们将加载所有照片,并将它们用作训练数据集。

照片为正方形,形状为 256×256,文件名如“ n02381460_2.jpg ”。

下面的示例将从火车和测试文件夹中加载所有照片,并为 A 类创建一组图像,为 b 类创建另一组图像

然后,两个数组都以压缩的 NumPy 数组格式保存到一个新文件中。

# example of preparing the horses and zebra dataset
from os import listdir
from numpy import asarray
from numpy import vstack
from keras.preprocessing.image import img_to_array
from keras.preprocessing.image import load_img
from numpy import savez_compressed

# load all images in a directory into memory
def load_images(path, size=(256,256)):
	data_list = list()
	# enumerate filenames in directory, assume all are images
	for filename in listdir(path):
		# load and resize the image
		pixels = load_img(path + filename, target_size=size)
		# convert to numpy array
		pixels = img_to_array(pixels)
		# store
		data_list.append(pixels)
	return asarray(data_list)

# dataset path
path = 'horse2zebra/'
# load dataset A
dataA1 = load_images(path + 'trainA/')
dataAB = load_images(path + 'testA/')
dataA = vstack((dataA1, dataAB))
print('Loaded dataA: ', dataA.shape)
# load dataset B
dataB1 = load_images(path + 'trainB/')
dataB2 = load_images(path + 'testB/')
dataB = vstack((dataB1, dataB2))
print('Loaded dataB: ', dataB.shape)
# save as compressed numpy array
filename = 'horse2zebra_256.npz'
savez_compressed(filename, dataA, dataB)
print('Saved dataset: ', filename)

运行示例首先将所有图像加载到内存中,显示 A 类(马)有 1,187 张照片,B 类(斑马)有 1,474 张照片。

然后,数组以压缩的 NumPy 格式保存,文件名为“ horse2zebra_256.npz ”。注意:这个数据文件大约 570 兆字节,比原始图像大,因为我们将像素值存储为 32 位浮点值。

Loaded dataA:  (1187, 256, 256, 3)
Loaded dataB:  (1474, 256, 256, 3)
Saved dataset:  horse2zebra_256.npz

然后,我们可以加载数据集并绘制一些照片,以确认我们正在正确处理图像数据。

下面列出了完整的示例。

# load and plot the prepared dataset
from numpy import load
from matplotlib import pyplot
# load the dataset
data = load('horse2zebra_256.npz')
dataA, dataB = data['arr_0'], data['arr_1']
print('Loaded: ', dataA.shape, dataB.shape)
# plot source images
n_samples = 3
for i in range(n_samples):
	pyplot.subplot(2, n_samples, 1 + i)
	pyplot.axis('off')
	pyplot.imshow(dataA[i].astype('uint8'))
# plot target image
for i in range(n_samples):
	pyplot.subplot(2, n_samples, 1 + n_samples + i)
	pyplot.axis('off')
	pyplot.imshow(dataB[i].astype('uint8'))
pyplot.show()

运行示例首先加载数据集,确认示例的数量和彩色图像的形状符合我们的预期。

Loaded: (1187, 256, 256, 3) (1474, 256, 256, 3)

创建一个图,显示一行来自马照片数据集(dataA)的三幅图像和一行来自斑马数据集(dataB)的三幅图像。

Plot of Photographs from the Horses2Zeba Dataset

霍斯 2 泽巴数据集的照片图

现在我们已经准备好了用于建模的数据集,我们可以开发 CycleGAN 生成器模型,该模型可以将照片从一个类别转换到另一个类别,反之亦然。

如何开发自行车——从马到斑马

在本节中,我们将开发 CycleGAN 模型,用于将马的照片翻译成斑马,将斑马的照片翻译成马

论文中描述的相同模型架构和配置被用于一系列图像到图像的翻译任务。这个架构在正文中有描述,在论文的附录中有更多的细节,还有一个完全工作的实现作为开源为 Torch 深度学习框架实现。

本节中的实现将使用 Keras 深度学习框架,该框架直接基于论文中描述的模型,并在作者的代码库中实现,旨在拍摄和生成大小为 256×256 像素的彩色图像。

该架构由四个模型、两个鉴别器模型和两个生成器模型组成。

鉴别器是执行图像分类的深度卷积神经网络。它将源图像作为输入,并预测目标图像是真实图像还是假图像的可能性。使用了两个鉴别器模型,一个用于域 A(马),一个用于域 B(斑马)。

鉴别器的设计基于模型的有效感受野,它定义了模型的一个输出与输入图像中的像素数之间的关系。这被称为 PatchGAN 模型,经过精心设计,模型的每个输出预测都映射到输入图像的 70×70 的正方形或面片。这种方法的好处是相同的模型可以应用于不同尺寸的输入图像,例如大于或小于 256×256 像素。

模型的输出取决于输入图像的大小,但可能是一个值或值的平方激活图。每一个值都是输入图像中的一个补丁是真实的可能性的概率。如果需要,可以对这些值进行平均,以给出总体可能性或分类分数。

模型中使用了卷积-batchorm-LeakyReLU 层的模式,这是深度卷积鉴别器模型所共有的。与其他型号不同,CycleGAN 鉴别器使用实例化代替批处理化。这是一种非常简单的标准化类型,包括标准化(例如,缩放至标准高斯)每个输出要素图上的值,而不是跨一批要素进行标准化。

keras-contrib 项目中提供了实例规范化的实现,该项目提供了对社区提供的 keras 特性的早期访问。

keras-contrib 库可以通过 pip 安装,如下所示:

sudo pip install git+https://www.github.com/keras-team/keras-contrib.git

或者,如果您使用的是 Anaconda 虚拟环境,如在 EC2 上:

git clone https://www.github.com/keras-team/keras-contrib.git
cd keras-contrib
sudo ~/anaconda3/envs/tensorflow_p36/bin/python setup.py install

新的实例化层可以如下使用:

...
from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization
# define layer
layer = InstanceNormalization(axis=-1)
...

”参数设置为-1,以确保每个要素地图的要素都是标准化的。

下面的 define_discriminator() 函数按照文中模型的设计实现了 70×70 的 PatchGAN 鉴别器模型。该模型以 256×256 大小的图像作为输入,并输出一个预测补丁。使用最小二乘损失(L2)优化模型,最小二乘损失()实现为均方误差,并使用权重,以便模型的更新具有通常效果的一半(0.5)。CycleGAN 论文的作者推荐这种模型更新的权重,以减缓训练期间相对于生成器模型的鉴别器的变化。

# define the discriminator model
def define_discriminator(image_shape):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# source image input
	in_image = Input(shape=image_shape)
	# C64
	d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
	d = LeakyReLU(alpha=0.2)(d)
	# C128
	d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# C256
	d = Conv2D(256, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# C512
	d = Conv2D(512, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# second last output layer
	d = Conv2D(512, (4,4), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# patch output
	patch_out = Conv2D(1, (4,4), padding='same', kernel_initializer=init)(d)
	# define model
	model = Model(in_image, patch_out)
	# compile model
	model.compile(loss='mse', optimizer=Adam(lr=0.0002, beta_1=0.5), loss_weights=[0.5])
	return model

生成器模型比鉴别器模型更复杂。

生成器是一个编码器-解码器模型架构。该模型拍摄源图像(例如,马的照片)并生成目标图像(例如,斑马的照片)。它是这样做的:首先对输入图像进行下采样或编码到瓶颈层,然后用多个使用跳过连接的 ResNet 层解释编码,接着用一系列层将表示上采样或解码到输出图像的大小。

首先,我们需要一个函数来定义 ResNet 块。这些块由两个 3×3 有线电视新闻网层组成,其中块的输入按通道连接到块的输出。

这是在 resnet_block() 函数中实现的,该函数在第二个块之后创建了两个带有 3×3 过滤器的卷积-实例化块和 1×1 步长块,并且没有 ReLU 激活,与 build_conv_block()函数中的官方 Torch 实现相匹配。为了简单起见,使用相同的填充,而不是文中推荐的反射填充。

# generator a resnet block
def resnet_block(n_filters, input_layer):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# first layer convolutional layer
	g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(input_layer)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# second convolutional layer
	g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	# concatenate merge channel-wise with input layer
	g = Concatenate()([g, input_layer])
	return g

接下来,我们可以定义一个函数,为 256×256 个输入图像创建 9-resnet 块版本。通过将 image_shape 设置为(128 x128 x3)n _ resnet函数参数设置为 6,可以轻松将其更改为 6-resnet 块版本。

重要的是,该模型输出的像素值与输入的形状相同,并且像素值在[-1,1]的范围内,这是 GAN 发生器模型的典型情况。

# define the standalone generator model
def define_generator(image_shape, n_resnet=9):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image input
	in_image = Input(shape=image_shape)
	# c7s1-64
	g = Conv2D(64, (7,7), padding='same', kernel_initializer=init)(in_image)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# d128
	g = Conv2D(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# d256
	g = Conv2D(256, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# R256
	for _ in range(n_resnet):
		g = resnet_block(256, g)
	# u128
	g = Conv2DTranspose(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# u64
	g = Conv2DTranspose(64, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# c7s1-3
	g = Conv2D(3, (7,7), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	out_image = Activation('tanh')(g)
	# define model
	model = Model(in_image, out_image)
	return model

鉴别器模型直接在真实和生成的图像上训练,而生成器模型不是。

相反,生成器模型通过它们相关的鉴别器模型来训练。具体来说,它们被更新以最小化由鉴别器为标记为“真实”的生成图像预测的损失,这被称为对抗性损失。因此,鼓励他们生成更适合目标领域的图像。

当与另一个发生器模型一起使用时,发生器模型也会根据它们在源图像再生时的效率进行更新,称为周期损失。最后,当从目标域提供一个称为身份丢失的示例时,生成器模型期望输出没有翻译的图像。

总之,每个发电机模型通过四个输出和四个损耗函数的组合进行优化:

  • 对抗性损失(L2 或均方误差)。
  • 身份损失(L1 或平均绝对误差)。
  • 正向周期损失(L1 或平均绝对误差)。
  • 反向循环损失(L1 或平均绝对误差)。

这可以通过定义用于训练每个生成器模型的复合模型来实现,该模型只负责更新该生成器模型的权重,尽管需要与相关的鉴别器模型和另一个生成器模型共享权重。

这在下面的 define_composite_model() 函数中实现,该函数获取一个已定义的发电机模型( g_model_1 )以及发电机模型输出的已定义鉴别器模型( d_model )和另一个发电机模型( g_model_2 )。其他模型的权重被标记为不可训练,因为我们只对更新第一个生成器模型感兴趣,即这个复合模型的焦点。

鉴别器连接到发生器的输出,以便将产生的图像分类为真实的或伪造的。复合模型的第二个输入被定义为来自目标域(而不是源域)的图像,生成器期望在没有身份映射转换的情况下输出该图像。接下来,正向循环损耗包括将发生器的输出连接到另一个发生器,后者将重建源图像。最后,后向循环丢失涉及来自用于身份映射的目标域的图像,该图像也通过另一个生成器传递,该生成器的输出连接到我们的主生成器作为输入,并输出来自目标域的该图像的重构版本。

综上所述,合成模型有两个输入用于来自域 A 和域 B 的真实照片,四个输出用于鉴别器输出、身份生成图像、正向循环生成图像和反向循环生成图像。

对于复合模型,只有第一个或主发电机模型的权重被更新,这是通过所有损失函数的加权和来完成的。循环损失的权重(10 倍)大于论文中描述的对抗性损失,身份损失的权重始终是循环损失的一半(5 倍),与官方实现源代码相匹配。

# define a composite model for updating generators by adversarial and cycle loss
def define_composite_model(g_model_1, d_model, g_model_2, image_shape):
	# ensure the model we're updating is trainable
	g_model_1.trainable = True
	# mark discriminator as not trainable
	d_model.trainable = False
	# mark other generator model as not trainable
	g_model_2.trainable = False
	# discriminator element
	input_gen = Input(shape=image_shape)
	gen1_out = g_model_1(input_gen)
	output_d = d_model(gen1_out)
	# identity element
	input_id = Input(shape=image_shape)
	output_id = g_model_1(input_id)
	# forward cycle
	output_f = g_model_2(gen1_out)
	# backward cycle
	gen2_out = g_model_2(input_id)
	output_b = g_model_1(gen2_out)
	# define model graph
	model = Model([input_gen, input_id], [output_d, output_id, output_f, output_b])
	# define optimization algorithm configuration
	opt = Adam(lr=0.0002, beta_1=0.5)
	# compile model with weighting of least squares loss and L1 loss
	model.compile(loss=['mse', 'mae', 'mae', 'mae'], loss_weights=[1, 5, 10, 10], optimizer=opt)
	return model

我们需要为每个生成器模型创建一个复合模型,例如用于斑马到马翻译的生成器-A (BtoA),以及用于马到斑马翻译的生成器-B (AtoB)。

所有这些跨越两个领域的前进和后退都变得令人困惑。下面是每个复合模型的所有输入和输出的完整列表。同一性和循环损失被计算为每个翻译序列的输入和输出图像之间的 L1 距离。对抗损失计算为模型输出和目标值之间的 L2 距离,真实值为 1.0,虚假值为 0.0。

发电机-复合模型(BtoA 或斑马对马)

模型的输入、转换和输出如下:

  • 对抗损失:域-B - >生成器-A - >域-A - >鉴别器-A - >【真/假】
  • 身份丢失:域-A - >生成器-A - >域-A
  • 正向循环损耗:域-B - >发电机-A - >域-A - >发电机-B - >域-B
  • 反向循环损耗:域-A - >发电机-B - >域-B - >发电机-A - >域-A

我们可以将输入和输出总结为:

  • 输入:域-B,域-A
  • 输出:实数,域-A,域-B,域-A

发电机-B 复合模型(AtoB 或马对斑马)

模型的输入、转换和输出如下:

  • 对抗损失:域-A - >生成器-B - >域-B - >鉴别器-B - >【真/假】
  • 身份丢失:域-B - >生成器-B - >域-B
  • 正向循环损耗:域-A - >发电机-B - >域-B - >发电机-A - >域-A
  • 反向循环损耗:域-B - >发电机-A - >域-A - >发电机-B - >域-B

我们可以将输入和输出总结为:

  • 输入:域-A,域-B
  • 输出:实数,域-B,域-A,域-B

定义模型是循环的难点;剩下的就是标准的 GAN 训练,相对简单。

接下来,我们可以以压缩的 NumPy 数组格式加载我们的配对图像数据集。这将返回两个 NumPy 数组的列表:第一个用于源图像,第二个用于对应的目标图像。

# load and prepare training images
def load_real_samples(filename):
	# load the dataset
	data = load(filename)
	# unpack arrays
	X1, X2 = data['arr_0'], data['arr_1']
	# scale from [0,255] to [-1,1]
	X1 = (X1 - 127.5) / 127.5
	X2 = (X2 - 127.5) / 127.5
	return [X1, X2]

每次训练迭代,我们都需要来自每个域的真实图像样本,作为鉴别器和复合生成器模型的输入。这可以通过随机选择一批样品来实现。

下面的 generate_real_samples() 函数实现了这一点,将一个域的 NumPy 数组作为输入,并返回随机选择的图像的请求数量,以及表示图像是真实的 PatchGAN 鉴别器模型的目标(目标=1.0 )。因此,还提供了 PatchgAN 输出的形状,在 256×256 图像的情况下,这将是 16,或 16x16x1 激活图,由 patch_shape 函数参数定义。

# select a batch of random samples, returns images and target
def generate_real_samples(dataset, n_samples, patch_shape):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# retrieve selected images
	X = dataset[ix]
	# generate 'real' class labels (1)
	y = ones((n_samples, patch_shape, patch_shape, 1))
	return X, y

类似地,在每次训练迭代中,需要生成图像的样本来更新每个鉴别器模型。

下面的 generate_fake_samples() 函数在给定生成器模型和来自源域的真实图像样本的情况下生成该样本。同样,为每个生成的图像的目标值提供了 PatchGAN 的正确形状,表明它们是假的或生成的(目标=0.0 )。

# generate a batch of images, returns images and targets
def generate_fake_samples(g_model, dataset, patch_shape):
	# generate fake instance
	X = g_model.predict(dataset)
	# create 'fake' class labels (0)
	y = zeros((len(X), patch_shape, patch_shape, 1))
	return X, y

典型地,GAN 模型不收敛;相反,在生成器和鉴别器模型之间找到了平衡。因此,我们不能轻易判断培训是否应该停止。因此,我们可以保存模型,并在训练期间定期使用它生成样本图像到图像的转换,例如每一个或五个训练时期。

然后,我们可以在训练结束时查看生成的图像,并使用图像质量来选择最终模型。

下面的 save_models() 功能将以 H5 格式将每个发电机模型保存到当前目录,包括文件名中的训练迭代编号。这需要安装 h5py 库

# save the generator models to file
def save_models(step, g_model_AtoB, g_model_BtoA):
	# save the first generator model
	filename1 = 'g_model_AtoB_%06d.h5' % (step+1)
	g_model_AtoB.save(filename1)
	# save the second generator model
	filename2 = 'g_model_BtoA_%06d.h5' % (step+1)
	g_model_BtoA.save(filename2)
	print('>Saved: %s and %s' % (filename1, filename2))

下面的*summary _ performance()*函数使用给定的生成器模型来生成一些随机选择的源照片的翻译版本,并将该图保存到文件中。

源图像绘制在第一行,生成的图像绘制在第二行。同样,绘图文件名包括训练迭代号。

# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, trainX, name, n_samples=5):
	# select a sample of input images
	X_in, _ = generate_real_samples(trainX, n_samples, 0)
	# generate translated images
	X_out, _ = generate_fake_samples(g_model, X_in, 0)
	# scale all pixels from [-1,1] to [0,1]
	X_in = (X_in + 1) / 2.0
	X_out = (X_out + 1) / 2.0
	# plot real images
	for i in range(n_samples):
		pyplot.subplot(2, n_samples, 1 + i)
		pyplot.axis('off')
		pyplot.imshow(X_in[i])
	# plot translated image
	for i in range(n_samples):
		pyplot.subplot(2, n_samples, 1 + n_samples + i)
		pyplot.axis('off')
		pyplot.imshow(X_out[i])
	# save plot to file
	filename1 = '%s_generated_plot_%06d.png' % (name, (step+1))
	pyplot.savefig(filename1)
	pyplot.close()

我们几乎准备好定义模型的训练。

鉴别器模型直接在真实和生成的图像上更新,尽管为了进一步管理鉴别器模型学习的速度,维护了一个假图像池。

本文为每个鉴别器模型定义了一个由 50 个生成图像组成的图像池,首先填充该图像池,然后通过替换现有图像或直接使用生成的图像来增加新图像。我们可以将其实现为每个鉴别器的 Python 图像列表,并使用下面的 update_image_pool() 函数来维护每个池列表。

# update image pool for fake images
def update_image_pool(pool, images, max_size=50):
	selected = list()
	for image in images:
		if len(pool) < max_size:
			# stock the pool
			pool.append(image)
			selected.append(image)
		elif random() < 0.5:
			# use image, but don't add it to the pool
			selected.append(image)
		else:
			# replace an existing image and use replaced image
			ix = randint(0, len(pool))
			selected.append(pool[ix])
			pool[ix] = image
	return asarray(selected)

我们现在可以定义每个生成器模型的训练。

下面的 train() 函数将所有六个模型(两个鉴别器、两个生成器和两个复合模型)与数据集一起作为参数,并训练模型。

批量大小固定在一张图片上,以匹配论文中的描述,模型适合 100 个时代。假设马数据集有 1,187 个图像,一个纪元被定义为 1,187 个批次和相同数量的训练迭代。每个时期使用两个生成器生成图像,并且每五个时期或(1187 * 5) 5,935 次训练迭代保存模型。

实现模型更新的顺序是为了匹配官方的 Torch 实现。首先,从每个域中选择一批真实图像,然后为每个域生成一批假图像。假图像然后被用来更新每个鉴别器的假图像池。

接下来,生成器-A 模型(斑马到马)通过复合模型更新,然后是鉴别器-A 模型(马)。然后更新生成器-B(马对斑马)合成模型和鉴别器-B(斑马)模型。

然后在训练迭代结束时报告每个更新模型的损失。重要的是,仅报告用于更新每个发电机的加权平均损失。

# train cyclegan models
def train(d_model_A, d_model_B, g_model_AtoB, g_model_BtoA, c_model_AtoB, c_model_BtoA, dataset):
	# define properties of the training run
	n_epochs, n_batch, = 100, 1
	# determine the output square shape of the discriminator
	n_patch = d_model_A.output_shape[1]
	# unpack dataset
	trainA, trainB = dataset
	# prepare image pool for fakes
	poolA, poolB = list(), list()
	# calculate the number of batches per training epoch
	bat_per_epo = int(len(trainA) / n_batch)
	# calculate the number of training iterations
	n_steps = bat_per_epo * n_epochs
	# manually enumerate epochs
	for i in range(n_steps):
		# select a batch of real samples
		X_realA, y_realA = generate_real_samples(trainA, n_batch, n_patch)
		X_realB, y_realB = generate_real_samples(trainB, n_batch, n_patch)
		# generate a batch of fake samples
		X_fakeA, y_fakeA = generate_fake_samples(g_model_BtoA, X_realB, n_patch)
		X_fakeB, y_fakeB = generate_fake_samples(g_model_AtoB, X_realA, n_patch)
		# update fakes from pool
		X_fakeA = update_image_pool(poolA, X_fakeA)
		X_fakeB = update_image_pool(poolB, X_fakeB)
		# update generator B->A via adversarial and cycle loss
		g_loss2, _, _, _, _  = c_model_BtoA.train_on_batch([X_realB, X_realA], [y_realA, X_realA, X_realB, X_realA])
		# update discriminator for A -> [real/fake]
		dA_loss1 = d_model_A.train_on_batch(X_realA, y_realA)
		dA_loss2 = d_model_A.train_on_batch(X_fakeA, y_fakeA)
		# update generator A->B via adversarial and cycle loss
		g_loss1, _, _, _, _ = c_model_AtoB.train_on_batch([X_realA, X_realB], [y_realB, X_realB, X_realA, X_realB])
		# update discriminator for B -> [real/fake]
		dB_loss1 = d_model_B.train_on_batch(X_realB, y_realB)
		dB_loss2 = d_model_B.train_on_batch(X_fakeB, y_fakeB)
		# summarize performance
		print('>%d, dA[%.3f,%.3f] dB[%.3f,%.3f] g[%.3f,%.3f]' % (i+1, dA_loss1,dA_loss2, dB_loss1,dB_loss2, g_loss1,g_loss2))
		# evaluate the model performance every so often
		if (i+1) % (bat_per_epo * 1) == 0:
			# plot A->B translation
			summarize_performance(i, g_model_AtoB, trainA, 'AtoB')
			# plot B->A translation
			summarize_performance(i, g_model_BtoA, trainB, 'BtoA')
		if (i+1) % (bat_per_epo * 5) == 0:
			# save the models
			save_models(i, g_model_AtoB, g_model_BtoA)

将所有这些结合在一起,下面列出了训练 CycleGAN 模型将马的照片翻译成斑马并将斑马的照片翻译成马的完整示例。

# example of training a cyclegan on the horse2zebra dataset
from random import random
from numpy import load
from numpy import zeros
from numpy import ones
from numpy import asarray
from numpy.random import randint
from keras.optimizers import Adam
from keras.initializers import RandomNormal
from keras.models import Model
from keras.models import Input
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.layers import Activation
from keras.layers import Concatenate
from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization
from matplotlib import pyplot

# define the discriminator model
def define_discriminator(image_shape):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# source image input
	in_image = Input(shape=image_shape)
	# C64
	d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
	d = LeakyReLU(alpha=0.2)(d)
	# C128
	d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# C256
	d = Conv2D(256, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# C512
	d = Conv2D(512, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# second last output layer
	d = Conv2D(512, (4,4), padding='same', kernel_initializer=init)(d)
	d = InstanceNormalization(axis=-1)(d)
	d = LeakyReLU(alpha=0.2)(d)
	# patch output
	patch_out = Conv2D(1, (4,4), padding='same', kernel_initializer=init)(d)
	# define model
	model = Model(in_image, patch_out)
	# compile model
	model.compile(loss='mse', optimizer=Adam(lr=0.0002, beta_1=0.5), loss_weights=[0.5])
	return model

# generator a resnet block
def resnet_block(n_filters, input_layer):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# first layer convolutional layer
	g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(input_layer)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# second convolutional layer
	g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	# concatenate merge channel-wise with input layer
	g = Concatenate()([g, input_layer])
	return g

# define the standalone generator model
def define_generator(image_shape, n_resnet=9):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image input
	in_image = Input(shape=image_shape)
	# c7s1-64
	g = Conv2D(64, (7,7), padding='same', kernel_initializer=init)(in_image)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# d128
	g = Conv2D(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# d256
	g = Conv2D(256, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# R256
	for _ in range(n_resnet):
		g = resnet_block(256, g)
	# u128
	g = Conv2DTranspose(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# u64
	g = Conv2DTranspose(64, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	g = Activation('relu')(g)
	# c7s1-3
	g = Conv2D(3, (7,7), padding='same', kernel_initializer=init)(g)
	g = InstanceNormalization(axis=-1)(g)
	out_image = Activation('tanh')(g)
	# define model
	model = Model(in_image, out_image)
	return model

# define a composite model for updating generators by adversarial and cycle loss
def define_composite_model(g_model_1, d_model, g_model_2, image_shape):
	# ensure the model we're updating is trainable
	g_model_1.trainable = True
	# mark discriminator as not trainable
	d_model.trainable = False
	# mark other generator model as not trainable
	g_model_2.trainable = False
	# discriminator element
	input_gen = Input(shape=image_shape)
	gen1_out = g_model_1(input_gen)
	output_d = d_model(gen1_out)
	# identity element
	input_id = Input(shape=image_shape)
	output_id = g_model_1(input_id)
	# forward cycle
	output_f = g_model_2(gen1_out)
	# backward cycle
	gen2_out = g_model_2(input_id)
	output_b = g_model_1(gen2_out)
	# define model graph
	model = Model([input_gen, input_id], [output_d, output_id, output_f, output_b])
	# define optimization algorithm configuration
	opt = Adam(lr=0.0002, beta_1=0.5)
	# compile model with weighting of least squares loss and L1 loss
	model.compile(loss=['mse', 'mae', 'mae', 'mae'], loss_weights=[1, 5, 10, 10], optimizer=opt)
	return model

# load and prepare training images
def load_real_samples(filename):
	# load the dataset
	data = load(filename)
	# unpack arrays
	X1, X2 = data['arr_0'], data['arr_1']
	# scale from [0,255] to [-1,1]
	X1 = (X1 - 127.5) / 127.5
	X2 = (X2 - 127.5) / 127.5
	return [X1, X2]

# select a batch of random samples, returns images and target
def generate_real_samples(dataset, n_samples, patch_shape):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# retrieve selected images
	X = dataset[ix]
	# generate 'real' class labels (1)
	y = ones((n_samples, patch_shape, patch_shape, 1))
	return X, y

# generate a batch of images, returns images and targets
def generate_fake_samples(g_model, dataset, patch_shape):
	# generate fake instance
	X = g_model.predict(dataset)
	# create 'fake' class labels (0)
	y = zeros((len(X), patch_shape, patch_shape, 1))
	return X, y

# save the generator models to file
def save_models(step, g_model_AtoB, g_model_BtoA):
	# save the first generator model
	filename1 = 'g_model_AtoB_%06d.h5' % (step+1)
	g_model_AtoB.save(filename1)
	# save the second generator model
	filename2 = 'g_model_BtoA_%06d.h5' % (step+1)
	g_model_BtoA.save(filename2)
	print('>Saved: %s and %s' % (filename1, filename2))

# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, trainX, name, n_samples=5):
	# select a sample of input images
	X_in, _ = generate_real_samples(trainX, n_samples, 0)
	# generate translated images
	X_out, _ = generate_fake_samples(g_model, X_in, 0)
	# scale all pixels from [-1,1] to [0,1]
	X_in = (X_in + 1) / 2.0
	X_out = (X_out + 1) / 2.0
	# plot real images
	for i in range(n_samples):
		pyplot.subplot(2, n_samples, 1 + i)
		pyplot.axis('off')
		pyplot.imshow(X_in[i])
	# plot translated image
	for i in range(n_samples):
		pyplot.subplot(2, n_samples, 1 + n_samples + i)
		pyplot.axis('off')
		pyplot.imshow(X_out[i])
	# save plot to file
	filename1 = '%s_generated_plot_%06d.png' % (name, (step+1))
	pyplot.savefig(filename1)
	pyplot.close()

# update image pool for fake images
def update_image_pool(pool, images, max_size=50):
	selected = list()
	for image in images:
		if len(pool) < max_size:
			# stock the pool
			pool.append(image)
			selected.append(image)
		elif random() < 0.5:
			# use image, but don't add it to the pool
			selected.append(image)
		else:
			# replace an existing image and use replaced image
			ix = randint(0, len(pool))
			selected.append(pool[ix])
			pool[ix] = image
	return asarray(selected)

# train cyclegan models
def train(d_model_A, d_model_B, g_model_AtoB, g_model_BtoA, c_model_AtoB, c_model_BtoA, dataset):
	# define properties of the training run
	n_epochs, n_batch, = 100, 1
	# determine the output square shape of the discriminator
	n_patch = d_model_A.output_shape[1]
	# unpack dataset
	trainA, trainB = dataset
	# prepare image pool for fakes
	poolA, poolB = list(), list()
	# calculate the number of batches per training epoch
	bat_per_epo = int(len(trainA) / n_batch)
	# calculate the number of training iterations
	n_steps = bat_per_epo * n_epochs
	# manually enumerate epochs
	for i in range(n_steps):
		# select a batch of real samples
		X_realA, y_realA = generate_real_samples(trainA, n_batch, n_patch)
		X_realB, y_realB = generate_real_samples(trainB, n_batch, n_patch)
		# generate a batch of fake samples
		X_fakeA, y_fakeA = generate_fake_samples(g_model_BtoA, X_realB, n_patch)
		X_fakeB, y_fakeB = generate_fake_samples(g_model_AtoB, X_realA, n_patch)
		# update fakes from pool
		X_fakeA = update_image_pool(poolA, X_fakeA)
		X_fakeB = update_image_pool(poolB, X_fakeB)
		# update generator B->A via adversarial and cycle loss
		g_loss2, _, _, _, _  = c_model_BtoA.train_on_batch([X_realB, X_realA], [y_realA, X_realA, X_realB, X_realA])
		# update discriminator for A -> [real/fake]
		dA_loss1 = d_model_A.train_on_batch(X_realA, y_realA)
		dA_loss2 = d_model_A.train_on_batch(X_fakeA, y_fakeA)
		# update generator A->B via adversarial and cycle loss
		g_loss1, _, _, _, _ = c_model_AtoB.train_on_batch([X_realA, X_realB], [y_realB, X_realB, X_realA, X_realB])
		# update discriminator for B -> [real/fake]
		dB_loss1 = d_model_B.train_on_batch(X_realB, y_realB)
		dB_loss2 = d_model_B.train_on_batch(X_fakeB, y_fakeB)
		# summarize performance
		print('>%d, dA[%.3f,%.3f] dB[%.3f,%.3f] g[%.3f,%.3f]' % (i+1, dA_loss1,dA_loss2, dB_loss1,dB_loss2, g_loss1,g_loss2))
		# evaluate the model performance every so often
		if (i+1) % (bat_per_epo * 1) == 0:
			# plot A->B translation
			summarize_performance(i, g_model_AtoB, trainA, 'AtoB')
			# plot B->A translation
			summarize_performance(i, g_model_BtoA, trainB, 'BtoA')
		if (i+1) % (bat_per_epo * 5) == 0:
			# save the models
			save_models(i, g_model_AtoB, g_model_BtoA)

# load image data
dataset = load_real_samples('horse2zebra_256.npz')
print('Loaded', dataset[0].shape, dataset[1].shape)
# define input shape based on the loaded dataset
image_shape = dataset[0].shape[1:]
# generator: A -> B
g_model_AtoB = define_generator(image_shape)
# generator: B -> A
g_model_BtoA = define_generator(image_shape)
# discriminator: A -> [real/fake]
d_model_A = define_discriminator(image_shape)
# discriminator: B -> [real/fake]
d_model_B = define_discriminator(image_shape)
# composite: A -> B -> [real/fake, A]
c_model_AtoB = define_composite_model(g_model_AtoB, d_model_B, g_model_BtoA, image_shape)
# composite: B -> A -> [real/fake, B]
c_model_BtoA = define_composite_model(g_model_BtoA, d_model_A, g_model_AtoB, image_shape)
# train models
train(d_model_A, d_model_B, g_model_AtoB, g_model_BtoA, c_model_AtoB, c_model_BtoA, dataset)

该示例可以在 CPU 硬件上运行,尽管建议使用 GPU 硬件。

该示例可能需要几个小时才能在现代 GPU 硬件上运行。

如果需要,可以通过亚马逊 EC2 访问便宜的 GPU 硬件;请参见教程:

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在每个训练迭代中报告损失,包括真假示例的鉴别器-A 损失( dA )、真假示例的鉴别器-B 损失( dB )以及生成器-AtoB 和生成器-BtoA 损失,每个损失都是对抗、同一性、前向和后向循环损失的加权平均值( g )。

如果鉴别器的损耗变为零并在那里停留很长时间,考虑重新开始训练,因为这是训练失败的一个例子。

>1, dA[2.284,0.678] dB[1.422,0.918] g[18.747,18.452]
>2, dA[2.129,1.226] dB[1.039,1.331] g[19.469,22.831]
>3, dA[1.644,3.909] dB[1.097,1.680] g[19.192,23.757]
>4, dA[1.427,1.757] dB[1.236,3.493] g[20.240,18.390]
>5, dA[1.737,0.808] dB[1.662,2.312] g[16.941,14.915]
...
>118696, dA[0.004,0.016] dB[0.001,0.001] g[2.623,2.359]
>118697, dA[0.001,0.028] dB[0.003,0.002] g[3.045,3.194]
>118698, dA[0.002,0.008] dB[0.001,0.002] g[2.685,2.071]
>118699, dA[0.010,0.010] dB[0.001,0.001] g[2.430,2.345]
>118700, dA[0.002,0.008] dB[0.000,0.004] g[2.487,2.169]
>Saved: g_model_AtoB_118700.h5 and g_model_BtoA_118700.h5

生成的图像图在每个时期结束时或每 1,187 次训练迭代后保存,迭代号用在文件名中。

AtoB_generated_plot_001187.png
AtoB_generated_plot_002374.png
...
BtoA_generated_plot_001187.png
BtoA_generated_plot_002374.png

每五个纪元或(1187 * 5) 5,935 次训练迭代后保存模型,并且在文件名中再次使用迭代编号。

g_model_AtoB_053415.h5
g_model_AtoB_059350.h5
...
g_model_BtoA_053415.h5
g_model_BtoA_059350.h5

生成图像的图可以用于选择模型,并且更多的训练迭代不一定意味着生成的图像质量更好。

马到斑马的翻译在大约 50 个时代后开始变得可靠。

Plot of Source Photographs of Horses (top row) and Translated Photographs of Zebras (bottom row) After 53,415 Training Iterations

53,415 次训练迭代后,马的源照片(上排)和斑马的翻译照片(下排)的绘图

从斑马到马的翻译似乎对模型学习来说更具挑战性,尽管有些似是而非的翻译也开始在 50 到 60 个时代后产生。

我怀疑,如本文所用,通过增加 100 个具有权重衰减的训练时期,可能会获得更高质量的结果,或许还可以使用一个数据生成器,系统地处理每个数据集,而不是随机采样。

Plot of Source Photographs of Zebras (top row) and Translated Photographs of Horses (bottom row) After 90,212 Training Iterations

90,212 次训练迭代后斑马的源照片(上排)和马的翻译照片(下排)的绘图

既然我们已经安装了 CycleGAN 生成器,我们就可以使用它们来临时翻译照片。

如何使用循环生成器执行图像转换

保存的生成器模型可以被加载并用于特定的图像转换。

第一步是加载数据集。我们可以使用与上一节中开发的相同的 load_real_samples() 函数。

...
# load dataset
A_data, B_data = load_real_samples('horse2zebra_256.npz')
print('Loaded', A_data.shape, B_data.shape)

查看生成的图像图,选择一对可用于图像生成的模型。在这种情况下,我们将使用在纪元 89(训练迭代 89,025)前后保存的模型。我们的生成器模型使用了来自 keras_contrib 库的定制层,特别是实例化层。因此,我们需要指定在加载每个生成器模型时如何加载该层。

这可以通过指定层名称到对象的字典映射并将其作为参数传递给 load_model() keras 函数来实现。

...
# load the models
cust = {'InstanceNormalization': InstanceNormalization}
model_AtoB = load_model('g_model_AtoB_089025.h5', cust)
model_BtoA = load_model('g_model_BtoA_089025.h5', cust)

我们可以使用上一节中开发的 select_sample() 函数从数据集中选择一张随机照片。

# select a random sample of images from the dataset
def select_sample(dataset, n_samples):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# retrieve selected images
	X = dataset[ix]
	return X

接下来,我们可以使用 Generator-AtoB 模型,首先从 Domain-A(马)中选择一幅随机图像作为输入,使用 Generator-AtoB 将其翻译成 Domain-B(斑马),然后使用 Generator-BtoA 模型重建原始图像(马)。

# plot A->B->A
A_real = select_sample(A_data, 1)
B_generated  = model_AtoB.predict(A_real)
A_reconstructed = model_BtoA.predict(B_generated)

然后,我们可以将这三张照片并排绘制为原始照片或真实照片、翻译后的照片以及原始照片的重建。下面的 show_plot() 函数实现了这一点。

# plot the image, the translation, and the reconstruction
def show_plot(imagesX, imagesY1, imagesY2):
	images = vstack((imagesX, imagesY1, imagesY2))
	titles = ['Real', 'Generated', 'Reconstructed']
	# scale from [-1,1] to [0,1]
	images = (images + 1) / 2.0
	# plot images row by row
	for i in range(len(images)):
		# define subplot
		pyplot.subplot(1, len(images), 1 + i)
		# turn off axis
		pyplot.axis('off')
		# plot raw pixel data
		pyplot.imshow(images[i])
		# title
		pyplot.title(titles[i])
	pyplot.show()

然后我们可以调用这个函数来绘制真实的和生成的照片。

...
show_plot(A_real, B_generated, A_reconstructed)

这是对两个模型的一个很好的测试,但是,我们也可以反向执行相同的操作。

具体来说,一张来自域 B(斑马)的真实照片被翻译成域 A(马),然后被重建为域 B(斑马)。

# plot B->A->B
B_real = select_sample(B_data, 1)
A_generated  = model_BtoA.predict(B_real)
B_reconstructed = model_AtoB.predict(A_generated)
show_plot(B_real, A_generated, B_reconstructed)

将所有这些结合在一起,下面列出了完整的示例。

# example of using saved cyclegan models for image translation
from keras.models import load_model
from numpy import load
from numpy import vstack
from matplotlib import pyplot
from numpy.random import randint
from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization

# load and prepare training images
def load_real_samples(filename):
	# load the dataset
	data = load(filename)
	# unpack arrays
	X1, X2 = data['arr_0'], data['arr_1']
	# scale from [0,255] to [-1,1]
	X1 = (X1 - 127.5) / 127.5
	X2 = (X2 - 127.5) / 127.5
	return [X1, X2]

# select a random sample of images from the dataset
def select_sample(dataset, n_samples):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# retrieve selected images
	X = dataset[ix]
	return X

# plot the image, the translation, and the reconstruction
def show_plot(imagesX, imagesY1, imagesY2):
	images = vstack((imagesX, imagesY1, imagesY2))
	titles = ['Real', 'Generated', 'Reconstructed']
	# scale from [-1,1] to [0,1]
	images = (images + 1) / 2.0
	# plot images row by row
	for i in range(len(images)):
		# define subplot
		pyplot.subplot(1, len(images), 1 + i)
		# turn off axis
		pyplot.axis('off')
		# plot raw pixel data
		pyplot.imshow(images[i])
		# title
		pyplot.title(titles[i])
	pyplot.show()

# load dataset
A_data, B_data = load_real_samples('horse2zebra_256.npz')
print('Loaded', A_data.shape, B_data.shape)
# load the models
cust = {'InstanceNormalization': InstanceNormalization}
model_AtoB = load_model('g_model_AtoB_089025.h5', cust)
model_BtoA = load_model('g_model_BtoA_089025.h5', cust)
# plot A->B->A
A_real = select_sample(A_data, 1)
B_generated  = model_AtoB.predict(A_real)
A_reconstructed = model_BtoA.predict(B_generated)
show_plot(A_real, B_generated, A_reconstructed)
# plot B->A->B
B_real = select_sample(B_data, 1)
A_generated  = model_BtoA.predict(B_real)
B_reconstructed = model_AtoB.predict(A_generated)
show_plot(B_real, A_generated, B_reconstructed)

运行该示例首先选择一张马的随机照片,翻译它,然后尝试重建原始照片。

Plot of a Real Photo of a Horse, Translation to Zebra, and Reconstructed Photo of a Horse Using CycleGAN.

使用 CycleGAN 绘制真实的马照片、翻译成斑马和重建的马照片。

然后以相反的方式执行类似的过程,选择斑马的随机照片,将其翻译成马,然后重建斑马的原始照片。

Plot of a Real Photo of a Zebra, Translation to Horse, and Reconstructed Photo of a Zebra Using CycleGAN.

使用 CycleGAN 绘制斑马的真实照片、翻译成马以及重建斑马的照片。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

模型并不完美,尤其是斑马到马的模型,所以你可能需要生成许多翻译的例子来回顾。

这两种模型在重建图像时似乎都更有效,这很有趣,因为它们本质上执行的翻译任务与在真实照片上操作时相同。这可能是训练中对抗损失不够强的一个信号。

我们可能还想在单个照片文件上单独使用一个生成器模型。

首先,我们可以从训练数据集中选择一张照片。在这种情况下,我们将使用“horse 2 zebra/trainA/n 02381460 _ 541 . jpg”。

Photograph of a Horse

一匹马的照片

我们可以开发一个函数来加载这个图像,并将其缩放到 256×256 的首选大小,将像素值缩放到[-1,1]的范围,并将像素阵列转换为单个样本。

下面的 load_image() 函数实现了这一点。

def load_image(filename, size=(256,256)):
	# load and resize the image
	pixels = load_img(filename, target_size=size)
	# convert to numpy array
	pixels = img_to_array(pixels)
	# transform in a sample
	pixels = expand_dims(pixels, 0)
	# scale from [0,255] to [-1,1]
	pixels = (pixels - 127.5) / 127.5
	return pixels

然后,我们可以像以前一样加载我们选择的图像以及 AtoB 生成器模型。

...
# load the image
image_src = load_image('horse2zebra/trainA/n02381460_541.jpg')
# load the model
cust = {'InstanceNormalization': InstanceNormalization}
model_AtoB = load_model('g_model_AtoB_089025.h5', cust)

然后,我们可以转换加载的图像,将像素值缩放回预期范围,并绘制结果。

...
# translate image
image_tar = model_AtoB.predict(image_src)
# scale from [-1,1] to [0,1]
image_tar = (image_tar + 1) / 2.0
# plot the translated image
pyplot.imshow(image_tar[0])
pyplot.show()

将这些结合在一起,完整的示例如下所示。

# example of using saved cyclegan models for image translation
from numpy import load
from numpy import expand_dims
from keras.models import load_model
from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization
from keras.preprocessing.image import img_to_array
from keras.preprocessing.image import load_img
from matplotlib import pyplot

# load an image to the preferred size
def load_image(filename, size=(256,256)):
	# load and resize the image
	pixels = load_img(filename, target_size=size)
	# convert to numpy array
	pixels = img_to_array(pixels)
	# transform in a sample
	pixels = expand_dims(pixels, 0)
	# scale from [0,255] to [-1,1]
	pixels = (pixels - 127.5) / 127.5
	return pixels

# load the image
image_src = load_image('horse2zebra/trainA/n02381460_541.jpg')
# load the model
cust = {'InstanceNormalization': InstanceNormalization}
model_AtoB = load_model('g_model_AtoB_100895.h5', cust)
# translate image
image_tar = model_AtoB.predict(image_src)
# scale from [-1,1] to [0,1]
image_tar = (image_tar + 1) / 2.0
# plot the translated image
pyplot.imshow(image_tar[0])
pyplot.show()

运行该示例加载选定的图像,加载生成器模型,将马的照片转换为斑马,并绘制结果。

Photograph of a Horse Translated to a Photograph of a Zebra using CycleGAN

用 CycleGAN 把马的照片翻译成斑马的照片

扩展ˌ扩张

本节列出了一些您可能希望探索的扩展教程的想法。

  • 更小的图像尺寸。更新示例以使用更小的图像大小,例如 128×128,并调整生成器模型的大小以使用 6 个 ResNet 层,如 cycleGAN 论文中所用。
  • 不同的数据集。更新示例以使用苹果到橘子数据集。
  • 无身份映射。更新示例以训练没有身份映射的生成器模型并比较结果。

如果你探索这些扩展,我很想知道。 在下面的评论中发表你的发现。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

项目

应用程序接口

文章

摘要

在本教程中,您发现了如何开发一个 CycleGAN 模型来将马的照片翻译成斑马,然后再翻译回来。

具体来说,您了解到:

  • 如何加载和准备马到斑马图像转换数据集进行建模。
  • 如何训练一对 CycleGAN 发电机模型,将马翻译成斑马,斑马翻译成马。
  • 如何加载保存的 CycleGAN 模型,并使用它们来翻译照片。

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

生成对抗性网络损失函数的温和介绍

原文:machinelearningmastery.com/generative-…

生成对抗网络,简称 GAN,是一种深度学习架构,用于训练图像合成的生成模型。

GAN 体系结构相对简单,尽管对初学者来说仍然具有挑战性的一个方面是 GAN 损耗函数。主要原因是该架构涉及两个模型的同时训练:生成器和鉴别器。

鉴别器模型像任何其他深度学习神经网络一样被更新,尽管生成器使用鉴别器作为损失函数,这意味着生成器的损失函数是隐式的,并且在训练期间被学习。

在这篇文章中,你将发现生成对抗网络的损失函数的介绍。

看完这篇文章,你会知道:

  • GAN 架构是用最小最大 GAN 损耗来定义的,尽管它通常使用非饱和损耗函数来实现。
  • 现代 GANs 中常用的交替损失函数包括最小二乘法和 Wasserstein 损失函数。
  • 当计算预算和模型超参数等其他因素保持不变时,GAN 损失函数的大规模评估表明差别不大。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

A Gentle Introduction to Generative Adversarial Network Loss Functions

生成对抗性网络损失功能简介 图片由杨浩良提供,版权所有。

概观

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

  1. GAN 损耗的挑战
  2. 标准 GAN 损耗函数
  3. 交替 GAN 损耗函数
  4. 不同 GAN 损耗函数的影响

GAN 损耗的挑战

生成对抗网络,简称 GAN,是一种深度学习架构,用于训练图像合成的生成模型。

事实证明,它们非常有效,在生成逼真的人脸、场景等方面取得了令人印象深刻的效果。

GAN 体系结构相对简单,尽管对初学者来说仍然具有挑战性的一个方面是 GAN 损耗函数。

GAN 架构由两个模型组成:鉴别器和生成器。鉴别器直接在真实和生成的图像上训练,并负责将图像分类为真实或虚假(生成的)。生成器不是直接训练的,而是通过鉴别器模型训练的。

具体来说,学习鉴别器来为发生器提供损耗函数。

这两个模型在两人游戏中竞争,同时对竞争的生成器和鉴别器模型进行改进。

我们通常寻求模型在训练数据集上的收敛,该收敛被观察为训练数据集上所选损失函数的最小化。在 GAN 中,收敛标志着两个玩家游戏的结束。相反,寻求发生器和鉴别器损耗之间的平衡。

我们将仔细研究用于训练发生器和鉴别器模型的官方 GAN 损耗函数,以及一些可以替代使用的替代流行损耗函数。

标准 GAN 损耗函数

伊恩·古德费勒等人在 2014 年的论文《生成对抗网络》中描述了 GAN 架构

该方法引入了两个损失函数:第一个被称为最小最大 GAN 损失,第二个被称为非饱和 GAN 损失。

鉴别器损耗

在两种方案下,鉴别器损耗是相同的。鉴别器试图最大化分配给真实和虚假图像的概率。

我们训练 D,以最大化为训练示例和来自 g 的样本分配正确标签的概率。

——生成对抗网络,2014。

从数学上来说,鉴别器寻求最大化真实图像的对数概率的平均值和伪造图像的反转概率的对数。

  • 最大化对数 D(x) +对数(1–D(G(z)))

如果直接实现,这将需要使用随机上升而不是随机下降来改变模型权重。

它通常被实现为传统的二进制分类问题,对于生成的图像和真实的图像分别使用标签 0 和 1。

该模型适合寻求最小化平均值二元交叉熵,也称为对数损失。

  • 最小化 y _ true -log(y _ predicted)+(1–y _ true)-log(1–y _ predicted)

最小最大 GAN 损耗

极大极小 GAN 损失指的是鉴别器和发生器模型的极大极小同时优化。

Minimax 是指两人回合游戏中的一种优化策略,目的是在对方最差的情况下,将损失或成本降到最低。

对于 GAN,发生器和鉴别器是两个玩家,轮流更新他们的模型权重。最小值和最大值是指发电机损耗最小化和鉴频器损耗最大化。

  • 最小最大值(D,G)

如上所述,鉴别器寻求最大化真实图像的对数概率和伪造图像的逆概率的对数的平均值。

  • 鉴别器:最大化对数 D(x) +对数(1–D(G(z)))

生成器寻求最小化由鉴别器预测的假图像的逆概率的对数。这具有鼓励生成器生成伪造概率低的样本的效果。

  • 生成器:最小化对数(1–D(G(z)))

在这里,生成器学习生成伪造概率较低的样本。

——GANs 生来平等吗?一项大型研究,2018 年。

GAN 的这种损失框架被发现在作为极小极大博弈的模型分析中是有用的,但是在实践中,发现在实践中,发生器的这种损失函数饱和。

这意味着如果它不能像鉴别器一样快速学习,鉴别器就赢了,游戏就结束了,模型就不能得到有效的训练。

实际上,[损失函数]可能无法为 G 提供足够的梯度来学好。在学习初期,当 G 很差时,D 可以高自信地拒绝样本,因为它们与训练数据明显不同。

——生成对抗网络,2014。

非饱和 GAN 损耗

非饱和 GAN 损耗是对发生器损耗的修正,以克服饱和问题。

这是一个微妙的变化,涉及到生成器最大化生成图像的鉴别概率的对数,而不是最小化生成图像的反向鉴别概率的对数。

  • 生成器:最大化对数(D(G(z)))

这是问题框架的变化。

在前面的例子中,生成器试图最小化图像被预测为假的概率。这里,生成器寻求最大化图像被预测为真实的概率。

为了改善梯度信号,作者还提出了非饱和损失,其中生成器的目标是最大化生成的样本是真实的概率。

——GANs 生来平等吗?一项大型研究,2018 年。

结果是当更新生成器的权重时更好的梯度信息和更稳定的训练过程。

这个目标函数导致 G 和 D 的动力学相同的不动点,但是在学习的早期提供更强的梯度。

——生成对抗网络,2014。

实际上,这也是作为二进制分类问题来实现的,就像鉴别器一样。我们可以翻转真假图像的标签,最小化交叉熵,而不是最大化损失。

……一种方法是继续对生成器使用交叉熵最小化。我们不是翻转鉴别器成本上的符号来获得生成器的成本,而是翻转用于构建交叉熵成本的目标。

——NIPS 2016 教程:生成对抗网络,2016。

交替 GAN 损耗函数

损失函数的选择是一个热门的研究课题,许多替代损失函数已经被提出和评估。

在许多 GAN 实现中使用的两种流行的交替损耗函数是最小二乘损耗和 Wasserstein 损耗。

最小二乘 GAN 损耗

最小二乘损失是由毛旭东等人在他们 2016 年发表的题为“最小二乘生成对抗网络的论文中提出的

他们的方法是基于当生成的图像与真实图像非常不同时使用二进制交叉熵损失的局限性的观察,这可能导致非常小或消失的梯度,并且反过来,很少或没有更新模型。

……然而,当使用位于决策边界正确侧但仍远离真实数据的假样本更新生成器时,该损失函数将导致梯度消失的问题。

——最小二乘生成对抗网络,2016。

鉴别器寻求最小化真实和虚假图像的预测值和期望值之间的平方差。

  • 鉴别器:最小化(d(x)–1)²+(d(g(z)))²

生成器试图最小化预测值和期望值之间的平方差总和,就像生成的图像是真实的一样。

  • 发电机:最小化(d(g(z))–1)²

在实践中,这包括分别保持假图像和真实图像的类别标签为 0 和 1,最小化最小二乘,也称为均方误差或 L2 损失。

  • l2 损失=总和(y _ 预测-y_true)²)

最小二乘损失的好处是,它对较大的误差有更多的惩罚,从而导致较大的校正,而不是消失的梯度和没有模型更新。

……最小二乘损失函数能够将假样本移向决策边界,因为最小二乘损失函数会惩罚位于决策边界正确一侧很远的样本。

——最小二乘生成对抗网络,2016。

Wasserstein 甘损失

Wasserstein 损失是由马丁·阿尔乔夫斯基等人在 2017 年发表的题为“Wasserstein 甘”的论文中提出的

Wasserstein 损失是由观察到的,即传统的遗传神经网络的动机是最小化真实和生成图像的实际和预测概率分布之间的距离,即所谓的库尔巴克-莱布勒散度或詹森-香农散度。

相反,他们建议在地球推进器的距离上建模这个问题,也被称为 Wasserstein-1 距离。运土者的距离根据将一个分布(一堆土)转化为另一个分布的成本来计算两个概率分布之间的距离。

使用 Wasserstein 损失的遗传神经网络包括将鉴别器的概念改变为比发生器模型更新更频繁(例如五倍)的批评器。评论家用真实值而不是预测概率来给图像评分。它还要求模型权重保持较小,例如修剪为[-0.01,0.01]的超立方体。

计算分数,使得真实图像和伪造图像的分数之间的距离最大程度地分开。

损失函数可以通过计算真实图像和伪造图像的平均预测分数并将平均分数分别乘以 1 和-1 来实现。这具有将真实和虚假图像的分数分开的预期效果。

Wasserstein 损失的好处是,它几乎在任何地方都提供了一个有用的梯度,允许继续训练模型。这也意味着较低的 Wasserstein 损失与较好的发生器图像质量相关,这意味着我们明确寻求发生器损失最小化。

据我们所知,这是 GAN 文献中第一次显示出这样的性质,其中 GAN 的损失显示出收敛的性质。

——2017 年一根筋

不同 GAN 损耗函数的影响

为了提高训练 GAN 模型的稳定性,已经开发和评估了许多损失函数。

最常见的是一般的非饱和损失,以及较大的和较新的 GAN 模型中的最小二乘和 Wasserstein 损失。

因此,对于给定的模型实现,人们对一个损失函数是否真的比另一个损失函数更好很感兴趣。

这个问题激发了马里奥·卢西克等人在 2018 年发表的题为《GANs 是平等的吗?一项大规模研究”

尽管非常丰富的研究活动导致了许多有趣的 GAN 算法,但仍然很难评估哪种算法比其他算法表现更好。我们对最先进的模型和评估方法进行了中立的、多方面的大规模实证研究。

——GANs 生来平等吗?一项大型研究,2018 年。

他们修正了模型的计算预算和超参数配置,并研究了一套七个损失函数。

这包括上述的极大极小损失、非饱和损失、Wasserstein 损失和最小二乘损失。这项研究还包括对 Wasserstein 减肥法的扩展,以去除被称为 Wasserstein 梯度惩罚减肥法(WGAN GP)的权重削减,以及另外两个方法,DRAGAN 和 BEGAN。

下表摘自论文,提供了鉴别器和发生器不同损耗函数的有用总结。

Summary of Different GAN Loss Functions

不同 GAN 损耗函数的总结。 摘自:甘斯生来平等吗?一项大规模研究。

使用一系列 GAN 评估指标对模型进行了系统评估,包括流行的 Frechet 初始距离(FID)。

令人惊讶的是,他们发现,当所有其他元素保持不变时,所有评估的损失函数的表现都大致相同。

我们对最先进的 GANs 进行了公平和全面的比较,并根据经验证明,在足够高的计算预算下,几乎所有 GANs 都能达到类似的 FID 值。

——GANs 生来平等吗?一项大型研究,2018 年。

这并不意味着损失的选择对于具体问题和型号配置无关紧要。

相反,结果表明,当模型的其他考虑因素(如计算预算和模型配置)保持不变时,损失函数选择的差异就会消失。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

文章

摘要

在这篇文章中,你发现了生成对抗网络损失函数的介绍。

具体来说,您了解到:

  • GAN 架构是用最小最大 GAN 损耗来定义的,尽管它通常使用非饱和损耗函数来实现。
  • 现代 GANs 中常用的交替损失函数包括最小二乘法和 Wasserstein 损失函数。
  • 当计算预算和模型超参数等其他因素保持不变时,GAN 损失函数的大规模评估表明差别不大。

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

如何从零开始开发 Wasserstein 生成对抗网络

原文:machinelearningmastery.com/how-to-code…

最后更新于 2021 年 1 月 18 日

Wasserstein 生成对抗网络是生成对抗网络的扩展,既提高了训练模型时的稳定性,又提供了与生成图像质量相关的损失函数。

WGAN 的发展有一个密集的数学动机,尽管在实践中只需要对已建立的标准深度卷积生成对抗网络或 DCGAN 进行一些小的修改。

在本教程中,您将发现如何从零开始实现 Wasserstein 生成对抗网络。

完成本教程后,您将知道:

  • 标准深度卷积 GAN 和新的 Wasserstein GAN 的区别。
  • 如何从头实现 Wasserstein GAN 的具体细节。
  • 如何开发用于图像生成的 WGAN 并解释模型的动态行为。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

  • 2021 年 1 月更新:更新所以层冻结用批量定额。

How to Code a Wasserstein Generative Adversarial Network (WGAN) From Scratch

如何从零开始编码 Wasserstein 生成对抗网络 图片由费利西亚诺·吉马雷斯提供,版权所有。

教程概述

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

  1. Wasserstein 生成对抗网络
  2. Wasserstein GAN 实现细节
  3. 如何训练 Wasserstein GAN 模型

Wasserstein 生成对抗网络

Wasserstein 甘,简称 WGAN,是由 Martin Arjovsky 等人在 2017 年发表的论文《Wasserstein 甘》中介绍的

它是 GAN 的扩展,寻求一种训练生成器模型的替代方法,以更好地近似给定训练数据集中观察到的数据分布。

WGAN 不是使用鉴别器来分类或预测生成的图像是真的还是假的概率,而是改变鉴别器模型或用给定图像的真实性或假性评分的批评家来代替鉴别器模型。

这种变化是由一个理论论点驱动的,即训练生成器应该寻求最小化在训练数据集中观察到的数据分布和在生成的示例中观察到的分布之间的距离。

WGAN 的好处是训练过程更稳定,对模型架构和超参数配置的选择不太敏感。也许最重要的是,鉴别器的损失似乎与发生器产生的图像质量有关。

Wasserstein GAN 实现细节

虽然 WGAN 的理论基础是密集的,但是 WGAN 的实现需要对标准的深度卷积 GAN 或 DCGAN 进行一些小的改变。

下图总结了训练 WGAN 的主要训练循环,摘自论文。请注意模型中使用的推荐超参数列表。

Algorithm for the Wasserstein Generative Adversarial Networks

Wasserstein 生成对抗网络的算法。 取自:Wasserstein GAN。

工作组的实现差异如下:

  1. 在批评模型的输出层使用线性激活函数(而不是 sigmoid)。
  2. 对真实图像使用-1 标签,对假图像使用 1 标签(而不是 1 和 0)。
  3. 用 Wasserstein 损失来训练批评家和发电机模型。
  4. 每次小批量更新后,将关键模型权重限制在有限的范围内(例如[-0.01,0.01])。
  5. 每次迭代更新批评模型的次数比生成器多(例如 5 次)。
  6. 使用 RMSProp 版本的梯度下降,学习率小,没有动量(例如 0.00005)。

使用标准的 DCGAN 模型作为起点,让我们依次看一下这些实现细节。

1.临界输出层中的线性激活

DCGAN 使用鉴别器输出层的 sigmoid 激活函数来预测给定图像真实的可能性。

在 WGAN 中,评论家模型需要线性激活来预测给定图像的真实度分数。

这可以通过在批评模型的输出层中将“激活”参数设置为“线性”来实现。

# define output layer of the critic model
...
model.add(Dense(1, activation='linear'))

线性激活是层的默认激活,因此我们可以不指定激活来获得相同的结果。

# define output layer of the critic model
...
model.add(Dense(1))

2.真实和虚假图像的类别标签

DCGAN 对假图像使用类 0,对真图像使用类 1,这些类标签用于训练 GAN。

在 DCGAN 中,这些是鉴别器期望达到的精确标签。WGAN 没有给这位评论家贴上精确的标签。相反,它鼓励评论家输出真实和虚假图像的不同分数。

这是通过 Wasserstein 函数实现的,该函数巧妙地利用了正负类标签。

可以实现 WGAN,其中-1 类标签用于真实图像,而+1 类标签用于伪造或生成的图像。

这可以使用one()NumPy 功能来实现。

例如:

...
# generate class labels, -1 for 'real'
y = -ones((n_samples, 1))
...
# create class labels with 1.0 for 'fake'
y = ones((n_samples, 1))

3.Wasserstein 损失函数

DCGAN 将鉴别器训练为二分类模型,以预测给定图像真实的概率。

为了训练该模型,使用二元交叉熵损失函数优化鉴别器。相同的损失函数用于更新发电机模型。

WGAN 模型的主要贡献是使用了一个新的损失函数,该函数鼓励鉴别器预测给定输入的真假分数。这将鉴别器的角色从分类器转变为对图像的真实性或虚假性进行评分的批评家,其中评分之间的差异尽可能大。

我们可以在 Keras 中将 Wasserstein 损失实现为一个自定义函数,该函数计算真实或虚假图像的平均分数。

真实例子的得分是最大的,虚假例子的得分是最小的。假设随机梯度下降是一种最小化算法,我们可以将类别标签乘以平均分数(例如-1 表示真实,1 表示虚假,无效),这确保了真实和虚假图像对网络的损失最小化。

下面列出了 Keras 的这种损失函数的有效实现。

from keras import backend

# implementation of wasserstein loss
def wasserstein_loss(y_true, y_pred):
	return backend.mean(y_true * y_pred)

通过在编译模型时指定函数名,该损失函数可用于训练 Keras 模型。

例如:

...
# compile the model
model.compile(loss=wasserstein_loss, ...)

4.评论家权重剪辑

DCGAN 不使用任何梯度剪裁,尽管 WGAN 要求对批评模型进行梯度剪裁。

我们可以实现权重裁剪作为 Keras 约束。

这是一个必须扩展约束类的类,并定义用于应用操作的 call() 函数和用于返回任何配置的 get_config() 函数的实现。

我们还可以定义 init() 函数来设置配置,在这种情况下,权重超立方体的边界框的对称大小,例如 0.01。

下面定义了剪辑约束类。

# clip model weights to a given hypercube
class ClipConstraint(Constraint):
	# set clip value when initialized
	def __init__(self, clip_value):
		self.clip_value = clip_value

	# clip model weights to hypercube
	def __call__(self, weights):
		return backend.clip(weights, -self.clip_value, self.clip_value)

	# get the config
	def get_config(self):
		return {'clip_value': self.clip_value}

要使用约束,可以构造类,然后通过设置 kernel_constraint 参数在层中使用;例如:

...
# define the constraint
const = ClipConstraint(0.01)
...
# use the constraint in a layer
model.add(Conv2D(..., kernel_constraint=const))

该约束仅在更新批评模型时需要。

5.更新评论多于生成器

在 DCGAN 中,生成器和鉴别器模型必须等量更新。

具体来说,鉴别器在每次迭代中用半批真实样本和半批假样本更新,而生成器用单批生成的样本更新。

例如:

...
# main gan training loop
for i in range(n_steps):

	# update the discriminator

	# get randomly selected 'real' samples
	X_real, y_real = generate_real_samples(dataset, half_batch)
	# update critic model weights
	c_loss1 = c_model.train_on_batch(X_real, y_real)
	# generate 'fake' examples
	X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
	# update critic model weights
	c_loss2 = c_model.train_on_batch(X_fake, y_fake)

	# update generator

	# prepare points in latent space as input for the generator
	X_gan = generate_latent_points(latent_dim, n_batch)
	# create inverted labels for the fake samples
	y_gan = ones((n_batch, 1))
	# update the generator via the critic's error
	g_loss = gan_model.train_on_batch(X_gan, y_gan)

在 WGAN 模型中,批评者模型必须比生成器模型更新得更多。

具体来说,定义了一个新的超参数来控制生成器模型的每次更新都更新评论的次数,称为 n _ criteria,并将其设置为 5。

这可以作为主 GAN 更新循环内的新循环来实现;例如:

...
# main gan training loop
for i in range(n_steps):

	# update the critic
	for _ in range(n_critic):
		# get randomly selected 'real' samples
		X_real, y_real = generate_real_samples(dataset, half_batch)
		# update critic model weights
		c_loss1 = c_model.train_on_batch(X_real, y_real)
		# generate 'fake' examples
		X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
		# update critic model weights
		c_loss2 = c_model.train_on_batch(X_fake, y_fake)

	# update generator

	# prepare points in latent space as input for the generator
	X_gan = generate_latent_points(latent_dim, n_batch)
	# create inverted labels for the fake samples
	y_gan = ones((n_batch, 1))
	# update the generator via the critic's error
	g_loss = gan_model.train_on_batch(X_gan, y_gan)

6.使用随机梯度下降

DCGAN 使用 Adam 版本的随机梯度下降,学习率小,动量适中。

WGAN 推荐使用 RMSProp 代替,学习率小 0.00005。

这可以在编译模型时在 Keras 中实现。例如:

...
# compile model
opt = RMSprop(lr=0.00005)
model.compile(loss=wasserstein_loss, optimizer=opt)

如何训练 Wasserstein GAN 模型

现在我们知道了 WGAN 的具体实现细节,就可以实现图像生成的模型了。

在本节中,我们将开发一个 WGAN,从 MNIST 数据集生成一个手写数字(‘7’)。对于 WGAN 来说,这是一个很好的测试问题,因为它是一个小数据集,需要一个简单的模式来快速训练。

第一步是定义模型。

批评模型将一幅 28×28 的灰度图像作为输入,并输出图像真实度或虚假度的分数。它被实现为一个适度的卷积神经网络,使用 DCGAN 设计的最佳实践,例如使用斜率为 0.2 的 LeakyReLU 激活函数批量归一化,以及使用 2×2 步距来下采样

批评模型利用新的 ClipConstraint 权重约束在小批量更新后裁剪模型权重,并使用自定义 wasserstein_loss() 函数进行优化,该函数是随机梯度下降的 RMSProp 版本,学习率为 0.00005。

下面的*define _ critical()*函数实现了这一点,定义和编译了 critical 模型并返回。图像的输入形状被参数化为默认函数参数,以使其清晰。

# define the standalone critic model
def define_critic(in_shape=(28,28,1)):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# weight constraint
	const = ClipConstraint(0.01)
	# define model
	model = Sequential()
	# downsample to 14x14
	model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, kernel_constraint=const, input_shape=in_shape))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# downsample to 7x7
	model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, kernel_constraint=const))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# scoring, linear activation
	model.add(Flatten())
	model.add(Dense(1))
	# compile model
	opt = RMSprop(lr=0.00005)
	model.compile(loss=wasserstein_loss, optimizer=opt)
	return model

生成器模型将潜在空间中的一个点作为输入,并输出单个 28×28 灰度图像。

这是通过使用完全连接的层来解释潜在空间中的点并提供足够的激活来实现的,这些激活可以被重新成形为输出图像的低分辨率版本(例如 7×7)的许多副本(在这种情况下,128 个)。然后对其进行两次上采样,每次使用转置卷积层,将激活区域的大小增加一倍,面积增加四倍。

该模型使用了最佳实践,例如 LeakyReLU 激活、作为步长因子的内核大小以及输出层中的双曲正切(tanh)激活函数。

下面的 define_generator() 函数定义了生成器模型,但由于没有直接训练,所以故意不编译,然后返回模型。潜在空间的大小被参数化为函数参数。

# define the standalone generator model
def define_generator(latent_dim):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# define model
	model = Sequential()
	# foundation for 7x7 image
	n_nodes = 128 * 7 * 7
	model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
	model.add(LeakyReLU(alpha=0.2))
	model.add(Reshape((7, 7, 128)))
	# upsample to 14x14
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# upsample to 28x28
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# output 28x28x1
	model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
	return model

接下来,可以定义一个 GAN 模型,它将生成器模型和批评家模型组合成一个更大的模型。

这个较大的模型将用于训练生成器中的模型权重,使用由批评模型计算的输出和误差。批评模型是单独训练的,因此,在这个更大的 GAN 模型中,模型权重被标记为不可训练,以确保只有生成器模型的权重被更新。批评家权重的可训练性的这种改变仅在训练组合的 GAN 模型时有效,而在单独训练批评家时无效。

这个更大的 GAN 模型将潜在空间中的一个点作为输入,使用生成器模型来生成图像,该图像作为输入被馈送到批评者模型,然后输出被评分为真或假。使用带有自定义 wasserstein_loss() 函数的 RMSProp 拟合模型。

下面的 define_gan() 函数实现了这一点,将已经定义的生成器和评论模型作为输入。

# define the combined generator and critic model, for updating the generator
def define_gan(generator, critic):
	# make weights in the critic not trainable
	for layer in critic.layers:
		if not isinstance(layer, BatchNormalization):
			layer.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the critic
	model.add(critic)
	# compile model
	opt = RMSprop(lr=0.00005)
	model.compile(loss=wasserstein_loss, optimizer=opt)
	return model

现在我们已经定义了 GAN 模型,我们需要对它进行训练。但是,在我们训练模型之前,我们需要输入数据。

第一步是加载并缩放 MNIST 数据集。通过调用 load_data() Keras 函数加载整个数据集,然后选择属于类别 7 的图像子集(大约 5000 个),例如数字 7 的手写描述。然后,像素值必须缩放到范围[-1,1]以匹配生成器模型的输出。

下面的 load_real_samples() 函数实现了这一点,返回 MNIST 训练数据集的加载和缩放子集,为建模做好准备。

# load images
def load_real_samples():
	# load dataset
	(trainX, trainy), (_, _) = load_data()
	# select all of the examples for a given class
	selected_ix = trainy == 7
	X = trainX[selected_ix]
	# expand to 3d, e.g. add channels
	X = expand_dims(X, axis=-1)
	# convert from ints to floats
	X = X.astype('float32')
	# scale from [0,255] to [-1,1]
	X = (X - 127.5) / 127.5
	return X

我们将需要数据集的一批(或半批)真实图像来更新 GAN 模型。实现这一点的简单方法是每次从数据集中选择图像的随机样本

下面的 generate_real_samples() 函数实现了这一点,以准备好的数据集为自变量,为评论家选择并返回一个图像的随机样本及其对应的标签,具体为 target=-1 表示它们是真实图像。

# select real samples
def generate_real_samples(dataset, n_samples):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# select images
	X = dataset[ix]
	# generate class labels, -1 for 'real'
	y = -ones((n_samples, 1))
	return X, y

接下来,我们需要发电机模型的输入。这些是来自潜在空间的随机点,具体为高斯分布随机变量

generate _ 潜伏 _points() 函数实现了这一点,将潜伏空间的大小作为自变量和所需的点数,作为生成器模型的一批输入样本返回。

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n_samples, latent_dim)
	return x_input

接下来,我们需要使用潜在空间中的点作为生成器的输入,以便生成新的图像。

下面的 generate_fake_samples() 函数实现了这一点,将生成器模型和潜在空间的大小作为参数,然后在潜在空间中生成点,并将其用作生成器模型的输入。

该函数返回生成的图像及其对应的评论家模型标签,具体来说,target=1 表示它们是伪造的或生成的。

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n_samples)
	# predict outputs
	X = generator.predict(x_input)
	# create class labels with 1.0 for 'fake'
	y = ones((n_samples, 1))
	return X, y

我们需要记录模型的表现。也许评估 GAN 表现最可靠的方法是使用生成器生成图像,然后对其进行审查和主观评估。

下面的*summary _ performance()*函数在训练过程中获取给定点的生成器模型,并使用它在 10×10 的网格中生成 100 个图像,然后绘制并保存到文件中。这个模型现在也保存到文件中,以防我们以后想用它来生成更多的图像。

# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
	# prepare fake examples
	X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
	# scale from [-1,1] to [0,1]
	X = (X + 1) / 2.0
	# plot images
	for i in range(10 * 10):
		# define subplot
		pyplot.subplot(10, 10, 1 + i)
		# turn off axis
		pyplot.axis('off')
		# plot raw pixel data
		pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
	# save plot to file
	filename1 = 'generated_plot_%04d.png' % (step+1)
	pyplot.savefig(filename1)
	pyplot.close()
	# save the generator model
	filename2 = 'model_%04d.h5' % (step+1)
	g_model.save(filename2)
	print('>Saved: %s and %s' % (filename1, filename2))

除了图像质量,跟踪模型随时间的损失和准确性也是一个好主意。

对于每个模型更新,可以跟踪真实和虚假样本的批评者的损失,对于每个更新,生成器的损失也是如此。然后,这些数据可用于在训练结束时创建损失线图。下面的 plot_history() 函数实现了这一点,并将结果保存到文件中。

# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist):
	# plot history
	pyplot.plot(d1_hist, label='crit_real')
	pyplot.plot(d2_hist, label='crit_fake')
	pyplot.plot(g_hist, label='gen')
	pyplot.legend()
	pyplot.savefig('plot_line_plot_loss.png')
	pyplot.close()

我们现在已经准备好适应 GAN 模型。

该模型适用于 10 个训练时期,这是任意的,因为该模型可能在最初的几个时期之后开始生成似是而非的数字 7。使用 64 个样本的批次大小,并且每个训练时期涉及 6,265/64,或大约 97 个批次的真实和虚假样本以及对模型的更新。因此,模型被训练了 97 个批次的 10 个时期,或者 970 次迭代。

首先,对半批真实样本更新评论家模型,然后对半批假样本进行更新,一起形成一批权重更新。然后根据 WGAN 算法的要求,重复n _ critical(5)次。

然后通过复合 GAN 模型更新发生器。重要的是,对于生成的样本,目标标签被设置为-1 或实数。这具有更新生成器以更好地生成下一批真实样本的效果。

下面的 train() 函数实现了这一点,将定义的模型、数据集和潜在维度的大小作为参数,并使用默认参数参数化纪元的数量和批处理大小。发电机模型在训练结束时保存。

在每次迭代中都会报告批评模型和生成器模型的表现。样本图像在每个时期生成并保存,模型表现的线图在运行结束时创建并保存。

# train the generator and critic
def train(g_model, c_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=64, n_critic=5):
	# calculate the number of batches per training epoch
	bat_per_epo = int(dataset.shape[0] / n_batch)
	# calculate the number of training iterations
	n_steps = bat_per_epo * n_epochs
	# calculate the size of half a batch of samples
	half_batch = int(n_batch / 2)
	# lists for keeping track of loss
	c1_hist, c2_hist, g_hist = list(), list(), list()
	# manually enumerate epochs
	for i in range(n_steps):
		# update the critic more than the generator
		c1_tmp, c2_tmp = list(), list()
		for _ in range(n_critic):
			# get randomly selected 'real' samples
			X_real, y_real = generate_real_samples(dataset, half_batch)
			# update critic model weights
			c_loss1 = c_model.train_on_batch(X_real, y_real)
			c1_tmp.append(c_loss1)
			# generate 'fake' examples
			X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
			# update critic model weights
			c_loss2 = c_model.train_on_batch(X_fake, y_fake)
			c2_tmp.append(c_loss2)
		# store critic loss
		c1_hist.append(mean(c1_tmp))
		c2_hist.append(mean(c2_tmp))
		# prepare points in latent space as input for the generator
		X_gan = generate_latent_points(latent_dim, n_batch)
		# create inverted labels for the fake samples
		y_gan = -ones((n_batch, 1))
		# update the generator via the critic's error
		g_loss = gan_model.train_on_batch(X_gan, y_gan)
		g_hist.append(g_loss)
		# summarize loss on this batch
		print('>%d, c1=%.3f, c2=%.3f g=%.3f' % (i+1, c1_hist[-1], c2_hist[-1], g_loss))
		# evaluate the model performance every 'epoch'
		if (i+1) % bat_per_epo == 0:
			summarize_performance(i, g_model, latent_dim)
	# line plots of loss
	plot_history(c1_hist, c2_hist, g_hist)

既然已经定义了所有的函数,我们就可以创建模型,加载数据集,并开始训练过程。

# size of the latent space
latent_dim = 50
# create the critic
critic = define_critic()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, critic)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, critic, gan_model, dataset, latent_dim)

将所有这些结合在一起,下面列出了完整的示例。

# example of a wgan for generating handwritten digits
from numpy import expand_dims
from numpy import mean
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras import backend
from keras.optimizers import RMSprop
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.layers import BatchNormalization
from keras.initializers import RandomNormal
from keras.constraints import Constraint
from matplotlib import pyplot

# clip model weights to a given hypercube
class ClipConstraint(Constraint):
	# set clip value when initialized
	def __init__(self, clip_value):
		self.clip_value = clip_value

	# clip model weights to hypercube
	def __call__(self, weights):
		return backend.clip(weights, -self.clip_value, self.clip_value)

	# get the config
	def get_config(self):
		return {'clip_value': self.clip_value}

# calculate wasserstein loss
def wasserstein_loss(y_true, y_pred):
	return backend.mean(y_true * y_pred)

# define the standalone critic model
def define_critic(in_shape=(28,28,1)):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# weight constraint
	const = ClipConstraint(0.01)
	# define model
	model = Sequential()
	# downsample to 14x14
	model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, kernel_constraint=const, input_shape=in_shape))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# downsample to 7x7
	model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, kernel_constraint=const))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# scoring, linear activation
	model.add(Flatten())
	model.add(Dense(1))
	# compile model
	opt = RMSprop(lr=0.00005)
	model.compile(loss=wasserstein_loss, optimizer=opt)
	return model

# define the standalone generator model
def define_generator(latent_dim):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# define model
	model = Sequential()
	# foundation for 7x7 image
	n_nodes = 128 * 7 * 7
	model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
	model.add(LeakyReLU(alpha=0.2))
	model.add(Reshape((7, 7, 128)))
	# upsample to 14x14
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# upsample to 28x28
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
	model.add(BatchNormalization())
	model.add(LeakyReLU(alpha=0.2))
	# output 28x28x1
	model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
	return model

# define the combined generator and critic model, for updating the generator
def define_gan(generator, critic):
	# make weights in the critic not trainable
	for layer in critic.layers:
		if not isinstance(layer, BatchNormalization):
			layer.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the critic
	model.add(critic)
	# compile model
	opt = RMSprop(lr=0.00005)
	model.compile(loss=wasserstein_loss, optimizer=opt)
	return model

# load images
def load_real_samples():
	# load dataset
	(trainX, trainy), (_, _) = load_data()
	# select all of the examples for a given class
	selected_ix = trainy == 7
	X = trainX[selected_ix]
	# expand to 3d, e.g. add channels
	X = expand_dims(X, axis=-1)
	# convert from ints to floats
	X = X.astype('float32')
	# scale from [0,255] to [-1,1]
	X = (X - 127.5) / 127.5
	return X

# select real samples
def generate_real_samples(dataset, n_samples):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# select images
	X = dataset[ix]
	# generate class labels, -1 for 'real'
	y = -ones((n_samples, 1))
	return X, y

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n_samples, latent_dim)
	return x_input

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n_samples)
	# predict outputs
	X = generator.predict(x_input)
	# create class labels with 1.0 for 'fake'
	y = ones((n_samples, 1))
	return X, y

# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
	# prepare fake examples
	X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
	# scale from [-1,1] to [0,1]
	X = (X + 1) / 2.0
	# plot images
	for i in range(10 * 10):
		# define subplot
		pyplot.subplot(10, 10, 1 + i)
		# turn off axis
		pyplot.axis('off')
		# plot raw pixel data
		pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
	# save plot to file
	filename1 = 'generated_plot_%04d.png' % (step+1)
	pyplot.savefig(filename1)
	pyplot.close()
	# save the generator model
	filename2 = 'model_%04d.h5' % (step+1)
	g_model.save(filename2)
	print('>Saved: %s and %s' % (filename1, filename2))

# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist):
	# plot history
	pyplot.plot(d1_hist, label='crit_real')
	pyplot.plot(d2_hist, label='crit_fake')
	pyplot.plot(g_hist, label='gen')
	pyplot.legend()
	pyplot.savefig('plot_line_plot_loss.png')
	pyplot.close()

# train the generator and critic
def train(g_model, c_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=64, n_critic=5):
	# calculate the number of batches per training epoch
	bat_per_epo = int(dataset.shape[0] / n_batch)
	# calculate the number of training iterations
	n_steps = bat_per_epo * n_epochs
	# calculate the size of half a batch of samples
	half_batch = int(n_batch / 2)
	# lists for keeping track of loss
	c1_hist, c2_hist, g_hist = list(), list(), list()
	# manually enumerate epochs
	for i in range(n_steps):
		# update the critic more than the generator
		c1_tmp, c2_tmp = list(), list()
		for _ in range(n_critic):
			# get randomly selected 'real' samples
			X_real, y_real = generate_real_samples(dataset, half_batch)
			# update critic model weights
			c_loss1 = c_model.train_on_batch(X_real, y_real)
			c1_tmp.append(c_loss1)
			# generate 'fake' examples
			X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
			# update critic model weights
			c_loss2 = c_model.train_on_batch(X_fake, y_fake)
			c2_tmp.append(c_loss2)
		# store critic loss
		c1_hist.append(mean(c1_tmp))
		c2_hist.append(mean(c2_tmp))
		# prepare points in latent space as input for the generator
		X_gan = generate_latent_points(latent_dim, n_batch)
		# create inverted labels for the fake samples
		y_gan = -ones((n_batch, 1))
		# update the generator via the critic's error
		g_loss = gan_model.train_on_batch(X_gan, y_gan)
		g_hist.append(g_loss)
		# summarize loss on this batch
		print('>%d, c1=%.3f, c2=%.3f g=%.3f' % (i+1, c1_hist[-1], c2_hist[-1], g_loss))
		# evaluate the model performance every 'epoch'
		if (i+1) % bat_per_epo == 0:
			summarize_performance(i, g_model, latent_dim)
	# line plots of loss
	plot_history(c1_hist, c2_hist, g_hist)

# size of the latent space
latent_dim = 50
# create the critic
critic = define_critic()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, critic)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, critic, gan_model, dataset, latent_dim)

运行这个例子很快,在没有图形处理器的现代硬件上大约需要 10 分钟。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

首先,在训练循环的每次迭代中,都会向控制台报告批评模型和生成器模型的丢失。具体来说,c1 是真实例子中的批评者的损失,c2 是生成样本中的批评者的损失,g 是通过批评者训练的生成器的损失。

c1 分数作为损失函数的一部分被反转;这意味着如果他们被报告为阴性,那么他们真的是阳性,如果他们被报告为阳性,那么他们真的是阴性。c2 分数的符号不变。

回想一下,Wasserstein 的损失是为了寻找真实和虚假的分数,而真实和虚假的分数在训练中会有所不同。我们可以在运行接近结束时看到这一点,例如最后一个时期,真实示例的 c1 损失为 5.338(真的-5.338),而虚假示例的 c2 损失为-14.260,这种大约 10 个单位的分离至少在之前的几次迭代中是一致的。

我们还可以看到,在这种情况下,模型将发电机的损失评分为 20 分左右。同样,回想一下,我们通过批评模型更新生成器,并将生成的示例视为真实的,目标为-1,因此分数可以解释为-20 左右的值,接近假样本的损失。

...
>961, c1=5.110, c2=-15.388 g=19.579
>962, c1=6.116, c2=-15.222 g=20.054
>963, c1=4.982, c2=-15.192 g=21.048
>964, c1=4.238, c2=-14.689 g=23.911
>965, c1=5.585, c2=-14.126 g=19.578
>966, c1=4.807, c2=-14.755 g=20.034
>967, c1=6.307, c2=-16.538 g=19.572
>968, c1=4.298, c2=-14.178 g=17.956
>969, c1=4.283, c2=-13.398 g=17.326
>970, c1=5.338, c2=-14.260 g=19.927

在运行结束时,创建并保存损失的线图。

该图显示了批评者在真实样本上的损失(蓝色)、批评者在虚假样本上的损失(橙色)以及批评者在用虚假样本更新生成器时的损失(绿色)。

在回顾 WGAN 的学习曲线时,有一个重要的因素,那就是趋势。

WGAN 的好处是损耗与生成的图像质量相关。对于稳定的训练过程来说,较低的损失意味着更好的图像质量。

在这种情况下,较低的损失具体是指评论家报告的生成图像的较低的 Wasserstein 损失(橙色线)。这种损失的标志不会被目标标签反转(例如,目标标签为+1.0),因此,随着生成的模型的图像质量的提高,表现良好的 WGAN 应该会显示这条线向下的趋势。

Line Plots of Loss and Accuracy for a Wasserstein Generative Adversarial Network

Wasserstein 生成对抗网络的损失和准确率线图

在这种情况下,更多的训练似乎会产生质量更好的图像,主要障碍出现在 200-300 年左右,之后模型的质量仍然很好。

在这个障碍之前和周围,图像质量很差;例如:

Sample of 100 Generated Images of a Handwritten Number 7 at Epoch 97 from a Wasserstein GAN.

100 张由 Wasserstein GAN 手写数字 7 在纪元 97 生成的图像样本。

在这个时代之后,WGAN 继续生成看似可信的手写数字。

Sample of 100 Generated Images of a Handwritten Number 7 at Epoch 970 from a Wasserstein GAN.

100 个样本的手写数字 7 在纪元 970 从 Wasserstein 甘生成的图像。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

应用程序接口

文章

摘要

在本教程中,您发现了如何从零开始实现 Wasserstein 生成对抗网络。

具体来说,您了解到:

  • 标准深度卷积 GAN 和新的 Wasserstein GAN 的区别。
  • 如何从头实现 Wasserstein GAN 的具体细节。
  • 如何开发用于图像生成的 WGAN 并解释模型的动态行为。

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