Machine Learning Mastery 生成对抗网络教程(八)
训练稳定生成对抗网络的技巧
最后更新于 2019 年 9 月 12 日
训练稳定的生成对抗网络需要了解的经验启发、技巧和诀窍。
生成对抗网络,简称 GANs,是一种使用深度学习方法(如深度卷积神经网络)进行生成性建模的方法。
虽然 GANs 产生的结果可能是显著的,但是训练一个稳定的模型可能是具有挑战性的。原因是训练过程本身就不稳定,导致两个竞品模型同时动态训练。
然而,给定大量的经验尝试和错误,但是许多实践者和研究人员,已经发现并报告了少量的模型架构和训练配置,其导致稳定 GAN 模型的可靠训练。
在这篇文章中,你将发现稳定的一般对抗性网络模型的配置和训练的经验启发。
看完这篇文章,你会知道:
- GANs 中生成器和鉴别器模型的同时训练本质上是不稳定的。
- 来之不易的经验发现的配置为大多数 GAN 应用提供了一个坚实的起点。
- GANs 的稳定训练仍然是一个悬而未决的问题,已经提出了许多其他经验发现的技巧和诀窍,可以立即采用。
用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
如何训练稳定的生成对抗网络 克里斯·斯特恩-约翰逊摄,版权所有。
概观
本教程分为三个部分;它们是:
- 培训 GANs 的挑战
- 深度卷积 GAN
- 其他提示和技巧
生成对抗网络训练的挑战
GANs 很难训练。
它们难以训练的原因是生成器模型和鉴别器模型都是在一个游戏中同时训练的。这意味着一个模型的改进是以另一个模型为代价的。
训练两个模型的目标包括在两个相互竞争的关注点之间找到平衡点。
训练 GANs 在于找到两人非合作博弈的纳什均衡。[……]不幸的是,找到纳什均衡是一个非常困难的问题。算法存在于专门的情况下,但我们不知道有任何可行的应用于 GAN 游戏,其中成本函数是非凸的,参数是连续的,并且参数空间是极其高维的
——训练 GANs 的改进技术,2016 年。
这也意味着,每次更新其中一个模型的参数时,正在解决的优化问题的性质都会发生变化。
这具有创建动态系统的效果。
但是有了 GAN,下山的每一步都会稍微改变整个景观。这是一个动态系统,其中优化过程不是寻求最小值,而是两个力之间的平衡。
—第 306 页,Python 深度学习,2017。
用神经网络术语来说,同时训练两个竞争神经网络的技术挑战是它们可能无法收敛。
研究人员应该努力解决的 GANs 面临的最大问题是不收敛问题。
——NIPS 2016 教程:生成对抗网络,2016。
GANs 可能会遭受少量故障模式中的一种,而不是收敛。
一种常见的故障模式是,发电机不是找到平衡点,而是在域中生成特定示例之间振荡。
在实践中,GANs 似乎经常振荡,这意味着它们从产生一种样本发展到产生另一种样本,但最终没有达到平衡。
——NIPS 2016 教程:生成对抗网络,2016。
也许最具挑战性的模型故障是发电机的多个输入导致产生相同输出的情况。
这被称为“T0”模式崩溃,可能是训练 GANs 时最具挑战性的问题之一。
模式崩溃,也称为场景,是发生在生成器学习将几个不同的输入 z 值映射到同一个输出点时的问题。
——NIPS 2016 教程:生成对抗网络,2016。
最后,没有好的客观标准来评估一个 GAN 在训练中是否表现良好。审查损失是不够的。
相反,最好的方法是直观地检查生成的示例,并使用主观评估。
生成对抗网络缺乏目标函数,使得不同模型的表现难以比较。一个直观的表现度量可以通过让人类注释者判断样本的视觉质量来获得。
——训练 GANs 的改进技术,2016 年。
在撰写本文时,关于如何设计和训练 GAN 模型,还没有很好的理论基础,但是已经有了启发式的既定文献,或者“T0”黑客,它们已经被经验证明在实践中运行良好。
深度卷积生成对抗网络
在设计和训练稳定的 GAN 模型方面,最重要的一步可能是亚历克·拉德福德等人在 2015 年发表的论文,题为“利用深度卷积生成对抗网络的无监督表示学习”
在论文中,他们描述了深度卷积 GAN,或 DCGAN,这种 GAN 开发方法已经成为事实上的标准。
GAN 学习的稳定性仍然是一个开放的问题。幸运的是,当仔细选择模型架构和超参数时,GAN 学习表现良好。拉德福德等人(2015 年)精心制作了一个深度卷积 GAN(DCGAN),表现非常好的图像合成任务…
—第 701 页,深度学习,2016。
DCGAN 的生成器模型架构示例。 摘自《深度卷积生成对抗网络的无监督表示学习》,2015 年。
本文中的发现来之不易,是在对不同的模型架构、配置和训练方案进行大量的经验试错后得出的。他们的方法仍然被强烈推荐作为开发新 GANs 的起点,至少对于基于图像合成的任务是如此。
…经过广泛的模型探索,我们确定了一系列架构,这些架构能够在一系列数据集上实现稳定的训练,并允许训练更高分辨率和更深层的生成模型。
——深度卷积生成对抗网络的无监督表示学习,2015
下面提供了论文中 GAN 架构建议的摘要。
训练稳定的深度卷积生成对抗网络的体系结构指南概要。 摘自《深度卷积生成对抗网络的无监督表示学习》,2015 年。
让我们仔细看看。
1.使用条纹卷积
在卷积神经网络中,通常使用池化层(如最大池化层)进行下采样。
在 GANs 中,建议不要使用池层,而是使用卷积层中的步长在鉴别器模型中执行下采样。
类似地,分数步幅(反卷积层)可用于发生器的上采样。
[替换]具有交错卷积的确定性空间池功能(如最大池),允许网络学习自己的空间下采样。我们在生成器中使用这种方法,允许它学习自己的空间上采样和鉴别器。
——深度卷积生成对抗网络的无监督表示学习,2015
2.移除完全连接的层
通常在卷积层的特征提取层之后使用全连接层,作为模型输出层之前提取特征的解释。
相反,在 GANs 中,不使用全连接层,在鉴别器中,卷积层被展平并直接传递到输出层。
此外,传递到生成器模型的随机高斯输入向量被直接整形为多维张量,该多维张量可以被传递到第一卷积层以准备放大。
GAN 的第一层以均匀的噪声分布 Z 作为输入,可以称为完全连接,因为它只是一个矩阵乘法,但结果被重新整形为 4 维张量,并用作卷积堆栈的开始。对于鉴频器,最后一个卷积层被平坦化,然后馈入单个 sigmoid 输出。
——深度卷积生成对抗网络的无监督表示学习,2015
3.使用批处理规范化
批量标准化标准化前一层的激活,使其均值和单位方差为零。这具有稳定训练过程的效果。
批量归一化已经成为训练深度卷积神经网络时的一项主要任务,GANs 也不例外。除了发生器的输出和鉴别器的输入之外,鉴别器和发生器模型都推荐使用批量范数层。
然而,将 batchnorm 直接应用于所有层会导致样本振荡和模型不稳定。这是通过不对生成器输出层和鉴别器输入层应用 batchnorm 来避免的。
——深度卷积生成对抗网络的无监督表示学习,2015。
4.使用继电器,leaky relu,和 Tanh
激活函数如 ReLU 用于解决深度卷积神经网络中的梯度消失问题,并促进稀疏激活(如大量零值)。
建议生成器使用 ReLU,但鉴别器模型不建议使用。相反,允许小于零的值的 ReLU 的变体,称为泄漏 ReLU,在鉴别器中是优选的。
除了使用 Tanh 函数的输出层之外,ReLU 激活在生成器中使用。[……]在鉴别器中,我们发现泄漏整流激活工作良好…
——深度卷积生成对抗网络的无监督表示学习,2015。
此外,发生器在输出层使用双曲正切(tanh)激活函数,发生器和鉴别器的输入被缩放到范围[-1,1]。
除了缩放到 tanh 激活函数[-1,1]的范围之外,没有对训练图像进行预处理。
——深度卷积生成对抗网络的无监督表示学习,2015
模型权重被初始化为小的高斯随机值,鉴别器中漏 ReLU 的斜率被初始化为 0.2。
所有权重都是从标准偏差为 0.02 的以零为中心的正态分布初始化的。在泄漏率中,所有模型中的泄漏斜率都设置为 0.2。
——深度卷积生成对抗网络的无监督表示学习,2015。
5.使用亚当优化
生成器和鉴别器都是用随机梯度下降训练的,具有 128 幅图像的适度批量。
所有模型都用小批量随机梯度下降(SGD)训练,小批量大小为 128
——深度卷积生成对抗网络的无监督表示学习,2015。
具体来说, Adam 版随机梯度下降用于训练学习率为 0.0002、动量(β1)为 0.5 的模型。
我们将 Adam 优化器用于调整后的超参数。我们发现建议的学习率 0.001 太高了,用 0.0002 代替。此外,我们发现将动量项β1 保留在建议值 0.9 会导致训练振荡和不稳定性,而将其降低到 0.5 有助于稳定训练。
——深度卷积生成对抗网络的无监督表示学习,2015。
其他提示和技巧
DCGAN 论文为配置和训练生成器和鉴别器模型提供了一个很好的起点。
此外,已经编写了大量的回顾演示和论文来总结这些以及配置和培训 GANs 的其他启发。
在本节中,我们将研究其中的一些,并强调一些需要考虑的额外提示和技巧。
来自 OpenAI 的 Tim Salimans 等人在 2016 年发表的题为“训练 GANs 的改进技术”的论文中列出了五种被认为在训练 GANs 时可以提高收敛性的技术。
它们是:
- 特征匹配。使用半监督学习开发一个 GAN。
- 迷你批次判别。在一个小批次中开发多个样品的特性。
- 历史平均。更新损失函数以纳入历史记录。
- 单面标签平滑。将鉴别器的目标值从 1.0 缩小。
- 虚拟批量归一化。使用真实图像的参考批次计算批次定额统计。
在 2016 年 NIPS 会议上关于 GANs 的教程中,伊恩·古德费勒详细阐述了其中一些更成功的建议,这些建议写在随附的题为《教程:生成对抗网络》的论文中具体来说,第四部分标题为“提示和技巧”,其中描述了四种技巧。
它们是:
**1。带标签的火车。**在 GANs 中使用标签可以提高图像质量。
以任何方式、形状或形式使用标签几乎总是会显著提高模型生成的样本的主观质量。
——NIPS 2016 教程:生成对抗网络,2016。
2。单面标签平滑。在鉴别器中使用真实例子的目标值为 0.9 或随机范围的目标会产生更好的结果。
单侧标签平滑的思想是将真实示例的目标替换为略小于 1 的值,例如. 9【…】。这防止了鉴别器中的极端外推行为…
——NIPS 2016 教程:生成对抗网络,2016。
3。虚拟批量归一化。对真实图像或具有一个生成图像的真实图像计算批量统计更好。
……可以改为使用虚拟批处理规范化,其中每个示例的规范化统计数据是使用该示例和参考批处理的并集来计算的
——NIPS 2016 教程:生成对抗网络,2016。
**4。一个人能平衡 G 和 D 吗?**基于损耗的相对变化在发生器或鉴别器中安排或多或少的训练是直观的,但不可靠。
实际上,鉴别器通常更深,有时每层比发生器有更多的过滤器。
——NIPS 2016 教程:生成对抗网络,2016。
DCGAN 论文的合著者之一 sousmith Chintala在 NIPS 2016 上做了题为“如何训练 GAN?”总结了许多技巧和诀窍。
该视频可在 YouTube 上获得,强烈推荐。这些技巧的总结也可以作为 GitHub 资源库获得,标题为“如何训练 GAN?让 GANs 发挥作用的提示和技巧。”
<iframe title="NIPS 2016 Workshop on Adversarial Training - Soumith Chintala - How to train a GAN" width="500" height="281" src="https://www.youtube.com/embed/X1mUN6dD8uE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""/></div> <p/> <p>这些提示借鉴了 DCGAN 论文以及其他地方的建议。</p> <p>下面提供了一些更可行的提示的摘要。</p> <ul> <li>将输入标准化至范围[-1,1],并在发电机输出中使用 tanh。</li> <li>训练发电机时,翻转标签和损耗函数。</li> <li>采样高斯随机数作为生成器的输入。</li> <li>使用全真或全假的小批量来计算批量定额统计。</li> <li>在发生器和鉴别器中使用泄漏 ReLU。</li> <li>使用平均池和步长进行下采样;使用转换 2D 和步幅进行上采样。</li> <li>在鉴别器中使用标签平滑,随机噪声小。</li> <li>向鉴别器中的标签添加随机噪声。</li> <li>使用 DCGAN 架构,除非你有很好的理由不这样做。</li> <li>鉴别器损失 0.0 是一种故障模式。</li> <li>如果发生器的损耗稳步下降,很可能会用垃圾图像欺骗鉴别器。</li> <li>使用标签,如果你有。</li> <li>向鉴频器的输入添加噪声,并随时间衰减噪声。</li> <li>在培训和生成期间使用 50%的丢弃率。</li> </ul> <p>最后,关于 Keras 的《Python 深度学习》一书提供了许多实用技巧,供您在培训 GANs 时参考,主要基于 DCAN 论文中的建议。</p> <p>一个额外的提示建议在生成器模型中使用可被步长整除的内核大小,以避免所谓的“<em>棋盘</em>”假象(<a href="https://en.wikipedia.org/wiki/Artifact_(error)">错误</a>)。</p> <blockquote><p>…通常会看到由生成器中像素空间的不均匀覆盖引起的棋盘状伪像。为了解决这个问题,每当我们在生成器和鉴别器中使用跨步的 Conv2DTranpose 或 Conv2D 时,我们都使用一个可被跨步大小整除的内核大小。</p></blockquote> <p>—第 308 页,<a href="https://amzn.to/2U2bHuP">Python 深度学习</a>,2017。</p> <p>这与 2016 年关于蒸馏的文章<a href="https://distill.pub/2016/deconv-checkerboard/">反卷积和棋盘伪影</a>中提供的建议相同</p> <h2>进一步阅读</h2> <p>如果您想更深入地了解这个主题,本节将提供更多资源。</p> <h3>书</h3> <ul> <li>第二十章。深度生成模型,<a href="https://amzn.to/2YuwVjL">深度学习</a>,2016。</li> <li>第八章。生成式深度学习,<a href="https://amzn.to/2U2bHuP">Python 深度学习</a>,2017。</li> </ul> <h3>报纸</h3> <ul> <li><a href="https://arxiv.org/abs/1406.2661">生成对抗网络</a>,2014。</li> <li><a href="https://arxiv.org/abs/1701.00160">教程:生成对抗网络,NIPS </a>,2016。</li> <li><a href="https://arxiv.org/abs/1511.06434">深度卷积生成对抗网络的无监督表示学习</a>,2015</li> <li><a href="https://arxiv.org/abs/1606.03498">训练 GANs 的改进技术</a>,2016。</li> </ul> <h3>文章</h3> <ul> <li><a href="https://github.com/soumith/ganhacks">如何训练一个 GAN?让 GANs 发挥作用的提示和技巧</a></li> <li><a href="https://distill.pub/2016/deconv-checkerboard/">反卷积和棋盘格伪影</a>,2016。</li> </ul> <h3>录像</h3> <ul> <li><a href="https://www.youtube.com/watch?v=9JpdAg6uMXs"> Ian Goodfellow,GANs 入门,NIPS 2016 </a>。</li> <li><a href="https://www.youtube.com/watch?v=X1mUN6dD8uE">苏史密斯·钦塔拉,如何训练一个 GAN,NIPS 2016 对抗训练研讨会</a>。</li> </ul> <h2>摘要</h2> <p>在这篇文章中,你发现了配置和训练稳定的一般对抗性网络模型的经验启发法。</p> <p>具体来说,您了解到:</p> <ul> <li>GANs 中生成器和鉴别器模型的同时训练本质上是不稳定的。</li> <li>来之不易的经验发现的配置为大多数 GAN 应用提供了一个坚实的起点。</li> <li>GANs 的稳定训练仍然是一个悬而未决的问题,已经提出了许多其他经验发现的技巧和诀窍,可以立即采用。</li> </ul> <p>你有什么问题吗?<br/>在下面的评论中提问,我会尽力回答。</p> <p/> </body></html>生成对抗网络的 18 个令人印象深刻的应用
最后更新于 2019 年 7 月 12 日
生成对抗网络是一种用于生成建模的神经网络体系结构。
生成性建模包括使用模型来生成新的示例,这些示例似乎来自现有的样本分布,例如生成与现有照片数据集相似但又特别不同的新照片。
GAN 是使用两个神经网络模型训练的生成模型。一种模型被称为“T0”生成器或“T2”生成网络模型,它学习生成新的似是而非的样本。另一个模型称为“鉴别器”或“鉴别网络”,并学习将生成的示例与真实示例区分开来。
这两个模型是在竞赛或游戏(在博弈论意义上)中建立的,其中生成器模型试图欺骗鉴别器模型,并且鉴别器被提供真实样本和生成样本的例子。
经过训练后,生成模型可以用于按需创建新的似是而非的样本。
GANs 有非常具体的用例,开始时可能很难理解这些用例。
在这篇文章中,我们将回顾大量有趣的 GANs 应用程序,以帮助您对 GANs 可以使用和有用的问题类型形成直觉。这不是一个详尽的列表,但它确实包含了媒体上出现的许多 GANs 的示例用法。
我们将这些应用分为以下几个方面:
- 为图像数据集生成示例
- 生成人脸照片
- 生成逼真的照片
- 生成卡通人物
- 图像到图像的翻译
- 文本到图像的翻译
- 语义-图像-照片翻译
- 正面视图生成
- 生成新的人体姿势
- 表情符号的照片
- 照片编辑
- 面部衰老
- 照片混合
- 超分辨率
- 照片修复
- 服装翻译
- 视频预测
- 三维对象生成
我错过了 GANs 的一个有趣的应用还是一篇关于特定 GAN 应用的优秀论文? 请在评论中告知。
用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
为图像数据集生成示例
生成新的似是而非的样本是伊恩·古德费勒等人在 2014 年的论文“生成对抗网络”中描述的应用,其中 GANs 用于为 MNIST 手写数字数据集、CIFAR-10 小对象照片数据集和多伦多人脸数据库生成新的似是而非的示例。
用于为图像数据集生成新的可信示例的 GANs 示例。摘自《生成对抗网络》,2014 年。
这也是亚历克·拉德福德等人在 2015 年发表的题为“深度卷积生成对抗网络的无监督表示学习”的重要论文中使用的演示,该论文名为 DCGAN,演示了如何大规模训练稳定的 GANs。他们演示了生成新卧室示例的模型。
GAN 生成的卧室照片示例。摘自《深度卷积生成对抗网络的无监督表示学习》,2015 年。
重要的是,在这篇论文中,他们还展示了使用生成的卧室和生成的人脸对 GANs(在潜在空间中)进行矢量运算的能力。
GAN 生成人脸的矢量算法示例。摘自《深度卷积生成对抗网络的无监督表示学习》,2015 年。
生成人脸照片
Tero Karras 等人在他们 2017 年发表的题为“为提高质量、稳定性和变化性而进行的 GANs 渐进式增长”的论文中,展示了生成看似真实的人脸照片。事实上,它们看起来如此真实,以至于可以公平地称其结果为非凡。因此,这个结果受到了很多媒体的关注。人脸生成是根据名人的例子进行训练的,这意味着在生成的人脸中有现有名人的元素,使他们看起来很熟悉,但并不完全熟悉。
照片真实感 GAN 生成的脸的例子。摘自 2017 年《为提高质量、稳定性和变化性而逐步发展的 GANs》。
他们的方法也被用来演示对象和场景的生成。
照片真实感甘生成的对象和场景的例子从改进质量,稳定性和变化的甘渐进增长,2017 年。
本文中的例子被用于 2018 年一份名为“人工智能的恶意使用:预测、预防和缓解”的报告中,以展示 2014 年至 2017 年间 GANs 的快速发展(通过伊恩·古德费勒的推文找到)。
从 2014 年到 2017 年全球网络能力发展的例子。摘自《人工智能的恶意使用:预测、预防和缓解》,2018 年。
生成逼真的照片
Andrew Brock 等人在 2018 年发表的题为“高保真自然图像合成的大规模 GAN 训练”的论文中,展示了用他们的技术 BigGAN 生成合成照片,这些照片实际上与真实照片无法区分。
使用 BigGANTaken 从大规模 GAN 训练生成的逼真合成照片示例,用于高保真自然图像合成,2018 年。
生成卡通人物
金等人在 2017 年发表的论文《利用生成对抗网络实现动漫角色的自动创作》中演示了生成动漫角色(即日本漫画角色)人脸的 GAN 的训练和使用。
GAN 生成的动漫角色脸示例。摘自《走向生成对抗网络下的自动动漫角色创作》,2017 年。
受动漫例子的启发,很多人都尝试过生成口袋妖怪角色,比如口袋妖怪项目和用 DCGAN 项目生成口袋妖怪,但成功有限。
GAN 生成的口袋妖怪角色示例。取自 pokeGAN 项目。
图像到图像的翻译
这是一个有点包罗万象的任务,对于那些提出可以做很多图像转换任务的 GANs 的论文来说。
Phillip Isola 等人在他们 2016 年发表的题为“利用条件对抗网络进行图像到图像的翻译”的论文中演示了 GANs,特别是其用于许多图像到图像转换任务的 pix2pix 方法。
示例包括翻译任务,例如:
- 语义图像到城市风景和建筑照片的翻译。
- 将卫星照片翻译成谷歌地图。
- 从白天到晚上的照片翻译。
- 黑白照片的彩色翻译。
- 将草图翻译成彩色照片。
用 pix2pix 拍摄白天城市风景到夜间的照片示例。摘自《条件对抗网络下的图像转换》,2016 年。
带有 pix2pix 的彩色照片草图示例。摘自《条件对抗网络下的图像转换》,2016 年。
朱俊彦在他们 2017 年的论文《使用循环一致对抗网络的不成对图像到图像的翻译》中介绍了他们著名的循环根和一系列令人印象深刻的图像到图像的翻译例子。
以下示例演示了四种图像转换案例:
- 从照片到艺术画风的翻译。
- 马到斑马的翻译。
- 照片从夏天到冬天的翻译。
- 将卫星照片翻译成谷歌地图视图。
使用循环执行的四种图像到图像转换的示例从使用循环一致对抗网络的不成对图像到图像转换中获得,2017。
本文还提供了许多其他示例,例如:
- 绘画到摄影的翻译。
- 草图到照片的翻译。
- 苹果转橘子。
- 照片到艺术绘画的翻译。
用 CycleGAN 从绘画到照片的翻译示例。摘自《使用循环一致对抗网络的不成对图像到图像转换》,2017 年。
文本到图像转换(文本 2 图像)
张寒等人在他们 2016 年的论文《堆叠:利用堆叠生成对抗网络的文本到照片的真实感图像合成》中演示了 GANs 的使用,特别是它们的堆叠,从简单对象(如鸟和花)的文本描述中生成逼真的照片。
来自 StackGAN 的文本描述和 GAN 生成的鸟瞰照片示例:使用堆叠生成对抗网络的文本到照片真实感图像合成,2016。
斯科特·里德(Scott Reed)等人在 2016 年发表的论文《生成对抗性文本到图像合成》中,也提供了一个早期的例子,说明文本到图像生成小对象和场景,包括鸟、花等等。
鸟类和花卉的文本描述和 GAN 生成的照片示例。从生成对抗文本到图像合成。
Ayushman Dash 等人在他们 2017 年发表的题为“TAC-GAN–文本条件辅助分类器生成对抗网络”的论文中提供了更多关于看似相同数据集的例子。
斯科特·里德(Scott Reed)等人在 2016 年发表的论文《学习在哪里画什么》中扩展了这一功能,并使用 GANs 从文本生成图像,并使用边界框和关键点作为在哪里画一个描述对象(如鸟)的提示。
用 GAN 从文本和位置提示生成的对象照片示例。摘自《学习什么和在哪里画画》,2016 年。
语义-图像-照片翻译
ting-王春等人在他们 2017 年发表的题为“H 高分辨率图像合成和使用条件 GANs 的语义处理”的论文中演示了使用条件 GANs 生成给定语义图像或草图作为输入的真实感图像。
语义图像和 GAN 生成的城市景观照片的例子。摘自 2017 年高分辨率图像合成和条件 GANs 语义处理。
具体例子包括:
- 城市景观照片,给定语义图像。
- 卧室照片,给定语义图像。
- 人脸照片,给定语义图像。
- 人脸照片,给定草图。
他们还演示了一个交互式编辑器来操作生成的图像。
正面视图生成
黄锐等人在他们 2017 年的论文《超越人脸旋转:全局和局部感知 GAN 用于照片真实感和身份保持正面视图合成》中演示了在给定角度拍摄的照片的情况下,使用 GAN 来生成人脸的正面视图(即人脸打开)照片。这个想法是,生成的正面照片可以被用作人脸验证或人脸识别系统的输入。
基于 GAN 的人脸正面视图照片生成示例摘自《超越人脸旋转:全局和局部感知 GAN 用于照片真实感和身份保持正面视图合成》,2017 年。
生成新的人体姿势
马丽倩等人在 2017 年发表的题为《姿势引导的人物图像生成》的论文中提供了一个用新姿势生成人体模型新照片的例子。
GAN 生成的人体姿势照片示例从姿势引导的人物图像生成中建立,2017 年。
表情符号的照片
Yaniv Taigman 等人在 2016 年发表的题为《无监督跨域图像生成》的论文中使用了一个 GAN 将图像从一个域翻译到另一个域,包括从街道号码到 MNIST 手写数字,从名人照片到他们所谓的表情符号或小卡通脸。
名人照片和 GAN 生成的表情符号示例。摘自《无监督跨域图像生成》,2016 年。
照片编辑
Guim Perarnau 等人在 2016 年发表的题为“用于图像编辑的可逆条件 GANs】”的论文中使用 GAN,特别是他们的 IcGAN,来重建具有特定特定特征的人脸照片,例如头发颜色、风格、面部表情甚至性别的变化。
IcGAN 人脸照片编辑示例。摘自《图像编辑用可逆条件遗传》,2016 年。
刘明宇等人在 2016 年发表的论文《耦合生成对抗网络》中也探索了具有特定属性的人脸生成,如头发颜色、面部表情和眼镜。他们还探索其他图像的生成,例如具有不同颜色和深度的场景。
用于生成有和没有金发的人脸的 GANs 示例。摘自《耦合生成对抗网络》,2016 年。
Andrew Brock 等人在他们 2016 年发表的题为“用内省对抗网络进行神经照片编辑”的论文中提出了一种使用可变自动编码器和 GANs 混合的人脸照片编辑器。编辑器允许快速逼真地修改人脸,包括改变头发颜色、发型、面部表情、姿势和添加面部毛发。
使用基于视觉诱发电位和触觉诱发电位的神经照片编辑器进行面部编辑的示例。摘自神经照片编辑与内省对抗网络,2016 年。
何章等在 2017 年发表的论文《利用条件生成对抗网络进行图像去雨》中使用了 GANs 进行图像编辑,包括从照片中去除雨雪等示例。
使用有条件生成对抗网络从图像去雨中使用有向神经网络去雨的例子
面部衰老
Grigory Antipov 等人在他们 2017 年发表的题为“利用条件生成对抗网络进行面部衰老”的论文中,使用 GANs 生成了不同表观年龄(从年轻到年老)的面部照片。
用不同表观年龄的 GAN 生成的人脸照片示例。摘自《条件生成对抗网络下的面部衰老》,2017 年。
张,在他们 2017 年的论文《条件对抗自动编码器的年龄进展/回归》中使用了一种基于 GAN 的人脸照片去老化方法。
使用 GAN 对面部照片进行老化的示例根据条件对抗自动编码器的年龄进展/回归,2017 年。
照片混合
吴等人在 2017 年发表的题为“ GP-GAN:走向逼真的高分辨率图像混合”的论文中,演示了 GANs 在混合照片中的使用,特别是来自不同照片的元素,如田野、山脉和其他大型结构。
基于 GAN 的照片混合示例。摘自 GP-GAN:走向逼真的高分辨率图像混合,2017 年。
超分辨率
克里斯蒂安·莱迪格(Christian Ledig)等人在 2016 年发表的论文《使用生成对抗网络的照片真实感单幅图像超分辨率》中演示了使用 GANs,特别是他们的 SRGAN 模型来生成像素分辨率更高(有时高得多)的输出图像。
GAN 生成的超分辨率图像示例。摘自 2016 年《使用生成对抗网络的照片真实感单幅图像超分辨率》。
黄斌等人在 2017 年的论文中倾斜了“使用条件生成对抗网络的高质量人脸图像 SR”使用 GANs 创建人脸照片的版本。
使用条件生成对抗网络从高质量人脸图像 SR 生成高分辨率人脸的示例,2017。
Subeesh 瓦苏等人在他们 2018 年的论文中倾斜了“使用增强感知超分辨率网络分析感知-失真权衡”提供了一个 GANs 创建高分辨率照片的例子,聚焦于街道场景。
高分辨率 GAN 生成的建筑物照片示例。摘自《使用增强感知超分辨率网络分析感知-失真权衡》,2018 年。
照片修复
Deepak Pathak 等人在 2016 年发表的题为“上下文编码器:通过修复进行特征学习”的论文中描述了使用 GANs,特别是上下文编码器来执行照片修复或孔洞填充,即填充照片中由于某种原因被移除的区域。
使用上下文编码器的 GAN 生成的照片修复示例。摘自《上下文编码器:通过修复的特征学习》描述了 GANs 的使用,特别是上下文编码器,2016 年。
Raymond A. Yeh 等人在 2016 年发表的题为“深度生成模型的语义图像修复”的论文中使用 GANs 来填充和修复故意损坏的人脸照片。
基于 GAN 的人脸照片修复示例来自深度生成模型的语义图像修复,2016。
李翊君等人在 2017 年发表的论文《生成人脸完成》中也使用了 GANs 来修复和重建人脸的受损照片。
《生成人脸完成》,2017 年。
服装翻译
Donggeun 等人在 2016 年发表的论文《像素级域转移》中演示了如何使用 GANs 生成服装照片,这些照片可以在目录或在线商店中看到,基于穿着这些服装的模特的照片。
输入照片和 GAN 生成的服装照片示例来自像素级域转移,2016 年。
视频预测
Carl Vondrick 等人在 2016 年发表的论文《利用场景动力学生成视频》中描述了 GANs 在视频预测中的应用,具体来说就是成功预测高达一秒的视频帧,主要针对场景的静态元素。
用 GAN 生成的视频帧示例。摘自《用场景动态生成视频》,2016 年。
三维对象生成
吴家军等人在他们 2016 年的论文《通过 3D 生成-对抗建模学习对象形状的概率潜在空间》中演示了一种用于生成新的三维对象(例如 3D 模型)的 GAN,例如椅子、汽车、沙发和桌子。
GAN 生成的三维对象的例子。取自通过三维生成对抗建模学习对象形状的概率潜在空间
Matheus Gadelha 等人在 2016 年发表的题为“从多个对象的 2D 视图中进行三维形状归纳”的论文中使用 GANs 从多个视角生成给定对象二维图片的三维模型。
从二维图像对椅子进行三维重建的示例。摘自 2016 年 2D 多对象视图中的三维形状归纳。
进一步阅读
本节提供了更多的 GAN 应用程序列表来补充这个列表。
摘要
在这篇文章中,你发现了生成对抗网络的大量应用。
我错过了 GANs 的一个有趣的应用还是一篇关于 GAN 具体应用的伟大论文? 请在评论中告知。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
渐进式增长 GAN 的温和介绍
渐进式增长 GAN 是 GAN 训练过程的扩展,允许稳定训练发电机模型,可以输出大的高质量图像。
它包括从一个非常小的图像开始,逐步增加层块,增加生成器模型的输出大小和鉴别器模型的输入大小,直到达到所需的图像大小。
事实证明,这种方法可以有效地生成逼真的高质量合成人脸。
在这篇文章中,你将发现用于生成大图像的渐进增长的生成对抗网络。
看完这篇文章,你会知道:
- GANs 在生成清晰图像方面很有效,尽管由于模型稳定性,它们仅限于小图像尺寸。
- 渐进式增长 GAN 是一种训练 GAN 模型以生成大的高质量图像的稳定方法,包括在训练过程中逐渐增加模型的大小。
- 渐进增长的 GAN 模型能够生成高分辨率的真实感合成人脸和对象,非常逼真。
用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
渐进成长的生成对抗网络简介 图片由桑德琳·内尔提供,版权所有。
概观
本教程分为五个部分;它们是:
- GANs 通常仅限于小图像
- 通过逐步添加层生成大图像
- 如何逐步生长 GAN
- 渐进式增长 GAN 产生的图像
- 如何配置渐进式增长 GAN 模型
GANs 通常仅限于小图像
生成对抗网络,简称 GANs,是一种训练深度卷积神经网络模型以生成合成图像的有效方法。
训练一个 GAN 模型涉及两个模型:一个用于输出合成图像的生成器,一个用于将图像分类为真或假的鉴别器模型,用于训练生成器模型。这两个模型以对抗的方式一起训练,寻求平衡。
与其他方法相比,它们既快又清晰。
GANs 的一个问题是,它们仅限于小数据集大小,通常是几百像素,通常小于 100 像素的正方形图像。
GANs 产生清晰的图像,尽管只有相当小的分辨率和稍微有限的变化,尽管最近有所进展,训练仍然不稳定。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
生成高分辨率图像被认为对 GAN 模型具有挑战性,因为生成器必须学会如何同时输出大结构和精细细节。
高分辨率使得鉴别器容易发现生成图像的细微细节中的任何问题,并且训练过程失败。
高分辨率图像的生成是困难的,因为更高的分辨率使得更容易区分生成的图像和训练图像…
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
大图像,如 1024 像素正方形图像,也需要明显更多的内存,与主内存相比,现代 GPU 硬件上的内存供应相对有限。
因此,定义每次训练迭代中用于更新模型权重的图像数量的批量必须减少,以确保大图像适合内存。这反过来又给训练过程带来了进一步的不稳定性。
由于内存限制,较大的分辨率也需要使用较小的迷你批次,进一步降低了训练稳定性。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
此外,GAN 模型的训练仍然不稳定,即使存在一套经验技术,旨在提高模型训练过程的稳定性。
通过逐步添加层生成大图像
针对较大图像训练稳定 GAN 模型问题的解决方案是在训练过程中逐步增加层数。
这种方法简称为渐进式增长 GAN、渐进 GAN 或 PGGAN。
该方法由英伟达的 Tero Karras 等人在 2017 年的论文中提出,该论文标题为“T2 为提高质量、稳定性和变化性而逐步发展 GANs】”和在 2018 年 ICLR 会议上发表。
我们的主要贡献是 GANs 的培训方法,从低分辨率图像开始,然后通过向网络添加层来逐步提高分辨率。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
渐进式增长 GAN 涉及使用具有相同一般结构的生成器和鉴别器模型,并从非常小的图像开始,例如 4×4 像素。
在训练期间,新的卷积层块被系统地添加到生成器模型和鉴别器模型中。
向生成器和鉴别器模型逐步添加层的示例。 取自:为提高质量、稳定性和变异而进行的肝的渐进式增长。
层的增量添加允许模型有效地学习粗略级别的细节,并且稍后学习更精细的细节,在生成器和鉴别器侧都是如此。
这种增量性质允许训练首先发现图像分布的大尺度结构,然后将注意力转移到越来越精细的尺度细节,而不是必须同时学习所有尺度。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
这种方法允许生成大的高质量图像,例如不存在的名人的 1024×1024 照片级真实脸。
如何逐步生长 GAN
渐进式增长 GAN 要求在训练过程中通过添加层来扩展生成器和鉴别器模型的容量。
这很像在开发 ReLU 和批处理规范化之前开发深度学习神经网络时常见的贪婪逐层训练过程。
例如,查看帖子:
与贪婪的逐层预训练不同,渐进式增长 GAN 涉及添加层块和逐步添加层块,而不是直接添加它们。
当新的层被添加到网络中时,我们会平滑地淡入它们[……]这避免了对已经训练有素、分辨率更低的层的突然冲击。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
此外,所有层在训练过程中都保持可训练性,包括添加新层时的现有层。
两个网络中的所有现有层在整个训练过程中都是可训练的。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
新层块的定相包括使用跳跃连接将新块连接到鉴别器的输入或发生器的输出,并通过加权将其添加到现有的输入或输出层。加权控制新块的影响,并使用参数α(a)来实现,该参数从零或非常小的数字开始,并在训练迭代中线性增加到 1.0。
下图展示了这一点,取自论文。
它显示了一个输出 16×16 图像的发生器和一个获取 16×16 像素图像的鉴别器。模型增长到 32×32 的大小。
生成器和鉴别器模型中添加新层的阶段示例。 取自:为提高质量、稳定性和变异而进行的肝的渐进式增长。
让我们仔细看看,当从 16×16 像素到 32×32 像素时,如何逐步向生成器和鉴别器添加层。
生长发电机
对于生成器,这包括添加一个新的卷积层块,输出 32×32 的图像。
这个新层的输出与 16×16 层的输出相结合,16×16 层使用最近邻插值向上采样到 32×32。这与许多使用转置卷积层的 GAN 发生器不同。
…使用最近邻滤波将图像分辨率提高一倍【…】
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
上采样的 16×16 层的贡献用(1–α)加权,而新的 32×32 层的贡献用α加权。
Alpha 最初很小,对 16×16 图像的放大版本赋予了最大的权重,尽管慢慢过渡到赋予更多的权重,然后在训练迭代中将所有权重赋予新的 32×32 输出层。
在过渡期间,我们将在较高分辨率下工作的层视为残差块,其权重α从 0 到 1 线性增加。
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
成长歧视者
对于鉴别器,这包括为模型的输入添加一个新的卷积层块,以支持 32×32 像素的图像大小。
使用平均池将输入图像下采样到 16×16,以便它可以通过现有的 16×16 卷积层。新的 32×32 层块的输出也使用平均池进行下采样,以便将其作为输入提供给现有的 16×16 块。这与大多数在卷积层中使用 2×2 步距进行下采样的 GAN 模型不同。
…使用[…]平均池将图像分辨率减半
——为提高质量、稳定性和变化性而进行的肝的渐进式增长,2017 年。
输入的两个下采样版本以加权方式组合,从对下采样原始输入的全加权开始,线性过渡到对新输入层块的解释输出的全加权。
渐进式增长 GAN 产生的图像
在本节中,我们可以回顾一下论文中描述的渐进式增长 GAN 所取得的一些令人印象深刻的成果。
论文的附录中提供了许多示例图像,我建议查看一下。此外,还制作了一段 YouTube 视频,总结了该模型令人印象深刻的结果。
<iframe title="Progressive Growing of GANs for Improved Quality, Stability, and Variation" width="500" height="281" src="https://www.youtube.com/embed/G06dEcZ-QTg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""/></div> <p/> <h3>名人脸的合成照片</h3> <p>渐进式增长 GAN 最令人印象深刻的成就可能是生成了大的 1024×1024 像素的真实感生成人脸。</p> <p>该模型是在一个名为 CELEBA-HQ 的高质量名人脸数据集上训练的。因此,这些面孔看起来很熟悉,因为它们包含了许多真正名人面孔的元素,尽管这些人实际上都不存在。</p> <div id="attachment_8417" style="width: 650px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-8417" loading="lazy" class="size-full wp-image-8417" src="img/4660238e1b891e675c3a1f447a2b04ab.png" alt="Example of Photorealistic Generated Faces using Progressive Growing GAN" width="640" height="326" sizes="(max-width: 640px) 100vw, 640px"/><p id="caption-attachment-8417" class="wp-caption-text">使用渐进式增长 GAN 生成的真实感人脸示例。<br/>取自:为提高质量、稳定性和变异而进行的肝的渐进式增长。</p></div> <p>有趣的是,生成人脸所需的模型在 8 个 GPU 上训练了 4 天,这可能超出了大多数开发人员的能力范围。</p> <blockquote><p>我们在 8 个特斯拉 V100 GPUs 上对网络进行了 4 天的训练,之后我们不再观察到连续训练迭代结果之间的定性差异。我们的实现根据当前的输出分辨率使用了自适应的迷你批处理大小,从而优化了可用的内存预算。</p></blockquote> <p>——<a href="https://arxiv.org/abs/1710.10196">为提高质量、稳定性和变化性而进行的肝的渐进式增长</a>,2017 年。</p> <h3>对象的合成照片</h3> <p>该模型还演示了如何从 LSUN 数据集生成 256×256 像素的真实感合成对象,如自行车、公共汽车和教堂。</p> <div id="attachment_8418" style="width: 650px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-8418" loading="lazy" class="size-full wp-image-8418" src="img/6b72c0c04d50097dc17d9700b740883a.png" alt="Example of Photorealistic Generated Objects using Progressive Growing GAN" width="640" height="472" sizes="(max-width: 640px) 100vw, 640px"/><p id="caption-attachment-8418" class="wp-caption-text">使用渐进式增长 GAN 生成的真实感对象示例。<br/>取自:为提高质量、稳定性和变异而进行的肝的渐进式增长。</p></div> <h2>如何配置渐进式增长 GAN 模型</h2> <p>本文描述了用于生成 1024×1024 名人脸合成照片的模型的配置细节。</p> <p>具体细节见附录一</p> <p>虽然我们可能不感兴趣,也没有资源来开发这样一个大模型,但是在实现渐进式增长 GAN 时,配置细节可能会很有用。</p> <p>鉴别器和发生器模型都是使用块<a href="https://machinelearningmastery.com/convolutional-layers-for-deep-learning-neural-networks/">卷积层</a>生成的,每个卷积层使用特定数量的大小为 3×3 的滤波器和斜率为 0.2 的 LeakyReLU 激活层。通过最近邻采样实现上采样,使用<a href="https://machinelearningmastery.com/pooling-layers-for-convolutional-neural-networks/">平均池</a>实现下采样。</p> <blockquote><p>这两个网络主要由我们在培训过程中一个接一个引入的复制 3 层模块组成。[……]我们在两个网络的所有层都使用泄漏率为 0.2 的泄漏 ReLU,除了最后一层使用线性激活。</p></blockquote> <p>——<a href="https://arxiv.org/abs/1710.10196">为提高质量、稳定性和变化性而进行的肝的渐进式增长</a>,2017 年。</p> <p>生成器使用了<a href="https://machinelearningmastery.com/how-to-generate-random-numbers-in-python/">高斯随机变量</a>的 512 元素潜在向量。它还使用了带有 1×1 尺寸滤波器和线性激活函数的输出层,而不是更常见的双曲正切激活函数(tanh)。鉴别器还使用了一个输出层,该输出层具有 1×1 尺寸的滤波器和线性激活函数。</p> <p>Wasserstein 甘损失与梯度惩罚一起使用,即 2017 年题为“Wasserstein 甘的<a href="https://arxiv.org/abs/1704.00028">改进训练</a>的论文中描述的所谓的 WGAN-GP。”测试了最小二乘损失,结果良好,但不如 WGAN-GP。</p> <p>这些模型从 4×4 的输入图像开始,直到达到 1024×1024 的目标。</p> <p>提供的表格列出了发生器和鉴别器模型的层数和每层中使用的过滤器数量,如下所示。</p> <div id="attachment_8419" style="width: 650px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-8419" loading="lazy" class="size-full wp-image-8419" src="img/01ca08dfb6d6842f4f187f9388464c40.png" alt="Tables Showing Generator and Discriminator Configuration for the Progressive Growing GAN" width="640" height="430" sizes="(max-width: 640px) 100vw, 640px"/><p id="caption-attachment-8419" class="wp-caption-text">渐进式增长 GAN 的发生器和鉴别器配置表。<br/>取自:为提高质量、稳定性和变异而进行的肝的渐进式增长。</p></div> <p><a href="https://machinelearningmastery.com/how-to-accelerate-learning-of-deep-neural-networks-with-batch-normalization/">批次归一化</a>未使用;取而代之的是,增加了另外两种技术,包括迷你批次标准偏差像素化归一化。</p> <p>在鉴别器模型中,在卷积层的最后一个块之前,添加小批量中跨图像激活的标准偏差作为新通道。这被称为“<em>迷你批次标准偏差</em>”</p> <blockquote><p>我们将跨迷你批次标准偏差作为额外的特征图,以 4×4 的分辨率注入鉴别器的末端</p></blockquote> <p>——<a href="https://arxiv.org/abs/1710.10196">为提高质量、稳定性和变化性而进行的肝的渐进式增长</a>,2017 年。</p> <p>在每个卷积层之后,在生成器中执行逐像素归一化,该卷积层将通道上的激活图中的每个像素值归一化为单位长度。这是一种激活约束,通常被称为“本地响应标准化”</p> <p>所有层的偏差被初始化为零,模型权重被初始化为使用 he 权重初始化方法重新缩放的随机高斯。</p> <blockquote><p>我们根据单位方差的正态分布将所有偏差参数和所有权重初始化为零。然而,我们在运行时用特定于层的常数来缩放权重…</p></blockquote> <p>——<a href="https://arxiv.org/abs/1710.10196">为提高质量、稳定性和变化性而进行的肝的渐进式增长</a>,2017 年。</p> <p>模型使用亚当版本的随机梯度下降进行优化,具有小的<a href="https://machinelearningmastery.com/understand-the-dynamics-of-learning-rate-on-deep-learning-neural-networks/">学习率</a>和低动量。</p> <blockquote><p>我们使用 Adam 训练网络,a = 0.001,B1=0,B2=0.99,eta = 10^−8.</p></blockquote> <p>——<a href="https://arxiv.org/abs/1710.10196">为提高质量、稳定性和变化性而进行的肝的渐进式增长</a>,2017 年。</p> <p>图像生成使用先前模型的<a href="https://machinelearningmastery.com/polyak-neural-network-model-weight-ensemble/">加权平均值</a>,而不是给定的模型快照,很像水平集合。</p> <blockquote><p>…可视化训练期间任何给定点的生成器输出,我们使用衰减为 0.999 的生成器权重的指数运行平均值</p></blockquote> <p>——<a href="https://arxiv.org/abs/1710.10196">为提高质量、稳定性和变化性而进行的肝的渐进式增长</a>,2017 年。</p> <h2>进一步阅读</h2> <p>如果您想更深入地了解这个主题,本节将提供更多资源。</p> <ul> <li><a href="https://arxiv.org/abs/1710.10196">为改善质量、稳定性和变异而进行的肝的渐进式增长</a>,2017 年。</li> <li><a href="https://research.nvidia.com/publication/2017-10_Progressive-Growing-of">为提高质量、稳定性和变异性而逐渐生长的肝,官方</a>。</li> <li><a href="https://github.com/tkarras/progressive_growing_of_gans">GitHub</a>gans 项目(官方)的递进生长。</li> <li><a href="https://openreview.net/forum?id=Hk99zCeAb&noteId=Hk99zCeAb">为了提高品质、稳定性和变异,进行性生长肝。开启审核</a>。</li> <li><a href="https://www.youtube.com/watch?v=G06dEcZ-QTg">为提高质量、稳定性和变化性而进行的肝的渐进式增长,YouTube </a>。</li> </ul> <h2>摘要</h2> <p>在这篇文章中,你发现了用于生成大图像的渐进增长的生成对抗网络。</p> <p>具体来说,您了解到:</p> <ul> <li>GANs 在生成清晰图像方面很有效,尽管由于模型稳定性,它们仅限于小图像尺寸。</li> <li>渐进式增长 GAN 是一种训练 GAN 模型以生成大的高质量图像的稳定方法,包括在训练过程中逐渐增加模型的大小。</li> <li>渐进增长的 GAN 模型能够生成高分辨率的真实感合成人脸和对象,非常逼真。</li> </ul> <p>你有什么问题吗?<br/>在下面的评论中提问,我会尽力回答。</p> <p/> </body></html>StyleGAN 的温和介绍——风格生成对抗网络
最后更新于 2020 年 5 月 10 日
生成对抗网络,简称 GANs,在生成大型高质量图像方面非常有效。
为了训练更有效的发电机模型,已经对鉴别器模型进行了大部分改进,尽管在改进发电机模型方面投入的精力较少。
样式生成对抗网络(简称 StyleGAN)是 GAN 架构的扩展,它对生成器模型提出了较大的更改,包括使用映射网络将潜在空间中的点映射到中间潜在空间,使用中间潜在空间来控制生成器模型中每个点的样式,以及引入噪声作为生成器模型中每个点的变化源。
生成的模型不仅能够生成令人印象深刻的照片级高质量人脸照片,还可以通过改变样式向量和噪声来控制生成的图像在不同细节级别的样式。
在这篇文章中,您将发现风格生成对抗网络,它可以控制生成的合成图像的风格。
看完这篇文章,你会知道:
- 对传统 GAN 模型生成的合成图像的风格缺乏控制。
- StyleGAN 模型的体系结构引入了对生成的图像在不同细节层次上的风格的控制。
- 当用于生成合成人脸时,StyleGAN 架构取得了令人印象深刻的结果。
用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
风格生成对抗网络简介(StyleGAN) 图片由伊恩·d·基廷提供,版权所有。
概观
本教程分为四个部分;它们是:
- 对合成图像缺乏控制
- 使用新生成器模型的控制方式
- 什么是 StyleGAN 模型架构
- 样式生成的图像示例
对合成图像缺乏控制
生成对抗网络在生成高质量和高分辨率的合成图像方面是有效的。
生成器模型将来自潜在空间的点作为输入,并生成图像。这个模型由第二个模型训练,称为鉴别器,它学习将训练数据集中的真实图像与生成器模型生成的假图像区分开来。因此,这两个模型在一个对抗性的游戏中竞争,并在训练过程中找到平衡或均衡。
通过对鉴别器模型的增强,已经实现了对 GAN 体系结构的许多改进。这些变化的动机是,更好的鉴别器模型将反过来导致生成更真实的合成图像。
因此,发电机在某种程度上被忽视了,仍然是一个黑匣子。例如,在合成图像的生成中使用的随机性的来源没有被很好地理解,包括采样点中的随机性的量和潜在空间的结构。
然而,发电机仍然像黑匣子一样运行,尽管最近做出了努力,但对图像合成过程的各个方面的理解仍然缺乏。对潜在空间的特性也知之甚少…
——一种基于风格的生成对抗网络生成器架构,2018 年。
对发生器的这种有限理解最典型的例子可能是对生成的图像普遍缺乏控制。很少有工具可以控制生成图像的属性,例如样式。这包括高级特征,如背景和前景,以及细粒度细节,如合成对象或主体的特征。
这需要解开图像中的特征或属性,并将这些属性的控件添加到生成器模型中。
使用新生成器模型的控制方式
风格生成对抗网络,简称 StyleGAN,是 GAN 架构的扩展,用于控制生成图像的松散风格属性。
我们的生成器从学习的常量输入开始,并基于潜在代码调整每个卷积层的图像“样式”,因此直接控制不同比例下的图像特征强度
——一种基于风格的生成对抗网络生成器架构,2018 年。
StyleGAN 是渐进式增长 GAN 的扩展,这是一种训练生成器模型的方法,能够在训练过程中通过鉴别器和生成器模型从小图像到大图像的增量扩展来合成非常大的高质量图像。
除了模型在训练期间的增量增长之外,GAN 的风格显著改变了生成器的架构。
StyleGAN 生成器不再从潜在空间中获取一个点作为输入;相反,有两种新的随机性来源用于生成合成图像:独立的映射网络和噪声层。
映射网络的输出是一个定义样式的向量,这些样式通过一个称为自适应实例规范化的新层集成在生成器模型的每个点上。使用这种风格矢量可以控制生成图像的风格。
随机变化是通过在发电机模型中的每个点添加噪声而引入的。噪声被添加到整个特征映射中,这允许模型以细粒度、每像素的方式解释样式。
风格向量和噪声的每块结合允许每个块将风格的解释和随机变化定位到给定的细节水平。
新的架构导致高级属性(例如,在人脸上训练时的姿势和身份)和生成的图像(例如,雀斑、头发)中的随机变化的自动学习的、无监督的分离,并且它实现了合成的直观的、比例特定的控制
——一种基于风格的生成对抗网络生成器架构,2018 年。
什么是 StyleGAN 模型架构
StyleGAN 被描述为一种渐进增长的 GAN 架构,有五种修改,每种修改都是在消融研究中逐步增加和评估的。
生成器的增量更改列表如下:
- 基线渐进性 GAN。
- 添加调谐和双线性上采样。
- 添加映射网络和 AdaIN(样式)。
- 消除发电机的潜在矢量输入。
- 向每个块添加噪声。
- 加法混合正则化。
下图总结了 StyleGAN 生成器的体系结构。
样式生成器模型体系结构概述。 摘自:生成对抗网络的基于风格的生成器架构。
我们可以更详细地回顾这些变化。
1.基线进行性肝动脉栓塞
StyleGAN 生成器和鉴别器模型使用渐进增长 GAN 训练方法进行训练。
这意味着两个模型都是从小图像开始的,在本例中是 4×4 图像。模型被拟合直到稳定,然后鉴别器和发生器都被扩展到宽度和高度的两倍(面积的四倍),例如 8×8。
每个模型都添加了一个新的块来支持更大的图像尺寸,随着训练的进行,图像尺寸会慢慢变小。一旦淡入,模型再次被训练,直到相当稳定,并且该过程以越来越大的图像尺寸重复,直到满足期望的目标图像尺寸,例如 1024×1024。
有关渐进式增长 GAN 的更多信息,请参见论文:
- 为改善质量、稳定性和变异而进行的肝的渐进式增长,2017 年。
2.双线性采样
渐进增长的 GAN 使用最近邻层进行上采样,而不是其他生成器模型中常见的转置卷积层。
StyleGAN 的第一个偏差点是双线性上采样层未被使用,而不是最近邻层。
我们用双线性采样替换了两个网络中的最近邻上/下采样,这是通过在每个上采样层之后和每个下采样层之前用可分离的二阶二项式滤波器对激活进行低通滤波来实现的。
——一种基于风格的生成对抗网络生成器架构,2018 年。
3.映射网络和数据适配器
接下来,使用独立的映射网络,该网络从潜在空间中获取随机采样点作为输入,并生成样式向量。
映射网络由八个完全连接的层组成,例如,它是一个标准的深度神经网络。
为了简单起见,我们将[潜在和中间潜在]空间的维数都设置为 512,并且使用 8 层 MLP …
——一种基于风格的生成对抗网络生成器架构,2018 年。
然后,通过一种称为自适应实例归一化或 AdaIN 的操作,在卷积层之后,样式向量被转换并合并到生成器模型的每个块中。
AdaIN 层首先将要素地图的输出标准化为标准高斯,然后添加样式向量作为偏差项。
然后,学习的仿射变换将[中间潜在向量]专门化为样式 y = (ys,yb),这些样式控制合成网络 g 的每个卷积层之后的自适应实例归一化(AdaIN)操作。
——一种基于风格的生成对抗网络生成器架构,2018 年。
StyleGAN 中自适应实例归一化的计算。 摘自:生成对抗网络的基于风格的生成器架构。
向架构中添加新的映射网络还会导致将生成器模型重命名为“合成网络”
4.潜在点输入的移除
下一个变化涉及修改生成器模型,使其不再从潜在空间中选取一个点作为输入。
相反,模型具有恒定的 4x4x512 常数值输入,以便开始图像合成过程。
5.添加噪声
合成网络中每个卷积层的输出是一组激活图。
在 AdaIN 操作之前,高斯噪声被添加到这些激活映射中的每一个。为每个块生成不同的噪声样本,并使用每层比例因子进行解释。
这些是由不相关的高斯噪声组成的单通道图像,我们向合成网络的每一层馈送一个专用噪声图像。使用学习的每个特征的缩放因子将噪声图像广播到所有特征图,然后添加到相应卷积的输出中…
——一种基于风格的生成对抗网络生成器架构,2018 年。
该噪声用于在给定的细节级别引入风格级别的变化。
6.混合正则化
混合正则化包括首先从映射网络生成两个样式向量。
选择合成网络中的一个分割点,并且在分割点之前的所有 AdaIN 操作使用第一个样式向量,并且在分割点之后的所有 AdaIN 操作获得第二个样式向量。
……我们采用混合正则化,其中给定百分比的图像是使用两个随机潜在代码生成的,而不是在训练期间使用一个。
——一种基于风格的生成对抗网络生成器架构,2018 年。
这有助于层和块将样式定位到模型的特定部分以及生成的图像中的相应细节级别。
样式生成的图像示例
StyleGAN 在生成大的高质量图像和控制生成图像的风格方面都很有效。
在本节中,我们将回顾一些生成图像的示例。
论文作者发布了一段展示该模型能力的视频,提供了一个有用的概述。
<iframe title="A Style-Based Generator Architecture for Generative Adversarial Networks" width="500" height="281" src="https://www.youtube.com/embed/kSLJriaOumA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""/></div> <p/> <h3>高质量的面孔</h3> <p>下图是用 4×4、8×8、16×16 和 32×32 的 StyleGAN 生成的合成人脸。</p> <div id="attachment_8442" style="width: 650px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-8442" loading="lazy" class="wp-image-8442 size-full" src="img/9dc8b32d84baf626a366045cfe6c52a5.png" alt="Example of High-Quality Generated Faces Using the StyleGAN" width="640" height="283" sizes="(max-width: 640px) 100vw, 640px"/><p id="caption-attachment-8442" class="wp-caption-text">使用样式生成高质量人脸的示例。<br/>摘自:生成对抗网络的基于风格的生成器架构。</p></div> <h3>根据细节层次改变风格</h3> <p>在合成网络的不同点使用不同的风格矢量,可以在不同的细节层次上控制最终图像的风格。</p> <p>例如,合成网络中较低分辨率(例如 4×4 和 8×8)的层块控制高级风格,例如姿势和发型。网络模型中的层块(例如 16×16 和 32×32)控制发型和面部表情。最后,更靠近网络输出端的层块(例如 64×64 到 1024×1024)控制色彩方案和非常精细的细节。</p> <p>下面这张从纸上拍摄的图像显示了左侧和顶部生成的图像。中间图像的两行是用于生成左侧图像的样式向量的示例,其中用于顶部图像的样式向量仅在较低级别使用。这允许左边的图像采用每列顶部图像的高级风格,如姿势和发型。</p> <blockquote><p>复制对应于粗略空间分辨率(4²-8²)的样式会带来高级方面,例如来自源 b 的姿势、一般发型、脸型和眼镜,而所有颜色(眼睛、头发、照明)和更精细的面部特征都类似于源 a。</p></blockquote> <p>——<a href="https://arxiv.org/abs/1812.04948">一种基于风格的生成对抗网络生成器架构</a>,2018 年。</p> <div id="attachment_8443" style="width: 650px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-8443" loading="lazy" class="size-full wp-image-8443" src="img/82ed7f24f22cb2826bbb3e67e72e443a.png" alt="Example of One Set of Generated Faces (Left) Adopting the Coarse Style of Another Set of Generated Faces (Top)" width="640" height="328" sizes="(max-width: 640px) 100vw, 640px"/><p id="caption-attachment-8443" class="wp-caption-text">一组生成面的示例(左)采用另一组生成面的粗略样式(上)<br/>摘自:生成对抗网络的基于样式的生成器体系结构。</p></div> <h3>使用噪声控制细节水平</h3> <p>作者在模型的不同细节层次(如精细、中等、粗糙)改变了噪声的使用,很像前面不同风格的例子。</p> <p>结果是噪声控制了细节的产生,从在粗糙层块中使用噪声时的更宽结构,到在更接近网络输出的层中添加噪声时的精细细节的产生。</p> <blockquote><p>我们可以看到噪音的人为省略导致了毫无特色的“绘画式”外观。粗噪声会导致大规模的头发卷曲和较大背景特征的出现,而细噪声会带来更细的头发卷曲、更细的背景细节和皮肤毛孔。</p></blockquote> <p>——<a href="https://arxiv.org/abs/1812.04948">一种基于风格的生成对抗网络生成器架构</a>,2018 年。</p> <div id="attachment_8444" style="width: 650px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-8444" loading="lazy" class="size-full wp-image-8444" src="img/c509fbff82651f74db639d1555ceba04.png" alt="Example of Varying Noise at Different Levels of the Generator Model" width="640" height="628" sizes="(max-width: 640px) 100vw, 640px"/><p id="caption-attachment-8444" class="wp-caption-text">发电机模型不同级别的噪声变化示例。<br/>摘自:生成对抗网络的基于风格的生成器架构。</p></div> <h2>进一步阅读</h2> <p>如果您想更深入地了解这个主题,本节将提供更多资源。</p> <ul> <li><a href="https://arxiv.org/abs/1812.04948">一种基于风格的生成对抗网络生成器架构</a>,2018。</li> <li><a href="https://arxiv.org/abs/1710.10196">为改善质量、稳定性和变异而进行的肝的渐进式增长</a>,2017 年。</li> <li><a href="https://github.com/NVlabs/stylegan">style gan–官方 TensorFlow 实现,GitHub </a>。</li> <li><a href="https://www.youtube.com/watch?v=kSLJriaOumA"> StyleGAN 结果视频,YouTube </a>。</li> </ul> <h2>摘要</h2> <p>在这篇文章中,你发现了风格生成对抗网络,它可以控制生成的合成图像的风格。</p> <p>具体来说,您了解到:</p> <ul> <li>对传统 GAN 模型生成的合成图像的风格缺乏控制。</li> <li>StyleGAN 模型的体系结构 GAN 模型引入了对生成的图像在不同细节级别上的风格的控制</li> <li>当用于生成合成人脸时,StyleGAN 架构取得了令人印象深刻的结果。</li> </ul> <p>你有什么问题吗?<br/>在下面的评论中提问,我会尽力回答。</p> <p/> </body></html>如何在 Keras 开发最小二乘生成对抗网络
最后更新于 2021 年 1 月 18 日
最小二乘生成对抗网络,简称 LSGAN,是 GAN 架构的扩展,解决了梯度消失和损耗饱和的问题。
它的动机是希望向生成器提供关于假样本的信号,这些假样本远离鉴别器模型的决策边界,用于将它们分类为真或假。生成的图像离决策边界越远,提供给生成器的误差信号就越大,从而鼓励生成更真实的图像。
LSGAN 可以通过对鉴别器层的输出层的微小改变和采用最小二乘或 L2 损失函数来实现。
在本教程中,您将发现如何开发一个最小二乘生成对抗网络。
完成本教程后,您将知道:
- LSGAN 解决了深度卷积 GAN 的梯度消失和损耗饱和问题。
- LSGAN 可以通过鉴别器模型的均方误差或 L2 损失函数来实现。
- 如何实现为 MNIST 数据集生成手写数字的 LSGAN 模型。
用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2021 年 1 月更新:更新所以层冻结用批量定额。
如何开发用于图像生成的最小二乘生成对抗网络。,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 什么是最小二乘法
- 如何开发 MNIST 手写数字的 LSGAN
- 如何用 LSGAN 生成图像
什么是最小二乘法
标准的生成对抗网络,简称 GAN,是一种训练无监督生成模型的有效架构。
该体系结构包括训练鉴别器模型来区分真实(来自数据集)和虚假(生成的)图像,并依次使用鉴别器来训练生成器模型。生成器以这样一种方式更新,即鼓励它生成更有可能欺骗鉴别器的图像。
鉴别器是一个二进制分类器,使用二进制交叉熵损失函数进行训练。这种损失函数的一个局限性是,它主要关心预测是否正确,而不太关心它们可能有多正确或不正确。
……当我们使用假样本通过使鉴别器相信它们来自真实数据来更新生成器时,它几乎不会导致错误,因为它们位于决策边界的正确侧,即真实数据侧
——最小二乘生成对抗网络,2016。
这可以在两个维度上概念化为一条线或决定边界,将代表真实和虚假图像的点分开。鉴别器负责设计判定边界,以最好地分离真实和虚假图像,生成器负责创建看起来像真实点的新点,混淆鉴别器。
交叉熵损失的选择意味着远离边界生成的点是对的还是错的,但是向生成器提供的关于如何生成更好的图像的梯度信息非常少。
远离决策边界的生成图像的这种小梯度被称为梯度消失问题或损失饱和度。损失函数无法给出关于如何最好地更新模型的强信号。
最小二乘生成对抗网络,简称 LSGAN,是毛旭东等人在 2016 年发表的论文《T2 最小二乘生成对抗网络》中提出的 GAN 架构的扩展 LSGAN 是对 GAN 架构的修改,将鉴别器的损耗函数从二进制交叉熵变为最小二乘损耗。
这种变化的动机是最小二乘损失将根据图像与决策边界的距离来惩罚生成的图像。这将为与现有数据非常不同或相差甚远的生成图像提供强梯度信号,并解决饱和损失问题。
……最小化规则 GAN 的目标函数会遭受梯度消失的困扰,这使得生成器很难更新。LSGANs 可以缓解这个问题,因为 LSGANs 根据样本到决策边界的距离来惩罚样本,这会生成更多的梯度来更新生成器。
——最小二乘生成对抗网络,2016。
这可以通过以下取自论文的图来概念化,该图在左侧显示了 sigmoid 决策边界(蓝色)和远离决策边界的生成的伪点(粉色),在右侧显示了最小二乘决策边界(红色)和远离边界的点(粉色),给定一个梯度将它们移近边界。
更新生成器的 Sigmoid 决策边界与最小平方决策边界的关系图。 摘自:最小二乘生成对抗网络。
除了避免损失饱和之外,与传统的深度卷积 GAN 相比,LSGAN 还导致更稳定的训练过程和更高质量和更大图像的生成。
首先,低分辨率图像传感器能够产生比普通图像传感器更高质量的图像。第二,LSGANs 在学习过程中表现更稳定。
——最小二乘生成对抗网络,2016。
可以通过使用真实图像的 1.0 和伪造图像的 0.0 的目标值,并使用均方误差损失函数(例如,L2 损失)优化模型来实现 LSGAN。鉴别器模型的输出层必须是线性激活函数。
作者提出了一个生成器和鉴别器模型架构,灵感来自 VGG 模型架构,并在生成器模型中使用交错上采样和正常卷积层,如下图左侧所示。
LSGAN 实验中使用的发生器(左)和鉴别器(右)模型架构总结。 摘自:最小二乘生成对抗网络。
如何开发 MNIST 手写数字的 LSGAN
在本节中,我们将为 MNIST 手写数字数据集开发一个 LSGAN。
第一步是定义模型。
鉴别器和发生器都将基于深度卷积 GAN 或 DCGAN 架构。这包括使用卷积-批处理-激活层块,使用 2×2 步距进行下采样,转置卷积层进行上采样。鉴别器中使用了 LeakyReLU 激活层,发生器中使用了 ReLU 激活层。
鉴别器期望灰度输入图像具有 28×28 的形状,图像的形状在 MNIST 数据集中,并且输出层是具有线性激活函数的单个节点。根据最小二乘法,使用均方误差损失函数优化模型。下面的 define_discriminator() 函数定义了鉴别器模型。
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='linear', kernel_initializer=init))
# compile model with L2 loss
model.compile(loss='mse', optimizer=Adam(lr=0.0002, beta_1=0.5))
return model
生成器模型以潜在空间中的一点作为输入,并通过输出层上的 tanh 激活函数输出形状为 28×28 像素的灰度图像,其中像素值在[-1,1]的范围内。
下面的 define_generator() 函数定义了生成器模型。这个模型没有被编译,因为它不是以独立的方式训练的。
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 256 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Reshape((7, 7, 256)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(BatchNormalization())
model.add(Activation('relu'))
# upsample to 28x28
model.add(Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(BatchNormalization())
model.add(Activation('relu'))
# output 28x28x1
model.add(Conv2D(1, (7,7), padding='same', kernel_initializer=init))
model.add(Activation('tanh'))
return model
生成器模型通过鉴别器模型进行更新。这是通过创建一个复合模型来实现的,该模型将发生器堆叠在鉴别器的顶部,以便误差信号可以通过鉴别器流回发生器。
当在该复合模型中使用时,鉴别器的权重被标记为不可训练。通过合成模型的更新包括使用生成器通过提供潜在空间中的随机点作为输入来创建新图像。生成的图像被传递给鉴别器,鉴别器将把它们分为真的或假的。权重被更新,就像生成的图像是真实的一样(例如,目标为 1.0),允许生成器被更新以生成更真实的图像。
define_gan() 函数定义并编译复合模型,用于通过鉴别器更新发电机模型,再次根据 LSGAN 通过均方误差进行优化。
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
for layer in discriminator.layers:
if not isinstance(layer, BatchNormalization):
layer.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model with L2 loss
model.compile(loss='mse', optimizer=Adam(lr=0.0002, beta_1=0.5))
return model
接下来,我们可以定义一个函数来加载 MNIST 手写数字数据集,并将像素值缩放到[-1,1]的范围,以匹配生成器模型输出的图像。
仅使用了 MNIST 数据集的训练部分,其中包含 60,000 幅居中的灰度图像,位数从零到九。
# load mnist images
def load_real_samples():
# load dataset
(trainX, _), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
然后,我们可以定义一个函数,从训练数据集中检索一批随机选择的图像。
真实图像与鉴别器模型的相应目标值一起返回,例如 y=1.0,以指示它们是真实的。
# select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
接下来,我们可以为生成器开发相应的功能。
首先,用于在潜在空间中生成随机点以用作通过生成器模型生成图像的输入的函数。
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
接下来,一个函数将使用生成器模型生成一批假图像,用于更新鉴别器模型,以及指示图像是假的目标值(y=0)。
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
我们需要在训练期间定期使用生成器来生成图像,我们可以主观检查这些图像,并将其用作选择最终生成器模型的基础。
在训练过程中可以调用下面的*summary _ performance()*函数,生成并保存一张图像图,保存生成器模型。使用反向灰度色图绘制图像,使数字在白色背景上变成黑色。
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
filename1 = 'generated_plot_%06d.png' % (step+1)
pyplot.savefig(filename1)
pyplot.close()
# save the generator model
filename2 = 'model_%06d.h5' % (step+1)
g_model.save(filename2)
print('Saved %s and %s' % (filename1, filename2))
我们也对训练中的损失行为感兴趣。
因此,我们可以在每次训练迭代的列表中记录损失,然后创建并保存模型学习动态的线图。创建并保存学习曲线的绘图在*绘图 _ 历史()*功能中实现。
# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist):
pyplot.plot(d1_hist, label='dloss1')
pyplot.plot(d2_hist, label='dloss2')
pyplot.plot(g_hist, label='gloss')
pyplot.legend()
filename = 'plot_line_plot_loss.png'
pyplot.savefig(filename)
pyplot.close()
print('Saved %s' % (filename))
最后,我们可以通过 train() 函数定义主训练循环。
该函数将定义的模型和数据集作为参数,并将训练时期的数量和批处理大小作为默认函数参数进行参数化。
每个训练循环首先生成半批真样本和假样本,并使用它们为鉴别器创建一批有价值的权重更新。接下来,通过复合模型更新生成器,提供真实(y=1)目标作为模型的预期输出。
在每次训练迭代中报告损失,并根据每个时期结束时生成的图像图总结模型表现。学习曲线的绘图在运行结束时创建并保存。
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=20, n_batch=64):
# calculate the number of batches per training epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the number of training iterations
n_steps = bat_per_epo * n_epochs
# calculate the size of half a batch of samples
half_batch = int(n_batch / 2)
# lists for storing loss, for plotting later
d1_hist, d2_hist, g_hist = list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# prepare real and fake samples
X_real, y_real = generate_real_samples(dataset, half_batch)
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# update discriminator model
d_loss1 = d_model.train_on_batch(X_real, y_real)
d_loss2 = d_model.train_on_batch(X_fake, y_fake)
# update the generator via the discriminator's error
z_input = generate_latent_points(latent_dim, n_batch)
y_real2 = ones((n_batch, 1))
g_loss = gan_model.train_on_batch(z_input, y_real2)
# summarize loss on this batch
print('>%d, d1=%.3f, d2=%.3f g=%.3f' % (i+1, d_loss1, d_loss2, g_loss))
# record history
d1_hist.append(d_loss1)
d2_hist.append(d_loss2)
g_hist.append(g_loss)
# evaluate the model performance every 'epoch'
if (i+1) % (bat_per_epo * 1) == 0:
summarize_performance(i, g_model, latent_dim)
# create line plot of training history
plot_history(d1_hist, d2_hist, g_hist)
将所有这些结合在一起,下面列出了在 MNIST 手写数字数据集上训练 LSGAN 的完整代码示例。
# example of lsgan for mnist
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras.optimizers import Adam
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import Activation
from keras.layers import LeakyReLU
from keras.layers import BatchNormalization
from keras.initializers import RandomNormal
from matplotlib import pyplot
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='linear', kernel_initializer=init))
# compile model with L2 loss
model.compile(loss='mse', optimizer=Adam(lr=0.0002, beta_1=0.5))
return model
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 256 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Reshape((7, 7, 256)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(BatchNormalization())
model.add(Activation('relu'))
# upsample to 28x28
model.add(Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(BatchNormalization())
model.add(Activation('relu'))
# output 28x28x1
model.add(Conv2D(1, (7,7), padding='same', kernel_initializer=init))
model.add(Activation('tanh'))
return model
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
for layer in discriminator.layers:
if not isinstance(layer, BatchNormalization):
layer.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model with L2 loss
model.compile(loss='mse', optimizer=Adam(lr=0.0002, beta_1=0.5))
return model
# load mnist images
def load_real_samples():
# load dataset
(trainX, _), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
# # select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
filename1 = 'generated_plot_%06d.png' % (step+1)
pyplot.savefig(filename1)
pyplot.close()
# save the generator model
filename2 = 'model_%06d.h5' % (step+1)
g_model.save(filename2)
print('Saved %s and %s' % (filename1, filename2))
# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist):
pyplot.plot(d1_hist, label='dloss1')
pyplot.plot(d2_hist, label='dloss2')
pyplot.plot(g_hist, label='gloss')
pyplot.legend()
filename = 'plot_line_plot_loss.png'
pyplot.savefig(filename)
pyplot.close()
print('Saved %s' % (filename))
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=20, n_batch=64):
# calculate the number of batches per training epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the number of training iterations
n_steps = bat_per_epo * n_epochs
# calculate the size of half a batch of samples
half_batch = int(n_batch / 2)
# lists for storing loss, for plotting later
d1_hist, d2_hist, g_hist = list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# prepare real and fake samples
X_real, y_real = generate_real_samples(dataset, half_batch)
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# update discriminator model
d_loss1 = d_model.train_on_batch(X_real, y_real)
d_loss2 = d_model.train_on_batch(X_fake, y_fake)
# update the generator via the discriminator's error
z_input = generate_latent_points(latent_dim, n_batch)
y_real2 = ones((n_batch, 1))
g_loss = gan_model.train_on_batch(z_input, y_real2)
# summarize loss on this batch
print('>%d, d1=%.3f, d2=%.3f g=%.3f' % (i+1, d_loss1, d_loss2, g_loss))
# record history
d1_hist.append(d_loss1)
d2_hist.append(d_loss2)
g_hist.append(g_loss)
# evaluate the model performance every 'epoch'
if (i+1) % (bat_per_epo * 1) == 0:
summarize_performance(i, g_model, latent_dim)
# create line plot of training history
plot_history(d1_hist, d2_hist, g_hist)
# size of the latent space
latent_dim = 100
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)
注:示例可以在 CPU 上运行,虽然可能需要一段时间,建议在 GPU 硬件上运行。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
运行该示例将报告真实( d1 )和虚假( d2 )示例上的鉴别器的丢失,以及生成的呈现为真实( g )的示例上的鉴别器的生成器的丢失。
这些分数会在每次培训结束时打印出来,预计在整个培训过程中保持较小的值。长时间的零值可能表示故障模式,应重新开始培训过程。
>1, d1=9.292, d2=0.153 g=2.530
>2, d1=1.173, d2=2.057 g=0.903
>3, d1=1.347, d2=1.922 g=2.215
>4, d1=0.604, d2=0.545 g=1.846
>5, d1=0.643, d2=0.734 g=1.619
...
生成的图像图是在每个时期结束时创建的。
运行开始时生成的图像很粗糙。
1 个训练周期后 100 个 LSGAN 生成的手写数字示例
经过几个训练阶段后,生成的图像开始看起来清晰逼真。
请记住:更多的训练时期可能对应于也可能不对应于输出更高质量图像的生成器。查看生成的图,并选择具有最佳图像质量的最终模型。
20 个训练时期后 100 个 LSGAN 生成的手写数字示例
在训练运行结束时,为鉴别器和生成器创建一个学习曲线图。
在这种情况下,我们可以看到训练在整个跑步过程中保持一定的稳定性,观察到一些非常大的峰值,这洗去了情节的规模。
训练期间 LSGAN 中发生器和鉴别器的学习曲线图。
如何用 LSGAN 生成图像
我们可以使用保存的生成器模型按需创建新图像。
这可以通过首先基于图像质量选择最终模型,然后加载它并提供来自潜在空间的新点作为输入,以便从该域生成新的似是而非的图像来实现。
在这种情况下,我们将使用在 20 个时期后保存的模型,或 18,740 (60K/64 或每个时期 937 批次* 20 个时期)训练迭代。
# example of loading the generator model and generating images
from keras.models import load_model
from numpy.random import randn
from matplotlib import pyplot
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
# create a plot of generated images (reversed grayscale)
def plot_generated(examples, n):
# plot images
for i in range(n * n):
# define subplot
pyplot.subplot(n, n, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(examples[i, :, :, 0], cmap='gray_r')
pyplot.show()
# load model
model = load_model('model_018740.h5')
# generate images
latent_points = generate_latent_points(100, 100)
# generate images
X = model.predict(latent_points)
# plot the result
plot_generated(X, 10)
运行该示例会生成 10×10 或 100 个新的看似可信的手写数字。
100 LSGAN 生成的可信手写数字图
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 最小二乘生成对抗网络,2016。
应用程序接口
- 硬数据集接口。
- Keras 顺序模型 API
- Keras 卷积层应用编程接口
- 如何“冻结”Keras 层?
- MatplotLib API
- NumPy 随机采样(numpy.random) API
- NumPy 数组操作例程
文章
- 最小二乘法 GAN ,2017。
- LSGAN 项目(官方),GitHub 。
- Keras-GAN 项目,GitHub 。
摘要
在本教程中,您发现了如何开发最小二乘生成对抗网络。
具体来说,您了解到:
- LSGAN 解决了深度卷积 GAN 的梯度消失和损耗饱和问题。
- LSGAN 可以通过鉴别器模型的均方误差或 L2 损失函数来实现。
- 如何实现为 MNIST 数据集生成手写数字的 LSGAN 模型。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
如何识别和诊断 GAN 故障模式
最后更新于 2021 年 1 月 21 日
生成对抗网络训练中如何识别不稳定模型?
GANs 很难训练。
它们难以训练的原因是生成器模型和鉴别器模型都是在零和博弈中同时训练的。这意味着一个模型的改进是以另一个模型为代价的。
训练两个模型的目标包括在两个相互竞争的关注点之间找到平衡点。
这也意味着,每次更新其中一个模型的参数时,正在解决的优化问题的性质都会发生变化。这具有创建动态系统的效果。用神经网络术语来说,同时训练两个竞争神经网络的技术挑战是它们可能无法收敛。
对于 GAN 模型的正常收敛和 GAN 模型的异常收敛(有时称为失效模式),开发一种直觉是很重要的。
在本教程中,我们将首先为一个简单的图像生成任务开发一个稳定的 GAN 模型,以便建立正常收敛的样子和更普遍的期望。
然后,我们将以不同的方式削弱 GAN 模型,并探索您在训练 GAN 模型时可能遇到的一系列故障模式。这些场景将帮助你形成一种直觉,当一个 GAN 模型训练失败时,你应该寻找什么或者期待什么,以及你可以做什么的想法。
完成本教程后,您将知道:
- 如何从发生器和鉴别器随时间的损耗中识别稳定的 GAN 训练过程。
- 如何通过查看学习曲线和生成的图像来识别模式崩溃?
- 如何通过查看生成器和鉴别器损耗随时间的学习曲线来识别收敛失败。
用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2020 年 8 月更新:在线图上修正标签。
- 2021 年 1 月更新:更新所以层冻结用批量定额。
- 2021 年 1 月更新:简化模型架构,确保我们看到故障。
生成对抗性网络故障模式实用指南 图片由杰森·海夫纳提供,版权所有。
教程概述
本教程分为三个部分;它们是:
- 如何识别稳定的生成对抗网络
- 如何识别生成对抗网络中的模式崩溃
- 如何识别生成对抗网络中的收敛失败
如何培养稳定的生成对抗网络
在本节中,我们将训练一个稳定的 GAN 来生成手写数字的图像。
具体来说,我们将使用 MNIST 手写数字数据集中的数字“8”。
这个模型的结果将建立一个稳定的 GAN,可以用于以后的实验,以及一个轮廓什么产生的图像和学习曲线看起来像一个稳定的 GAN 训练过程。
第一步是定义模型。
鉴别器模型将一幅 28×28 灰度图像作为输入,并输出该图像是真实的(类=1 )还是假的(类=0 )的二进制预测。它被实现为一个适度的卷积神经网络,使用 GAN 设计的最佳实践,例如使用斜率为 0.2 的 LeakyReLU 激活函数、2×2 步距下采样以及学习率为 0.0002、动量为 0.5 的随机梯度下降的 adam 版本
下面的 define_discriminator() 函数实现了这一点,定义并编译鉴别器模型并返回。图像的输入形状被参数化为默认函数参数,以使其清晰。
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
return model
生成器模型将潜在空间中的一个点作为输入,并输出单个 28×28 灰度图像。这是通过使用完全连接的层来解释潜在空间中的点并提供足够的激活来实现的,这些激活可以被重新成形为输出图像的低分辨率版本(例如 7×7)的许多副本(在这种情况下,128 个)。然后对其进行两次上采样,每次使用转置卷积层,将激活区域的大小增加一倍,面积增加四倍。该模型使用了最佳实践,例如 LeakyReLU 激活、作为步长因子的内核大小以及输出层中的双曲正切(tanh)激活函数。
下面的 define_generator() 函数定义了生成器模型,但由于没有直接训练,所以故意不编译,然后返回模型。潜在空间的大小被参数化为函数参数。
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 128 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(LeakyReLU(alpha=0.2))
model.add(Reshape((7, 7, 128)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# upsample to 28x28
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# output 28x28x1
model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
return model
接下来,可以定义一个 GAN 模型,它将生成器模型和鉴别器模型组合成一个更大的模型。这个更大的模型将被用于训练生成器中的模型权重,使用由鉴别器模型计算的输出和误差。鉴别器模型是单独训练的,因此,在这个更大的 GAN 模型中,模型权重被标记为不可训练,以确保只有生成器模型的权重被更新。鉴别器权重的可训练性的这种改变仅在训练组合的 GAN 模型时有效,而在单独训练鉴别器时无效。
这个更大的 GAN 模型将潜在空间中的一个点作为输入,使用生成器模型生成图像,该图像作为输入被馈送到鉴别器模型,然后输出或分类为真实或虚假。
下面的 define_gan() 函数实现了这一点,将已经定义的生成器和鉴别器模型作为输入。
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
discriminator.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt)
return model
现在我们已经定义了 GAN 模型,我们需要对它进行训练。但是,在我们训练模型之前,我们需要输入数据。
第一步是加载和缩放 MNIST 数据集。通过调用 load_data() Keras 函数加载整个数据集,然后选择属于类别 8 的图像子集(约 5,000 个),例如数字 8 的手写描述。然后,像素值必须缩放到范围[-1,1]以匹配生成器模型的输出。
下面的 load_real_samples() 函数实现了这一点,返回 MNIST 训练数据集的加载和缩放子集,为建模做好准备。
# load mnist images
def load_real_samples():
# load dataset
(trainX, trainy), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# select all of the examples for a given class
selected_ix = trainy == 8
X = X[selected_ix]
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
我们将需要数据集的一批(或半批)真实图像来更新 GAN 模型。实现这一点的简单方法是每次从数据集中随机选择一个图像样本。
下面的 generate_real_samples() 函数实现了这一点,以准备好的数据集为自变量,选择并返回一个人脸图像的随机样本,以及它们对应的类别标签给鉴别器,具体为 class=1 表示它们是真实图像。
# select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
接下来,我们需要发电机模型的输入。这些是来自潜在空间的随机点,具体为高斯分布随机变量。
generate _ 潜伏 _points() 函数实现了这一点,将潜伏空间的大小作为自变量和所需的点数,作为生成器模型的一批输入样本返回。
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
接下来,我们需要使用潜在空间中的点作为生成器的输入,以便生成新的图像。
下面的 generate_fake_samples() 函数实现了这一点,将生成器模型和潜在空间的大小作为参数,然后在潜在空间中生成点,并将其用作生成器模型的输入。该函数为鉴别器模型返回生成的图像及其对应的类标签,具体来说,class=0 表示它们是伪造的或生成的。
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
我们需要记录模型的表现。评估 GAN 表现最可靠的方法可能是使用生成器生成图像,然后对其进行审查和主观评估。
下面的*summary _ performance()*函数在训练过程中获取给定点的生成器模型,并使用它在 10×10 的网格中生成 100 个图像,然后绘制并保存到文件中。这个模型现在也保存到文件中,以防我们以后想用它来生成更多的图像。
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
pyplot.savefig('results_baseline/generated_plot_%03d.png' % (step+1))
pyplot.close()
# save the generator model
g_model.save('results_baseline/model_%03d.h5' % (step+1))
除了图像质量,跟踪模型随时间的损失和准确性也是一个好主意。
对于每个模型更新,可以跟踪真假样本鉴别器的损失和分类准确率,对于每个更新,生成器的损失也是如此。然后,这些可以用于在训练结束时创建损耗和准确率的线图。
下面的 plot_history() 函数实现了这一点,并将结果保存到文件中。
# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist):
# plot loss
pyplot.subplot(2, 1, 1)
pyplot.plot(d1_hist, label='d-real')
pyplot.plot(d2_hist, label='d-fake')
pyplot.plot(g_hist, label='gen')
pyplot.legend()
# plot discriminator accuracy
pyplot.subplot(2, 1, 2)
pyplot.plot(a1_hist, label='acc-real')
pyplot.plot(a2_hist, label='acc-fake')
pyplot.legend()
# save plot to file
pyplot.savefig('results_baseline/plot_line_plot_loss.png')
pyplot.close()
我们现在已经准备好适应 GAN 模型。
该模型适用于 10 个训练时期,这是任意的,因为该模型可能在最初的几个时期之后开始生成似是而非的数字 8。使用 128 个样本的批次大小,并且每个训练时期涉及 5,851/128 或大约 45 批次的真实和虚假样本以及对模型的更新。因此,模型被训练了 45 个批次的 10 个时期,或 450 次迭代。
首先对半批真实样本更新鉴别器模型,然后对半批伪样本更新鉴别器模型,共同形成一批权重更新。然后通过复合 GAN 模型更新发生器。重要的是,对于假样本,类标签被设置为 1 或真。这具有更新生成器以更好地生成下一批真实样本的效果。
下面的 train() 函数实现了这一点,将定义的模型、数据集和潜在维度的大小作为参数,并使用默认参数参数化纪元的数量和批处理大小。发电机模型在训练结束时保存。
每次迭代都报告鉴别器和生成器模型的表现。样本图像在每个时期生成并保存,模型表现的线图在运行结束时创建并保存。
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=128):
# calculate the number of batches per epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the total iterations based on batch and epoch
n_steps = bat_per_epo * n_epochs
# calculate the number of samples in half a batch
half_batch = int(n_batch / 2)
# prepare lists for storing stats each iteration
d1_hist, d2_hist, g_hist, a1_hist, a2_hist = list(), list(), list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# get randomly selected 'real' samples
X_real, y_real = generate_real_samples(dataset, half_batch)
# update discriminator model weights
d_loss1, d_acc1 = d_model.train_on_batch(X_real, y_real)
# generate 'fake' examples
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# update discriminator model weights
d_loss2, d_acc2 = d_model.train_on_batch(X_fake, y_fake)
# prepare points in latent space as input for the generator
X_gan = generate_latent_points(latent_dim, n_batch)
# create inverted labels for the fake samples
y_gan = ones((n_batch, 1))
# update the generator via the discriminator's error
g_loss = gan_model.train_on_batch(X_gan, y_gan)
# summarize loss on this batch
print('>%d, d1=%.3f, d2=%.3f g=%.3f, a1=%d, a2=%d' %
(i+1, d_loss1, d_loss2, g_loss, int(100*d_acc1), int(100*d_acc2)))
# record history
d1_hist.append(d_loss1)
d2_hist.append(d_loss2)
g_hist.append(g_loss)
a1_hist.append(d_acc1)
a2_hist.append(d_acc2)
# evaluate the model performance every 'epoch'
if (i+1) % bat_per_epo == 0:
summarize_performance(i, g_model, latent_dim)
plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist)
既然已经定义了所有的函数,我们可以创建存储图像和模型的目录(在本例中为“ results_baseline ”),创建模型,加载数据集,并开始训练过程。
# make folder for results
makedirs('results_baseline', exist_ok=True)
# size of the latent space
latent_dim = 50
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)
将所有这些结合在一起,下面列出了完整的示例。
# example of training a stable gan for generating a handwritten digit
from os import makedirs
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras.optimizers import Adam
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.initializers import RandomNormal
from matplotlib import pyplot
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
return model
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 128 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(LeakyReLU(alpha=0.2))
model.add(Reshape((7, 7, 128)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# upsample to 28x28
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# output 28x28x1
model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
return model
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
discriminator.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt)
return model
# load mnist images
def load_real_samples():
# load dataset
(trainX, trainy), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# select all of the examples for a given class
selected_ix = trainy == 8
X = X[selected_ix]
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
# select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
pyplot.savefig('results_baseline/generated_plot_%03d.png' % (step+1))
pyplot.close()
# save the generator model
g_model.save('results_baseline/model_%03d.h5' % (step+1))
# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist):
# plot loss
pyplot.subplot(2, 1, 1)
pyplot.plot(d1_hist, label='d-real')
pyplot.plot(d2_hist, label='d-fake')
pyplot.plot(g_hist, label='gen')
pyplot.legend()
# plot discriminator accuracy
pyplot.subplot(2, 1, 2)
pyplot.plot(a1_hist, label='acc-real')
pyplot.plot(a2_hist, label='acc-fake')
pyplot.legend()
# save plot to file
pyplot.savefig('results_baseline/plot_line_plot_loss.png')
pyplot.close()
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=128):
# calculate the number of batches per epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the total iterations based on batch and epoch
n_steps = bat_per_epo * n_epochs
# calculate the number of samples in half a batch
half_batch = int(n_batch / 2)
# prepare lists for storing stats each iteration
d1_hist, d2_hist, g_hist, a1_hist, a2_hist = list(), list(), list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# get randomly selected 'real' samples
X_real, y_real = generate_real_samples(dataset, half_batch)
# update discriminator model weights
d_loss1, d_acc1 = d_model.train_on_batch(X_real, y_real)
# generate 'fake' examples
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# update discriminator model weights
d_loss2, d_acc2 = d_model.train_on_batch(X_fake, y_fake)
# prepare points in latent space as input for the generator
X_gan = generate_latent_points(latent_dim, n_batch)
# create inverted labels for the fake samples
y_gan = ones((n_batch, 1))
# update the generator via the discriminator's error
g_loss = gan_model.train_on_batch(X_gan, y_gan)
# summarize loss on this batch
print('>%d, d1=%.3f, d2=%.3f g=%.3f, a1=%d, a2=%d' %
(i+1, d_loss1, d_loss2, g_loss, int(100*d_acc1), int(100*d_acc2)))
# record history
d1_hist.append(d_loss1)
d2_hist.append(d_loss2)
g_hist.append(g_loss)
a1_hist.append(d_acc1)
a2_hist.append(d_acc2)
# evaluate the model performance every 'epoch'
if (i+1) % bat_per_epo == 0:
summarize_performance(i, g_model, latent_dim)
plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist)
# make folder for results
makedirs('results_baseline', exist_ok=True)
# size of the latent space
latent_dim = 50
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)
运行这个例子很快,在没有图形处理器的现代硬件上大约需要 10 分钟。
鉴于学习算法的随机性,您的具体结果会有所不同。尽管如此,培训的总体结构应该非常相似。
首先,在训练循环的每次迭代中,都会向控制台报告鉴别器的损失和准确性以及生成器模型的损失。
这很重要。稳定的 GAN 的鉴别器损耗约为 0.5,通常在 0.5 到 0.7 或 0.8 之间。发电机损耗通常较高,可能在 1.0、1.5、2.0 左右徘徊,甚至更高。
鉴别器在真实和生成(假)图像上的准确度不会是 50%,但通常应该在 70%到 80%之间徘徊。
对于鉴别器和发生器来说,在模型收敛到一个稳定的平衡之前,行为很可能开始不稳定并移动很多。
>1, d1=0.859, d2=0.664 g=0.872, a1=37, a2=59
>2, d1=0.190, d2=1.429 g=0.555, a1=100, a2=10
>3, d1=0.094, d2=1.467 g=0.597, a1=100, a2=4
>4, d1=0.097, d2=1.315 g=0.686, a1=100, a2=9
>5, d1=0.100, d2=1.241 g=0.714, a1=100, a2=9
...
>446, d1=0.593, d2=0.546 g=1.330, a1=76, a2=82
>447, d1=0.551, d2=0.739 g=0.981, a1=82, a2=39
>448, d1=0.628, d2=0.505 g=1.420, a1=79, a2=89
>449, d1=0.641, d2=0.533 g=1.381, a1=60, a2=85
>450, d1=0.550, d2=0.731 g=1.100, a1=76, a2=42
在运行结束时,创建并保存损耗和准确率的线图。
该图包含两个支线剧情。顶部子图显示了真实图像的鉴别器损耗(蓝色)、生成的假图像的鉴别器损耗(橙色)和生成的假图像的发生器损耗(绿色)的线图。
我们可以看到,在 100 到 300 点稳定之前,这三次损失在早期都有些不稳定。此后亏损保持稳定,尽管方差增加。
这是训练中正常或预期损失的一个例子。也就是说,真样本和假样本的鉴别器损耗在 0.5 左右大致相同,发生器的损耗在 0.5 和 2.0 之间略高。如果生成器模型能够生成似是而非的图像,那么预期这些图像将在 100 到 300 个时代之间生成,并且也可能在 300 到 450 个时代之间生成。
底部子图显示了训练期间真实(蓝色)和假(橙色)图像上鉴别器准确度的线图。我们看到一个类似的结构作为损失的子图,即两种图像类型之间的准确率开始时非常不同,然后在 100 到 300 个时期之间稳定在大约 70%到 80%,并且在此之后保持稳定,尽管方差增加。
这些模式和绝对值的时间尺度(例如迭代次数或训练时期)将因 GAN 模型的问题和类型而异,尽管该图为训练稳定的 GAN 模型时的预期提供了良好的基线。
稳定生成对抗网络的损失和准确率线图
最后,我们可以查看生成图像的样本。注意:我们使用反向灰度色图生成图像,这意味着背景上的正常白色图形被反转为白色背景上的黑色图形。这样做是为了使生成的数字更容易查看。
正如我们所料,纪元 100 之前生成的图像样本质量相对较差。
来自稳定 GAN 的 100 个手写数字 8 在纪元 45 的生成图像样本。
在 100 到 300 个时代之间产生的图像样本是可信的,也许是最好的质量。
来自稳定 GAN 的 100 个手写数字 8 在纪元 180 的生成图像样本。
并且在时期 300 之后生成的图像的样本仍然是可信的,尽管可能具有更多的噪声,例如背景噪声。
来自稳定 GAN 的 100 个手写数字 8 在纪元 450 的生成图像样本。
这些结果很重要,因为它强调了即使在训练过程变得稳定之后,所产生的质量也可以并且确实在整个过程中变化。
超过某个训练稳定性点的更多训练迭代可能会也可能不会产生更高质量的图像。
对于稳定的 GAN 训练,我们可以将这些观察总结如下:
- 真假图像的鉴别器损失预计在 0.5 左右。
- 假图像的发电机损耗预计在 0.5 到 2.0 之间。
- 真假图像的鉴别准确率预计在 80%左右。
- 发生器和鉴别器损耗的变化预计保持适度。
- 该发生器有望在稳定期间产生最高质量的图像。
- 训练稳定性可能退化为高方差损失期和相应的低质量生成图像。
现在我们有了一个稳定的 GAN 模型,我们可以考虑修改它来产生一些特定的故障案例。
在新问题上训练 GAN 模型时,有两个常见的失败案例;它们是模式崩溃和收敛失败。
如何识别生成对抗网络中的模式崩溃
模式崩溃指的是一个生成器模型,它只能生成一个或一小部分不同的结果或模式。
这里,模式指的是输出分布,例如,多模式函数指的是具有多于一个峰值或最优值的函数。对于 GAN 生成器模型,模式失败意味着输入潜在空间中的大量点(例如,在许多情况下为 100 维的超球)导致生成图像的一个或一个小子集。
模式崩溃,也称为场景,是发生在生成器学习将几个不同的输入 z 值映射到同一个输出点时的问题。
——NIPS 2016 教程:生成对抗网络,2016。
查看生成图像的大样本时,可以识别模式崩溃。图像将表现出低多样性,相同的图像或相同图像的相同小子集重复多次。
也可以通过查看模型损失的线图来识别模式崩溃。线图将显示损耗随时间的振荡,尤其是在发电机模型中,因为发电机模型被更新,并从一个发电模式跳到另一个具有不同损耗的模型。
我们可以通过多种方式削弱我们稳定的 GAN,使其遭受模式崩溃。也许最可靠的方法是直接限制潜在维度的大小,迫使模型只生成看似合理的输出的一小部分。
具体来说,‘潜伏 _ dim’变量可以从 100 变为 1,实验重新运行。
# size of the latent space
latent_dim = 1
为了完整起见,下面提供了完整的代码列表。
# example of training an unstable gan for generating a handwritten digit
from os import makedirs
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras.optimizers import Adam
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.initializers import RandomNormal
from matplotlib import pyplot
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
return model
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 128 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(LeakyReLU(alpha=0.2))
model.add(Reshape((7, 7, 128)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# upsample to 28x28
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# output 28x28x1
model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
return model
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
discriminator.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt)
return model
# load mnist images
def load_real_samples():
# load dataset
(trainX, trainy), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# select all of the examples for a given class
selected_ix = trainy == 8
X = X[selected_ix]
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
# # select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
pyplot.savefig('results_collapse/generated_plot_%03d.png' % (step+1))
pyplot.close()
# save the generator model
g_model.save('results_collapse/model_%03d.h5' % (step+1))
# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist):
# plot loss
pyplot.subplot(2, 1, 1)
pyplot.plot(d1_hist, label='d-real')
pyplot.plot(d2_hist, label='d-fake')
pyplot.plot(g_hist, label='gen')
pyplot.legend()
# plot discriminator accuracy
pyplot.subplot(2, 1, 2)
pyplot.plot(a1_hist, label='acc-real')
pyplot.plot(a2_hist, label='acc-fake')
pyplot.legend()
# save plot to file
pyplot.savefig('results_collapse/plot_line_plot_loss.png')
pyplot.close()
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=128):
# calculate the number of batches per epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the total iterations based on batch and epoch
n_steps = bat_per_epo * n_epochs
# calculate the number of samples in half a batch
half_batch = int(n_batch / 2)
# prepare lists for storing stats each iteration
d1_hist, d2_hist, g_hist, a1_hist, a2_hist = list(), list(), list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# get randomly selected 'real' samples
X_real, y_real = generate_real_samples(dataset, half_batch)
# update discriminator model weights
d_loss1, d_acc1 = d_model.train_on_batch(X_real, y_real)
# generate 'fake' examples
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# update discriminator model weights
d_loss2, d_acc2 = d_model.train_on_batch(X_fake, y_fake)
# prepare points in latent space as input for the generator
X_gan = generate_latent_points(latent_dim, n_batch)
# create inverted labels for the fake samples
y_gan = ones((n_batch, 1))
# update the generator via the discriminator's error
g_loss = gan_model.train_on_batch(X_gan, y_gan)
# summarize loss on this batch
print('>%d, d1=%.3f, d2=%.3f g=%.3f, a1=%d, a2=%d' %
(i+1, d_loss1, d_loss2, g_loss, int(100*d_acc1), int(100*d_acc2)))
# record history
d1_hist.append(d_loss1)
d2_hist.append(d_loss2)
g_hist.append(g_loss)
a1_hist.append(d_acc1)
a2_hist.append(d_acc2)
# evaluate the model performance every 'epoch'
if (i+1) % bat_per_epo == 0:
summarize_performance(i, g_model, latent_dim)
plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist)
# make folder for results
makedirs('results_collapse', exist_ok=True)
# size of the latent space
latent_dim = 1
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)
运行该示例将报告每一步训练的损失和准确性,就像以前一样。
在这种情况下,鉴频器的损耗位于一个合理的范围内,尽管发生器的损耗会上下跳动。鉴别器的准确度也显示出更高的值,许多值在 100%左右,这意味着对于许多批次,它在识别真实或虚假例子方面具有完美的技能,这是图像质量或多样性的一个不好的迹象。
>1, d1=0.963, d2=0.699 g=0.614, a1=28, a2=54
>2, d1=0.185, d2=5.084 g=0.097, a1=96, a2=0
>3, d1=0.088, d2=4.861 g=0.065, a1=100, a2=0
>4, d1=0.077, d2=4.202 g=0.090, a1=100, a2=0
>5, d1=0.062, d2=3.533 g=0.128, a1=100, a2=0
...
>446, d1=0.277, d2=0.261 g=0.684, a1=95, a2=100
>447, d1=0.201, d2=0.247 g=0.713, a1=96, a2=100
>448, d1=0.285, d2=0.285 g=0.728, a1=89, a2=100
>449, d1=0.351, d2=0.467 g=1.184, a1=92, a2=81
>450, d1=0.492, d2=0.388 g=1.351, a1=76, a2=100
创建并保存带有学习曲线和准确率线图的图形。
在顶部的子图中,我们可以看到发电机(绿色)的损耗随着时间从可感知值振荡到高值,大约有 25 次模型更新(批次)。我们还可以在真样本和假样本(橙色和蓝色)上看到鉴别器损耗的一些小振荡。
在底部的子图中,我们可以看到鉴别器识别假图像的分类准确率在整个运行过程中保持很高。这表明生成器在以某种一致的方式生成示例方面很差,这使得鉴别器很容易识别假图像。
具有模式崩溃的生成对抗网络的损失和准确率线图
查看生成的图像显示了模式崩溃的预期特征,即许多相同的生成示例,而不考虑潜在空间中的输入点。碰巧的是,我们把潜在空间的维度改变得非常小,以至于产生了这种效果。
我选择了一个生成图像的例子来帮助弄清楚这一点。图像中似乎只有几种 8 字形,一种向左倾斜,一种向右倾斜,还有一种模糊地坐起来。
我已经围绕下图中的一些类似示例绘制了方框,以使其更加清晰。
100 张手写数字 8 在 315 时代的图像样本,来自遭受模式崩溃的 GAN。
根据 DCGAN 模型架构和训练配置的发现,模式崩溃在训练期间不太常见。
总之,您可以按如下方式识别模式折叠:
- 发生器的损耗,可能还有鉴别器的损耗,预计会随时间振荡。
- 生成器模型被期望从潜在空间中的不同点生成相同的输出图像。
如何识别生成对抗网络中的收敛失败
也许训练 GAN 最常见的失败是无法收敛。
典型地,当模型损失在训练过程中没有稳定下来时,神经网络不能收敛。在 GAN 的情况下,无法收敛是指在鉴别器和发生器之间找不到平衡。
您识别这种故障的可能方式是鉴别器的损耗已经变为零或接近于零。在某些情况下,发电机的损耗也可能上升,并在同一时期继续上升。
这种类型的丢失最常见的原因是生成器输出垃圾图像,鉴别器可以很容易地识别出来。
这种失败可能发生在跑步开始时,并在整个训练过程中持续,此时您应该停止训练过程。对于一些不稳定的 GAN,GAN 可能会因为多次批量更新,甚至多次时代更新而陷入这种故障模式,然后恢复。
有很多方法可以削弱我们稳定的 GAN 来实现收敛失败,例如将一个或两个模型更改为容量不足,将 Adam 优化算法更改为过于激进,以及在模型中使用非常大或非常小的内核大小。
在这种情况下,我们将更新示例,以便在更新鉴别器时结合真实和虚假样本。这个简单的改变会导致模型无法收敛。
这个变化很简单,使用 vstack() NumPy 函数将真假样本结合,然后调用 train_on_batch() 函数更新鉴别器模型。结果也是单一的损失和准确性分数,这意味着模型表现的报告也必须更新。
为了完整起见,下面提供了包含这些更改的完整代码列表。
# example of training an unstable gan for generating a handwritten digit
from os import makedirs
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy import vstack
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras.optimizers import Adam
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.initializers import RandomNormal
from matplotlib import pyplot
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
return model
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 128 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(LeakyReLU(alpha=0.2))
model.add(Reshape((7, 7, 128)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# upsample to 28x28
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# output 28x28x1
model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
return model
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
discriminator.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt)
return model
# load mnist images
def load_real_samples():
# load dataset
(trainX, trainy), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# select all of the examples for a given class
selected_ix = trainy == 8
X = X[selected_ix]
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
# # select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
pyplot.savefig('results_convergence/generated_plot_%03d.png' % (step+1))
pyplot.close()
# save the generator model
g_model.save('results_convergence/model_%03d.h5' % (step+1))
# create a line plot of loss for the gan and save to file
def plot_history(d_hist, g_hist, a_hist):
# plot loss
pyplot.subplot(2, 1, 1)
pyplot.plot(d_hist, label='dis')
pyplot.plot(g_hist, label='gen')
pyplot.legend()
# plot discriminator accuracy
pyplot.subplot(2, 1, 2)
pyplot.plot(a_hist, label='acc')
pyplot.legend()
# save plot to file
pyplot.savefig('results_convergence/plot_line_plot_loss.png')
pyplot.close()
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=128):
# calculate the number of batches per epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the total iterations based on batch and epoch
n_steps = bat_per_epo * n_epochs
# calculate the number of samples in half a batch
half_batch = int(n_batch / 2)
# prepare lists for storing stats each iteration
d_hist, g_hist, a_hist = list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# get randomly selected 'real' samples
X_real, y_real = generate_real_samples(dataset, half_batch)
# generate 'fake' examples
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# combine into one batch
X, y = vstack((X_real, X_fake)), vstack((y_real, y_fake))
# update discriminator model weights
d_loss, d_acc = d_model.train_on_batch(X, y)
# prepare points in latent space as input for the generator
X_gan = generate_latent_points(latent_dim, n_batch)
# create inverted labels for the fake samples
y_gan = ones((n_batch, 1))
# update the generator via the discriminator's error
g_loss = gan_model.train_on_batch(X_gan, y_gan)
# summarize loss on this batch
print('>%d, d=%.3f, g=%.3f, a=%d' % (i+1, d_loss, g_loss, int(100*d_acc)))
# record history
d_hist.append(d_loss)
g_hist.append(g_loss)
a_hist.append(d_acc)
# evaluate the model performance every 'epoch'
if (i+1) % bat_per_epo == 0:
summarize_performance(i, g_model, latent_dim)
plot_history(d_hist, g_hist, a_hist)
# make folder for results
makedirs('results_convergence', exist_ok=True)
# size of the latent space
latent_dim = 50
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)
运行该示例会报告每个模型更新的损失和准确性。
这种故障的一个明显标志是鉴频器损耗迅速下降到零,并保持在零上。
这就是我们在这个案例中看到的。
>1, d=0.514, g=0.969, a=80
>2, d=0.475, g=0.395, a=74
>3, d=0.452, g=0.223, a=69
>4, d=0.302, g=0.220, a=85
>5, d=0.177, g=0.195, a=100
>6, d=0.122, g=0.200, a=100
>7, d=0.088, g=0.179, a=100
>8, d=0.075, g=0.159, a=100
>9, d=0.071, g=0.167, a=100
>10, d=0.102, g=0.127, a=100
...
>446, d=0.000, g=0.001, a=100
>447, d=0.000, g=0.001, a=100
>448, d=0.000, g=0.001, a=100
>449, d=0.000, g=0.001, a=100
>450, d=0.000, g=0.001, a=100
创建学习曲线和分类准确率的线图。
顶部的子图显示了鉴别器(蓝色)和生成器(橙色)的损失,并清楚地显示了在最初的 20 到 30 次迭代中,这两个值向零下降,在剩余的运行中保持不变。
底部的子图显示了同一时期的鉴别器分类准确率为 100%,这意味着该模型在识别真假图像方面是完美的。人们的期望是,假图像有一些东西使鉴别者很容易识别它们。
收敛失败的生成对抗网络的损失和准确率线图
最后,查看生成图像的样本可以清楚地知道为什么鉴别器如此成功。
每个时期生成的图像样本质量都很低,呈现静态,背景中可能有一个模糊的数字 8。
通过对鉴别器的组合更新,从具有收敛失败的 GAN 获得的 100 个在时期 450 的手写数字 8 的生成图像样本。
看到这种失败的另一个例子是很有用的。
在这种情况下, Adam 优化算法的配置可以修改为使用默认值,这反过来使得模型的更新具有攻击性,并导致训练过程无法在训练两个模型之间找到平衡点。
例如,鉴别器可以编译如下:
...
# compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
复合 GAN 模型可以编制如下:
...
# compile model
model.compile(loss='binary_crossentropy', optimizer='adam')
为了完整起见,下面提供了完整的代码列表。
# example of training an unstable gan for generating a handwritten digit
from os import makedirs
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.initializers import RandomNormal
from matplotlib import pyplot
# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# downsample to 14x14
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=in_shape))
model.add(LeakyReLU(alpha=0.2))
# downsample to 7x7
model.add(Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# classifier
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
# compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# define the standalone generator model
def define_generator(latent_dim):
# weight initialization
init = RandomNormal(stddev=0.02)
# define model
model = Sequential()
# foundation for 7x7 image
n_nodes = 128 * 7 * 7
model.add(Dense(n_nodes, kernel_initializer=init, input_dim=latent_dim))
model.add(LeakyReLU(alpha=0.2))
model.add(Reshape((7, 7, 128)))
# upsample to 14x14
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# upsample to 28x28
model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init))
model.add(LeakyReLU(alpha=0.2))
# output 28x28x1
model.add(Conv2D(1, (7,7), activation='tanh', padding='same', kernel_initializer=init))
return model
# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
# make weights in the discriminator not trainable
discriminator.trainable = False
# connect them
model = Sequential()
# add generator
model.add(generator)
# add the discriminator
model.add(discriminator)
# compile model
model.compile(loss='binary_crossentropy', optimizer='adam')
return model
# load mnist images
def load_real_samples():
# load dataset
(trainX, trainy), (_, _) = load_data()
# expand to 3d, e.g. add channels
X = expand_dims(trainX, axis=-1)
# select all of the examples for a given class
selected_ix = trainy == 8
X = X[selected_ix]
# convert from ints to floats
X = X.astype('float32')
# scale from [0,255] to [-1,1]
X = (X - 127.5) / 127.5
return X
# select real samples
def generate_real_samples(dataset, n_samples):
# choose random instances
ix = randint(0, dataset.shape[0], n_samples)
# select images
X = dataset[ix]
# generate class labels
y = ones((n_samples, 1))
return X, y
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
# generate points in the latent space
x_input = randn(latent_dim * n_samples)
# reshape into a batch of inputs for the network
x_input = x_input.reshape(n_samples, latent_dim)
return x_input
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
# generate points in latent space
x_input = generate_latent_points(latent_dim, n_samples)
# predict outputs
X = generator.predict(x_input)
# create class labels
y = zeros((n_samples, 1))
return X, y
# generate samples and save as a plot and save the model
def summarize_performance(step, g_model, latent_dim, n_samples=100):
# prepare fake examples
X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot images
for i in range(10 * 10):
# define subplot
pyplot.subplot(10, 10, 1 + i)
# turn off axis
pyplot.axis('off')
# plot raw pixel data
pyplot.imshow(X[i, :, :, 0], cmap='gray_r')
# save plot to file
pyplot.savefig('results_opt/generated_plot_%03d.png' % (step+1))
pyplot.close()
# save the generator model
g_model.save('results_opt/model_%03d.h5' % (step+1))
# create a line plot of loss for the gan and save to file
def plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist):
# plot loss
pyplot.subplot(2, 1, 1)
pyplot.plot(d1_hist, label='d-real')
pyplot.plot(d2_hist, label='d-fake')
pyplot.plot(g_hist, label='gen')
pyplot.legend()
# plot discriminator accuracy
pyplot.subplot(2, 1, 2)
pyplot.plot(a1_hist, label='acc-real')
pyplot.plot(a2_hist, label='acc-fake')
pyplot.legend()
# save plot to file
pyplot.savefig('results_opt/plot_line_plot_loss.png')
pyplot.close()
# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=10, n_batch=128):
# calculate the number of batches per epoch
bat_per_epo = int(dataset.shape[0] / n_batch)
# calculate the total iterations based on batch and epoch
n_steps = bat_per_epo * n_epochs
# calculate the number of samples in half a batch
half_batch = int(n_batch / 2)
# prepare lists for storing stats each iteration
d1_hist, d2_hist, g_hist, a1_hist, a2_hist = list(), list(), list(), list(), list()
# manually enumerate epochs
for i in range(n_steps):
# get randomly selected 'real' samples
X_real, y_real = generate_real_samples(dataset, half_batch)
# update discriminator model weights
d_loss1, d_acc1 = d_model.train_on_batch(X_real, y_real)
# generate 'fake' examples
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
# update discriminator model weights
d_loss2, d_acc2 = d_model.train_on_batch(X_fake, y_fake)
# prepare points in latent space as input for the generator
X_gan = generate_latent_points(latent_dim, n_batch)
# create inverted labels for the fake samples
y_gan = ones((n_batch, 1))
# update the generator via the discriminator's error
g_loss = gan_model.train_on_batch(X_gan, y_gan)
# summarize loss on this batch
print('>%d, d1=%.3f, d2=%.3f g=%.3f, a1=%d, a2=%d' %
(i+1, d_loss1, d_loss2, g_loss, int(100*d_acc1), int(100*d_acc2)))
# record history
d1_hist.append(d_loss1)
d2_hist.append(d_loss2)
g_hist.append(g_loss)
a1_hist.append(d_acc1)
a2_hist.append(d_acc2)
# evaluate the model performance every 'epoch'
if (i+1) % bat_per_epo == 0:
summarize_performance(i, g_model, latent_dim)
plot_history(d1_hist, d2_hist, g_hist, a1_hist, a2_hist)
# make folder for results
makedirs('results_opt', exist_ok=True)
# size of the latent space
latent_dim = 50
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
print(dataset.shape)
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)
像以前一样,运行示例会报告训练期间每个步骤的损失和准确性。
正如我们预期的那样,鉴别器的损失迅速下降到接近于零的值,并且鉴别器在真实和虚假例子上的分类准确率保持在 100%。
>1, d1=0.728, d2=0.902 g=0.763, a1=54, a2=12
>2, d1=0.001, d2=4.509 g=0.033, a1=100, a2=0
>3, d1=0.000, d2=0.486 g=0.542, a1=100, a2=76
>4, d1=0.000, d2=0.446 g=0.733, a1=100, a2=82
>5, d1=0.002, d2=0.855 g=0.649, a1=100, a2=46
...
>446, d1=0.000, d2=0.000 g=10.410, a1=100, a2=100
>447, d1=0.000, d2=0.000 g=10.414, a1=100, a2=100
>448, d1=0.000, d2=0.000 g=10.419, a1=100, a2=100
>449, d1=0.000, d2=0.000 g=10.424, a1=100, a2=100
>450, d1=0.000, d2=0.000 g=10.427, a1=100, a2=100
创建了通过使用这一单一变化来训练模型的学习曲线和准确度的图。
该图显示,这种变化导致鉴频器的损耗降至接近零的值,并保持在该值。这种情况的一个重要区别是,发电机的损耗上升很快,并在训练期间继续上升。
激进优化导致收敛失败的生成对抗网络的损失和准确率线图
我们可以按如下方式查看收敛失败的属性:
- 鉴频器的损耗预计会迅速降低到接近零的值,在训练过程中仍保持在该值。
- 发电机的损耗预计会在训练期间减少到零或持续减少。
- 该发生器预计会产生极低质量的图像,很容易被鉴别器识别为假图像。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 生成对抗网络,2014。
- 教程:生成对抗网络,NIPS ,2016。
- 深度卷积生成对抗网络的无监督表示学习,2015。
文章
摘要
在本教程中,您通过查看生成的图像示例和培训期间记录的指标图,发现了如何识别稳定和不稳定的 GAN 培训。
具体来说,您了解到:
- 如何从发生器和鉴别器随时间的损耗中识别稳定的 GAN 训练过程。
- 如何通过查看学习曲线和生成的图像来识别模式崩溃?
- 如何通过查看生成器和鉴别器损耗随时间的学习曲线来识别收敛失败。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。