生成对抗网络实战-全-

生成对抗网络实战(全)

原文:GANs in Action

译者:飞龙

协议:CC BY-NC-SA 4.0

第一部分. GANs 和生成建模简介

第一部分介绍了生成对抗网络(GANs)的世界,并介绍了最经典的 GAN 变体的实现:

  • 在第一章中,您将学习 GAN 的基础知识,并发展对它们如何工作的直观理解。

  • 在第二章中,我们将稍微转换一下方向,研究自动编码器,以便您能够更全面地理解生成建模。自动编码器是 GANs 最重要的理论和实践先驱之一,并且至今仍被广泛使用。

  • 第三章 从第一章结束的地方开始,深入探讨了 GAN 和对抗学习背后的理论。在这一章中,您还将实现并训练您的第一个、完全功能的 GAN。

  • 第四章 通过探索深度卷积生成对抗网络(DCGAN)继续您的学习之旅。这一在原始 GAN 之上的创新使用了卷积神经网络来提高生成图像的质量。

第一章. GANs 简介

本章涵盖

  • 生成对抗网络概述

  • 使这一类机器学习算法特殊的是

  • 本书涵盖的一些令人兴奋的 GAN 应用

机器能否思考的观念比计算机本身还要古老。在 1950 年,著名的数学家、逻辑学家和计算机科学家艾伦·图灵——他因在解码纳粹战争加密机恩尼格玛中的角色而闻名——撰写了一篇将使他的名字永垂不朽的论文,“计算机与智能。”

在这篇论文中,图灵提出了一种他称之为“模仿游戏”的测试,今天更广为人知的是“图灵测试”。在这个假设的场景中,一个不知情观察者与两个在封闭门后的人交谈:一个,是人类;另一个,是计算机。图灵认为,如果观察者无法分辨出哪个人和哪台机器,那么计算机通过了测试,必须被认为是有智能的。

任何尝试与自动化聊天机器人或语音智能助手进行对话的人都知道,计算机还有很长的路要走才能通过这个看似简单的测试。然而,在其他任务中,计算机不仅与人类的表现持平,甚至超越了人类——即使在最近还被认为连最聪明的算法都无法触及的领域,如超人类准确的面部识别或掌握围棋游戏。^([1])

¹

参见“在 LFW 上超越人类水平的面部验证性能:基于 GaussianFace”,作者:Chaochao Lu 和 Xiaoou Tang,2014 年,arXiv.org/abs/1404.3840。另见《纽约时报》文章“谷歌的 AlphaGo 战胜中国围棋大师,人工智能取得胜利”,作者:Paul Mozur,2017 年,mng.bz/07WJ

机器学习算法擅长在现有数据中识别模式,并利用这些洞察力来完成诸如 分类(将正确的类别分配给示例)和 回归(根据多种输入估计数值)等任务。然而,当要求生成新数据时,计算机却遇到了困难。一个算法可以击败国际象棋大师,预测股价走势,或判断信用卡交易是否可能欺诈。相比之下,与亚马逊的 Alexa 或苹果的 Siri 进行闲聊的任何尝试都是注定要失败的。的确,人类最基本和最本质的能力——包括愉快的交谈或创作原创作品——甚至可以让最复杂的超级计算机陷入数字痉挛。

所有这一切都在 2014 年发生了变化,当时蒙特利尔大学的博士生 Ian Goodfellow 发明了生成对抗网络(GANs)。这项技术通过使用不是单个而是两个独立的神经网络,使计算机能够生成逼真的数据。GANs 并不是第一个用于生成数据的计算机程序,但它们的结果和多功能性使它们与其他所有程序都不同。GANs 取得了长期被认为对人工系统几乎不可能的显著成果,例如生成具有真实世界质量的假图像,将涂鸦变成类似照片的图像,或将马的视频变成奔跑的斑马——所有这些都不需要大量精心标注的训练数据。

生成对抗网络(GANs)如何使机器数据生成能力得到如此大的提升的一个令人信服的例子是合成人类面部,如图 1.1 所示。就在 GANs 被发明的那年,即 2014 年,机器能产生的最好的结果是一个模糊的面孔——甚至那也被庆祝为划时代的成功。到了 2017 年,仅仅三年后,GANs 的进步使计算机能够合成与高分辨率肖像照片相媲美的假面孔。在这本书中,我们将揭开使这一切成为可能的算法的面纱。

图 1.1. 人类面部生成进展

(来源:“人工智能的恶意使用:预测、预防和缓解”,作者:Miles Brundage 等,2018 年,arxiv.org/abs/1802.07228.)

1.1. 什么是生成对抗网络?

生成对抗网络(GANs)是一类机器学习技术,它由两个同时训练的模型组成:一个(生成器)被训练生成假数据,另一个(判别器)被训练从真实示例中辨别假数据。

词语生成表明了该模型的整体目的:创建新的数据。GAN 将学习生成的数据取决于训练集的选择。例如,如果我们想让 GAN 合成看起来像达芬奇的作品的图像,我们会使用达芬奇艺术作品的训练数据集。

术语对抗指向构成 GAN 框架的两个模型之间的游戏般、竞争性的动态:生成器和判别器。生成器的目标是创建与训练集中真实数据不可区分的示例。在我们的例子中,这意味着制作看起来就像达芬奇的作品的画作。判别器的目标是区分生成器产生的假示例和来自训练数据集的真实示例。在我们的例子中,判别器扮演了一个评估被认为是达芬奇作品的画作真实性的艺术专家的角色。这两个网络不断地试图欺骗对方:生成器在创建令人信服的数据方面越擅长,判别器在区分真实示例和假示例方面就需要越出色。

最后,词语网络表明了最常用来表示生成器和判别器的机器学习模型类别:神经网络。根据 GAN 实现的复杂性,这些网络可以从简单的前馈神经网络(你将在第三章 chapter 3 中看到)到卷积神经网络(你将在第四章 chapter 4 中看到)或甚至更复杂的变体,例如 U-Net(你将在第九章 chapter 9 中看到)。

1.2. GAN 是如何工作的?

支撑 GAN 的数学原理相当复杂(你将在后面的章节中了解到,特别是第三章 chapters 3 和第五章 5);幸运的是,许多现实世界的类比可以使 GAN 更容易理解。之前,我们讨论了艺术伪造者(生成器)试图欺骗艺术专家(判别器)的例子。伪造者制作的假画越令人信服,艺术专家在确定其真实性方面的能力就必须越强。在相反的情况下也是如此:艺术专家在判断某幅画是否为真时的能力越强,伪造者就必须越改进以避免被当场抓获。

另一个常用来描述生成对抗网络(GANs)的隐喻——伊恩·古德费勒本人也喜欢使用的隐喻——是一个罪犯(生成器)伪造货币,和一个侦探(判别器)试图抓住他。伪造的钞票看起来越逼真,侦探在检测它们时就必须越好,反之亦然。

在更技术性的术语中,生成器的目标是生成能够捕捉训练数据集特征的示例,以至于它生成的样本看起来与训练数据无法区分。生成器可以被视为一个反向的对象识别模型。对象识别算法通过学习图像中的模式来识别图像的内容。生成器不是识别模式,而是从零开始学习创建它们;实际上,生成器的输入通常只是一个随机数字的向量。

生成器通过从判别器的分类中获得的反馈来学习。判别器的目标是确定一个特定示例是真实的(来自训练数据集)还是伪造的(由生成器创建)。相应地,每次判别器被欺骗将伪造图像分类为真实时,生成器就知道它做得很好。相反,每次判别器正确地拒绝生成器生成的图像为伪造时,生成器就会收到需要改进的反馈。

判别器也在不断改进。像任何分类器一样,它通过学习其预测与真实标签(真实或伪造)之间的距离来学习。因此,随着生成器在生成看起来逼真的数据方面变得更好,判别器在区分伪造数据和真实数据方面也变得更好,并且两个网络继续同时改进。

表 1.1 总结了关于两个 GAN 子网络的关键要点。

表 1.1. 生成器和判别器子网络
生成器 判别器

| 输入 | 一个随机数字的向量 | 判别器从两个来源接收输入:

  • 来自训练数据集的真实示例

  • 来自生成器的伪造示例

|

输出 努力尽可能令人信服的伪造示例 预测输入示例是真实的概率
目标 生成与训练数据集成员无法区分的伪造数据 区分来自生成器的伪造示例和来自训练数据集的真实示例

1.3. GANs 在行动中

现在你已经对 GAN 及其组成网络有了高层次的理解,让我们更仔细地看看系统在实际中的应用。想象一下,我们的目标是教会一个 GAN 生成看起来逼真的手写数字。(你将在第三章(../Text/kindle_split_012.xhtml#ch03)中学习实现这样的模型,并在第四章(../Text/kindle_split_013.xhtml#ch04)中对其进行扩展。)图 1.2 说明了核心 GAN 架构。

图 1.2。两个 GAN 子网络、它们的输入和输出以及它们的交互

让我们详细了解一下图中的内容:

  1. 训练数据集— 我们希望生成器能够以近乎完美的质量学习的真实例数据集。在这种情况下,数据集由手写数字的图像组成。这个数据集作为输入 (x) 传递给判别器网络。

  2. 随机噪声向量— 生成器网络的原始输入 (z)。这个输入是一个随机数字向量,生成器使用它作为合成假例的起点。

  3. 生成器网络— 生成器以随机数字向量 (z) 作为输入,并输出假例 (x)。其目标是使其产生的假例与训练数据集中的真实例子的区别不可辨。

  4. 判别器网络— 判别器以来自训练集的真实例 (x) 或生成器产生的假例 (x) 作为输入。对于每个例子,判别器确定并输出该例子为真实的概率。

  5. 迭代训练/调整— 对于判别器的每个预测,我们确定其好坏——就像我们会对一个常规分类器做的那样——并使用结果通过反向传播迭代调整判别器和生成器网络:

    • 更新判别器的权重和偏差,以最大化其分类准确率(最大化正确预测的概率:x 为真实,x 为假)。

    • 更新生成器的权重和偏差,以最大化判别器将 x 错误分类为真实例的概率。

1.3.1. GAN 训练

了解各种 GAN 组件的目的是像看发动机的快照一样:除非我们看到它在运动中,否则无法完全理解。这正是本节的内容。首先,我们介绍 GAN 训练算法;然后,我们展示训练过程,以便您可以看到架构图的实际应用。

GAN 训练算法

对于每个训练迭代执行

  1. 训练判别器:

    1. 从训练数据集中随机取一个真实例 x

    2. 获取一个新的随机噪声向量 z,并使用生成器网络合成一个假例 x

    3. 使用判别器网络对 xx 进行分类。

    4. 计算分类错误,并将总错误反向传播以更新判别器的可训练参数,寻求最小化分类错误。

  2. 训练生成器:

    1. 获取一个新的随机噪声向量 z,并使用生成器网络合成一个假例 x

    2. 使用判别器网络对 x 进行分类。

    3. 计算分类错误,并将错误反向传播以更新生成器的可训练参数,寻求最大化判别器的错误。

结束 for

GAN 训练可视化

图 1.3 展示了 GAN 训练算法。图中的字母代表 GAN 训练算法步骤列表。

图 1.3。GAN 训练算法有两个主要部分。这两个部分,判别器训练和生成器训练,描绘了训练过程中相应阶段的同一 GAN 网络在不同时间快照下的情况。

子图图例

  1. 训练判别器:

    1. 从训练数据集中随机取一个真实例子 x

    2. 获取一个新的随机噪声向量 z,并使用生成器网络合成一个假例 *x**。

    3. 使用判别器网络对 xx* 进行分类。

    4. 计算分类误差并将总误差反向传播以更新判别器的权重和偏差,寻求最小化分类误差。

  2. 训练生成器:

    1. 获取一个新的随机噪声向量 z,并使用生成器网络合成一个假例 *x**。

    2. 使用判别器网络对 x* 进行分类。

    3. 计算分类误差并将误差反向传播以更新生成器的权重和偏差,寻求最大化判别器的误差。

1.3.2. 达到均衡

你可能会想知道 GAN 训练循环何时应该停止。更确切地说,我们如何知道 GAN 已经完全训练好,以便我们可以确定适当的训练迭代次数?对于常规神经网络,我们通常有一个明确的目标要实现和衡量。例如,当训练一个分类器时,我们衡量训练集和验证集上的分类误差,并在验证误差开始变差时停止过程(以避免过拟合)。在 GAN 中,两个网络有竞争目标:当一个网络变得更好时,另一个网络会变得更差。我们如何确定何时停止?

熟悉博弈论的人可能会认出这种设置是一个零和博弈——一种一个玩家的收益等于另一个玩家损失的情况。当一个玩家提高一定量时,另一个玩家会以相同的量变差。所有零和博弈都有一个纳什均衡,即一个点,在这个点上,任何玩家都不能通过改变自己的行动来改善自己的情况或收益。

当满足以下条件时,GAN 达到纳什均衡:

  • 生成器产生的假例在训练数据集中与真实数据无法区分。

  • 判别器最多只能随机猜测一个特定例子是真实还是假(即,以 50/50 的概率猜测一个例子是真实的)。

注意

纳什均衡是以美国经济学家和数学家约翰·福布斯·纳什(John Forbes Nash Jr.)的名字命名的,他的生平和事业在传记《美丽心灵》(A Beautiful Mind)中被捕捉,并启发了同名的电影。

让我们来说服你为什么这是真的。当每个假例子(x)与训练数据集中来的真实例子(x)真正无法区分时,判别器无法利用任何东西来区分它们。因为它收到的例子中有一半是真实的,一半是假的,判别器能做的最好的事情就是掷硬币,以 50%的概率将每个例子分类为真实或假。

生成器也处于一个点,它从进一步的调整中无法获得任何收益。因为它产生的例子已经与真实例子无法区分,即使是对它将随机噪声向量(z)转换为假例子(x)的过程进行微小的改变,也可能给判别器提供如何从真实数据中辨别假例子的线索,从而使生成器变得更糟。

当达到平衡时,人们说 GAN 已经收敛。这时事情变得复杂。在实践中,由于在非凸游戏中达到收敛的复杂性巨大,因此几乎不可能找到 GAN 的纳什均衡(关于收敛的更多内容将在后续章节中介绍,尤其是第五章)。确实,GAN 的收敛仍然是 GAN 研究中最重要的未解问题之一。

幸运的是,这并没有阻碍 GAN 研究或生成对抗学习的许多创新应用。即使在没有严格的数学保证的情况下,GAN 也取得了显著的经验性成果。本书涵盖了其中最具影响力的选择,下一节将预览其中的一些。

1.4. 为什么研究 GAN?

自从它们的发明以来,GAN 们被学术界和行业专家誉为深度学习中最有影响力的创新之一。Facebook 人工智能研究总监 Yann LeCun 甚至说,GAN 及其变体是“过去 20 年来深度学习中最酷的想法”。^([2])

²

请参阅 Cade Metz 在Wired杂志 2017 年发表的“Google 的对抗神经网络对决以变得更聪明”,mng.bz/KE1X

这种兴奋是有充分理由的。与其他在机器学习领域可能家喻户晓的进步不同,GAN 们既吸引了研究人员的想象力,也吸引了更广泛的公众。它们被《纽约时报》、《BBC》、《科学美国人》和其他许多知名媒体所报道。确实,正是那些令人兴奋的 GAN 结果可能促使你最初购买这本书。(对吗?)

最值得注意的是,GAN 能够创建超逼真的图像。图 1.4 中的所有面孔都不属于真实人类;它们都是假的,展示了 GAN 合成具有照片级质量图像的能力。这些面孔是使用渐进式 GAN 生成的,这一技术在第六章中有介绍。

图 1.4。这些逼真但虚假的人类面孔是由训练在名人高清肖像照片上的渐进式 GAN 合成的。

图片

(来源:“通过渐进式增长 GAN 以改善质量、稳定性和多样性”,由 Tero Karras 等人撰写的,发表于 2017 年,arxiv.org/abs/1710.10196。)

另一项引人注目的 GAN 成就是图像到图像的翻译。类似于一个句子可以从中文翻译成西班牙语,GANs 可以将一个图像从一种领域转换到另一种领域。如图 1.5 所示,GANs 可以将马的图像转换成斑马的图像(反之亦然),以及将照片转换成类似莫奈的画作——这一切几乎无需监督和任何标签。使这一切成为可能的 GAN 变体被称为CycleGAN;你将在第九章中详细了解它。

图 1.5。通过使用名为 CycleGAN 的 GAN 变体,我们可以将莫奈的画作变成照片,或将斑马的图像变成马的描绘,反之亦然。

图片

(来源:参见 Jun-Yan Zhu 等人撰写的《使用循环一致对抗网络进行无配对图像到图像翻译》,发表于 2017 年,arxiv.org/abs/1703.10593。)

更注重实际应用的 GAN 用例同样引人入胜。在线巨头亚马逊正在尝试利用 GANs 进行时尚推荐:通过分析无数套装,系统学会生产符合任何给定风格的全新商品.^([3])在医学研究中,GANs 被用于通过合成示例增强数据集,以提高诊断准确性.^([4])在第十一章—在你掌握了训练 GANs 及其变体的所有细节之后—你将详细探讨这两个应用。

³

参见威尔·奈特撰写的《亚马逊已开发人工智能时尚设计师》,发表于 2017 年《麻省理工学院技术评论》,mng.bz/9wOj

参见 Maayan Frid-Adar 等人撰写的《使用 GAN 进行合成数据增强以改进肝脏病变分类》,发表于 2018 年,arxiv.org/abs/1801.02385

GANs 也被视为实现通用人工智能的重要基石,^([5])这是一种能够匹配人类认知能力,在几乎任何领域获得专业知识的人工系统——从行走中涉及的肌肉技能,到语言,再到创作十四行诗所需的创造性技能。

参见托尼·彭撰写的《OpenAI 创始人:短期 AGI 是一个严肃的可能性》,发表于 2018 年 Synced,mng.bz/j5Oa。另见 Soumith Chintala 撰写的《通过对抗网络实现无监督学习之路》,发表于 2016 年 f Code,mng.bz/WOag

但由于能够生成新的数据和图像,GANs 也具有潜在的危险性。关于虚假新闻的传播和危害已经讨论了很多,但 GANs 创建可信虚假视频片段的潜力令人不安。在 2018 年一篇关于 GANs 的恰当标题的文章《如何通过人工智能的“猫鼠游戏”生成可信的虚假照片》的结尾,《纽约时报》的记者凯德·梅茨和基思·柯林斯讨论了 GANs 被利用来创建和传播令人信服的错误信息的令人担忧的前景,包括世界领导人的虚假视频片段。《麻省理工学院技术评论》旧金山分社社长马丁·吉尔斯在 2018 年的文章《GAN 之父:赋予机器想象力的男人》中重申了他们的担忧,并提到了另一个潜在风险:在熟练黑客的手中,GANs 可以以前所未有的规模直观地利用系统漏洞。正是这些担忧促使我们在第十二章中讨论 GANs 的伦理考量。

GANs(生成对抗网络)可以为世界带来许多好处,但所有技术创新都存在滥用风险。这里必须有一种意识哲学:因为无法“取消发明”一项技术,所以确保像你这样的人意识到这项技术的快速出现及其巨大潜力至关重要。

在这本书中,我们只能触及 GANs 可能性的表面。然而,我们希望这本书能为你提供必要的理论知识与实践技能,以便继续探索这个领域中最吸引你的任何方面。

因此,无需多言,让我们深入探讨吧!

摘要

  • GANs 是一种深度学习技术,它利用两个神经网络之间的竞争动态来合成真实的数据样本,例如虚假的逼真图像。构成 GAN 的两个网络如下:

    • 生成器,其目标是通过产生与训练数据集不可区分的数据来欺骗判别器

    • 判别器,其目标是正确区分来自训练数据集的真实数据和生成器产生的虚假数据

  • GANs 在许多不同领域都有广泛的应用,例如时尚、医学和网络安全。

第二章. 使用自编码器进行生成建模简介

本章涵盖

  • 将数据编码到潜在空间(降维)以及随后的维度扩展

  • 在变分自编码器的背景下理解生成建模的挑战

  • 使用 Keras 和自编码器生成手写数字

  • 理解自编码器的局限性以及 GANs 的动机

我将这一章献给我的祖母,奥雷利·朗罗瓦,她在我们完成这项工作的时候去世了。我们将非常怀念她。

雅库布

你可能想知道为什么我们选择将这一章包含在书中。有三个核心原因:

  • 生成模型对于大多数人来说是一个新的领域。 大多数接触到机器学习的人通常首先接触到机器学习中的分类任务,并且接触得更为广泛——也许是因为它们通常更直接。因此,通过生成模型我们试图产生一个看起来逼真的新示例,这个领域理解得较少。因此,我们决定在深入探讨生成对抗网络(GANs)之前,先包括一个涵盖生成模型章节,特别是在考虑到自动编码器——GANs 最接近的前驱——的资源和研究丰富的情况下。但如果你想要直接进入新而激动人心的部分,请随意跳过这一章。

  • 生成模型非常具有挑战性。 由于生成模型没有得到充分的表现,大多数人不知道典型模型的样子及其挑战。尽管自动编码器在许多方面更接近于最常教授的模型(例如,我们将在后面讨论的显式目标函数),但它们仍然面临着 GANs 面临许多挑战——例如评估样本质量有多困难。第五章更深入地讨论了这一点。

  • 生成模型是当今文献的重要组成部分。 自动编码器本身也有其用途,正如我们在本章中讨论的那样。它们也是一个活跃的研究领域,甚至在某些领域处于最前沿,并被许多 GAN 架构明确使用。其他 GAN 架构将它们用作隐含的灵感或心理模型——例如在第九章中介绍的 CycleGAN。

2.1. 生成模型简介

你应该熟悉深度学习如何将原始像素转换为,例如,类别预测。例如,我们可以取包含图像像素的三个矩阵(每个颜色通道一个)并通过一个变换系统来获得最后的单个数字。但如果我们想反方向进行呢?

我们从确定我们想要生产的内容开始,并在变换的另一端获得图像。这就是最简单、最非正式形式的生成模型;我们在整本书中会添加更多深度。

更正式一点,我们取一个特定的规定(z)——对于这个简单案例,让我们假设它是一个介于 0 和 9 之间的数字——并试图得到一个生成的样本(x)。理想情况下,这个x看起来应该和另一个真实样本x一样逼真。这个规定z存在于一个潜在空间中,并作为一个灵感,以确保我们不会总是得到相同的输出x。这个潜在空间是一个学习到的表示——希望以我们思考的方式对人们有意义(“解耦”)。不同的模型将学习到相同数据的不同的潜在表示。

我们在第一章中看到的随机噪声向量通常被称为潜在空间的样本。潜在空间是数据点的简单、隐藏表示。在我们的上下文中,它表示为 z,而简单只是意味着低维的——例如,一个包含 100 个数字的向量或数组,而不是我们将使用的样本的 768 维。在许多方面,一个好的潜在表示将允许你在这个空间中将相似的事物分组。我们将在图 2.3 中解释自编码器上下文中的潜在意味着什么,并展示它如何影响我们在图 2.6 和 2.7 中生成的样本,但在我们能够做到这一点之前,我们将描述自编码器是如何工作的。

2.2. 自编码器在高级层面上是如何工作的?

正如他们的名字所暗示的,自编码器帮助我们自动地编码数据。自编码器由两部分组成:编码器和解码器。为了解释的目的,让我们考虑一个用例:压缩。

想象一下,你正在给你的祖父母写一封信,关于你作为机器学习工程师的职业。你只有一页纸来解释你所做的一切,以便他们能够理解,考虑到他们对世界的知识和信念。

现在想象一下,如果你的祖父母患有急性健忘症,完全记不起你的一切。这感觉是不是更难了?这可能是因为现在你必须解释所有术语。例如,他们仍然可以阅读并理解你信中的基本内容,比如你描述你的猫做了什么,但机器学习工程师的概念可能对他们来说是陌生的。换句话说,他们从潜在空间 zx 的学习转换已经被(几乎)随机初始化。在你能够解释之前,你必须首先在他们的大脑中重新训练这些心理结构。你必须通过传递概念 x 并观察他们是否能够以有意义的方式将它们(x)重新呈现给你来训练他们的自编码器。这样,你可以测量他们的错误,称为重建损失(|| xx* ||)。

暗示地,我们每天都在压缩数据或信息,这样我们就不必花费大量时间解释已知的概念。人类的交流充满了自编码器,但它们是上下文相关的:我们对我们祖父母的解释,我们不必对我们工程同事解释,比如机器学习模型是什么。因此,某些人类潜在空间比其他潜在空间更适合,这取决于上下文。我们可以直接跳到他们自编码器已经理解的简洁表示。

我们可以压缩,因为将某些重复的概念简化为我们已经同意的抽象(例如,职位名称)是有用的。自动编码器可以系统地、自动地发现这些信息效率模式,定义它们,并将它们用作捷径来增加信息吞吐量。因此,我们只需要传输z,它通常是低维的,从而节省我们带宽。

从信息论的角度来看,你试图尽可能多地通过“信息瓶颈”(你的信件或口头交流)传递信息,而不牺牲太多的理解。你几乎可以想象这是一个只有你和你的家人理解的秘密捷径,但它已经针对你经常讨论的主题进行了优化。^([1)] 为了简单起见,并专注于压缩,我们选择忽略这样一个事实,即单词是一个明确的模型,尽管大多数单词背后也有巨大的上下文相关复杂性。

¹

实际上,著名的欧洲金融家家族罗思柴尔德家族在其信件中就是这样做的,这也是他们在金融上如此成功的原因。

定义

潜在空间是数据的隐藏表示。与表达单词或图像(例如,我们例子中的“机器学习工程师”,或图像的 JPEG 编解码器)的未压缩版本不同,自动编码器根据其对数据的理解压缩和聚类它们。

2.3. 自动编码器对 GANs 是什么?

与自动编码器相比,一个关键的区别是我们使用一个损失函数从头到尾训练整个网络,而生成对抗网络(GANs)对生成器和判别器有各自不同的损失函数。现在让我们看看自动编码器相对于生成对抗网络所处的上下文。如图 2.1 所示,两者都是人工智能(AI)和机器学习(ML)的生成模型的一部分。在自动编码器(或它们的变分替代品,VAEs)的情况下,我们有一个明确写出的函数,我们正在尝试优化(一个成本函数);但在 GANs 的情况下(你将了解到),我们没有像均方误差、准确率或 ROC 曲线下的面积那样简单的显式指标来优化。^([2)] GANs 相反,有两个相互竞争的目标,这些目标不能写在一个函数中。

²

代价函数(也称为损失函数目标函数)是我们试图优化/最小化的。例如,在统计学中,这将是均方根误差(RMSE)。均方根误差(RMSE)是一个数学函数,通过取真实值与我们的预测之间的差异的平方根来给出误差。在统计学中,我们通常希望评估分类器在多个假阳性和假阴性组合上的表现。曲线下面积(AUC)帮助我们做到这一点。有关更多详细信息,维基百科有出色的解释,因为这一概念超出了本书的范围。

图 2.1. 将 GAN 和自动编码器置于 AI 生态图中。不同的研究人员可能会有不同的绘制方式,但我们将把这个争论留给学者们。

图片

2.4. 自动编码器由什么组成?

当我们观察自动编码器的结构时,我们将使用图像作为例子,但这种结构也适用于其他情况(例如,语言,如我们关于给祖父母写信的例子)。像机器学习中的许多进步一样,自动编码器的高级思想是直观的,并遵循以下简单步骤,如图 2.2 所示:

  1. 编码网络:我们取一个表示 x(例如,一个图像),然后使用学习到的编码器(通常是一个单层或多层神经网络)将维度从 y 减少到 z

    图 2.2. 在我们的信件例子中使用自动编码器遵循以下步骤:(1)压缩你关于机器学习工程师的所有知识,然后(2)将其组合到潜在空间(给祖母的信)。当她,使用她对单词的理解作为解码器(3),重建一个(有损)版本的意义时,你得到一个在相同空间(在你祖母的头脑中)的原输入表示,即你的思想。

    图片

  2. 潜在空间 (z): 随着我们的训练,我们试图建立具有某些意义的潜在空间。潜在空间通常是较小维度的表示,并作为中间步骤。在我们的数据表示中,自动编码器试图“组织其思想”。

  3. 解码网络:我们使用解码器将原始对象重建到原始维度。这通常是通过一个与编码器镜像的神经网络来完成的。这是从 zx* 的步骤。我们应用编码过程的逆过程,例如,从潜在空间的 256 像素值长向量中获取一个 784 像素值长的重建向量(一个 28 × 28 图像)。

这里有一个自动编码器训练的例子:

  1. 我们将图像 x 输入到自动编码器中。

  2. 我们得到 *x**,图像的重建。

  3. 我们测量重建损失——即 xx* 之间的差异。

    • 这是通过计算 xx* 像素之间的距离(例如,平均绝对误差)来完成的。

    • 这为我们提供了一个显式的目标函数(|| x –*x** ||),我们可以通过梯度下降的一种版本来优化它。

因此,我们正在尝试找到编码器和解码器的参数,这些参数将最小化我们通过使用梯度下降更新的重建损失。

就这样!我们完成了。现在你可能想知道这有什么用或为什么重要。你会感到惊讶!

2.5. 自动编码器的使用

尽管它们很简单,但有许多原因需要关注自动编码器:

  • 首先,我们免费获得压缩!这是因为 图 2.2 中的中间步骤(2)变成了一个在潜在空间维度上智能减少的图像或对象。请注意,在理论上,这可以比原始输入小几个数量级。这显然不是无损的,但我们有权使用这个副作用,如果我们愿意的话。

  • 仍然使用潜在空间,我们可以想到许多实际应用,例如一个 单类分类器(一个异常检测算法),其中我们可以看到在减少的、更易于搜索的潜在空间中的项目,以检查与目标类的相似性。这可以在搜索(信息检索)或异常检测设置(在潜在空间中比较接近度)中工作。

  • 另一个用例是数据去噪或黑白图像的着色.^([3]) 例如,如果我们有一张旧照片或视频,或者非常嘈杂的——比如说,二战图像——我们可以使它们变得更少噪音,并重新添加颜色。因此,这与 GANs 的相似性,GANs 也擅长这些类型的应用。

    ³

    关于着色黑白图像的更多信息,请参阅 Emil Wallner 的“着色灰度图像”,GitHub 上有 (mng.bz/6jWy)。

  • 一些 GAN 架构——例如 BEGAN^([4])——使用自动编码器作为其架构的一部分,以帮助它们稳定训练,这在后面你会发现是至关重要的。

    BEGAN 是边界均衡生成对抗网络(Boundary Equilibrium Generative Adversarial Networks)的缩写。这个有趣的 GAN 架构是第一个将自动编码器作为设置一部分使用的。

  • 这些自动编码器的训练不需要标记数据。我们将在下一节中了解到这一点以及为什么无监督学习如此重要。这使得我们的生活变得更加容易,因为这只是自我训练,不需要我们寻找标签。

  • 最后,但绝对不是最不重要的,我们可以使用自动编码器来生成新的图像。自动编码器已经应用于从数字到面孔到卧室的任何东西,但通常图像的分辨率越高,性能越差,因为输出往往看起来模糊。但对于 MNIST 数据集——正如你稍后会发现的那样——和其他低分辨率图像,自动编码器工作得很好;你很快就会看到代码的样子!

定义

修改后的国家标准与技术研究院(MNIST)数据库是一组手写数字数据集。维基百科对这个在计算机视觉文献中极其流行的数据集有很好的概述。

所以所有这些事情都可以做到,仅仅因为我们找到了我们已有数据的新表示形式。这种表示形式很有用,因为它揭示了核心信息,这些信息是原生压缩的,但基于潜在表示来操作或生成新数据也更加容易!

2.6. 无监督学习

在上一章中,我们已经讨论了无监督学习,但没有使用这个术语。在本节中,我们将更深入地探讨。

定义

无监督学习是一种机器学习方法,我们从中学习数据本身,而不需要额外的标签来解释这些数据的意义。例如,聚类是无监督的——因为我们只是试图发现数据的潜在结构;但异常检测通常是监督的,因为我们需要人类标记的异常。

在本章中,你将了解为什么无监督机器学习是不同的:我们可以使用任何数据,而无需为特定目的对其进行标记。我们可以随意加入来自互联网的所有图像,而无需注释每个样本的目的,对于我们可能关心的每种表示。例如:这张图片里有没有狗?有没有车?

相反,在监督学习中,如果你没有该特定任务的标签,(几乎)所有的标签都可能无法使用。如果你试图制作一个能够从谷歌街景中分类汽车的分类器,但你没有这些图像的动物标签,使用同一数据集训练一个能够用同一数据集分类动物的分类器基本上是不可能的。即使动物经常出现在这些样本中,你也需要回去要求你的标记者重新标记谷歌街景数据集中的动物。

从本质上讲,在我们知道用例之前,我们需要考虑数据的应用,这是困难的!但对于许多压缩类型任务,你总是有标记数据:你的数据。一些研究人员,如弗朗索瓦·肖莱特(Google 的研究科学家和 Keras 的作者),将这种类型的机器学习称为自监督。在这本书的大部分内容中,我们唯一的标签将是示例本身或数据集中的任何其他示例。

由于我们的训练数据也充当我们的标签,从这一关键角度来看,训练许多这些算法变得容易得多:我们现在有更多的数据可以工作,而且我们不需要等待数周并支付数百万美元来获取足够的标记数据。

2.6.1. 对旧想法的新看法

自编码器本身是一个相当古老的想法——至少当你从机器学习作为一个领域的角度来看它的年龄时。但是鉴于今天每个人都在研究“某种”深度学习,人们成功地将深度学习应用于编码器和解码器的一部分,这应该不会让任何人感到惊讶。

自编码器由两个神经网络组成:一个编码器和一个解码器。在我们的例子中,两者都有激活函数,^([5]),我们将为每个网络使用一个中间层。这意味着每个网络中有两个权重矩阵——一个从输入到中间层,然后一个从中间层到潜在层。然后,我们还有一个从潜在层到不同的中间层,然后一个从中间层到输出层。如果我们每个网络只有一个权重矩阵,我们的过程将类似于一个已建立的统计分析技术,称为“主成分分析(PCA)”。如果你有线性代数的背景,你应该对这一领域有广泛的了解。

我们在将任何来自早期层计算的输出通过一个激活函数传递到下一个层之前,将其传递给下一个层。人们经常选择一个整流线性单元(ReLU)——定义为max(0, x)。我们不会深入探讨激活函数,因为它们本身就可以是一个长篇博客文章的主题。

注意

在学习解决方案的方式上存在一些技术差异——例如,PCA 是数值确定的,而自编码器通常使用随机优化器进行训练。解决方案的最终形式也存在差异。但我们不会给你上一堂关于其中一个如何给出正交基以及它们如何本质上仍然覆盖相同的向量空间的长篇大论——尽管如果你碰巧知道这意味着什么,那么恭喜你。

2.6.2. 使用自编码器进行生成

在本章的开头,我们提到自编码器可以用来生成数据。一些真正热衷于此的人可能已经在思考潜在空间的使用以及它是否可以被重新用于其他目的……这完全可能!(如果你答对了,你可以给自己一个官方的、认可的自我点赞!)

但你可能不会买这本书来让自己看起来很傻,所以我们直接进入正题。如果我们回到你祖父母的例子,并使用一个稍微不同的视角,使用自编码器作为生成模型可能开始变得有意义。例如,想象一下你对“工作”这个概念的理解成为解码网络的输入。将“工作”这个词写在纸上的想法视为潜在空间输入,而你祖父母头脑中的工作概念作为输出。

在这种情况下,我们看到潜在空间编码(一个书面词,结合你祖父母阅读和理解概念的能力)变成了一种在他们的脑海中生成想法的生成模型。书面字母充当灵感或某种潜在向量,而输出——想法——与原始输入处于相同的高维空间。你祖父母的想法和你的一样复杂——尽管略有不同。

现在,让我们回到图像领域。我们在一组图像上训练我们的自动编码器。因此,我们调整编码器和解码器的参数,以找到两个网络适当的参数。我们还对例子在潜在空间中的表示方式有了感觉。对于生成,我们切断编码器部分,只使用潜在空间和解码器。图 2.3 显示了生成过程的示意图。

图 2.3. 因为我们知道从训练中我们的例子在潜在空间中的位置,我们可以轻松地生成与模型所见相似的例子。即使不是,我们也可以轻松地在潜在空间中进行迭代或网格搜索,以确定我们的模型可以生成的表示类型。

图片

(图片改编自 GitHub 上 Mat Leonard 的简单自动编码器项目,mng.bz/oNXM。)

2.6.3. 变分自动编码器

你可能想知道:变分自动编码器和“常规”自动编码器之间的区别是什么?这所有的一切都与神奇的潜在空间有关。在变分自动编码器的情况下,我们选择将潜在空间表示为一个具有学习到的均值和标准差的分布,而不是仅仅是一组数字。通常,我们选择多元高斯分布,但具体是什么或者为什么我们选择这种分布而不是其他分布现在并不重要。如果你想回顾一下这可能是什么样子,请查看图 2.5。

如此一来,那些更倾向于统计学的你们可能已经意识到,变分自动编码器是一种基于贝叶斯机器学习的技术。在实践中,这意味着我们必须学习分布,这增加了进一步的约束。换句话说,频率派自动编码器会试图将潜在空间学习为一个数字数组,但贝叶斯——例如,变分——自动编码器会试图找到定义分布的正确参数。

我们随后从潜在分布中进行采样并得到一些数字。我们将这些数字通过解码器。我们得到一个看起来像是原始数据集中的一些东西的例子,但它是由模型新创建的。哇塞!

2.7. 代码即生命

在本书中,我们使用了一个流行的、深度学习的高级 API,称为Keras。我们强烈建议你熟悉它。如果你还不熟悉它,网上有大量的免费资源,包括像 Towards Data Science (towardsdatascience.com)这样的平台,我们经常在上面贡献。如果你想从书籍中了解更多关于 Keras 的信息,存在一些很好的资源,包括另一本优秀的 Manning 书籍,由 Keras 的作者和创建者 François Chollet 所著的用 Python 进行深度学习

Keras 是几个深度学习框架(TensorFlow、Microsoft Cognitive Toolkit (CNTK)和 Theano)的高级 API。它易于使用,并允许你在更高的抽象级别上工作,因此你可以专注于概念,而不是记录每个标准的乘法、偏差、激活和池化^([6])或过多地担心变量作用域。

池化块是对一层进行的操作,允许我们将多个输入合并为更少的输入——例如,有一个包含四个数字的矩阵,并得到一个最大值作为单个数字。这是计算机视觉中常用的操作,用于降低复杂性。

为了展示 Keras 的真实力量以及它是如何简化编写神经网络的过程,我们将查看其最简单的变分自动编码器示例^([7])。在本教程中,我们使用 Keras 的功能性 API,以更面向函数的方法编写深度学习代码,但在后续教程中,我们将展示序列 API(另一种方法),因为事情变得更加复杂。

作者为了简化,对示例进行了高度修改,来自mng.bz/nQ4K

本练习的目的是根据潜在空间生成手写数字。我们将创建一个对象,generatordecoder,它可以使用predict()方法根据输入种子生成新的手写数字示例,其中输入种子只是潜在空间向量。当然,我们必须使用 MNIST,因为我们不希望任何人产生其他数据集可能存在的想法;参见图 2.4。

图 2.4。计算机视觉研究人员是如何思考的。无需多言。

图片

(来源:Facebook 上的人工智能青少年的人工智能迷因,mng.bz/vNjM。)

在我们的代码中,我们首先必须导入所有依赖项,如下所示列表。为了参考,此代码已与 Keras 的最新版本 2.2.4 和 TensorFlow 的最新版本 1.12.0 进行了检查。

列表 2.1。标准导入
from keras.layers import Input, Dense, Lambda
from keras.models import Model
from keras import backend as K
from keras import objectives
from keras.datasets import mnist
import numpy as np

下一步是设置全局变量和超参数,如代码清单 2.2 所示。它们都应该很熟悉:原始维度是 28 × 28,这是标准尺寸。然后我们将 MNIST 数据集中的图像展平,得到一个 784 维(28 × 28)的向量。我们还将有一个单一的中间层,例如 256 个节点。但请尝试其他尺寸;这就是为什么它是超参数的原因!

代码清单 2.2. 设置超参数
batch_size = 100
original_dim = 28*28          ***1***
latent_dim = 2
intermediate_dim = 256
nb_epoch = 5                  ***2***
epsilon_std = 1.0
  • 1 MNIST 图像的高度 × 宽度

  • 2 训练的轮数

在代码清单 2.3 中,我们开始构建编码器。为了实现这一点,我们使用 Keras 的功能 API。

注意

功能性 API使用 Python 中的 lambda 函数来返回另一个函数的构造函数,该函数接受另一个输入,产生最终结果。

简而言之,我们将简单地声明每一层,在常规参数之后提到前一个输入作为第二个参数组。例如,层hx作为输入。最后,当我们编译模型并指出它开始的位置(x)和结束的位置(z_mean, z_log_varz)时,Keras 将理解起始输入和最终输出列表是如何联系在一起的。记住从图中,z是我们的潜在空间,在这种情况下,它是由均值和方差定义的正态分布。现在让我们定义编码器。[⁸

这个想法是从我们书论坛中的 Branko Blagojevic 那里得到的灵感。感谢这个建议。

代码清单 2.3. 创建编码器
x = Input(shape=(original_dim,), name="input")                          ***1***
h = Dense(intermediate_dim, activation='relu', name="encoding")(x)      ***2***
z_mean = Dense(latent_dim, name="mean")(h)                              ***3***
z_log_var = Dense(latent_dim, name="log-variance")(h)                   ***4***
z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])   ***5***
encoder = Model(x, [z_mean, z_log_var, z], name="encoder")              ***6***
  • 1 编码器的输入

  • 2 中间层

  • 3 定义潜在空间的均值

  • 4 定义潜在空间的对数方差

  • 5 注意,在 TensorFlow 后端中,output_shape 不是必需的。

  • 6 将编码器定义为 Keras 模型

现在是有点棘手的部分,我们从潜在空间中进行采样,然后将这些信息传递到解码器。但稍微思考一下z_meanz_log_var是如何连接的:它们都通过一个有两个节点的密集层连接到h,这是正态分布的定义特征:均值和方差。前面的采样函数实现如下所示。

代码清单 2.4. 创建采样辅助函数
def sampling(args):
    z_mean, z_log_var = args
    epsilon = K.random_normal(shape=(batch_size, latent_dim), mean=0.)
    return z_mean + K.exp(z_log_var / 2) * epsilon

换句话说,我们学习均值(μ)和方差(μ)。这种整体实现,其中我们有一个(ω)通过采样函数以及z_meanz_log_var连接,使我们能够既进行训练,又能够高效地采样以在最后得到一些看起来很漂亮的图像。在生成过程中,我们根据这些学习到的参数从这个分布中进行采样,然后将这些值通过解码器传递以获得输出,正如你将在后面的图中看到的那样。对于那些对分布——或者在这个案例中是概率密度函数——有点生疏的人,我们在图 2.5 中包含了几个单峰二维高斯分布的例子。

图 2.5。为了提醒大家什么是多元(2D)分布的样子,我们绘制了二元(2D)高斯分布的概率密度函数。它们是不相关的 2D 正态分布,只是方差不同。(a)的方差为 0.5,(b)为 1,(c)为 2。(d)、(e)和(f)分别与(a)、(b)和(c)完全相同的分布,但以 0.7 为 z 轴限制进行绘制。直观上,这只是一个对于每个点说明其发生的可能性的函数。所以(a)和(d)更加集中,而(c)和(f)则使得远离原点(0,0)的值出现成为可能,但每个给定值出现的可能性并不高。

现在你已经了解了定义我们的潜在空间以及这些分布的样子,我们将编写解码器。在这种情况下,我们首先将层作为变量编写,这样我们可以在生成时重用它们。

列表 2.5。编写解码器
input_decoder = Input(shape=(latent_dim,), name="decoder_input")   ***1***
decoder_h = Dense(intermediate_dim, activation='relu',             ***2***
name="decoder_h")(input_decoder)
x_decoded = Dense(original_dim, activation='sigmoid',
name="flat_decoded")(decoder_h)                                    ***3***
decoder = Model(input_decoder, x_decoded, name="decoder")          ***4***
  • 1 解码器的输入

  • 2 将潜在空间转换为中间维度

  • 3 从原始维度获取均值

  • 4 将解码器定义为 Keras 模型

我们现在可以将编码器和解码器组合成一个单一的 VAE 模型。

列表 2.6。组合模型
output_combined = decoder(encoder(x)[2])      ***1***
vae = Model(x, output_combined)               ***2***
vae.summary()                                 ***3***
  • 1 获取输出。回想一下,我们需要获取第三个元素,我们的采样 z。

  • 2 连接输入和整体输出

  • 3 打印出整体模型的样子

接下来,我们进入机器学习的更熟悉的部分:定义损失函数,以便我们的自动编码器可以训练。

列表 2.7。定义我们的损失函数
def vae_loss(x, x_decoded_mean, z_log_var, z_mean,
    original_dim=original_dim):
    xent_loss = original_dim * objectives.binary_crossentropy(
        x, x_decoded_mean)
    kl_loss = - 0.5 * K.sum(
        1 + z_log_var - K.square(z_mean) - K.exp(z_log_var),
        axis=-1)
    return xent_loss + kl_loss

vae.compile(optimizer='rmsprop', loss=vae_loss)     ***1***
  • 1 最后编译我们的模型

在这里,你可以看到使用二元交叉熵和 KL 散度相加形成整体损失的地方。KL 散度衡量分布之间的差异;想象一下图 2.5 中的两个团块,然后测量重叠的体积。二元交叉熵是两分类中常见的损失函数之一:在这里,我们简单地比较x的每个灰度像素值与x_decoded_mean中的值,这就是我们之前提到的重建。如果你在以下定义之后仍然对这个段落感到困惑,第五章提供了关于测量分布之间差异的更多细节。

定义

对于那些对细节感兴趣且熟悉信息理论的人来说,库尔巴克-莱布勒散度(KL 散度),也称为相对熵,是两个分布的交叉熵与它们自身的熵之间的差异。对于其他人来说,想象画出这两个分布,它们不重叠的部分将是一个与 KL 散度成比例的区域。

然后我们定义模型从x开始,到x_decoded_mean结束。模型使用 RMSprop 编译,但我们可以使用 Adam 或普通的随机梯度下降(SGD)。与任何深度学习系统一样,我们使用反向传播的错误来导航参数空间。我们始终使用某种类型的梯度下降,但通常人们很少尝试除了这里提到的三种以外的其他方法:Adam、SGD 或 RMSprop。

定义

随机梯度下降(SGD)是一种优化技术,它允许我们通过确定任何给定权重对错误的贡献并更新此权重(如果预测 100%正确则不更新)来训练复杂模型。我们建议在例如《Python 深度学习》中复习这一内容。

我们通过使用标准的训练-测试分割和输入归一化程序来训练模型。

列表 2.8. 创建训练/测试分割
(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))

我们对数据进行归一化,并将训练集和测试集重塑为每个示例一个 784 位长的数组,而不是一个 28×28 的矩阵。

然后我们应用fit函数,通过打乱数据来获取一个真实的(非有序)数据集。我们还在训练过程中使用验证数据来监控进度:

vae.fit(x_train, x_train,
        shuffle=True,
        nb_epoch=nb_epoch,
        batch_size=batch_size,
        validation_data=(x_test, x_test),verbose=1)

我们完成了!

代码的完整版本提供了一个有趣的潜在空间可视化;然而,为了查看它,请查看附带的 Jupyter/Google Colaboratory 笔记本。现在我们可以放松,观看那些漂亮的进度条。完成后,我们甚至可以查看潜在空间在二维平面上的值,如图图 2.6 所示。

图 2.6. 将测试集中的所有点及其类别投影到潜在空间中的二维投影。在这个图中,我们将二维潜在空间显示在图上。然后我们绘制这些生成示例的类别,并按照右侧图例中的颜色进行着色。在这里,我们可以看到类别往往整齐地分组在一起,这告诉我们这是一个好的表示。本书的 GitHub 仓库中有彩色版本。

图片

我们还可以计算潜在空间网格上固定增量处的值,以查看生成的输出。例如,从 0.05 到 0.95,在两个维度上以 0.15 的线性增量进行,我们得到了图 2.7 中的可视化。记住,在这种情况下,我们使用的是双变量高斯分布,给我们两个轴来迭代。再次提醒,对于这个可视化的代码,请查看完整的 Jupyter/Google Colab 笔记本。

图 2.7. 我们在网格上绘制潜在空间子集的值,并将每个潜在空间值通过生成器传递以生成此图。这让我们对随着 z 的变化,最终图片的变化程度有一个感觉。

图片

2.8. 为什么我们尝试了 aGAN?

看起来这本书几乎可以在这里结束。毕竟,我们已经成功地生成了 MNIST 的图像,这将成为我们几个示例的测试案例。所以在你放弃之前,让我们解释一下接下来章节的动机。

为了理解挑战,想象我们有一个简单的一维双峰分布——如图 2.8 所示。(就像之前一样,把它想象成一个简单的数学函数,它在 0 和 1 之间有界,代表任何给定点的概率。函数的值越高,我们在该确切点之前采样的点就越多。)

图 2.8. 最大似然估计、点估计和真实分布。灰色(理论)分布是双峰的,而不是单峰。但由于我们假设了这一点,我们的模型是灾难性的错误。或者,我们可能会遇到模式坍塌,这在第五章中值得记住。第五章。这在我们使用 KL 散度的变体,如 VAE 或早期 GANs 时尤其如此。

图片

假设我们从这个真实分布中抽取了一组样本,但我们不知道底层模型。我们现在试图推断生成这些样本的分布,但出于某种原因,我们假设真实分布是一个简单的高斯分布,我们只需要估计均值和方差。但由于我们没有正确指定模型(在这种情况下,我们对样本的模态做出了错误的假设),我们陷入了麻烦。例如,如果我们应用一种传统的统计技术,称为最大似然估计来估计这个分布为单峰——在某种程度上,这就是 VAE 试图做的——我们会得到错误的估计。因为我们没有正确指定模型,^([9])它将估计围绕两个分布平均值的正态分布——称为点估计。最大似然估计是一种不知道也无法弄清楚存在两个不同分布的技术。因此,为了最小化误差,它围绕点估计创建了一个“胖尾”正态分布。在这里,这似乎很微不足道,但请始终记住,我们正在尝试在非常高的维空间中指定模型,这并不容易!

参见 Christopher Bishop 的《模式识别与机器学习》(Springer,2011 年)。

定义

双峰意味着有两个峰值或模态。这个概念将在第五章中很有用。在这种情况下,我们将整体分布由两个均值为 0 和 5 的正态分布组成。

有趣的是,点估计也可能出错,甚至可能位于一个没有任何实际数据样本的真实分布区域。当你查看样本(黑色交叉点)时,我们估计均值的地方并没有出现任何真实样本。这再次是非常令人不安的。为了将其与自编码器联系起来,看看在图 2.6 中我们是如何在以原点为中心的潜在空间中学习 2D 正态分布的?但如果我们把名人脸的图像扔进训练数据中呢?我们就不再有一个容易估计的中心了,因为两个数据分布的峰态比我们想象的要多。因此,即使在分布的中心附近,VAE 也可能产生两个数据集的奇怪混合体,因为 VAE 会试图以某种方式分离这两个数据集。

到目前为止,我们只讨论了统计错误假设的影响。为了将这一方面完全与自编码器生成的图像联系起来,我们应该考虑我们的高斯潜在空间z允许我们做什么。VAE 使用高斯作为构建它所看到的数据表示的方式。但由于高斯有 99.7%的概率质量在中间三个标准差内,VAE 也会选择安全的中庸之道。因为 VAEs 在某种程度上试图直接基于高斯提出底层模型,但现实可能相当复杂,VAEs 的扩展性不如 GANs,GANs 可以捕捉“场景”。

你可以在图 2.9 中看到当你的 VAE 选择“安全的中庸之道”时会发生什么。在 CelebA 数据集上,该数据集具有对齐和裁剪的名人脸特征,VAE 很好地模拟了持续存在的面部特征,如眼睛或嘴巴,但在背景上犯错误。

图 2.9。在这些由 VAE 生成的假名人脸图像中,边缘相当模糊,并融入背景中。这是因为 CelebA 数据集具有围绕眼睛和嘴巴周围特征一致的中心和校准图像,但背景往往变化。VAE 选择了安全的路径,通过选择一个“安全”的像素值来使背景模糊,这最小化了损失,但并没有提供好的图像。

图 2.9 的替代图片

(来源:Zhenliang He 的 VAE-TensorFlow,GitHub,github.com/LynnHo/VAE-Tensorflow

另一方面,GANs 对真实数据分布有一个隐含且难以分析的理解。正如你将在第五章中发现的那样,VAEs 生活在直接估计的最大似然模型家族中。

这一部分希望让你对思考目标数据的分布以及这些分布性影响如何在我们的训练过程中体现自己感到舒适。我们将在第十章(../Text/kindle_split_021.xhtml#ch10)中更深入地探讨这些假设,在那里模型假设如何填充分布,而这成为了一个对抗性示例能够利用来使我们的机器学习模型失败的问题。

摘要

  • 在高层次上,自动编码器由编码器、潜在空间和解码器组成。自动编码器通过使用一个常见的目标函数来训练,该函数衡量重生产的数据与原始数据之间的距离。

  • 自动编码器有许多应用,也可以用作生成模型。在实践中,这通常不是它们的主要用途,因为其他方法,尤其是 GANs,在生成任务上表现得更好。

  • 我们可以使用 Keras(TensorFlow 的高级 API)编写一个简单的变分自动编码器,它可以生成手写数字。

  • VAEs 有一些局限性,这促使我们转向 GANs。

第三章。你的第一个 GAN:生成手写数字

本章涵盖

  • 探索 GANs 和对抗性训练背后的理论

  • 理解 GANs 与常规神经网络的不同

  • 在 Keras 中实现 GAN,并训练它生成手写数字

在本章中,我们探讨了 GANs 背后的基础理论。我们介绍了如果你选择深入研究这个领域,可能会遇到的常用数学符号,这可能通过阅读更理论化的出版物或甚至许多关于这个主题的学术论文来实现。本章还为更高级的章节提供了背景知识,特别是第五章。

从严格实用的角度来看,然而,你不必担心许多这些形式化——就像你不需要知道内燃机的工作原理就能开车一样。像 Keras 和 TensorFlow 这样的机器学习库将底层数学抽象化,并将它们巧妙地打包成可导入的代码行。

这将是贯穿整本书的一个反复出现的主题;这对于机器学习和深度学习来说也是正确的。所以,如果你是那种喜欢直接进入实践的人,你可以自由地浏览理论部分,然后跳到编码教程。

3.1. GANs 的基础:对抗性训练

形式上,生成器和判别器由不同的可微函数表示,例如神经网络,每个都有自己的成本函数。这两个网络通过使用判别器的损失进行反向传播来训练。判别器努力最小化真实和虚假示例的损失,而生成器则试图最大化它产生的虚假示例的判别器损失。

这种动态在图 3.1 中得到了总结。这是第一章中图表的更通用版本,在那里我们首先解释了 GAN 是什么以及它们是如何工作的。在这个图表中,我们没有使用手写数字的具体示例,而是一个通用的训练数据集,在理论上可以是任何东西。

图 3.1。在这个 GAN 架构图中,生成器和判别器都使用判别器的损失进行训练。判别器力求最小化损失;生成器寻求最大化它产生的假例的损失。

重要的是,训练数据集决定了生成器将学习模仿的示例类型。例如,如果我们目标是生成看起来逼真的猫的图像,我们会向我们的 GAN 提供一个猫的图像数据集。

在更技术性的术语中,生成器的目标是生成能够捕捉训练数据集数据分布的示例。^([1]) 请记住,对计算机来说,图像只是一个值矩阵:灰度图像是二维的,RGB 图像是三维的。当在屏幕上渲染时,这些矩阵内的像素值表现出图像的所有视觉元素——线条、边缘、轮廓等等。这些值在数据集中的每个图像中遵循复杂的分布;毕竟,如果没有遵循分布,图像将不会比随机噪声更多。物体识别模型学习图像中的模式以辨别图像的内容。生成器可以被视为这一过程的逆过程:它不是识别这些模式,而是学习合成它们。

¹

请参阅 Ian J. Goodfellow 等人撰写的《生成对抗网络》,2014 年,arxiv.org/abs/1406.2661

3.1.1. 成本函数

按照标准符号,用 J^((G)) 表示生成器的成本函数,用 J^((D)) 表示判别器的成本函数。两个网络的训练参数(权重和偏置)由希腊字母 theta 表示:θ^((G)) 用于生成器,θ^((D)) 用于判别器。

GAN 与传统的神经网络在两个关键方面有所不同。首先,传统神经网络的成本函数 J 仅以它自己的可训练参数 θ 为定义。数学上,这表示为 J(θ)。相比之下,GAN 由两个网络组成,其成本函数依赖于两个网络的所有参数。也就是说,生成器的成本函数是 J((*G*))(*θ*((G)), θ^((D))),判别器的成本函数是 J((*D*))(*θ*((G)), θ((*D*)))。([2])

²

请参阅 Ian Goodfellow 撰写的《NIPS 2016 教程:生成对抗网络》,2016 年,arxiv.org/abs/1701.00160

第二个(相关)的区别是,传统的神经网络可以在训练过程中调整其所有参数,即 θ。在 GAN 中,每个网络只能调整自己的权重和偏差。生成器只能调整 θ^((G)),判别器只能调整 θ^((D))。因此,每个网络只控制决定其损失的部分。

为了使这个问题更加具体,考虑以下类比。想象一下,我们正在选择从工作中回家的路线。如果没有交通,最快的选项是高速公路。然而,在高峰时段,我们可能更倾向于选择一条侧路。尽管这些路更长、更曲折,但在高速公路交通拥堵时,它们可能更快地带我们回家。

让我们把这个问题表述为一个数学问题。设 J 为我们的成本函数,定义为我们回家所需的时间。我们的目标是使 J 最小化。为了简化,我们假设我们有一个固定的时间离开办公室,因此我们无法提前离开以避开高峰时段,或者延迟离开以避免高峰时段。我们唯一可以改变的是我们的路线,即 θ

如果我们路上只有一辆车,我们的成本将与常规神经网络类似:它将仅取决于路线,并且我们完全有能力对其进行优化,即 J(θ)。然而,一旦我们将其他司机纳入方程,情况就会变得更加复杂。突然之间,我们回家所需的时间不仅取决于我们的决定,还取决于其他司机的行动,即 J(θ((*us*)),*θ*((other drivers)))。就像生成器和判别器网络一样,我们的“成本函数”将取决于一系列因素,其中一些在我们控制之下,而另一些则不在。

3.1.2。训练过程

我们所描述的两个差异对 GAN 训练过程有着深远的影响。传统神经网络的训练是一个优化问题。我们试图通过找到一组参数来最小化成本函数,使得移动到参数空间中的任何相邻点都会增加成本。这可能是参数空间中的局部或全局最小值,这取决于我们试图最小化的成本函数。图 3.2 说明了最小化成本函数的优化过程。

图 3.2。碗状的网格表示参数空间 θ[1] 和 θ[2] 中的损失 J。黑色虚线说明了通过优化在参数空间中损失的最小化过程。

(来源:“对抗机器学习”由 Ian Goodfellow 撰写,ICLR 主题演讲,2019,www.iangoodfellow.com/slides/2019-05-07.pdf。)

由于生成器和判别器只能调整自己的参数而不能调整对方的参数,GAN 训练可以更好地描述为一场游戏,而不是优化。(参考文献 [3])这个游戏中的玩家是 GAN 包含的两个网络。

³

同上。

回想一下 第一章,GAN 训练在两个网络达到纳什均衡时结束,这是游戏中一个点,此时任何玩家都不能通过改变策略来改善自己的情况。从数学上来说,这发生在生成器成本 J((*G*))(*θ*((G)), θ^((D))) 在生成器的可训练参数 θ^((G)) 上最小化,同时,判别器成本 J((*D*))(*θ*((G)), θ^((D))) 在该网络控制的参数上最小化。(参考文献 [4])图 3.3 展示了两人零和游戏的设置以及达到纳什均衡的过程。

同上。

图 3.3。玩家 1(左侧)通过调整 θ[1] 来最小化 V。玩家 2(中间)通过调整 θ[2] 来最小化 –V(最大化 V)。鞍形网格(右侧)显示了参数空间 V(θ[1], θ[2]) 中的总损失。虚线显示了收敛到鞍形中心纳什均衡的过程。(来源:Goodfellow,2019,www.iangoodfellow.com/slides/2019-05-07.pdf。)

回到我们的类比,当每条回家的路线所需时间完全相同——对我们以及我们可能遇到的任何其他司机来说——纳什均衡就会发生。任何更快的路线都会因为交通量的相应增加而被抵消,从而恰好减慢每个人的速度。正如你可能想象的那样,这种状态在现实生活中几乎无法实现。即使有像 Google Maps 这样的工具提供实时交通更新,也往往无法完美地评估回家的最佳路线。

在训练 GANs 的高维非凸世界中,情况也是如此。即使是 MNIST 数据集中像那样的 28 × 28 像素的灰度图像也有 28 × 28 = 784 维。如果它们是彩色的(RGB),它们的维度将增加三倍,达到 2,352。捕捉训练数据集中所有图像的这种分布极为困难,尤其是在最佳学习方法是从对手(判别器)那里学习的情况下。

成功训练 GAN 需要反复试验,尽管有最佳实践,但它仍然既是一门艺术,也是一门科学。第五章 更详细地回顾了 GAN 收敛性的问题。现在,你可以放心,情况并没有听起来那么糟糕。正如我们在 第一章 中预览的那样,以及你将在本书的其余部分看到的那样,近似生成分布的巨大复杂性以及我们对使 GAN 收敛的条件缺乏完全理解,都没有阻碍 GAN 的实际可用性和生成逼真数据样本的能力。

3.2. 生成器和判别器

让我们通过引入更多符号来回顾你所学的内容。生成器 (G) 接收一个随机噪声向量 z 并生成一个伪造示例 x**。从数学上讲,G(z) = x**。判别器 (D) 被提供真实示例 x 或伪造示例 *x**;对于每个输入,它输出一个介于 0 和 1 之间的值,表示输入是真实的概率。图 3.4 使用我们刚刚提出的术语和符号描述了 GAN 架构。

图 3.4. 生成器网络 G 将随机向量 z 转换为伪造示例 x:G(z) = x。判别器网络 D 输出对输入示例是否为真实的分类。对于真实示例 x,判别器努力输出尽可能接近 1 的值。对于伪造示例 x,判别器努力输出尽可能接近 0 的值。相反,生成器希望 D(x) 尽可能接近 1,这表明判别器被欺骗,将伪造示例分类为真实。

3.2.1. 冲突目标

判别器的目标是尽可能准确。对于真实示例 xD(x) 努力使其尽可能接近 1(正类的标签)。对于伪造示例 x**,D(x**) 努力使其尽可能接近 0(负类的标签)。

生成器的目标是相反的。它通过产生与训练数据集中的真实数据不可区分的伪造示例 x* 来欺骗判别器。从数学上讲,生成器努力产生伪造示例 x**,使得 D(x**) 尽可能接近 1。

3.2.2. 混淆矩阵

判别器的分类可以用混淆矩阵来表示,它是二进制分类中所有可能结果的表格表示。对于判别器,这些如下:

  • 真阳性—将真实示例正确分类为真实;D(x) ≈ 1

  • 假阴性—将真实示例错误地分类为伪造;D(x) ≈ 0

  • 真阴性—将伪造示例正确分类为伪造;D(*x**) ≈ 0

  • 假阳性—将伪造示例错误地分类为真实;D(*x**) ≈ 1

表 3.1 展示了这些结果。

表 3.1. 判别器结果的混淆矩阵
输入 判别器输出
接近 1(真实) 接近 0(假)
--- ---
真实 (x) 真阳性
假 (x)* 假阳性

使用混淆矩阵术语,判别器试图最大化真阳性和真阴性分类,或者等价地,最小化假阳性和假阴性分类。相比之下,生成器的目标是最大化判别器的假阳性分类——这些是生成器成功欺骗判别器相信假例是真实例子的实例。生成器并不关心判别器对真实例子的分类效果如何;它只关心判别器对假数据样本的分类。

3.3. GAN 训练算法

让我们回顾一下第一章中介绍的 GAN 训练算法,并使用本章引入的符号对其进行形式化。与第一章中的算法不同,这个算法使用的是小批量而不是逐个例子。

GAN 训练算法

对于每个训练迭代执行

  1. 训练判别器:

    1. 取一个随机真实例子的批量:x。

    2. 取一个随机噪声向量 z 的小批量,并生成一个假例的小批量:G(z) = x*。

    3. 计算 D(x)和 D(x*)的分类损失,并将总误差反向传播以更新θ^((D))以最小化分类损失。

  2. 训练生成器:

    1. 取一个随机噪声向量 z 的小批量,并生成一个假例的小批量:G(z) = x*。

    2. 计算 D(x*)的分类损失,并将损失反向传播以更新θ^((G))以最大化分类损失。

结束 for

注意,在第 1 步中,我们在训练判别器的同时保持生成器的参数不变。同样,在第 2 步中,我们保持判别器的参数固定,同时训练生成器。我们只允许更新正在训练的网络的权重和偏置,是为了将所有变化仅限于网络控制的参数。这确保了每个网络都能获得关于要进行的更新的相关信号,而不会受到其他网络更新的干扰。你几乎可以把它想象成两个玩家轮流进行。

当然,你可以想象一个场景,其中每个玩家只是抵消了对方的进展,因此即使是回合制游戏也不能保证产生有用的结果。(我们是否已经说过 GAN 的训练非常困难?)更多内容请见第五章,其中我们还将讨论提高成功机会的技术。

理论部分就到这里,暂时告一段落。现在让我们将所学应用到实践中,并实现我们的第一个 GAN。

3.4. 指南:生成手写数字

在本教程中,我们将实现一个 GAN,该 GAN 学习生成看起来逼真的手写数字。我们将使用 Python 神经网络库 Keras 和 TensorFlow 后端。图 3.5

图 3.5. 在训练迭代过程中,生成器学习将随机噪声输入转换为看起来像训练数据成员的图像:手写数字的 MNIST 数据集。同时,判别器学习区分生成器产生的假图像和来自训练数据集的真实图像。

本教程中使用的很大一部分代码——特别是训练循环中使用的样板代码——是从 Keras 中开源的 GAN 实现 GitHub 仓库(Keras-GAN)改编的,由 Erik Linder-Norén 创建(github.com/eriklindernoren/Keras-GAN)。该仓库还包括几个高级 GAN 变体,其中一些将在本书的后续章节中介绍。我们对代码和网络架构进行了相当大的修订和简化,并重命名了变量,以便它们与本书中使用的符号一致。

一个包含完整实现(包括添加的训练进度可视化)的 Jupyter 笔记本可在本书的网站上找到,网址为www.manning.com/books/gans-in-action,以及本书 GitHub 仓库的github.com/GANs-in-Action/gans-in-action下的第三章文件夹中。代码已在 Python 3.6.0、Keras 2.1.6 和 TensorFlow 1.8.0 上进行了测试。

3.4.1. 导入模块和指定模型输入维度

首先,我们导入运行模型所需的所有包和库。注意我们直接从keras.datasets导入手写数字的 MNIST 数据集。

列表 3.1. 导入语句
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from keras.datasets import mnist
from keras.layers import Dense, Flatten, Reshape
from keras.layers.advanced_activations import LeakyReLU
from keras.models import Sequential
from keras.optimizers import Adam

其次,我们指定模型和数据集的输入维度。MNIST 中的每张图像都是 28 × 28 像素的单通道(因为图像是灰度的)。变量z_dim设置噪声向量z的大小。

列表 3.2. 模型输入维度
img_rows = 28
img_cols = 28
channels = 1

img_shape = (img_rows, img_cols, channels)    ***1***

z_dim = 100                                   ***2***
  • 1 输入图像维度

  • 2 噪声向量的大小,用作生成器的输入

接下来,我们实现生成器和判别器网络。

3.4.2. 实现生成器

为了简单起见,生成器是一个只有一个隐藏层的神经网络。它以z为输入,并生成一个 28 × 28 × 1 的图像。在隐藏层中,我们使用Leaky ReLU激活函数。与将任何负输入映射到 0 的常规 ReLU 函数不同,Leaky ReLU 允许一个小的正梯度。这防止了在训练过程中梯度消失,这往往会产生更好的训练结果。

在输出层,我们使用tanh激活函数,将输出值缩放到[–1, 1]的范围。使用tanh(而不是,比如说,输出值在更典型的 0 到 1 范围内的sigmoid)的原因是tanh倾向于产生更清晰的图像。

下面的列表实现了生成器。

列表 3.3. 生成器
def build_generator(img_shape, z_dim):
    model = Sequential()

    model.add(Dense(128, input_dim=z_dim))              ***1***

    model.add(LeakyReLU(alpha=0.01))                    ***2***

    model.add(Dense(28 * 28 * 1, activation='tanh'))    ***3***

    model.add(Reshape(img_shape))                       ***4***

    return model
  • 1 全连接层

  • 2 Leaky ReLU 激活

  • 3 带有 tanh 激活函数的输出层

  • 4 将生成器输出重塑为图像维度

3.4.3. 实现判别器

判别器接收一个 28 × 28 × 1 的图像,并输出一个概率,表示输入被认为是真实还是虚假。判别器由一个两层神经网络表示,有 128 个隐藏单元,在隐藏层使用Leaky ReLU激活函数。

为了简单起见,我们的判别器网络看起来几乎与生成器相同。这并不一定如此;实际上,在大多数 GAN 实现中,生成器和判别器网络架构在大小和复杂性上都有很大的差异。

注意,与生成器不同,在下面的列表中,我们在判别器的输出层应用了sigmoid激活函数。这确保了我们的输出值将在 0 到 1 之间,因此它可以被解释为生成器分配给输入为真实的概率。

列表 3.4. 判别器
def build_discriminator(img_shape):

    model = Sequential()

    model.add(Flatten(input_shape=img_shape))      ***1***

    model.add(Dense(128))                          ***2***

    model.add(LeakyReLU(alpha=0.01))               ***3***

    model.add(Dense(1, activation='sigmoid'))      ***4***

    return model
  • 1 将输入图像展平

  • 2 全连接层

  • 3 Leaky ReLU 激活

  • 4 带有 sigmoid 激活函数的输出层

3.4.4. 构建模型

在列表 3.5 中,我们构建并编译了之前实现的生成器和判别器模型。注意,在用于训练生成器的组合模型中,我们通过将discriminator.trainable设置为False来固定判别器的参数。此外,请注意,在判别器设置为不可训练的组合模型中,仅用于训练生成器。判别器作为一个独立编译的模型进行训练。(当我们回顾训练循环时,这一点将变得明显。)

我们使用二元交叉熵作为我们在训练期间寻求最小化的损失函数。二元交叉熵是计算概率与实际概率之间的差异的度量,对于只有两个可能类别的预测。交叉熵损失越大,我们的预测与真实标签的距离就越远。

为了优化每个网络,我们使用Adam 优化算法。这个算法的名字来源于自适应矩估计,是一个基于高级梯度下降的优化器。这个算法的内部工作原理超出了本书的范围,但可以简单地说,Adam 由于其通常优越的性能,已经成为大多数 GAN 实现的首选优化器。

列表 3.5. 构建和编译 GAN
def build_gan(generator, discriminator):

    model = Sequential()

    model.add(generator)                                    ***1***
    model.add(discriminator)

    return model

discriminator = build_discriminator(img_shape)              ***2***
discriminator.compile(loss='binary_crossentropy',
                      optimizer=Adam(),
                      metrics=['accuracy'])

generator = build_generator(img_shape, z_dim)               ***3***

discriminator.trainable = False                             ***4***

gan = build_gan(generator, discriminator)                   ***5***
gan.compile(loss='binary_crossentropy', optimizer=Adam())
  • 1 结合生成器 + 判别器模型

  • 2 构建和编译判别器

  • 3 构建生成器

  • 4 在生成器训练期间保持判别器参数不变

  • 5 使用固定的判别器构建和编译 GAN 模型以训练生成器

3.4.5. 训练

列表 3.6 中的训练代码实现了 GAN 训练算法。我们获取一个随机的 MNIST 图像小批量作为真实示例,并从随机噪声向量z生成一个假图像小批量。然后我们使用这些图像来训练判别器网络,同时保持生成器参数不变。接下来,我们生成一个假图像小批量,并使用这些图像来训练生成器网络,同时保持判别器参数固定。我们重复这个过程,直到每个迭代。

我们使用 one-hot 编码的标签:1 表示真实图像,0 表示假图像。为了生成z,我们从标准正态分布(均值为 0,标准差为 1 的钟形曲线)中采样。判别器被训练来将标签分配给假图像,将真实标签分配给真实图像。生成器被训练,使得判别器将其产生的假示例分配为真实标签。

注意,我们在训练数据集中将真实图像从-1 缩放到 1。正如前一个示例中看到的,生成器在输出层使用tanh激活函数,因此假图像将在范围(-1,1)内。因此,我们必须将判别器所有输入的缩放范围调整为相同。

列表 3.6. GAN 训练循环
losses = []
accuracies = []
iteration_checkpoints = []

def train(iterations, batch_size, sample_interval):

    (X_train, _), (_, _) = mnist.load_data()                        ***1***

    X_train = X_train / 127.5 - 1.0                                 ***2***
    X_train = np.expand_dims(X_train, axis=3)

    real = np.ones((batch_size, 1))                                 ***3***

    fake = np.zeros((batch_size, 1))                                ***4***

    for iteration in range(iterations):

        idx = np.random.randint(0, X_train.shape[0], batch_size)    ***5***
        imgs = X_train[idx]

        z = np.random.normal(0, 1, (batch_size, 100))               ***6***
        gen_imgs = generator.predict(z)

        d_loss_real = discriminator.train_on_batch(imgs, real)      ***7***
        d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
        d_loss, accuracy = 0.5 * np.add(d_loss_real, d_loss_fake)

        z = np.random.normal(0, 1, (batch_size, 100))               ***8***
        gen_imgs = generator.predict(z)

        g_loss = gan.train_on_batch(z, real)                        ***9***

        if (iteration + 1) % sample_interval == 0:

            losses.append((d_loss, g_loss))                         ***10***
            accuracies.append(100.0 * accuracy)
            iteration_checkpoints.append(iteration + 1)

            print("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" %    ***11***
                  (iteration + 1, d_loss, 100.0 * accuracy, g_loss))

            sample_images(generator)                                ***12***
  • 1 加载 MNIST 数据集

  • 2 将[0, 255]灰度像素值缩放到[–1, 1]

  • 3 真实图像的标签:全部为 1

  • 4 假图像的标签:全部为 0

  • 5 获取一个随机批量的真实图像

  • 6 生成一批假图像

  • 7 训练判别器

  • 8 生成一批假图像

  • 9 训练生成器

  • 10 保存损失和准确率,以便在训练后绘制

  • 11 输出训练进度

  • 12 输出生成的图像样本

3.4.6. 输出样本图像

在生成器训练代码中,你可能注意到对sample_images()函数的调用。这个函数在每个sample_interval迭代中被调用,并输出一个由生成器在给定迭代中合成的 4 × 4 图像网格。运行我们的模型后,我们将使用这些图像来检查中间和最终输出。

列表 3.7. 显示生成的图像
def sample_images(generator, image_grid_rows=4, image_grid_columns=4):

    z = np.random.normal(0, 1, (image_grid_rows * image_grid_columns, z_dim)) ***1***

    gen_imgs = generator.predict(z)                                           ***2***

    gen_imgs = 0.5 * gen_imgs + 0.5                                           ***3***

    fig, axs = plt.subplots(image_grid_rows,                                  ***4***
                            image_grid_columns,
                            figsize=(4, 4),
                            sharey=True,
                            sharex=True)

    cnt = 0
    for i in range(image_grid_rows):
        for j in range(image_grid_columns):
            axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray')             ***5***
            axs[i, j].axis('off')
            cnt += 1
  • 1 生成随机噪声样本

  • 2 从随机噪声生成图像

  • 2 将图像像素值缩放到[0, 1]

  • 4 设置图像网格

  • 5 输出图像网格

3.4.7. 运行模型

这将带我们进入最后一步,如列表 3.8 所示。我们设置训练超参数——迭代次数和批量大小——并训练模型。没有一成不变的方法来确定正确的迭代次数或正确的批量大小;我们通过观察训练进度,通过试错实验来确定它们。

虽然如此,这些数字有一些重要的实际限制:每个小批量必须足够小,以便适合处理内存(人们通常使用的批量大小是 2 的幂:32、64、128、256 和 512)。迭代次数也有实际限制:我们拥有的迭代次数越多,训练过程就越长。对于像 GAN 这样的复杂深度学习模型,这可能会迅速失控,即使有显著的计算能力。

为了确定合适的迭代次数,我们监控训练损失,并将迭代次数设置在损失平台期附近,这表明我们通过进一步训练获得的增量改进很小或没有。(因为这是一个生成模型,过拟合与监督学习算法一样是一个担忧。)

列表 3.8。运行模型
iterations = 20000                                ***1***
batch_size = 128
sample_interval = 1000

train(iterations, batch_size, sample_interval)    ***2***
  • 1 设置超参数

  • 2 训练 GAN 指定次数的迭代

3.4.8。检查结果

图 3.6 展示了生成器在训练迭代过程中产生的示例图像,从最早到最新。

图 3.6。从看起来只是随机噪声开始,生成器逐渐学会模拟训练数据集的特征:在我们的案例中,是手写数字的图像。

图片

如您所见,生成器最初产生的只是随机噪声。在训练迭代过程中,它逐渐变得越来越好地模拟训练数据的特征。每当判别器拒绝一个生成的图像为假或接受一个图像为真时,生成器都会有所改进。图 3.7 展示了生成器在完全训练后可以合成的图像示例。

图 3.7。尽管远非完美,我们简单的两层生成器学会了生成看起来逼真的数字,例如 9 和 1。

图片

为了比较,图 3.8 显示了从 MNIST 数据集中随机选择的真实图像样本。

图 3.8。用于训练我们的 GAN 的 MNIST 数据集中真实手写数字的示例。尽管生成器在模拟训练数据方面取得了显著的进步,但它产生的数字与真实人类书写的数字之间的差异仍然明显。

图片

3.5。结论

尽管我们 GAN 生成的图像远非完美,但其中许多很容易被识别为真实的数字——这是一个令人印象深刻的成就,考虑到我们只使用了简单的两层网络架构来构建生成器和判别器。在下一章中,您将学习如何通过为生成器和判别器使用更复杂、更强大的神经网络架构来提高生成图像的质量:卷积神经网络。

摘要

  • GAN 由两个网络组成:生成器(G)和判别器(D),每个网络都有自己的损失函数:J((*G*))(*θ*((G)), θ^((D))) 和 J((*D*))(*θ*((G)), θ^((D))),分别。

  • 在训练过程中,生成器和判别器只能调整自己的参数:θ^((G)) 和 θ^((D)),分别。

  • 通过类似游戏的动态同时训练两个 GAN 网络。生成器试图最大化判别器的假阳性分类(将生成的图像分类为真实),而判别器试图最小化其假阳性和假阴性分类。

第四章 深度卷积生成对抗网络

本章涵盖

  • 理解卷积神经网络背后的关键概念

  • 使用批归一化

  • 实现深度卷积生成对抗网络(Deep Convolutional GAN),一种高级 GAN 架构

在上一章中,我们实现了一个 GAN,其生成器和判别器都是具有单个隐藏层的简单前馈神经网络。尽管这种架构很简单,但经过充分训练后,GAN 的生成器产生的许多手写数字图像都令人信服。即使那些不能被识别为人类书写的数字,也具有许多手写符号的特征,如可辨别的线条边缘和形状——尤其是与用作生成器原始输入的随机噪声相比。

想象一下,如果我们使用更强大的网络架构能取得什么样的成果。在本章中,我们将做到这一点:我们的生成器和判别器都将实现为卷积神经网络(CNNs,或 ConvNets),而不是简单的两层前馈网络。这种 GAN 架构被称为深度卷积生成对抗网络,或简称DCGAN

在深入探讨 DCGAN 实现细节之前,我们将回顾构成卷积神经网络(ConvNets)的关键概念,回顾 DCGAN 发现背后的历史,并介绍一个使 DCGAN 等复杂架构在实践上成为可能的关键突破:批归一化。

4.1. 卷积神经网络

我们预计你已经接触过卷积网络;话虽如此,如果这项技术对你来说是新的,请不要担心。在本节中,我们将回顾本章以及本书其余部分所需的所有关键概念。

4.1.1. 卷积滤波器

与神经元排列在平坦、全连接层的常规前馈神经网络不同,ConvNet 的层排列在三个维度(宽度×高度×深度)中。通过在输入层上滑动一个或多个滤波器来执行卷积。每个滤波器具有相对较小的感受野(宽度×高度),但总是延伸到整个输入体积的深度。

在滑动输入的每一步,每个过滤器输出一个激活值:输入值和过滤器条目之间的点积。这个过程为每个过滤器产生一个二维的激活图。每个过滤器产生的激活图随后堆叠在一起,形成一个三维的输出层;输出深度等于使用的过滤器数量。

4.1.2. 参数共享

重要的是,过滤器参数被给定过滤器的所有输入值共享。这既有直观的优势,也有实际的优势。直观上,参数共享使我们能够高效地学习视觉特征和形状(如线条和边缘),无论它们在输入图像中的位置如何。从实际角度来看,参数共享极大地减少了可训练参数的数量。这降低了过拟合的风险,并允许这种技术扩展到更高分辨率的图像,而无需相应地指数级增加可训练参数,正如传统全连接网络那样。

4.1.3. 可视化的 ConvNets

如果这一切听起来很复杂,让我们通过可视化来使这些概念更具体一些。图表对大多数人(包括我们)来说都更容易理解!图 4.1 显示了一个单个卷积操作;图 4.2 在 ConvNet 的输入和输出层上下文中说明了卷积操作。

图 4.1. 一个 3 × 3 卷积过滤器在 5 × 5 输入上滑动——从左到右,从上到下。在每一步,过滤器移动两个步长;因此,它总共移动了四步,产生一个 2 × 2 的激活图。注意在每个步骤中,整个过滤器只产生一个激活值。

(来源:“深度学习卷积算术指南”,作者 Vincent Dumoulin 和 Francesco Visin,2016,arxiv.org/abs/1603.07285。)

图 4.1 描述了单个过滤器在二维输入上的卷积操作。在实践中,输入体积通常是三维的,我们使用几个堆叠的过滤器。然而,基本原理保持不变:每个过滤器每一步只产生一个值,无论输入体积的深度如何。我们使用的过滤器数量决定了输出体积的深度,因为它们的激活图是堆叠在一起的。所有这些都在 图 4.2 中得到了说明。

图 4.2. 在激活图(特征图)和输入、输出体积的上下文中,单个卷积步骤的激活值。请注意,ConvNet 过滤器贯穿整个输入体积的深度,输出体积的深度由堆叠在一起的激活图决定。

(来源:“卷积神经网络”,作者:Nameer Hirschkind 等,Brilliant.org,2018 年 11 月 1 日检索,mng.bz/8zJK.)

备注

如果你想深入了解卷积网络及其背后的概念,我们建议阅读 François Chollet 的《Python 深度学习》(Manning,2017 年),该书提供了对深度学习所有关键概念和技术的卓越、实用的介绍,包括卷积网络。对于那些更倾向于学术研究的人来说,Andrej Karpathy 在斯坦福大学关于视觉识别卷积神经网络的优秀讲义是一个很好的资源(cs231n.github.io/convolutional-networks/)).

4.2. DCGAN 的简要历史

DCGAN 由 Alec Radford、Luke Metz 和 Soumith Chintala 于 2016 年提出,是 GAN 技术自两年前诞生以来最重要的早期创新之一。^([1]) 这并不是第一次有研究团队尝试利用卷积神经网络(ConvNets)在 GAN 中的应用,但这是他们第一次成功地将 ConvNets 直接整合到全规模的 GAN 模型中。

¹

参见“Alec Radford 等人,《无监督表示学习与深度卷积生成对抗网络》,2015 年,arxiv.org/abs/1511.06434.”

使用 ConvNets 加剧了困扰 GAN 训练的许多困难,包括不稳定性和梯度饱和。事实上,这些挑战如此艰巨,以至于一些研究人员求助于替代方法,如 LAPGAN,它使用拉普拉斯金字塔内的级联卷积网络,并在每个级别上使用 GAN 框架单独训练一个 ConvNet。^([2]) 如果这些内容对你来说难以理解,请不要担心。随着更优越方法的取代,LAPGAN 已被很大程度上归入历史,因此了解其内部机制并不重要。

²

参见“Emily Denton 等人,《使用拉普拉斯金字塔的对抗网络进行深度生成图像模型》,2015 年,arxiv.org/abs/1506.05751.”

虽然 LAPGAN 在发布时复杂、计算量大,但它在当时提供了最高质量的图像,比原始 GAN(40%的人认为生成的图像是真实的,而原始 GAN 只有 10%)提高了四倍。因此,LAPGAN 展示了将 GAN 与 ConvNets 结合的巨大潜力。

在 DCGAN 中,Radford 及其合作者引入了技术和优化方法,使得卷积神经网络(ConvNets)能够扩展到完整的 GAN 框架,而无需修改底层 GAN 架构,也不需要将 GAN 简化为更复杂模型框架(如 LAPGAN)的子例程。Radford 等人使用的关键技术之一是批量归一化(batch normalization),它通过在每个应用层对输入进行归一化来帮助稳定训练过程。让我们更详细地了解一下批量归一化是什么以及它是如何工作的。

4.3. 批量归一化

批量归一化是由 Google 科学家 Sergey Ioffe 和 Christian Szegedy 于 2015 年提出的。^([[3])他们的洞察既简单又具有开创性。正如我们归一化网络输入一样,他们提出了对每个层在每个训练小批量通过网络流动时进行归一化的建议。

³

参见 Sergey Ioffe 和 Christian Szegedy 于 2015 年发表的论文“Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift”,arxiv.org/abs/1502.03167

4.3.1. 理解归一化

它有助于提醒我们归一化的概念以及为什么我们最初要归一化输入特征值。归一化是指对数据进行缩放,使其具有零均值和单位方差。这是通过取每个数据点 x,减去均值 μ,然后将结果除以标准差 σ 来实现的,如方程式 4.1 所示:

equation 4.1.

归一化具有几个优点。也许最重要的是,它使得具有不同比例的特征之间的比较变得更容易,并且通过扩展,使得训练过程对特征的比例不那么敏感。考虑以下(相当牵强的)例子。假设我们正在尝试根据两个特征来预测一个家庭的月支出:家庭的年收入和家庭规模。我们预计,一般来说,一个家庭赚得越多,他们花费的就越多;家庭规模越大,他们花费的就越多。

然而,这些特征的比例差异很大——年收入额外增加 10 万可能不会影响一个家庭的花费,但额外增加 10 个家庭成员可能会对任何家庭预算造成破坏。归一化通过将每个特征值缩放到标准尺度上,使得每个数据点不是以其面值表示,而是作为一个相对“分数”,表示给定数据点与均值的多少个标准差。

批归一化的背后理念是,仅对输入进行归一化在处理具有许多层的深度神经网络时可能还不够。随着输入值通过网络流动,从一层到下一层,它们会被每一层中的可训练参数所缩放。当参数通过反向传播进行调整时,每一层输入的分布容易在后续训练迭代中发生变化,这会破坏学习过程。在学术界,这个问题被称为 协变量偏移。批归一化通过按每个迷你批的均值和方差缩放每个迷你批的值来解决它。

4.3.2. 批归一化计算

批归一化的计算方式与我们在前面提出的简单归一化方程在几个方面有所不同。本节将逐步介绍它。

μ[B] 为迷你批 B 的均值,σ[B]² 为迷你批 B 的方差(均方偏差)。标准化值 的计算方法如下所示 方程式 4.2:

方程式 4.2.

术语 ϵ(epsilon)是为了数值稳定性而添加的,主要是为了避免除以零。它被设置为一个小正的常数值,例如 0.001。

在批归一化中,我们不会直接使用这些标准化值。相反,我们在将它们作为输入传递给下一层之前,将它们乘以 γ(伽马)并加上 β(贝塔);参见 方程式 4.3。

方程式 4.3.

重要的是,术语 γβ 是可训练参数,它们就像权重和偏差一样,在网络训练过程中进行调整。这样做的原因是,中间输入值可能有益于围绕非零均值和具有非一方差进行标准化。因为 γβ 是可训练的,网络可以学习哪些值最有效。

幸运的是,我们不必担心这些。Keras 函数 keras.layers.BatchNormalization 会为我们处理所有后台的迷你批计算和更新。

批归一化限制了更新前一层参数对当前层接收到的输入分布的影响程度。这减少了层间参数之间的任何不希望有的相互依赖性,有助于加快网络训练过程并提高其鲁棒性,尤其是在网络参数初始化方面。

批归一化已被证明对于许多深度学习架构的可行性至关重要,包括你将在以下教程中看到的 DCGAN。

4.4. 教程:使用 DCGAN 生成手写数字

在本教程中,我们将回顾来自第三章的手写数字 MNIST 数据集。然而,这一次,我们将使用 DCGAN 架构,并将生成器和判别器都表示为卷积网络,如图 4.3 所示。图 4.3。除了这个变化之外,网络架构的其他部分保持不变。在教程结束时,我们将比较两个 GAN(传统与 DCGAN)生成的手写数字的质量,以便您可以看到使用更先进的网络架构所能实现的改进。

图 4.3. 本章教程的整体模型架构与我们在第三章中实现的 GAN 相同。唯一的区别(在此高级图表中不可见)是生成器和判别器网络的内部表示(生成器和判别器框的内部)。这些网络将在本教程的后面部分详细说明。

图 4.3

正如第三章中所述,本教程中的大部分代码都是从 Erik Linder-Norén 的开源 GitHub 仓库中改编的,该仓库包含 Keras 中的 GAN 模型(github.com/eriklindernoren/Keras-GAN),经过许多修改和改进,包括实现细节和网络架构。包含完整实现以及添加的训练进度可视化的 Jupyter 笔记本可在本书的 GitHub 仓库github.com/GANs-in-Action/gans-in-action下的第四章文件夹中找到。代码已在 Python 3.6.0、Keras 2.1.6 和 TensorFlow 1.8.0 上进行了测试。为了加快训练时间,建议在 GPU 上运行模型。

4.4.1. 导入模块和指定模型输入维度

首先,我们导入所有需要用于训练和运行模型的包、模块和库。正如第三章中所述,MNIST 手写数字数据集直接从 keras.datasets 导入。

列表 4.1. 导入语句
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from keras.datasets import mnist
from keras.layers import (
    Activation, BatchNormalization, Dense, Dropout, Flatten, Reshape)
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Conv2D, Conv2DTranspose
from keras.models import Sequential
from keras.optimizers import Adam

我们还指定了模型输入维度:图像形状和噪声向量 z 的长度。

列表 4.2. 模型输入维度
img_rows = 28
img_cols = 28
channels = 1

img_shape = (img_rows, img_cols, channels)    ***1***

z_dim = 100                                   ***2***
  • 1 输入图像维度

  • 2 噪声向量的尺寸,用作生成器的输入

4.4.2. 实现生成器

卷积神经网络(ConvNets)传统上用于图像分类任务,其中网络接收一个具有高度 × 宽度 × 颜色通道数维度的图像作为输入,并通过一系列卷积层输出一个单维度的类别得分向量,其维度为 1 × n,其中n是类别标签的数量。要使用卷积神经网络架构生成图像,我们反转这个过程:不是将图像处理成向量,而是将向量上采样成图像。

这个过程的关键是转置卷积。回想一下,常规卷积通常用于在增加深度的同时减少输入的宽度和高度。转置卷积则相反:它用于在减少深度的同时增加宽度和高度,正如您可以在图 4.4 的生成器网络图中看到的。

图 4.4。生成器接收一个随机噪声向量作为输入,并生成一个 28 × 28 × 1 的图像。它是通过多层转置卷积来实现的。在卷积层之间,我们应用批量归一化以稳定训练过程。(图像未按比例缩放。)

生成器从噪声向量z开始。使用全连接层,我们将向量重构成一个具有小基础(宽度×高度)和大深度的三维隐藏层。使用转置卷积,输入逐渐被重塑,使其基础增长而深度减少,直到我们达到最终层,其形状是我们想要合成的图像,28 × 28 × 1。在每个转置卷积层之后,我们应用批量归一化和Leaky ReLU激活函数。在最终层,我们不应用批量归一化,并且,而不是 ReLU,我们使用tanh激活函数。

将所有步骤组合起来,我们执行以下操作:

  1. 将一个随机噪声向量通过全连接层重构成一个 7 × 7 × 256 张量。

  2. 使用转置卷积,将 7 × 7 × 256 张量转换为 14 × 14 × 128 张量。

  3. 应用批量归一化和Leaky ReLU激活函数。

  4. 使用转置卷积,将 14 × 14 × 128 张量转换为 14 × 14 × 64 张量。请注意,宽度和高度维度保持不变;这是通过在Conv2DTranspose中将步长参数设置为 1 来实现的。

  5. 应用批量归一化和Leaky ReLU激活函数。

  6. 使用转置卷积,将 14 × 14 × 64 张量转换为输出图像大小,28 × 28 × 1。

  7. 应用tanh激活函数。

以下列表显示了在 Keras 中实现时生成器网络的外观。

列表 4.3。DCGAN 生成器
def build_generator(z_dim):

    model = Sequential()

    model.add(Dense(256 * 7 * 7, input_dim=z_dim))                           ***1***
    model.add(Reshape((7, 7, 256)))

    model.add(Conv2DTranspose(128, kernel_size=3, strides=2, padding='same'))***2***

    model.add(BatchNormalization())                                          ***3***

    model.add(LeakyReLU(alpha=0.01))                                         ***4***

    model.add(Conv2DTranspose(64, kernel_size=3, strides=1, padding='same')) ***5***

    model.add(BatchNormalization())                                          ***3***

    model.add(LeakyReLU(alpha=0.01))                                         ***4***

    model.add(Conv2DTranspose(1, kernel_size=3, strides=2, padding='same'))  ***6***

    model.add(Activation('tanh'))                                            ***7***

    return model
  • 1 通过全连接层将输入重构成 7 × 7 × 256 张量

  • 2 转置卷积层,从 7 × 7 × 256 张量转换为 14 × 14 × 128 张量

  • 2 批量归一化

  • 4 Leaky ReLU 激活

  • 5 转置卷积层,从 14 × 14 × 128 转换为 14 × 14 × 64 张量*

  • 6 转置卷积层,从 14 × 14 × 64 转换为 28 × 28 × 1 张量*

  • 7 输出层使用 tanh 激活*

4.4.3. 实现判别器

判别器是一个熟悉的卷积神经网络,它接收一个图像并输出一个预测向量:在这种情况下,是一个二元分类,指示输入图像被认为是真实的而不是伪造的。图 4.5 展示了我们将要实现的判别器网络。

图 4.5. 判别器接收一个 28 × 28 × 1 的图像作为输入,应用多个卷积层,并使用 sigmoid 激活函数 σ 输出一个概率,表明输入图像是真实的而不是伪造的。在卷积层之间,我们应用批标准化以稳定训练过程。(图像未按比例缩放。)

判别器的输入是一个 28 × 28 × 1 的图像。通过应用卷积,图像被转换,使其基础(宽度 × 高度)逐渐减小,其深度逐渐增加。在所有卷积层上,我们应用 Leaky ReLU 激活函数。批标准化用于所有卷积层,除了第一层。对于输出,我们使用全连接层和 sigmoid 激活函数。

将所有步骤组合起来,我们执行以下操作:

  1. 使用卷积层将一个 28 × 28 × 1 的输入图像转换为 14 × 14 × 32 张量。

  2. 应用 Leaky ReLU 激活函数。

  3. 使用卷积层,将 14 × 14 × 32 张量转换为 7 × 7 × 64 张量。

  4. 应用批标准化和 Leaky ReLU 激活函数。

  5. 使用卷积层,将 7 × 7 × 64 张量转换为 3 × 3 × 128 张量。

  6. 应用批标准化和 Leaky ReLU 激活函数。

  7. 将 3 × 3 × 128 张量展平为大小为 3 × 3 × 128 = 1152 的向量。

  8. 使用一个全连接层,并输入到 sigmoid 激活函数,以计算输入图像是否为真实的概率。

以下列表是判别器模型的 Keras 实现。

列表 4.4. DCGAN 判别器
def build_discriminator(img_shape):

    model = Sequential()

    model.add(                                  ***1***
        Conv2D(32,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(LeakyReLU(alpha=0.01))            ***2***

    model.add(                                  ***3***
        Conv2D(64,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(BatchNormalization())             ***4***

    model.add(LeakyReLU(alpha=0.01))            ***5***

    model.add(                                  ***6***
        Conv2D(128,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(BatchNormalization())             ***7***

    model.add(LeakyReLU(alpha=0.01))            ***8***

    model.add(Flatten())                        ***9***
    model.add(Dense(1, activation='sigmoid'))

    return model
  • 1 卷积层,从 28 × 28 × 1 转换为 14 × 14 × 32 张量*

  • 2 Leaky ReLU 激活*

  • 3 卷积层,从 14 × 14 × 32 转换为 7 × 7 × 64 张量*

  • 4 批标准化*

  • 5 Leaky ReLU 激活*

  • 6 卷积层,从 7 × 7 × 64 张量转换为 3 × 3 × 128 张量*

  • 7 批标准化*

  • 8 Leaky ReLU 激活*

  • 9 输出层使用 sigmoid 激活*

4.4.4. 构建 和 运行 DCGAN

除了用于生成器和判别器的网络架构外,DCGAN 网络的其余设置和实现与我们在第三章(chapter 3)中使用的简单 GAN 相同。这突出了 GAN 架构的通用性。列表 4.5 代码构建模型,列表 4.6 训练模型。

列表 4.5. 构建 和 编译 DCGAN
def build_gan(generator, discriminator):

    model = Sequential()

    model.add(generator)                                      ***1***
    model.add(discriminator)

    return model

discriminator = build_discriminator(img_shape)                ***2***
discriminator.compile(loss='binary_crossentropy',
                      optimizer=Adam(),
                      metrics=['accuracy'])

generator = build_generator(z_dim)                            ***3***

discriminator.trainable = False                               ***4***

gan = build_gan(generator, discriminator)                     ***5***
gan.compile(loss='binary_crossentropy', optimizer=Adam())
  • 1 结合生成器和判别器模型

  • 2 构建 和 编译 判别器

  • 2 构建 生成器

  • 4 在生成器训练期间保持判别器参数不变

  • 5 使用固定的判别器构建和编译 GAN 模型以训练生成器

列表 4.6. DCGAN 训练循环
losses = []
accuracies = []
iteration_checkpoints = []

def train(iterations, batch_size, sample_interval):

    (X_train, _), (_, _) = mnist.load_data()                              ***1***

    X_train = X_train / 127.5 - 1.0                                       ***2***
    X_train = np.expand_dims(X_train, axis=3)

    real = np.ones((batch_size, 1))                                       ***3***

    fake = np.zeros((batch_size, 1))                                      ***4***

    for iteration in range(iterations):

        idx = np.random.randint(0, X_train.shape[0], batch_size)          ***5***
        imgs = X_train[idx]

        z = np.random.normal(0, 1, (batch_size, 100))                     ***6***
        gen_imgs = generator.predict(z)

        d_loss_real = discriminator.train_on_batch(imgs, real)            ***7***
        d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
        d_loss, accuracy = 0.5 * np.add(d_loss_real, d_loss_fake)

        z = np.random.normal(0, 1, (batch_size, 100))                     ***8***
        gen_imgs = generator.predict(z)

        g_loss = gan.train_on_batch(z, real)                              ***9***

        if (iteration + 1) % sample_interval == 0:

            losses.append((d_loss, g_loss))                               ***10***
            accuracies.append(100.0 * accuracy)                           ***10***
            iteration_checkpoints.append(iteration + 1)                   ***10***

            print("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" %          ***11***
                  (iteration + 1, d_loss, 100.0 * accuracy, g_loss))

            sample_images(generator)                                      ***12***
  • 1 加载 MNIST 数据集

  • 2 将 [0, 255] 灰度像素值缩放到 [–1, 1]

  • 3 真实图像的标签:全部为 1

  • 4 假图像的标签:全部为 0

  • 5 获取一批随机真实图像

  • 6 生成一批假图像

  • 7 训练判别器

  • 8 生成一批假图像

  • 9 训练生成器

  • 10 保存损失和准确率,以便在训练后绘制

  • 11 输出训练进度

  • 12 输出一个样本生成的图像

为了完整性,我们还在以下列表中包括了 sample_images() 函数。回想一下,在第三章(chapter 3)中,此函数输出一个由生成器在给定训练迭代中合成的 4 × 4 图像网格。

列表 4.7. 显示生成的图像
def sample_images(generator, image_grid_rows=4, image_grid_columns=4):

    z = np.random.normal(0, 1, (image_grid_rows * image_grid_columns, z_dim))***1***

    gen_imgs = generator.predict(z)                                          ***2***

    gen_imgs = 0.5 * gen_imgs + 0.5                                          ***3***

    fig, axs = plt.subplots(image_grid_rows,                                 ***4***
                            image_grid_columns,
                            figsize=(4, 4),
                            sharey=True,
                            sharex=True)

    cnt = 0
    for i in range(image_grid_rows):
        for j in range(image_grid_columns):
            axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray')            ***5***
            axs[i, j].axis('off')
            cnt += 1
  • 1 生成随机噪声样本

  • 2 从随机噪声生成图像

  • 3 将图像像素值缩放到 [0, 1]

  • 4 设置图像网格

  • 5 输出一个图像网格

接下来,以下代码用于运行模型。

列表 4.8. 运行模型
iterations = 20000                                 ***1***
batch_size = 128
sample_interval = 1000

train(iterations, batch_size, sample_interval)     ***2***
  • 1 设置超参数

  • 2 训练 DCGAN 指定次数的迭代

4.4.5. 模型输出

图 4.6 展示了 DCGAN 完全训练后生成器生成的手写数字样本。为了对比,图 4.7 展示了第三章(chapter 3)中 GAN 生成的数字样本,图 4.8 展示了 MNIST 数据集中的真实手写数字样本。

图 4.6. 一个由完全训练的 DCGAN 生成的手写数字样本

图片

图 4.7. 由第三章(chapter 3)中实现的 GAN 生成的手写数字样本

图片

图 4.8. 从 MNIST 数据集中随机生成的真实手写数字网格,用于训练我们的 DCGAN。与我们在第三章(chapter 3)中实现的简单 GAN 生成的图像不同,许多由完全训练的 DCGAN 生成的手写数字基本上与训练数据无法区分。

图片

如前文所示,我们为实施 DCGAN 所投入的额外工作得到了丰厚的回报。网络在完全训练后产生的许多手写数字图像几乎与人类手写的无法区分。

4.5. 结论

DCGAN 展示了 GAN 框架的通用性。从理论上讲,判别器和生成器可以表示为任何可微分的函数,甚至是一个多层卷积网络这样复杂的函数。然而,DCGAN 也表明,在实际中实现更复杂的实现存在重大障碍。如果没有批标准化这样的突破,DCGAN 将无法正确训练。

在下一章中,我们将探讨一些理论上的和实践上的限制,这些限制使得 GAN 的训练变得如此具有挑战性,以及克服这些限制的方法。

摘要

  • 卷积神经网络(ConvNets)使用一个或多个卷积滤波器在输入体积上滑动。在每次滑动输入的过程中,滤波器使用单一组参数来产生一个激活值。所有滤波器产生的激活值共同构成了输出层。

  • 批标准化是一种通过在传递给下一层之前对每一层的输出进行归一化,以减少神经网络中协变量偏移(训练过程中输入值分布在不同层之间的变化)的方法。

  • 深度卷积生成对抗网络(DCGAN)是一种生成对抗网络,其生成器和判别器均采用卷积神经网络。这种架构在图像处理任务中表现出色,包括手写数字生成,我们在代码教程中实现了这一功能。

第二部分. GANs 的高级主题

第二部分探讨了 GANs 的一些高级主题。在第一部分的基础概念之上,你将深化对 GANs 的理论理解,并扩展 GAN 实现的实际工具箱:

  • 第五章涵盖了训练 GANs 的理论和实践障碍以及如何克服它们。

  • 第六章介绍了一种开创性的训练方法,称为渐进式 GAN,它使 GANs 能够以前所未有的分辨率合成图像。

  • 第七章涵盖了 GANs 在半监督学习(仅用一小部分标记示例训练分类器的方法)中的应用,这是一个具有巨大实际重要性的领域。

  • 第八章介绍了条件 GAN,这是一种在训练生成器和判别器时使用标签(或其他条件信息)来实现有针对性的数据生成的技术。

  • 第九章探讨了 CycleGAN,这是一种通用的图像到图像翻译技术——将一个图像(如苹果的照片)转换为另一个图像(如橙子的照片)。

第五章. 训练和常见挑战:GANing for success

本章涵盖

  • 应对评估 GANs 的挑战

  • 最小-最大、非饱和和水合 GANs

  • 使用技巧和窍门来最佳训练 GAN

注意

在阅读本章时,请记住,GANs 在训练和评估方面都非常有名,难以进行。与其他任何尖端领域一样,关于最佳方法的观点总是不断演变。

诸如“如何训练您的 DRAGAN”之类的论文既证明了机器学习研究人员制造糟糕笑话的惊人能力,也说明了训练生成对抗网络(GANs)的难度。数十篇 arXiv 论文专注于提高 GANs 训练的目的,许多研讨会也致力于在顶级学术会议(包括神经信息处理系统,或 NIPS,这是一场重要的机器学习会议^([1]))的各个方面进行培训。

¹

NIPS 2016 年举办了一次 GAN 训练研讨会,许多该领域的著名研究人员参加了此次研讨会,本章即基于此。NIPS 最近将其缩写更改为 NeurIPS。

但 GAN 训练是一个不断发展的挑战,因此现在需要更新大量资源——包括通过论文和会议展示的资源。本章提供了一个全面且最新的训练技术概述。在本章中,你也将最终体验到没有人会讨厌的东西——数学。(但我们承诺不会使用严格必要的更多。)

开个玩笑,然而,作为本书“GANs 高级主题”部分的第一章,这一章内容相当密集。我们建议您回顾并尝试一些具有多个参数的模型。然后您就可以回到这一章,正如您应该带着对 GAN 的每个部分的功能以及训练它们所面临的挑战有深刻理解来阅读它。

就像这个高级部分的其他章节一样,这一章旨在教会您,同时也为未来几年提供有用的参考。因此,这一章是对人们经验、博客文章和最相关论文中的技巧和窍门的总结。(如果您不喜欢学术,现在是时候拿出那些涂鸦笔在脚注上涂鸦了。)我们将这一章视为一个短暂的学术休息,它将为您提供一张清晰的地图,指示 GAN 的现在和未来的所有惊人发展。

我们还希望这样让您掌握所有基本工具,以便理解可能出现的绝大多数新论文。在许多书中,这会被呈现为优缺点列表,这不会给读者提供对选择的全面高级理解。但由于 GAN 是一个如此新的领域,简单的列表是不可能的,因为文献还没有在一些方面达成最终共识。GAN 也是一个快速发展的领域,所以我们更愿意让您具备导航这个领域的能力,而不是提供可能很快就会过时的信息。

在解释了本章的目的之后,让我们再次明确 GAN 的位置。图 5.1 扩展了第二章中的图表,并展示了模型的分类学,以便您了解存在哪些其他生成技术以及它们是如何(不)相似的。

图 5.1. GAN 在哪里?

图片

(来源:“Generative Adversarial Networks (GANs),” by Ian Goodfellow,NIPS 2016 教程,mng.bz/4O0V

从这个图表中可以得出两个关键要点:

  • 所有这些生成模型最终都源于最大似然,至少是隐式的。

  • 在第二章中引入的变分自动编码器位于树的显式部分。记住我们有一个清晰的损失函数(重建损失)吗?好吧,在 GAN 中我们不再有了。相反,我们现在有两个相互竞争的损失函数,我们将在后面的内容中更深入地探讨。但因此,系统没有单一的解析解。

如果你了解图中展示的其他任何技术,那很好。关键思想是我们正在从显式和可处理的领域转向隐式方法来训练。然而,到目前为止,你应该在想:如果我们没有显式的损失函数(尽管我们在第三章的“冲突目标”部分的第三章中遇到了两个单独的损失),我们如何评估 GAN?如果你正在进行并行、大规模的实验呢?

为了消除潜在的混淆,图 5.1 中的所有技术并不都来自深度学习,我们当然不需要你了解它们中的任何一种,除了 VAEs 和 GANs!

5.1. 评估

让我们重新回顾一下关于伪造达芬奇画作的第一章的类比。想象一下,一个伪造者(生成器)正在试图模仿达芬奇,以便将伪造的画作在展览会上获得认可。这个伪造者正在与一个艺术评论家(判别器)竞争,后者试图只接受真正的作品进入展览。在这种情况下,如果你是那个试图通过模仿达芬奇的风格创作出一幅“失传之作”来欺骗评论家的伪造者,你将如何评估你的表现?每个参与者将如何评估他们的表现?

GANs 试图解决伪造者和艺术评论家之间永无止境的竞争问题。确实,鉴于通常生成器比判别器更有兴趣,我们应该格外仔细地考虑其评估。但是,我们如何量化一位伟大画家的风格或我们模仿得有多接近?我们如何量化生成的整体质量?

5.1.1. 评估框架

最佳解决方案是让达芬奇用他的风格绘制所有可能绘制的画作,然后看看使用 GAN 生成的图像是否会在那个集合中。你可以将这个过程视为最大似然最大化的一种非近似版本。实际上,我们会知道图像要么在这个集合中,要么不在,所以没有涉及到似然。然而,在实践中,这个解决方案从未真正可行。

下一个最好的办法是评估图像,指出要寻找的实例,然后累计错误或伪影的数量。但这些将非常局部化,最终总是需要人类评论家亲自查看艺术品本身。这是一个本质上不可扩展的——尽管可能是第二好的——解决方案。

我们希望有一种统计方法来评估生成样本的质量,因为这可以扩展,并允许我们在实验中进行评估。如果我们没有一个容易计算的指标,我们也无法监控进度。这对于评估不同的实验尤其是一个问题——想象一下在每个实验中都要测量或甚至反向传播,例如超参数初始化。鉴于 GANs 对超参数非常敏感,所以没有统计指标是困难的,因为每次我们想要评估训练质量时,我们都必须回过头来检查人类。

我们为什么不直接使用我们已理解的东西,比如最大似然?它是统计的,衡量的是某种模糊的期望值,而且我们无论如何都会从它那里隐式地推导出来。尽管如此,最大似然难以使用,因为我们需要有一个对潜在分布及其似然的良好估计——这可能意味着超过数十亿张图片。2 此外,即使我们有一个好的样本——这就是我们实际上在训练集中拥有的样本——也有理由超越最大似然。

²

我们在第十章中对维度问题进行了更好的处理。

最大似然还有什么问题?毕竟,它是机器学习研究中的一个成熟指标。一般来说,最大似然有很多理想的特性,但正如我们所提到的,将其用作 GANs 的评估技术并不容易处理。

此外,在实践中,最大似然估计的近似往往过于泛化,因此提供的样本变化过多,不够真实。3 在最大似然下,我们可能会发现现实中不可能出现的样本,例如多头狗或没有身体的多头长颈鹿。但因为我们不希望 GAN 的暴力行为让人做噩梦,所以我们可能需要使用损失函数和/或评估方法来剔除“过于泛化”的样本。

³

请参阅 Ferenc Huszár 于 2015 年发表的论文“如何(不)训练你的生成模型:计划采样、似然、对抗者?”,arxiv.org/abs/1511.05101

考虑过度泛化的另一种方法是,从假数据和真实数据(例如,图像)的概率分布开始,看看在应该为零概率质量的情况下,距离函数(一种衡量真实和假图像分布之间距离的方法)会做什么。如果这些过度泛化样本不太不同,那么由于这些样本的额外损失可能很小,例如,因为这些模式在所有关键问题(如多头)之外都接近真实数据。因此,一个过度泛化的指标将允许在根据真实数据生成过程,本不应该有任何样本的情况下创建样本,例如多头牛。

正因如此,研究人员认为我们需要不同的评估原则,尽管我们实际上总是在最大化似然。我们只是在不同的方式上测量它。对于那些好奇的人来说,KL 散度和 JS 散度——我们稍后会讨论——也是基于最大似然,所以在这里我们可以将它们视为可互换的。

因此,你现在明白我们必须能够评估一个样本,而不能简单地使用最大似然来进行评估。在接下来的几页中,我们将讨论统计评估生成样本质量最常用和接受的两种指标:* inception 分数 (IS)* 和 Fréchet inception 距离 (FID)。这两个指标的优势在于它们已经被广泛验证,与至少某些期望的特性高度相关,例如视觉吸引力或图像的真实感。inception 分数的设计完全围绕样本应可识别的想法,但它也已经显示出与人类对构成真实图像的直觉相关联,这一点通过亚马逊机械工人验证。

亚马逊机械工人是一种服务,允许你按小时购买人们的时间来完成预指定的任务。这就像按需自由职业者或 Task Rabbit,但仅限于在线。

5.1.2. Inception 分数

我们显然需要一个良好的统计评估方法。让我们从理想评估方法应确保的高层次愿望清单开始:

  • 生成的样本看起来像一些真实、可区分的东西——例如,桶或牛。样本看起来很真实,我们可以生成数据集中物品的样本。此外,我们的分类器确信它看到的是它所识别的物品。幸运的是,我们已经有能够将图像分类为特定类别并具有一定置信度的计算机视觉分类器。实际上,这个分数本身是以 Inception 网络命名的,而 Inception 网络就是那些分类器之一。

  • 生成的样本种类繁多,理想情况下应包含原始数据集中表示的所有类别。这一点也非常理想,因为我们的样本应该代表我们给出的数据集;如果我们的 MNIST 生成 GAN 总是缺少数字 8,我们就不会有一个好的生成模型。我们不应该有类间(类别之间)的模式坍塌.^([5])

    请参阅“生成对抗网络图像合成的介绍”,由 He Huang 等人著,2018 年,arxiv.org/pdf/1803.04469.pdf

尽管我们可能对我们的生成模型有进一步的要求,但这是一个良好的开端。

  • inception 分数(IS)* 首次在 2016 年的一篇论文中提出,该论文广泛验证了这一指标,并确认它确实与人类对高质量样本构成的认识相关.^([6])这一指标自那以后在 GAN 研究社区中变得流行。

请参阅“改进的 GANS 训练技术”,由 Tim Salimans 等人著,2016 年,arxiv.org/pdf/1606.03498.pdf

我们已经解释了为什么我们想要这个指标。现在让我们深入了解技术细节。计算 IS 是一个简单的过程:

  1. 我们计算真实分布和生成分布之间的 Kullback–Leibler(KL)散度.^([7])

    我们在第二章中介绍了 KL 散度。

  2. 我们对步骤 1 的结果进行指数化。

让我们来看一个例子:一个辅助分类器生成对抗网络(ACGAN)的失败模式,^([8])其中我们试图从 ImageNet 数据集中生成雏菊的示例。当我们对以下 ACGAN 失败模式运行 Inception 网络时,我们看到了类似图 5.2 的东西;你的结果可能因操作系统、TensorFlow 版本和实现细节而异。

请参阅“Auxiliary Classifier GANs 的辅助图像合成”,由 Augustus Odena 等人著,2017 年,arxiv.org/pdf/1610.09585.pdf

图 5.2. ACGAN 失败模式。右侧的分数表示 softmax 输出。

(来源:Odena,2017 年,arxiv.org/pdf/1610.09585.pdf。)

这里需要注意的重要一点是,Inception 分类器并不确定它在看什么,尤其是在前三个类别中。人类会推断这可能是一朵花,但我们也不确定。总体预测的置信度也相当低(分数最高为 1.00)。这是一个会得到低 IS 的例子,这与本节开头我们提出的两个要求相匹配。因此,我们的指标之旅是成功的,因为这符合我们的直觉。

5.1.3. Fréchet inception 距离

接下来要解决的问题是没有足够的例子。通常,GAN 只为每个类别学习少量图像。2017 年,提出了一种新的解决方案:Fréchet inception distance (FID).^([9]) FID 通过使其对噪声更鲁棒并允许检测类内(类内)样本缺失来改进 IS。

参见“GANs Trained by a Two Time-Scale Update Rule Converge to a Local Nash Equilibrium”,作者 Martin Heusel 等,2017 年,arxiv.org/abs/1706.08500.

这很重要,因为我们如果接受 IS 基线,那么只生成某一类的一个样本在技术上就满足了类别生成有时性的要求。但是,例如,如果我们试图创建一个猫生成算法,这实际上并不是我们想要的(比如说,如果我们有多个品种的猫)。此外,我们希望 GAN 能够输出从多个角度展示猫的样本,以及通常情况下独特的图像。

我们同样不希望 GAN 简单地记忆图像。幸运的是,这要容易检测得多——我们可以查看图像在像素空间中的距离。图 5.3 显示了这可能看起来像什么。FID 的技术实现再次复杂,但高级的想法是我们正在寻找一个生成的样本分布,该分布最小化了我们需要进行的修改数量,以确保生成的分布看起来像真实数据的分布。

图 5.3。GAN 通过主要记忆项目来捕捉模式,这也产生了一个不希望看到的结果,表明 GAN 没有学习到多少有用的信息,并且很可能无法泛化。证据在图像中。前两行是重复样本的对;最后一行是训练集中中间行的最近邻。注意,这些例子在论文中由于低分辨率的 GAN 设置而具有非常低的分辨率。

图 5.3

(来源:“GANs Actually Learn the Distribution? An Empirical Study”,作者 Sanjeev Arora 和 Yi Zhang,2017 年,arxiv.org/pdf/1706.08224v2.pdf.)

FID 是通过运行图像通过 Inception 网络来计算的。在实践中,我们比较中间表示——特征图或层——而不是最终输出(换句话说,我们嵌入它们)。更具体地说,我们评估两个分布——真实和生成的——嵌入均值、方差和协方差的距离。

为了从图像中抽象出来,如果我们有一个对分类器有深入了解的领域,我们可以使用它们的预测作为衡量这个特定样本是否看起来逼真的标准。总结来说,FID 是一种从人类评估者抽象出来的方法,它允许我们从分布的角度进行统计推理,甚至对于像图像的真实性这样难以量化的东西也是如此。

因为这个指标如此新颖,所以仍然值得等待,看看是否会在后来的论文中揭示出其缺陷。但考虑到已经有众多信誉良好的作者开始使用这个指标,我们决定将其包括在内.^([10])

¹⁰

请参阅 Augustus Odena 等人于 2018 年发表的“Is Generator Conditioning Causally Related to GAN Performance?”,arxiv.org/abs/1802.08768。另请参阅 S. Nowozin(微软研究院)于 2018 年 2 月 10 日在 UCL 的演讲。

5.2. 训练挑战

训练 GAN 可能很复杂,我们将向您介绍最佳实践。但在这里,我们只提供一套高级、易于理解的解释,不深入任何证明定理或展示证据的数学,因为细节超出了本书的范围。但我们鼓励您查阅原始资料并自行决定。通常,作者甚至提供代码示例以帮助您开始。

这里是一个主要问题的列表:

  • 模式坍塌—在模式坍塌中,某些模式(例如,类别)在生成的样本中没有得到很好的表示。即使真实数据分布支持这部分分布中的样本,模式也会坍塌;例如,MNIST 数据集中将不会有数字 8。请注意,即使网络已经收敛,模式坍塌也可能发生。我们在解释 IS 时谈到了类间模式坍塌,在讨论 FID 时讨论了类内模式坍塌。

  • 收敛速度慢—这是 GAN 和无监督设置中的一个重大问题,在这种设置中,通常收敛速度和可用计算是主要约束——与监督学习不同,在监督学习中,可用的标记数据通常是第一个障碍。此外,有些人认为计算,而不是数据,将是未来 AI 竞赛的决定性因素。而且,每个人都希望快速模型,不需要几天时间来训练。

  • 过度泛化—在这里,我们特别讨论了模式(潜在数据样本)不应该有支持(不应该存在)的情况。例如,你可能会看到一个有多个身体但只有一个头的牛,或者相反。这种情况发生在 GAN 过度泛化,并基于真实数据学习不应该存在的事物时。

注意,模式坍塌和过度泛化有时可以通过重新初始化算法来最天真地解决,但这样的算法是脆弱的,这是不好的。这个列表大致给出了两个关键指标:速度和质量。但即使这两个指标相似,因为训练最终很大程度上集中在更快地缩小真实分布和生成分布之间的差距。

那么,我们该如何解决这个问题呢?在 GAN 训练方面,几种技术可以帮助我们改进训练过程,就像你使用任何其他机器学习算法一样:

  • 增加网络深度

  • 改变游戏设置

    • 原文提出的 Min-Max 设计及停止标准

    • 原文提出的非饱和设计及停止标准^([11])

      ¹¹

      参见 Ian Goodfellow 等人于 2014 年发表的“生成对抗网络”,arxiv.org/abs/1406.2661

    • 水晶距离 GAN 作为最近的一项改进

  • 带有注释的训练技巧数量

    • 标准化输入

    • 对梯度进行惩罚

    • 更多地训练判别器

    • 避免稀疏梯度

    • 转换为软标签和噪声标签

5.2.1. 增加网络深度

与许多机器学习算法一样,使学习更加稳定的最简单方法就是降低复杂性。如果你可以从一个简单的算法开始,并逐步添加,那么在训练过程中你会获得更多的稳定性,更快的收敛,以及可能的其他好处。第六章更深入地探讨了这一想法。

你可以快速通过一个简单的生成器和判别器实现稳定性,然后在训练过程中逐步增加复杂性,正如在一篇最令人震惊的 GAN 论文中所解释的那样.^([12]) 在这里,NVIDIA 的作者逐步增长两个网络,使得在每个训练周期结束时,生成器的输出大小加倍,判别器的输入也加倍。我们从一个简单的网络开始,并训练直到达到良好的性能。

¹²

参见 Tero Karras 等人于 2017 年发表的“用于提高质量、稳定性和变化的 GAN 的渐进式增长”,arxiv.org/abs/1710.10196

这确保了,而不是从一个比初始输入大小大几个数量级的巨大参数空间开始,我们首先生成一个 4 × 4 像素的图像,并在输出大小加倍之前在这个参数空间中导航。我们重复这个过程,直到达到 1024 × 1024 像素的图像。

亲自看看这是多么令人印象深刻;图 5.4 中的图片都是生成的。现在我们正在超越自动编码器可以生成的模糊 64 × 64 像素图像。

图 5.4. GAN 生成的全高清图像。你可以把这看作是下一章的预告,在那里你将因在本章中所做的所有辛勤工作而得到回报。

(来源:Karras 等人,2017 年,arxiv.org/abs/1710.10196。)

这种方法具有以下优点:稳定性、训练速度,以及最重要的是,生成的样本质量和规模。尽管这种范式是新的,但我们预计越来越多的论文会使用它。你绝对应该尝试一下,因为它是一种可以应用于几乎任何类型 GAN 的技术。

5.2.2. 游戏设置

思考 GAN 的双玩家竞争性质的一种方法是可以想象你在玩围棋或其他任何可以随时结束的棋盘游戏,包括象棋。(实际上,这是借鉴了 DeepMind 对 AlphaGo 的方法及其分为策略网络和价值网络。)作为一个玩家,你需要不仅知道游戏的目标以及因此两个玩家试图完成什么,还要了解你离胜利有多近。所以你有 规则,你有一个 距离(胜利)指标——例如,失去的兵的数量。

但正如不是每个棋盘游戏的胜利指标都同样适用于每个游戏一样,一些 GAN 胜利指标(距离或发散度)往往与特定的游戏设置一起使用,而不是与其他设置一起使用。值得单独检查每个损失函数(胜利指标)和玩家动态(游戏设置)。

在这里,我们开始介绍一些描述 GAN 问题的一些数学符号。方程式很重要,我们承诺不会用不必要的复杂度吓到你。我们引入它们的原因是,给你一个高层次的理解,并为你提供理解许多 GAN 研究人员似乎还没有区分清楚的工具。(也许他们应该在心中训练判别器——哦,好吧。)

5.2.3. Min-Max GAN

正如我们在本书前面解释的那样,你可以从博弈论的角度思考 GAN 设置,其中有两个玩家试图超越对方。但即使是原始的 2014 年论文也提到,这个游戏有两种版本。原则上,更易于理解且理论基础更扎实的正是我们所描述的方法:只需将 GAN 问题视为一个 min-max 游戏。方程式 5.1 描述了判别器的损失函数。

方程式 5.1.

E 代表对 x(真实数据分布)或 z(潜在空间)的期望,D 代表判别器的函数(将图像映射到概率),而 G 代表生成器的函数(将潜在向量映射到图像)。这个第一个方程式可以从任何二元分类问题中熟悉。如果我们给自己一些自由,并消除复杂性,我们可以将这个方程式重写如下:

这表明判别器正在尝试最小化将真实样本误判为伪造样本(第一部分)或将伪造样本误判为真实样本(第二部分)的可能性。

现在,让我们将注意力转向 方程式 5.2 中的生成器的损失函数。

方程式 5.2.

因为我们只有两个智能体,并且它们正在相互竞争,所以生成器的损失是判别器损失的负值是有意义的。

将所有这些放在一起:我们有两个损失函数,其中一个函数是另一个函数的负值。对抗性性质是明显的。生成器试图智胜判别器。至于判别器,记住它是一个二元分类器。判别器也只输出一个数字——而不是二元类别——因此它因自信或缺乏自信而受到惩罚。其余的只是一些复杂的数学,给我们带来了一些美好的性质,例如渐近一致性到 Jensen-Shannon 散度(如果你试图诅咒某人,这是一个很好的短语来记忆)。

我们之前解释了为什么我们通常不使用最大似然。相反,我们使用诸如 KL 散度、Jensen-Shannon 散度 (JSD) 以及最近的地移距离(也称为 Wasserstein 距离)等度量。但所有这些散度都有助于我们理解真实分布和生成分布之间的差异。现在,只需将 JSD 视为我们在第二章中介绍的 KL 散度的对称版本即可。

定义

Jensen-Shannon 散度 (JSD) 是 KL 散度的对称版本。而 KL(p,q)! = KL(q,p),但 JSD(p,q) == JSD(q,p)。

对于那些想要更多细节的人来说,KL 散度以及 JSD 通常被认为是 GANs 最终试图最小化的目标。这些都是距离度量类型,帮助我们理解在多维空间中两个分布之间的差异。一些巧妙的证明将那些散度与 GAN 的 min-max 版本联系起来;然而,这些关注点对于这本书来说过于学术化。如果这段话让你感到困惑,你并没有中风;不要担心。这只是统计学家的东西。

我们通常不会使用 Min-Max GAN (MM-GAN) 之外的任何内容,因为它给我们提供了很好的理论保证。它作为一个整洁的理论框架来理解 GANs:既是一个博弈论概念——源于两个网络/玩家之间的竞争性本质——也是一个信息论概念。除此之外,MM-GAN 通常没有其他优势。通常,只有下述两种设置被使用。

5.2.4. 非饱和 GAN

在实践中,经常发现 min-max 方法会带来更多问题,例如判别器的收敛速度慢。原始的 GAN 论文提出了一种替代方案:非饱和 GAN (NS-GAN)。在这个问题版本中,我们不是试图将两个损失函数作为直接的竞争对手,而是使两个损失函数相互独立,如 方程 5.3 所示,但与原始公式 (方程 5.2) 方向上一致。

再次,让我们关注一般理解:两个损失函数不再直接相互设置。但在方程式 5.3 中,你可以看到生成器正在尝试最小化方程式 5.4 中判别器的第二项的相反数。基本上,它试图不让其生成的样本被发现。

方程式 5.3。

图片

方程式 5.4。

图片

对于判别器的直觉与之前完全相同—方程式 5.1 和方程式 5.4 是相同的,但方程式 5.2 的等效物现在已改变。NS-GAN 的主要原因是,在 MM-GAN 的情况下,梯度可以轻易地饱和—接近 0,这会导致收敛速度慢,因为反向传播的权重更新要么是 0 要么非常小。或许一张图能更清楚地说明这一点;参见图 5.5。

图 5.5. 理论上所假设的关系应该是什么样的草图。y 轴是生成器的损失函数,而 D(G(z))是判别器对生成样本似然性的“猜测”。你可以看到,Minimax (MM)保持平坦的时间太长,因此给生成器提供的信息太少——梯度消失。

图片

(来源:“理解生成对抗网络”,作者:Daniel Seita,2017,mng.bz/QQAj

你可以看到,在 0.0 附近,最大似然和 MM-GAN 的梯度都接近 0,这是早期训练发生的地方,而 NS-GAN 的梯度在那里要高得多,因此训练应该从一开始就更快。

我们对为什么 NS 变体应该收敛到纳什均衡没有好的理论理解。事实上,由于 NS-GAN 是启发式驱动的,使用这种形式不再给我们带来我们曾经得到的任何整洁的数学保证;参见图 5.6。然而,由于 GAN 问题的复杂性,即使在 NS-GAN 的情况下,训练可能根本不会收敛,尽管经验上已经证明它比 MM-GAN 表现更好。

图 5.6. 请保持沉默。

图片

但我们可怕的牺牲带来了性能的显著提升。NS 方法的好处不仅在于初始训练更快,而且由于生成器学习得更快,判别器也学习得更快。这是我们所希望的,因为(几乎)我们所有人都面临着紧张的计算和时间预算,我们学得越快越好。有些人认为,在固定的计算预算下,NS-GAN 尚未被超越,甚至 Wasserstein GAN 也不是一个更好的架构。13

^(13)

请参阅 Mario Lucic 等人于 2017 年发表的“Are GANs Created Equal? A Large-Scale Study”,arxiv.org/abs/1711.10337

5.2.5. 何时停止训练

严格来说,NS-GAN

  • 不再与 JSD 渐近一致

  • 具有理论上甚至更难以捉摸的平衡状态

第一个观点很重要,因为 JSD 是一个有意义的工具,可以解释为什么隐式生成的分布甚至应该收敛到真实数据分布。原则上,这为我们提供了停止标准;但在实践中,这几乎毫无意义,因为我们永远无法验证真实分布和生成分布何时收敛。人们通常每隔几轮迭代就查看生成的样本来决定何时停止。最近,一些人开始通过 FID、IS 或不太流行的切片 Wasserstein 距离来定义停止标准。

第二个观点也很重要,因为不稳定性显然会导致训练问题。一个更重要的问题是知道何时停止。在 GAN 问题的两个原始公式中,我们从未得到一个明确的条件集,在实际情况中,训练何时完成。原则上,我们总是被告知一旦达到纳什均衡,训练就完成了,但在实践中这又很难验证,因为高维性使得均衡难以证明。

如果你想绘制生成器和判别器的损失函数,它们通常会四处跳跃。这是有道理的,因为它们是在相互竞争,所以如果一个变得更好,另一个就会得到更大的损失。仅仅通过查看两个损失函数,我们就不清楚何时实际上已经完成了训练。

在 NS-GAN 的辩护中,应该指出,它仍然比 Wasserstein GAN 快得多。因此,NS-GAN 可能通过能够更快地运行来克服这些限制。

5.2.6. Wasserstein GAN

最近,GAN 训练中出现了一种新的发展,并迅速在学术界获得认可:Wasserstein GAN (WGAN).^([14]) 它现在几乎被每篇重要的学术论文和许多从业者所提及。最终,WGAN 之所以重要,有三个原因:

¹⁴

请参阅 Martin Arjovsky 等人于 2017 年发表的“Wasserstein GAN”,arxiv.org/pdf/1701.07875.pdf

  • 它显著改进了损失函数,现在它们是可解释的,并提供了更清晰的停止标准。

  • 经验上,WGAN 往往有更好的结果。

  • 与许多关于 GAN 的研究不同,它有明确的理论支持,从损失函数开始,展示了我们试图逼近的 KL 散度在理论上或实际上最终并不充分。基于这个理论,它随后提出了一种更好的损失函数,以减轻这个问题。

从上一节中,第一点的意义应该是相当明显的。鉴于生成器和判别器之间的竞争性,我们没有一个明确的停止训练的点。WGAN 使用地球迁移距离作为损失函数,它与生成的样本的视觉质量有明显的相关性。第二点和第三点的益处是相当明显的——我们希望有更高质量的样本和更好的理论基础。

这种魔法是如何实现的?让我们更详细地看看判别器——或者说 WGAN 所称的评论家——的水晶损失。看看 方程式 5.5。

方程式 5.5.

这个方程式与您之前见过的内容有些相似(作为一个高级简化版的 方程式 5.1),但有一些重要的区别。我们现在有函数 f[w],它充当判别器。评论家试图估计地球迁移距离,并寻找在 f[w] 函数的不同(有效)参数化下,真实(第一项)和生成(第二项)分布之间的最大差异。我们现在只是测量差异。评论家试图通过查看使用 f[w] 到共享空间的不同投影来使发生器的日子尽可能难过,以最大化它必须移动的概率质量。

方程式 5.6 展示了发生器,因为它现在必须包括地球迁移距离。

方程式 5.6.

在这个方程式中,从高层次来看,我们试图最小化真实分布的期望值与生成分布的期望值之间的距离。引入 WGAN 的论文本身很复杂,但要点是 f[w] 是一个满足技术约束的函数。

注意

技术约束 f[w] 满足的是 1 – Lipschitz:对于所有 x1, x2:| f(x1) – f(x2) | ≤ | x1 – x2 |。

发生器试图解决的问题与之前的问题类似,但让我们更详细地探讨一下:

  1. 我们从真实分布 (x ~ P[r]) 或生成的分布 x* (gθ,其中 z ~ p(z)) 中抽取 x

  2. 生成的样本是从 z(潜在空间)中抽取的,然后通过 g[θ] 转换得到相同空间中的样本 (*x**),然后使用 f[w] 进行评估。

  3. 我们试图最小化我们的损失函数——在这种情况下是距离函数——地球迁移距离。实际的数字是通过地球迁移距离计算的,我们将在后面解释。

该设置也很棒,因为我们有一个更易于理解的损失函数(例如,没有对数)。我们还有更多可调的训练,因为在 WGAN 设置中,我们必须设置一个裁剪常数,它在标准机器学习中的作用类似于学习率。这为我们提供了一个额外的可调参数,但这也可能是一把双刃剑,如果你的 GAN 架构最终对它非常敏感。但不过多涉及数学,WGAN 有两个实际的影响:

  • 我们现在有更清晰的停止标准,因为这篇论文已经通过后来的论文得到了验证,这些论文显示了判别器损失与感知质量之间的相关性。我们可以简单地测量 Wasserstein 距离,这有助于我们决定何时停止。

  • 我们现在可以训练 WGAN 直到收敛。这很重要,因为元审查论文^([15])表明,使用 JS 损失和生成器在真实分布中的发散性作为训练进度的衡量标准往往是没有意义的.^([16])用人类的语言来说,有时在棋类游戏中,你需要输掉几轮,因此暂时做得更差,以便在几轮迭代中学习,并最终做得更好。

    ¹⁵

    元审查只是对评论的评论。它帮助研究人员从多篇文章中汇总发现。

    ¹⁶

    请参阅威廉·费杜斯等人于 2018 年发表的论文“Many Paths to Equilibrium: GANs Do Not Need to Decrease a Divergence at Every Step”,openreview.net/forum?id=ByQpn1ZA

这可能听起来像魔法。但这部分是因为 WGAN 使用了一种与迄今为止你遇到的所有东西都不同的距离度量。它被称为地球迁移距离Wasserstein 距离,其背后的想法很巧妙。我们这次会善待你,不会用更多的数学来折磨你,但让我们谈谈这个想法。

你隐含地理解有两个非常高维的分布:一个是真实数据生成的一个(我们从未完全看到),另一个是生成器(伪造的)的样本。想想即使是 32 × 32 RGB (x3 × 256 像素值)图像的样本空间有多么庞大。现在想象这两个分布的所有概率质量都只是两座山。第十章更详细地回顾了这一点。为了参考,我们包括了图 5.7,但它主要基于第二章中的相同思想。

图 5.7. 图(a)应该来自第二章。为了增加清晰度,我们在数据图(b)中提供了高斯分布的另一种视图,这些数据来自相同的分布,但只显示了顶部第一个分布的垂直切片和右侧第二个分布的垂直切片。图(a)然后是这些数据的概率密度抽象,其中 z 轴表示该点被采样的概率。现在,尽管其中一个是另一个的抽象,你将如何比较这两个?你将如何确保它们在我们告诉你时是一样的时候?如果这个分布有 3,072 个可能的维度呢?在这个例子中,我们只有两个!我们正在构建如何比较看起来像(b)中的沙堆分布的方法,但记住,随着我们的分布变得更加复杂,正确匹配同类事物也变得更加困难。

图片

想象一下,你需要把代表概率质量的全部地面从虚假分布中移除,使得分布看起来完全像真实分布,或者至少像我们所看到的那样。这就像你的邻居有一个超级酷的沙堡,而你有很多沙子,试图建造一个完全相同的沙堡。要移动所有这些质量到恰到好处的地方需要多少工作量?嘿,没关系,我们都有过这样的经历;有时候你只是希望你的沙堡能更酷一些,更有光泽一些。

使用 Wasserstein 距离的近似版本,我们可以评估我们生成看起来像来自真实分布的样本有多接近。为什么是近似?好吧,一方面是因为我们从未见过真实的数据分布,所以很难评估确切的地球移动距离。

最后,你需要知道的是,地球移动距离比 JS 或 KL 具有更好的性质,并且已经有重要的贡献建立在 WGAN 之上,以及验证了其通常优越的性能。^([17)] 尽管在某些情况下,WGAN 并不完全优于其他所有方法,但它通常在每种情况下至少和它们一样好(尽管应该注意的是,有些人可能不同意这种解释)。^([18)]

^(17)

参见 Ishaan Gulrajani 等人于 2017 年发表的“Improved Training of Wasserstein GANs”,arxiv.org/abs/1704.00028

^(18)

参见 Lucic 等人于 2017 年发表的论文,arxiv.org/abs/1711.10337

总体来说,WGAN(或者梯度惩罚版本,WGAN-GP)被广泛使用,并已成为 GAN 研究与实践中的事实标准——尽管 NS-GAN 在不久的将来也不应该被忘记。当你看到一篇新论文,其中没有将 WGAN 作为比较的基准之一,并且没有很好地解释为什么不包括它时——要小心!

5.3. 游戏设置总结

我们已经介绍了 GAN 设置的三个核心版本:min-max、非饱和和水波斯坦。每个论文的开头都会提到这些版本之一,现在您至少会知道该论文是否使用原始公式,这种公式更易于解释但在实践中效果不佳;或者非饱和版本,它失去了许多数学保证但效果更好;或者较新的水波斯坦版本,它既有理论基础又具有优越的性能。

作为一份便捷指南,表 5.1 列出了我们在本书中使用的 NS-GAN、WGAN 以及甚至改进的 WGAN-GP 公式。这里列出是为了让您有一个相关版本的地方——抱歉,MM-GAN。我们包括 WGAN-GP 是为了完整性,因为这三个是学术界和工业界的首选。

表 5.1. 损失函数总结^([a])

^a

来源:“TensorFlow 中生成模型的集合”,作者 Hwalsuk Lee,mng.bz/Xgv6

名称 值函数 备注
NS-GAN L[D]^(NS) = E[log(D(x))] + E[log(1 – D(G(z)))] L[G]^(NS) = E[log(D(G(z)))] 这是一种原始的公式之一。通常不再实际使用,除了作为基础块或比较。这与您所看到的 NS-GAN 等效,只是没有常数。但这些都是有效等价的。^([[b])]
WGAN L[D]^(WGAN) = E[D(x)] – E[D(G(z)))] L[G]^(WGAN) = E[D(G(z)))] 这是一种损失简化后的 WGAN。这似乎正在为 GAN 创造一个新的范式。我们之前在 方程 5.5 中更详细地解释了此方程。
WGAN-GP^([c]) (梯度惩罚) L[D]^(W – GP) = E[D(x)] – E[D(G(z)))] + GPterm L[G]^(W – GP) = E[D(G(z)))] 这是一种带有梯度惩罚 (GP) 的 GAN 示例。WGAN-GP 通常显示出最佳结果。我们没有在本章中详细讨论 WGAN-GP;我们将其包括在这里是为了完整性。

^b

我们倾向于在书面代码中使用常数,在论文中使用更简洁的数学公式。

^c

这是一种带有梯度惩罚的 WGAN 版本,在新的学术论文中常用。参见 Gulrajani 等人,2017 年,arxiv.org/abs/1704.00028

5.4. 训练技巧

我们现在正从稳固的学术成果转向学术界或从业者刚刚“想出来”的领域。这些仅仅是技巧,通常您只需要尝试一下,看看它们是否适用于您。本节中的列表受到了 Soumith Chintala 2016 年帖子“如何训练一个 GAN:使 GAN 工作的技巧和窍门”(github.com/soumith/ganhacks)的启发,但自那时以来有些事情已经改变了。

变化的一个例子是一些架构建议,例如深度卷积生成对抗网络(DCGAN)是所有事物的基线。目前,大多数人从 WGAN 开始;未来,自注意力生成对抗网络(SAGAN 在第十二章中有所涉及)可能会成为焦点。此外,一些事情仍然是真实的,我们将它们视为普遍接受的,例如使用 Adam 优化器而不是传统的随机梯度下降.^([19]) 我们鼓励您查看该列表,因为它的创建是 GAN 历史上的一个形成性时刻。

¹⁹

为什么 Adam 比传统的随机梯度下降(SGD)更好?因为 Adam 是 SGD 的一个扩展,在实践中往往表现得更好。Adam 将几个训练技巧与 SGD 结合成一个易于使用的包。

5.4.1. 输入归一化

根据几乎每个机器学习资源,包括 Chintala 的列表,将图像归一化到-1 和 1 之间仍然是通常的好主意。我们通常归一化是因为计算更容易处理,就像机器学习的其他部分一样。考虑到对输入的限制,使用例如tanh激活函数来限制生成器的最终输出是一个好主意。

5.4.2. 批标准化

批标准化在第四章中进行了详细讨论。我们将其包括在这里以示完整性。关于我们对批标准化的看法如何改变:最初,批标通常被认为是一个非常成功的技巧,但最近研究表明它有时会得到不良的结果,尤其是在生成器.^([20]) 相反,在判别器中,结果几乎普遍是积极的.^([21])

²⁰

参见 Gulrajani 等人于 2017 年发表的《批标准化》,arxiv.org/abs/1704.00028

²¹

参见 Soumith Chintala 于 2017 年发表的《生成对抗网络教程——GAN 在野外的应用》,www.youtube.com/watch?v=Qc1F3-Rblbw

5.4.3. 梯度惩罚

这个训练技巧建立在 Chintala 列表中的第 10 点之上,其直觉是如果梯度的范数太高,那么可能有问题。即使今天,像 BigGAN 这样的网络在这个领域也在进行创新,正如我们在第十二章中提到的.^([22])

²²

参见 Andrew Brock 等人于 2019 年发表的《大规模 GAN 训练用于高保真自然图像合成》,arxiv.org/pdf/1809.11096.pdf

然而,技术问题仍然存在:简单的加权裁剪可以产生深度学习中常见的消失或爆炸梯度。^([[23)] 我们可以限制判别器输出相对于其输入的梯度范数。换句话说,如果你稍微改变你的输入,你的更新权重不应该改变太多。深度学习充满了这样的魔法。这在 WGAN 设置中尤为重要,但也可以应用于其他地方。^([[24)] 通常,这种技巧以某种形式被许多论文使用。^([[25)]

²³

参见 Gulrajani 等人于 2017 年发表的论文,arxiv.org/abs/1704.00028

²⁴

尽管在这里作者将判别器称为批评者,借鉴了强化学习,因为那篇论文的大部分灵感都来自它。

²⁵

参见 Xudong Mao 等人于 2016 年发表的“Least Squares Generative Adversarial Networks”,arxiv.org/abs/1611.04076。另见 David Berthelot 等人于 2017 年发表的“BEGAN: Boundary Equilibrium Generative Adversarial Networks”,arxiv.org/abs/1703.10717

在这里,我们可以简单地使用你最喜欢的深度学习框架的原生实现来惩罚梯度,而不用关注我们描述之外的实现细节。最近,顶级研究人员(包括一位优秀的同行)已经发表了更智能的方法,并在 ICML 2018 上进行了展示,但它们的广泛学术接受度尚未得到证明。^([[26)] 许多工作正在进行中,以使 GANs 更加稳定——例如 Jacobian clamping,这也在任何元研究中尚未得到再现——因此我们需要等待并看看哪些方法会成功。

²⁶

参见 Odena 等人于 2018 年发表的论文,arxiv.org/abs/1802.08768

5.4.4. 更多地训练判别器

训练判别器更多是一种最近取得很大成功的方法。在 Chintala 的原始列表中,这被标记为不确定,所以请谨慎使用。有两种主要方法:

  • 在生成器有机会产生任何东西之前先预训练判别器。

  • 每个训练周期中为判别器提供更多的更新。一个常见的比例是每生成器更新一次,判别器更新五次。

深度学习研究员和教师 Jeremy Howard 的话说,这是因为它是“盲人领盲人”。你需要最初和持续地注入有关真实世界数据外观的信息。

5.4.5. 避免稀疏梯度

直观上,稀疏梯度(如 ReLU 或 MaxPool 产生的)会使训练更困难。这是因为以下原因:

  • 直觉,尤其是平均池化背后的直觉,可能会让人困惑,但可以这样想:如果我们采用标准的最大池化,我们将丢失整个卷积感受野中除了最大值之外的所有值,这使得使用转置卷积(在 DCGAN 的情况下)来恢复信息变得更加困难。使用平均池化,我们至少对平均值有一个概念。这仍然不是完美的——我们仍然在丢失信息——但至少比之前少,因为平均值比简单的最大值更能代表。

  • 另一个问题是我们使用,比如说,常规的修正线性单元(ReLU)激活时可能会出现的信息损失。看待这个问题的方法之一是考虑在应用这个操作时丢失了多少信息,因为我们可能以后需要恢复它。回想一下,ReLU(x) 简单地是 max(0,x),这意味着对于所有负值,所有这些信息都将永远丢失。如果我们确保从负区域传递信息,并表明这些信息是不同的,我们就可以保留所有这些信息。

正如我们所建议的,幸运的是,对于这两个问题都存在一个简单的解决方案:我们可以使用 Leaky ReLU——对于负 x,它类似于 0.1 × x,而对于至少为 0 的 x,则是 1 × x——以及平均池化来绕过许多这些问题。其他激活函数也存在(例如 sigmoid、ELU 和 tanh),但人们通常最常使用 Leaky ReLU。

备注

Leaky ReLU 可以是任何实数,通常情况下,0 < x < 1。

总体来说,我们试图最小化信息损失,并使信息流尽可能合理,而无需要求 GAN 以某种奇怪的方式反向传播错误,同时它还要学习映射。

5.4.6. 软标签和噪声标签

研究者们采用多种方法来对标签添加噪声或进行平滑处理。伊恩·古德费洛(Ian Goodfellow)倾向于推荐单侧标签平滑(例如,使用 0 和 0.9 作为二进制标签),但通常来说,在添加噪声或裁剪方面进行尝试似乎是个不错的想法。

摘要

  • 你已经学会了为什么评估对于生成模型来说是一个如此困难的话题,以及我们如何通过明确的停止标准来训练一个良好的 GAN。

  • 不同的评估技术超越了分布的简单统计评估,为我们提供了与视觉样本质量相关的一些更有用的信息。

  • 训练在三种设置中进行:博弈论的最小-最大 GAN、启发式动机的非饱和 GAN 以及最新且理论基础良好的 Wasserstein-GAN。

  • 以下是一些允许我们更快训练的训练技巧:

    • 正则化输入,这在机器学习中是标准的

    • 使用梯度惩罚,这为我们提供了在训练中的更多稳定性

    • 帮助预热判别器,最终为我们提供一个好的生成器,因为这样做为生成的样本设定了一个更高的标准

    • 避免稀疏梯度,因为它们会丢失太多信息

    • 在软标签和噪声标签上玩弄,而不是典型的二分类

第六章. 与 GAN 一起进步

本章涵盖

  • 在训练过程中逐步增长判别器和生成器网络

  • 使训练更加稳定,输出更加多样化、质量更高和分辨率更高

  • 使用 TFHub,一个用于模型和 TensorFlow 代码的新中央仓库

在本章中,我们提供了一个使用 TensorFlow 和最新发布的 TensorFlow Hub(TFHub)构建 Progressive GAN 的实战教程。Progressive GAN(也称为PGGANProGAN)是一种前沿技术,它成功地生成了全高清逼真图像。在 2018 年举办的顶级机器学习会议——国际学习表示会议(ICLR)上展出,这项技术引起了巨大的轰动,谷歌立即将其集成为数不多的 TensorFlow Hub 模型之一。事实上,这项技术受到了深度学习之父之一 Yoshua Bengio 的高度赞扬,称其为“几乎太好了以至于不真实。”发布后,它立即成为学术演示和实验项目的热门选择。

我们建议你使用 TensorFlow 1.7 或更高版本阅读本章,但写作时最新的发布版本是 1.8+,所以我们使用了这个版本。对于 TensorFlow Hub,我们建议使用不超过 0.4.0 的版本,因为后续版本由于与 TensorFlow 1.x 的兼容性问题而难以导入。阅读本章后,你将能够实现 Progressive GAN 的所有关键改进。这四个创新如下:

  • 在更高分辨率的层中逐步增长和平滑衰减

  • 小批量标准差

  • 等化学习率

  • 像素级特征归一化

本章包含两个主要示例:

  • Progressive GANs 的关键创新代码——更具体地说,是平滑渐变的高分辨率层以及其他三个之前列出的创新。Progressive GAN 技术的其余实现内容过于庞大,无法包含在这本书中。

  • Google 在 TFHub 上提供的预训练、易于下载的实现,TFHub 是一个新的机器学习模型集中仓库,类似于软件包世界的 Docker Hub 或 Conda 和 PyPI 仓库。这个实现将使我们能够进行潜在空间插值,以控制生成的示例的特征。它将简要介绍生成器潜在空间中的种子向量,以便我们可以得到我们想要的图片。你可以在第二章和第四章中看到这个想法。

我们决定使用 TFHub 实现 PGGAN 而不是像其他章节那样从头开始实现的原因有三:

  • 尤其对于实践者来说,我们想确保你至少在一个章节中接触到可能加快你工作流程的软件工程最佳实践。想要尝试快速 GAN 来解决你的问题吗?只需使用 TFHub 上的实现之一。现在比我们最初写这一章时多了很多,包括许多参考实现(例如,第十二章中的 BigGAN 和第五章中的 NS-GAN)。我们想让你接触到易于使用、最前沿的例子,因为这就是机器学习的趋势——尽可能自动化机器学习,这样我们就可以专注于最重要的事情:产生影响力。谷歌的 Cloud AutoML(cloud.google.com/automl/)和亚马逊的 SageMaker(aws.amazon.com/sagemaker/)是这个趋势的典型例子。甚至 Facebook 最近也推出了 PyTorch Hub,因此现在两个主要框架都拥有这样的资源。

  • PGGAN 的原始实现需要 NVIDIA 研究人员一到两个月的时间来运行,我们认为这对于任何个人来说都是不切实际的,尤其是如果你想要实验或出错的话。1 TFHub 仍然提供了一个完全可训练的 PGGAN,所以如果你想将计算时间用于其他目的,你可以这样做!

    ¹

    参见 Tero Karras 于 2018 年发表的“Progressive Growing of GANs for Improved Quality, Stability, and Variation”,github.com/tkarras/progressive_growing_of_gans

  • 我们仍然想向你展示 PGGANs 最重要的创新。但如果我们想要很好地解释这些——包括代码——即使是在 Keras 中,我们也无法将所有实现细节都放入一个章节中,因为所有的实现都相当庞大。TFHub 允许我们跳过样板代码,专注于重要的想法。

6.1. 潜空间插值

回想一下第二章,我们有一个这个低分辨率空间——称为潜空间——它是我们输出的种子。与第四章中的 DCGAN 以及渐进式 GAN 一样,最初训练的潜空间具有语义上有意义的属性。这意味着我们可以找到一些向量偏移量,例如,将眼镜添加到一张人脸图像中,同样的偏移量会在新的图像中添加眼镜。我们还可以选择两个随机向量,然后在它们之间以相等的增量移动,从而逐渐——平滑地——得到一个与第二个向量匹配的图像。

这被称为插值,你可以在图 6.1 中看到这个过程。正如 BigGAN 的作者所说,从一个向量到另一个向量的有意义过渡表明 GAN 已经学习了一些底层结构。

图 6.1。我们可以执行潜在空间插值,因为发送给生成器的潜在向量会产生一致的结果,这些结果在某些方面是可预测的;不仅生成过程是可预测的,而且输出也不是锯齿状的——或者对小的变化反应剧烈——考虑到潜在向量的变化。例如,如果我们想要一张融合两个面孔的图像,我们只需要在两个向量的平均值附近搜索。

图片

6.2. 他们成长得如此之快

在前面的章节中,你学习了使用 GAN 容易实现哪些结果以及哪些结果难以实现。此外,像模式坍塌(只显示总体分布的几个示例)和收敛性不足(导致结果质量不佳的原因之一)这样的术语对我们来说已经不再陌生了。

最近,一个芬兰的 NVIDIA 团队发布了一篇论文,成功地超越了之前许多前沿的论文:“用于提高质量、稳定性和多样性的 GAN 的渐进式增长”,由 Tero Karras 等人撰写。这篇论文有四个基本创新,所以让我们按顺序逐一介绍。

6.2.1. 高分辨率层的渐进式增长和平滑

在我们深入探讨渐进式生成对抗网络(GAN)的功能之前,让我们从一个简单的类比开始。想象一下从鸟瞰的角度观察一个山区:你看到许多山谷,那里有美丽的溪流和村庄——通常非常适合居住。然后,你还有许多山顶,由于天气条件,它们崎岖不平,通常不适宜居住。这类似于损失函数的地形,我们希望通过沿着山坡向下进入山谷来最小化损失,因为这些山谷要美好得多。

我们可以想象训练就像将登山者随机地投入到这个山区的一个地方,然后跟随他们沿着山坡进入山谷的路径。这就是随机梯度下降所做的事情,第十章更详细地回顾了这一点。现在,不幸的是,如果我们从一个非常复杂的山脉开始,登山者将不知道该走哪个方向。我们冒险者周围的空间将是锯齿状和崎岖的。很难辨认出哪个最宜居住、最低的山谷。相反,我们放大视野,降低山脉的复杂性,给登山者一个这个特定区域的总体印象。

当我们的登山者接近山谷时,我们可以通过放大地形来开始增加复杂性。然后,我们不再只看到粗糙/像素化的纹理,而是可以看到更细致的细节。这种方法的优点是,随着登山者沿着山坡下行,他们可以轻松地进行一些小的优化,使徒步旅行更加容易。例如,他们可以穿过一条干涸的溪流,使山谷的下降更加迅速。这就是渐进式增长:随着我们的前进,提高地形的分辨率。

然而,如果你曾经玩过开放世界的电脑游戏,或者用 3D 模式快速滚动过 Google Earth,你就会知道快速提高你周围地形的分辨率可能会令人震惊且不愉快。物体突然出现。因此,我们逐渐平滑地引入,并随着登山者接近目标而缓慢地引入更多复杂性。

从技术角度讲,随着训练的进行,我们从几个低分辨率的卷积层转向许多高分辨率的层。因此,我们首先训练早期层,然后才引入一个更高分辨率的层,在那里导航损失空间更困难。我们从简单的东西开始——例如,训练了几个步骤的 4 × 4——到更复杂的东西——例如,训练了几个时期的 1024 × 1024,如图 6.2 所示。图 6.2。

图 6.2。你能看到我们是怎样从一个平滑的山脉开始,并通过放大来逐渐增加复杂度吗?这正是额外层对损失函数所做的。这很有用,因为我们的山脉区域(损失函数)在变得不那么尖锐时更容易导航。你可以这样想:当我们有一个更复杂的结构(b)时,损失函数是尖锐的,难以导航(d),因为有很多参数——尤其是在早期层——可以产生巨大影响,通常会增加问题的维度。然而,如果我们最初移除一些复杂性(a),我们就可以在早期得到一个更容易导航的损失函数(c),并且只有在我们对损失空间的近似正确部分有信心时,复杂性才会增加。只有在这种情况下,我们才从(a)和(c)版本移动到(b)和(d)版本。

图片

在这种情况下的问题是,每次引入一个额外的层(例如,从 4 × 4 到 8 × 8),我们仍然在训练中引入了巨大的冲击。PGGAN 的作者所做的相反,是在图 6.3 中平滑地引入这些层,以便给系统时间来适应更高的分辨率。

图 6.3。当我们用 16 × 16 分辨率(a)等训练足够步骤后,我们在生成器(G)中引入另一个转置卷积,在判别器(D)中引入另一个卷积,以获得 G 和 D 之间的“接口”为 32 × 32。但我们还引入了两个路径:(1 – α)简单的最近邻上采样,它没有训练参数,但也很天真;(α)额外的转置卷积,需要训练但最终会表现得更好。

图片

然而,我们并没有立即跳到这个分辨率,而是通过参数 alpha(α)平滑地引入这个更高分辨率的新的层,alpha 的值在 0 和 1 之间。Alpha 影响我们使用多少旧层(但已放大)或原生更大的层。在 D 的方面,我们简单地缩小 0.5x,以便平滑地注入用于判别的训练层。这是图 6.3 中的(b)。当我们对这一新层有信心时,我们保留 32 × 32——图中的(c),然后在我们正确训练了 32 × 32 之后,我们准备再次生长。

6.2.2. 示例实现

对于我们详细描述的所有创新,在本节中我们将提供工作但独立的版本,这样我们就可以讨论代码。作为一个练习,你可能想尝试将这些内容实现为一个 GAN 网络,也许使用现有的先验架构。如果你准备好了,让我们加载我们那可靠的、值得信赖的机器学习库,并开始工作:

import tensorflow as tf
import keras as K

在代码中,渐进式平滑可能看起来像以下列表。

列表 6.1. 渐进式生长和平滑上采样
def upscale_layer(layer, upscale_factor):
    '''
    Upscales layer (tensor) by the factor (int) where
    the tensor is [group, height, width, channels]
    '''
    height = layer.get_shape()[1]
    width = layer.get_shape()[2]
    size = (upscale_factor * height, upscale_factor * width)
    upscaled_layer = tf.image.resize_nearest_neighbor(layer, size)
    return upscaled_layer

def smoothly_merge_last_layer(list_of_layers, alpha):
    '''
    Smoothly merges in a layer based on a threshold value alpha.
    This function assumes: that all layers are already in RGB.
    This is the function for the Generator.
    :list_of_layers    :   items should be tensors ordered by resolution
    :alpha             :    float \in (0,1)
    '''
    last_fully_trained_layer = list_of_layers[-2]                            ***1***
    last_layer_upscaled = upscale_layer(last_fully_trained_layer, 2)         ***2***

    larger_native_layer = list_of_layers[-1]                                 ***3***

    assert larger_native_layer.get_shape() == last_layer_upscaled.get_shape()***4***

    new_layer = (1-alpha) * upscaled_layer + larger_native_layer * alpha     ***5***

    return new_layer
  • 1 提示!如果你使用的是纯 TensorFlow 而不是 Keras,始终要记住作用域。

  • 2 现在我们有了最初训练的层。

  • 3 新增的层尚未完全训练

  • 4 这确保我们可以运行合并代码。

  • 5 此代码块应充分利用广播功能。

现在你已经理解了渐进式生长和平滑的底层细节,且没有不必要的复杂性,希望你能欣赏这一想法的普适性。尽管 Karras 等人并非第一个在训练过程中想出某种方法来增加模型复杂性的,但这似乎是最有希望的途径,并且确实是反响最强烈的创新。截至 2019 年 6 月,这篇论文已被引用超过 730 次。考虑到这个背景,让我们继续探讨第二个重大创新。

6.2.3. 小批量标准差

Karras 等人在其论文中引入的下一个创新是小批量标准差。在我们深入探讨之前,让我们从第五章回顾一下模式坍塌的问题,这是当 GAN 学习如何创建几个好的示例或仅对其做轻微排列时发生的。我们通常希望生成真实数据集中所有人的面孔,而不仅仅是某位女性的一个图片。

因此,Karras 等人创造了一种方法,让判别器能够判断它所获得的样本是否足够多样化。本质上,我们为判别器计算一个额外的标量统计量。这个统计量是生成器生成的或来自真实数据的迷你批处理中所有像素的标准差。这是一个非常简单且优雅的解决方案:现在判别器需要学习的只是,如果它评估的批处理图像中的标准差低,那么图像很可能是伪造的,因为真实数据具有更多的方差。2 生成器别无选择,只能增加生成样本的方差,以有机会欺骗判别器。

²

有些人可能会反对,当采样的真实数据包含大量非常相似的图片时,这也可能发生。虽然从技术上讲这是正确的,但在实践中这很容易解决,记住相似度必须非常高,以至于简单的最近邻聚类就能揭示这一点。

超越直觉,技术实现很简单,因为它只应用于判别器。鉴于我们还想最小化可训练参数的数量,我们只包含一个额外的数字,这似乎已经足够。这个数字作为特征图附加——想想 维度tf.shape 列表中的最后一个数字。

具体的步骤如下,并在 列表 6.2 中展示:

  1. [4D -> 3D] 我们计算批处理中所有图像的标准差,以及所有剩余的通道——高度、宽度和颜色。然后我们得到一个包含每个像素和每个通道标准差的单一图像。

  2. [3D -> 2D] 我们对所有通道的标准差进行平均——得到一个单独的特征图或标准差矩阵,针对该像素,但颜色通道已合并。

  3. [2D -> 标量/0D] 我们对前一个矩阵中所有像素的标准差进行平均,以得到一个单独的标量值。

列表 6.2. 小批量标准差
def minibatch_std_layer(layer, group_size=4):
    '''
    Will calculate minibatch standard deviation for a layer.
    Will do so under a prespecified tf-scope with Keras.
    Assumes layer is a float32 data type. Else needs validation/casting.
    NOTE: there is a more efficient way to do this in Keras, but just for
    clarity and alignment with major implementations (for understanding)
    this was done more explicitly. Try this as an exercise.
    '''
    group_size = K.backend.minimum(group_size, tf.shape(layer)[0])         ***1***

    shape = list(K.int_shape(input))                                       ***2***
    shape[0] = tf.shape(input)[0]

    minibatch = K.backend.reshape(layer,
        (group_size, -1, shape[1], shape[2], shape[3]))                    ***3***
    minibatch -= tf.reduce_mean(minibatch, axis=0, keepdims=True)          ***4***
    minibatch = tf.reduce_mean(K.backend.square(minibatch), axis = 0)      ***5***
    minibatch = K.backend.square(minibatch + 1e8)                          ***6***
    minibatch = tf.reduce_mean(minibatch, axis=[1,2,4], keepdims=True)     ***7***
    minibatch = K.backend.tile(minibatch,
        [group_size, 1, shape[2], shape[3]])                               ***8***
return K.backend.concatenate([layer, minibatch], axis=1)                   ***9***
  • 1 提示!如果你使用的是纯 TensorFlow 而不是 Keras,请始终记住作用域。小批量组必须能被 group_size 整除(或 <=)。

  • 2 仅获取一些形状信息,以便我们可以将其用作缩写,并确保默认值。我们从 tf.shape 获取输入,因为“前图像”维度在图执行之前通常被转换为 None。

  • 3 重新塑形,以便我们在小批量级别上进行操作。在这段代码中,我们假设层为 [组 (G), 小批量 (M), 宽度 (W), 高度 (H), 通道 (C)],但请注意:不同的实现使用 Theano 特定的顺序。

  • 4 在组 [M,W,H,C] 上对均值进行中心化

  • 5 计算组 [M,W,H,C] 的方差

  • 6 计算组 [M,W,H,C] 的标准差

  • 7 对特征图和像素 [M,1,1,1] 取平均值

  • 8 将标量值转换为适合组和像素

  • 9 作为新的特征图添加

6.2.4. 均衡学习率

均衡学习率是那些深度学习暗黑艺术技巧之一,可能对任何人来说都不太清楚。尽管研究人员在 PGGAN 论文中提供了一个简短的解释,但他们避免了口头报告中的这个话题,这表明这可能是似乎有效的一个技巧。在深度学习中,这种情况经常发生。

此外,关于均衡学习率(equalized learning rate)的许多细微之处需要你对 RMSProp 或 Adam(所使用的优化器)的实现以及权重初始化有一个扎实的理解。所以,如果你觉得这不太明白,请不要担心,因为这可能对任何人来说都不太明白。

但如果你好奇,解释是这样的:我们需要确保所有权重(w)都通过一个常数c进行归一化(w’),使其在某个范围内,即w’ = w/c,这个常数c对于每一层都是不同的,取决于权重矩阵的形状。这也确保了如果任何参数需要采取更大的步骤以达到最优——因为它们倾向于变化更多——这些相关的参数可以做到这一点。

Karras 等人使用简单的标准正态初始化,然后在运行时按层缩放权重。有些人可能认为 Adam 已经做到了这一点——是的,Adam 允许不同参数有不同的学习率,但有一个问题。Adam 通过参数估计的标准差调整反向传播的梯度,这确保了该参数的尺度与更新无关。Adam 在不同方向上有不同的学习率,但并不总是考虑到动态范围——即维度或特征在给定的小批量中变化的程度。正如有些人指出的,这似乎解决了与权重初始化相似的问题。^([3)]

³

请参阅 Alexander Jung 于 2017 年撰写的“Progressive Growing of GANs.md”,mng.bz/5A4B

然而,如果这还不清楚,请不要担心;我们强烈推荐两个优秀的资源:Andrew Karpathy 于 2016 年的计算机科学讲座笔记,关于权重初始化^([4)],以及一篇 Distill 文章,详细介绍了 Adam 的工作原理^([5)]。以下列表显示了均衡学习率。

请参阅 Fei-Fei Li 等人于 2016 年撰写的“Lecture 5: Training Neural Networks, Part I”,mng.bz/6wOo

请参阅 Gabriel Goh 于 2017 年撰写的 Distill 文章“Why Momentum Really Works”,distill.pub/2017/momentum/

列表 6.3. 均衡学习率
def equalize_learning_rate(shape, gain, fan_in=None):
    '''
    This adjusts the weights of every layer by the constant from
    He's initializer so that we adjust for the variance in the dynamic
    range in different features
    shape   :  shape of tensor (layer): these are the dimensions
        of each layer.
    For example, [4,4,48,3]. In this case, [kernel_size, kernel_size,
        number_of_filters, feature_maps]. But this will depend
        slightly on your implementation.
    gain    :  typically sqrt(2)
    fan_in  :  adjustment for the number of incoming connections
        as per Xavier's / He's initialization
    '''
    if fan_in is None: fan_in = np.prod(shape[:-1])             ***1***
    std = gain / K.sqrt(fan_in)                                 ***2***
    wscale = K.constant(std, name='wscale', dtype=np.float32)   ***3***
    adjusted_weights = K.get_value('layer', shape=shape,        ***4***
        initializer=tf.initializers.random_normal()) * wscale
    return adjusted_weights
  • 1 默认值是所有形状维度乘积减去特征图维度;这给出了每个神经元输入连接的数量。

  • **2 这使用 He 的初始化常数。^([6)]

    参见 Kaiming He 等人撰写的“Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification”,arxiv.org/pdf/1502.01852.pdf.

  • 3 将调整创建为一个常数*

  • 4 获取权重值并使用广播来应用调整

如果您仍然感到困惑,请放心,这些初始化技巧和这些复杂的学习率调整在学术界或工业界很少成为区分点。此外,尽管限制权重值在-1 和 1 之间似乎在大多数重跑中效果较好,但这并不意味着这个技巧可以推广到其他设置。那么,让我们转向更经证明的技术。

6.2.5. 生成器中的像素级特征归一化

让我们从为什么要对特征进行归一化的动机开始——训练的稳定性。经验上,NVIDIA 的作者发现,训练发散的早期迹象之一是特征幅度的爆炸。BigGAN 作者在第十二章中也做出了类似的观察。因此,Karras 等人引入了一种对抗这种问题的技术。更广泛地说,这就是 GAN 训练通常是如何进行的:我们观察到训练中存在特定问题,然后我们引入机制来防止该问题发生。

注意,大多数网络都使用某种形式的归一化。通常,它们使用批量归一化或这种技术的虚拟版本。表 6.1 展示了本书中迄今为止所展示的 GAN 中使用的归一化技术的概述。您在第四章(DCGAN)和第五章中看到了这些——在那里我们提到了其他 GAN 和梯度惩罚(GPs)。不幸的是,为了使批量归一化和其虚拟等效版本能够工作,我们必须有大的迷你批量,以便单个样本的平均值相互抵消。

表 6.1. GAN 中使用归一化技术
方法 作者 G 归一化 D 归一化
DCGAN (Radford et al., 2015, arxiv.org/abs/1511.06434) 批量 批量
改进的 GAN (Salimans et al., 2016, arxiv.org/pdf/1606.03498.pdf) 虚拟批量 虚拟批量
WGAN (Arjovsky et al., 2017, arxiv.org/pdf/1701.07875.pdf) 批量
WGAN-GP (Gulrajani et al., 2017, arxiv.org/abs/1704.00028) 批量 层归一化

基于所有这些主要实现都使用归一化的事实,它显然很重要,但为什么不直接使用标准的批量归一化呢?遗憾的是,批量归一化在我们的分辨率下太占用内存。我们必须想出一种方法,允许我们使用少量示例——这些示例可以适应我们的 GPU 内存,并且有两个网络图——但仍然效果良好。现在我们理解了像素级特征归一化的需求以及为什么我们使用它。

如果我们深入算法,像素归一化会在输入被馈送到下一层之前,在每个层的激活幅度上操作。

图 6.4 说明了像素级特征归一化的过程。步骤 3 的确切描述显示在方程 6.1 中。

方程 6.1.

图 6.4. 我们将图像中的所有点(步骤 1)映射到一组向量(步骤 2),然后对它们进行归一化,使它们都在相同的范围内(通常在多维空间中的 0 到 1 之间),这是步骤 3。

像素级特征归一化

对于每个特征图执行

  1. 取该特征图(fm)在位置(x, y)的像素值。

  2. 为每个(x, y)构建一个向量,其中

    1. v[0,0] = [(0,0) value for fm[1], (0,0) value for fm[2], ..., (0,0) value for fm[n]*]

    2. v[0,1] = [(0,1) value for fm[1], (0,1) value for fm[2], ..., (0,1) value for fm[n]*] ...

    3. v[n,n] = [(n,n) value for fm[1], (n,n) value for fm[2], ..., (n,n) value for fm[n]*]

  3. 将步骤 2 中定义的每个向量v[i,i]标准化为具有单位范数;称之为n[i,i]。

  4. 将其传递到原始张量形状的下一层。

结束 for

该公式将图 6.4 步骤 2 中构建的每个向量进行标准化(除以平方根下的表达式)。这个表达式只是该特定(x, y)像素每个平方值的平均值。可能让你感到惊讶的是,添加了一个小的噪声项(ϵ)。这仅仅是一种确保我们不会除以零的方法。整个过程在 2012 年的论文“ImageNet Classification with Deep Convolutional Neural Networks”中得到了更详细的解释,作者为 Alex Krizhevsky 等人。(mng.bz/om4d

最后要注意的是,这个术语仅应用于生成器,因为激活幅度的爆炸只会导致如果两个网络都参与的话,才会是一场军备竞赛。下面的列表显示了代码。

列表 6.4. 像素级特征归一化
def pixelwise_feat_norm(inputs, **kwargs):
    '''
    Uses pixelwise feature normalization as proposed by
    Krizhevsky et at. 2012\. Returns the input normalized
    :inputs     :    Keras / TF Layers
    '''
    normalization_constant = K.backend.sqrt(K.backend.mean(
        inputs**2, axis=-1, keepdims=True) + 1.0e-8)
    return inputs / normalization_constant

6.3. 关键创新总结

我们已经探讨了四种如何改进 GAN 训练的巧妙想法;然而,如果没有基于它们对训练的影响,可能很难隔离这些影响。幸运的是,论文的作者提供了一个有用的表格,帮助我们理解这一点;参见图 6.5。

图 6.5. 各种技术对得分改进的贡献。我们可以看到,引入均衡学习率产生了重大影响,像素归一化也增加了这一点,尽管作者没有告诉我们,如果我们只有像素归一化而没有引入均衡学习率,这种技术将有多有效。我们只包括这个表格,以说明我们可以期望从这些变化中获得的粗略改进程度——这本身就是一个有趣的教训——但更详细的讨论将在后面进行。

图片

PGGAN 论文的作者正在使用sliced Wasserstein 距离 (SWD),越小越好。回想一下第五章,较小的 Wasserstein 距离(也称为地球迁移者距离)意味着更好的结果,这是通过必须移动多少概率质量来使两个分布相似来衡量的。SWD 意味着真实数据和生成样本的块都最小化这个距离。这项技术的细微差别在论文中有解释,但正如作者在 ICLR 的演示中所说,现在存在更好的度量标准——例如 Fréchet inception 距离 (FID)。我们在第五章中更深入地介绍了 FID。

从这张表中可以得出的一个关键结论是,小批量并不奏效,因为在兆像素分辨率下,我们没有足够的虚拟 RAM 来加载许多图像到 GPU 内存中。我们必须使用更小的小批量——这可能会在总体上表现得更差——并且我们必须进一步减少小批量的大小,这使得我们的训练变得困难。

6.4. TensorFlow Hub 和动手实践

谷歌最近宣布,作为 TensorFlow Extended 和将软件工程的最佳实践应用于机器学习领域的一般性举措的一部分,谷歌创建了一个中央模型和代码仓库,称为TensorFlow Hub,或TFHub。使用 TFHub 几乎可以说是易如反掌,尤其是与谷歌放置在那里的模型一起使用。

在导入 hub 模块并调用正确的 URL 后,TensorFlow 会自动下载并导入模型,然后你可以开始使用。这些模型在用于下载模型的相同 URL 上有很好的文档说明;只需将它们放入你的网络浏览器中。实际上,要获取预训练的 Progressive GAN,你只需要输入一条导入语句和一行代码。就是这样!

下面的列表显示了生成面孔的完整代码示例——基于你在latent_vector中指定的随机种子。图 6.6 显示了输出。

此示例是使用 TFHub 生成的,基于mng.bz/nvEa提供的 Colab 示例。

列表 6.5. 使用 TFHub 入门
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_hub as hub

with tf.Graph().as_default():
    module = hub.Module("https://tfhub.dev/google/progan-128/1")  ***1***
    latent_dim = 512                                              ***2***

    latent_vector = tf.random_normal([1, latent_dim], seed=1337)  ***3***

    interpolated_images = module(latent_vector)                   ***4***

    with tf.Session() as session:                                 ***5***
    session.run(tf.global_variables_initializer())
    image_out = session.run(interpolated_images)

plt.imshow(image_out.reshape(128,128,3))
plt.show()
  • 1 从 TFHub 导入 Progressive GAN

  • 2 在运行时采样的潜在维度

  • 3 改变种子以获取不同的面孔

  • 4 使用模块从潜在空间生成图像。实现细节见在线资料。

  • 5 运行 TensorFlow 会话并返回形状为(1,128,128,3)的图像

图 6.6. 代码清单 6.5 的输出。尝试更改latent_vector定义中的种子以获取不同的输出。提醒一下:尽管这个随机种子参数应该一致地定义我们期望得到的输出,但我们发现,在重新运行时,有时会得到不同的结果,这取决于 TensorFlow 的版本。此图像使用 1.9.0-rc1 版本获得。

希望这足以让您开始使用渐进式 GAN!请随意尝试修改代码并扩展它。需要注意的是,TFHub 版本的渐进式 GAN 并没有使用完整的 1024 × 1024,而是仅使用 128 × 128。这可能是由于运行完整版本曾经计算成本高昂,并且在这个计算机视觉问题领域中,模型大小可以迅速变得非常大。

6.5. 实际应用

可以理解的是,人们对渐进式 GAN 的实际应用和泛化能力感到好奇。我们将展示的一个很好的例子来自位于英国伦敦的 Kheiron Medical Technologies 的同事们。最近,他们发布了一篇论文,这篇论文是对 PGGAN 的泛化能力和实际应用的极好证明.^([8])

请参阅 Dimitrios Korkinof 等人撰写的“使用渐进式生成对抗网络进行高分辨率乳腺钼靶合成”,2018 年,arxiv.org/pdf/1807.03401.pdf

使用大量医学乳腺钼靶数据集,^([9]) 这些研究人员成功生成了 1280 × 1024 的全场数字化乳腺钼靶(FFDM)的逼真合成图像,如图 6.7 所示。这是在两个层面上的一项了不起的成就:

用于乳腺癌筛查的 X 射线扫描。

  • 它展示了这项技术的泛化能力。想想钼靶图像与人类面部图像的不同——特别是在结构上。组织结构的合理性标准非常高,然而他们的网络设法产生了迄今为止最高分辨率的样本,这些样本经常能欺骗医疗专业人员。

  • 它展示了这些技术可以应用于许多领域和用途。例如,我们可以在下一章中向您展示如何以半监督的方式使用这个新数据集。或者,合成数据集可以开源用于医学研究,从一般数据保护条例(GDPR)或其他法律后果的角度来看,可能担忧较少,因为这些数据不属于任何个人。

图 6.7. FFDM 的渐进式增长。这是一张很棒的图,因为它不仅展示了这些钼靶图像上分辨率逐渐提高的情况(e),还展示了部分训练统计信息(a)至(d),以表明训练这些 GAN 对于每个人来说都是混乱的,而不仅仅是您。

图片

图 6.8 展示了这些乳腺 X 光片可以有多逼真。这些是随机抽取的(因此没有挑选),然后与数据集中最接近的一张图片进行了比较。

图 6.8。在比较真实和生成的数据集时,数据看起来相当逼真,通常接近训练集中的示例。在其后续工作中,MammoGAN,Kheiron 已经表明这些图像可以欺骗经过培训和认证的放射科医生。^([11]) 这通常是一个好迹象,尤其是在这种高分辨率下。当然,从原则上讲,我们希望有一种统计方法来衡量生成的质量。但正如我们在第五章中了解到的,这已经足够困难,对于任何任意的 GAN 来说更是如此。

¹¹

查看“MammoGAN: High-Resolution Synthesis of Realistic Mammograms”,作者 Dimitrios Korkinof 等,2019 年,openreview.net/pdf?id=SJeichaN5E.

图片

GANs 可以用于许多应用,而不仅仅是与乳腺癌作斗争或生成人脸,还包括截至 2018 年 7 月底发布的 62 个其他医学 GAN 应用。^([10]) 我们鼓励您查看它们——但当然,并非所有这些都使用 PGGANs。通常,GANs 在许多研究领域都带来了巨大的飞跃,但它们通常被非直观地应用。我们希望使这些技术更容易获得,以便更多的研究人员可以使用。制造 GANs,而不是战争!

¹⁰

查看“GANs for Medical Image Analysis”,作者 Salome Kazeminia 等,2018 年,arxiv.org/pdf/1809.06222.pdf.

本章中我们提出的所有技术代表了解决 GAN 问题的一般类别——模型越来越复杂。我们预计这种范式将在 GAN 中得到应用。对于 TensorFlow Hub 也是如此:它对 TensorFlow 的作用就像 PyPI/Conda 对 Python 的作用一样。大多数 Python 程序员每周都会使用它们!

我们希望这种新的渐进式 GAN 技术让您对 GAN 能做什么以及为什么人们对这篇论文如此兴奋有了新的认识。并且希望不仅仅是因为 PGGAN 可以生成的猫膜向量。^([12]) 下一章将为您提供工具,让您可以开始自己为研究做出贡献。到时候见!

¹²

查看 Gene Kogan 的 Twitter 图片,2018 年,twitter.com/genekogan/status/1019943905318572033.

摘要

  • 我们得益于最先进的 PGGAN 技术,可以生成 1 兆像素的合成图像。

  • 这种技术有四个关键的训练创新:

    • 高分辨率层中的渐进式增长和平滑

    • 小批量标准差以强制生成样本中的变化

    • 平衡的学习率确保我们可以在每个方向上采取适当大小的学习步骤

    • 像素级向量归一化,确保生成器和判别器在军备竞赛中不会失控

  • 你跟随了一个使用新发布的 TensorFlow Hub 的手动教程,并有机会使用其渐进式 GAN 的下采样版本来生成图像!

  • 你了解了 GANs 是如何帮助对抗癌症的。

第七章。半监督 GAN

本章涵盖

  • 基于原始 GAN 模型的热门创新领域

  • 半监督学习和其巨大的实际重要性

  • 半监督 GANs(SGANs)

  • SGAN 模型的实现

恭喜你——你已经完成了这本书的一半以上。到现在为止,你不仅已经学会了什么是生成对抗网络(GANs)以及它们是如何工作的,还有机会实现了两种最经典的实现:最初启动一切的原始 GAN 和为大量高级 GAN 变体奠定基础的 DCGAN,包括上一章中介绍的渐进式 GAN。

然而,就像许多领域一样,当你认为你开始真正掌握它时,你会发现自己所了解的领域比最初想象的要大得多、复杂得多。可能看似彻底的理解实际上只是冰山一角。

GANs 也不例外。自从它们被发明以来,它们一直是一个活跃的研究领域,每年都会添加无数的变化。一个非官方的列表——恰当地命名为“GAN 动物园”(github.com/hindupuravinash/the-gan-zoo)——旨在追踪所有命名的 GAN 变体(由撰写它们的作者命名的具有不同名称的 GAN 实现)在撰写本文时已经增长到超过 300 种。然而,根据原始 GAN 论文至今已被引用超过 9,000 次(截至 2019 年 7 月)并且在过去几年中在深度学习领域排名最被引用的研究论文之一的事实,研究社区发明的 GAN 变体数量可能更高。^([[1]) 见图 7.1。

¹

根据 Microsoft Academic(MA)搜索引擎的追踪器:mng.bz/qXXJ。另见“机器学习和深度学习的前 20 篇研究论文”,作者 Thuy T. Pham,2017,mng.bz/E1eq

图 7.1。此图近似了研究社区从 2014 年 GAN 的发明开始,直到 2018 年初几个月每月累积的独特 GAN 实现的数量。正如图表所清楚显示的,生成对抗学习领域自其诞生以来一直在呈指数增长,这种兴趣和受欢迎程度的增长似乎没有尽头。

图片

(来源:“GAN 动物园”,作者 Avinash Hindupur,2017,github.com/hindupuravinash/the-gan-zoo。)

然而,这并不是绝望的理由。尽管不可能在这本书或任何书中涵盖所有这些 GAN 变体,但我们可以介绍一些关键创新,这将给你一个很好的了解,以及这些变化对生成对抗学习领域的独特贡献。

值得注意的是,并非所有这些命名的变体都与原始 GAN 有显著差异。事实上,其中许多在高级别上与原始模型非常相似,例如第四章中的 DCGAN。即使是许多复杂的创新,如 Wasserstein GAN(在第五章中讨论),也主要关注提高原始 GAN 模型或类似模型的性能和稳定性。

在本章以及接下来的两章中,我们将重点关注那些不仅在模型实现的架构和底层数学上与原始 GAN 不同,而且在动机和目标上也有所不同的 GAN 变体。特别是,我们将涵盖以下三种 GAN 模型:

  • 半监督 GAN(本章)

  • 条件 GAN (第八章)

  • CycleGAN (第九章)

对于这些 GAN 变体中的每一个,你将了解它们的目标和动机,它们的模型架构,以及它们的网络如何训练和工作。这些主题将通过概念和具体示例进行讲解。我们还将提供教程,包含每个模型的完整工作实现,以便你可以亲身体验。

因此,无需多言,让我们深入探讨吧!

7.1. 半监督 GAN 的介绍

半监督学习 是 GAN 在实际应用中最有前景的领域之一。与需要为数据集中的每个示例提供标签的监督学习不同,以及不使用标签的无监督学习,半监督学习只为训练数据集的一个小子集提供类别标签。通过内化数据中的隐藏结构,半监督学习力求从标记数据点的较小子集中泛化,从而有效地分类新的、以前未见过的示例。重要的是,为了使半监督学习有效,标记和无标记数据必须来自相同的潜在分布。

缺乏标记数据集是机器学习研究和实际应用中的主要瓶颈之一。尽管无标记数据很丰富(互联网是一个几乎无限的、无标记图像、视频和文本的来源),但为它们分配类别标签通常成本高昂、不切实际且耗时。手工标注 ImageNet 原始的 320 万张图像花了两年半时间——这是一个标记图像数据库,帮助推动了过去十年中图像处理和计算机视觉的许多进步.^([2])

²

请参阅 Dave Gershgorn 于 2017 年发表的《数据如何改变 AI 研究——以及可能改变世界》,mng.bz/DNVy

深度学习先驱、斯坦福大学教授、百度前首席科学家安德鲁·吴(Andrew Ng)将大量标注数据对于训练的重要性视为监督学习的阿基里斯之踵,而监督学习被广泛应用于当今工业界的大多数 AI 应用。^([[3)] 其中,受大型标注数据集缺乏影响最严重的行业之一是医药行业,获取数据(例如,临床试验的结果)通常需要巨大的努力和支出,更不用说伦理和隐私等更为重要的问题。^([[4)] 因此,提高算法从越来越少的标注示例中学习的能力具有极大的实际意义。

³

请参阅安德鲁·吴(Andrew Ng)于 2016 年发表的《现在人工智能能做什么(以及不能做什么)》,mng.bz/lopj

请参阅 Michael Chui 等人于 2018 年发表的《AI 能为您的业务做什么(以及现在不能做什么)》,mng.bz/BYDv

有趣的是,半监督学习也可能是与人类学习方式最接近的机器学习类比。当学童学习阅读和写作时,老师不必带他们去旅行看成千上万的字母和数字示例,要求他们识别这些符号,并根据需要纠正他们——这与监督学习算法的操作方式相似。相反,只需要一组示例,孩子们就能学习字母和数字,然后能够识别它们,无论字体、大小、角度、光照条件以及许多其他因素。半监督学习的目标是以类似高效的方式教会机器。

作为训练的额外信息来源,生成模型在提高半监督模型准确性方面证明是有用的。不出所料,GANs 证明是最有希望的。2016 年,Tim Salimans、Ian Goodfellow 及其在 OpenAI 的同事们仅使用 2,000 个标注示例在 Street View House Numbers (SVHN)基准数据集上实现了近 94%的准确率。^([[5)] 作为比较,当时使用 SVHN 训练集中所有 73,257 个图像的标签的最佳全监督算法的准确率约为 98.40%。^([[6)] 换句话说,半监督 GAN 在整体准确率上与全监督基准非常接近,而训练时使用的标签不到 3%。

请参阅 Ian Goodfellow 等人于 2016 年发表的《训练 GANs 的改进技术》,arxiv.org/abs/1606.03498

请参阅高黄等人于 2016 年发表的《密集连接卷积网络》,arxiv.org/abs/1608.06993

让我们来看看 Salimans 和他的同事们是如何从如此之少中取得如此多的成就。

7.1.1. 什么是半监督生成对抗网络?

半监督生成对抗网络(SGAN) 是一种生成对抗网络,其判别器是一个多类分类器。它不是仅仅区分两类(真实伪造),而是学习区分 N + 1 类,其中 N 是训练数据集中类的数量,额外增加一类用于由生成器产生的伪造示例。

例如,手写数字的 MNIST 数据集有 10 个标签(每个数字一个标签,0 到 9),因此在这个数据集上训练的 SGAN 判别器将预测 10 + 1 = 11 个类别。在我们的实现中,SGAN 判别器的输出将表示为一个包含 10 个类概率的向量(总和为 1.0)以及另一个表示图像是真实还是伪造的概率。

将判别器从二分类器转换为多类分类器可能看起来是一个微不足道的改变,但其影响可能比乍看之下更为深远。让我们从一个图表开始。图 7.2 展示了 SGAN 的架构。

图 7.2. 在这个半监督生成对抗网络中,生成器接收一个随机噪声向量 z 并产生一个伪造示例 x**。判别器接收三种类型的数据输入:来自生成器的伪造数据、真实未标记示例 x 和真实标记示例 (x*, y),其中 y 是与给定示例对应的标签。然后判别器输出一个分类;其目标是区分伪造示例和真实示例,并对真实示例识别正确的类别。请注意,有标签的示例部分远小于无标签数据部分。在实践中,这种对比甚至更为鲜明,标记数据仅占训练数据的一小部分(通常仅为 1-2%)。

如图 7.2 所示,区分多个类别的任务不仅影响判别器本身,而且与传统的 GAN 相比,增加了 SGAN 架构、其训练过程和其训练目标的复杂性。

7.1.2. 架构

SGAN 生成器的目的是与原始 GAN 相同:它接收一个随机数字向量,并产生伪造示例,其目标是与训练数据集不可区分——这里没有变化。

然而,SGAN 判别器与原始 GAN 实现有相当大的差异。它接收三种类型的输入:生成器产生的伪造示例 (x**)、来自训练数据集的无标签真实示例 (x) 和来自训练数据集的有标签真实示例 (x, y*),其中 y 表示给定示例 x 的标签。SGAN 判别器的目标不是二元分类,而是如果示例是真实的,则正确地将输入示例分类到其对应的类别中,或者拒绝该示例为伪造(这可以被视为一个特殊的附加类别)。

表 7.1 总结了关于两个 SGAN 子网络的关键要点。

表 7.1. SGAN 生成器和判别器网络
生成器 判别器

| 输入 | 一组随机数向量 (z) | 判别器接收三种类型的输入:

  • 来自训练数据集的无标签真实示例 (x)

  • 来自训练数据集的有标签真实示例 (x, y)

  • 由生成器产生的伪造示例 (x*)

|

输出 努力变得尽可能令人信服的伪造示例 (x*) 概率,表示输入示例属于 N 个真实类别之一或伪造类别的可能性
目标 生成伪造示例,通过欺骗判别器将其分类为真实示例,使其在训练数据集成员中难以区分 学习为真实示例分配正确的类别标签,同时拒绝所有来自生成器的示例作为伪造

7.1.3. 训练过程

记住,在常规 GAN 中,我们通过计算 D(x) 和 D(x**) 的损失,并通过反向传播总损失来更新判别器的可训练参数以最小化损失来训练判别器。生成器通过反向传播判别器对 D(x**) 的损失来训练,目的是最大化它,从而使它合成的伪造示例被错误地分类为真实。

训练 SGAN 时,除了计算 D(x) 和 D(x**) 的损失之外,我们还需要计算监督训练示例的损失:D((x, y)). 这些损失对应于 SGAN 判别器必须应对的二元学习目标:在区分真实示例和伪造示例的同时,还要学会将真实示例分类到正确的类别中。使用原文中的术语,这些二元目标对应于两种类型的损失:监督损失* 和 无监督损失.^([7])

参见 Tim Salimans 等人于 2016 年发表的“Improved Techniques for Training GANs”,arxiv.org/abs/1606.03498.

7.1.4. 训练目标

你迄今为止所看到的所有 GAN 变体都是生成模型。它们的目标是产生看起来逼真的数据样本;因此,生成器网络一直是首要关注点。判别器网络的主要目的是帮助生成器提高其产生的图像质量。在训练结束时,我们通常忽略判别器,仅使用完全训练好的生成器来创建看起来逼真的合成数据。

相比之下,在 SGAN 中,我们主要关注判别器。训练过程的目标是将这个网络变成一个半监督分类器,其准确率尽可能接近全监督分类器(在训练数据集中的每个示例都有标签),同时只使用一小部分标签。生成器的目标是作为额外信息的来源(它产生的伪造数据)来帮助生成器学习数据中的相关模式,从而提高其分类准确率。在训练结束时,生成器被丢弃,我们使用训练好的判别器作为分类器。

现在你已经了解了 SGAN 的创建动机,我们也解释了模型的工作原理,现在是时候通过实现一个模型来观察模型的实际应用了。

7.2. 教程:实现半监督生成对抗网络

在这个教程中,我们实现了一个 SGAN 模型,该模型通过仅使用 100 个训练示例来学习在 MNIST 数据集中对手写数字进行分类。在教程结束时,我们将模型的分类准确率与一个等效的全监督模型进行比较,以亲自看到半监督学习带来的改进。

7.2.1. 架构图

图 7.3 展示了本教程中实现的 SGAN 模型的高级示意图。它比我们在本章开头介绍的一般概念图要复杂一些。毕竟,魔鬼隐藏在(实现)细节中。

图 7.3. 这个 SGAN 图是本章教程中实现的 SGAN 的高级说明。生成器将随机噪声转换为伪造示例。判别器接收带有标签的实图像(x, y)、没有标签的实图像(x)以及生成器产生的伪造图像(x**)。为了区分真实示例和伪造示例,判别器使用sigmoid函数。为了区分真实类别,判别器使用softmax*函数。

图片

为了解决多类分类问题,即区分真实标签,判别器使用softmax函数,它给出了指定数量类别的概率分布——在我们的案例中是 10 个类别。分配给某个标签的概率越高,判别器就越确信该示例属于该类别。为了计算分类误差,我们使用交叉熵损失,它衡量输出概率与目标、单热编码标签之间的差异。

为了输出真实与虚假的概率,判别器使用sigmoid激活函数,并通过反向传播二进制交叉熵损失来训练其参数——这与我们在第三章和第四章中实现的 GANs 相同。

7.2.2. 实现

如您可能注意到的,我们的大部分 SGAN 实现是从第四章中的 DCGAN 模型改编而来的。这并非出于懒惰(好吧,可能有一点),而是为了让您更好地看到 SGAN 所需的独特修改,而不受网络无关部分的实现细节的干扰。

一个包含完整实现以及添加的训练进度可视化的 Jupyter 笔记本可在我们的 GitHub 仓库(github.com/GANs-in-Action/gans-in-action)中找到,位于第七章文件夹下。代码已在 Python 3.6.0、Keras 2.1.6 和 TensorFlow 1.8.0 上进行了测试。为了加快训练时间,我们建议在 GPU 上运行模型。

7.2.3. 设置

如同往常,我们首先导入运行模型所需的全部模块和库,如下所示。

列表 7.1. 导入语句
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from keras import backend as K

from keras.datasets import mnist
from keras.layers import (Activation, BatchNormalization, Concatenate, Dense,
                          Dropout, Flatten, Input, Lambda, Reshape)
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Conv2D, Conv2DTranspose
from keras.models import Model, Sequential
from keras.optimizers import Adam
from keras.utils import to_categorical

我们还指定了输入图像大小、噪声向量z的大小以及半监督分类中的真实类别数(每个数字对应我们的判别器将学习识别的一个类别),如下所示。

列表 7.2. 模型输入维度
img_rows = 28
img_cols = 28
channels = 1

img_shape = (img_rows, img_cols, channels)    ***1***

z_dim = 100                                   ***2***

num_classes = 10                              ***3***
  • 1 输入图像维度

  • 2 噪声向量的大小,用作生成器的输入

  • 3 数据集中的类别数

7.2.4. 数据集

尽管 MNIST 训练数据集有 50,000 个标记的训练图像,但我们只会使用其中的一小部分(由num_labeled参数指定)进行训练,并假装所有剩余的图像都是未标记的。我们通过在生成标记数据批次时只从前num_labeled个图像中进行采样,在生成未标记示例批次时从剩余的(50,000 - num_labeled)个图像中进行采样来实现这一点。

Dataset 对象(如列表 7.3 所示)还提供了一个函数,用于返回所有 num_labeled 训练示例及其标签,以及一个函数,用于返回 MNIST 数据集中的所有 10,000 个带标签测试图像。训练后,我们将使用测试集来评估模型的分类如何泛化到之前未见过的示例。

列表 7.3. 训练和测试数据集
class Dataset:
    def __init__(self, num_labeled):

        self.num_labeled = num_labeled                                   ***1***

        (self.x_train, self.y_train), (self.x_test,                      ***2***
                                       self.y_test) = mnist.load_data()

        def preprocess_imgs(x):
            x = (x.astype(np.float32) - 127.5) / 127.5                   ***3***
            x = np.expand_dims(x, axis=3)                                ***4***
            return x

        def preprocess_labels(y):
            return y.reshape(-1, 1)

        self.x_train = preprocess_imgs(self.x_train)                     ***5***
        self.y_train = preprocess_labels(self.y_train)

        self.x_test = preprocess_imgs(self.x_test)                       ***6***
        self.y_test = preprocess_labels(self.y_test)

    def batch_labeled(self, batch_size):
        idx = np.random.randint(0, self.num_labeled, batch_size)         ***7***
        imgs = self.x_train[idx]
        labels = self.y_train[idx]
        return imgs, labels

    def batch_unlabeled(self, batch_size):
        idx = np.random.randint(self.num_labeled, self.x_train.shape[0], ***8***
                                batch_size)
        imgs = self.x_train[idx]
        return imgs

    def training_set(self):
        x_train = self.x_train[range(self.num_labeled)]
        y_train = self.y_train[range(self.num_labeled)]
        return x_train, y_train

    def test_set(self):
        return self.x_test, self.y_test
  • 1 用于训练的带标签示例数量

  • 2 加载 MNIST 数据集

  • 3 将 [0, 255] 灰度像素值缩放到 [–1, 1]

  • 4 扩展图像维度为宽度 × 高度 × 通道

  • 5 训练数据

  • 6 测试数据

  • 7 获取一个随机批次的带标签图像及其标签

  • 8 获取一个随机批次的未标记图像

在本教程中,我们将假装我们只有 100 个带标签的 MNIST 图像用于训练:

num_labeled = 100             ***1***

dataset = Dataset(num_labeled)
  • 1 用于训练的带标签示例数量(其余将用作未标记)

7.2.5. 生成器

生成器网络与我们在第四章中为 DCGAN 实现的网络相同。使用转置卷积层,生成器将输入的随机噪声向量转换为 28 × 28 × 1 图像;请参阅以下列表。

列表 7.4. SGAN 生成器
def build_generator(z_dim):

    model = Sequential()
    model.add(Dense(256 * 7 * 7, input_dim=z_dim))                           ***1***
    model.add(Reshape((7, 7, 256)))

    model.add(Conv2DTranspose(128, kernel_size=3, strides=2, padding='same'))***2***

    model.add(BatchNormalization())                                          ***3***

    model.add(LeakyReLU(alpha=0.01))                                         ***4***

    model.add(Conv2DTranspose(64, kernel_size=3, strides=1, padding='same')) ***5***

    model.add(BatchNormalization())                                          ***3***

    model.add(LeakyReLU(alpha=0.01))                                         ***4***

    model.add(Conv2DTranspose(1, kernel_size=3, strides=2, padding='same'))  ***6***

    model.add(Activation('tanh'))                                            ***7***

    return model
  • 1 通过全连接层将输入重塑为 7 × 7 × 256 张量

  • 2 转置卷积层,从 7 × 7 × 256 到 14 × 14 × 128 张量

  • 3 批归一化

  • 4 Leaky ReLU 激活

  • 5 转置卷积层,从 14 × 14 × 128 到 14 × 14 × 64 张量

  • 6 转置卷积层,从 14 × 14 × 64 到 28 × 28 × 1 张量

  • 7 输出层带有 tanh 激活

7.2.6. 判别器

判别器是 SGAN 模型中最复杂的一部分。回想一下,SGAN 判别器具有双重目标:

  • 区分真实示例和伪造示例。为此,SGAN 判别器使用 sigmoid 函数,输出单个输出概率,用于二元分类。

  • 对于真实示例,准确分类其标签。为此,SGAN 判别器使用 softmax 函数,输出一个概率向量,每个目标类别一个。

核心判别器网络

我们首先定义核心判别器网络。正如你可能注意到的,列表 7.5 中的模型与我们在第四章中实现的基于 ConvNet 的判别器相似;实际上,它一直相同,直到 3 × 3 × 128 卷积层、其批归一化和 Leaky ReLU 激活。

在那一层之后,我们添加了一个dropout,这是一种正则化技术,通过在训练过程中随机丢弃神经网络中的神经元及其连接来帮助防止过拟合。这迫使剩余的神经元减少它们的相互依赖,并发展出对底层数据的更一般化表示。要随机丢弃的神经元比例由速率参数指定,在我们的实现中设置为 0.5:model.add(Dropout(0.5))。我们添加 dropout 是因为 SGAN 分类任务的复杂性增加,以及为了提高模型从仅 100 个标记示例中泛化的能力。

请参阅 Geoffrey E. Hinton 等人于 2012 年发表的“通过防止特征检测器的共适应来改进神经网络”,arxiv.org/abs/1207.0580。另请参阅 Nitish Srivastava 等人于 2014 年发表的“Dropout:防止神经网络过拟合的简单方法”,Journal of Machine Learning Research 15,1929–1958。

列表 7.5. SGAN 判别器
def build_discriminator_net(img_shape):

    model = Sequential()

    model.add(                                 ***1***
        Conv2D(32,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(LeakyReLU(alpha=0.01))           ***2***

    model.add(                                 ***3***
        Conv2D(64,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(BatchNormalization())            ***4***

    model.add(LeakyReLU(alpha=0.01))           ***5***

    model.add(                                 ***6***
        Conv2D(128,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(BatchNormalization())            ***4***

    model.add(LeakyReLU(alpha=0.01))           ***5***

    model.add(Dropout(0.5))                    ***7***

    model.add(Flatten())                       ***8***

    model.add(Dense(num_classes))              ***9***

    return model
  • 1 卷积层,从 28 × 28 × 1 张量转换为 14 × 14 × 32 张量

  • 2 Leaky ReLU 激活

  • 3 卷积层,从 14 × 14 × 32 张量转换为 7 × 7 × 64 张量

  • 4 批标准化

  • 5 Leaky ReLU 激活

  • 6 卷积层,从 7 × 7 × 64 张量转换为 3 × 3 × 128 张量

  • 7 Dropout

  • 8 展平张量

  • 9 具有 num_classes 个神经元的全连接层

注意,dropout 层是在批标准化之后添加的,而不是相反;这已经证明由于两种技术的相互作用而具有更好的性能。^([9])

请参阅 Xiang Li 等人于 2018 年发表的“通过方差偏移理解 Dropout 和批标准化的不和谐”,arxiv.org/abs/1801.05134

此外,请注意,前面的网络以一个具有 10 个神经元的全连接层结束。接下来,我们需要定义从这些神经元计算出的两个判别器输出:一个用于监督的多类分类(使用softmax),另一个用于非监督的二分类(使用sigmoid)。

监督判别器

在下面的列表中,我们采用之前实现的核心判别器网络,并使用它来构建判别器模型的监督部分。

列表 7.6. SGAN 判别器:监督
def build_discriminator_supervised(discriminator_net):

    model = Sequential()

    model.add(discriminator_net)

    model.add(Activation('softmax'))      ***1***

    return model
  • 1 Softmax 激活,输出对真实类别的预测概率分布
非监督判别器

以下列表在核心判别器网络之上实现了判别器模型的非监督部分。请注意predict(x)函数,其中我们将核心判别器网络中 10 个神经元的输出转换为二进制真实与伪造预测。

列表 7.7. SGAN 判别器:非监督
def build_discriminator_unsupervised(discriminator_net):

    model = Sequential()

    model.add(discriminator_net)

    def predict(x):
        prediction = 1.0 - (1.0 /                                          ***1***
                            (K.sum(K.exp(x), axis=-1, keepdims=True) + 1.0))

        return prediction

    model.add(Lambda(predict))                                             ***2***

    return model
  • 1 将真实类别的分布转换为二进制真实与伪造概率

  • 2 之前定义的真实与假输出神经元

7.2.7. 构建模型

接下来,我们构建和编译判别器和生成器模型。注意使用categorical_crossentropybinary_crossentropy损失函数分别用于监督损失和无监督损失。

列表 7.8. 构建模型
def build_gan(generator, discriminator):

    model = Sequential()

    model.add(generator)                                                    ***1***
    model.add(discriminator)

    return model

discriminator_net = build_discriminator_net(img_shape)                      ***2***

discriminator_supervised = build_discriminator_supervised(discriminator_net)***3***
discriminator_supervised.compile(loss='categorical_crossentropy',           ***3***
                                 metrics=['accuracy'],                      ***3***
                                 optimizer=Adam())                          ***3***

discriminator_unsupervised = build_discriminator_unsupervised(              ***4***
                                 discriminator_net)                         ***4***
discriminator_unsupervised.compile(loss='binary_crossentropy',              ***4***
                                   optimizer=Adam())                        ***4***
generator = build_generator(z_dim)                                          ***5***
discriminator_unsupervised.trainable = False                                ***6***
gan = build_gan(generator, discriminator_unsupervised)                      ***7***
gan.compile(loss='binary_crossentropy', optimizer=Adam())                   ***7***
  • 1 结合生成器和判别器模型

  • 2 核心判别器网络:这些层在监督和无监督训练期间是共享的。

  • 3 为监督训练构建和编译判别器

  • 4 为无监督训练构建和编译判别器

  • 5 构建生成器

  • 6 在生成器训练期间保持判别器参数不变

  • 7 使用固定的判别器构建和编译 GAN 模型以训练生成器。注意:使用具有无监督输出的判别器版本。

7.2.8. 训练

以下伪代码概述了 SGAN 训练算法。

SGAN 训练算法

对于每个训练迭代 do

  1. 训练判别器(监督):

    1. 从标记的真实示例中随机抽取一个迷你批次(xy)。

    2. 对于给定的迷你批次计算 D((x, y)) 并反向传播多类分类损失以更新 θ^((D)) 以最小化损失。

  2. 训练判别器(无监督):

    1. 从未标记的真实示例中随机抽取一个迷你批次 x

    2. 对于给定的迷你批次计算 D(x) 并反向传播二分类损失以更新 θ^((D)) 以最小化损失。

    3. 取一个随机噪声向量迷你批次 z 并生成一个假例迷你批次:G(z) = *x**。

    4. 对于给定的迷你批次计算 D(x**) 并反向传播二分类损失以更新 θ^((D*)) 以最小化损失。

  3. 训练生成器:

    1. 取一个随机噪声向量迷你批次 z 并生成一个假例迷你批次:G(z) = *x**。

    2. 对于给定的迷你批次计算 D(x**) 并反向传播二分类损失以更新 θ^((G*)) 以最大化损失。

End for

以下列表实现了 SGAN 训练算法。

列表 7.9. SGAN 训练算法
supervised_losses = []
iteration_checkpoints = []
def train(iterations, batch_size, sample_interval):

    real = np.ones((batch_size, 1))                                            ***1***

    fake = np.zeros((batch_size, 1))                                           ***2***

    for iteration in range(iterations):

        imgs, labels = dataset.batch_labeled(batch_size)                       ***3***

        labels = to_categorical(labels, num_classes=num_classes)               ***4***

        imgs_unlabeled = dataset.batch_unlabeled(batch_size)                   ***5***

        z = np.random.normal(0, 1, (batch_size, z_dim))                        ***6***
        gen_imgs = generator.predict(z)

        d_loss_supervised,
                 accuracy = discriminator_supervised.train_on_batch(imgs, labels)   ***7***

        d_loss_real = discriminator_unsupervised.train_on_batch(               ***8***
            imgs_unlabeled, real)

        d_loss_fake = discriminator_unsupervised.train_on_batch(gen_imgs, fake)***9***

        d_loss_unsupervised = 0.5 * np.add(d_loss_real, d_loss_fake)

        z = np.random.normal(0, 1, (batch_size, z_dim))                        ***10***
        gen_imgs = generator.predict(z)

        g_loss = gan.train_on_batch(z, np.ones((batch_size, 1)))               ***11***

        if (iteration + 1) % sample_interval == 0:

            supervised_losses.append(d_loss_supervised)                        ***12***
            iteration_checkpoints.append(iteration + 1)

            print(                                                             ***13***
                "%d [D loss supervised: %.4f, acc.: %.2f%%] [D loss" +
                " unsupervised: %.4f] [G loss: %f]"
                % (iteration + 1, d_loss_supervised, 100 * accuracy,
                  (d_loss_unsupervised, g_loss))
  • 1 真实图像的标签:全为 1

  • 2 假图像的标签:全为 0

  • 3 获取标记示例

  • 4 One-hot-encoded 标签

  • 5 获取未标记示例

  • 6 生成一批假图像

  • 7 在真实标记示例上训练

  • 8 在真实未标记示例上训练

  • 9 在假例上训练

  • 10 生成一批假图像

  • 11 训练生成器

  • 12 将判别器的监督分类损失保存以在训练后绘制*

  • 13 输出训练进度*

训练模型

我们使用较小的批量大小,因为我们只有 100 个标记示例用于训练。迭代次数通过试错确定:我们不断增加迭代次数,直到判别器的监督损失达到平台期,但不要超过太多(以减少过拟合的风险):

列表 7.10. 训练模型
iterations = 8000                                 ***1***
batch_size = 32
sample_interval = 800

train(iterations, batch_size, sample_interval)    ***2***
  • 1 设置超参数

  • 2 训练 SGAN 指定次数的迭代

模型训练和测试准确率

现在,让我们来揭晓我们一直期待的时刻——让我们看看我们的 SGAN 作为分类器的表现。在训练过程中,我们看到我们达到了 100%的监督准确率。虽然这可能看起来很令人印象深刻,但请记住,我们只有 100 个标记示例用于监督训练。也许我们的模型只是记住了训练数据集。重要的是我们的分类器如何将泛化到训练集中之前未见过的数据,如下面的列表所示。

列表 7.11. 检查准确率
x, y = dataset.test_set()
y = to_categorical(y, num_classes=num_classes)

_, accuracy = discriminator_supervised.evaluate(x, y)      ***1***
print("Test Accuracy: %.2f%%" % (100 * accuracy))
  • 1 在测试集上计算分类准确率

鼓掌,请。

我们的 SGAN 能够准确分类测试集中大约 89%的示例。为了了解这一点有多么显著,让我们将其性能与一个完全监督分类器进行比较。

7.3. 与完全监督分类器的比较

为了尽可能公平地进行比较,我们使用与用于监督判别器训练的相同网络架构来训练完全监督分类器,如下面的列表所示。想法是这将使我们能够隔离通过 GAN 启用的半监督学习实现的分类器泛化能力的提升。

列表 7.12. 完全监督分类器
mnist_classifier = build_discriminator_supervised(
                         build_discriminator_net(img_shape))     ***1***
mnist_classifier.compile(loss='categorical_crossentropy',
                         metrics=['accuracy'],
                         optimizer=Adam())
  • 1 与 SGAN 判别器具有相同网络架构的完全监督分类器

我们通过使用与训练 SGAN 相同的 100 个训练示例来训练完全监督分类器。为了简洁,这里没有展示训练代码和输出训练和测试准确率的代码。您可以在我们的 GitHub 仓库中找到代码,在章节-7 文件夹下的 SGAN Jupyter 笔记本中。

与 SGAN 判别器一样,完全监督分类器在训练数据集上达到了 100%的准确率。然而,在测试集上,它只能正确分类大约 70%的示例——比我们的 SGAN 差了整整 20 个百分点!换句话说,SGAN 将训练准确率提高了近 30%!

随着更多训练数据的加入,完全监督分类器的泛化能力显著提高。使用相同的设置和训练,拥有 10,000 个标记示例(是我们最初使用的 100 倍)的完全监督分类器,我们达到了大约 98%的准确率。但那将不再是一个 半监督 环境。

7.4. 结论

在本章中,我们探讨了如何通过教会判别器为真实示例输出类别标签来使用 GAN 进行半监督学习。您看到,SGAN 训练的分类器从少量训练示例中泛化的能力显著优于一个可比的完全监督分类器。

从 GAN 创新的角度来看,SGAN 的一个关键区别特征是使用标签进行判别器训练。你可能想知道是否可以利用标签进行生成器训练。你问得真巧——这正是下一章(条件 GAN)的主题。

摘要

  • 半监督 GAN (SGAN) 是一种生成对抗网络,其判别器学习以下内容:

    • 区分伪造示例和真实示例

    • 为真实示例分配正确的类别标签

  • SGAN 的目的是通过尽可能少的标记示例来训练判别器成为一个分类器,从而实现优越的分类准确率,从而减少分类任务对大量标记数据集的依赖。

  • 在我们的实现中,我们使用了softmax和多项式交叉熵损失来处理分配真实标签的监督任务,以及sigmoid和二元交叉熵来处理区分真实和伪造数据的任务。

  • 我们证明了 SGAN 在测试集中对之前未见数据点的分类准确率远优于在相同数量的标记训练示例上训练的同类全监督分类器。

第八章. 条件 GAN

本章涵盖

  • 使用标签训练生成器和判别器

  • 教会 GAN 生成与指定标签匹配的示例

  • 实现条件 GAN (CGAN)以生成我们选择的任意手写数字

在上一章中,你了解了 SGAN,它介绍了在 GAN 训练中使用标签的概念。SGANs 使用标签来训练判别器成为一个强大的半监督分类器。在本章中,你将了解条件 GAN (CGAN),它使用标签来训练生成器判别器。多亏了这一创新,条件 GAN 允许我们指导生成器合成我们想要的伪造示例。

8.1. 动机

正如你在本书的整个过程中所看到的,GANs 能够生成从简单的手写数字到逼真的人脸图像的各种示例。然而,尽管我们可以通过选择训练数据集来控制我们的 GAN 学习模拟的示例域,但我们无法指定 GAN 将生成的数据样本的任何特征。例如,我们在第四章中实现的 DCGAN 可以合成看起来逼真的手写数字,但我们无法控制它是否会在任何给定时间产生数字 7 而不是数字 9。

在像 MNIST 这样的简单数据集上,其中示例只属于 10 个类别中的一个,这种担忧可能看起来微不足道。例如,如果我们目标是生成数字 9,我们只需不断生成示例,直到我们得到想要的数字。然而,在更复杂的数据生成任务中,可能的答案范围太大,以至于这种蛮力解决方案不再实用。以生成人类面孔的任务为例。尽管第六章中 Progressive GAN 生成的图像令人印象深刻,但我们无法控制将生成哪种面孔。我们无法指导生成器合成,比如说,一个男性或女性面孔,更不用说其他特征,如年龄或面部表情。

能够决定生成哪种类型的数据,为各种应用打开了大门。作为一个有些牵强的例子,想象一下我们是一群侦探在解决一起谋杀案,一个目击者描述凶手是一个中年红发绿眼的女性。如果我们能够将描述的特征输入到一个计算机程序中,并让它输出一系列符合标准的面孔,这将大大加快这个过程。我们的目击者随后可以指向最像罪犯的那一个。

我们相信你可以想到许多其他实际应用,其中能够生成符合我们选择标准图像的能力将是一个颠覆性的变化。在医学研究中,我们可以引导新药化合物的创建;在电影制作和计算机生成图像(CGI)中,我们可以用最少的输入从人类动画师那里创建我们想要的场景。这个列表还可以继续下去。

CGAN(条件生成对抗网络)是首批使有针对性的数据生成成为可能 GAN 创新之一,可以说是最具影响力的一个。在本章的剩余部分,你将学习 CGAN 是如何工作的,并通过使用(你猜对了!)MNIST 数据集来实现一个小规模版本。

8.2. 什么是条件生成对抗网络?

由蒙特利尔大学博士研究生 Mehdi Mirza 和 Flickr 人工智能架构师 Simon Osindero 于 2014 年提出,条件生成对抗网络是一种在训练过程中通过使用一些附加信息对生成器和判别器进行条件化的生成对抗网络。^([[1)]]这种辅助信息在理论上可以是任何东西,比如一个类别标签,一组标签,甚至是一个书面描述。为了清晰和简单,我们将使用标签作为条件信息,在我们解释 CGAN 是如何工作的过程中。

¹

参见 Mehdi Mirza 和 Simon Osindero 于 2014 年发表的“Conditional Generative Adversarial Nets”,arxiv.org/abs/1411.1784

在 CGAN 训练过程中,生成器学习为训练数据集中每个标签生成逼真的示例,而判别器学习区分伪造的示例-标签对和真实的示例-标签对。与上一章中的半监督 GAN 相比,其判别器学习为每个真实示例分配正确的标签(除了区分真实示例和伪造示例之外),CGAN 中的判别器不学习识别哪个类别是哪个。它只学习接受匹配的真实对,同时拒绝不匹配的对以及示例为伪造的对。

例如,CGAN 判别器应该学习拒绝对(, 4),无论示例(手写的数字 3)是真实还是伪造,因为它与标签 4 不匹配。CGAN 判别器还应该学习拒绝所有图像-标签对,其中图像是伪造的,即使标签与图像匹配。

因此,为了欺骗判别器,CGAN 生成器不仅要生成看起来逼真的数据,它生成的示例还需要与它们的标签匹配。生成器完全训练后,这使我们能够通过传递所需的标签来指定我们想要 CGAN 合成的示例。

8.2.1. CGAN 生成器

为了使事情更加形式化,让我们将条件标签称为y。生成器使用噪声向量z和标签y来合成一个伪造的示例G(z, y) = x**|y(读作“x**给定,或者基于y”)。这个伪造示例的目标是在判别器的眼中尽可能接近给定标签的真实示例。图 8.1 说明了生成器。

图 8.1. CGAN 生成器:G(z, y) = x**|y。使用随机噪声向量z和标签y作为输入,生成器产生一个伪造的示例x**|y,力求与标签看起来逼真。

8.2.2. CGAN 判别器

判别器接收带有标签(xy)的真实示例和带有生成它们的标签的伪造示例(x**|yy*)。在真实示例-标签对上,判别器学习如何识别真实数据以及如何识别匹配的对。在生成器生成的示例上,它学习识别伪造的图像-标签对,从而学习将它们与真实示例区分开来。

判别器输出一个概率值,表示它认为输入是一个真实匹配对的信心程度。判别器的目标是学习拒绝所有伪造示例和所有未能匹配其标签的示例,同时接受所有真实示例-标签对,如图 8.2 所示。

图 8.2。CGAN 判别器接收带有标签的真实示例(x, y)和假示例,以及用于合成它们的标签(x**|y*, y)。然后判别器输出一个概率(由 sigmoid 激活函数σ计算),指示输入对是真实的还是假的。

8.2.3. 摘要表

两个 CGAN 子网络、它们的输入、输出和目标在表 8.1 中总结。

表 8.1。CGAN 生成器和判别器网络
生成器 判别器

| 输入 | 随机数字向量和标签:(z, y) | 判别器接收以下输入:

  • 来自训练数据集的带标签的真实示例:(x, y)

  • 生成器为匹配给定标签而创建的假示例,以及标签:(x**|y, y*)

|

输出 努力匹配其标签的尽可能令人信服的假示例:G(z, y) = x**|y* 表示输入示例是否为真实匹配示例-标签对的单一概率
目标 生成与标签匹配的逼真假数据 区分来自生成器的假示例-标签对和来自训练数据集的真实示例-标签对

8.2.4. 架构图

将所有这些放在一起,图 8.3 展示了 CGAN 的高级架构图。注意,对于每个假示例,相同的标签 y 都传递给了生成器和判别器。此外,请注意,判别器从未被明确训练来通过在具有不匹配标签的真实示例上训练来拒绝不匹配的配对;它识别不匹配配对的能力是训练仅接受真实匹配配对的副产品。

图 8.3。CGAN 生成器使用随机噪声向量 z 和标签 yn 个可能标签之一)作为输入,并生成一个既逼真又令人信服的假示例 x**|y*。

注意

你可能已经注意到一个模式:对于几乎每个 GAN 变体,我们都提供了一个总结判别器和生成器网络输入、输出和目标的表格,以及网络架构图。这并非偶然;实际上,这些章节的主要目标之一是提供一个心理模板——一种可重复使用的框架——当你遇到偏离原始 GAN 的 GAN 实现时,可以查找的东西。分析生成器和判别器网络以及整体模型架构通常是最佳的第一步。

CGAN 判别器接收生成器产生的假标签示例(x**|y, y)和真实标签示例(x*, y),并学习判断给定的示例-标签是真实还是假。

理论部分就到这里。现在是时候将你所学的知识付诸实践,并实现我们自己的 CGAN 模型了。

8.3. 教程:实现条件 GAN

在本教程中,我们将实现一个 CGAN 模型,该模型学习生成我们选择的手写数字。最后,我们将为每个数字生成一个图像样本,以查看模型学习生成目标数据的效果如何。

8.3.1. 实现

我们的实现受到了开源 GitHub 仓库中 Keras GAN 模型的 CGAN 的启发(与我们在第三章和第四章中使用的是同一个)。特别是,我们使用了该仓库中 Embedding 层结合示例和标签到联合隐藏表示的方法(关于这一点稍后还会详细介绍)。

²

请参阅 Erik Linder-Norén 的 Keras-GAN GitHub 仓库,2017,github.com/eriklindernoren/Keras-GAN

然而,我们的 CGAN 模型与 Keras-GAN 仓库中找到的模型有所不同。我们对嵌入实现进行了重构,使其更易于阅读,并添加了详细的解释性注释。关键的是,我们还使我们的 CGAN 能够使用卷积神经网络,这会产生更真实的效果——回想一下第三章中 GAN 生成的图像与第四章中 DCGAN 生成的图像之间的差异!

包含添加的训练进度可视化的完整实现 Jupyter 笔记本可在我们的 GitHub 仓库的 chapter-8 文件夹中找到:github.com/GANs-in-Action/gans-in-action。代码已在 Python 3.6.0、Keras 2.1.6 和 TensorFlow 1.8.0 上进行了测试。为了加快训练时间,我们建议在 GPU 上运行模型。

8.3.2. 设置

你猜对了——第一步是导入我们模型所需的全部模块和库,如下所示。

列表 8.1. 导入语句
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from keras.datasets import mnist
from keras.layers import (
        Activation, BatchNormalization, Concatenate, Dense,
        Embedding, Flatten, Input, Multiply, Reshape)
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Conv2D, Conv2DTranspose
from keras.models import Model, Sequential
from keras.optimizers import Adam

就像之前一样,我们在这里也指定了输入图像的大小、噪声向量 z 的大小以及数据集中的类别数量,具体如下。

列表 8.2. 模型输入维度
img_rows = 28
img_cols = 28
channels = 1

img_shape = (img_rows, img_cols, channels)     ***1***

z_dim = 100                                    ***2***

num_classes = 10                               ***3***
  • 1 输入图像维度

  • 2 噪声向量的大小,用作生成器的输入

  • 3 数据集中的类别数量

8.3.3. CGAN 生成器

在本节中,我们实现 CGAN 生成器。到目前为止,你应该已经熟悉了网络的大部分内容,这些内容来自第四章和第七章。我们对 CGAN 的修改主要集中在输入处理上,其中我们使用嵌入和逐元素乘法将随机噪声向量 z 和标签 y 结合成一个联合表示。让我们来看看代码做了什么:

  1. 使用 Keras 的Embedding层将标签y(一个从 0 到 9 的整数)转换为一个大小为z_dim(随机噪声向量的长度)的密集向量。

  2. 使用 Keras 的Multiply层将标签嵌入与噪声向量z结合成一个联合表示。正如其名所示,该层将两个等长向量的对应条目相乘,并输出一个包含乘积的单个向量。

  3. 将得到的向量作为输入输入到 CGAN 生成器网络的其余部分以合成图像。

图 8.4 展示了使用标签 7 作为示例的过程。

图 8.4. 将条件标签(本例中的 7)和随机噪声向量 z 组合成单个联合表示的步骤

首先,我们将标签嵌入到与z相同大小的向量中。其次,我们乘以嵌入的标签和z(符号表示逐元素乘法)。然后,将得到的联合表示用作 CGAN 生成器网络的输入。

最后,以下列表展示了所有这些在 Python/Keras 代码中的样子。

列表 8.3. CGAN 生成器
def build_generator(z_dim):

    model = Sequential()

    model.add(Dense(256 * 7 * 7, input_dim=z_dim))                           ***1***
    model.add(Reshape((7, 7, 256)))

    model.add(Conv2DTranspose(128, kernel_size=3, strides=2, padding='same'))***2***

    model.add(BatchNormalization())                                          ***3***

    model.add(LeakyReLU(alpha=0.01))                                         ***4***

    model.add(Conv2DTranspose(64, kernel_size=3, strides=1, padding='same')) ***5***

    model.add(BatchNormalization())                                          ***3***

    model.add(LeakyReLU(alpha=0.01))                                         ***4***

    model.add(Conv2DTranspose(1, kernel_size=3, strides=2, padding='same'))  ***6***

    model.add(Activation('tanh'))                                            ***7***

    return model

def build_cgan_generator(z_dim):

    z = Input(shape=(z_dim, ))                                               ***8***

    label = Input(shape=(1, ), dtype='int32')                                ***9***

    label_embedding = Embedding(num_classes, z_dim, input_length=1)(label)   ***10***

    label_embedding = Flatten()(label_embedding)                             ***11***

    joined_representation = Multiply()([z, label_embedding])                 ***12***

    generator = build_generator(z_dim)

    conditioned_img = generator(joined_representation)                       ***13***

    return Model([z, label], conditioned_img)
  • 1 通过全连接层将输入重塑为 7 × 7 × 256 张量

  • 2 转置卷积层,从 7 × 7 × 256 转换为 14 × 14 × 128 张量

  • 3 批标准化

  • 4 漏波 ReLU 激活函数

  • 5 转置卷积层,从 14 × 14 × 128 转换为 14 × 14 × 64 张量

  • 6 转置卷积层,从 14 × 14 × 64 转换为 28 × 28 × 1 张量

  • 7 带有 tanh 激活函数的输出层

  • 8 随机噪声向量 z

  • 9 条件标签:整数 0–9,指定 G 应该生成的数量

  • 10 标签嵌入:将标签转换为大小为 z_dim 的密集向量;生成形状为(batch_size,1,z_dim)的 3D 张量

  • 11 将嵌入的 3D 张量展平为形状为(batch_size,z_dim)的 2D 张量

  • 12 向量 z 和标签嵌入的逐元素乘积

  • 13 为给定标签生成图像

8.3.4. CGAN 判别器

接下来,我们实现 CGAN 判别器。就像前面的章节一样,网络架构应该对你来说很熟悉,除了我们处理输入图像及其标签的部分。在这里,我们也使用 Keras 的Embedding层将输入标签转换为密集向量。然而,与生成器不同,那里的模型输入是一个扁平向量,判别器接收三维图像。这需要定制处理,以下步骤将描述:

  1. 取一个标签(一个从 0 到 9 的整数)并使用 Keras 的Embedding层将标签转换为大小为 28 × 28 × 1 = 784(展开图像的长度)的密集向量。

  2. 将标签嵌入重塑为图像维度(28 × 28 × 1)。

  3. 将重塑的标签嵌入连接到相应的图像上,创建一个形状为(28 × 28 × 2)的联合表示。你可以将其视为在其嵌入的标签“盖章”在顶部的图像。

  4. 将图像-标签联合表示作为输入输入到 CGAN 判别器网络。请注意,为了使一切正常工作,我们必须调整模型输入维度为(28 × 28 × 2),以反映新的输入形状。

再次,为了使其更具体,让我们通过使用标签 7 作为示例来查看这个过程的外观;参见图 8.5。

图 8.5. 将标签(本例中为 7)和输入图像组合成单个联合表示的步骤

首先,我们将标签嵌入到一个与展平图像大小相同的向量中(28 × 28 × 1 = 784)。其次,我们将嵌入的标签重塑为与输入图像相同的形状(28 × 28 × 1)。第三,我们将重塑的嵌入标签连接到相应的图像上。然后,这个联合表示被传递到 CGAN 判别器网络作为输入。

除了预处理步骤之外,与第四章中的判别器网络相比,我们还需要对判别器网络进行一些额外的调整。(正如前一章所述,基于我们的 DCGAN 实现构建模型应该更容易看到 CGAN 特有的变化,而不会受到模型无关部分的实现细节的干扰。)首先,我们必须调整模型输入维度为(28 × 28 × 2),以反映新的输入形状。

第二,我们将第一个卷积层的深度从 32 增加到 64。这种变化背后的原因是由于连接的标签嵌入,编码的信息更多;这种网络架构在实验中确实产生了更好的结果。

在输出层,我们使用 sigmoid 激活函数来产生一个概率,即输入图像-标签对是真实的而不是伪造的——这里没有变化。最后,以下列表是我们的 CGAN 判别器实现。

列表 8.4. CGAN 判别器
def build_discriminator(img_shape):

    model = Sequential()

    model.add(                                                        ***1***

        Conv2D(64,
               kernel_size=3,
               strides=2,
               input_shape=(img_shape[0], img_shape[1], img_shape[2] + 1),
               padding='same'))

    model.add(LeakyReLU(alpha=0.01))                                  ***2***

    model.add(                                                        ***3***
        Conv2D(64,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(BatchNormalization())                                   ***4***

    model.add(LeakyReLU(alpha=0.01))                                  ***5***

    model.add(                                                        ***6***
        Conv2D(128,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    model.add(BatchNormalization())                                   ***7***

    model.add(LeakyReLU(alpha=0.01))                                  ***8***

    model.add(Flatten())                                              ***9***
    model.add(Dense(1, activation='sigmoid'))

    return model

def build_cgan_discriminator(img_shape):

    img = Input(shape=img_shape)                                      ***10***

    label = Input(shape=(1, ), dtype='int32')                         ***11***

    label_embedding = Embedding(num_classes,                          ***12***
                                np.prod(img_shape),
                                input_length=1)(label)

    label_embedding = Flatten()(label_embedding)                      ***13***

    label_embedding = Reshape(img_shape)(label_embedding)             ***14***

    concatenated = Concatenate(axis=-1)([img, label_embedding])       ***15***

    discriminator = build_discriminator(img_shape)

    classification = discriminator(concatenated)                      ***16***

    return Model([img, label], classification)
  • 1 卷积层,从 28 × 28 × 2 张量转换为 14 × 14 × 64 张量

  • 2 漏波 ReLU 激活

  • 3 卷积层,从 14 × 14 × 64 张量转换为 7 × 7 × 64 张量

  • 4 批标准化

  • 5 漏波 ReLU 激活

  • 6 卷积层,从 7 × 7 × 64 张量转换为 3 × 3 × 128 张量

  • 7 批标准化

  • 8 漏波 ReLU(Leaky ReLU

  • 9 带有 sigmoid 激活的输出层

  • 10 输入图像

  • 11 输入图像的标签

  • 12 标签嵌入:将标签转换为大小为 z_dim 的密集向量;产生形状为(batch_size, 1, 28 × 28 × 1)的 3D 张量

  • 13 将嵌入的 3D 张量展平为形状为(batch_size, 28 × 28 × 1)的 2D 张量

  • 14 将标签嵌入重塑为与输入图像相同的维度

  • 15 将图像与其标签嵌入连接起来

  • 16 对图像-标签对进行分类

8.3.5. 构建模型

接下来,我们构建和编译 CGAN 判别器和生成器模型,如下所示。注意,在用于训练生成器的组合模型中,相同的输入标签被传递给生成器(以生成样本)和判别器(以做出预测)。

列表 8.5. 构建和编译 CGAN 模型
def build_cgan(generator, discriminator):

    z = Input(shape=(z_dim, ))                               ***1***

    label = Input(shape=(1, ))                               ***2***

    img = generator([z, label])                              ***3***

    classification = discriminator([img, label])

    model = Model([z, label], classification)                ***4***

    return model

discriminator = build_cgan_discriminator(img_shape)          ***5***
discriminator.compile(loss='binary_crossentropy',
                      optimizer=Adam(),
                      metrics=['accuracy'])

generator = build_cgan_generator(z_dim)                      ***6***

discriminator.trainable = False                              ***7***

cgan = build_cgan(generator, discriminator)                  ***8***
cgan.compile(loss='binary_crossentropy', optimizer=Adam())
  • 1 随机噪声向量 z

  • 2 图像标签

  • 2 对该标签生成的图像

  • 4 组合生成器 -> 判别器模型 G([z, label]) = x* D(x*) = classification

  • 5 构建和编译判别器

  • 6 构建生成器

  • 7 在生成器训练期间保持判别器参数不变

  • 8 使用固定判别器的 CGAN 模型构建和编译以训练生成器

8.3.6. 训练

对于 CGAN 训练算法,每个训练迭代的细节如下。

CGAN 训练算法

对于每个训练迭代执行

  1. 训练判别器:

    1. 从随机迷你批次中取出一批真实示例及其标签 (x, y)。

    2. 对于迷你批次计算 D((x, y)) 并将二元分类损失反向传播以更新 θ^((D)) 以最小化损失。

    3. 取出一批随机噪声向量及其类别标签 (z, y) 并生成一批假示例:G(z, y) = x**|y*。

    4. 对于迷你批次计算 D(x**|y, y) 并将二元分类损失反向传播以更新 θ^((D*)) 以最小化损失。

  2. 训练生成器:

    1. 取出一批随机噪声向量及其类别标签 (z, y) 并生成一批假示例:G(z, y) = x**|y*。

    2. 对于给定的迷你批次,计算 D(x**|y, y) 并将二元分类损失反向传播以更新 θ^((G*)) 以最大化损失。

结束 for

以下列表实现了这个 CGAN 训练算法。

列表 8.6. CGAN 训练循环
accuracies = []
losses = []

def train(iterations, batch_size, sample_interval):

    (X_train, y_train), (_, _) = mnist.load_data()                           ***1***

    X_train = X_train / 127.5 - 1\.                                           ***2***
    X_train = np.expand_dims(X_train, axis=3)

    real = np.ones((batch_size, 1))                                          ***3***

    fake = np.zeros((batch_size, 1))                                         ***4***

    for iteration in range(iterations):

        idx = np.random.randint(0, X_train.shape[0], batch_size)             ***5***
        imgs, labels = X_train[idx], y_train[idx]

        z = np.random.normal(0, 1, (batch_size, z_dim))                      ***6***
        gen_imgs = generator.predict([z, labels])

        d_loss_real = discriminator.train_on_batch([imgs, labels], real)     ***7***
        d_loss_fake = discriminator.train_on_batch([gen_imgs, labels], fake)
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

        z = np.random.normal(0, 1, (batch_size, z_dim))                      ***8***

        labels = np.random.randint(0, num_classes, batch_size).reshape(-1, 1)***9***

        g_loss = cgan.train_on_batch([z, labels], real)                      ***10***

        if (iteration + 1) % sample_interval == 0:

            print("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" %             ***11***
                  (iteration + 1, d_loss[0], 100 * d_loss[1], g_loss))

            losses.append((d_loss[0], g_loss))                               ***12***
            accuracies.append(100 * d_loss[1])

            sample_images()                                                  ***13***
  • 1 加载 MNIST 数据集

  • 2 将[0, 255]灰度像素值缩放到[–1, 1]

  • 2 真图像的标签:全部为 1s

  • 4 假图像的标签:全部为 0

  • 5 获取一批随机真实图像及其标签

  • 6 生成一批假图像

  • 7 训练判别器

  • 8 生成一批噪声向量

  • 9 获取一批随机标签

  • 10 训练生成器

  • 11 输出训练进度

  • 12 保存损失和准确率,以便在训练后绘制*

  • 13 输出生成的图像样本*

8.3.7. 输出样本图像

你可能从第三章和第四章中认出下一个函数。我们使用它来检查随着训练的进行,生成器产生的图像质量是如何提高的。列表 8.7 中的函数确实相似,但存在一些关键差异。

首先,我们不是生成一个 4×4 的随机手写数字网格,而是生成一个 2×5 的数字网格,第一行是 1 到 5,第二行是 6 到 9。这使得我们能够检查 CGAN 生成器学习生成特定数字的效果。其次,我们通过使用set_title()方法显示每个示例的标签。

列表 8.7. 显示生成的图像
def sample_images(image_grid_rows=2, image_grid_columns=5):

    z = np.random.normal(0, 1, (image_grid_rows * image_grid_columns, z_dim))***1***

    labels = np.arange(0, 10).reshape(-1, 1)                                 ***2***

    gen_imgs = generator.predict([z, labels])                                ***3***

    gen_imgs = 0.5 * gen_imgs + 0.5                                          ***4***

    fig, axs = plt.subplots(image_grid_rows,                                 ***5***
                            image_grid_columns,
                            figsize=(10, 4),
                            sharey=True,
                            sharex=True)

    cnt = 0
    for i in range(image_grid_rows):
        for j in range(image_grid_columns):
            axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray')            ***6***
            axs[i, j].axis('off')
            axs[i, j].set_title("Digit: %d" % labels[cnt])
            cnt += 1
  • *1. 采样随机噪声

  • *2. 获取图像标签 0-9

  • *3. 从随机噪声生成图像

  • *4. 将图像像素值缩放到[0, 1]

  • *5. 设置图像网格

  • *6. 输出一个图像网格

图 8.6 展示了该函数的样本输出,并说明了在训练过程中 CGAN 生成的数字的改进。

图 8.6. 从随机噪声开始,GCAN 学会了为训练数据集中的每个标签生成看起来逼真的数字。

8.3.8. 训练模型

最后,让我们运行我们刚刚实现的模型:

iterations = 12000                                ***1***
batch_size = 32
sample_interval = 1000

train(iterations, batch_size, sample_interval)    ***2***
  • *1. 设置超参数

  • *2. 对 CGAN 进行指定次数的迭代训练

8.3.9. 检查输出:目标数据生成

图 8.7 展示了 CGAN 生成器在完全训练后产生的数字图像。在每一行中,我们指示生成器合成一个不同的数字,从 0 到 9。请注意,每个数字都以不同的书写风格呈现,这证明了 CGAN 不仅能够学习生成与训练数据集中每个标签匹配的示例,而且能够捕捉到训练数据的全部多样性。

图 8.7. 每一行展示了一个用于匹配给定数字(0 到 9)的图像样本。如您所见,CGAN 生成器已经成功地学会了生成我们数据集中表示的每个类别。

8.4. 结论

在本章中,您看到了如何使用标签来指导生成器和判别器的训练,以教会一个生成对抗网络(GAN)生成我们选择的假样本。除了 DCGAN 之外,CGAN 是最有影响力的早期 GAN 变体之一,它激发了无数新的研究方向。

在这些方法中,最有影响力和最有前景的是使用条件对抗网络作为图像到图像翻译问题的一般性解决方案。这类问题旨在将图像从一种模态转换为另一种模态。图像到图像翻译的应用范围从给黑白照片上色到将白天场景转换为夜晚,以及从地图视图合成卫星视图。

基于条件 GAN 范式最成功的早期实现之一是 pix2pix,它使用图像对(一个作为输入,另一个作为标签)来学习从一个域翻译到另一个域。回想一下,在理论和实践中,用于训练 CGAN 的条件信息可以远不止标签,以提供更复杂的使用案例和场景。例如,对于着色任务,图像对将是一张黑白照片(输入)和同一照片的彩色版本(标签)。你将在下一章中看到这些示例。

我们没有详细介绍 pix2pix,因为它发表后大约一年,就被另一种 GAN 变体所超越。这种变体不仅在图像到图像的翻译任务上超越了 pix2pix 的性能,而且无需配对图像就能完成这项任务。循环一致对抗网络(或 CycleGAN,正如这项技术后来所知)只需要两组代表两个域的图像(例如,一组黑白照片和一组彩色照片)。你将在下一章中了解这种引人注目的 GAN 变体。

概述

  • 条件 GAN(CGAN)是一种 GAN 变体,其中生成器和判别器在训练期间都基于辅助数据,如类标签。

  • 额外的信息限制了生成器合成特定类型的输出,并使判别器只能接受与给定额外信息匹配的真实示例。

  • 作为教程,我们实现了一个 CGAN,通过使用 MNIST 类标签作为我们的条件信息,生成我们选择的逼真的手写数字。

  • 嵌入将整数映射到所需大小的密集向量。我们使用嵌入从随机噪声向量和标签(用于 CGAN 生成器训练)以及输入图像和标签(用于 CGAN 判别器训练)创建联合隐藏表示。

第九章. CycleGAN

本章涵盖

  • 通过对整个图像进行条件化来扩展条件 GANs 的想法

  • 探索最强大和复杂的 GAN 架构之一:CycleGAN

  • 展示 GANs 的面向对象设计和其四个主要组件的架构

  • 实现一个 CycleGAN 以运行苹果到橙子的转换

最后,一个几乎具有普遍吸引力的技术突破,因为似乎每个人都喜欢比较苹果和橙子。在本章中,你将学习如何做到这一点!但这不是一件小事,因此我们需要至少两组判别器和两组生成器来实现这一点。这显然使架构复杂化,因此我们需要花更多的时间来讨论它,但至少,这是一个很好的起点,以完全面向对象编程(OOP)的方式思考。

9.1. 图像到图像的翻译

在上一章末尾我们提到的 GANs 应用的一个令人着迷的领域是图像到图像的翻译。在这个应用中,GANs 取得了巨大的成功——在视频、静态图像,甚至是风格转换中。事实上,GANs 在这些应用中处于前沿,因为它们几乎开启了一个全新的使用类别。由于它们的视觉特性,更成功的 GAN 变体通常在 YouTube 和 Twitter 上广为流传,所以如果您还没有看过这些视频,我们鼓励您通过搜索pix2pixCycleGANvid2vid来查看它们。

实际上,这种翻译意味着我们的生成器输入是一个图片,因为我们需要我们的生成器(翻译器)从这个图像开始。换句话说,我们正在将一个图像从一个域映射到另一个域。以前,生成过程中用于播种的潜在向量通常是一个难以理解的向量。现在我们用输入图像来替换它。

将图像到图像翻译视为条件 GAN 的一个特殊情况是一个很好的思考方式。然而,在这种情况下,我们是在一个完整的图像上进行条件化(而不是仅仅一个类别)——通常与输出图像具有相同的维度性,然后将其作为标签(在第八章中介绍)提供给网络。在这个领域,第一个著名的例子是来自加州大学伯克利分校的一个图像翻译工作,如图 9.1 所示。

图 9.1. 条件 GANs 提供了一个强大的框架,用于图像翻译,在许多领域都表现出色。

(来源:“使用条件对抗网络的图像到图像翻译”,作者:Phillip Isola,github.com/phillipi/pix2pix.)

如您所见,我们可以从以下任何一种映射:

  • 从语义标签(例如,在汽车应该画蓝色的地方画蓝色,在道路应该画紫色的地方画紫色)到街道的逼真图像

  • 从卫星图像到类似于谷歌地图的视图

  • 从日间图像到夜间图像

  • 从黑白到彩色

  • 从轮廓到合成的时尚物品

这个想法显然非常强大且灵活;然而,问题在于需要成对的数据。从第八章,您了解到我们需要为条件 GAN 提供标签。因为在这种情况下,我们使用另一张图像作为标签,除非我们映射到对应的图像——即在其他域中的完全相同的图像,否则这种映射没有意义。

因此,夜间图像需要与日间图像来自完全相同的位置。时尚物品的轮廓需要与训练集中其他域中完全彩色/合成的物品的精确匹配。换句话说,在训练过程中,GAN 需要能够访问原始域中物品的对应标签。

这通常是通过首先加载大量的彩色图片,对所有这些图片应用黑白滤镜,然后使用未修改的图像作为一个域,而黑白滤镜后的图像作为另一个域来完成的。这确保了我们在这两个域中都有相应的图像。然后我们可以在任何地方应用训练好的 GAN,但如果我们没有生成这些“完美”对子的简单方法,我们就很不幸了!

9.2. 循环一致性损失:去而复返的 aGAN

这组加州大学伯克利分校的天才洞察力是,我们实际上并不需要完美的对子.^([1]) 相反,我们只是完成循环:从一个域翻译到另一个域,然后再翻译回来。例如,我们将公园的夏季图片(域 A)翻译成冬季图片(域 B),然后再翻译回夏季(域 A)。现在我们基本上创建了一个循环,理想情况下,原始图片(a)和重建图片(图片)是相同的。如果它们不相同,我们可以在像素级别上衡量它们的损失,从而得到 CycleGAN 的第一个损失:循环一致性损失,这在图 9.2 中有所描述。

¹

参见 Jun-Yan Zhu 等人 2017 年的论文“使用循环一致性对抗网络的未配对图像到图像翻译”,arxiv.org/pdf/1703.10593.pdf

图 9.2。因为损失是双向的,我们现在不仅可以从夏季到冬季再现图像,还可以从冬季到夏季再现。如果 G 是从 A 到 B 的生成器,F 是从 B 到 A 的生成器,那么图片

图片

(来源:Jun-Yan Zhu 等人,2017 年,arxiv.org/pdf/1703.10593.pdf

一个常见的类比是思考回译的过程——一个中文句子翻译成英文,然后再翻译回中文应该给出相同的句子。如果不是这样,我们可以通过第一句和第三句之间的差异来衡量循环一致性损失。

要能够使用循环一致性损失,我们需要有两个生成器:一个将 A 翻译到 B,称为G[AB],有时简单地称为G,然后另一个将 B 翻译到 A,称为G[BA],简称F。技术上存在两个损失——正向循环一致性损失和反向循环一致性损失——但因为他们本质上意味着图片以及图片,你可以认为这些基本上是相同的,但差一个。

9.3. 对抗损失

除了循环一致性损失之外,我们仍然有对抗损失。每个由生成器G[AB]进行的翻译都有一个相应的判别器D[B],而G[BA]有判别器D[A]。可以这样考虑,我们在翻译到域 A 时总是测试图片是否看起来真实;因此我们使用D[A],反之亦然。

这与更简单的架构中的想法相同,但现在,由于有两个损失,我们有两个判别器。我们需要确保不仅从苹果到橙子的转换看起来真实,而且从我们估计的橙子回到重建的苹果的转换也看起来真实。回想一下,对抗性损失确保图像看起来真实,因此它仍然是 CycleGAN 工作的关键。因此,对抗性损失被列为第二。循环中的第一个判别器尤为重要——否则,我们只会得到帮助 GAN 记忆它应该重建的噪声。^([3)] 图 9.3 展示了身份丢失的效果。

²

在实践中,这要复杂一些,这取决于例如你是否包括正向和反向循环损失。但你可以将此作为一个心理模型来思考对抗性损失的重要性——记住我们有两个映射 A-B-A 和 B-A-B,所以两个判别器都可能在某个时刻成为第一个。

9.4. 身份丢失

身份丢失 的概念很简单:我们希望 CycleGAN 保留图片的整体色彩结构(或 温度)。因此,我们引入了一个正则化项,帮助我们保持图片色调与原始图像的一致性。想象一下,这是一种确保即使在你对图片应用了许多滤镜之后,你仍然可以恢复原始图片的方法。

这是通过将域 A 中的图像输入到从 B 到 A 的生成器 (G[BA]) 中来实现的,因为 CycleGAN 应该理解它们已经在正确的域中。换句话说,我们惩罚对图像的不必要更改:如果我们输入一只斑马并试图“斑马化”一张图片,我们会得到同样的斑马,因为没有其他事情要做。图 9.3 展示了身份丢失的效果。

³

Jun Yan Zhu 等人,2017,arxiv.org/pdf/1703.10593.pdf。更多信息请访问 mng.bz/loE8

图 9.3. 一张图片胜过千言万语,用以阐明身份丢失的效果:在没有身份丢失的情况下,图片有明显的色调,由于似乎没有理由,所以我们试图惩罚这种行为。即使在黑白图片中,你也应该能够看到差异。然而,要看到它的全部效果,请查看在线的全彩版本。

图片

尽管从严格意义上讲,身份丢失不是 CycleGAN 工作的必要条件,但我们为了完整性而包含它。我们的实现和 CycleGAN 作者的最新实现都包含它,因为这种调整通常会导致经验上更好的结果,并强制执行一个看似合理的约束。但即使 CycleGAN 论文本身也只简要地将其作为似乎事后的合理化,所以我们没有对其进行深入探讨。

表 9.1 总结了本章中你学到的损失。

表 9.1. 损失
计算 衡量 确保
对抗性损失 L[GAN] (G,D[B],B,A) = E[b~p(b)][logD[B](b)] + E[a~p(a)][log(1-D[B](G[AB](a))] (这是在第五章中介绍的古老的好 NS-GAN。) 与之前的情况一样,损失衡量两个术语:第一个是给定图像是真实图像而不是翻译图像的可能性。第二个是生成器可能欺骗判别器的地方。请注意,这个公式仅适用于D[B],等效的D[A]将在最终损失中体现。 翻译图像看起来真实、清晰,与真实图像无法区分。
循环一致性损失:正向传播 a正向传播之间的差异(用正向传播^([a])表示) 原始域a中的图像和两次翻译后的图像正向传播之间的差异。 原始图像和两次翻译后的图像是相同的。如果这失败了,我们可能没有连贯的映射 A-B-A。
循环一致性损失:反向传播 反向传播 原始域b中的图像和两次翻译后的图像反向传播之间的差异。 原始图像和两次翻译后的图像是相同的。如果这失败了,我们可能没有连贯的映射 B-A-B。
整体损失 L = L[GAN](G,D[B],A,B) + L[GAN](F,D[A],B,A) + λcyc 所有四个损失相结合(由于两个生成器,因此是 2×对抗性)加上循环损失:一个术语中的正向和反向。 整体翻译是逼真的,并且有意义(提供匹配的图片)。
标识损失(在整体损失之外,为了与 CycleGAN 论文的符号保持一致) L[identity] = E[a~p(a)][|| G[BA](a) – a ||] + E[b~p(b)] [|| G[AB] (b) – b ||] B中的图像与GAB 之间的差异以及反之亦然。 CycleGAN 仅在需要时才更改图像的某些部分。

^a

这种符号可能对一些人来说不熟悉,但它代表了两个项目之间的 L1 范数。为了简单起见,你可以将其视为对于每个像素,它与重建图像上相应像素之间的绝对差异。

9.5. 架构

CycleGAN 的设置直接基于 CGAN 架构,本质上是由两个 CGAN 结合在一起——或者,正如 CycleGAN 的作者们自己指出的,是一个自动编码器。回顾第二章,我们有一个输入图像x和重建图像x**,这是在通过潜在空间z*重建后的结果;参见图 9.4。

图 9.4。在这张来自第二章的自动编码器图像中,我们使用了将人类概念(步骤 1)压缩成更紧凑的书面形式在信件中(步骤 2),然后将其扩展到(不完美的)同一概念在别人头脑中的想法(步骤 3)的类比。

图 9.5

为了将这个图转换为 CycleGAN 的世界,a是域 A 中的一个图像,b是域 B 中的一个图像,而图 9.4是重建的 A。然而,在 CycleGAN 的情况下,我们处理的是一个具有相同维度的潜在空间——步骤 2。这恰好是 CycleGAN 必须找到的另一个有意义的域(B)。即使对于自动编码器,潜在空间也只是另一个域,尽管它并不容易解释。

与我们在第二章中了解的内容相比,主要的新概念是引入了对抗性损失。这些以及许多其他自动编码器和 GAN 的混合体本身就是一个活跃的研究领域!因此,这也是一个对感兴趣的研究者来说很好的领域。但就目前而言,将这两个映射视为两个自动编码器:F(G(a))和G(F(b))。我们采用了自动编码器的基本思想——包括一种显式的损失函数,由循环一致性损失所代替——并在此基础上添加了判别器。两个判别器,每个步骤一个,确保两种转换(包括进入潜在空间)在其各自的域中看起来都像真实的图像。

9.5.1. CycleGAN 架构:构建网络

在我们深入 CycleGAN 的实际实现之前,让我们简要地看一下图 9.5 中描述的整体简化实现。有两个流程:在上面的图中,流程 A-B-A 从一个域 A 的图像开始,在下面的图中,流程 B-A-B 从一个域 B 的图像开始。

图 9.5。在这个 CycleGAN 的简化架构中,我们从一个输入图像开始,这个图像要么(1)被送到判别器进行评估,要么(2)被转换到另一个域,由另一个判别器评估,然后转换回来。

图 9.5

(来源:“在 TensorFlow 中理解和实现 CycleGAN”,作者 Hardik Bansal 和 Archit Rathore,2017 年,hardikbansal.github.io/CycleGANBlog/。)

然后,图像遵循两条路径:它(1)被送入判别器以获取我们对其是否真实的判断,并且(2)(i)被送入生成器以将其转换为 B,然后(ii)由判别器 B 评估以查看它是否在域 B 中看起来像真实的图像,最终(iii)转换回 A 以允许我们测量循环损失。

底部图像基本上是顶部图像的一个偏移一个周期,遵循所有相同的根本步骤。我们将使用 apple2orange 数据集,但还有许多其他数据集可用,包括著名的 horse2zebra 数据集,你可以通过稍微修改代码并使用提供的 bash 脚本来下载数据轻松使用。

为了更清晰地总结图 9.5,表 9.2 回顾了所有四个主要网络。

表 9.2. 网络
输入 输出 目标
生成器:从 A 到 B 我们加载来自 A 的真实图片或从 B 到 A 的翻译。 我们将其翻译到域 B。 尝试在域 B 中创建看起来逼真的图像。
生成器:从 B 到 A 我们加载来自 B 的真实图片或从 A 到 B 的翻译。 我们将其翻译到域 A。 尝试在域 A 中创建看起来逼真的图像。
A 域判别器 我们提供一个 A 域的图片——无论是翻译的还是真实的。 图片为真实的概率。 尝试不要被从 B 到 A 的生成器欺骗。
B 域判别器 我们提供一个 B 域的图片——无论是翻译的还是真实的。 图片为真实的概率。 尝试不要被从 A 到 B 的生成器欺骗。

9.5.2. 生成器架构

图 9.6 展示了生成器的架构。我们使用代码中的变量名重新创建了图表,并包括了形状供您参考。这是一个U-Net架构的例子,因为当你以这种方式绘制它,使得每个分辨率都有自己的级别时,网络看起来像字母 U。

图 9.6. 生成器的架构。生成器本身有一个收缩路径(d0 到 d3)和一个扩展路径(u1 到 u4)。收缩和扩展路径有时分别被称为编码器和解码器。

这里有几个需要注意的事项:

  • 我们在编码器中使用标准的卷积层。

  • 从这些中,我们创建跳过连接,以便信息更容易在网络中传播。在图中,这分别用 d0 到 d3 和 u1 到 u4 之间的轮廓和颜色编码表示。你可以看到解码器中一半的块来自这些跳过连接(注意特征图数量翻倍!)^([4])

    正如你将看到的,这仅仅意味着我们将整个块/张量连接到解码器部分的相应颜色张量。

  • 解码器使用反卷积层,加上一个最终的卷积层,将图像上采样到与原始图像等效的大小。

由于生成器具有编码器-解码器架构,因此自编码器对于生成器架构本身也是一个有用的教学工具:

  • 编码器— 第 1 步来自图 9.4:这些是减少每个特征图分辨率(切片)的卷积层。这是收缩路径(d0 到 d3)。

  • 解码器— 第 3 步来自图 9.4:这些是反卷积层(转置卷积),将图像放大回 128 × 128。这是扩展路径(u1 到 u4)。

为了明确,这里的自动编码器模型在两个方面是有用的。首先,整个 CycleGAN 架构可以看作是训练了两个自动编码器。^([[5)] 第二,U-Net 本身有被称为编码器解码器的部分。

参见 Jun-Yan Zhu 等人,2017 年,arxiv.org/pdf/1703.10593.pdf

你可能对降采样和随后的升采样也感到有些困惑,但这只是为了将图像压缩到最有意义的表示,同时还能添加回所有细节。这与自动编码器的推理相同,但现在我们还有一个路径来记住细微差别。这种架构——U-Net 架构——已经在几个领域被实证证明在多种分割任务上表现更好。关键思想是,尽管在降采样过程中我们可以专注于分类和理解大区域,包括更高分辨率的跳过连接可以保留可以准确分割的细节。

在我们的 CycleGAN 实现中,我们将使用带有跳过连接的 U-Net 架构,如图图 9.6 所示,这更易于阅读。然而,许多 CycleGAN 实现使用 ResNet 架构,你可以通过一些额外的工作自己实现。

注意

ResNet 的主要优势是它使用了更少的参数,并在中间引入了一个称为transformer的步骤,该步骤使用残差连接代替我们的编码器-解码器跳过连接。

根据我们的测试,至少在所使用的数据集上,apple2orange 的结果保持不变。我们不是明确地定义了 transformer,而是提供了从卷积层到反卷积层的跳过连接(如图中所示)。我们将在代码中再次提及这些相似之处。现在,只需记住这一点。

9.5.3. 判别器架构

CycleGAN 的判别器基于 PatchGAN 架构——我们将在代码部分深入技术细节。可能令人困惑的一点是,我们从这个判别器得到的不是一个浮点数作为输出,而是一组单通道值,可以将其视为一组迷你判别器,然后我们将它们平均在一起。

最终,这允许 CycleGAN 的设计完全卷积,意味着它可以相对容易地扩展到更高的分辨率。确实,在将视频游戏转换为现实或反之亦然的例子中,CycleGAN 的作者使用了 CycleGAN 的升级版本,由于完全卷积的设计,只有微小的修改。除此之外,判别器应该是对你之前见过的判别器的一个相对直接的实现,但现在有两个。

9.6. GANs 的面向对象设计

我们在 TensorFlow 和面向对象编程(OOP)中始终使用对象,但在我们的代码中通常更功能性地处理架构,因为它们通常是简单的。在 CycleGAN 的情况下,架构是复杂的,因此我们需要一个结构,允许我们继续访问我们定义的原始属性和方法。因此,我们将 CycleGAN 编写为一个具有构建生成器和判别器以及运行训练的方法的 Python 类。

9.7. 教程:CycleGAN

在这个教程中,我们将使用 Keras-GAN 实现,并使用具有 TensorFlow 后端的 Keras。^([6]) 在 Keras 2.2.4 和 TensorFlow 1.12.0 的最新测试中,Keras_contrib是从 hash 46fcdb9384b3bc9399c651b2b43640aa54098e64 安装的。这次,我们必须使用不同的数据集(也为了向你展示,尽管我们在第二章(../Text/kindle_split_011.xhtml#ch02)中的玩笑,但我们确实知道其他数据集)。但出于教育目的,我们将继续使用其中一个较简单的数据集——apple2orange。让我们直接进入正题,按照以下列表所示进行所有我们通常的导入。

查看 Erik Linder-Norén 于 2017 年的 Keras-GAN GitHub 仓库,github.com/eriklindernoren/Keras-GAN

列表 9.1. 导入所有内容
from __future__ import print_function, division
import scipy
from keras.datasets import mnist
from keras_contrib.layers.normalization import InstanceNormalization
from keras.layers import Input, Dense, Reshape, Flatten, Dropout, Concatenate
from keras.layers import BatchNormalization, Activation, ZeroPadding2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D
from keras.models import Sequential, Model
from keras.optimizers import Adam
import datetime
import matplotlib.pyplot as plt
import sys
from data_loader import DataLoader
import numpy as np
import os

如同承诺的那样,我们将使用面向对象的编程风格。在下面的列表中,我们创建了一个包含所有初始化参数的 CycleGAN 类,包括数据加载器。数据加载器定义在我们的书籍 GitHub 仓库中。它简单地加载预处理后的数据。

列表 9.2. 开始 CycleGAN 类
class CycleGAN():
    def __init__(self):
        self.img_rows = 128                                             ***1***
        self.img_cols = 128                                             ***1***
        self.channels = 3                                               ***1***
        self.img_shape = (self.img_rows, self.img_cols, self.channels)

        self.dataset_name = 'apple2orange'                              ***2***
        self.data_loader = DataLoader(dataset_name=self.dataset_name,   ***3***
                                      img_res=(self.img_rows, self.img_cols))

        patch = int(self.img_rows / 2**4)                               ***4***
        self.disc_patch = (patch, patch, 1)

        self.gf = 32                                                    ***5***
        self.df = 64                                                    ***6***

        self.lambda_cycle = 10.0                                        ***7***
        self.lambda_id = 0.9 * self.lambda_cycle                        ***8***

        optimizer = Adam(0.0002, 0.5)
  • 1 输入形状

  • 2 配置数据加载器

  • 3 使用 DataLoader 对象导入预处理后的数据集

  • 4 计算 D(PatchGAN)的输出形状

  • 5 G 的第一层中的过滤器数量

  • 6 D 的第一层中的过滤器数量

  • 7 循环一致性损失权重

  • 8 标识损失权重

两个新术语是 lambda_cyclelambda_id。第二个超参数影响身份损失。CycleGAN 作者本人指出,此值影响变化程度——尤其是在训练过程的早期。7 设置较低值会导致不必要的改变:例如,早期完全反转颜色。我们根据多次重新运行 apple2orange 训练过程选择了此值。通常,这个过程是理论驱动的炼金术。

请参阅 Jun-Yan Zhu 的“pytorch-CycleGAN-and-pix2pix 常见问题解答”,2019 年 4 月,mng.bz/BY58

第一个超参数—lambda_cycle—控制循环一致性损失的执行严格程度。设置此值较高将确保您的原始图像和重建图像尽可能接近。

9.7.1. 构建网络

因此,现在我们已经处理好了基本参数,我们将构建基本网络,如图 清单 9.3 所示。我们将从高层次视图开始,逐步深入。这包括以下内容:

  1. 创建两个判别器 D[A]D[B] 并编译它们

  2. 创建两个生成器:

    1. 实例化 G[AB]G[BA]

    2. 为两个方向的图像输入创建占位符

    3. 将它们都链接到另一个域中的图像

    4. 为在原始域中重建的图像创建占位符

    5. 为两个方向创建身份损失约束

    6. 目前不使判别器的参数可训练

    7. 编译两个生成器

清单 9.3. 构建网络
        self.d_A = self.build_discriminator()                     ***1***
        self.d_B = self.build_discriminator()                     ***1***
        self.d_A.compile(loss='mse',                              ***1***
                         optimizer=optimizer,                     ***1***
                         metrics=['accuracy'])                    ***1***
        self.d_B.compile(loss='mse',                              ***1***
                         optimizer=optimizer,                     ***1***
                         metrics=['accuracy'])                    ***1***

        self.g_AB = self.build_generator()                        ***2***
        self.g_BA = self.build_generator()                        ***2***

        img_A = Input(shape=self.img_shape)                       ***3***
        img_B = Input(shape=self.img_shape)                       ***3***

        fake_B = self.g_AB(img_A)                                 ***4***
        fake_A = self.g_BA(img_B)                                 ***4***
        reconstr_A = self.g_BA(fake_B)                            ***5***
        reconstr_B = self.g_AB(fake_A)                            ***5***
        img_A_id = self.g_BA(img_A)                               ***6***
        img_B_id = self.g_AB(img_B)                               ***6***

        self.d_A.trainable = False                                ***7***
        self.d_B.trainable = False                                ***7***

        valid_A = self.d_A(fake_A)                                ***8***
        valid_B = self.d_B(fake_B)                                ***8***

        self.combined = Model(inputs=[img_A, img_B],              ***9***
                              outputs=[valid_A, valid_B,          ***9***

                                       reconstr_A, reconstr_B,    ***9***
                                       img_A_id, img_B_id])       ***9***
        self.combined.compile(loss=['mse', 'mse',                 ***9***
                                    'mae', 'mae',                 ***9***
                                    'mae', 'mae'],                ***9***
                              loss_weights=[1, 1,
                                            self.lambda_cycle,
     self.lambda_cycle,
                                            self.lambda_id, self.lambda_id],
                              optimizer=optimizer)
  • 1 构建并编译判别器

  • 2 从这里开始,我们构建生成器的计算图。这两行首先构建生成器。

  • 3 从两个域获取图像

  • 4 将图像转换到另一个域

  • 5 将图像转换回原始域

  • 6 图像的身份映射

  • 7 对于结合模型,我们只训练生成器。

  • 8 判别器确定转换图像的有效性

  • 9 结合模型训练生成器欺骗判别器

最后一点需要从前面的代码中澄清:combined 模型的输出以六个列表的形式出现。这是因为我们总是得到有效性(来自判别器)、重建和身份损失——一个用于 A-B-A,一个用于 B-A-B 循环——因此是六个。前两个是平方误差,其余是平均绝对误差。相对权重受前面描述的 lambda 因素影响。

9.7.2. 构建生成器

接下来,我们在 清单 9.4 中构建生成器代码,该代码使用我们在 第 9.5.2 节 中描述的跳跃连接。这是 U-Net 架构。这个架构比一些实现中使用的 ResNet 架构更简单。在我们的生成器函数中,我们首先定义辅助函数:

  1. conv2d() 函数定义为如下:

    1. 标准的 2D 卷积层

    2. Leaky ReLU 激活

    3. 实例归一化^([8])

      实例归一化类似于第四章中的批归一化,除了我们不是基于整个批次的全部信息进行归一化,而是对每个通道中的每个特征图分别进行归一化。实例归一化通常会产生更好的图像质量,适用于风格迁移或图像到图像翻译等任务——这正是 CycleGAN 所需要的!

  2. 定义deconv2d()函数作为一个转置卷积(即 反卷积)层,它执行以下操作:

    这里,转置卷积是——有人认为——一个更准确的术语。然而,只需将其视为卷积的相反,或反卷积。

    1. 提升输入层input_layer

    2. 如果我们设置了 dropout 率,可能会应用 dropout

    3. 总是应用InstanceNormalization

    4. 更重要的是,在其输出层和从图 9.4 的下采样部分对应维度的层之间创建跳过连接

    注意

    在步骤 2d 中,我们使用简单的UpSampling2D,这不是一个学习参数,而是使用最近邻插值。

    然后我们创建实际的生成器:

  3. 将输入(128 × 128 × 3)分配给d0

  4. 通过卷积层d1运行,得到 64 × 64 × 32 层。

  5. d1(64 × 64 × 32)应用conv2d得到 32 × 32 × 64(d2)。

  6. d2(32 × 32 × 64)应用conv2d得到 16 × 16 × 128(d3)。

  7. d3(16 × 16 × 128)应用conv2d得到 8 × 8 × 256(d4)。

  8. u1:上采样d4并在d3u1之间创建跳过连接。

  9. u2:上采样u1并在d2u2之间创建跳过连接。

  10. u3:上采样u2并在d1u3之间创建跳过连接。

  11. u4:使用常规上采样得到 128 × 128 × 64 图像。

  12. 使用常规的 2D 卷积去除额外的特征图,只得到 128 × 128 × 3(高度 × 宽度 × 颜色通道)

列表 9.4. 构建生成器
    def build_generator(self):
        """U-Net Generator"""

        def conv2d(layer_input, filters, f_size=4):
            """Layers used during downsampling"""
            d = Conv2D(filters, kernel_size=f_size,
                       strides=2, padding='same')(layer_input)
            d = LeakyReLU(alpha=0.2)(d)
            d = InstanceNormalization()(d)
            return d

        def deconv2d(layer_input, skip_input, filters, f_size=4,
            dropout_rate=0):
            """Layers used during upsampling"""
            u = UpSampling2D(size=2)(layer_input)
            u = Conv2D(filters, kernel_size=f_size, strides=1,
                       padding='same', activation='relu')(u)
            if dropout_rate:
                u = Dropout(dropout_rate)(u)
            u = InstanceNormalization()(u)
            u = Concatenate()([u, skip_input])
            return u

        d0 = Input(shape=self.img_shape)     ***1***

        d1 = conv2d(d0, self.gf)             ***2***
        d2 = conv2d(d1, self.gf * 2)         ***2***
        d3 = conv2d(d2, self.gf * 4)         ***2***
        d4 = conv2d(d3, self.gf * 8)         ***2***

        u1 = deconv2d(d4, d3, self.gf * 4)   ***3***
        u2 = deconv2d(u1, d2, self.gf * 2)   ***3***
        u3 = deconv2d(u2, d1, self.gf)       ***3***

        u4 = UpSampling2D(size=2)(u3)
        output_img = Conv2D(self.channels, kernel_size=4,
                            strides=1, padding='same', activation='tanh')(u4)

        return Model(d0, output_img)
  • 1 图像输入

  • 2 下采样

  • 3 上采样

9.7.3. 构建判别器

现在是判别器方法,它使用一个辅助函数来创建由 2D 卷积、LeakyReLU和可选的InstanceNormalization组成的层。

我们以以下方式应用这些层,如图列表 9.5 所示:

  1. 我们将输入图像(128 × 128 × 3)分配给d1(64 × 64 × 64)。

  2. 我们将d1(64 × 64 × 64)分配给d2(32 × 32 × 128)。

  3. 我们将d2(32 × 32 × 128)分配给d3(16 × 16 × 256)。

  4. 我们将d3(16 × 16 × 256)分配给d4(8 × 8 × 512)。

  5. 我们将d4(8 × 8 × 512)通过conv2d展平为 8 × 8 × 1。

列表 9.5. 构建判别器
def build_discriminator(self):

        def d_layer(layer_input, filters, f_size=4, normalization=True):
            """Discriminator layer"""
            d = Conv2D(filters, kernel_size=f_size,
                       strides=2, padding='same')(layer_input)
            d = LeakyReLU(alpha=0.2)(d)
            if normalization:
                d = InstanceNormalization()(d)
            return d

        img = Input(shape=self.img_shape)

        d1 = d_layer(img, self.df, normalization=False)
        d2 = d_layer(d1, self.df * 2)
        d3 = d_layer(d2, self.df * 4)
        d4 = d_layer(d3, self.df * 8)

        validity = Conv2D(1, kernel_size=4, strides=1, padding='same')(d4)

        return Model(img, validity)

9.7.4. 训练 CycleGAN

所有网络编写完毕后,我们现在将实现创建训练循环的方法。对于 CycleGAN 训练算法,每个训练迭代的细节如下。

CycleGAN 训练算法

对于每个训练迭代执行

  1. 训练判别器:

    1. 从每个域(imgs[A]imgs[B])中随机抽取一个图像的小批量。

    2. 使用生成器 G[AB]imgs[A] 转换为域 B,反之亦然,使用 G[BA]

    3. 计算 D[A](imgs[A], 1) 和 D[A](G[BA](imgs[B]), 0) 以获取 A 中真实图像和从 B 转换的图像的损失。然后将这两个损失相加。D[A] 中的 1 和 0 作为标签。

    4. 计算 D[B](imgs[B], 1) 和 D[B](G[AB](imgs[A]), 0) 以获取 B 中真实图像和从 A 转换的图像的损失。然后将这两个损失相加。D[B] 中的 1 和 0 作为标签。

  2. 将步骤 c 和 d 的损失相加以获得总判别器损失。训练生成器:

    1. 我们使用组合模型来

      • 输入来自域 A (imgs[A]) 和 B (imgs[B]) 的图像

      • 输出如下

        1. A 的有效性:D[A](G[BA](imgs[B]))

        2. B 的有效性:D[B](G[AB](imgs[A]))

        3. 重建 A:G[BA](G[AB](imgs[A]))

        4. 重建 B:G[AB](G[BA](imgs[B]))

        5. A 的身份映射:G[BA](imgs[A]))

        6. B 的身份映射:G[AB](imgs[B]))

    2. 然后,我们根据循环一致性损失、身份损失和对抗损失更新两个生成器的参数

      • 标量(判别器概率)的均方误差 (MSE)

      • 均方绝对误差 (MAE) 用于图像(无论是重建的还是身份映射的)

结束 for

下面的列表实现了这个 CycleGAN 训练算法。

列表 9.6. 训练 CycleGAN
    def train(self, epochs, batch_size=1, sample_interval=50):

        start_time = datetime.datetime.now()

        valid = np.ones((batch_size,) + self.disc_patch)                  ***1***
        fake = np.zeros((batch_size,) + self.disc_patch)

        for epoch in range(epochs):
            for batch_i, (imgs_A, imgs_B) in enumerate(
                self.data_loader.load_batch(batch_size)):

                fake_B = self.g_AB.predict(imgs_A)                        ***2***
                fake_A = self.g_BA.predict(imgs_B)                        ***2***

                dA_loss_real = self.d_A.train_on_batch(imgs_A, valid)     ***3***
                dA_loss_fake = self.d_A.train_on_batch(fake_A, fake)      ***3***
                dA_loss = 0.5 * np.add(dA_loss_real, dA_loss_fake)        ***3***
                                                                          ***3***
                dB_loss_real = self.d_B.train_on_batch(imgs_B, valid)     ***3***
                dB_loss_fake = self.d_B.train_on_batch(fake_B, fake)      ***3***
                dB_loss = 0.5 * np.add(dB_loss_real, dB_loss_fake)        ***3***

                d_loss = 0.5 * np.add(dA_loss, dB_loss)                   ***4***

                g_loss = self.combined.train_on_batch([imgs_A, imgs_B],   ***5***
                                                      [valid, valid,
                                                       imgs_A, imgs_B,
                                                       imgs_A, imgs_B])
                if batch_i % sample_interval == 0:                        ***6***
                    self.sample_images(epoch, batch_i)                    ***7***
  • 1. 对抗损失真实值

  • *2. 现在我们开始训练判别器。这些行将图像转换为相反的域。

  • 3. 训练判别器(原始图像 = 真实 / 转换 = 假)

  • 4. 总判别器损失

  • 5. 训练生成器

  • 6. 如果达到保存间隔 => 保存生成的图像样本

  • *7. 这个函数与您遇到过的类似,并在 GitHub 仓库中明确说明。

9.7.5. 运行 CycleGAN

我们已经编写了所有这些复杂的代码,现在准备好实例化一个 CycleGAN 对象,并查看一些从采样图像中获得的结果:

gan = CycleGAN()
gan.train(epochs=100, batch_size=64, sample_interval=10)

图 9.7 展示了我们辛勤工作的部分结果。

图 9.7. 苹果转换为橙子,橙子转换为苹果。这些是我们 Jupyter 笔记本中直接显示的结果。(结果可能因随机种子、TensorFlow 和 Keras 的实现以及超参数而略有不同。)

9.8. 扩展、增强和应用

当你运行这些结果时,我们希望你会像我们一样印象深刻。由于绝对惊人的结果,许多研究人员纷纷改进这项技术。本节详细介绍了 CycleGAN 的扩展,然后讨论了一些 CycleGAN 的应用。

9.8.1. 增强 CycleGAN

“从未配对数据中学习多对多映射的增强 CycleGAN”是对标准 CycleGAN 的一个真正巧妙的扩展,它在翻译过程中注入了潜在空间信息。在斯德哥尔摩的 ICML 2018 会议上提出,增强 CycleGAN 为我们提供了额外的变量,这些变量驱动着生成过程。^([10)] 就像我们在条件 GAN 的情况下使用潜在空间一样,我们可以在 CycleGAN 的设置中使用它,超越 CycleGAN 本身所做的工作。

^([10])

参见 Ehsan Hosseini-Asl 的“增强循环对抗学习用于低资源域适应”,2019 年,arxiv.org/pdf/1807.00374.pdf.

例如,如果我们有 A 域中鞋的轮廓,我们可以在 B 域中生成一个样本,其中相同类型的鞋是蓝色的。在传统的 CycleGAN 情况下,它将始终是蓝色。但现在,有了我们可用的潜在变量,它可以变成橙色、黄色,或者我们选择的任何颜色。

这也是一个思考原始 CycleGAN 局限性的有用框架:因为我们没有给出任何额外的种子参数(例如额外的潜在向量 z),所以我们无法控制或改变输出结果。如果从一个特定的手提包轮廓中得到一个橙色的图像,它将始终是橙色。增强 CycleGAN 让我们对结果有了更多的控制,如图 9.8 所示。

图 9.8. 在增强 CycleGAN 的信息流中,我们有潜在向量 Za 和 Zb,它们与图像输入一起作为生成器的种子,有效地将问题简化为两个联合的 CGAN。这使我们能够控制生成。

图片

(来源:“从未配对数据中学习多对多映射的增强 CycleGAN”,作者:Amjad Almahairi 等,2018 年,arxiv.org/abs/1802.10151.)

9.8.2. 应用

在 CycleGAN(或 CycleGAN 启发式)出现的不长的时间里,已经提出了许多应用。它们通常围绕创建模拟的虚拟环境,然后使它们变得逼真。例如,想象一下,如果你需要为一家自动驾驶汽车公司提供更多训练数据:只需在 Unity 或 GTA 5 图形引擎中模拟它,然后使用 CycleGAN 来转换数据。

如果你需要重现特定风险情况,而这些情况既昂贵又耗时(例如,汽车相撞,或消防车急速赶往目的地),但你需要在数据集中拥有它们,这尤其有效。对于一家自动驾驶汽车公司来说,这可能非常有用,以平衡数据集中罕见的风险情况,而正确的行为则更为重要。

这种框架的一个例子是 Cycle Consistent Adversarial Domain Adaptation (CyCADA).^([11]) 不幸的是,对这个工作方式的完整解释超出了本章的范围。这是因为还有许多这样的框架:一些甚至尝试在语言、音乐或其他形式的域适应中使用 CycleGAN。为了给你一个复杂性的感觉,图 9.9 展示了 CyCADA 的架构和设计。

¹¹

见 Judy Hoffman 等人于 2017 年发表的“CyCADA: Cycle-Consistent Adversarial Domain Adaptation”,arxiv.org/pdf/1711.03213.pdf

图 9.9。这种结构应该与之前的内容有些熟悉,所以希望这一章至少能给你一个良好的开端。有一点需要指出:我们现在有一个额外的步骤,包括标签和语义理解,这为我们提供了所谓的任务损失。这使我们能够检查生成的图像的语义意义。

概述

  • 由于需要完美的成对图像,图像到图像的翻译框架通常很难训练;CycleGAN 通过将其变成一个无配对域翻译来解决这一问题。

  • CycleGAN 有三个损失函数:

    • 循环一致性,它衡量原始图像与转换到不同域并再次转换回来的图像之间的差异

    • 对抗性,确保生成图像的逼真度

    • 身份,它保留图像的色彩空间

  • 两个生成器使用 U-Net 架构,两个判别器使用基于 PatchGAN 的架构。

  • 我们实现了 CycleGAN 的面向对象设计,并使用它将苹果转换为橙子。

  • CycleGAN 的实际应用包括自动驾驶汽车训练以及在翻译过程中创建不同风格图像的扩展。

第三部分。从这里走向何方

第三部分 探索了你可以应用你在第一部分和第二部分中学到的关于 GANs 及其实现的一些实际用例和其他领域:

  • 第十章 讨论了对抗样本(故意欺骗分类器犯错的手段),这是一个具有极大实践和理论重要性的领域。

  • 第十一章探讨了 GANs 在医学和时尚领域的实际应用,其实现使用了本书中涵盖的 GAN 变体。

  • 第十二章概述了 GANs 及其应用的伦理考量。我们还提到了那些对继续探索这一领域感兴趣的读者所关注的新兴 GAN 技术。

第十章。对抗样本

本章涵盖

  • 一个在 GANs 之前出现且有着交织历史的迷人研究领域

  • 计算机视觉环境中的深度学习方法

  • 我们自己的带有真实图像和噪声的对抗样本

在本书的整个过程中,你已经将生成对抗网络(GANs)作为一个直观的概念来理解。然而,在 2014 年,GANs 似乎是一个巨大的信仰飞跃,尤其是对于那些不熟悉对抗样本这一新兴领域的人来说,包括伊恩·古德费洛(Ian Goodfellow)和其他人在这一领域的工作。1 这一章深入探讨了对抗样本——专门构建的样本,使其他分类算法失败。

¹

请参阅 Christian Szegedy 等人于 2014 年发表的“神经网络的有趣特性”,arxiv.org/pdf/1312.6199.pdf

我们还讨论了它们与生成对抗网络(GANs)的联系,以及为什么和如何对抗性学习在机器学习(ML)中仍然是一个未解决的问题——这是当前方法的一个重要但很少被讨论的缺陷。尽管对抗样本在机器学习的鲁棒性、公平性和(网络安全)中扮演着重要角色,但这仍然是事实。

我们无法否认,在过去五年中,我们在机器学习匹配甚至超越人类水平性能的能力上取得了实质性进展——例如,在计算机视觉(CV)分类任务或玩游戏的能力。2 然而,仅仅关注指标和 ROC 曲线 3 对于我们理解(a)神经网络做出决策的原因(它们是如何工作的)以及(b)它们容易犯的错误是不够的。这一章触及了第一个问题,并深入探讨了第二个问题。在我们开始之前,应该指出的是,尽管这一章几乎完全涉及 CV 问题,但对抗样本已经在文本甚至人类等多样化的领域中得到了识别。4]

²

视觉分类任务中构成人类水平表现的因素是一个复杂的话题。然而,至少在例如 Dota 2 和围棋这样的游戏中,人工智能已经以相当大的优势击败了人类专家。

³

一个 接收者操作特征(ROC)曲线 解释了假阳性和假阴性之间的权衡。我们也在第二章中遇到了它们。arxiv.org/abs/1901.06796。更多细节,维基百科有很好的解释。

参见 Wei Emma Zhang 等人撰写的“自然语言处理中深度学习模型的对抗攻击:综述”,2019 年,arxiv.org/abs/1901.06796。另见 Gamaleldin F. Elsayed 等人撰写的“欺骗计算机视觉和有限时间人类的对抗示例”,2018 年,arxiv.org/abs/1802.08195

首先,当我们谈论神经网络的性能时,我们经常读到它们的错误率在大型 ImageNet 数据集上低于人类。这个经常被引用的统计数据——它更多的是一个学术玩笑而不是其他——掩盖了隐藏在平均数背后的性能差异。虽然人类的错误率往往是由他们无法区分数据集中突出显示的不同品种的狗所驱动的,但机器学习失败的情况则更为严重。经过进一步调查,对抗样本应运而生。

与人类不同,CV 算法在处理本质上非常不同且可能接近训练数据的问题上存在困难。因为算法必须对每张可能的图片做出预测,它必须在训练数据中看到的孤立且相距甚远的单个实例之间进行外推,即使我们有很多这样的实例。

当我们训练了 Inception V3 和 VGG-19 这样的网络时,我们发现了一种令人惊叹的方法,可以在训练数据周围的薄流形上实现图像分类。但当人们试图在这些算法的分类能力上找漏洞时,他们发现了一个宇宙般大小的陨坑——当前的机器学习算法很容易被微小的扭曲所欺骗。迄今为止,几乎所有主要的成功机器学习算法都或多或少地存在这一缺陷,而且确实,有些人推测这正是机器学习之所以有效的原因。

注意

在监督设置中,想想我们的训练集。我们有一个训练流形——这是一个描述我们例子所在的高维分布的术语。例如,我们的 300 × 300 像素图像生活在 270,000 维的空间中(300 × 300 × 3 种颜色)。这使得训练变得非常复杂。

10.1. 对抗样本的背景

首先,我们想简要谈谈为什么我们把这一章放在书的末尾:

  • 使用对抗性示例,我们通常试图生成新的示例来欺骗我们现有的系统,使其错误分类输入。我们通常要么作为邪恶的攻击者,要么作为研究人员来观察我们的系统将如何表现出鲁棒性。对抗性示例与生成对抗网络(GANs)的关系非常密切,尽管存在一些重要差异。

  • 这将让你了解为什么生成对抗网络(GANs)可能如此难以训练,以及为什么我们现有的系统如此脆弱。

  • 对抗性示例允许从生成对抗网络(GANs)中获得不同的一组应用,我们希望至少向您介绍它们的基本能力。

在应用方面,对抗性示例有几个有趣的原因:

  • 正如讨论的那样,对抗性示例可以用于恶意目的,因此在关键系统中进行鲁棒性测试很重要。如果攻击者能够轻易地欺骗面部识别系统以获取你的手机访问权限怎么办?

  • 它们帮助我们理解机器学习公平性——这是一个日益重要的主题。我们可以使用对抗性学习得到的表示,这些表示对分类很有用,但不会让攻击者恢复受保护的事实,这可能是确保我们的机器学习不会歧视任何人的最佳方法之一。

  • 类似地,我们可以使用对抗性学习来保护个人敏感信息(可能是医疗或财务信息)的隐私。在这种情况下,我们只是关注个人信息不可恢复的问题。

根据当前的研究,了解对抗性示例是开始理解对抗性防御的唯一方法,因为大多数论文都是从描述他们所防御的攻击类型开始的,然后才尝试解决这些问题。在撰写本书时,没有通用的防御方法可以抵御所有类型的攻击。但这是否是研究它们的好理由取决于你对对抗性示例的看法。我们决定不详细讨论防御——在本章末尾的高层次思想之上——因为超出本书的范围。

10.2. 谎言、该死的谎言和分布

要真正理解对抗性示例,我们必须回到计算机视觉分类任务的领域——部分是为了了解这项任务有多困难。回想一下,从原始像素到最终能够对图像集进行分类是一项挑战。

这部分是因为,为了有一个真正可泛化的算法,我们必须对训练集中没有看到过的数据进行合理的预测。此外,当前图像与同一类训练集中最接近的图像在像素级上的差异很大,即使我们稍微改变拍摄图片的角度也是如此。

当我们拥有 300 × 300 像素大小的 RGB 空间中 10 万个图像的培训集时,我们必须以某种方式处理 270,000 个维度。当我们考虑所有可能的图像(不是我们实际观察到的,而是可能发生的),每个维度的像素值与其他维度是独立的,因为我们总是可以通过掷一个假设的 256 面骰子 270,000 次来生成一个有效的图片。因此,在 8 位色彩空间中,我们理论上拥有 256^(270,000)个示例(一个有 650,225 位的数字)。

我们需要大量的示例来覆盖这个空间的 1%。当然,这些图像中的大多数都没有意义。通常,我们的训练集比这稀疏得多,因此我们需要我们的算法使用这些相对有限的数据进行训练,以便外推到他们尚未看到的所有区域。这是因为算法最有可能看到的东西与我们训练集中的东西相差甚远。

注意

拥有 10 万个示例通常被认为是深度学习算法真正开始发光的最低标准。

我们理解算法必须有意义地泛化;它们必须能够有意义地填补它们尚未看到任何示例的巨大空间。计算机视觉算法之所以有效,主要是因为它们可以为大量缺失的概率提供良好的猜测,但它们的优点也是它们的最大弱点。

10.3. 训练的使用与滥用

在本节中,我们介绍两种思考对抗样本的方法——一种是从第一原理出发,另一种是通过类比。思考对抗样本的第一种方法是从小型机器学习分类的训练方式开始。记住,这些网络有数百万个参数。在整个训练过程中,我们更新其中的一些参数,以便类别与训练集中提供的标签相匹配。我们需要找到恰到好处的参数更新,这正是随机梯度下降(SGD)允许我们做到的。

现在回想一下在你知道很多关于 GANs 之前简单的分类器时代。这里我们有一种可学习的分类函数 f[θ](x)(例如,深度神经网络,或 DNN),它由 θ(DNN 的参数)参数化,并接受 x(例如,图像)作为输入,产生一个分类 。在训练时,我们然后取 并将其与真实的 y 进行比较,这就是我们如何得到损失 (L)。然后我们更新 f[θ](x) 的参数,使得损失最小化。方程式 10.1,10.2,和 10.3 总结了这些。^([[5])

请记住,这只是一个快速总结,我们不得不跳过一些细节,所以如果你能指出它们——太好了。如果你不能,我们建议阅读像 François Chollet 的《用 Python 进行深度学习》(Manning, 2017)这样的书籍,以复习具体细节。

equation 10.1.

equation 10.2.

图片

equation 10.3。

图片

从本质上讲,我们已经将预测定义为神经网络在输入一个示例后的输出(方程式 10.1)。损失是真实标签和预测标签之间的一种差异(方程式 10.2)。因此,整体问题被表述为尝试最小化真实标签和预测标签之间的差异,这些差异构成了给定示例的预测,即 DNN 的参数(方程式 10.3)。

所有这些都进行得很好,但我们实际上如何最小化我们的分类损失?我们如何解决方程式 10.3 中表述的优化问题?我们通常使用基于 SGD 的方法来获取批次的x;然后我们计算损失函数相对于当前参数(θ[t])的导数,并将其乘以我们的学习率(α),这构成了我们的新参数(θ[t] [+ 1])。参见方程式 10.4。

equation 10.4。

图片

这是你能找到的关于深度学习最快的介绍。但现在你有了这个背景,想想这个强大的工具(SGD)是否也可以用于其他目的。例如,当我们向上而不是向下移动损失空间时会发生什么?结果发现,最大化错误而不是最小化错误要容易得多,但也很重要。而且像许多伟大的发现一样,它开始时是一个看似的 bug,后来变成了一个 hack:如果我们开始更新像素而不是权重会怎样?如果我们恶意地更新它们,就会发生对抗样本。

一些人对 SGD 的快速回顾可能感到困惑,所以让我们提醒自己一个典型的损失空间在图 10.1 中可能看起来是什么样子。

图 10.1。记住,这是我们用深度学习算法可以实际得到的损失值类型。在左侧,你有等损失值的 2D 等高线,在右侧,你有损失空间的 3D 渲染。还记得第六章中的登山类比吗?

图片

(来源:“可视化神经网络损失景观”,作者 Tom Goldstein 等,2018 年,github.com/tomgoldstein/loss-landscape。)

考虑对抗样本的第二个有用的(尽管不完美)心智模型是通过类比。你可以将对抗样本视为类似于我们在前两章中遇到的那些条件生成对抗网络(Conditional GANs)。在对抗样本中,我们是在整个图像上进行条件化,并试图生成一个域转换或类似图像,但这个域能够欺骗分类器。“生成器”可以是一个简单的随机梯度上升算法,它只是调整图像以欺骗其他分类器。

无论哪种方式对你来说更有意义,我们现在直接深入探讨对抗性示例及其外观。它们是通过观察如何容易错误分类这些修改后的图像而被发现的。实现这一目标的第一种方法是最简单的快速符号梯度方法(FSGM),它就像我们之前的描述一样简单。

你从梯度更新(方程 10.4)开始,查看符号,然后在相反方向上迈出小一步。事实上,图像经常看起来(几乎)完全相同!一张图片胜过千言万语,向您展示所需的噪声有多少;请参阅图 10.2。

图 10.2. 一点噪声就能产生很大的差异。中间的图片应用了噪声(差异),(右边的图片)。当然,右边的图片被大幅放大——大约 300 倍——并移动,以便它可以创建一个有意义的图像。

现在我们在这个未修改的假日图像上运行一个预训练的 ResNet-50 分类器,并检查前三个预测结果,如表 10.1 所示;请鼓掌。

表 10.1. 原始图像预测
排序 类别 置信度
第一 山顶帐篷 0.6873
第二 岩石突出部 0.0736
第三 山谷 0.0717

最前面的三个都是合理的,其中mountain_tent占据首位,正如它应该的那样。表 10.2 显示了对抗性图像预测。前三个完全错过了mountain_tent,尽管有些建议至少与户外相符,但修改后的图像显然不是一座悬索桥。

表 10.2. 对抗性图像预测
排序 类别 置信度
第一 火山 0.5914
第二 悬索桥 0.1685
第三 山谷 0.0869

这就是我们可以在仅约 200 个像素值(相当于将一个几乎黑色的像素变成几乎白色的像素)的预算内扭曲预测的程度——在整个图像上分散。

有一点令人害怕的是,创建整个示例所需的代码如此之少。在本章中,我们将使用一个名为foolbox的神奇库,它提供了许多方便的方法来创建对抗性示例。无需多言,让我们深入探讨。我们首先进行我们熟悉的导入,包括foolbox,这是一个专门设计来使对抗性攻击更简单的库。

列表 10.1. 我们可靠的导入
import numpy as np
from keras.applications.resnet50 import ResNet50
from foolbox.criteria import Misclassification, ConfidentMisclassification
from keras.preprocessing import image as img
from keras.applications.resnet50 import preprocess_input, decode_predictions
import matplotlib.pyplot as plt
import foolbox
import pprint as pp
Import keras
%matplotlib inline

接下来,我们定义一个方便的函数来加载更多图像。

列表 10.2. 辅助函数
def load_image(img_path: str):
  image = img.load_img(img_path, target_size=(224, 224))
  plt.imshow(image)
  x = img.img_to_array(image)
  return x

image = load_image('DSC_0897.jpg')

接下来,我们必须设置 Keras 以注册我们的模型并从 Keras 方便函数下载 ResNet-50。

列表 10.3. 创建表 10.1 和 10.2
keras.backend.set_learning_phase(0)                                      ***1***
kmodel = ResNet50(weights='imagenet')
preprocessing = (np.array([104, 116, 123]), 1)

fmodel = foolbox.models.KerasModel(kmodel, bounds=(0, 255),              ***2***
     preprocessing=preprocessing)                                        ***2***

to_classify = np.expand_dims(image, axis=0)                              ***3***
preds = kmodel.predict(to_classify)                                      ***4***
print('Predicted:', pp.pprint(decode_predictions(preds, top=20)[0]))
label = np.argmax(preds)                                                 ***5***

image = image[:, :, ::-1]                                                ***6***
attack = foolbox.attacks.FGSM(fmodel, threshold=.9,                      ***7***
     criterion=ConfidentMisclassification(.9))                           ***7***
adversarial = attack(image, label)                                       ***8***

new_preds = kmodel.predict(np.expand_dims(adversarial, axis=0))          ***9***
print('Predicted:', pp.pprint(decode_predictions(new_preds, top=20)[0]))
  • 1 实例化模型

  • 2 从 Keras 模型创建 foolbox 模型对象

  • 3 将图像调整为(1, 224, 224, 3),以便它适合 ResNet-50,因为 ResNet-50 期望预测图像为批量形式。

  • 4 调用 predict 并打印结果。

  • 5 获取最高数字的索引,作为后续使用的标签

  • 6 ::-1 反转颜色通道,因为 Keras ResNet-50 期望 BGR 而不是 RGB。

  • 7 创建攻击对象,设置高误分类标准

  • 8 对源图像应用攻击

  • 9 获取对抗图像的新预测

这就是使用这些例子有多简单!现在你可能正在想,也许这只是 ResNet-50 在这些例子中受到影响。好吧,我们有一些坏消息要告诉你。ResNet 不仅在我们测试本章的各种代码设置时证明是最难被打破的分类器,而且在 DAWNBench 的每个 ImageNet 类别(这是 DAWNBench 中 CV 类别中最具挑战性的任务)中都是无可争议的赢家,如图 10.3 所示。图 10.3.^([6])

请参阅 DAWNBench 上的“Image Classification on ImageNet”,dawn.cs.stanford.edu/benchmark/#imagenet

图 10.3. DAWNBench 是一个很好的地方,可以查看当前最先进的模型和 ResNet-50 的统治地位,至少截至 2019 年 7 月初。

图片

但对抗样本的最大问题是它们的普遍性。对抗样本不仅限于深度学习,而且可以迁移到不同的机器学习技术。如果我们针对一种技术生成一个对抗样本,那么它在另一个我们试图攻击的模型上也能合理地工作,如图 10.4 所示。

图 10.4. 这里显示的数字表示为该行中用来欺骗分类器的对抗样本的百分比,同时也欺骗了该列的分类器。这些方法是深度神经网络(DNNs)、逻辑回归(LR)、支持向量机(SVM)、决策树(DT)、最近邻(kNN)和集成(Ens.)。

图片

(来源:“机器学习中的可迁移性:使用对抗样本从现象到黑盒攻击”,Nicolas Papernot 等人,2016 年,arxiv.org/pdf/1605.07277.pdf

10.4. 信号和噪声

更糟糕的是,许多对抗样本构建起来非常简单,我们可以用从np.random.normal中采样的高斯噪声同样容易地欺骗分类器。另一方面——为了支持我们之前关于 ResNet-50 是一个相当鲁棒的架构的观点——我们将向你展示其他架构对此问题的影响更大。

图 10.5 显示了在纯高斯噪声上运行 ResNet-50 的结果。然而,我们可以通过对噪声本身进行对抗攻击来查看我们的图像可能被误分类到什么程度——相当快地。

图 10.5. 很明显,在大多数情况下,我们不会在简单地采样的噪声上得到一个自信的分类作为错误类别。所以这是 ResNet-50 的加分项。在左侧,我们包括了我们所使用的均值和方差,以便你可以看到它们的影响。

图片 10.5_ 替代

在列表 10.4 中,我们将使用一种投影梯度下降(PGD)攻击,如图图 10.6 所示。尽管这仍然是一种简单的攻击,但它值得进行高级解释。与之前的攻击不同,我们现在无论走向何方都会迈出一步——甚至“无效”的像素值——然后将其投影回可行空间。现在让我们将 PGD 攻击应用于图 10.7 中的高斯噪声,并运行 ResNet-50 以查看我们的表现。

图 10.6. 投影梯度下降在任意方向上迈出一步,然后使用投影找到点集中最近的等效点。在这种情况下,我们试图确保最终得到一个有效的图片:我们取一个示例 x(k),并对其采取最优步骤到 y^((k + 1)),然后将其投影到一个有效的图像集 x^((k + 1))。

图片 10.6

图 10.7. 当我们在对抗噪声上运行 ResNet-50 时,我们得到一个不同的故事:在应用 PGD 攻击后,大多数项目都被错误分类——仍然是一个简单的攻击。

图片 10.7_ 替代

为了证明大多数架构甚至更差,我们将研究 Inception V3——一个在 CV 社区中赢得声誉的架构。的确,这个网络被认为非常可靠,我们在第五章中提到了它。在图 10.8 中,你可以看到即使是产生了 inception score 的东西在简单的例子上仍然失败。为了消除任何疑虑,Inception V3 仍然是最好的预训练网络之一,并且确实具有超人的准确率。

图 10.8. Inception V3 应用于高斯噪声。请注意,我们没有使用任何攻击;这种噪声只是从分布中采样的。

图片 10.8_ 替代

备注

这只是普通的 Gaussian 噪声。你可以在代码中亲自看到没有应用任何对抗步骤。当然,你可以争论噪声可以预处理得更好。但即使是这一点也是一个巨大的对抗弱点。

如果你和我们一样,你可能在想,不可能,我想亲自看看。好吧,现在我们给你代码来重现这些图。因为每个代码都很相似,我们只看一次,并承诺下次会有更 DRY 的代码。

备注

关于不要重复自己(DRY)代码的解释,请参阅维基百科en.wikipedia.org/wiki/Don%27t_repeat_yourself

列表 10.4. 高斯噪声
fig = plt.figure(figsize=(20,20))
sigma_list = list(max_vals.sigma)                                          ***1***
mu_list = list(max_vals.mu)
conf_list = []

def make_subplot(x, y, z, new_row=False):                                  ***2***
    rand_noise = np.random.normal(loc=mu, scale=sigma, size=(224,224, 3))  ***3***
    rand_noise = np.clip(rand_noise, 0, 255.)                              ***4***
    noise_preds = kmodel.predict(np.expand_dims(rand_noise, axis=0))       ***5***
    prediction, num = decode_predictions(noise_preds, top=20)[0][0][1:3]   ***6***
    num = round(num * 100, 2)
    conf_list.append(num)
    ax = fig.add_subplot(x,y,z)                                            ***7***
    ax.annotate(prediction, xy=(0.1, 0.6),
            xycoords=ax.transAxes, fontsize=16, color='yellow')
    ax.annotate(f'{num}%' , xy=(0.1, 0.4),
            xycoords=ax.transAxes, fontsize=20, color='orange')
    if new_row:
        ax.annotate(f'$\mu$:{mu}, $\sigma$:{sigma}' ,
                    xy=(-.2, 0.8), xycoords=ax.transAxes,
                    rotation=90, fontsize=16, color='black')
    ax.imshow(rand_noise / 255)                                            ***8***
    ax.axis('off')

for i in range(1,101):                                                     ***9***
    if (i-1) % 10==0:
        mu = mu_list.pop(0)
        sigma = sigma_list.pop(0)
        make_subplot(10,10, i, new_row=True)
    else:
        make_subplot(10,10, i)

plt.show()
  • 1 以浮点数形式表示均值和方差列表

  • 2 生成图 10.8 的核心功能

  • 3 为每个均值和方差采样噪声

  • 4 只允许 0-255 像素值

  • 5 获得我们的第一个预测

  • 6 分别获取预测类别和置信度

  • 7 设置为注释代码图 10.8 并添加注释和文本

  • 8 除以 255 将[0, 255]转换为[0, 1]

  • 9 主循环允许我们将子图插入到图中

10.5. 并非所有希望都已破灭

现在,有些人开始担心对抗性示例的安全影响。然而,重要的是要从一个假设的攻击者的有意义的视角来看待这个问题。如果攻击者可以稍微改变每个像素,为什么不改变整个图像呢?^([7]) 为什么不输入一个完全不同的图像呢?为什么传入的示例必须是不易察觉的——而不是明显不同的?

请参阅 Justin Gilmer 等人撰写的“对抗性示例研究游戏规则激励”,2018 年,arxiv.org/abs/1807.06732

有些人以自动驾驶汽车和对抗性地干扰停车标志为例。但如果我们可以做到这一点,为什么攻击者不会完全喷漆覆盖停车标志,或者简单地用高速限制标志暂时遮挡停车标志呢?因为这些“传统攻击”与对抗性示例不同,将始终 100%有效,而对抗性攻击只有在它能够很好地转移并且没有被预处理扭曲时才会有效。

这并不意味着当你有一个关键任务机器学习应用时,你可以忽略这个问题。然而,在大多数情况下,对抗性攻击需要比更常见的攻击向量更多的努力,所以考虑到这一点是值得的。

然而,与大多数安全影响一样,对抗性攻击也有对抗性防御,试图防御许多类型的攻击。本章中涵盖的攻击是一些较容易的,但甚至更简单的攻击也存在——例如在 MNIST 上画一条线。即使这样也足以欺骗大多数分类器。

对抗性防御是一场不断发展的游戏,其中对某些类型的攻击有许多好的防御措施,但并非所有。这种转变可能非常快,以至于在 ICLR 2018 的提交截止日期后的三天内,就有八项提议和检查的防御措施被破解了.^([8])

ICLR 是国际学习表示会议,这是一个较小但很棒的机器学习会议。请参阅 2018 年 Anish Athalye 在 Twitter 上的内容,mng.bz/ad77。需要注意的是,还有三个作者未检查的防御措施。

10.6. GAN 的对抗者

为了使与 GANs 的联系更加清晰,想象一个生成对抗性示例的系统,另一个系统则评估该示例的好坏——这取决于示例是否成功地欺骗了系统。这难道不让你想起了生成器(对手)和判别器(分类算法)吗?这两个算法再次竞争:对手试图通过轻微扰动图像来欺骗分类器,而分类器则试图不被欺骗。实际上,将 GANs 视为几乎是在循环中进行的机器学习对抗性示例,最终生成图像。

另一方面,你可以将迭代对抗攻击视为如果你取了一个 GAN,而不是指定目标是要生成最逼真的示例,而是指定目标是要生成能够欺骗分类器的示例。当然,你必须始终记住存在一些重要差异,通常在部署系统中你有一个固定的分类器。但这并不妨碍我们在对抗性训练中使用这个想法,其中一些实现甚至包括基于欺骗它的对抗性示例的重复重新训练分类器。这些技术正在逐渐接近典型的 GANs 设置。

为了给您一个例子,让我们看看一种已经作为可行的防御方法存在了一段时间的技术。在 鲁棒流形防御 中,我们采取以下步骤来防御对抗性示例:^([9])

参见 Ajil Jalal 等人于 2019 年发表的论文“《鲁棒流形防御:使用生成模型的对抗性训练》”,arxiv.org/pdf/1712.09196.pdf

  1. 我们取一个图像 x(对抗性或常规)并

    1. 将其投影回潜在空间 z

    2. 使用生成器 G 生成与 x 相似的示例,称为 x,通过 G(z)。

  2. 使用分类器 C 对此示例进行分类,即 C(x),这通常已经比直接在 x 上运行分类错误率低得多。

然而,该防御的作者发现,仍然存在一些模糊的情况,其中分类器会被轻微的扰动欺骗。尽管如此,我们鼓励您查看他们的论文,因为这些情况对人类来说也往往是不清晰的,这是鲁棒模型的一个标志。为了解决这个问题,我们在流形上应用了对抗性训练:我们将一些这些对抗性案例纳入训练集,以便分类器学会区分这些案例和真实训练数据。

这篇论文表明,使用 GANs 可以给我们提供在轻微扰动后不会完全崩溃的分类器,甚至对抗一些最复杂的方法。下游分类器的性能确实会下降,就像大多数这些防御一样,因为我们的分类器现在必须被训练来隐式地处理这些对抗性案例。但即使有这个挫折,它也不是一个通用的防御。

当然,对抗训练有一些有趣的应用。例如,在一段时间内,半监督学习中最优秀的结果——最先进的技术——是通过使用对抗训练实现的。^([10)] 这随后被 GANs(还记得第七章吗?)和其他方法所挑战,但这并不意味着在你阅读这些文字的时候,对抗训练不会再次成为最先进的技术。

^(10)

请参阅 Takeru Miyato 等人于 2018 年发表的“虚拟对抗训练:监督和半监督学习的正则化方法”,arxiv.org/pdf/1704.03976.pdf

希望这为你学习 GANs 和对抗样本提供了另一个理由——部分原因是因为在关键任务分类中,GANs 可能是未来的最佳防御手段,或者是因为本书范围之外的其它应用。^([11)] 这最好留给假设性的“对抗样本实战”。

^(11)

这是在 ICLR 2019 上激烈争论的话题。尽管这些对话大多数是非正式的,但使用(伪)可逆生成模型作为分类图像“样本外”性的方法似乎是一条富有成效的途径。

总结来说,我们阐述了对抗样本的概念,并将它与 GANs 的联系变得更加具体。这是一个被低估的联系,但可以巩固你对这个具有挑战性的主题的理解。此外,对抗样本的一种防御手段就是 GANs 本身!^([12)] 因此,GANs 也具有解决这一差距的潜力,这可能是它们最初存在的原因。

^(12)

请参阅 Jalal 等人于 2019 年发表的文章,arxiv.org/pdf/1712.09196.pdf

10.7. 结论

对抗样本是一个重要的领域,因为即使是商业计算机视觉产品也受到了这种不足的影响,并且仍然可以被学术界轻易欺骗。^([13)] 除了安全和机器学习可解释性应用之外,在公平性和鲁棒性方面还有许多实际用途。

^(13)

请参阅 Andrew Ilyas 等人于 2018 年发表的“带有有限查询和信息限制的黑盒对抗攻击”,arxiv.org/abs/1804.08598

此外,对抗样本是巩固你对深度学习和 GANs 理解的极好方式。对抗样本利用了训练分类器的一般困难以及欺骗特定分类器的相对容易。分类器需要对许多图像进行预测,而制作一个特殊的偏移量来精确欺骗分类器是容易的,因为有很多自由度。因此,我们可以轻松地得到对抗噪声,它完全改变了图片的标签,而不会在感知上改变图像。

对抗样本可以在许多领域和 AI 的许多领域中找到,而不仅仅是深度学习或计算机视觉。但正如你在代码中看到的,在计算机视觉中创建这些样本并不具有挑战性。存在针对这些样本的防御措施,你看到了一个使用 GANs 的例子,但对抗样本远未完全解决。

摘要

  • 对抗样本,它们来自滥用问题空间的维度,是机器学习的一个重要方面,因为它们展示了 GANs 为什么能工作,以及为什么某些分类器容易被破坏。

  • 我们可以轻松地使用真实图像和噪声生成自己的对抗样本。

  • 在对抗样本中,可以使用的有效攻击向量很少。

  • 对抗样本的应用包括网络安全和机器学习公平性,我们可以通过使用 GANs 来防御它们。

第十一章:GANs 的实际应用

本章涵盖

  • GANs 在医学领域的应用

  • GANs 在时尚领域的应用

就像生成手写数字和将苹果变成橘子一样吸引人,生成对抗网络(GANs)可以用于更多领域。本章探讨了 GANs 的一些实际应用。本章专注于 GANs 在实际应用中被充分利用的领域,这是非常合适的。毕竟,我们编写这本书的主要目标之一是提供必要的知识和工具,不仅让你理解到目前为止 GANs 所取得的成就,还让你能够找到你选择的新的应用。没有比研究几个成功的例子更好的开始这段旅程的地方了。

你已经看到了几个 GANs 的创新应用案例。第六章展示了渐进式 GANs 不仅可以创建逼真的人类面部渲染,还可以创建具有更大实际重要性的样本:医学乳腺 X 光片。第九章展示了 CycleGAN 如何通过将视频游戏中的剪辑转换为类似电影的场景来创建逼真的模拟虚拟环境,这些场景可以用来训练自动驾驶汽车。

本章更详细地回顾了 GANs 的应用。我们将探讨是什么激发了这些应用,是什么使它们特别适合从 GANs 带来的进步中受益,以及它们的创造者是如何实施它们的。具体来说,我们将探讨 GANs 在医学和时尚领域的应用。我们选择这两个领域基于以下标准:

  • 它们不仅展示了 GANs 的学术价值,而且更重要的是,展示了商业价值。它们代表了 GAN 研究人员通过 GANs 取得的学术进步如何应用于解决现实世界问题。

  • 它们使用的是本书中讨论的工具和技术可以理解的 GAN 模型。我们不会引入新的概念,而是将探讨我们实现的模型如何应用于除了 MNIST 以外的其他用途。

  • 它们易于理解,无需专门的领域专业知识。例如,GAN 在化学和物理学中的应用通常对没有该领域强大背景的人来说难以理解。

此外,我们选择的领域和例子旨在展示 GAN 的通用性。在医学领域,我们展示了 GAN 在数据有限的情况下如何有用。在时尚领域,我们展示了另一个极端,并探讨了在大量数据集可用的情况下的 GAN 应用。即使你对医学或时尚没有兴趣,你将在本章中学到的工具和方法也适用于无数其他用例。

很遗憾,正如经常发生的那样,我们将要回顾的实际应用几乎无法在编码教程中重现,这是因为训练数据是专有的或难以获得的。因此,我们无法提供像本书中其他部分那样的完整编码教程,而只能提供 GAN 模型及其背后实现选择的详细解释。相应地,到本章结束时,你应该已经完全准备好通过仅对之前实现的 GAN 模型进行少量修改,并为其提供特定用例或类似用例的数据集,来实施本章中的任何应用。有了这些,让我们深入探讨。

11.1. GAN 在医学中的应用

本节介绍了 GAN 在医学中的应用。具体来说,我们探讨如何使用 GAN 生成的合成数据来扩大训练数据集,以帮助提高诊断准确性。

11.1.1. 使用 GAN 提高诊断准确性

医学中的机器学习应用面临一系列挑战,这些挑战使得该领域非常适合从 GAN 中受益。也许最重要的是,由于收集医疗数据所涉及的困难,获取足够大的训练数据集以供监督机器学习算法使用是具有挑战性的。[1] 获取医疗状况的样本往往既昂贵又不切实际。

¹

请参阅 Maayan Frid-Adar 等人于 2018 年发表的《使用 GAN 进行合成数据增强以改善肝脏病变分类》,mng.bz/rPBg

与光学字符识别(OCR)的手写字母数据集或自动驾驶汽车的路面视频数据集不同,任何人都可以获取这些数据集,而医疗状况的例子则更难获得,并且通常需要专用设备来收集。更不用说患者隐私这一至关重要的考虑因素,它限制了医疗数据的收集和使用方式。

除了获取医疗数据集的困难之外,正确标记这些数据也是一个挑战,这个过程通常需要具有特定状况专业知识的人进行标注。[2] 因此,许多医学应用未能从深度学习和人工智能的进步中受益。

²

同上。

已经开发了许多技术来帮助解决小标签数据集的问题。在第七章中,你学习了如何使用 GANs 在半监督设置中提高分类算法的性能。你看到了 SGAN 如何仅使用极小部分标签进行训练就实现了优越的准确率。然而,这仅解决了医疗研究人员面临问题的一半。半监督学习有助于我们在拥有大量数据集但只有一小部分被标记的情况下。在许多医学应用中,拥有数据集一小部分标签只是问题的一部分——这小部分数据往往是唯一的数据!换句话说,我们没有成千上万的额外样本可供标记或用于半监督设置中的奢侈。

医学研究人员通过使用数据增强技术来克服数据集不足的挑战。对于图像,这些包括缩放(放大和缩小)、平移(左右和上下移动)以及旋转等小的调整和变换。图 11.1 展示了在计算机视觉中常用的数据增强示例。

³

同上。

图 11.1. 通过改变现有数据来扩大数据集的技术包括缩放(放大和缩小)、平移(左右和上下移动)以及旋转。尽管这些技术在增加数据集大小方面非常有效,但经典的数据增强技术只能带来有限的数据多样性。

图片

(来源:“数据增强:当数据有限时如何使用深度学习”,作者 Bharath Raj,2018 年,mng.bz/dxPD。)

如您所想象,标准数据增强有许多局限性。首先,小的修改只会产生与原始图像差异不大的示例。因此,这些额外的示例并没有增加很多多样性,无法帮助算法学习泛化。[例如,在处理手写数字时,我们希望看到数字 6 以不同的书写风格呈现,而不仅仅是同一基本图像的排列组合。]^([4])

同上。

在医学诊断的情况下,我们希望看到同一基本病理的不同示例。通过使用如 GANs 生成的合成示例来丰富数据集,有可能在传统增强技术之外进一步丰富可用数据。这正是以色列研究人员 Maayan Frid-Adar、Eyal Klang、Michal Amitai、Jacob Goldberger 和 Hayit Greenspan 着手研究的问题。

受到 GAN 在几乎任何领域都能合成高质量图像的能力的鼓舞,Frid-Adar 及其同事决定探索 GAN 在医学数据增强中的应用。他们选择专注于提高肝脏病变的分类。他们专注于肝脏的一个主要动机是,这个器官是三种最常见的转移性癌症发生地之一,仅在 2012 年就有超过 74.5 万人因肝癌而死亡。5 因此,有助于医生诊断高风险患者的工具和机器学习模型有可能拯救生命并改善无数患者的预后。

参见 J. Ferlay 等人撰写的“全球癌症发病率与死亡率:GLOBOCAN 2012 的数据来源、方法和主要模式”,2015 年,国际癌症杂志www.ncbi.nlm.nih.gov/pubmed/25220842

11.1.2. 方法论

Frid-Adar 及其团队发现自己陷入了一个两难境地:他们的目标是训练一个 GAN 来增强一个小数据集,但 GAN 本身需要大量的数据来训练。换句话说,他们想利用 GAN 来创建一个大数据集,但首先他们需要一个大数据集来训练 GAN。

他们的解决方案非常巧妙。首先,他们使用标准的数据增强技术来创建一个更大的数据集。其次,他们使用这个数据集来训练一个 GAN 以创建合成示例。第三,他们使用步骤 1 中增强的数据集以及步骤 2 中由 GAN 产生的合成示例来训练一个肝脏病变分类器。

研究人员使用的 GAN 模型是第四章中介绍的深度卷积 GAN(DCGAN)的一个变体。第四章。为了证明 GAN 在广泛的数据集和场景中的适用性,Frid-Adar 等人只需进行一些小的调整和定制,就能使 DCGAN 适用于他们的用例。正如图 11.2 所示,需要调整的模型部分只有隐藏层的维度以及生成器输出的维度和判别器网络的输入维度。

图 11.2。Frid-Adar 等人采用的 DCGAN 模型架构,用于生成肝脏病变的合成图像以增强他们的数据集,旨在提高分类精度。该模型架构与第四章中的 DCGAN 相似,强调了 GAN 在广泛的数据集和用例中的适用性。(注意,该图只显示了伪造示例的 GAN 流程。)

图片

(来源:Frid-Adar 等人,2018 年,mng.bz/rPBg

与 MNIST 数据集中的 28 × 28 × 1 大小的图像不同,这个 GAN 处理的是 64 × 64 × 1 的图像。正如他们论文中提到的,Frid-Adar 等人还使用了 5 × 5 的卷积核——然而,这仅仅是网络超参数的一个小改动。除了由训练数据给出的图像大小之外,所有这些调整很可能都是通过试错确定的。研究人员不断调整参数,直到模型生成令人满意的图像。

在我们回顾 Frid-Adar 及其团队设计的方法效果如何之前,让我们暂停一下,来欣赏一下你对 GANs 的理解已经进步到了何种程度。早在本书的第四章中,你就已经学到了足够多的关于 GANs 的知识,可以将其应用于一个真实世界的场景,该场景在 2018 年国际生物医学成像研讨会上的一篇论文中有所讨论。(6)

见 Frid-Adar 等,2018,mng.bz/rPBg

11.1.3. 结果

使用 DCGAN 进行数据增强,Frid-Adar 及其团队与仅使用标准数据增强的基线相比,实现了分类准确率的显著提升。7 他们的结果总结在图 11.3 中,该图显示了随着训练样本数量(x 轴)增加,分类准确率(y 轴)的变化。

同上。

图 11.3。此图表显示了使用两种数据集增强策略添加新示例时的分类准确率:标准/经典数据增强;以及使用由 DCGAN 生成的合成示例进行增强。使用标准增强(点线),分类性能在约 80%时达到峰值。使用由 GAN 创建的示例(虚线)将准确率提升到超过 85%。

(来源:Frid-Adar 等,2018,mng.bz/rPBg

点线图展示了经典数据增强的分类性能。随着新(增强)训练样本数量的增加,性能得到提升;然而,当准确率达到 80%左右时,提升趋于平缓,超出这个范围后,额外的样本无法带来进一步的提升。

虚线显示了通过使用由 GAN 生成的合成示例增强数据集所获得的额外准确率提升。从额外经典增强示例停止提升准确率的点开始,Frid-Adar 等人添加了由他们的 DCGAN 生成的合成数据。分类性能从大约 80%提升到超过 85%,证明了 GANs 的有用性。

改善肝脏病变的分类只是医学中许多数据受限的应用场景之一,这些场景可以通过 GANs 产生的合成示例进行数据增强而受益。例如,由伦敦帝国理工学院 Christopher Bowles 领导的一组英国研究人员利用 GANs(特别是第六章中讨论的渐进式 GANs)来提高脑部分割任务的表现。^([8]) 关键的是,性能的提高可以解锁模型在实际应用中的可用性,特别是在医学等领域,准确性可能意味着生死之别。

参见 Christopher Bowles 等人于 2018 年发表的“GAN Augmentation: Augmenting Training Data Using Generative Adversarial Networks”,arxiv.org/abs/1810.10863.

让我们转换一下话题,探索 GANs 在一个风险较低且具有完全不同考虑和挑战领域的应用:时尚。

11.2. 时尚领域的 GANs

与数据难以获得的医学不同,时尚研究人员幸运地拥有大量可用的数据集。像 Instagram 和 Pinterest 这样的网站上有无数套装和服装的图片,而像亚马逊和 eBay 这样的零售巨头拥有从袜子到连衣裙的数百万件商品的购买数据。

除了数据可用性之外,许多其他特性使时尚非常适合 AI 应用。时尚品味因顾客而异,个性化内容的能力有可能解锁重大的商业利益。此外,时尚趋势经常变化,对于品牌和零售商来说,快速反应并适应顾客不断变化的偏好至关重要。

在本节中,我们将探讨 GANs 在时尚领域的一些创新应用。

11.2.1. 使用 GANs 设计时尚

从无人机配送到无收银员杂货店,亚马逊对关于其未来探索的头条新闻并不陌生。2017 年,亚马逊又获得了一个,这次是关于公司利用 GANs(生成对抗网络)开发 AI 时尚设计师的雄心。这个故事发表在《MIT Technology Review》上,不幸的是,除了提到使用 GANs 设计符合特定风格的新产品外,细节很少。^([9])

参见 Will Knight 于 2017 年发表的“Amazon Has Developed an AI Fashion Designer”,《MIT Technology Review》,mng.bz/VPqX.

幸运的是,Adobe 公司和加州圣地亚哥大学的学者们发表了一篇论文,其中他们着手实现相同的目标.^([10])他们的方法可以给我们一些线索,了解亚马逊 AI 研究实验室在秘密的面纱背后正在进行的时尚革命。他们使用从亚马逊爬取的数十万个用户、物品和评论的数据集,主要作者王成康和他的合作者训练了两个不同的模型:一个用于推荐时尚,另一个用于创造时尚.^([11])

¹⁰

请参阅 2017 年 Jackie Snow 在《麻省理工学院技术评论》上发表的“This AI Learns Your Fashion Sense and Invents Your Next Outfit”,mng.bz/xlJ8

¹¹

请参阅 Wang-Cheng Kang 等人 2017 年发表的“Visually-Aware Fashion Recommendation and Design with Generative Image Models”,arxiv.org/abs/1711.02231

对于我们的目的,我们可以将推荐模型视为一个黑盒。我们唯一需要了解关于模型的信息是它做什么:对于任何一个人-物品对,它返回一个偏好分数;分数越高,物品与个人口味的匹配度越好。没有什么特别不寻常的。

后者模型更加新颖和有趣——不仅因为它使用了生成对抗网络(GANs),还因为康和他的同事们设计了两个富有创意的应用:

  • 创建符合特定个人时尚品味的全新时尚物品

  • 根据个人的时尚偏好对现有物品提出个性化修改建议。

在本节中,我们探讨康和他的团队是如何实现这些目标的。

11.2.2. 方法论

让我们从模型开始。康和他的同事们使用了一个条件生成对抗网络(CGAN),以产品的类别作为条件标签。他们的数据集包含六个类别:上衣(男性和女性),下装(男性和女性),和鞋子(男性和女性)。

回想一下,在第八章中,我们使用 MNIST 标签来教一个 CGAN 生成我们想要的任何手写数字。以类似的方式(有意为之),康等人使用类别标签来训练他们的 CGAN 生成属于指定类别的时尚物品。尽管我们现在处理的是衬衫和裤子而不是三和四,但 CGAN 模型设置几乎与我们第八章中实现的相同。生成器使用随机噪声z和条件信息(标签/类别c)来合成图像,而判别器输出一个特定图像-类别对是真实还是虚假的概率。图 11.4 详细说明了康等人使用的网络架构。

图 11.4. Kang 等人研究中使用的 CGAN 生成器和判别器网络的架构。标签 c 代表服装类别。研究人员将其用作条件标签,以引导生成器合成与给定类别匹配的图像,并使判别器识别真实图像-类别对。

图片

(来源:Kang 等人,2017,arxiv.org/abs/1711.02231。)

每个框代表一层;fc代表全连接层st表示卷积核的步长,其尺寸(宽度×高度)作为卷积/反卷积层的第一个两个数字给出;deconvconv表示使用的层类型:分别是常规卷积或转置卷积。convdeconv后面的数字设置层的深度或等价地,使用的卷积核数量。BN告诉我们,在给定层的输出上使用了批归一化。注意,Kang 等人选择使用最小二乘损失而不是交叉熵损失。

配备了能够为数据集中每个顶级类别生成逼真服装物品的 CGAN,Kang 和他的同事们对其进行了两个具有重大实际潜力的应用的测试:创建新的个性化物品和对现有物品进行个性化修改。

11.2.3. 创建符合个人偏好的新项目

为了确保产生的图像符合个人的时尚品味,Kang 和他的同事们提出了一种巧妙的方法。他们从以下洞察开始:鉴于他们的推荐模型根据一个人对给定物品的喜好程度为现有物品分配分数,生成物品以最大化这种偏好分数的能力可能会产生符合个人风格和品味的物品。12

^(12)

同上。

借用经济学和选择理论中的一个术语,13 Kang 等人称这个过程为偏好最大化。Kang 等人的方法独特之处在于,他们可能的物品宇宙不仅限于训练数据集的语料库,甚至不是整个亚马逊目录。多亏了他们的 CGAN,他们可以将新物品的生成微调到几乎无限的粒度。

^(13)

见“选择理论导论”,作者:Jonathan Levin 和 Paul Milgrom,2004 年,mng.bz/AN2p

下一问题康和他的同事们需要解决的是确保 CGAN 生成器能够生成最大化个人偏好的时尚单品。毕竟,他们的 CGAN 被训练来只为给定类别生成看起来逼真的图像,而不是为特定个人。一个可能的选择是持续生成图像并检查它们的偏好分数,直到我们偶然发现一个分数足够高的图像。然而,考虑到可以生成的图像的几乎无限变化,这种方法将非常低效且耗时。

相反,康和他的团队通过将其构造成一个优化问题来解决这个问题:特别是约束最大化。约束(他们的算法必须操作的边界)是潜在空间的大小,由向量z的大小给出。Kang 等人使用了标准大小(100 维向量),每个数字在[–1, 1]范围内。为了使这些值可微分,以便它们可以在优化算法中使用,作者将向量z中的每个元素设置为tanh函数,随机初始化。^(14)

^(14)

参见 Kang 等人,2017 年,arxiv.org/abs/1711.02231

研究人员随后采用了梯度上升法。梯度上升与梯度下降类似,只不过我们不是通过迭代地向最陡的下降方向移动来最小化成本函数,而是通过迭代地向最陡的增加方向移动来最大化奖励函数(在这种情况下,是推荐模型给出的分数)。

Kang 等人展示的结果见图 11.5,该图比较了数据集中排名前三的图像与六个不同个人生成的排名前三的图像。Kang 等人解决方案的独创性得到了证实,他们产生的示例具有更高的偏好分数,这表明它们与购物者的风格和偏好更匹配。

图 11.5。在 Kang 等人论文中呈现的结果中,每张图像都标注了其偏好分数。每一行显示了一个不同购物者和产品类别(男性和女性上衣、男性和女性下装、男性和女性鞋类)的结果。

图 11.5

(来源:Kang 等人,2017 年,arxiv.org/abs/1711.02231

左侧的三列显示了数据集中得分最高的物品;右侧的三列显示了得分最高的生成物品。根据偏好分数,生成的图像与购物者的偏好更匹配。

康和他的团队并没有止步于此。除了创造新物品外,他们还探索了他们开发的模型是否可以用来对现有物品进行修改,以适应个人的风格。鉴于时尚购物的主观性很强,能够调整服装直到“恰到好处”,具有显著的商业潜力。让我们看看康等人是如何解决这个挑战的。

11.2.4. 调整现有物品以更好地匹配个人偏好

记住,潜在空间中的数字(由输入向量z表示)具有现实意义,并且数学上彼此接近的向量(通过它们在占据的高维空间中的距离来衡量)往往会产生在内容和风格上相似的内容。因此,正如康等人所指出的,为了生成某些图像A的变体,我们只需要找到生成器用来创建该图像的潜在向量zA。然后,我们可以从邻近的向量生成图像,以生成相似的图像。

为了让它不那么抽象,让我们用一个具体的例子来看,使用我们最喜欢的数据集 MNIST。考虑一个输入向量z’,当将其输入生成器时,会产生数字 9 的图像。如果我们然后输入向量z”,从数学上讲,它在 100 维潜在空间中与z’非常接近,那么z”将产生另一个稍微不同的数字 8 的图像。这如图 11.6 所示。你曾在第二章中看到过一些这样的例子。在变分自编码器的背景下,中间/压缩表示与 GAN 世界中的z起的作用一样。

图 11.6. 在潜在空间中移动得到的数字 9 的变体(图片来自第二章)。邻近的向量会产生相同数字的变体。例如,注意当我们从第一行的左边移动到右边时,数字 9 最初是略微右倾的,但最终完全直立。也请注意,当我们足够远离时,数字 9 会变成另一个视觉上相似的数字。这种渐进的变体同样适用于更复杂的数据集,其中变体往往更加微妙。

图片

当然,在时尚领域,事情更加微妙。毕竟,一件裙子的照片比数字的灰度图像复杂得多。在生成 T 恤的向量周围在潜在空间中移动,可以产生不同颜色、图案和风格的 T 恤(例如 V 领与圆领相比)。这完全取决于生成器在训练期间内化的编码和意义类型。最好的办法是尝试一下。

这使我们来到了康和他的团队必须克服的下一个挑战。为了使前面的方法有效,我们需要想要修改的图像的向量 z。如果我们想要修改一个合成图像,这将很简单:我们每次生成图像时都可以记录向量 z,以便以后可以引用它。在我们的场景中,使情况复杂化的是,我们想要修改一个真实图像。

根据定义,真实图像不可能是由生成器产生的,因此没有向量 z。我们能做到的最好的事情是找到与我们要修改的图像尽可能接近的生成图像的潜在空间表示。换句话说,我们必须找到一个生成器用于合成与真实图像相似的图像的向量 z,并将其用作假设的z的代理,该z本应产生真实图像。

这正是康等人所做的事情。正如之前一样,他们首先将场景制定为一个优化问题。他们定义了一个损失函数,称为所谓的重建损失(两个图像之间差异的度量;损失越大,给定的一对图像之间的差异就越大)。^([15]) 以这种方式制定问题后,康等人随后通过梯度下降(最小化重建损失)迭代地找到与任何真实图像最接近的可能生成的图像。一旦我们得到一个与真实图像相似(因此也是用于生成它的向量 z)的假图像,我们就可以通过潜在空间操作来修改它。

¹⁵

同上。

这就是康和他的同事们设计的这种方法充分展示其潜力的地方。我们可以在潜在空间中移动到生成我们想要修改的图像相似点的位置,同时优化给定用户的偏好。我们可以在图 11.7 中看到这个过程:当我们从每一行的左边移动到右边时,衬衫和裤子逐渐变得更加个性化。

图 11.7. 使用相同起始图像对六位购物者(三位男性和三位女性)进行个性化过程:男性的圆领衫和女性的裤子。

(来源:康等人,2017,arxiv.org/abs/1711.02231。)

例如,第一行的人正在寻找更鲜艳的选项,正如康等人所观察到的,第五行的人似乎更喜欢更亮的颜色和更破旧的外观;而最后一个人,看起来更喜欢裙子而不是牛仔裤。这是超个性化最极致的体现。难怪亚马逊注意到了这一点。

最左侧的照片显示了训练数据集中的真实产品;左侧第二张照片显示了与用作个性化过程起点的真实照片最接近的生成图像。每张图像都标注了其偏好分数。随着我们从左到右移动,该物品逐渐针对给定的个人进行优化。正如不断上升的分数所示,个性化过程提高了该物品与给定购物者的风格和品味相匹配的可能性。

11.3. 结论

本章涵盖的应用仅是 GANs 可能实现的应用的冰山一角。仅医学和时尚领域就有无数其他用例,更不用说其他领域。可以肯定的是,GANs 已经远远超出了学术领域,利用它们合成真实数据的能力,在众多应用中发挥着作用。

摘要

  • 由于 GANs 的通用性,它们可以被用于广泛的非学术应用,并且可以轻松地重新用于 MNIST 之外的用例。

  • 在医学领域,GANs 产生的合成示例可以提高分类精度,这超出了标准数据集增强策略所能达到的。

  • 在时尚领域,GANs 可以用来创建新物品和修改现有物品,以更好地符合某人的个人风格。这是通过生成最大化推荐算法提供的偏好分数的图像来实现的。

第十二章. 展望未来

本章涵盖

  • 生成模型的伦理

  • 我们预计在未来几年中将占主导地位的三个最近改进:

    • 相对论生成对抗网络(RGAN)

    • 自注意力生成对抗网络(SAGAN)

    • BigGAN

  • 三种更前沿技术的进一步阅读

  • 本书关键主题和主要收获的总结

在本章的最后,我们想简要概述一下我们对 GANs 伦理的看法。然后我们将讨论一些我们认为在未来将变得更加重要的创新。本章包含了一些高级理念,我们预计这些理念将定义 GANs 的未来,但它不包含任何代码。我们希望你能为即将到来的 GAN 之旅做好准备——即使是写作时还未发表的进步。最后,我们将总结并表达我们依依不舍的告别。

12.1. 伦理

世界开始意识到,AI 伦理——包括 GANs——是一个重要问题。一些机构已经决定不发布它们昂贵的预训练模型,以防它们被误用作生成虚假新闻的工具。1 数篇文章描述了 GANs 可能具有潜在恶意用途的方式。2

¹

参见 Will Knight 在 2019 年《麻省理工学院技术评论》上发表的“An AI That Writes Convincing Prose Risks Mass-Producing Fake News”,mng.bz/RPGj

²

请参阅 Karen Hao 在 2019 年发表于《麻省理工学院技术评论》上的文章“AI 世界内部:创造美丽艺术和恐怖深度伪造”,mng.bz/2JA8。另请参阅 Jakub Langr 在 2019 年发表于《福布斯》上的文章“AI 因 GANs 创新而变得富有创造力”,mng.bz/1w71

我们都明白错误信息可能是一个大问题,并且具有逼真合成图像的 GANs 可能构成危险。想象一下合成一位世界领导人的视频,声称他们即将对另一个国家发动军事打击。纠正信息能否迅速传播以平息随之而来的恐慌?

这不是一本关于 AI 伦理的书,所以我们只是简要地触及这个话题。但我们坚信,对于我们所有人来说,思考我们所做的事情的伦理、我们工作的风险和意外后果是非常重要的。鉴于 AI 是一项如此可扩展的技术,思考我们是否在帮助创造一个我们想要生活的世界至关重要。

我们敦促您思考您的原则,并至少通过一个更成熟的伦理框架。我们不会讨论哪一个比另一个更好——毕竟,人类在许多更为平凡的事情上还没有就道德框架达成一致——但如果你还没有读过,请放下这本书并至少阅读其中之一。

注意

您可以在ai.google/principles上了解谷歌的 AI 原则。道德 AI 与机器学习研究所详细介绍了其原则,请参阅ethical.institute/principles.html。另请参阅 Larry Dignan 于 2017 年发表在 ZDNet 上的文章“IBM 的 Rometty 阐述 AI 考虑因素和伦理原则”,mng.bz/ZeZm

例如,被称为深度伪造的技术——尽管最初并非基于 GANs——被许多人视为一个令人担忧的来源。^([3)] 深度伪造——由深度学习伪造图像组合而成——已经通过生成虚假政治视频和合成色情内容而证明具有争议性。很快,这项技术可能达到一个点,以至于无法判断视频或图像是否真实。鉴于 GANs 合成新图像的能力,它们可能很快就会主导这个领域。

³

请参阅 Paul Chadwick 在 2018 年发表于《卫报》上的文章“谎言的回报,以及其他深度伪造新闻的挑战”,mng.bz/6wN5。另请参阅 Roula Khalaf 在 2018 年发表于《金融时报》上的文章“如果你认为假新闻是个问题,那你就等着深度伪造吧”,mng.bz/PO8Y

说每个人都应该考虑他们研究和代码的后果似乎还不够,但现实是,没有一劳永逸的解决方案。即使我们的初始关注完全是伦理方面的,无论我们是在研究还是工业界工作,我们都应该考虑这些影响。我们也不想给你们一个枯燥的讲座或未经证实的媒体炒作预测,但这是我们非常关心的问题。

人工智能伦理是一个真实存在的问题 已经存在了,我们在这里提出了三个真实的问题——AI 生成的虚假新闻、合成的政治宣言和强制性的色情内容。但还有很多其他问题存在,比如亚马逊使用一个显示对女性存在负面偏见的 AI 招聘工具。[3] 但实际情况很复杂——有些人认为 GANs 倾向于在面部生成中偏爱女性形象。另一个角度是,GANs 也有潜力帮助 AI 更加道德——通过在半监督设置中合成代表性不足的类别,例如在面部识别问题中,从而提高在代表性不足的社区中的分类质量。

见“亚马逊取消了对女性存在偏见的秘密 AI 招聘工具”,作者 Jeffrey Dastin,2018 年,路透社,mng.bz/Jz8K

我们写这本书的部分原因是为了让每个人更加意识到 GANs 的可能性和可能的滥用。我们对 GANs 未来的学术和实际应用以及正在进行的研究感到兴奋,但我们也意识到一些应用可能具有负面用途。由于技术无法“重新发明”,我们必须了解其能力。我们绝不是说,如果没有 GANs,世界会变得更好——GANs 只是一个工具,正如我们所知,工具可以被滥用。

我们感到道德上有义务讨论这项技术的承诺和危险,否则,一小部分知情者更容易滥用它。尽管这本书不是为普通大众所写,但我们希望这能成为迈向更广泛意识的一个台阶——超越至今仍由 GANs 领域占主导地位的学术圈。同样,我们进行的许多公众宣传——我们希望——正在为这一主题的知识和讨论做出贡献。

随着越来越多的人了解这项技术,甚至现有的恶意行为者也不再能够出其不意地抓住任何人。我们希望 GANs 永远不会成为恶意行为的来源,但这可能过于理想化。最好的办法是让每个人都能了解 GANs 的知识——而不仅仅是学者和真正投入的恶意分子。我们还希望(迄今为止的所有证据似乎都指向这一现实),GANs 总体上将对艺术、科学和工程产生积极贡献。此外,人们也在研究 DeepFake 检测,结合了 GANs 和对抗性样本的想法,但我们必须谨慎,因为任何能够以任何程度的准确性检测这些样本的分类器都将使能够欺骗它的示例更加可信。

在许多方面,我们也希望开始一场更加深入的对话,而不需要任何夸张的表现——这是通过我们的书籍论坛或我们的 Twitter 账户与我们联系的一种邀请。我们意识到,我们需要各种不同的观点来不断检验我们的道德框架。我们也意识到,这些事情会随着时间的推移而发展,特别是在用例变得更加清晰的情况下。确实,有些人——比如来自 a16z 的 Benedict Evans——认为,谈论 AI 的监管或伦理与谈论数据库的伦理一样没有意义。重要的是用例,而不是技术。

12.2. GAN 创新

说到用例,我们知道 GANs 是一个不断发展的领域。在本节中,我们想快速更新您关于社区中不如前几章中某些主题稳健的一些事情,但我们预计这些事情在未来将非常重要。为了保持实用性,我们挑选出了三个具有有趣实际应用的 GAN 创新:一篇实用论文(RGAN)、GitHub 项目(SAGAN)或艺术应用(BigGAN)。

12.2.1. 相对论生成对抗网络

很少有机会看到如此简单而优雅的更新,它几乎可以出现在原始论文中,同时足够强大,足以击败许多最先进的算法。相对论生成对抗网络(RGAN)就是这样一个例子。RGAN 的核心思想是,除了原始的 GAN(特别是你可能从第五章中回忆起的 NS-GAN)之外,我们还在生成器中添加了一个额外的项——迫使它使生成的数据看起来比真实数据更真实。

换句话说,生成器除了使假数据看起来更真实之外,还应该使真实数据看起来相对不那么真实,从而也增加了训练的稳定性。但当然,生成器唯一能够控制的数据是合成数据,因此生成器只能相对地实现这一点。

RGAN 的作者将其描述为 WGAN(我们之前讨论过的)的通用版本。让我们从第五章中的表 5.1 中的简化损失函数开始。

方程式 12.1。

方程式 12.2。

回想一下,方程式 12.1 描述了判别器的损失函数——我们测量真实数据(D(x))和生成数据(D(G(z)))之间的差异。方程式 12.2 然后描述了生成器的损失函数,其中我们试图让判别器相信它看到的样本是真实的。

要回到我们的直接前辈,记住 WGAN 试图最小化我们需要移动的概率质量,以使生成的分布看起来像真实分布。在这方面,RGAN 有很多相似之处(例如,判别器经常被称为评论员,而 WGAN 在本论文中被呈现为 RGAN 的特殊情况)。最终,两者都将当前状态作为单个数字来衡量——还记得地球迁移距离吗?

RGAN 的创新之处在于,我们不再得到生成器总是处于追赶状态的前所未有的动态。换句话说,生成器正在尝试生成比真实数据更真实的数据,这样它就不再总是处于防守状态。因此,D(x)可以被解释为真实数据比生成数据更真实概率。

在我们深入探讨高级别差异之前,我们将引入一种略微不同的符号,以近似论文中使用的符号,但简化。在方程式 12.3 和 12.4 中,C(x)充当类似于 WGAN 设置的评论员,^([5])您可能将其视为判别器。此外,a()定义为 log(sigmoid())。在论文中,G(z)被替换为x[f]表示假样本,而x通过下标r表示真实样本,但我们将遵循早期章节中更简单的符号。

由于我们跳过了一些细节,我们希望向您提供高级概念,并保持符号的一致性,以便您可以自己填补空白。

方程式 12.3。

方程式 12.4。

重要的是,在这些方程中,我们只看到生成器中的一个关键差异:真实数据现在被添加到损失函数中。这个看似简单的技巧使生成器的激励与永久劣势保持一致。为了理解这一点以及在其他理想化设置中的两个其他视角,让我们绘制不同的判别器输出,如图 12.1 所示。

图 12.1. 在发散最小化(a)中,生成器总是追赶判别器(因为发散总是≥0)。在(b)中,我们看到“良好”的 NS-GAN 训练是什么样的。再次,生成器不能获胜。在(c)中,我们可以看到现在生成器可以获胜,但更重要的是,生成器在训练的任何阶段都有东西可以追求(因此恢复有用的梯度)。

(来源:“相对判别器:标准 GAN 缺失的关键元素”,作者:Alexia Jolicoeur-Martineau,2018 年,arxiv.org/abs/1807.00734.)

您可能会想,为什么仅仅添加这个术语就值得关注?嗯,这个简单的添加使得训练在略微增加的计算成本下变得更加稳定。这很重要,特别是当你想起“GANs Created Equal?”这篇论文来自第五章,其中作者们争论说,到目前为止考虑的所有主要 GAN 架构在调整额外的处理需求后,对原始 GAN 的改进仅是有限的。这是因为许多新的 GAN 架构仅在巨大的计算成本下表现更好,这使得它们不太有用,但 RGAN 有潜力改变整个 GAN 架构。

总是要意识到这个技巧,因为即使一个方法可能需要更少的更新步骤,但如果每个步骤因为额外的计算而需要两倍的时间,这真的值得吗?大多数会议的同行评审过程并不免疫于这种弱点,所以你必须小心。

应用

您接下来的问题可能是,这在实践中为什么很重要?不到一年时间,这篇论文已经收集了超过 50 次引用^([6])——对于一个之前未知的作者的新论文来说,这是一个相当大的数字。此外,人们已经使用 RGAN 撰写了论文,例如,实现了最先进的语音(即,迄今为止的最佳性能)增强,击败了其他基于 GAN 和非 GAN 的方法.^([7])

以下链接列出了所有引用 RGAN 论文的论文:mng.bz/omGj

参见 Deepak Baby 和 Sarah Verhulst 于 2019 年发表的 IEEE-ICASSP 论文“SERGAN: 使用梯度惩罚的相对生成对抗网络进行语音增强”,ieeexplore.ieee.org/document/8683799

当您阅读此内容时,这篇论文应该已经可用,所以请随意查看。然而,解释这篇论文,包括所有必要的背景知识,超出了本书的范围。

12.2.2. 自注意力 GAN

我们相信下一个即将改变格局的创新是自注意力生成对抗网络(SAGAN)。注意力基于一个非常人性化的关于我们如何观察世界——一次关注一小块的想法。8 GAN 的注意力工作方式类似:你的意识能够专注于,比如说,桌子的一小部分,但你的大脑能够通过快速、微小的眼动,称为眼跳(saccades),将整个桌子拼接在一起,同时仍然一次只关注图像的子集。

请参阅尼克·查特(Nick Chater)所著的《The Mind Is Flat: The Illusion of Mental Depth and the Improvised Mind》(企鹅出版社,2018 年)。

计算机等效方法已在许多领域得到应用,包括自然语言处理(NLP)和计算机视觉。注意力可以帮助我们解决,例如,卷积神经网络(CNNs)忽略图片大部分内容的问题。众所周知,CNNs 依赖于一个小的感受野——由卷积的大小决定。然而,如您在第五章中可能回忆的那样,在生成对抗网络(GANs)中,感受野的大小可能会引起问题(如多头或多身体的情况),而 GAN 不会认为这是奇怪的。

这是因为在生成或评估图像的该子集时,我们可能会看到在一个区域中有一个腿,但我们没有看到在另一个区域中已经存在其他腿。这可能是由于卷积忽略了物体的结构,或者是因为腿或腿的旋转由不同的高级神经元表示,这些神经元之间没有交流。我们经验丰富的数据科学家会记得这正是 Hinton 的 CapsuleNets 试图解决的问题,但它们从未真正起飞。对其他人来说,简而言之,没有人能绝对确定为什么注意力可以解决这个问题,但一种好的思考方式是我们现在可以创建具有灵活感受野(形状)的特征检测器,真正关注给定图片的几个关键方面(参见图 12.2)。

图 12.2. 输出像素(2 × 2 块)忽略除了小的高亮区域之外的所有内容。注意力帮助我们解决这个问题。

图片

(来源:“Convolution Arithmetic”,作者:vdmoulin,2016 年,github.com/vdumoulin/conv_arithmetic

回想一下,当我们的图像是 512 × 512 时,这尤其是一个问题,但最大的常用卷积大小是 7,这意味着有大量的被忽略的特征!即使在高级节点中,神经网络也可能没有适当地检查,例如,头部是否在正确的位置。因此,只要牛头在牛身体旁边,网络就不关心任何其他头部,只要至少有一个。但结构是错误的。

这些高级表示更难推理,因此即使是研究人员也不同意为什么会发生这种情况,但根据经验,网络似乎并没有注意到这一点。注意力使我们能够挑选出相关的区域——无论形状或大小——并相应地考虑它们。要了解注意力可以灵活关注的区域类型,请考虑 图 12.3。

图 12.3. 在这里,我们可以看到在给定的代表性查询位置下,注意力机制最关注的图像区域。我们可以看到,注意力机制通常关注不同形状和大小的区域,这是一个好兆头,因为我们希望它能挑选出图像中表明其类型的区域。

(来源:“Self-Attention Generative Adversarial Networks”,Han Zhang,2018,arxiv.org/abs/1805.08318。)

应用

DeOldify (github.com/jantic/DeOldify) 是由 Jeremy Howard 的 fast.ai 课程学生 Jason Antic 开发的一种基于 SAGAN 的流行应用。DeOldify 使用 SAGAN 将旧图像和绘画着色到令人难以置信的精确程度。如图 12.4 所示,你可以将著名的历史照片和画作转换为全彩版本。

图 12.4. 南达科他州 Deadwood,1877 年。右侧的图像已被着色……用于黑白书籍。请相信我们。如果您不相信我们,请查看 Manning 网站上的在线 liveBook,亲自看看!

12.2.3. BigGAN

另一个震撼世界的架构是 BigGAN.^([9]) BigGAN 在 ImageNet 的所有 1,000 个类别上实现了高度逼真的 512 × 512 图像——这是当前一代 GAN 所认为几乎不可能完成的壮举。BigGAN 实现了之前最佳 inception 分数的三倍。简而言之,BigGAN 建立在 SAGAN 和频谱归一化之上,并在五个方向上进一步创新:

参见 Andrew Brock 等人于 2019 年发表的“Large Scale GAN Training for High Fidelity Natural Image Synthesis”,arxiv.org/pdf/1809.11096.pdf

  • 将 GAN 扩展到之前难以置信的计算规模。BigGAN 的作者使用了八倍的批量大小进行训练,这是他们成功的一部分——已经提供了 46% 的提升。理论上,训练 BigGAN 所需的资源总计达到 59,000 美元的计算能力.^([10])

    ¹⁰

    参见 Mario Klingemann 的 Twitter 帖子 mng.bz/wll2

  • 与 SAGAN 架构相比,BigGAN 的每个层的通道数(特征图)数量是 1.5 倍。这可能是因为数据集的复杂性。

  • 通过控制对抗过程来提高生成器和判别器的稳定性,从而带来整体更好的结果。不幸的是,这种基础的数学超出了本书的范围,但如果您对此感兴趣,我们建议从理解谱归一化开始。对于那些不感兴趣的人,您可以安慰自己,即使是作者在训练的后期部分也放弃了这种策略,并让模式崩溃,因为计算成本过高。

  • 引入一种截断技巧来给我们一种控制多样性和保真度之间权衡的方法。如果我们从分布的中间部分采样(截断它),截断技巧可以达到更好的平等结果。这很有道理,因为这是 BigGAN“经验最丰富”的地方。

  • 作者介绍了另外三个理论进步。然而,根据作者自己的性能表,这些似乎只对分数有轻微的影响,并且经常导致稳定性降低。它们对计算效率很有用,但我们将不讨论它们。

应用

BigGAN 一个令人着迷的艺术应用是 Ganbreeder 应用程序,这得益于预训练模型和 Joel Simon 的辛勤工作。Ganbreeder 是一个基于网络的交互式(免费!)方式来探索 BigGAN 的潜在空间。它已被用于众多艺术应用中,作为产生新图像的一种方式。

您可以选择探索相邻的潜在空间,或者使用两个图像样本之间的线性插值来创建新的图像。图 12.5 展示了创建 Ganbreeder 后代的示例。

图 12.5。每次您点击“制作孩子”按钮时,Ganbreeder 都会在附近的潜在空间中为您提供一组变异图像,产生下面的三幅图像。您可以从自己的样本或他人的样本开始——因此使其成为一种协作练习。这就是交叉混合部分的作用,您可以从空间的其它部分选择另一个有趣的样本,并将两个样本混合。最后,在编辑-基因中,您可以编辑参数(例如,在本例中的城堡和石墙)并添加更多或更少的这种特征到图片中。

图片

(来源:Ganbreeder,mng.bz/nv28。)

BigGAN 另一个值得注意的地方是 DeepMind 免费为我们提供了所有这些计算资源,并将预训练模型上传到了 TensorFlow Hub——一个我们用于第六章的机器学习代码仓库。

12.3. 进一步阅读

我们本想涵盖许多其他在学者和实践者作品中似乎越来越受欢迎的话题,但我们没有足够的空间。在这里,我们将至少列出三个供感兴趣的读者参考。我们希望我们已经为您提供了理解这些论文所需的一切。我们只挑选了三个,因为我们预计这一部分会很快发生变化:

  • Style GAN (arxiv.org/abs/1812.04948) 将 GAN 和“传统”风格迁移的理念相结合,使用户对生成的输出有更多的控制。这款来自 NVIDIA 的条件 GAN 已经能够通过几个级别的控制——从更细致的细节到整体图像——产生令人惊叹的全高清结果。这项工作建立在第六章的基础上,因此在你深入研究这篇论文之前,你可能想要重新阅读它。

  • 谱归一化 (arxiv.org/abs/1802.05957) 是一种复杂的正则化技术,需要一定的先进线性代数知识。现在,只需记住其用例——通过在网络上归一化权重以满足特定属性来稳定训练,这在 WGAN 中也是形式上要求的(在第五章arxiv.org/abs/1802.05957中有所涉及)。谱归一化在某种程度上与梯度惩罚相似。

  • SPADE,也称为 GauGAN (arxiv.org/pdf/1903.07291.pdf),是 2019 年发表的一项前沿工作,它根据图像的语义图合成逼真的图像,正如你从第九章的开头所回忆的那样。图像的分辨率可以达到 512 × 256,但鉴于 NVIDIA 的能力,这可能在年底前增加。这可能是三种技术中最具挑战性的,但也是最受媒体关注的——可能是因为技术演示的令人印象深刻!

在 GAN 的世界中,发生的事情如此之多,以至于可能无法始终跟上最新动态。然而,我们希望,无论是在伦理框架还是最新有趣论文方面,我们都已经为你提供了所需的资源,以便观察这个不断发展的空间中的问题。确实,这是我们的希望,即使是对本章中介绍的 GAN 背后的创新也是如此。我们不知道这些是否会成为人们日常技巧包的一部分,但我们认为它们可能会。我们也希望这一点适用于本节中列出的最新创新。

12.4. 回顾与总结

我们希望我们讨论的尖端技术能够给你足够的素材,让你在本书结束时仍能继续探索 GAN。然而,在我们让你离开之前,回顾并总结你所学的内容是值得的。

我们从对 GANs 是什么以及它们如何工作的基本解释开始(第一章)并实现了这个系统的简单版本(第三章)。我们在一个更容易的环境中向您介绍了生成模型,即自编码器(第二章)。我们涵盖了 GANs 的理论(第三章和第五章)以及它们的不足之处和克服这些不足的一些方法(第五章)。这为后续的、更高级的章节提供了基础和工具。

我们实现了几个最经典和最有影响力的 GAN 变体——深度卷积 GAN(第四章)和条件 GAN(第八章)——以及一些最先进和复杂的变体——渐进式 GANs(第六章)和 CycleGANs(第九章)。我们还实现了半监督 GANs(第八章),这是一种旨在解决机器学习中最严重不足之一的 GAN 变体:缺乏大量标记的数据集。我们还探讨了 GANs 的许多实用和创新应用(第十一章),并展示了对抗性示例(第十章),这对所有机器学习都是一个挑战。

在这个过程中,您扩展了您的理论和实践工具箱。从 inception score 和 Fréchet inception distance (第五章) 到像素级特征归一化 (第六章),批量归一化 (第四章),以及 dropout (第七章),您学习了关于概念和技术,这些将在 GANs 及其它领域为您服务得很好。

回顾过去,当我们探索生成对抗网络(GANs)时,有几个主题反复出现,值得我们强调:

  • GANs 在实用用例和抵御理论要求及约束方面的适应性都非常强。这一点在第九章中 CycleGAN 的案例中可能最为明显。这项技术不仅不受其前辈需要成对数据的需求的限制,而且几乎可以在任何领域之间进行转换,从苹果和橙子到马和斑马。GANs 的适应性在第六章中也得到了体现,您在那里看到渐进式 GANs 可以学习生成与人类面部和医学乳腺 X 光片一样不同的图像,在第七章中,我们只需进行少量调整,就可以将判别器转变为多类分类器。

  • GANs 既是艺术也是科学。GANs 的美丽和诅咒——实际上,深度学习总体上也是如此——在于我们对它们在实践中为何如此有效理解有限。已知数学保证很少,大多数成就仅限于实验。这使得 GANs 容易受到许多训练陷阱的影响,例如模式崩溃,您可能还记得我们在第五章中的讨论。第五章。幸运的是,研究人员已经找到了许多技巧和窍门,这些技巧和窍门极大地减轻了这些挑战——从输入预处理到我们选择的优化器和激活函数——其中许多您在本书中已经了解,甚至在代码教程中亲自看到了。确实,正如本章介绍的 GAN 变体所显示的,提高 GANs 的技术仍在不断发展。

除了训练中的困难之外,还必须牢记,即使像 GANs 这样强大而多功能的技巧也有其他重要的局限性。GANs 被许多人誉为赋予机器创造力的技术。这在一定程度上是正确的——在短短几年内,GANs 已经成为合成假数据的无可争议的最先进技术;然而,它们在人类创造力方面还有所不足。

事实上,正如我们在本书中一次又一次地展示的那样,GANs 可以模仿几乎所有现有数据集的特征,并提出看起来似乎来自该数据集的例子。然而,由于它们的本质,GANs 不会远离训练数据。例如,如果我们有一个经典艺术大师作品的训练数据集,我们的 GAN 产生的例子将看起来更像米开朗基罗而不是杰克逊·波洛克。除非出现一种新的 AI 范式,赋予机器真正的自主性,否则最终将由(人类)研究人员引导 GAN 达到预期的最终目标。

当你实验 GAN 及其应用时,请记住,不仅包括在这本书中涵盖的实用技术、技巧和窍门,还包括本章讨论的伦理考量。带着这些,我们祝愿你们在 GAN 之旅中一切顺利。

—雅库布和弗拉基米尔

摘要

  • 我们讨论了人工智能和生成对抗网络(GAN)的伦理问题,并探讨了道德框架、意识需求和讨论的开放性。

  • 我们提供了我们认为将推动 GAN 未来发展的创新,并给出了以下高级概念的背景想法:

    • 相对论 GAN,它现在确保生成器考虑真实数据和生成数据的相对可能性

    • SAGAN,其注意力机制与人类感知相似

    • BigGAN,它使我们能够生成前所未有的 1,000 个 ImageNet 类别

  • 我们强调了本书的两个关键重复主题:(1)GAN 的通用性以及(2)实验的必要性,因为,与深度学习的其他部分一样,GAN 既是艺术也是科学。

训练生成对抗网络(GANs)

图片描述


  1. [1] ↩︎

  2. [2] ↩︎

  3. [4] ↩︎

posted @ 2025-11-23 09:26  绝不原创的飞龙  阅读(7)  评论(0)    收藏  举报