生成对抗网络学习指南-全-
生成对抗网络学习指南(全)
原文:
annas-archive.org/md5/0e2dfbc468b1899221a88758597af0ce译者:飞龙
前言
本书中回顾的概念和模型将帮助构建并有效地将深度网络从监督学习任务(如图像分类)带入到具有创造性能力的无监督学习领域。
运用基本的生成网络概念,您将学会如何从无标签数据中生成真实图像,如何从文本描述合成图像,以及如何发掘不同领域之间的关系进行风格迁移。
本书内容概述
第一章,深度学习简介,用简单的方式讲解了与深度学习相关的基础概念和术语,避免过多的数学公式和方程式。同时,本章还展示了深度学习网络如何随着时间的推移不断演变,以及它们如何通过生成模型的出现,在无监督领域取得突破。
第二章,使用 GAN 进行无监督学习,展示了生成对抗网络(GAN)的工作原理,并讲解了 GAN 的基本构成元素。本章还展示了如何在半监督领域使用深度学习网络,并将其应用于图像生成和创造性工作。由于 GAN 的训练较为困难,本章探讨了一些改进训练和学习过程的技术。
第三章,跨多个领域转移图像风格,介绍了如何利用简单但强大的 CGAN 和 CycleGAN 模型进行创造性工作。它解释了如何使用条件生成对抗网络(Conditional GAN)基于特定特征或条件生成图像。本章还讨论了如何通过使用 BEGAN 稳定网络训练来克服模型崩溃问题。最后,本章还讲解了如何使用 CycleGAN 进行跨不同领域的风格迁移(如苹果到橙子,马到斑马)。
第四章,从文本生成真实图像,介绍了将生成对抗网络(GAN)分阶段堆叠的最新方法,利用 StackGAN 将文本到图像合成的问题分解为两个更易管理的子问题。本章还展示了 DiscoGAN 如何成功地跨多个领域转换风格,从给定的鞋子图像生成手袋图像,或进行名人图像的性别转化。
第五章,使用各种生成模型生成图像,介绍了预训练模型的概念,并讨论了如何在大规模分布式系统中使用 Apache Spark 运行深度学习和生成模型。然后,我们将使用预训练的 GAN 模型提高低质量图像的分辨率。最后,我们将学习其他种类的生成模型,如 DeepDream 和 VAE,用于图像生成和风格化。
第六章,将机器学习投入生产,描述了将基于机器学习和深度学习的智能应用部署到生产环境中的各种方法,涵盖了数据中心和云端,采用基于微服务的容器化或无服务器技术。
本书所需的内容
本书中使用的所有工具、库和数据集都是开源的,免费提供。一些书中使用的云环境也提供免费试用供评估。通过本书以及一定程度的机器学习(或深度学习)基础,读者将能够通过生成对抗网络深入了解深度学习的创造性。
您需要安装 Python 和一些额外的 Python 包,并使用pip来有效地运行本书中呈现的代码示例。
本书适合的人群
本书的目标读者是那些希望探索生成模型的强大功能,利用无标签原始数据或噪声生成逼真图像的机器学习专家和数据科学家。
拥有足够机器学习和神经网络知识,并接触过 Python 编程语言的读者,可以使用本书学习如何从文本合成图像,或自动发现相似领域之间的关系,并通过基于深度学习的生成架构探索无监督领域。
约定
本书中,您将看到多种文本样式,它们区分不同类型的信息。以下是这些样式的一些示例及其含义的解释。
导入必要的包和库模块的代码块如下所示:
<div class="packt_code">
nsamples=6
Z_sample = sample_Z(nsamples, noise_dim)
y_sample = np.zeros(shape=[nsamples, num_labels])
y_sample[:, 7] = 1 # generating image based on label
samples = sess.run(G_sample, feed_dict={Z: Z_sample, Y:y_sample})
</div>
新术语和重要词汇用粗体显示。例如,您在屏幕上看到的、菜单或对话框中的词汇,文本中会像这样出现:“点击下一步按钮将您带到下一个页面。”
注意
警告或重要说明会以这样的框形式出现。
提示
提示和技巧像这样显示。
读者反馈
我们始终欢迎读者的反馈。让我们知道您对本书的看法——您喜欢什么,或者可能不喜欢什么。读者反馈对我们开发能够真正让您获益的书籍至关重要。
要发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并通过邮件主题提及书名。
如果您在某个领域拥有专业知识,并且有兴趣编写或为书籍做出贡献,请查看我们在www.packtpub.com/authors上的作者指南。
客户支持
现在,作为 Packt 书籍的自豪拥有者,我们提供了许多资源帮助您充分利用您的购买。
下载示例代码
您可以从www.packtpub.com的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support,并注册以便直接通过电子邮件收到文件。
您可以按照以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持标签上。
-
点击代码下载与勘误。
-
在搜索框中输入书籍名称。
-
选择您希望下载代码文件的书籍。
-
从下拉菜单中选择你购买此书的地点。
-
点击代码下载。
您也可以通过点击本书页面上的代码文件按钮下载代码文件。可以通过在搜索框中输入书名来访问该页面。请注意,您需要登录到您的 Packt 账户。
文件下载完成后,请确保使用以下最新版本的工具解压或提取文件夹:
-
适用于 Windows 的 WinRAR / 7-Zip。
-
适用于 Mac 的 Zipeg / iZip / UnRarX。
-
适用于 Linux 的 7-Zip / PeaZip。
本书的代码包也托管在 GitHub 上,链接为github.com/PacktPublishing/Learning–Generative–Adversarial–Networks。我们还有其他丰富的书籍和视频代码包,可以在github.com/PacktPublishing/找到,快来看看吧!
下载本书的彩色图像
我们还为您提供了一份 PDF 文件,包含本书中使用的截图/图表的彩色图像。彩色图像将帮助您更好地理解输出结果的变化。您可以从www.packtpub.com/sites/default/files/downloads/LearningGenerativeAdversarialNetworks_ColorImages.pdf下载该文件。
勘误
尽管我们已尽最大努力确保内容的准确性,但错误仍然会发生。如果您在本书中发现错误——例如文本或代码中的错误——我们将非常感激您向我们报告。通过这样做,您可以避免其他读者的困扰,并帮助我们改进本书的后续版本。如果您发现任何勘误,请访问www.packtpub.com/submit-errata报告,选择您的书籍,点击勘误提交表单链接,并输入勘误的详细信息。一旦勘误被验证,您的提交将被接受,并且勘误将上传到我们的网站,或添加到该书籍的现有勘误列表中。
要查看先前提交的勘误表,请访问 www.packtpub.com/books/content/support 并在搜索框中输入书名。所需信息将显示在勘误表部分下。
盗版
互联网上侵犯版权材料的盗版问题是所有媒体的持续问题。在 Packt,我们非常重视版权和许可的保护。如果您在互联网上发现我们作品的任何非法副本,请立即提供地址或网站名称,以便我们采取措施。
请通过 <copyright@packtpub.com> 联系我们,提供涉嫌侵权材料的链接。
我们感谢您在保护我们的作者和为您带来有价值的内容方面提供的帮助。
问题
如果您对本书的任何方面有问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章:深度学习简介
深度神经网络目前能够为许多问题提供接近人类水平的解决方案,例如图像识别、语音识别、机器翻译、自然语言处理等等。
本章我们将探讨神经网络这一生物启发式架构是如何在这些年中发展的。接下来,我们将回顾一些与深度学习相关的重要概念和术语,为后续章节做准备。最后,我们将通过生成模型理解深度网络的创造性本质背后的直觉。
本章我们将讨论以下主题:
-
深度学习的演变
-
随机梯度下降、ReLU、学习率等
-
卷积网络、递归神经网络和长短期记忆网络(LSTM)
-
判别模型与生成模型的区别
深度学习的演变
许多关于神经网络的重要研究发生在 80 年代和 90 年代,但那时计算机速度很慢,数据集非常小。这些研究在现实世界中并没有找到很多应用。因此,在 21 世纪的第一个十年,神经网络完全从机器学习的世界中消失了。直到最近几年,首先是在 2009 年左右的语音识别,然后是在 2012 年左右的计算机视觉领域,神经网络才迎来了一次大的复兴(比如 LeNet、AlexNet 等)。发生了什么变化?
大量数据(大数据)和廉价、高速的 GPU。如今,神经网络无处不在。因此,如果你从事任何与数据、分析或预测相关的工作,深度学习绝对是你需要熟悉的领域。
请参见以下图示:

图 1:深度学习的演变
深度学习是机器学习的一个令人兴奋的分支,它利用大量的数据来教会计算机执行以前只有人类能够完成的任务,比如识别图像中的内容、理解人们在打电话时说的话、将文档翻译成另一种语言,以及帮助机器人探索世界并与之互动。深度学习已成为解决感知问题的核心工具,并且在计算机视觉和语音识别领域处于最前沿。
如今,许多公司已经将深度学习作为其机器学习工具包的核心部分——Facebook、百度、亚马逊、微软和谷歌都在其产品中使用深度学习,因为深度学习在数据量大且问题复杂的场景中具有显著优势。
深度学习通常是我们用来指代由多个层组成的“深度神经网络”的名称。每一层由节点组成。计算发生在节点中,它们将输入数据与一组参数或权重结合起来,这些参数或权重要么放大输入,要么减少输入。然后将这些输入权重的乘积求和,并通过activation函数传递,以确定数值应如何通过网络以影响最终预测,如分类等操作。一层由一行节点组成,这些节点在输入通过网络时打开或关闭。第一层的输入成为第二层的输入,依此类推。以下是神经网络可能看起来的图示:

让我们熟悉一些深度神经网络的概念和术语。
sigmoid 激活
在神经网络中使用的 sigmoid 激活函数具有输出边界(0, 1),α是偏移参数,用于设置 sigmoid 评估为 0 的值。
对于梯度下降来说,sigmoid 函数在输入数据x保持在限制范围内时通常效果良好。对于x的大值,y是常数。因此,导数dy/dx(梯度)等于0,这通常称为梯度消失问题。
这是一个问题,因为当梯度为 0 时,将其与损失(实际值-预测值)相乘也会得到 0,最终网络停止学习。
修正线性单元(ReLU)
可以通过将一些线性分类器与一些非线性函数组合来构建神经网络。修正线性单元(ReLU)在过去几年中变得非常流行。它计算函数f(x)=max(0,x)。换句话说,激活在零处被阈值化。不幸的是,ReLU 单元在训练过程中可能会变得脆弱并死亡,因为 ReLU 神经元可能导致权重更新的方式使得神经元永远不会在任何数据点上激活,因此从那时起通过单元的梯度将永远为零。
为了克服这个问题,一个带有小负斜率(大约 0.01)的渗漏ReLU函数将在x<0时具有非零斜率:

其中αα是一个小常数。

图-2:修正线性单元
指数线性单元(ELU)
ReLU 激活的均值不为零,因此有时会使网络学习困难。指数线性单元(ELU)与 ReLU 激活函数类似,当输入x为正时,但对于负值,它是一个被固定值-1界定的函数,对于α=1(超参数α控制 ELU 对负输入饱和的值)。这种行为有助于将神经元的均值激活推向接近零的位置;这有助于学习更能抵抗噪声的表示。
随机梯度下降(SGD)
批量梯度下降的扩展比较繁琐,因为当数据集较大时,需要进行大量的计算。作为经验法则,如果计算损失需要 n 次浮点运算,那么计算梯度大约需要三倍的运算量。
但在实际操作中,我们希望能够训练大量的数据,因为在真实问题中,我们总是能从使用更多数据中获得更多的收益。而且因为梯度下降是迭代的,并且需要进行许多步更新,这意味着为了在单步中更新参数,必须遍历所有数据样本,然后对数据进行数十次或数百次的迭代。
与其在每一步都计算整个数据集的损失,我们可以计算一个非常小的随机训练数据子集的平均损失。每次选择的样本数在 1 到 1000 之间。这种技术叫做随机梯度下降(SGD),是深度学习的核心。这是因为 SGD 在数据和模型大小上都能很好地扩展。
SGD 因其有很多超参数需要调整而被认为是黑魔法,例如初始化参数、学习率参数、衰减和动量,你必须正确调整这些参数。
AdaGrad 是 SGD 的一个简单修改,它隐式地实现了动量和学习率衰减。使用 AdaGrad 通常使学习对超参数的敏感度降低。但它往往比精确调优的带动量的 SGD 要稍微差一点。不过,如果你只是想让模型运行起来,它仍然是一个非常好的选择:

图 4a:批量梯度下降和 SGD 中的损失计算
来源:www.coursera.org/learn/machine-learning/lecture/DoRHJ/stochasticgradient-descent

图 4b:随机梯度下降和 AdaGrad
从 图 4a 中可以看出,批量梯度下降的 loss/优化 函数已经得到很好的最小化,而 SGD 在每一步随机选择数据子集来计算损失,往往在该点附近震荡。在实际应用中,这并不算太糟,SGD 往往会更快地收敛。
学习率调优
神经网络的 loss 函数可以与一个表面相关联,其中网络的权重表示可以移动的每个方向。梯度下降提供了当前坡度方向上的步长,而学习率则决定了每一步的长度。学习率帮助网络抛弃旧的信念,接受新的信念。
学习率调优可能非常奇怪。例如,你可能认为使用较高的学习率意味着学习更多,或者学习更快。实际上并非如此。事实上,你常常可以通过降低学习率,快速得到更好的模型。

图-3:学习率
你可能会想查看显示损失随时间变化的学习曲线,看看网络学习的速度有多快。这里较高的学习率一开始学习较快,但随后趋于平稳,而较低的学习率则持续进行并且变得更好。对于任何训练过神经网络的人来说,这都是一个非常熟悉的画面。永远不要相信你学习的速度有多快。
正则化
防止过拟合的第一种方法是查看验证集上的表现,并在表现不再提升时停止训练。这被称为早停法,它是防止神经网络在训练集上过度优化的一种方法。另一种方法是应用正则化。正则化意味着对网络施加人工约束,这些约束在不增加优化难度的情况下,隐性地减少自由参数的数量。

图 6a:早停法
在瘦身牛仔裤类比中,如图6b所示,想象弹力裤。它们同样合身,但由于具有弹性,它们不会让物品更难放入其中。深度学习中的弹力裤有时被称为L2 正则化。其思路是向损失函数中添加另一个项,从而惩罚较大的权重。

图 6b:深度学习的弹力裤类比

图 6c:L2 正则化
目前,在深度学习实践中,防止过拟合的广泛使用的方法是将大量数据输入到深度网络中。
共享权重与池化
假设一张图像中有一只猫,而猫的位置不重要,因为它仍然是一张包含猫的图片。如果网络必须独立学习左上角和右上角的猫,那就需要做很多工作。但无论对象或图像位于图片的左侧还是右侧,它们在本质上是相同的。这就叫做平移不变性。
在网络中实现这一点的方法叫做权重共享。当网络知道两个输入可以包含相同类型的信息时,就可以共享权重,并共同训练这些输入的权重。这是一个非常重要的概念。统计不变性是指在时间或空间上平均而言不发生变化的事物,并且它们无处不在。对于图像来说,权重共享的概念将引导我们研究卷积神经网络。对于文本和一般的序列,它将引导我们研究循环神经网络:

图 7a:平移不变性

图 7b:权重共享
为了减少卷积金字塔中特征图的空间范围,可以使用非常小的步幅,并在邻域内进行所有卷积操作,并以某种方式将它们结合起来。这被称为池化。
在最大池化中,如图 7d所示,在特征图的每个点上,查看该点周围的小邻域,并计算该邻域内所有响应的最大值。使用最大池化有一些优势。首先,它不会增加你的参数数量,因此不会导致过拟合的风险。其次,它通常能产生更准确的模型。然而,由于卷积操作在较低的步幅下运行,模型的计算成本会大幅增加。最大池化提取最重要的特征,而平均池化有时无法提取好的特征,因为它会考虑所有特征并产生一个平均值,而这个平均值可能对物体检测类任务来说并不重要。

图 7c:池化

图 7d:最大池化与平均池化
局部感受野
一种简单的编码局部结构的方法是将相邻输入神经元的子矩阵连接成一个属于下一层的单一隐藏神经元。这个单一的隐藏神经元代表一个局部感受野。假设我们考虑 CIFAR-10 图像,其输入特征为[32 x 32 x 3]。如果感受野(或滤波器大小)为 4 x 4,那么卷积层中的每个神经元将有权重连接到输入特征中的[4 x 4 x 3]区域,总共有 443 = 48 个权重(再加一个偏置参数)。沿深度轴的连接程度必须为 3,因为这是输入特征的深度(或通道数:RGB)。
卷积神经网络(ConvNet)
卷积神经网络(ConvNets)是通过在空间上共享其参数/权重的神经网络。一幅图像可以表示为一个平的煎饼,具有宽度、高度和深度或通道数(对于 RGB 图像,深度是 3,表示红、绿、蓝三个通道,而对于灰度图像,深度是 1)。
现在让我们将一个输出为K的小型神经网络滑过图像,且不改变权重。

图 8a:空间上的权重共享

图 8b:具有卷积层的卷积金字塔
在输出端,将绘制一幅不同的图像,具有不同的宽度、不同的高度和不同的深度(从仅有的 RGB 颜色通道到K个通道)。这个操作被称为卷积。
卷积神经网络(ConvNet)基本上是一个深度网络,包含多个卷积层,这些卷积层堆叠在一起形成类似金字塔的结构。从前面的图可以看到,网络将图像作为输入(维度为宽度 x 高度 x 深度),然后逐步对其应用卷积操作,以减少空间维度,同时增加深度,这大致等同于其语义复杂度。让我们理解一下卷积神经网络中的一些常见术语。
图像堆栈中的每一层或深度叫做特征图(feature map),并且使用补丁或卷积核(kernels)将三个特征图映射到K个特征图。步幅(stride)是指每次移动滤波器时,像素的移动数量。根据填充方式,步幅为 1 时,输出的大小大致与输入相同;步幅为 2 时,输出约为输入的一半。对于有效填充(valid padding),滑动滤波器不会越过图像的边缘,而在同样填充(same-padding)情况下,滤波器会越过边缘,并且用零填充,使得输出图的大小与输入图的大小完全相同:


图 8c:与卷积网络相关的不同术语
反卷积或转置卷积
对于需要最终输出分辨率大于输入的计算机视觉应用,反卷积/转置卷积是事实上的标准。这一层被广泛应用于如 GAN、图像超分辨率、从图像估计表面深度、光流估计等热门应用中。
CNN 通常执行下采样,即输出分辨率低于输入,而在反卷积中,层会对图像进行上采样,使其获得与输入图像相同的分辨率。需要注意的是,由于简单的上采样不可避免地会丢失细节,因此一个更好的选择是使用可训练的上采样卷积层,其参数会在训练过程中发生变化。
Tensorflow 方法:tf.nn.conv2d_transpose(value, filter, output_shape, strides, padding, name)
递归神经网络和 LSTM
递归神经网络(RNN)的关键思想是共享参数。假设你有一系列事件,在每个时刻你需要根据到目前为止发生的事件做出决策。如果序列是相对稳定的,你可以在每个时刻使用相同的分类器,这样就简化了很多。但由于这是一个序列,你还需要考虑过去——即在某个时刻之前发生的所有事情。
RNN 将拥有一个单一模型,负责总结过去的内容并将这些信息提供给分类器。它基本上形成了一个具有相对简单重复模式的网络,其中分类器的一部分在每个时间步连接到输入,而另一部分叫做递归连接,在每一步将你与过去的内容连接,如下图所示:

图 9a:递归神经网络

图-9b:长短期记忆(LSTM)
LSTM代表长短期记忆。从概念上讲,循环神经网络由简单的重复单元组成,这些单元以过去的信息、一个新的输入为输入,产生新的预测并与未来连接。其中心通常是一组简单的层,带有一些权重和线性激活。
在 LSTM 中,如图 9b所示,每个门的门控值都由输入参数上的一个小型逻辑回归控制。每个门都有自己的一组共享参数。此外,还额外加入了双曲正切函数,以保持输出值在-1 和 1 之间。并且它是可微分的,这意味着它可以很容易地优化参数。所有这些小门帮助模型在需要时保持更长时间的记忆,并在应该时忽略不重要的内容。
深度神经网络
深度学习的核心思想是增加更多的层次,使模型更深。这样做有很多好的理由。其中之一就是参数效率。通常,通过增加网络深度而非宽度,你可以用更少的参数获得更好的性能。
另一个原因是,许多你可能感兴趣的自然现象,通常具有层次结构,而深度模型自然能够捕捉这一点。例如,如果你探究一个图像模型,并可视化模型学习到的内容,你通常会发现在最底层学到的是非常简单的东西,比如线条或边缘。

图 10a:深度神经网络

图 10b:网络层捕捉图像的层次结构
卷积神经网络(ConvNet)的一种典型架构是几层交替进行卷积和最大池化,最后顶部接几层全连接层。第一个使用这种架构的著名模型是 1998 年 Yann Lecun 为字符识别设计的 LeNet-5。
现代卷积神经网络,如 AlexNet,在 2012 年著名赢得了 ImageNet 物体识别挑战赛,采用了与之非常相似的架构,并进行了一些调整。另一种值得注意的池化方式是平均池化。与其选择最大值,不如在特定位置周围的像素窗口上取平均值。
判别式模型与生成式模型
判别式模型学习条件概率分布p(y|x),这可以解释为x 给定 y 的概率。判别式分类器通过观察数据来学习。它对分布的假设较少,但严重依赖数据的质量。分布p(y|x)简单地将给定的样本 x 直接分类到标签y中。例如,在逻辑回归中,我们所要做的就是学习能够最小化平方损失的权重和偏差。
而生成模型学习的是联合概率分布p(x,y),其中x是输入数据,y是你希望分类的标签。生成模型可以根据对数据分布的假设,自行生成更多样本。例如,在朴素贝叶斯模型中,我们可以从数据中学习p(x),还可以学习p(y),即先验类别概率,我们也可以使用最大似然估计从数据中学习p(x|y)。
一旦我们有了p(x),p(y)和p(x|y),那么p(x, y)就不难找出来了。现在使用贝叶斯定理,我们可以将p(y|x)替换为(p(x|y)p(y))/p(x)。由于我们只关心arg max,分母可以被去掉,因为对于每个y来说它都是相同的:

这是我们在生成模型中使用的方程,p(x, y) = p(x | y) p(y),它明确地建模了每个类别的实际分布。
实际上,判别模型通常在分类任务中优于生成模型,但在创作/生成任务中,生成模型则胜过判别模型。
总结
到目前为止,你已经刷新了与深度学习相关的各种概念,并且学习了深度网络如何从监督任务的领域(例如分类图像、语音识别、文本等)演变到通过生成模型展现创作能力。在下一章中,我们将看到深度学习如何在无监督领域中利用生成对抗网络(GANs)执行精彩的创作任务。
第二章:使用 GAN 进行无监督学习
最近,随着生成模型的进步,神经网络不仅能识别图像,还能用来生成音频和逼真的图像。
在这一章中,我们将通过生成对抗网络(Generative Adversarial Network),也就是通常所说的GAN,深入探讨深度学习的创造性特点。你将通过动手实例学习如何利用神经网络的生成能力,从各种现实世界的数据集(如MNIST和CIFAR)中生成逼真的图像。同时,你将理解如何通过半监督学习方法克服深度网络在无监督学习中的主要挑战,并将其应用到自己的问题领域。在本章的最后,你将学习一些训练中遇到的障碍,并获得一些实用的 GAN 模型工作技巧和窍门。
本章将覆盖以下主题:
-
什么是 GAN?它的应用、技巧与窍门
-
通过 TensorFlow 解释 GAN 的概念,使用双层神经网络生成图像
-
使用 Keras 生成深度卷积 GAN(DCGAN)的图像
-
使用 TensorFlow 实现半监督学习
用深度神经网络自动化人类任务
在过去的几年里,深度神经网络的爆炸式发展使得它们能够在图像分类、语音识别和自然语言理解方面取得良好的准确性。
当前深度神经网络领域的先进算法能够学习数据集中的高度复杂的模式模型。虽然这些能力令人印象深刻,但人类能够做的远不止图像识别或理解人们在说什么,自动化这些任务通过机器实现似乎仍然遥不可及。
让我们看一些需要人类创造力的应用场景(至少目前是这样):
-
训练一个人工作者,能够写文章并以非常简单的方式向社区解释数据科学概念,通过学习 Wikipedia 中过去的文章
-
创建一个人工画家,能够通过学习某位著名艺术家的过去作品来像他/她一样作画
你相信机器能够完成这些任务吗?令你惊讶的是,答案是“YES”。
当然,这些任务很难自动化,但 GAN 已经开始让其中一些任务成为可能。
Yann LeCun,深度学习领域的知名人物(Facebook AI 的总监)曾说:
生成对抗网络(GANs)及其目前提出的各种变体是过去 10 年中机器学习领域最有趣的想法。
如果你觉得 GAN 这个名字让你感到害怕,别担心!到本书结束时,你将掌握这项技术,并能将其应用于现实问题。
GAN 的目的
一些生成模型能够从模型分布中生成样本。GAN 是生成模型的一个例子。GAN 主要集中在从分布中生成样本。
你可能会想知道为什么生成模型值得研究,尤其是那些只能生成数据,而不是提供密度函数估计的生成模型。
学习生成模型的一些原因如下:
-
采样(或生成)是直接的
-
训练不涉及最大似然估计
-
对过拟合具有鲁棒性,因为生成器从不接触训练数据
-
GAN 擅长捕捉分布的模式
现实世界的类比
让我们考虑现实世界中,一个伪造货币的罪犯和警察之间的关系。让我们从金钱的角度列举罪犯和警察的目标:

图 1a:GAN 的现实世界类比
-
要成为一名成功的伪造货币者,罪犯需要欺骗警察,使警察无法分辨伪造/假币和真币之间的区别。
-
作为正义的典范,警察希望尽可能有效地检测假币。
这可以被建模为博弈论中的极小化最大化博弈。这种现象被称为对抗过程。GAN,由 Ian Goodfellow 于 2014 年在arXiv: 1406.2661中提出,是对抗过程的一个特例,其中两个神经网络相互竞争。第一个网络生成数据,第二个网络尝试找出真实数据和第一个网络生成的假数据之间的差异。第二个网络将输出一个标量[0, 1],表示真实数据的概率。
GAN 的构建模块
在 GAN 中,第一个网络被称为生成器,通常表示为G(z),第二个网络被称为判别器,通常表示为D(x):

图 1b:生成对抗网络
在平衡点上,这是极小化最大化博弈中的最优点,第一个网络将建模真实数据,第二个网络将输出一个概率 0.5,作为第一个网络 = 真实数据的输出:

有时两个网络最终达到平衡,但这并不总是保证的,两个网络可能会继续学习很长时间。下面的图示展示了生成器和判别器损失的学习过程:

图 1c:两个网络的损失,生成器和判别器
生成器
生成器网络以随机噪声作为输入,尝试生成一个数据样本。在前面的图中,我们可以看到生成器G(z)从概率分布p(z)中获取输入z,然后生成数据,数据随后被输入到判别器网络D(x)。
判别器
判别器网络输入可以来自真实数据或生成器生成的数据,并试图预测输入是否为真实数据还是生成数据。它从真实数据分布 P [data] (x) 中获取输入 x,然后解决一个二分类问题,输出在 0 到 1 的标量范围内。
GANs 正在获得大量的关注,因为它们能够解决无监督学习中的重要挑战,毕竟可用的无标签数据远远大于有标签数据的数量。另一个它们受欢迎的原因是 GANs 能够生成在生成模型中最真实的图像。虽然这是主观的,但这是大多数从业者的共同观点。

图-1d:GAN 中的向量算术
除此之外,GAN 通常非常具有表现力:它能够在潜在空间中执行算术操作,即 z 向量的空间,并将这些操作转换为特征空间中的相应操作。如 图 1d 所示,如果你在潜在空间中取一个戴眼镜的男人的表示,减去 中性男人 向量并加上 中性女人 向量,你会在特征空间中得到一个戴眼镜的女人的图像。这真是令人惊叹。
GAN 实现
根据 GAN 的定义,我们基本上需要两个网络,无论是像 ConvNet 这样的复杂网络,还是简单的两层神经网络。为了实现目的,让我们使用简单的两层神经网络和 MNIST 数据集来进行 TensorFlow 的实现。MNIST 是一个手写数字数据集,每个图像是 28x28 像素的灰度图:
# Random noise setting for Generator
Z = tf.placeholder(tf.float32, shape=[None, 100], name='Z')
#Generator parameter settings
G_W1 = tf.Variable(xavier_init([100, 128]), name='G_W1')
G_b1 = tf.Variable(tf.zeros(shape=[128]), name='G_b1')
G_W2 = tf.Variable(xavier_init([128, 784]), name='G_W2')
G_b2 = tf.Variable(tf.zeros(shape=[784]), name='G_b2')
theta_G = [G_W1, G_W2, G_b1, G_b2]
# Generator Network
def generator(z):
G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)
G_log_prob = tf.matmul(G_h1, G_W2) + G_b2
G_prob = tf.nn.sigmoid(G_log_prob)
return G_prob
generator(z) 接受一个来自随机分布的 100 维向量(在这种情况下我们使用均匀分布)作为输入,并返回一个 786 维的向量,这就是一个 MNIST 图像(28x28)。这里的 z 是 G(z) 的先验。通过这种方式,它学习了先验空间到 p [数据](真实数据分布)之间的映射关系:
#Input Image MNIST setting for Discriminator [28x28=784]
X = tf.placeholder(tf.float32, shape=[None, 784], name='X')
#Discriminator parameter settings
D_W1 = tf.Variable(xavier_init([784, 128]), name='D_W1')
D_b1 = tf.Variable(tf.zeros(shape=[128]), name='D_b1')
D_W2 = tf.Variable(xavier_init([128, 1]), name='D_W2')
D_b2 = tf.Variable(tf.zeros(shape=[1]), name='D_b2')
theta_D = [D_W1, D_W2, D_b1, D_b2]
# Discriminator Network
def discriminator(x):
D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1)
D_logit = tf.matmul(D_h1, D_W2) + D_b2
D_prob = tf.nn.sigmoid(D_logit)
return D_prob, D_logit
discriminator(x) 将 MNIST 图像作为输入并返回一个标量,表示真实图像的概率。现在,让我们讨论一个训练 GAN 的算法。以下是论文 arXiv: 1406.2661, 2014 中的训练算法伪代码:

图 1e:GAN 训练算法伪代码
G_sample = generator(Z)
D_real, D_logit_real = discriminator(X)
D_fake, D_logit_fake = discriminator(G_sample)
# Loss functions according the GAN original paper
D_loss = -tf.reduce_mean(tf.log(D_real) + tf.log(1\. - D_fake))
G_loss = -tf.reduce_mean(tf.log(D_fake))
TensorFlow 优化器只能进行最小化操作,因此为了最大化 loss 函数,我们使用负号来表示损失函数,如前所述。此外,根据论文的伪算法,最好最大化 tf.reduce_mean(tf.log(D_fake)),而不是最小化 tf.reduce_mean(1 - tf.log(D_fake))。然后,我们逐个训练这些网络,并使用前述的 loss 函数进行训练:
# Only update D(X)'s parameters, so var_list = theta_D
D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)
# Only update G(X)'s parameters, so var_list = theta_G
G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)
def sample_Z(m, n):
'''Uniform prior for G(Z)'''
return np.random.uniform(-1., 1., size=[m, n])
for it in range(1000000):
X_mb, _ = mnist.train.next_batch(mb_size)
_, D_loss_curr = sess.run([D_solver, D_loss], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})
_, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(mb_size, Z_dim)})
之后,我们从随机噪声开始,随着训练的进行,G(Z) 开始向 p [数据] 移动。这一点可以通过 G(Z) 生成的样本与原始 MNIST 图像的相似度来证明。
以下展示了经过 60,000 次迭代后生成的一些输出:

图 1f:GAN 生成输出图像的实现
GAN 的应用
GAN 在多个领域引起了广泛的关注。近年来,GAN 的一些令人兴奋的应用如下所示:
-
使用 CycleGAN 将一种图像转换为另一种图像(例如从马到斑马),并通过条件 GAN 进行图像编辑。具体内容将在第三章,跨领域转移图像风格中介绍。
-
使用 StackGAN 从文本句子自动合成逼真的图像,并使用Discovery GAN(DiscoGAN)将一种风格转移到另一个领域。具体内容将在第四章,从文本生成逼真的图像中介绍。
-
使用预训练模型通过 SRGAN 提高图像质量并生成高分辨率图像。具体内容将在第五章,使用各种生成模型生成图像中介绍。
-
根据属性生成逼真图像:假设一个小偷闯入你的公寓,但你没有他的照片。现在,警察局的系统可以根据你提供的描述生成小偷的逼真图像,并在数据库中进行搜索。更多信息请参考arXiv: 1605.05396, 2016。
-
预测视频中的下一帧或动态视频生成:(
carlvondrick.com/tinyvideo/)。
使用 Keras 实现 DCGAN 生成图像
论文中介绍了深度卷积生成对抗网络(DCGAN):无监督表示学习与深度卷积生成对抗网络,由A. Radford, L. Metz, 和 S. Chintala,arXiv:1511.06434, 2015。
生成器使用一个 100 维的均匀分布空间,Z,然后通过一系列卷积操作将其映射到一个更小的空间。以下图示为例:

图 2:生成器的 DCGAN 架构
来源:arXiv, 1511.06434, 2015
DCGAN 通过以下架构约束来稳定网络:
-
在鉴别器中用步长卷积替换所有池化层,在生成器中用分数步长卷积替换
-
在生成器和鉴别器中都使用批量归一化(batchnorm)
-
删除深层架构中的全连接隐藏层,只在末尾使用平均池化
-
在生成器的所有层中使用 ReLU 激活函数,输出层使用
tanh -
在鉴别器的所有层中使用 leaky ReLU 激活函数
在 Keras 中实现的 DCGAN 生成器可以通过以下代码描述,代码位于:github.com/jacobgil/keras-dcgan。
使用以下命令启动训练/生成过程:
python dcgan.py --mode train --batch_size <batch_size>
python dcgan.py --mode generate --batch_size <batch_size> --nice

请注意,先前打印的批次数是基于输入图像形状/批次大小(提供的)计算的。
现在让我们来看看代码。生成器可以通过以下方式描述:
def generator_model():
model = Sequential()
model.add(Dense(input_dim=100, output_dim=1024))
model.add(Activation('tanh'))
model.add(Dense(128*7*7))
model.add(BatchNormalization())
model.add(Activation('tanh'))
model.add(Reshape((7, 7, 128), input_shape=(128*7*7,)))
model.add(UpSampling2D(size=(2, 2)))
model.add(Conv2D(64, (5, 5), padding='same'))
model.add(Activation('tanh'))
model.add(UpSampling2D(size=(2, 2)))
model.add(Conv2D(1, (5, 5), padding='same'))
model.add(Activation('tanh'))
return model
生成器的第一个密集层接受一个 100 维向量作为输入,并使用 tanh 激活函数生成 1,024 维的输出。
网络中的下一个稠密层通过批量归一化生成 128 x 7 x 7 的输出数据(参考S. Ioffe和C.Szegedy的文章《Batch Normalization Accelerating Deep Network Training by Reducing Internal Covariate Shift》,arXiv: 1502.03167,2014),这种技术通常通过将输入归一化为零均值和单位方差来帮助稳定学习。经验表明,批量归一化在许多情况下可以加快训练速度,减少糟糕初始化的问题,并且通常产生更精确的结果。还有一个 Reshape() 模块生成了 128 x 7 x 7(128 个通道,7 宽,7 高)的数据,dim_ordering 设置为 tf,并且 UpSampling() 模块将每个数据重复成一个 2 x 2 的方块。随后是一个卷积层,使用 5 x 5 的卷积核生成 64 个滤波器,激活函数为 tanh,具有相同填充,然后是一个新的 UpSampling() 和一个最终的卷积层,使用一个滤波器,大小为 5 x 5,激活函数为 tanh。请注意,在 ConvNet 中没有池化操作。
以下代码描述了判别器:
def discriminator_model():
model = Sequential()
model.add(Conv2D(64, (5, 5), padding='same',input_shape=(28, 28, 1)))
model.add(Activation('tanh'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(128, (5, 5)))
model.add(Activation('tanh'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(1024))
model.add(Activation('tanh'))
model.add(Dense(1))
model.add(Activation('sigmoid'))
return model
判别器使用形状为(1, 28, 28)的标准 MNIST 图像,并应用了包含 64 个 5 x 5 大小滤波器和 tanh 激活函数的卷积。接着是一个大小为 2 x 2 的最大池化操作,然后是进一步的卷积最大池化操作。
最后两个阶段是密集的,最后一个是关于伪造预测的,仅包含一个使用 sigmoid 激活函数的神经元。对于给定的周期数,生成器和判别器使用 binary_crossentropy 作为 loss 函数进行训练。在每个周期中,生成器预测一个数字(例如,创建伪造的 MNIST 图像),判别器试图在将预测与真实的 MNIST 图像混合后进行学习。经过几个周期,生成器自动学习伪造这组手写数字:

图-3:深度卷积 GAN 生成的手写数字输出
请注意,训练 GAN 可能非常困难,因为需要在两个参与者之间找到平衡,因此,实践者使用的一些有价值的技术和提示将在本章的最后部分给出。
使用 TensorFlow 实现 SSGAN
半监督学习生成对抗网络(SSGAN)的基本直觉是利用生成器生成的样本,通过提高泛化能力,增强判别器在图像分类任务中的表现。关键思想是将其中一个网络训练为图像分类器和判别器(用于区分生成的图像与真实图像)。
对于一个具有n类的数据集,经过双重训练(判别器/分类器)的网络将图像作为输入,真实图像分类为前n类,生成图像分类为n+1类,如下图所示:

来源:github.com/gitlimlab/SSGAN-Tensorflow/blob/master/figure/ssgan.png
这个多任务学习框架由两个损失组成,第一个是监督损失:

其次是判别器的 GAN 损失:

在训练阶段,这两个损失函数将一起最小化。
环境设置
执行以下步骤以在 Cifar-10 数据集上运行 SSGAN:
-
更改目录:
cd SSGAN-Tensorflow/ -
下载
CIFAR-10数据集:![环境设置]()
-
训练模型:
![环境设置]()
-
测试或评估模型:
python evaler.py --dataset CIFAR10 --checkpoint ckpt_dir
现在让我们深入代码。生成器从均匀分布中获取随机噪声:
z = tf.random_uniform([self.batch_size, n_z], minval=-1, maxval=1, dtype=tf.float32)
然后,生成器模型使用reshape方法将输入噪声展平为一维向量。接着,它对输入噪声应用三层反卷积,激活函数为ReLU,然后再应用一次反卷积,激活函数为tanh,以生成输出图像,尺寸为[h=高度, w=宽度, c],其中c为通道数(灰度图像:1,彩色图像:3):
# Generator model function
def G(z, scope='Generator'):
with tf.variable_scope(scope) as scope:
print ('\033[93m'+scope.name+'\033[0m')
z = tf.reshape(z, [self.batch_size, 1, 1, -1])
g_1 = deconv2d(z, deconv_info[0], is_train, name='g_1_deconv')
print (scope.name, g_1)
g_2 = deconv2d(g_1, deconv_info[1], is_train, name='g_2_deconv')
print (scope.name, g_2)
g_3 = deconv2d(g_2, deconv_info[2], is_train, name='g_3_deconv')
print (scope.name, g_3)
g_4 = deconv2d(g_3, deconv_info[3], is_train, name='g_4_deconv', activation_fn='tanh')
print (scope.name, g_4)
output = g_4
assert output.get_shape().as_list() == self.image.get_shape().as_list(), output.get_shape().as_list()
return output
# Deconvolution method
def deconv2d(input, deconv_info, is_train, name="deconv2d", stddev=0.02,activation_fn='relu'):
with tf.variable_scope(name):
output_shape = deconv_info[0]
k = deconv_info[1]
s = deconv_info[2]
deconv = layers.conv2d_transpose(input,
num_outputs=output_shape,
weights_initializer=tf.truncated_normal_initializer(stddev=stddev),
biases_initializer=tf.zeros_initializer(),
kernel_size=[k, k], stride=[s, s], padding='VALID')
if activation_fn == 'relu':
deconv = tf.nn.relu(deconv)
bn = tf.contrib.layers.batch_norm(deconv, center=True, scale=True,
decay=0.9, is_training=is_train, updates_collections=None)
elif activation_fn == 'tanh':
deconv = tf.nn.tanh(deconv)
else:
raise ValueError('Invalid activation function.')
return deconv
判别器将图像作为输入,并尝试输出n+1类标签。它应用一些具有泄漏 ReLU 和批归一化的卷积层,接着对输入图像进行 dropout,最后使用softmax函数输出类label:
# Discriminator model function
def D(img, scope='Discriminator', reuse=True):
with tf.variable_scope(scope, reuse=reuse) as scope:
if not reuse: print ('\033[93m'+scope.name+'\033[0m')
d_1 = conv2d(img, conv_info[0], is_train, name='d_1_conv')
d_1 = slim.dropout(d_1, keep_prob=0.5, is_training=is_train, scope='d_1_conv/')
if not reuse: print (scope.name, d_1)
d_2 = conv2d(d_1, conv_info[1], is_train, name='d_2_conv')
d_2 = slim.dropout(d_2, keep_prob=0.5, is_training=is_train, scope='d_2_conv/')
if not reuse: print (scope.name, d_2)
d_3 = conv2d(d_2, conv_info[2], is_train, name='d_3_conv')
d_3 = slim.dropout(d_3, keep_prob=0.5, is_training=is_train, scope='d_3_conv/')
if not reuse: print (scope.name, d_3)
d_4 = slim.fully_connected(
tf.reshape(d_3, [self.batch_size, -1]), n+1, scope='d_4_fc', activation_fn=None)
if not reuse: print (scope.name, d_4)
output = d_4
assert output.get_shape().as_list() == [self.batch_size, n+1]
return tf.nn.softmax(output), output
# Convolution method with dropout
def conv2d(input, output_shape, is_train, k_h=5, k_w=5, stddev=0.02, name="conv2d"):
with tf.variable_scope(name):
w = tf.get_variable('w', [k_h, k_w, input.get_shape()[-1], output_shape],
initializer=tf.truncated_normal_initializer(stddev=stddev))
conv = tf.nn.conv2d(input, w, strides=[1, 2, 2, 1], padding='SAME')
biases = tf.get_variable('biases', [output_shape], initializer=tf.constant_initializer(0.0))
conv = lrelu(tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape()))
bn = tf.contrib.layers.batch_norm(conv, center=True, scale=True,
decay=0.9, is_training=is_train, updates_collections=None)
return bn
# Leaky Relu method
def lrelu(x, leak=0.2, name="lrelu"):
with tf.variable_scope(name):
f1 = 0.5 * (1 + leak)
f2 = 0.5 * (1 - leak)
return f1 * x + f2 * abs(x)
判别器网络有两个loss函数,一个(s_loss)用于使用 Huber 损失对 CIFAR-10 图像中的真实数据进行监督分类(Huber 损失比平方误差损失更能抵抗异常值),另一个(d_loss)用于使用softmax函数和交叉熵对生成的图像进行真/假分类,以标量形式表示:
# Discriminator/classifier loss
s_loss = tf.reduce_mean(huber_loss(label, d_real[:, :-1]))

图:4a:监督判别器的损失
d_loss_real = tf.nn.softmax_cross_entropy_with_logits(logits=d_real_logits, labels=real_label)
d_loss_fake = tf.nn.softmax_cross_entropy_with_logits(logits=d_fake_logits, labels=fake_label)
d_loss = tf.reduce_mean(d_loss_real + d_loss_fake)

图:4b:总判别器损失(真实 + 假损失)
# Huber loss
def huber_loss(labels, predictions, delta=1.0):
residual = tf.abs(predictions - labels)
condition = tf.less(residual, delta)
small_res = 0.5 * tf.square(residual)
large_res = delta * residual - 0.5 * tf.square(delta)
return tf.where(condition, small_res, large_res)
# Generator loss
g_loss = tf.reduce_mean(tf.log(d_fake[:, -1]))
g_loss += tf.reduce_mean(huber_loss(real_image, fake_image)) * self.recon_weight
注释
注:权重退火作为辅助损失帮助生成器摆脱初始的局部最小值。

图:4c:生成器损失
生成器和判别器网络的loss函数使用AdamOptimizer进行优化,并且应用梯度裁剪(clip_gradients)来稳定训练过程:
# Optimizer for discriminator
self.d_optimizer = tf.contrib.layers.optimize_loss(
loss=self.model.d_loss,
global_step=self.global_step,
learning_rate=self.learning_rate*0.5,
optimizer=tf.train.AdamOptimizer(beta1=0.5),
clip_gradients=20.0,
name='d_optimize_loss',
variables=d_var
)
# Optimizer for generator
self.g_optimizer = tf.contrib.layers.optimize_loss(
loss=self.model.g_loss,
global_step=self.global_step,
learning_rate=self.learning_rate,
optimizer=tf.train.AdamOptimizer(beta1=0.5),
clip_gradients=20.0,
name='g_optimize_loss',
variables=g_var
)
最终,监督损失(s_loss)和生成对抗损失(即判别器损失(d_loss)和生成器损失(g_loss)的组合)一起训练,以最小化总损失:
for s in xrange(max_steps):
step, accuracy, summary, d_loss, g_loss, s_loss, step_time, prediction_train, gt_train, g_img = \
self.run_single_step(self.batch_train, step=s, is_train=True)
经过 150 个 epochs 后生成的样本输出如下:

GAN 模型的挑战
训练 GAN 基本上是两个网络,生成器G(z)和判别器D(z),相互竞争,试图达到最优解,更具体地说,是达到纳什均衡。根据维基百科(在经济学和博弈论中),纳什均衡的定义是:一个系统的稳定状态,涉及不同参与者之间的相互作用,在这种状态下,如果其他参与者的策略保持不变,则没有参与者能够通过单方面改变策略来获益。
设置失败和坏的初始化
如果你仔细想一想,这正是 GAN 试图做的事情;生成器和判别器达到一种状态,即在另一个保持不变的情况下,它们无法进一步改进。现在,梯度下降的设置是沿着一个方向迈出一步,从而减少定义在问题上的损失度量——但是我们并不强迫网络在 GAN 中达到纳什均衡,毕竟 GAN 具有非凸目标且包含连续的高维参数。网络试图通过连续的步骤来最小化一个非凸目标,结果进入了一个振荡过程,而不是减少潜在的真实目标。
在大多数情况下,当判别器的损失接近零时,你可以立刻发现模型存在问题。但最大的问题是弄清楚问题出在哪里。
训练 GAN 时,另一个常见的做法是故意让其中一个网络停滞或学习得更慢,以便另一个网络能够赶上。在大多数情况下,落后的通常是生成器,因此我们通常让判别器等待。这在一定程度上可能是可以接受的,但请记住,为了让生成器变得更好,它需要一个好的判别器,反之亦然。理想情况下,系统希望两个网络都以一种互相促进的速度学习,让两者随时间不断进步。判别器的理想最小损失接近 0.5——这意味着从判别器的角度看,生成的图像与真实图像无法区分。
模式崩溃
训练生成对抗网络时的主要失败模式之一叫做模式崩溃,或者有时称为 Helvetica 情境。基本思想是生成器可能会不小心开始生成完全相同的多个副本,原因与博弈论的设定有关。我们可以把训练生成对抗网络的方式看作是先相对于判别器进行最大化,然后再相对于生成器进行最小化。如果我们在开始最小化生成器之前,完全相对于判别器进行最大化,一切都会顺利进行。但如果我们反过来,先最小化生成器,再最大化判别器,一切就会崩溃。原因是,如果我们保持判别器不变,它会将空间中的单一区域描述为最可能是真实的点,而不是虚假的点,然后生成器会选择将所有噪声输入值映射到那个最可能是真实的点。
计数问题
GAN 有时会出现远视现象,无法区分某个位置应出现的特定物体数量。如我们所见,它给头部生成了比原本更多的眼睛:

来源:NIPS 2016- arXiv: 1701.00160, 2017
透视问题
GAN 有时无法区分前后视角,因此在从 3D 物体生成 2D 表示时,未能很好地适应,如下所示:

来源:NIPS 2016- arXiv: 1701.00160, 2017
全球结构问题
GAN(生成对抗网络)无法理解整体结构,类似于透视问题。例如,在左下方的图像中,它生成了一只四条腿的牛,也就是一只同时用后腿站立并且四条腿都着地的牛。这显然是不现实的,现实生活中是无法做到的!

来源:NIPS 2016- arXiv: 1701 .00160, 2017
改进的 GAN 训练方法和技巧
为了克服 GAN 模型的困难,深度学习实践者根据问题的性质采取各种方法。一些改进技巧将在以下部分中提到。
特征匹配
GAN 的不稳定性通过为生成器指定一个新的目标来解决,该目标防止生成器在当前判别器上过度训练。
这个想法是使用判别器中间层的特征来匹配真实和虚假图像,并将其作为监督信号来训练生成器。
具体来说,我们训练生成器生成与真实数据统计特征匹配的数据,并匹配判别器中间层特征的期望值。通过训练判别器,我们要求它找到最能区分真实数据与当前模型生成数据的特征。
小批量
模式崩塌问题可以通过向判别器添加一些额外的特征来解决,其中判别器实际上一次查看一个完整的“样本小批量”,而不是只查看单个样本。如果这些特征测量的是与其他样本的距离,那么判别器就能检测到生成器是否开始以这种方式崩塌,而不是鼓励生成器的每个样本都向最可能的单一点逼近。小批量整体必须看起来真实,并且不同样本之间要有适当的间隔。
历史平均
历史平均的思想是添加一个惩罚项,惩罚那些远离其历史平均值的权重。例如,损失函数是:
distance (current parameters, average of parameters over the last t batches)
单边标签平滑
通常会使用标签 0(图像为真实)和 1(图像为虚假)。然而,使用一些更平滑的标签(0.1 和 0.9)似乎能使网络更能抵抗对抗样本。
输入归一化
大多数情况下,将图像归一化到-1 和 1 之间,并使用tanh作为生成器输出的最后一层是一个不错的选择。
批量归一化
这个想法是为真实和虚假数据构建不同的小批量,即每个小批量只包含所有真实图像或所有生成图像。但当批量归一化不可用时,可以使用实例归一化(对于每个样本,减去均值并除以标准差)。
避免使用 ReLU、MaxPool 时的稀疏梯度
如果梯度稀疏,GAN 的稳定性会受到影响。Leaky ReLU 对于生成器和判别器都是一个不错的选择。
在下采样时,使用平均池化和Conv2d + stride的组合,而在上采样时,使用PixelShuffle、ConvTranspose2d + stride的组合:
PixelShuffle- arXiv: 1609.05158, 2016
优化器与噪声
使用 ADAM 优化器用于生成器,使用 SGD 用于判别器。同时,在生成器的多个层中引入 dropout 形式的噪声。
不要仅通过统计量来平衡损失
相反,采用有原则的方法,而非直觉:
while lossD > A:
train D
while lossG > B:
train G
注意
注意:尽管有这些技巧和训练增强步骤,生成对抗模型在人工智能和深度学习领域仍然相对较新,因此像任何其他快速发展的领域一样,它也需要大量的改进。
摘要
到目前为止,你已经了解了深度学习是如何通过 GAN 的概念进入无监督学习领域的。你已经使用MNIST、CIFAR数据集生成了一些逼真的图像,比如手写数字、飞机、汽车、鸟类等。此外,你还了解了与生成对抗网络相关的各种挑战,并学会了如何通过实际的调整技巧克服这些挑战。
在接下来的几章中,我们将继续探索基于 GAN 的不同架构,利用真实数据集执行一些令人惊叹的任务。
第三章。在各个领域之间传递图像风格
生成对抗网络是深度学习中最迅速发展的分支之一,适用于各种创意应用(如图像编辑或绘画、风格转移、物体变换、照片增强等)。
在本章中,您将首先学习根据特定条件或特征生成或编辑图像的技术。然后,您将通过边界平衡方法稳定 GAN 训练,以克服模式崩溃问题,并应用收敛度量度量标准。最后,您将使用 Cycle Consistent 生成网络在各个领域进行图像到图像的翻译(例如将苹果变成橙子或马变成斑马)。
本章节将涵盖以下主题:
-
什么是 CGAN?其概念和架构
-
使用 CGAN 从 Fashion-MNIST 数据生成时尚衣橱
-
使用边界平衡 GAN 与 Wasserstein 距离稳定 GAN 训练
-
使用 CycleGAN 在不同领域之间传递图像风格
-
使用 Tensorflow 从苹果生成橙子
-
自动将马的图像转换为斑马
架起监督学习和无监督学习之间的桥梁
人类通过观察和体验物理世界来学习,我们的大脑非常擅长在不进行显式计算的情况下进行预测以得出正确答案。监督学习是关于预测与数据相关联的标签,其目标是泛化到新的未见数据。在无监督学习中,数据没有标签,并且其目标通常不是对新数据进行任何形式的泛化预测。
在现实世界中,标记数据通常稀缺且昂贵。生成对抗网络采用监督学习方法进行无监督学习,通过生成看起来伪造的数据,试图确定生成的样本是虚假还是真实的。这部分(分类器进行分类)是一个监督组件。但 GAN 的实际目标是理解数据的外观(即其分布或密度估计),并能够生成其学习到的新样本。
条件 GAN 简介
生成对抗网络(GAN)同时训练两个网络——生成器学习从未知分布或噪声生成伪样本,鉴别器学习区分虚假和真实样本。
在条件 GAN(CGAN)中,生成器学习根据特定条件或特征(例如与图像相关联的标签或更详细的标签)生成伪样本,而不是从未知噪声分布生成通用样本。现在,为了向生成器和鉴别器添加这样的条件,我们将简单地向两个网络提供一些向量y。因此,鉴别器D(X,y)和生成器G(z,y)都联合条件于两个变量z或X和y。
现在,CGAN 的目标函数是:

GAN 损失和 CGAN 损失之间的区别在于判别器和生成器函数中额外的参数y。下面的 CGAN 架构现在有一个额外的输入层(以条件向量C的形式),该层同时输入到判别器网络和生成器网络中。

使用 CGAN 生成时尚衣橱
在这个例子中,我们将实现条件 GAN,使用Fashion-MNIST数据集(github.com/zalandoresearch/fashion-mnist)来生成时尚衣橱。Fashion-MNIST数据集类似于原始的MNIST数据集,但使用了一组新的灰度图像和标签。

让我们进入代码,了解使用简单的神经网络架构来实现 CGAN 的工作原理,其中生成器和判别器都采用这种架构。
首先,我们将定义一个新的输入变量来保存我们的条件:
Y = tf.placeholder(tf.float32, shape=(None, num_labels))
接下来,我们将新的变量y加入到判别器D(X)和生成器G(z)中。现在,判别器(x,y)和生成器(z,y)与原始的 GAN 不同:
Dhidden = 256 # hidden units of Discriminator's network
Ghidden = 512 # hidden units of Generator's network
K = 8 # maxout units of Discriminator
# Discriminator Network
def discriminator(x, y):
u = tf.reshape(tf.matmul(x, DW1x) + tf.matmul(y, DW1y) + Db1, [-1, K, Dhidden])
Dh1 = tf.nn.dropout(tf.reduce_max(u, reduction_indices=[1]), keep_prob)
return tf.nn.sigmoid(tf.matmul(Dh1, DW2) + Db2)
# Generator Network
def generator(z,y):
Gh1 = tf.nn.relu(tf.matmul(Z, GW1z) + tf.matmul(Y, GW1y) + Gb1)
G = tf.nn.sigmoid(tf.matmul(Gh1, GW2) + Gb2)
return G
接下来,我们将使用我们的新网络,并定义一个loss函数:
G_sample = generator(Z, Y)
DG = discriminator(G_sample, Y)
Dloss = -tf.reduce_mean(tf.log(discriminator(X, Y)) + tf.log(1 - DG))
Gloss = tf.reduce_mean(tf.log(1 - DG) - tf.log(DG + 1e-9))
在训练过程中,我们将y的值同时输入到生成器网络和判别器网络:
X_mb, y_mb = mnist.train.next_batch(mini_batch_size)
Z_sample = sample_Z(mini_batch_size, noise_dim)
_, D_loss_curr = sess.run([Doptimizer, Dloss], feed_dict={X: X_mb, Z: Z_sample, Y:y_mb, keep_prob:0.5})
_, G_loss_curr = sess.run([Goptimizer, Gloss], feed_dict={Z: Z_sample, Y:y_mb, keep_prob:1.0})
最后,我们根据特定条件生成新的数据样本。在这个例子中,我们使用图像标签作为条件,并将标签设置为7,即生成Sneaker的图像。条件变量y_sample是一个集合,包含在第七索引位置值为1的独热编码向量:
nsamples=6
Z_sample = sample_Z(nsamples, noise_dim)
y_sample = np.zeros(shape=[nsamples, num_labels])
y_sample[:, 7] = 1 # generating image based on label
samples = sess.run(G_sample, feed_dict={Z: Z_sample, Y:y_sample})
现在,让我们执行以下步骤,根据类别标签条件生成衣橱图像。首先,下载Fashion-MNIST数据集,并通过运行download.py脚本将其保存到data/fashion目录下:
python download.py

接下来,使用以下命令训练 CGAN 模型,这将生成每 1000 次迭代后的示例图像,并保存在output目录下:
python simple-cgan.py

以下是使用条件标签设置为4(外套)在80k次迭代后和7(运动鞋)在60k次迭代后运行 CGAN 的输出结果:

使用边界平衡 GAN 稳定训练
GAN 在机器学习研究人员中迅速流行。GAN 的研究可以分为两类:一类是将 GAN 应用于具有挑战性的问题,另一类是尝试稳定训练。稳定 GAN 训练至关重要,因为原始的 GAN 架构存在许多缺点和不足:
-
模式崩塌:生成器崩塌到非常窄的分布中,生成的样本缺乏多样性。当然,这个问题违反了 GAN 的精神。
-
收敛度量评估:没有一个明确的度量可以告诉我们判别器损失和生成器损失之间的收敛情况。
改进的Wasserstein GAN(arXiv: 1704.00028,2017)是一种新提出的 GAN 算法,通过最小化 Wasserstein 距离(或地球搬运距离)来解决前述问题,通过为网络提供简单的梯度(如果输出被认为是真实的,则为+1;如果输出被认为是假的,则为-1)。
BEGAN(arXiv: 1703.10717,2017)的主要思想是使用自动编码器作为判别器,提出一种新的损失函数,其中真实损失是由真实图像和生成图像的重建损失之间的 Wasserstein 距离(以解决模式崩塌问题)得出的:

通过使用加权参数 k 添加超参数 gamma,使用户能够控制期望的多样性:

与大多数 GAN 不同,BEGAN 允许在每个时间步骤同时对这两个网络进行对抗性训练:

最终,它允许对收敛的近似度量 M 进行评估,以理解整个网络的性能:

BEGAN 的训练过程
BEGAN 训练过程中涉及的步骤如下:
-
判别器(自动编码器)更新其权重,以最小化真实图像的重建损失,从而开始更好地重建真实图像。
-
与此同时,判别器开始最大化生成图像的重建损失。
-
生成器以对抗性方式工作,最小化生成图像的重建损失。
BEGAN 架构
如下图所示,判别器是一个卷积网络,具有深度的编码器和解码器。解码器有多个 3x3 卷积层,后跟指数线性单元(ELU)。下采样通过步幅为 2 的卷积完成。自动编码器的嵌入状态被映射到全连接层。生成器和解码器都是深度反卷积,具有相同的架构,但权重不同,上采样通过最近邻方法完成:

图-1:BEGAN 架构。
来源:arXiv: 1703.10717,2017,2017
在前面的图中,判别器的生成器和解码器显示在左侧。判别器的编码器网络显示在右侧。
使用 Tensorflow 实现 BEGAN
现在让我们深入研究代码,并实现上述概念及架构,以生成逼真且具有吸引力的图像。
生成器网络具有多个 3x3 卷积层,并使用elu 激活函数,接着进行最近邻上采样,除最后一层外。卷积层的数量是根据图像的高度计算的:
self.repeat_num = int(np.log2(height)) – 2.
def GeneratorCNN(z, hidden_num, output_num, repeat_num, data_format, reuse):
with tf.variable_scope("G", reuse=reuse) as vs:
num_output = int(np.prod([8, 8, hidden_num]))
x = slim.fully_connected(z, num_output, activation_fn=None)
x = reshape(x, 8, 8, hidden_num, data_format)
for idx in range(repeat_num):
x = slim.conv2d(x, hidden_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
x = slim.conv2d(x, hidden_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
if idx < repeat_num - 1:
x = upscale(x, 2, data_format)
out = slim.conv2d(x, 3, 3, 1, activation_fn=None, data_format=data_format)
variables = tf.contrib.framework.get_variables(vs)
return out, variables
判别器网络的编码器具有多个卷积层,并使用elu 激活函数,接着进行最大池化下采样,除最后一层卷积层外:
def DiscriminatorCNN(x, input_channel, z_num, repeat_num, hidden_num, data_format):
with tf.variable_scope("D") as vs:
# Encoder
x = slim.conv2d(x, hidden_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
prev_channel_num = hidden_num
for idx in range(repeat_num):
channel_num = hidden_num * (idx + 1)
x = slim.conv2d(x, channel_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
x = slim.conv2d(x, channel_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
if idx < repeat_num - 1:
x = slim.conv2d(x, channel_num, 3, 2, activation_fn=tf.nn.elu, data_format=data_format)
#x = tf.contrib.layers.max_pool2d(x, [2, 2], [2, 2], padding='VALID')
x = tf.reshape(x, [-1, np.prod([8, 8, channel_num])])
z = x = slim.fully_connected(x, z_num, activation_fn=None)
判别器网络的解码器与生成器网络相似,具有多个卷积层,并使用elu 激活函数,接着进行最近邻上采样,除最后一层卷积层外:
num_output = int(np.prod([8, 8, hidden_num]))
x = slim.fully_connected(x, num_output, activation_fn=None)
x = reshape(x, 8, 8, hidden_num, data_format)
for idx in range(repeat_num):
x = slim.conv2d(x, hidden_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
x = slim.conv2d(x, hidden_num, 3, 1, activation_fn=tf.nn.elu, data_format=data_format)
if idx < repeat_num - 1:
x = upscale(x, 2, data_format)
out = slim.conv2d(x, input_channel, 3, 1, activation_fn=None, data_format=data_format)
variables = tf.contrib.framework.get_variables(vs)
现在,通过执行以下代码块,使用Adam 优化器优化之前讨论的真实和伪图像的生成器损失和判别器损失:
d_out, self.D_z, self.D_var = DiscriminatorCNN(
tf.concat([G, x], 0), self.channel, self.z_num, self.repeat_num,
self.conv_hidden_num, self.data_format)
AE_G, AE_x = tf.split(d_out, 2)
self.G = denorm_img(G, self.data_format)
self.AE_G, self.AE_x = denorm_img(AE_G, self.data_format), denorm_img(AE_x, self.data_format)
if self.optimizer == 'adam':
optimizer = tf.train.AdamOptimizer
else:
raise Exception("[!] Caution! Paper didn't use {} opimizer other than Adam".format(config.optimizer))
g_optimizer, d_optimizer = optimizer(self.g_lr), optimizer(self.d_lr)
self.d_loss_real = tf.reduce_mean(tf.abs(AE_x - x))
self.d_loss_fake = tf.reduce_mean(tf.abs(AE_G - G))
self.d_loss = self.d_loss_real - self.k_t * self.d_loss_fake
self.g_loss = tf.reduce_mean(tf.abs(AE_G - G))
现在是时候执行代码来生成令人印象深刻的名人图像了:
-
首先克隆以下仓库,并切换到相应的目录:
git clone https://github.com/carpedm20/BEGAN-tensorflow.git cd BEGAN-tensorflow -
接下来,运行以下脚本下载
CelebA数据集到data目录,并将其划分为训练集、验证集和测试集:python download.py -
请确保您的机器上已安装 p7zip。
-
现在开始训练过程,训练过程中生成的样本将保存在
logs目录下:python main.py --dataset=CelebA --use_gpu=True
注意
如果您遇到错误Conv2DCustomBackpropInputOp 仅支持 NHWC,请参考以下问题:
[github.com/carpedm20/BEGAN-tensorflow/ issues/29](https://github.com/carpedm20/BEGAN-tensorflow/ issues/29)
执行上述命令后,在训练过程中,您将看到如下信息,包括Model目录、日志目录和各种损失值:

BEGAN 生成的输出人脸在视觉上非常逼真且具有吸引力,如下图所示:

图 2:经过 350k 步后,生成器输出的图像(64x64),gamma=0.5
以下是经过 250k 步后生成的示例输出图像(128x128):

图 3:经过 250k 步后,生成器输出的图像(128x128),gamma=0.5
基于 CycleGAN 的图像到图像风格转换
循环一致生成网络(CycleGAN),最初在论文《Unpaired image-to-image translation using CycleGAN—arXiv: 1703.10593, 2017》中提出,旨在为给定的图像寻找源领域和目标领域之间的映射,而无需任何配对信息(例如灰度到彩色、图像到语义标签、边缘图到照片、马到斑马等)。
CycleGAN 背后的关键思想是拥有两个转换器 F 和 G,其中 F 将图像从领域A转换为领域B,而 G 将图像从领域B转换为领域A。因此,对于领域A中的图像x,我们应期望函数G(F(x))与x等效;同样,对于领域B中的图像y,我们应期望函数F(G(y))与y等效。
CycleGAN 的模型公式
CycleGAN 模型的主要目标是学习使用训练样本{xi}Ni=1 ∈ X和{yj}Mj=1 ∈ Y之间的映射,领域X和Y之间的关系。它还有两个对抗判别器D[X]和D[Y]:其中D[X]试图区分原始图像{x}和翻译后的图像{F(y)},同样,D[Y]试图区分{y}和{G(x)}。
CycleGAN 模型有两个loss函数:
-
对抗损失:它将生成图像的分布与目标领域的分布匹配:
![CycleGAN 模型公式]()
-
循环一致性损失:它防止学习到的映射G和F相互矛盾:
![CycleGAN 模型公式]()
完整的 CycleGAN 目标函数如下所示:

使用 Tensorflow 将苹果转化为橙子
在这个例子中,我们将把领域A中的图像风格迁移到另一个领域B中的图像:更具体地说,我们将应用 CycleGAN 将苹果转化为橙子,或将橙子转化为苹果,步骤如下:
-
首先,克隆以下
git仓库并将目录更改为 CycleGAN-tensorflow:git clone https://github.com/xhujoy/CycleGAN-tensorflow cd CycleGAN-tensorflow -
现在,使用
download_dataset.sh脚本下载apple2orange数据集 ZIP 文件,解压并将其保存在datasets目录下:bash ./download_dataset.sh apple2orange -
接下来,使用下载的
apple2orange数据集训练 CycleGAN 模型。在训练阶段,模型将保存在checkpoint目录中,并在logs目录中启用日志记录,以便通过 TensorBoard 进行可视化:python main.py --dataset_dir=apple2orange![使用 Tensorflow 将苹果转化为橙子]()
-
运行以下命令以在浏览器中可视化训练阶段的各种损失(判别器损失和生成器损失),通过访问
http://localhost:6006/:tensorboard --logdir=./logs![使用 Tensorflow 将苹果转化为橙子]()
-
最后,我们将从
checkpoint目录加载训练好的模型,将风格从一种图像传递到另一种图像,从而生成橙子(或者相反,取决于传递的值(AtoB或BtoA)来指示从领域 1 到领域 2 的风格迁移):python main.py --dataset_dir=apple2orange --phase=test --which_direction=AtoB -
以下是
test阶段生成的样本输出图像:![使用 Tensorflow 将苹果转化为橙子]()
图 4:左侧显示通过传递 AtoB 在方向参数中将苹果转变为橘子,而右侧显示通过传递 BtoA 在方向参数中生成的输出。
使用 CycleGAN 将马转变为斑马
就像之前的示例一样,在本节中,我们将使用 CycleGAN 通过执行以下步骤将马转变为斑马,或将斑马转变为马:
-
首先克隆以下
git仓库并切换目录到CycleGAN-tensorflow(如果你已经执行了前面的示例,可以跳过此步骤):git clone https://github.com/xhujoy/CycleGAN-tensorflow cd CycleGAN-tensorflow -
现在从伯克利下载
horse2zebra压缩包,解压并通过download_dataset.sh脚本将其保存到datasets目录下:bash ./download_dataset.sh horse2zebra -
接下来,我们将使用
horse2zebra数据集训练我们的 CycleGAN 模型,并使用 TensorBoard 可视化训练过程中的损失:python main.py --dataset_dir=horse2zebra![使用 CycleGAN 将马转变为斑马]()
-
运行以下命令并导航到
http://localhost:6006/以可视化各种生成器或判别器损失:tensorboard --logdir=./logs![使用 CycleGAN 将马转变为斑马]()
-
最终,我们将使用
checkpoint目录中的训练模型,将马转变为斑马,或将斑马转变为马,这取决于传递给which_direction参数的值是AtoB还是BtoA:python main.py --dataset_dir=horse2zebra --phase=test --which_direction=AtoB
以下是test阶段生成的示例输出图像:

图 5:左侧显示将马转变为斑马,而右侧显示将斑马转变为马。
总结
到目前为止,你已经学会了通过将条件向量传递给生成器和判别器,基于特定的特征或条件来创建图像。同时,你也理解了如何通过使用 BEGAN 稳定网络训练来克服模型崩溃问题。最后,你已经实现了图像到图像的风格迁移,通过使用 CycleGAN 将苹果变成橘子,将马变成斑马,或者反过来。在下一章中,我们将通过堆叠或结合两个或更多 GAN 模型来解决复杂的现实问题,如文本到图像合成和跨领域发现。
第四章:从文本构建逼真的图像
对于许多现实中的复杂问题,单一的生成对抗网络可能不足以解决问题。相反,最好将复杂问题分解为多个更简单的子问题,并使用多个 GAN 分别解决每个子问题。最后,你可以将这些 GAN 堆叠或连接在一起,以找到解决方案。
在本章中,我们将首先学习如何堆叠多个生成网络,从文本信息中生成逼真图像。接下来,你将将两个生成网络结合起来,自动发现不同领域之间的关系(如鞋子和手袋或男女演员之间的关系)。
本章我们将讨论以下主题:
-
什么是 StackGAN?它的概念和架构
-
使用 TensorFlow 从文本描述合成逼真图像
-
使用 DiscoGAN 发现跨领域关系
-
使用 PyTorch 从边缘生成手袋图像
-
使用 facescrub 数据转换性别(演员到女演员或反之)
StackGAN 简介
StackGAN 的思想最初由Han Zhang、Tao Xu、Hongsheng Li、Shaoting Zhang、Xiaolei Huang、Xiaogang Wang和Dimitris Metaxas在论文《Text to Photo-realistic Image Synthesis with Stacked Generative Adversarial Networks》[arXiv: 1612.03242, 2017]中提出,其中使用了 GAN 从文本描述生成伪造图像。
从文本合成逼真图像是计算机视觉中的一个挑战性问题,具有巨大的实际应用。使用 StackGAN,生成图像的问题可以分解成两个可管理的子问题。在这种方法中,我们基于某些条件(如文本描述和前一阶段的输出)将生成网络的两个阶段进行堆叠,从而实现从文本输入生成逼真图像的这一挑战性任务。
在深入讨论模型架构和实现细节之前,让我们先定义一些概念和符号:
-
Io: 这是原始图像
-
t: 文本描述
-
t: 文本嵌入
-
µ(t): 文本嵌入的均值
-
∑(t): 文本嵌入的对角协方差矩阵
-
pdata: 真实数据分布
-
pz: 噪声的高斯分布
-
z: 从高斯分布随机采样的噪声
条件增强
正如我们在第二章《无监督学习与 GAN》中所知道的,无监督学习与 GAN,在条件 GAN 中,生成器和判别器网络都接收附加的条件变量 c 来生成 G(z;c) 和 D(x;c)。这种公式化帮助生成器根据变量 c 生成图像。条件增强可以在给定少量图像-文本对的情况下生成更多的训练对,并且对于建模文本到图像的转换非常有用,因为同一句话通常会映射到具有不同外观的物体。文本描述首先通过编码器转换为文本嵌入 t,然后通过 char-CNN-RNN 模型非线性地转换,生成作为第一阶段生成器网络输入的条件潜变量。
由于文本嵌入的潜空间通常是高维的,为了缓解有限数据量下潜在数据流形不连续的问题,应用条件增强技术以生成从高斯分布 N(µ(t), ∑(t)) 中采样的附加条件变量 c^。
第一阶段
在这一阶段,GAN 网络学习以下内容:
-
生成粗略形状和基本颜色,用于根据文本描述创建物体
-
从从先验分布采样的随机噪声生成背景区域
在这一阶段生成的低分辨率粗略图像可能看起来不真实,因为它们有一些缺陷,比如物体形状畸变、缺失物体部分等。
第一阶段 GAN 交替训练判别器 D0(最大化损失)和生成器 G0(最小化损失),如以下方程所示:

第二阶段
在这一阶段,GAN 网络仅专注于绘制细节并修正从第一阶段生成的低分辨率图像中的缺陷(例如缺乏生动的物体部分、形状畸变以及文本中遗漏的一些细节),以生成基于文本描述的高分辨率真实图像。
第二阶段 GAN 交替训练判别器 D(最大化损失)和生成器 G(最小化损失),以低分辨率 G[0](z; c⁰) 结果和高斯潜变量 c^ 为条件:

注意
在第二阶段,随机噪声 z 被高斯条件变量 c^ 替代。此外,第二阶段中的条件增强具有不同的全连接层,用于生成文本嵌入的不同均值和标准差。
StackGAN 的架构细节
如下图所示,对于第一阶段的生成器网络 G[0],文本嵌入 t 首先输入一个全连接层生成高斯分布 N(µ0(t); ∑0(t)) 的 µ0 和 σ0(σ0 是 ∑0 的对角线值),然后从高斯分布中采样文本条件变量 c⁰。
对于第一阶段的判别器网络 D0,文本嵌入 t 首先通过全连接层被压缩至 Nd 维度,然后在空间上复制成 Md x Md x Nd 的张量。图像通过一系列下采样块被压缩到 Md x Md 的空间维度,然后通过与文本张量沿通道维度拼接的滤波器映射。最终,所得张量通过 1x1 卷积层共同学习图像和文本中的特征,并最终通过一个单节点全连接层输出决策得分。
第二阶段的生成器设计为一个带有残差块的编码器-解码器网络,并使用文本嵌入 t 来生成 Ng 维度的文本条件向量 c^,该向量被空间上复制成 Md x Md x Nd 的张量。然后,将第一阶段生成的结果 s0 输入到几个下采样块(即编码器),直到压缩到 Mg x Mg 的空间大小。图像特征与文本特征沿通道维度拼接后,通过多个残差块,学习图像和文本特征之间的多模态表示。最后,所得张量通过一系列上采样层(即解码器),生成一个 W x H 的高分辨率图像。
第二阶段的判别器与第一阶段相似,只是增加了额外的下采样块,以适应这一阶段的大尺寸图像。在训练判别器时,正样本对由真实图像及其相应的文本描述构成,而负样本则由两组组成:一组是包含文本嵌入不匹配的真实图像,另一组是包含合成图像及其相应文本嵌入的图像:

图 1. StackGAN 的架构。
来源:arXiv: 1612.03242, 2017
第一阶段生成器首先通过从给定文本草绘物体的粗略形状和基本颜色,并从随机噪声向量绘制背景,来绘制一张低分辨率的图像。第二阶段生成器则修正缺陷并为第一阶段的结果添加引人注目的细节,从而生成一张更为真实的高分辨率图像,并以第一阶段的结果为条件。
上采样块由最近邻上采样和 33 卷积层组成,每层步长为 1。每次卷积后应用批归一化和ReLU激活函数,最后一层除外。残差块再次由 33 卷积层组成,每层步长为 1,之后是批归一化和ReLU激活函数。下采样块由 44 卷积层组成,每层步长为 2,后接批归一化和 Leaky-ReLU 激活函数,第一卷积层不进行批归一化。
使用 TensorFlow 从文本合成图像
让我们实现代码,从文本合成现实图像并产生令人震惊的结果:
-
首先克隆
git仓库:github.com/Kuntal-G/StackGAN.git,然后切换到StackGAN目录:git clone https://github.com/Kuntal-G/StackGAN.git cd StackGAN注意
当前代码与旧版本的 TensorFlow(0.11)兼容,因此您需要安装低于 1.0 版本的 TensorFlow 才能成功运行此代码。您可以使用以下命令修改 TensorFlow 版本:
sudo pip install tensorflow==0.12.0。另外,请确保您的系统中已安装 torch。更多信息请参见此链接:
torch.ch/docs/getting-started.html。 -
然后,使用
pip命令安装以下软件包:sudo pip install prettytensor progressbar python-dateutil easydict pandas torchfile requests -
接下来,通过以下命令从以下链接下载预处理的 char-CNN-RNN 文本嵌入鸟类模型:
drive.google.com/file/d/0B3y_msrWZaXLT1BZdVdycDY5TEE/view:python google-drive-download.py 0B3y_msrWZaXLT1BZdVdycDY5TEE Data/ birds.zip -
现在,使用
unzip命令解压下载的文件:unzip Data/birds.zip -
接下来,下载并解压来自 Caltech-UCSD 的鸟类图像数据:
wget http://www.vision.caltech.edu/visipedia-data/CUB-200-2011/ CUB_200_2011.tgz -O Data/birds/CUB_200_2011.tgz tar -xzf CUB_200_2011.tgz -
现在,我们将对图像进行预处理,拆分为训练集和测试集,并将图像保存为 pickle 格式:
python misc/preprocess_birds.py![从文本生成图像,使用 TensorFlow]()
-
现在,我们将从以下链接下载预训练的 char-CNN-RNN 文本嵌入模型:
drive.google.com/file/d/0B3y_msrWZaXLNUNKa3BaRjAyTzQ/view,并将其保存到models/目录中,使用以下命令:python google-drive-download.py 0B3y_msrWZaXLNUNKa3BaRjAyTzQ models/ birds_model_164000.ckpt -
同样从
drive.google.com/file/d/0B0ywwgffWnLLU0F3UHA3NzFTNEE/view下载鸟类的 char-CNN-RNN 文本编码器,并将其保存在models/text_encoder目录下:python google-drive-download.py 0B0ywwgffWnLLU0F3UHA3NzFTNEE models/text_encoder/ lm_sje_nc4_cub_hybrid_gru18_a1_c512_0.00070_1_10_trainvalids.txt_iter30000.t7 -
接下来,我们将在
example_captions.txt文件中添加一些句子,以生成一些令人兴奋的鸟类图像:一只白色的鸟,有黑色的头顶和红色的喙这只鸟有红色的胸部和黄色的腹部 -
最后,我们将在
demo目录下执行birds_demo.sh文件,从example_captions.txt文件中的文本描述生成真实的鸟类图像:sh demo/birds_demo.sh![从文本生成图像,使用 TensorFlow]()
-
现在,生成的图像将保存在
Data/birds/example_captions/目录下,以下是相应的截图:![从文本生成图像,使用 TensorFlow]()
就这样,您已经根据文本描述生成了令人印象深刻的鸟类图像。您可以尝试自己的句子来描述鸟类,并通过描述来视觉验证结果。
通过 DiscoGAN 发现跨领域关系
跨领域关系对人类来说通常是自然的,他们可以轻松地识别来自不同领域的数据之间的关系而无需监督(例如,识别英文句子与其西班牙语翻译句子之间的关系,或者选择一双鞋来搭配一件衣服),但是自动学习这种关系是非常具有挑战性的,且需要大量的真实配对信息来说明这些关系。
发现生成对抗网络(DiscoGAN) arXiv: 1703.05192 ,2017 发现了两个视觉领域之间的关系,并通过生成一个领域的新图像来成功地将样式从一个领域转移到另一个领域,无需任何配对信息。DiscoGAN 的目标是将两个 GAN 模型耦合在一起,使每个领域都能映射到其对应的领域。DiscoGAN 背后的关键思想是确保领域 1 中的所有图像都能通过领域 2 中的图像表示,并使用重构损失来衡量在进行两次转换后——即从领域 1 到领域 2,再回到领域 1——原始图像的重构效果。
DiscoGAN 的架构和模型构造
在深入探讨与 DiscoGAN 相关的模型构造和各种损失函数之前,让我们首先定义一些相关的术语和概念:
-
G[AB]:
生成器函数,将输入图像 x[A]从领域 A 转换为领域 B 中的图像 x[AB] -
G[BA]:
生成器函数,将输入图像 x[B]从领域 B 转换为领域 A 中的图像 x[BA] -
G**AB:这是领域 A 中所有* x [A*]值的完整集合,这些值应该包含在领域 B 中
-
G**BA:这是领域 B 中所有* x [B*]值的完整集合,这些值应该包含在领域 A 中
-
D[A]:领域 A 中的
判别器函数 -
D[B]:领域 B 中的
判别器函数!DiscoGAN 的架构和模型构造图 2:DiscoGAN 架构,包含两个耦合的 GAN 模型
来源:arXiv- 1703.05192, 2017
DiscoGAN 的生成器模块由一个编码器-解码器对组成,用于执行图像翻译。生成器 G[AB]首先将输入图像 x[A]从领域 A 转换为领域 B 中的图像 x[AB]。然后,生成的图像被转换回领域 A 的图像 x[ABA],以使用重构损失(方程式-3)和某种形式的距离度量(如 MSE、余弦距离、铰链损失)与原始输入图像匹配。最后,生成器转换后的输出图像 x[AB]被输入到判别器中,并通过与领域 B 的真实图像进行比较进行评分:

生成器 GAB 接收两种类型的损失,如下所示(方程式-5):
-
L[CONSTA]:一种重建损失,衡量在两次翻译过程中,原始图像从领域 A->领域 B->领域 A 后重建得有多好
-
L[GANB]:标准的 GAN 损失,衡量生成的图像在 B 领域中的逼真度
而判别器D[B]接收标准的 GAN 判别器损失,如下所示(方程-6):

这两个耦合的 GANs 是同时训练的,两个 GANs 都学习从一个领域到另一个领域的映射,并且使用两个重建损失:L[CONSTA]和L[CONSTB],通过反向映射从两个领域重建输入图像。
生成器G[AB]和G[BA]的参数在两个 GANs 之间是共享的,生成的图像x[BA]和x[AB]分别输入到各自的判别器L[DA]和L[DB]中:

总生成器损失L[G]是耦合模型的两个 GAN 损失和每个部分模型的重建损失之和,如下所示(方程-7)。总判别器损失L[D]是两个判别器损失L[DA]和L[DB]的和,它们分别用于判别领域 A 和领域 B 中的真实和伪造图像(方程-8)。为了实现双射映射并保持一一对应,DiscoGAN 模型通过两个L[GAN]损失和两个L[CONST]重建损失进行约束。
注射映射意味着A中的每个成员都有其在B中的唯一匹配成员,而满射映射意味着B中的每个成员至少有一个匹配的A成员。
双射映射意味着注射和满射同时存在,且两个集合之间的成员有完美的一一对应关系。

DiscoGAN 的实现
现在让我们深入代码,理解 DiscoGAN 的概念(损失函数和衡量标准),以及 DiscoGAN 的架构。
生成器接收一个大小为 64x64x3 的输入图像,并将其通过一个编码器-解码器对进行处理。生成器的编码器部分由五个 4x4 卷积层组成,每个卷积层后跟着批量归一化和 Leaky ReLU。解码器部分由五个 4x4 的反卷积层组成,每个反卷积层后跟着批量归一化和ReLU激活函数,并输出一个目标领域的图像,大小为 64x64x3。以下是生成器的代码片段:
class Generator(nn.Module):
self.main = nn.Sequential(
# Encoder
nn.Conv2d(3, 64, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64, 64 * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64 * 2, 64 * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64 * 4, 64 * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 8),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64 * 8, 100, 4, 1, 0, bias=False),
nn.BatchNorm2d(100),
nn.LeakyReLU(0.2, inplace=True),
# Decoder
nn.ConvTranspose2d(100, 64 * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(64 * 8),
nn.ReLU(True),
nn.ConvTranspose2d(64 * 8, 64 * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 4),
nn.ReLU(True),
nn.ConvTranspose2d(64 * 4, 64 * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 2),
nn.ReLU(True),
nn.ConvTranspose2d(64 * 2, 64, 4, 2, 1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(True),
nn.ConvTranspose2d(64,3, 4, 2, 1, bias=False),
nn.Sigmoid()
. . .
)
判别器与生成器的编码器部分相似,由五个 4x4 卷积层组成,每个卷积层后跟着批量归一化和LeakyReLU激活函数。最后,我们对最终的卷积层(conv-5)应用sigmoid函数,生成一个在[0,1]之间的标量概率值,用于判断数据的真假。以下是判别器的代码片段:
class Discriminator(nn.Module):
self.conv1 = nn.Conv2d(3, 64, 4, 2, 1, bias=False)
self.relu1 = nn.LeakyReLU(0.2, inplace=True)
self.conv2 = nn.Conv2d(64, 64 * 2, 4, 2, 1, bias=False)
self.bn2 = nn.BatchNorm2d(64 * 2)
self.relu2 = nn.LeakyReLU(0.2, inplace=True)
self.conv3 = nn.Conv2d(64 * 2, 64 * 4, 4, 2, 1, bias=False)
self.bn3 = nn.BatchNorm2d(64 * 4)
self.relu3 = nn.LeakyReLU(0.2, inplace=True)
self.conv4 = nn.Conv2d(64 * 4, 64 * 8, 4, 2, 1, bias=False)
self.bn4 = nn.BatchNorm2d(64 * 8)
self.relu4 = nn.LeakyReLU(0.2, inplace=True)
self.conv5 = nn.Conv2d(64 * 8, 1, 4, 1, 0, bias=False)
. . . .
return torch.sigmoid( conv5 ), [relu2, relu3, relu4]
然后我们定义生成器和重构的损失标准,使用均方误差和二元交叉熵度量:
recon_criterion = nn.MSELoss()
gan_criterion = nn.BCELoss()
optim_gen = optim.Adam( gen_params, lr=args.learning_rate, betas=(0.5,0.999), weight_decay=0.00001)
optim_dis = optim.Adam( dis_params, lr=args.learning_rate, betas=(0.5,0.999), weight_decay=0.00001)
现在我们开始从一个领域生成图像到另一个领域,并计算重构损失,以了解在两次翻译(ABA 或 BAB)后原始图像的重构效果:
AB = generator_B(A)
BA = generator_A(B)
ABA = generator_A(AB)
BAB = generator_B(BA)
# Reconstruction Loss
recon_loss_A = recon_criterion( ABA, A )
recon_loss_B = recon_criterion( BAB, B )
接下来,我们计算每个领域的生成器损失和判别器损失:
# Real/Fake GAN Loss (A)
A_dis_real, A_feats_real = discriminator_A( A )
A_dis_fake, A_feats_fake = discriminator_A( BA )
dis_loss_A, gen_loss_A = get_gan_loss( A_dis_real, A_dis_fake, gan_criterion, cuda )
fm_loss_A = get_fm_loss(A_feats_real, A_feats_fake, feat_criterion)
# Real/Fake GAN Loss (B)
B_dis_real, B_feats_real = discriminator_B( B )
B_dis_fake, B_feats_fake = discriminator_B( AB )
dis_loss_B, gen_loss_B = get_gan_loss( B_dis_real, B_dis_fake, gan_criterion, cuda )
fm_loss_B = get_fm_loss( B_feats_real, B_feats_fake, feat_criterion )
gen_loss_A_total = (gen_loss_B*0.1 + fm_loss_B*0.9) * (1.-rate) + recon_loss_A * rate
gen_loss_B_total = (gen_loss_A*0.1 + fm_loss_A*0.9) * (1.-rate) + recon_loss_B * rate
最后,通过将两个跨域(A 和 B)的损失相加,计算 discogan 模型的总损失:
if args.model_arch == 'discogan':
gen_loss = gen_loss_A_total + gen_loss_B_total
dis_loss = dis_loss_A + dis_loss_B
使用 PyTorch 从边缘生成手袋
在本示例中,我们将使用来自伯克利的 pix2pix 数据集,根据相应的边缘生成逼真的手袋图像。在执行以下步骤之前,请确保已在计算机上安装了 PyTorch(pytorch.org/)和 OpenCV(docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html):
-
首先克隆
git仓库并切换到DiscoGAN目录:git clone https://github.com/SKTBrain/DiscoGAN.git cd DiscoGAN -
接下来使用以下命令下载
edges2handbags数据集:python ./datasets/download.py edges2handbags -
然后在两个领域之间应用图像翻译:边缘和手袋,使用下载的数据集:
python ./discogan/image_translation.py --task_name='edges2handbags'![使用 PyTorch 从边缘生成手袋]()
-
现在,图像将在每个 epoch 后每 1,000 次迭代保存一次(根据
image_save_interval参数),保存在results目录下,文件名包括在图像翻译步骤中之前使用的任务名称:![使用 PyTorch 从边缘生成手袋]()
以下是从域 A 到域 B 生成的跨域图像的示例输出:

图-3:左侧是跨域(A -> B -> A)生成的图像(边缘 -> 手袋 -> 边缘),而右侧是跨域(B -> A -> B)生成的图像(手袋 -> 边缘 -> 手袋)
使用 PyTorch 进行性别转换
在本示例中,我们将使用 facescrub 数据集中的名人面部图像,将演员和女演员之间的性别进行转换,或反之。和之前的示例一样,请确保你在执行以下步骤之前已在计算机上安装了 PyTorch 和 OpenCV:
-
首先克隆
git仓库并切换目录到DiscoGAN(如果你已经执行过之前的从边缘生成手袋示例,可以跳过这一步):git clone https://github.com/SKTBrain/DiscoGAN.git cd DiscoGAN -
接下来使用以下命令下载
facescrub数据集:python ./datasets/download.py facescrub -
然后在两个领域之间应用图像翻译,男性和女性,使用下载的数据集:
python ./discogan/image_translation.py --task_name= facescrub![使用 PyTorch 进行性别转换]()
-
现在,图像将在每个 epoch 后每 1,000 次迭代保存一次(根据
image_save_interval参数),保存在results目录下,文件名包括相应的任务名称(facescrub)和 epoch 间隔:![使用 PyTorch 进行性别转换]()
下面是从域 A(男性)到域 B(女性)生成的跨域图像的示例输出:

图 4:左侧是跨域(A -> B -> A)生成的图像(男性 -> 女性 -> 男性),右侧是跨域(B -> A -> B)生成的图像(女性 -> 男性 -> 女性)
DiscoGAN 对比 CycleGAN
DiscoGAN(之前讨论过)和 CycleGAN(在 第二章 中讨论,GAN 的无监督学习)的主要目标是通过找到源域 X 和目标域 Y 之间的映射来解决图像到图像的转换问题,而不需要配对信息。
从架构角度来看,这两个模型都由两个将一个域映射到其对应域的 GAN 组成,并将它们的损失组合为传统生成器损失的函数(通常在 GAN 中看到)和重建损失/循环一致性损失。
这两个模型之间并没有很大的不同之处,除了 DiscoGAN 使用两个重建损失(衡量在两个转换 X->Y->X 后原始图像的重建效果),而 CycleGAN 使用单一的循环一致性损失,其中包括两个翻译器 F 和 G(F 将图像从域 X 翻译到域 Y,G 执行相反操作),以确保保持两个平衡(F(G(b)) = b 和 G(F(a)) = a,其中 a,b 分别为域 X、Y 中的图像)。
摘要
到目前为止,您已经学习了通过使用 StackGAN 和 DiscoGAN 将多个 GAN 模型结合起来解决复杂现实生活中的问题(例如从文本合成图像和发现跨域关系)。在下一章中,您将学习一种处理深度学习中小数据集的重要技术,即使用预训练模型和特征转移,以及如何在分布式系统上大规模运行您的深度模型。
第五章 使用各种生成模型生成图像
深度学习在大数据和更深层模型中展现出巨大的优势。它拥有数百万个参数,训练可能需要数周的时间。一些现实场景可能没有足够的数据、硬件或资源来训练更大的网络,以达到所需的准确性。是否存在其他的解决方案,还是我们每次都需要从头开始重新发明训练的方法?
在本章中,我们将首先通过使用真实数据集(MNIST,cars vs cats vs dogs vs flower,LFW)的实际操作示例,了解现代深度学习应用中一种强大且广泛使用的训练方法——迁移学习。此外,您还将使用 Apache Spark 和 BigDL 在大型分布式集群上构建基于深度学习的网络。然后,您将结合迁移学习和 GAN,利用面部数据集生成高分辨率的真实图像。最后,您还将了解如何在图像上创造艺术性的幻觉,超越 GAN 的范畴。
本章将覆盖以下主题:
-
什么是迁移学习?——其优势和应用
-
使用预训练 VGG 模型和 Keras 对
cars vs dog vs flower进行分类 -
使用 Apache Spark 在大型分布式集群上训练和部署深度网络——深度学习流水线
-
通过特征提取和使用 BigDL 微调来识别手写数字
-
使用预训练模型和 SRGAN 生成高分辨率图像
-
使用 DeepDream 生成艺术性幻觉图像,并通过 VAE 进行图像生成
从零开始构建一个深度学习模型需要复杂的资源,并且非常耗时。因此,您不一定要从零开始构建这样的深度模型来解决手头的问题。您可以通过重用已经为类似问题构建的现有模型来满足您的需求,而不是重新发明同样的轮子。
假设您想要构建一辆自动驾驶汽车。您可以选择花费数年时间从零开始构建一个合适的图像识别算法,或者您可以直接使用 Google 基于 ImageNet 大数据集构建的预训练 Inception 模型。预训练模型可能无法达到您应用所需的准确度,但它能节省大量重新发明轮子的工作。通过一些微调和技巧,您的准确度水平肯定会提高。
迁移学习简介
预训练模型并不是针对特定用户数据集进行优化的,但对于与训练模型任务相似的当前任务,它们非常有用。
例如,一个流行的模型 InceptionV3 经过优化用于对 1000 个类别的图像进行分类,但我们的领域可能是分类某些狗的品种。深度学习中常用的技术之一就是迁移学习,它将一个已经训练好的模型适应于类似任务的当前任务。
这就是为什么迁移学习在深度学习从业者中获得了广泛的关注,并且近年来在许多实际应用中成为了首选技术的原因。它的核心是将知识(或特征)在相关领域之间进行迁移。
迁移学习的目的
假设你已经训练了一个深度神经网络来区分新鲜芒果和烂芒果。在训练过程中,网络需要成千上万张新鲜和烂芒果的图片以及数小时的训练,以学习一些知识,例如,如果任何水果腐烂,液体会流出来并产生难闻的气味。现在,凭借这一训练经验,网络可以用于其他任务/用例,利用在芒果图片训练过程中学到的腐烂特征知识来区分烂苹果和新鲜苹果。
迁移学习的一般方法是训练一个基础网络,然后将其前 n 层复制到目标网络的前 n 层。目标网络的剩余层随机初始化并朝着目标用例进行训练。
使用迁移学习的主要场景如下:
-
较小的数据集:当你拥有较小的数据集时,从头开始构建深度学习模型效果不好。迁移学习提供了一种方法,能够将预训练的模型应用于新类别的数据。例如,一个从 ImageNet 数据的百万张图片中训练出来的预训练模型,相较于一个从小数据集从头开始训练的深度学习模型,即使只用一小部分较小训练数据(例如 CIFAR-10),也能收敛到一个不错的解决方案。
-
更少的资源:深度学习过程(如卷积)需要大量的资源和时间。深度学习过程非常适合在高性能基于 GPU 的机器上运行。但是,通过使用预训练模型,你可以轻松地在不到一分钟的时间内,利用没有 GPU 的笔记本电脑/笔记本对完整的训练集(比如说 50,000 张图片)进行训练,因为大多数时候,模型仅在最终层进行修改,通过简单地更新一个分类器或回归器来完成。
使用预训练模型的各种方法
我们将讨论如何以不同的方式使用预训练模型:
-
使用预训练架构:我们可以仅使用架构并为新的数据集初始化随机权重,而不是转移训练好的模型的权重。
-
特征提取器:预训练模型可以作为特征提取机制使用,只需简单地去除网络的输出层(即提供每个类别的概率),然后冻结网络的所有前层,将其作为新数据集的固定特征提取器。
-
部分冻结网络:有时我们会训练我们的新模型的部分网络(即保持网络初始层的权重被冻结,只重新训练较高层的权重),而不是仅替换最后一层并从所有前一层提取特征。冻结的层数可以看作是另一个超参数。
![使用预训练模型的各种方法]()
图-1:使用预训练模型进行迁移学习
主要取决于数据大小和数据集的相似性,您可能需要决定如何进行迁移学习。以下表格讨论了这些场景:
| 高数据相似性 | 低数据相似性 | |
|---|---|---|
| 数据集较小 | 在数据量小但数据相似性高的情况下,我们只会修改预训练模型的输出层,并将其作为特征提取器使用。 | 当数据量和数据相似性都较低时,我们可以冻结预训练网络的前k层,只重新训练剩余的(n-k)层。这样,顶层可以根据新数据集进行定制,而小数据量也可以通过冻结的初始k层得到补偿。 |
| 数据集较大 | 在这种情况下,我们可以使用预训练模型的架构和初始权重。 | 尽管我们有一个较大的数据集,但与用于训练预训练模型的数据相比,数据差异很大,因此在这种情况下使用预训练模型效果不佳。相反,最好从头开始训练深度网络。 |
在图像识别中,迁移学习利用预训练的卷积层提取新输入图像的特征,这意味着只有原始模型的一小部分(主要是全连接层)需要重新训练。其余的网络保持冻结。通过这种方式,它通过仅将原始图像传递给冻结部分的网络一次,并且以后再也不会经过这部分网络,节省了大量时间和资源。
使用 Keras 进行汽车、猫、狗和花的分类
让我们实现迁移学习和微调的概念,使用定制数据集识别可定制的物体类别。每个类别(汽车、猫、狗和花)有 150 张训练图像和 50 张验证图像。
请注意,数据集是通过采集来自Kaggle Dogs vs.Cats(www.kaggle.com/c/dogs-vs-cats)、斯坦福汽车数据集(ai.stanford.edu/~jkrause/cars/car_dataset.html)和Oxford flower数据集(www.robots.ox.ac.uk/~vgg/data/flowers)的图像准备的。

图-2:汽车 vs 猫 vs 狗 vs 花的数据集结构
我们需要使用preprocessing函数进行一些预处理,并通过rotation、shift、shear、zoom和flip等参数应用各种数据增强变换:
train_datagen = ImageDataGenerator(
preprocessing_function=preprocess_input,
rotation_range=30,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True
)
test_datagen = ImageDataGenerator(
preprocessing_function=preprocess_input,
rotation_range=30,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True
)
train_generator = train_datagen.flow_from_directory(
args.train_dir,
target_size=(IM_WIDTH, IM_HEIGHT),
batch_size=batch_size,
)
validation_generator = test_datagen.flow_from_directory(
args.val_dir,
target_size=(IM_WIDTH, IM_HEIGHT),
batch_size=batch_size,
)
接下来,我们需要从keras.applications模块加载 InceptionV3 模型。include_top=False标志用于省略最后一层全连接层的权重:
base_model = InceptionV3(weights='imagenet', include_top=False)
通过添加大小为 1024 的全连接Dense层并在输出上应用softmax函数来初始化一个新的最后一层,以将值压缩到[0,1]之间:
def addNewLastLayer(base_model, nb_classes):
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(FC_SIZE, activation='relu')(x)
predictions = Dense(nb_classes, activation='softmax')(x)
model = Model(input=base_model.input, output=predictions)
return model
一旦网络的最后一层稳定下来(迁移学习),我们就可以开始重新训练更多的层(微调)。
使用一个实用方法冻结所有层并编译模型:
def setupTransferLearn(model, base_model):
for layer in base_model.layers:
layer.trainable = False
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
这是另一个实用方法,用于冻结 InceptionV3 架构中前两个 Inception 模块的底部并重新训练剩余的顶部:
def setupFineTune(model):
for layer in model.layers[:NB_IV3_LAYERS_TO_FREEZE]:
layer.trainable = False
for layer in model.layers[NB_IV3_LAYERS_TO_FREEZE:]:
layer.trainable = True
model.compile(optimizer=SGD(lr=0.0001, momentum=0.9),
loss='categorical_crossentropy')
现在我们已准备好使用fit_generator方法进行训练,并最终保存我们的模型:
history = model.fit_generator(
train_generator,
samples_per_epoch=nb_train_samples,
nb_epoch=nb_epoch,
validation_data=validation_generator,
nb_val_samples=nb_val_samples,
class_weight='auto')
model.save(args.output_model_file)
运行以下命令进行训练和微调:
python training-fine-tune.py --train_dir <path to training images> --val_dir <path to validation images>

即使数据集如此小,我们仍然能够利用预训练模型和迁移学习的优势,在验证集上实现 98.5%的准确率:

完成了!现在我们可以使用保存的模型来预测图像(无论是来自本地文件系统还是 URL)并进行测试:
python predict.py --image_url https://goo.gl/DCbuq8 --model inceptionv3-ft.model

使用 Apache Spark 进行大规模深度学习
深度学习是一个资源密集且计算密集的过程,拥有更多数据和更大的网络可以获得更好的结果,但其速度也会受到数据集大小的影响。在实践中,深度学习需要通过调整训练参数(即超参数调整)来进行实验,在这个过程中,你需要多次迭代或运行深度网络并且速度至关重要。常见的解决方法是使用更快的硬件(通常是 GPU)、优化的代码(使用适合生产环境的框架)和通过分布式集群扩展来实现某种形式的并行性。
数据并行性是将大型数据集分割成多个数据块,然后在分布式集群的独立节点上运行神经网络处理这些数据块的概念。
Apache Spark 是一个快速、通用、容错的框架,用于在大规模分布式数据集上进行交互式和迭代计算,它通过对 RDD 或 DataFrame 进行内存处理,而不是将数据保存到硬盘中,来实现大数据集的处理。它支持多种数据源以及存储层,并提供统一的数据访问,能够结合不同的数据格式、流数据,并使用高级可组合操作符定义复杂的操作。
今天,Spark 是大数据处理的超能力,使每个人都能访问大数据。但仅凭 Spark 或其核心模块无法在集群上训练或运行深度网络。在接下来的几个部分,我们将利用优化后的库在 Apache Spark 集群上开发深度学习应用程序。
注意
出于编码目的,我们将不涵盖分布式 Spark 集群的设置,而是使用 Apache Spark 独立模式。有关 Spark 集群模式的更多信息,请参见:spark.apache.org/docs/latest/cluster-overview.html。
使用 Spark 深度学习运行预训练模型
深度学习管道是一个开源库,利用 Apache Spark 集群的强大功能,轻松将可扩展的深度学习集成到机器学习工作流中。它建立在 Apache Spark 的 ML 管道之上进行训练,并使用 Spark DataFrame 和 SQL 部署模型。它提供高级 API,通过将预训练模型作为转换器集成到 Spark ML 管道中,以分布式方式运行迁移学习。
深度学习管道通过特征提取器的概念简化了迁移学习。特征提取器(在图像操作中是DeepImageFeaturizer)自动去除预训练神经网络模型的最后一层,并将所有前面的层输出作为特征,供特定于新问题领域的分类算法(例如逻辑回归)使用。
让我们在 Spark 集群上实现深度学习管道,预测flower数据集(download.tensorflow.org/example_images/flower_photos.tgz)的示例图像,使用预训练模型:
-
首先,启动带有深度学习管道包的 PySpark:
pyspark --master local[*] --packages databricks:spark-deep-learning:0.1.0-spark2.1-s_2.11提示
注意:如果在启动 PySpark 时遇到No module named sparkdl的错误,请查看 GitHub 页面以获取解决方法:
-
首先,读取图像并将其随机拆分为
train和test集。img_dir= "path to base image directory" roses_df = readImages(img_dir + "/roses").withColumn("label", lit(1)) daisy_df = readImages(img_dir + "/daisy").withColumn("label", lit(0)) roses_train, roses_test = roses_df.randomSplit([0.6, 0.4]) daisy_train, daisy_test = daisy_df.randomSplit([0.6, 0.4]) train_df = roses_train.unionAll(daisy_train) -
然后使用
InceptionV3模型通过DeepImageFeaturizer创建一个管道:featurizer = DeepImageFeaturizer(inputCol="image", outputCol="features", modelName="InceptionV3") lr = LogisticRegression(maxIter=20, regParam=0.05, elasticNetParam=0.3, labelCol="label") p = Pipeline(stages=[featurizer, lr]) -
现在,使用现有的预训练模型来拟合图像,其中
train_images_df是图像和标签的数据集:p_model = p.fit(train_df) -
最后,我们将评估准确性:
tested_df = p_model.transform(test_df) evaluator = MulticlassClassificationEvaluator(metricName="accuracy") print("Test set accuracy = " + str(evaluator.evaluate(tested_df.select("prediction", "label"))))![使用 Spark 深度学习运行预训练模型]()
除了DeepImageFeaturizer,我们还可以仅使用预先存在的模型进行预测,无需任何重新训练或微调,使用DeepImagePredictor:
sample_img_dir=<path to your image>
image_df = readImages(sample_img_dir)
predictor = DeepImagePredictor(inputCol="image", outputCol="predicted_labels", modelName="InceptionV3", decodePredictions=True, topK=10)
predictions_df = predictor.transform(image_df)
predictions_df.select("filePath", "predicted_labels").show(10,False)
输入图像及其前五个预测结果如下所示:

除了使用内置的预训练模型外,深度学习管道还允许用户在 Spark 预测管道中插入 Keras 模型或 TensorFlow 图。这实际上将任何在单节点机器上运行的单节点深度模型转化为可以在分布式环境下进行训练和部署的模型,并能够处理大量数据。
首先,我们将加载 Keras 内置的 InceptionV3 模型,并将其保存到文件中:
model = InceptionV3(weights="imagenet")
model.save('model-full.h5')
在预测阶段,我们只需加载模型并将图像传递给它,以获得期望的预测结果:
def loadAndPreprocessKerasInceptionV3(uri):
# this is a typical way to load and prep images in keras
image = img_to_array(load_img(uri, target_size=(299, 299)))
image = np.expand_dims(image, axis=0)
return preprocess_input(image)
transformer = KerasImageFileTransformer(inputCol="uri",
outputCol="predictions",
modelFile="model-full.h5",
imageLoader=loadAndPreprocessKerasInceptionV3,
outputMode="vector")
dirpath=<path to mix-img>
files = [os.path.abspath(os.path.join(dirpath, f)) for f in os.listdir(dirpath) if f.endswith('.jpg')]
uri_df = sqlContext.createDataFrame(files, StringType()).toDF("uri")
final_df = transformer.transform(uri_df)
final_df.select("uri", "predictions").show()
深度学习管道是一种在分布式 Spark 集群上进行迁移学习的非常快速的方法。但你一定注意到,特征提取器仅允许我们更改预训练模型的最终层。但是在某些场景下,你可能需要修改预训练网络的多个层以获得期望的结果,而深度学习管道并未提供这种完整的能力。
使用 BigDL 进行大规模手写数字识别
BigDL 是一个开源的分布式高性能深度学习库,可以直接运行在 Apache Spark 集群上。其高性能通过结合Intel® 数学核心库(MKL)和每个 Spark 任务中的多线程编程来实现。BigDL 提供类似 Keras 的高级 API(包括顺序和功能模型),用于构建深度学习应用并扩展以执行大规模的分析。使用 BigDL 的主要目的包括:
-
在 Spark 或 Hadoop 集群中运行深度学习模型并分析大量数据(比如在 Hive、HDFS 或 HBase 中)
-
将深度学习功能(包括训练和预测)添加到你的大数据工作流中!使用 BigDL 进行大规模手写数字识别
图 3:BigDL 在 Spark 集群上的执行
如图所示,BigDL 驱动程序首先在集群的 Spark 主节点上启动。然后,在集群管理器和驱动程序的帮助下,Spark 任务会分配到工作节点上的 Spark 执行器。BigDL 与 Intel MKL 交互,以加快这些任务的执行速度。
让我们使用mnist数据集实现一个大规模的深度神经网络,用于识别手写数字。首先,我们将准备训练和验证样本:
mnist_path = "datasets/mnist"
(train_data, test_data) = get_mnist(sc, mnist_path)
print train_data.count()
print test_data.count()
然后,我们将创建 LeNet 架构,包含两组卷积层、激活层和池化层,接着是一个全连接层、激活层、另一个全连接层,最后是一个SoftMax分类器。LeNet 小巧但足够强大,能够提供有趣的结果:
def build_model(class_num):
model = Sequential()
model.add(Reshape([1, 28, 28]))
model.add(SpatialConvolution(1, 6, 5, 5).set_name('conv1'))
model.add(Tanh())
model.add(SpatialMaxPooling(2, 2, 2, 2).set_name('pool1'))
model.add(Tanh())
model.add(SpatialConvolution(6, 12, 5, 5).set_name('conv2'))
model.add(SpatialMaxPooling(2, 2, 2, 2).set_name('pool2'))
model.add(Reshape([12 * 4 * 4]))
model.add(Linear(12 * 4 * 4, 100).set_name('fc1'))
model.add(Tanh())
model.add(Linear(100, class_num).set_name('score'))
model.add(LogSoftMax())
return model
lenet_model = build_model(10)
现在,我们将配置一个优化器并设置验证逻辑:
optimizer = Optimizer(
model=lenet_model,
training_rdd=train_data,
criterion=ClassNLLCriterion(),
optim_method=SGD(learningrate=0.4, learningrate_decay=0.0002),
end_trigger=MaxEpoch(20),
batch_size=2048)
optimizer.set_validation(
batch_size=2048,
val_rdd=test_data,
trigger=EveryEpoch(),
val_method=[Top1Accuracy()]
)
trained_model = optimizer.optimize()
然后,我们将取几个测试样本,并通过检查预测标签和真实标签来进行预测:
predictions = trained_model.predict(test_data)
最后,我们将在 Spark 集群中使用spark-submit命令训练 LeNet 模型。根据你的 Apache Spark 版本下载 BigDL 发行版(bigdl-project.github.io/master/#release-download/),然后执行代码中提供的文件(run.sh),以在 Spark 集群中提交任务:
SPARK_HOME= <path to Spark>
BigDL_HOME= <path to BigDL>
PYTHON_API_ZIP_PATH=${BigDL_HOME}/bigdl-python-<version>.zip
BigDL_JAR_PATH=${BigDL_HOME}/bigdl-SPARK-<version>.jar
export PYTHONPATH=${PYTHON_API_ZIP_PATH}:${BigDL_HOME}/conf/spark-bigdl.conf:$PYTHONPATH
${SPARK_HOME}/bin/spark-submit \
--master <local or spark master url>\
--driver-cores 5 \
--driver-memory 5g \
--total-executor-cores 16 \
--executor-cores 8 \
--executor-memory 10g \
--py-files ${PYTHON_API_ZIP_PATH},${BigDL_HOME}/BigDL-MNIST.py\
--properties-file ${BigDL_HOME}/conf/spark-bigdl.conf \
--jars ${BigDL_JAR_PATH} \
--conf spark.driver.extraClassPath=${BigDL_JAR_PATH} \
--conf spark.executor.extraClassPath=bigdl-SPARK<version>.jar \
${BigDL_HOME}/BigDL-MNIST.py
有关spark-submit的更多信息,请访问:spark.apache.org/docs/latest/submitting-applications.html。
一旦你提交了任务,你可以通过Spark Master应用页面跟踪任务进度,如下所示:

图 4:在 Apache Spark 集群上运行的 BigDL LeNet5 模型任务
任务成功完成后,你可以查看 Spark 工作节点的日志,以验证你的模型的准确性,类似于下图所示:
INFO DistriOptimizer$:536 - Top1Accuracy is Accuracy(correct: 9568, count: 10000, accuracy: 0.9568)
使用 SRGAN 生成高分辨率图像
超分辨率生成网络(SRGAN)在从低分辨率图像生成高分辨率图像方面表现出色。在训练阶段,通过对高分辨率图像应用高斯滤波器后进行下采样操作,将高分辨率图像转化为低分辨率图像。
在深入了解网络架构之前,让我们定义一些符号:
-
I^(LR): 低分辨率图像,大小为宽度(W) × 高度(H) × 颜色通道(C)
-
I^(HR): 高分辨率图像,大小为rW × rH × C
-
I^(SR): 超分辨率图像,大小为rW × rH × C
-
r: 下采样因子
-
G[θG]: 生成器网络
-
D[θD]: 判别器网络
为了实现从低分辨率图像估计高分辨率输入图像的目标,生成器网络作为一个前馈卷积神经网络进行训练,G[θG]由θG参数化,其中θG由深度网络 L 层的权重(W1:L)和偏置(b1:L)表示,并通过优化超分辨率特定的loss函数得到。对于具有高分辨率的训练图像!使用 SRGAN 生成高分辨率图像,n=1;N 及其对应的低分辨率图像!使用 SRGAN 生成高分辨率图像,n=1,N,我们可以按如下方式求解θG:

注意
感知损失函数lSR的公式对生成器网络的性能至关重要。通常,感知损失基于均方误差(MSE)建模,但为了避免过于平滑的纹理带来的不理想结果,提出了一种新的基于预训练 19 层 VGG 网络的 ReLU 激活层的内容损失函数。
感知损失是几种损失函数的加权组合,这些损失函数映射超分辨率图像的重要特征,如下所示:

-
内容损失:基于 VGG 的内容损失定义为重建图像G**[θG] (I^(LR))与对应高分辨率图像I**HR之间的欧氏距离。这里
[i,j]表示在 VGG19 网络中第 i 个最大池化层前的第 j 个卷积层(激活后)获得的特征图。而Wi,j、Hi,j描述了VGG网络中各个特征图的维度:![使用 SRGAN 生成高分辨率图像]()
-
对抗损失:生成损失基于判别器D[θD](G[θG](I[LR]*))对所有训练图像的概率,鼓励网络选择位于自然图像流形上的解决方案,以此来欺骗判别器网络:
![使用 SRGAN 生成高分辨率图像]()
类似于对抗网络的概念,SRGAN 方法背后的总体思想是训练一个生成器G,其目标是欺骗一个判别器D,该判别器经过训练,能够区分超分辨率图像和真实图像:

基于这一方法,生成器学习创造出与真实图像高度相似的解决方案,从而使判别器D很难进行分类。这也鼓励感知上优越的解决方案位于自然图像的子空间流形上。
SRGAN 架构
如下图所示,生成器网络G由B个残差块组成,布局相同。每个块包含两个卷积层,使用小的 3×3 核和 64 个特征图,随后是批归一化层[32]和 ParametricReLU[28]作为激活函数。输入图像的分辨率通过两个训练过的子像素卷积层增加。
判别器网络使用 Leaky ReLU 激活函数(α = 0.2),并由八个卷积层组成,卷积核的数量逐渐增加,从 64 个 3×3 卷积核增加到 512 个,每次特征数量翻倍,使用步幅卷积来减少图像分辨率。
结果的 512 个特征图通过两个全连接层,随后通过最终的 sigmoid 激活层,获得生成图像样本的分类概率:

图 5:生成器和判别器网络的架构,标注了每个卷积层的对应核大小(k)、特征图数量(n)和步幅(s)。
来源:arXiv, 1609.04802, 2017
现在是时候深入了解 TensorFlow 代码,并使用 LFW 人脸数据集生成高分辨率图像了。
生成器网络首先构建为一个单独的反卷积层,具有 3×3 的内核和 64 个特征图,后接 ReLU 作为activation函数。接下来是五个残差块,每个块具有两个卷积层,后接批量归一化和 ReLU。最后,通过两个训练过的像素洗牌层增加输入图像的分辨率:
def generator(self, x, is_training, reuse):
with tf.variable_scope('generator', reuse=reuse):
with tf.variable_scope('deconv1'):
x = deconv_layer(
x, [3, 3, 64, 3], [self.batch_size, 24, 24, 64], 1)
x = tf.nn.relu(x)
shortcut = x
# 5 Residual block with identical layout of deconvolution layers having batch norm and relu as activation function.
for i in range(5):
mid = x
with tf.variable_scope('block{}a'.format(i+1)):
x = deconv_layer(x, [3, 3, 64, 64], [self.batch_size, 24,
24, 64], 1)
x = batch_normalize(x, is_training)
x = tf.nn.relu(x)
# 2 deconvolution layers having pixel-suffle and relu as activation function.
with tf.variable_scope('deconv3'):
x = deconv_layer(x, [3, 3, 256, 64], [self.batch_size, 24,
24, 256], 1)
x = pixel_shuffle_layer(x, 2, 64) # n_split = 256 / 2 ** 2
x = tf.nn.relu(x)
with tf.variable_scope('deconv4'):
x = deconv_layer(x, [3, 3, 64, 64], [self.batch_size, 48,
48, 64], 1)
x = pixel_shuffle_layer(x, 2, 16)
x = tf.nn.relu(x)
. . . . . . . . [code omitted for clarity]
return x
deconvolution layer函数使用 TensorFlow 的conv2d_transpose方法定义,并采用 Xavier 初始化,具体如下:
def deconv_layer(x, filter_shape, output_shape, stride, trainable=True):
filter_ = tf.get_variable(
name='weight',
shape=filter_shape,
dtype=tf.float32,
initializer=tf.contrib.layers.xavier_initializer(),
trainable=trainable)
return tf.nn.conv2d_transpose(
value=x,
filter=filter_,
output_shape=output_shape,
strides=[1, stride, stride, 1])
判别器网络由八个卷积层组成,使用 3×3 的滤波器内核,内核数量从 64 逐步增加至 512。最终的 512 个特征图被拉平,并通过两层密集的全连接层,最后通过一个 softmax 层以获得生成图像样本的分类概率:
def discriminator(self, x, is_training, reuse):
with tf.variable_scope('discriminator', reuse=reuse):
with tf.variable_scope('conv1'):
x = conv_layer(x, [3, 3, 3, 64], 1)
x = lrelu(x)
with tf.variable_scope('conv2'):
x = conv_layer(x, [3, 3, 64, 64], 2)
x = lrelu(x)
x = batch_normalize(x, is_training)
. . . . . . [code omitted for clarity]
x = flatten_layer(x)
with tf.variable_scope('fc'):
x = full_connection_layer(x, 1024)
x = lrelu(x)
with tf.variable_scope('softmax'):
x = full_connection_layer(x, 1)
return x
网络使用 LeakyReLU(α = 0.2)作为卷积层的activation函数:
def lrelu(x, trainbable=None):
alpha = 0.2
return tf.maximum(alpha * x, x)
convolution layer函数使用 TensorFlow 的conv2d方法定义,并采用 Xavier 初始化,具体如下:
def conv_layer(x, filter_shape, stride, trainable=True):
filter_ = tf.get_variable(
name='weight',
shape=filter_shape,
dtype=tf.float32,
initializer=tf.contrib.layers.xavier_initializer(),
trainable=trainable)
return tf.nn.conv2d(
input=x,
filter=filter_,
strides=[1, stride, stride, 1],
padding='SAME')
请注意,代码实现使用了最小二乘loss函数(以避免梯度消失问题)来代替 SRGAN 原论文中提出的 sigmoid 交叉熵loss函数(arXiv, 1609.04802, 2017):
def inference_adversarial_loss(real_output, fake_output):
alpha = 1e-5
g_loss = tf.reduce_mean(
tf.nn.l2_loss(fake_output - tf.ones_like(fake_output)))
d_loss_real = tf.reduce_mean(
tf.nn.l2_loss(real_output - tf.ones_like(true_output)))
d_loss_fake = tf.reduce_mean(
tf.nn.l2_loss(fake_output + tf.zeros_like(fake_output)))
d_loss = d_loss_real + d_loss_fake
return (g_loss * alpha, d_loss * alpha)
generator_loss, discriminator_loss = (
inference_adversarial_loss(true_output, fake_output))
有关最小二乘 GAN的更多信息,请访问:arxiv.org/abs/1611.04076
运行 SRGAN 的code目录结构如下:

首先让我们下载一个LFW 人脸数据集并进行一些预处理(正面人脸检测以及将数据集拆分为训练集和测试集),并将数据集存储在data/目录下:
python download-preprocess-lfw.py

接下来从以下链接下载预训练的 VGG19 模型,解压后保存在backup/目录下:
drive.google.com/open?id=0B-s6ok7B0V9vcXNfSzdjZ0lCc0k
接下来执行trainSrgan.py文件,使用 VGG19 模型开始 SRGAN 操作:
python trainSrgan.py
一旦训练开始,生成器网络将开始在result/目录中生成超分辨率图像。以下是从result/目录中展示的一些示例图像:

使用 DeepDream 生成艺术化的幻觉图像
DeepDream 算法是一种改进的神经网络,通过改变图像以适应训练数据的方向,从而能够产生令人印象深刻的超现实、梦幻般的幻觉效果。它使用反向传播改变图像,而不是通过网络改变权重。
大致上,算法可以概括为以下步骤:
-
选择一个你认为有趣的网络层和滤波器。
-
然后计算图像在该层的激活值。
-
将滤波器的激活反向传播回输入图像。
-
将梯度与学习率相乘,并将其添加到输入图像中。
-
重复步骤 2 到 4,直到对结果满意为止。
在输出上迭代地应用算法,并在每次迭代后进行一些缩放,帮助网络通过探索它已知的事物集合,生成源源不断的新印象。
让我们深入代码,生成一个致幻的梦幻图像。我们将对 Keras 中预训练的 VGG16 模型的各个层应用以下设置。请注意,除了使用预训练模型外,我们也可以将这个设置应用于你选择的全新神经网络架构:
settings_preset = {
'dreamy': {
'features': {
'block5_conv1': 0.05,
'block5_conv2': 0.02
},
'continuity': 0.1,
'dream_l2': 0.02,
'jitter': 0
}
}
settings = settings_preset['dreamy']
这个实用函数基本上加载、调整大小并将图像格式化为适当的张量格式:
def preprocess_image(image_path):
img = load_img(image_path, target_size=(img_height, img_width))
img = img_to_array(img)
img = np.expand_dims(img, axis=0)
img = vgg16.preprocess_input(img)
return img
然后我们计算连续性损失,以使图像具有局部连贯性,并避免出现像论文中讨论的全变差损失变体那样的模糊混乱(www.robots.ox.ac.uk/~vedaldi/assets/pubs/mahendran15understanding.pdf):
def continuity_loss(x):
assert K.ndim(x) == 4
a = K.square(x[:, :img_height-1, :img_width-1, :] -
x[:, 1:, :img_width-1, :])
b = K.square(x[:, :img_height-1, :img_width-1, :] -
x[:, :img_height-1, 1:, :])
# (a+b) is the squared spatial gradient, 1.25 is a hyperparameter # that should be >1.0 as discussed in the aforementioned paper
return K.sum(K.pow(a+b, 1.25))
接下来,我们将加载 VGG16 模型,并使用预训练的权重:
model = vgg16.VGG16(input_tensor=dream, weights='imagenet', include_top=False)
之后,我们将添加层特征的l2范数到loss中,然后再添加连续性损失,以提供图像的局部连贯性,接着再次将l2范数添加到损失中,以防止像素值过高,然后计算与损失相关的梦境梯度:
loss += settings['continuity'] * continuity_loss(dream) / np.prod(img_size)
loss += settings['dream_l2'] * K.sum(K.square(dream)) / np.prod(img_size)
grads = K.gradients(loss, dream)
最后,我们将在输入图像上添加random_jitter,并对生成图像的像素运行L-BFGS优化器,以最小化损失:
random_jitter = (settings['jitter']*2) * (np.random.random(img_size)-0.5)
x += random_jitter
# run L-BFGS
x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x.flatten(),
fprime=evaluator.grads, maxfun=7)
最后,我们将解码梦境并将其保存在输出图像文件中:
x = x.reshape(img_size)
x -= random_jitter
img = deprocess_image(np.copy(x))
fn = result_prefix + '_at_iteration_%d.png' % i
imsave(fn, img)
仅经过五次迭代生成的梦幻输出如下所示:

图-6:左侧显示原始输入图像,右侧显示由 DeepDream 创建的梦幻艺术图像
使用 TensorFlow 生成手写数字与 VAE
变分自编码器(VAE)将无监督学习与变分贝叶斯方法巧妙地结合在一起,形成一个精简的包。它通过将输入、隐藏表示和重建输出视为有向图模型中的概率随机变量,在基本的自编码器方法上加入了概率化的转折。
从贝叶斯的角度来看,编码器变成了一个变分推理网络,它将观察到的输入映射到潜在空间的后验分布,而解码器则变成了一个生成网络,将任意的潜在坐标映射回原始数据空间的分布。
VAE 的核心在于对编码网络添加约束,使其生成的大致遵循单位高斯分布的潜在向量(这个约束使得 VAE 与标准自编码器有所区别),然后通过解码器网络将潜在向量重新构建成图像:

VAE 的现实世界类比
假设我们想生成数据(一个动物),一种有效的方式是先决定我们想生成什么样的数据,再实际生成数据。因此,我们必须想象一些关于如何描述动物的标准,比如它应该有四条腿并能够游泳。一旦我们有了这些标准,我们就可以通过从动物王国中采样来生成动物。我们的想象标准类似于潜在变量。一开始决定潜在变量有助于很好地描述数据,否则就像是盲目地生成数据。
VAE 的基本思想是通过 p(z|x) 推断 p(z)。现在让我们使用一些数学符号来展开:
-
x:表示我们想生成的数据(即动物)
-
z:表示潜在变量(即我们的想象力)
-
p(x):表示数据的分布(即动物王国)
-
p(z):表示潜在变量的正态概率分布(即想象力的来源——我们的脑袋)
-
p(x|z):给定潜在变量生成数据的概率分布(即将想象力转化为现实的动物)
根据类比示例,我们希望将想象力仅局限于动物王国领域,因此我们不应该去想诸如根、叶、金钱、玻璃、GPU、冰箱、地毯等事物,因为这些东西与动物王国几乎没有任何共同点。
VAE 的loss函数基本上是负对数似然函数,并带有一个正则项,如下所示:

第一个项是第 i 个数据点的期望负对数似然或重建损失,其中期望是根据编码器对表示的分布进行计算的。这个项帮助解码器很好地重建数据,如果解码失败则会产生很大的损失。第二个项表示编码器分布 q(z|x) 和 p(z) 之间的 Kullback-Leibler 散度,并充当正则项,当编码器的输出表示 z 偏离正态分布时会对损失加上惩罚。
现在让我们深入研究如何使用 TensorFlow 从 MNIST 数据集生成手写数字的代码。首先,我们创建一个具有单隐藏层的编码器网络 Q(z|X),它将 X 作为输入,并输出 μ(X) 和 Σ(X) 作为高斯分布的一部分:
X = tf.placeholder(tf.float32, shape=[None, X_dim])
z = tf.placeholder(tf.float32, shape=[None, z_dim])
Q_W1 = tf.Variable(xavier_init([X_dim, h_dim]))
Q_b1 = tf.Variable(tf.zeros(shape=[h_dim]))
Q_W2_mu = tf.Variable(xavier_init([h_dim, z_dim]))
Q_b2_mu = tf.Variable(tf.zeros(shape=[z_dim]))
Q_W2_sigma = tf.Variable(xavier_init([h_dim, z_dim]))
Q_b2_sigma = tf.Variable(tf.zeros(shape=[z_dim]))
def Q(X):
h = tf.nn.relu(tf.matmul(X, Q_W1) + Q_b1)
z_mu = tf.matmul(h, Q_W2_mu) + Q_b2_mu
z_logvar = tf.matmul(h, Q_W2_sigma) + Q_b2_sigma
return z_mu, z_logvar
现在我们创建解码器网络 P(X|z):
P_W1 = tf.Variable(xavier_init([z_dim, h_dim]))
P_b1 = tf.Variable(tf.zeros(shape=[h_dim]))
P_W2 = tf.Variable(xavier_init([h_dim, X_dim]))
P_b2 = tf.Variable(tf.zeros(shape=[X_dim]))
def P(z):
h = tf.nn.relu(tf.matmul(z, P_W1) + P_b1)
logits = tf.matmul(h, P_W2) + P_b2
prob = tf.nn.sigmoid(logits)
return prob, logits
然后我们计算重构损失和 Kullback-Leibler 散度损失,并将它们相加得到 VAE 网络的总损失:
recon_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=X), 1)
kl_loss = 0.5 * tf.reduce_sum(tf.exp(z_logvar) + z_mu**2 - 1\. - z_logvar, 1)
# VAE loss
vae_loss = tf.reduce_mean(recon_loss + kl_loss)
然后使用 AdamOptimizer 来最小化损失:
solver = tf.train.AdamOptimizer().minimize(vae_loss)
运行文件(VAE.py 或 VAE.ipynb)以启动 VAE 操作,并使用 MNIST 数据集,图像将生成在输出文件夹中。样本手写数字图像在经过 10,000 次迭代后生成:

两种生成模型——GAN 和 VAE 的比较
尽管生成模型的这两种方法都非常激动人心,并且帮助研究人员在无监督领域取得了进展,同时具备生成能力,但这两种模型在训练方式上有所不同。GAN 根植于博弈论,目标是找到判别网络和生成网络之间的纳什均衡。而 VAE 基本上是一个基于贝叶斯推断的概率图模型,其目标是潜在建模,也就是说,它尝试对底层数据的概率分布进行建模,从而从该分布中采样生成新数据。
与 GAN 相比,VAE 有一种明确已知的评估模型质量的方式(例如对数似然,通常通过重要性采样或下界估算),但 VAE 的问题在于它在计算潜在损失时使用直接的均方误差,而不是使用对抗网络,因此它们过度简化了目标任务,因为它们只能在潜在空间中工作,结果生成的图像通常比 GAN 更模糊。
总结
迁移学习解决了如何有效处理小数据的问题,而无需从头开始重新发明训练轮子。你已经学会了从预训练模型中提取并迁移特征,并将其应用于自己的问题领域。此外,你还掌握了在大规模分布式系统中使用 Spark 及其相关组件训练和运行更深层的模型。然后,你利用迁移学习的力量,在 SRGAN 中生成了一个逼真的高分辨率图像。此外,你还掌握了其他生成模型方法的概念,如 VAE 和 DeepDream,用于艺术图像生成。在最后一章中,我们将把焦点从训练深度模型或生成模型转移到学习在生产环境中部署基于深度学习的应用程序的各种方法。
第六章。将机器学习带入生产环境
许多机器学习和深度学习的教程、教材和视频只专注于模型的训练和评估。但如何将训练好的模型投入生产,并在实时场景中使用或让客户访问呢?
在本章中,您将使用 LFW 数据集开发一个面部图像修正系统,通过您训练的 GAN 模型自动修复损坏的图像。接下来,您将学习几种将机器学习或深度学习模型投入生产的技术,包括在数据中心和云端的微服务容器化环境中部署模型。最后,您将学习如何在无服务器环境和托管的云服务中运行深度模型。
本章我们将讨论以下内容:
-
使用 DCGAN 构建图像修正系统
-
部署机器学习模型的挑战
-
基于容器的微服务架构
-
部署深度学习模型的各种方法
-
在 Docker 上服务 Keras 基础的深度模型
-
在 GKE 上部署深度模型
-
使用 AWS Lambda 和 Polly 进行无服务器的图像识别与语音处理
-
在云托管服务上运行人脸检测
使用 DCGAN 构建图像修正系统
图像修正和图像修复是相关的技术,用于填充或补全图像中缺失或损坏的部分。构建一个能够填补缺失部分的系统通常需要两类信息:
-
上下文信息:帮助根据周围像素提供的信息推断缺失的像素
-
感知信息:帮助解释填充/补全的部分看起来是正常的,就像在现实生活中或其他图片中看到的一样
在本例中,我们将使用 Labeled Face in the Wild(LFW)数据集和 DCGAN 开发一个图像修正或补全系统。有关 DCGAN 及其架构,请参考第二章,无监督学习与 GAN。
在开始构建图像修正系统的步骤之前,我们先定义一些符号和 loss 函数:
-
x: 损坏的图像。
-
M: 表示一个二进制掩码,其值为 1(表示我们想保留的图像部分)或 0(表示我们想修复/补全的图像部分)。x和M这两个矩阵的逐元素相乘,如
所示,将返回图像的原始部分。 -
pdata: 被采样数据的未知分布。
一旦我们训练好 DCGAN 的判别器 D(x) 和生成器 G(z),就可以利用它通过最大化 D(x) 来完成图像 x 中缺失的像素。
上下文损失惩罚 G(z),如果其没有为输入图像中已知像素位置创建相似图像,方法是逐元素地将 x 中的像素从 G(z) 中减去,并计算它们之间的差异:

感知损失使用与训练 DCGAN 时相同的标准,以确保恢复的图像看起来真实:

接下来,我们需要从生成器中找到一个图像,G(z),它能提供合理的缺失像素重建。然后,可以将完成的像素
添加到原始像素中,从而生成重建图像:

提示
在 CPU 上训练深度卷积网络可能会非常慢,因此建议使用支持 CUDA 的 GPU 进行涉及卷积或反卷积的图像深度学习活动。
构建图像修正系统的步骤
确保你已下载本章的代码:
-
DCGAN-ImageCorrection项目将具有以下目录结构:![构建图像修正系统的步骤]()
-
现在从
vis-www.cs.umass.edu/lfw下载LFW数据集(已对深度漏斗进行对齐),并将其内容解压到lfw目录下:wget http://vis-www.cs.umass.edu/lfw/lfw-funneled.tgz tar -xvzf lfw-funneled.tgz -
接下来,执行
create_tfrecords.py,将LFW图像转换为 TensorFlow 标准格式。修改 Python 文件中LFW图像路径的位置:base_path = <Path to lfw directory> python create_tfrecords.py这将生成
data目录下的tfrecords文件,如下所示:![构建图像修正系统的步骤]()
-
现在,执行以下命令训练 DCGAN 模型:
python train_generate.py你可以在 Python 文件中修改
max_itr属性,确定训练应持续的最大迭代次数。一旦训练开始,每进行 5000 次迭代,你将在lfw-gen目录下找到生成的图像,如下所示:![构建图像修正系统的步骤]()
-
最后,你可以使用训练好的 DCGAN 模型来修正损坏的图像。你需要将损坏的图像放在
complete_src目录下,并执行以下命令:python image_correction.py --is_complete True --latest_ckpt <checkpoint number>
你还可以通过在上述命令中指定center或random来修改遮罩类型,使用masktype属性。

上述命令将在完整目录下生成修正或完成的图像,如下所示:

将模型部署到生产环境中的挑战
大多数研究人员和机器学习从业者专注于机器学习或深度学习模型的训练和评估。研究中构建模型的现实世界类比类似于在家做饭,而在生产环境中构建或部署该模型则类似于在餐厅为各种不同的顾客(他们的口味随时间变化)做饭。
以下是模型在生产部署过程中常见的一些挑战:
-
可扩展性:真实的生产环境与训练或研究环境有很大的不同。你通常需要应对大量的请求而不影响性能。你的模型应该能够根据流量自动扩展(向上/向外),并在流量低时自动缩减(向下/向内)。
-
自动化模型训练或更新:现实世界中的数据具有时间动态性,随着模型进入真实的生产环境,数据开始与最初用于训练模型的数据有所不同。这意味着你需要重新训练模型(有时是自动化的),并在模型之间无缝切换。
-
开发语言之间的互操作性:通常,由两个人或不同的团队负责模型的研究(训练)和生产化,而研究使用的语言可能与生产所偏好的语言不同。这会导致许多问题,因为机器学习模型在不同的编程语言中有不同的实现,尽管模型本质上是相同的。
-
训练集元数据的知识:现实世界中的生产数据可能会缺失值,你需要使用缺失值填充技术来处理这个问题。虽然在生产系统中你不保留训练数据的信息,但为了正确地填充生产环境中到达的缺失值,你必须存储用于填充的训练集统计信息。
-
实时评估模型性能:在生产环境中评估模型性能通常需要收集实际数据(或其他实时指标),并随着模型处理更多数据而生成动态页面。此外,你可能需要进行A/B测试,通过同时部署两个或更多具有相同功能的模型来评估生产中的性能。
使用容器的微服务架构
在传统的单体架构中,应用程序将所有功能打包到单一的包(如 EAR 或 WAR)中,并将其部署到应用服务器(如 JBoss、Tomcat 或 WebLogic)上。尽管单体应用程序有各自独立且可区分的组件,但所有组件都打包在一个框架下。
单体架构的缺点
单体设计的一些常见陷阱如下:
-
单体架构中的功能组件被打包在一个应用程序下,并且没有隔离。因此,修改单个组件需要更新整个应用程序,从而使整个应用程序停机。在生产环境中,这是不希望出现的情况。
-
扩展单体应用程序效率低下,因为为了扩展,你必须在多个服务器上部署应用程序的每一个副本(WAR 或 EAR),这将占用相同数量的资源。
-
在现实世界中,通常一两个功能组件相比其他组件使用频繁。但是在单体设计中,所有组件将使用相同的资源,因此很难将高度使用的组件分离开来,从而提升整个应用程序的性能。
微服务是一种技术,它将大型软件项目分解为松散耦合的模块/服务,这些模块/服务通过简单的 API 相互通信。基于微服务的架构将每个功能放入独立的服务中,从而克服了单体设计的缺点。
微服务架构的好处
微服务设计模式的一些优势如下:
-
单一责任原则:微服务架构确保每个功能通过简单的 API 作为独立的服务进行部署或暴露。
-
高可扩展性:高度使用或需求量大的服务可以部署在多个服务器上,以处理大量请求/流量,从而提高性能。这对于单一的大型单体服务来说是很难实现的。
-
提高容错性:单个模块/服务的故障不会影响整个应用程序,并且可以快速恢复或重启故障模块,因为该模块作为独立服务运行。而在单体或庞大的服务中,某个组件/模块出现错误可能会影响到其他模块/功能。
-
技术栈的自由度:微服务允许您为特定功能选择最适合的技术,并帮助您在单个服务上尝试新的技术栈。
部署基于微服务的应用程序的最佳方式是将其放入容器中。
容器
容器是可共享的、轻量级的进程,运行在主机操作系统之上,并共享主机操作系统的内核(二进制文件和库)。容器通过一层抽象同时解决了许多复杂的问题。容器的流行可以用这个美妙的三位一体来描述:隔离性!可移植性!可重复性!
Docker
Docker 是最受欢迎的开源项目之一,是一种非常流行的容器化引擎,允许将服务/应用程序及其所有依赖项打包在一起,便于在本地或云中部署。
Kubernetes
Kubernetes 是 Google 的另一个开源项目,它为容器提供了编排服务,支持自动水平扩展、服务发现、负载均衡等功能。简而言之,它自动化了在云中管理容器化应用程序/服务的过程。
注意
在本节中,我们将 Docker 作为容器引擎进行说明,尽管其他容器引擎也提供类似的功能或特性。
使用容器的好处
使用容器的一些优点如下所述:
-
持续部署和测试:通常,涉及不同环境(如开发和生产)的发布生命周期会因为不同的包版本或依赖关系而有所不同。Docker 通过确保环境的一致性来弥补这一差距,从开发到生产都保持内部配置和依赖关系。因此,您可以在开发和生产之间使用相同的容器,而不会出现任何差异或手动干预。
-
多云平台:Docker 的最大优点之一是它在各种环境和平台上的可移植性。所有主要的云服务提供商,如亚马逊网络服务(AWS)和谷歌计算平台(GCP),都通过增加个别支持(AWS ECS 或 Google GKE)来支持 Docker 的可用性。只要主机操作系统支持 Docker,Docker 容器就可以在虚拟机 VM 实例(如 Amazon EC2 或 Google Compute Engine)中运行。
-
版本控制:Docker 容器像
Git/SVN仓库一样充当版本控制系统,您可以将更改提交到 Docker 镜像并进行版本控制。 -
隔离与安全性:Docker 确保容器内运行的应用程序彼此完全隔离,提供对流量流动和管理的完全控制。没有任何 Docker 容器能够访问另一个容器内运行的进程。从架构角度看,每个容器都有自己的一套资源。
您可以将高级机器学习或深度学习应用程序与容器的部署能力相结合,使系统更高效、可共享。
部署深度模型的各种方法
机器学习既令人兴奋又有趣!不过它也有挑战,无论是在建模阶段还是在部署阶段,尤其是当您希望模型为真实用户和系统提供服务时。
将机器学习模型部署到生产环境中可以通过多种方式完成,生产化机器学习模型的不同方式实际上受到各种因素的影响:
-
您希望您的模型成为实时流分析的一部分,还是批量分析的一部分?
-
您是否希望有多个模型提供相同的功能,或者您需要对模型进行 A/B 测试?
-
您希望您的模型多久更新一次?
-
您如何根据流量扩展您的模型?
-
您如何与其他服务集成或将机器学习服务嵌入到管道中?
方法 1 - 离线建模和基于微服务的容器化部署
在这种方法中,您将在离线训练并评估模型后,使用预训练的模型构建一个 RESTful 服务,并将其部署在容器内。接下来,您可以根据成本、安全性、扩展性和基础设施需求,选择在数据中心或云端运行该容器。当您的机器学习或深度学习服务需要持续流量并根据请求激增动态扩展时,这种方法非常适合。
方法 2 - 离线建模与无服务器部署
在这种方法中,您将在离线训练模型,并将服务部署在无服务器环境中,如 AWS Lambda(您只需为调用 API 付费;无需为容器或虚拟机实例按小时/分钟支付费用)。当您的模型服务不会持续使用,而是会在某个时间后被调用时,这种方法非常适合。但即使有持续流量(取决于请求数量),与方法 1 相比,这种方法仍可能具有成本效益。
方法 3 - 在线学习
有时,您需要通过将机器学习服务与数据流管道集成来执行实时流式分析(例如将其放在具有 IOT 传感器数据的消息队列的消费端)。在实时流式场景中,数据可能会非常频繁地变化。在这种情况下,离线模型训练并不是最佳选择。相反,您需要让模型能够自动适应数据——即它将基于数据使用类似 SGD 或其小批量变体来更新权重/参数。
方法 4 - 使用托管的机器学习服务
当您没有资源或团队成员来内部构建机器学习模型时,这种方法非常合适。相反,您可以利用现有的云端托管机器学习或深度学习服务,如 Google Cloud ML、Azure ML、AWS Rekognition、AWS Polly、Google Cloud Vision 等,通过简单的 API 调用满足您的需求。
接下来,我们将通过实践示例来展示之前提到的部署方法。
在 Docker 上部署基于 Keras 的深度模型
在这个示例中,我们将构建一个图像识别系统,使用预训练的 Keras InceptionV3 模型,并将其部署在本地机器的容器中。有关预训练模型的更多信息,请参考第四章,从文本生成真实图像。我们的预训练 Keras 模型将在 Docker 容器内运行,并通过 Flask 以 REST API 形式暴露出来。

确保您有可用的 keras-microservice 项目,然后执行以下步骤以在 docker 容器中运行基于 Keras 的深度模型:
-
首先,检查 Dockerfile 是否在当前工作目录中,然后构建一个 Docker 镜像:
docker build -t keras-recognition-service . -
一旦 Docker 镜像成功构建,使用该镜像运行容器,命令是
docker run:docker run -it --rm -d -p <host port>:<container port> -v <host path>:<container path> keras-recognition-service例如:
docker run -it --rm -d -p 5000:80 -v /Users/kuntalg/knowledge:/deep/model keras-recognition-service注意
在
docker容器内,Keras 模型运行在名为Gunicorn的 WSGI HTTP Python 服务器的5001端口上,且由Nginx代理服务器在80端口进行负载均衡。我们之前使用–p属性将主机端口与容器端口映射。此外,我们使用了-v卷属性,将主机路径与容器路径映射,以便从该路径加载预训练模型。现在是通过执行
test.sh脚本来测试我们的图像识别服务的时候了。该脚本包含一个curl命令,用于调用并测试我们公开的图像识别服务的 REST API:#!/bin/bash echo "Prediction for 1st Image:" echo "--------------------------------" (echo -n '{"data": "'; base64 test-1.jpg; echo '"}') | curl -X POST -H "Content-Type: application/json" -d @- http://127.0.0.1:5000 echo "Prediction for 2nd Image:" echo "--------------------------------" (echo -n '{"data": "'; base64 test-1.jpg; echo '"}') | curl -X POST -H "Content-Type: application/json" -d @- http://127.0.0.1:5000 -
最后,执行脚本以从我们的 Keras 服务生成预测:
./test_requests.sh![在 Docker 上提供基于 Keras 的深度模型]()
Voilà!我们已经成功将第一个基于 Keras 的深度学习模型部署到容器内。
在云上使用 GKE 部署深度模型
一旦深度学习模型创建完成,部署在容器内并在本地生成预测结果,就可以使用 Docker 和 Kubernetes 将模型迁移到云端(例如,本示例中的 Google Cloud)。
执行以下步骤将本地创建的容器化模型带到云端:
-
注册 Google Cloud 免费试用账户(
cloud.google.com/free),然后通过输入相关的项目名称创建一个新项目:![在云上使用 GKE 部署深度模型]()
请记下包含项目名称及一些数字的项目 ID,格式为
<项目名称>-xxxxxx。稍后我们需要使用项目 ID将本地模型部署到云端。 -
在你的机器上安装 SDK(
cloud.google.com/sdk)。然后安装 kubectl 以管理 Kubernetes 集群:gcloud components install kubectlgcloud命令包含在 Google Cloud SDK 中。 -
使用
gcloud命令行工具的config命令设置一些环境变量:gcloud config set project <project ID> gcloud config set compute/zone <zone name such as us-central1-b> export PROJECT_ID="$(gcloud config get-value project -q) -
现在使用标签或版本(此示例中的
v1)构建 Docker 镜像:docker build -t gcr.io/<project ID>/keras-recognition-service:v1 .例如:
docker build -t gcr.io/deeplearning-123456/keras-recognition-service:v1 . -
接下来,使用
docker push命令将之前构建的镜像上传到 Google 容器注册表:gcloud docker -- push gcr.io/<project ID>/keras-recognition-service:v1例如:
gcloud docker -- push gcr.io/deeplearning-123456/keras-recognition-service:v1![在云上使用 GKE 部署深度模型]()
-
一旦容器镜像存储在注册表中,我们需要通过指定计算引擎虚拟机实例的数量来创建容器集群。该集群将由 Kubernetes 协调和管理。执行以下命令以创建一个名为
dl-cluster的两节点集群:gcloud container clusters create dl-cluster --num-nodes=2 -
我们将使用 Kubernetes 的
kubectl命令行工具在容器引擎集群上部署并运行应用,监听80端口:gcloud container clusters get-credentials dl-cluster kubectl run keras-recognition-service --image=gcr.io/deeplearning-123456/keras-recognition-service:v1 --port 80![在云上使用 GKE 部署深度模型]()
-
现在,将容器集群内运行的应用程序附加到负载均衡器,以便我们可以将图像识别服务暴露给真实世界的用户:
kubectl expose deployment keras-recognition-service --type=LoadBalancer --port 80 --target-port 80 -
接下来,运行以下
kubectl命令以获取我们服务的外部 IP:kubectl get service -
最后,执行以下命令,以从托管在云中容器集群中的图像识别服务获取预测:
(echo -n '{"data": "'; base64 test-1.jpeg; echo '"}') | curl -X POST -H "Content-Type: application/json" -d @- http://<External IP>:80![在云上部署深度模型与 GKE]()
使用 AWS Lambda 和 Polly 进行无服务器音频图像识别
在这个示例中,我们将构建一个基于音频的图像预测系统,使用 TensorFlow 预训练的 InceptionV3 模型,并将其部署在 AWS Lambda 的无服务器环境中。我们将在 AWS Lambda 上运行我们的图像预测代码,并从 S3 加载预训练的模型,然后通过 AWS API 网关将服务暴露给我们的真实客户。

执行以下步骤以在无服务器平台上构建基于音频的图像识别系统:
-
注册一个 AWS 免费试用账户(
aws.amazon.com/free/),并进入 IAM 服务创建一个新的角色用于 AWS Lambda。附加两个新的托管策略:S3FullAccess 和 PollyFullAccess,以及 lambda_basic_execution 的内联策略。![使用 AWS Lambda 和 Polly 的无服务器音频图像识别]()
-
接下来,创建一个 S3 桶,用于存储我们的 lambda 代码(包括自定义 Python 包,如
numpy、scipy、tensorflow等)。还需在 S3 桶内创建三个文件夹:-
code:我们将在这里存储 lambda 环境的代码 -
audio:我们的预测音频将保存在此位置 -
model:我们将把预训练的模型保存在此位置
![使用 AWS Lambda 和 Polly 的无服务器音频图像识别]()
-
-
下载预训练的 TensorFlow 模型(
download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz),解压缩后,将以下文件上传到S3桶中的model目录:classify_image_graph_def.pb imagenet_synset_to_human_label_map.txt imagenet_synset_to_human_label_map.txt![使用 AWS Lambda 和 Polly 的无服务器音频图像识别]()
-
lambda_tensorflow.zip包含一个classify.py文件,该文件将在lambda函数执行期间被调用。更改桶名称,并在classify.py中再次打包,然后将其上传到 S3 桶中的code目录:def lambda_handler(event, context): if not os.path.exists('/tmp/imagenetV3/'): os.makedirs('/tmp/imagenetV3/') # imagenet_synset_to_human_label_map.txt: # Map from synset ID to a human readable string. strBucket = 'kg-image-prediction' strKey = 'models/imagenetV3/imagenet_synset_to_human_label_map.txt' strFile = '/tmp/imagenetV3/imagenet_synset_to_human_label_map.txt' downloadFromS3(strBucket,strKey,strFile) print(strFile) # imagenet_2012_challenge_label_map_proto.pbtxt: # Text representation of a protocol buffer mapping a label to synset ID. strBucket = 'kg-image-prediction' strKey = 'models/imagenetV3/imagenet_2012_challenge_label_map_proto.pbtxt' strFile = '/tmp/imagenetV3/imagenet_2012_challenge_label_map_proto.pbtxt' downloadFromS3(strBucket,strKey,strFile) print(strFile) # classify_image_graph_def.pb: # Binary representation of the GraphDef protocol buffer. strBucket = 'kg-image-prediction' strKey = 'models/imagenetV3/classify_image_graph_def.pb' strFile = '/tmp/imagenetV3/classify_image_graph_def.pb' downloadFromS3(strBucket,strKey,strFile) print(strFile) data = base64.b64decode(event['base64Image']) imageName= event['imageName'] image=io.BytesIO(data) strBucket = 'kg-image-prediction' strKey = 'raw-image/tensorflow/'+imageName+'.png' uploadToS3(image, strBucket, strKey) print("Image file uploaded to S3") audioKey=imageName+'.mp3' print(audioKey) print("Ready to Run inference") strBucket = 'kg-image-prediction' strKey = 'raw-image/tensorflow/'+imageName+'.png' imageFile = '/tmp/'+imageName+'.png' downloadFromS3(strBucket,strKey,imageFile) print("Image downloaded from S3") strResult = run_inference_on_image(imageFile) # Invoke AWS Polly to generate Speech from text polly_client=boto3.client('polly') response = polly_client.synthesize_speech(Text =strResult,OutputFormat = "mp3",VoiceId = "Joanna") if "AudioStream" in response: output = os.path.join("/tmp", audioKey) with open(output, "wb") as file: file.write(response["AudioStream"].read()) #Upload speech to S3 print("Ready upload to S3 audio") strBucket = 'kg-image-prediction' strKey = 'audio/'+audioKey strFile = '/tmp/'+audioKey with open(strFile, 'rb') as data: uploadToS3(data,strBucket,strKey) # Clean up directory os.remove(imageFile) os.remove(strFile) return strResult![使用 AWS Lambda 和 Polly 的无服务器音频图像识别]()
-
现在,从 Web 控制台导航到 Lambda 服务,创建一个新的 Lambda 函数。从头开始创建时,提供 名称 和 描述,选择 运行时 为 Python 2.7,并将之前创建的
IAM角色附加到此 Lambda 函数中。![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
然后,在 Lambda 函数配置中指定代码文件(
lambda_tensorflow.zip)的位置:![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
还需在 高级设置 标签下增加 Lambda 函数的 内存(MB) 和 超时时间。第一次执行时,由于从 S3 加载预训练模型,Lambda 执行可能会花费一些时间。
![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
接下来,导航到 API 网关 服务,创建一个新的 API:
![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
然后,点击 API 左侧面板中的 二进制支持 标签,添加以下内容类型:
-
image/png
-
image/jpeg
![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
-
接下来,创建一个 新子资源,指定 资源路径(例如,
tensorflow-predict):![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
接下来,点击 操作 菜单中的 创建方法,为子资源添加一个 POST 方法。将我们之前创建的 Lambda 函数添加到此 API 资源的 AMP 中。你可能需要选择正确的区域,以从下拉菜单中找到你的 Lambda 函数。
-
一旦 POST 方法创建完成,点击 集成请求 并展开 请求体映射模板 标签。在 请求体直通 下,选择 当没有定义模板时(推荐)。然后,在 Content-Type 下添加
image/jpeg,并在 生成模板 部分添加如下内容:{ "base64Image": "$input.body", "imageName": "$input.params(imageName)" }![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
-
最后,从 操作 菜单部署 API,并定义 阶段名称(例如
prod或dev)。API 部署完成后,你将得到如下 API URL:https://<API ID>.execute-api.<region>.amazonaws.com/ -
接下来,从 REST 客户端(例如本示例中的 POSTMAN)访问你的 API,调用图像预测服务。在 API 请求 中,将 Content-Type 设置为 image/jpeg,并添加参数名 imageName,值为(例如
animal)。在请求体中添加一个图像作为binary文件,我们的服务将进行预测:https://<API ID>.execute-api.<region>.amazonaws.com/prod/tensorflow-predict?imageName=animal![使用 AWS Lambda 和 Polly 进行无服务器图像识别与音频处理]()
太好了!你将在 Postman 响应中看到以下来自无服务器服务的输出:
"该图像被识别为大熊猫、熊猫、熊猫熊、浣熊熊、熊猫(得分 = 0.89107)"

此外,预测响应的音频将被生成并存储在 S3 桶中的audio文件夹下。

修改代码和包以适应 Lambda 环境的步骤
如果需要为你的服务添加额外的 Python 包或更新任何现有包,请执行以下操作:
-
启动一个 EC2 实例,选择Amazon Linux AMI 2017.03.1 (HVM),SSD 卷类型:
![修改代码和包以适应 Lambda 环境的步骤]()
-
登录到 EC2 实例并将当前 Lambda 代码复制到实例中。然后,创建一个目录,并在该目录中解压 ZIP 文件:
mkdir lambda cd lambda unzip lambda_tensorflow.zip![修改代码和包以适应 Lambda 环境的步骤]()
-
若要更新任何现有包,首先删除它,然后使用
pip命令重新安装。若要添加新的包,请使用pip安装(如果该包依赖共享的.so库,则需要创建一个lib文件夹,并将这些文件从//usr/lib和/usr/lib64目录中复制到该文件夹中):rm -rf tensorflow* pip install tensorflow==1.2.0 -t /home/ec2-user/lambda -
然后创建整个目录的 ZIP 文件:
zip –r lambda_tensorflow.zip * -
最后,将 ZIP 文件复制到 S3,并通过提及 S3 上的新 ZIP 文件位置来更新 Lambda 函数。
注意
你可能需要去除一些包或不相关的目录,以确保code目录中解压后的文件总大小小于 250 MB;否则,Lambda 将无法部署你的代码。
前往以下链接,获取更多关于 Lambda 上自定义包部署的信息:docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html
使用云托管服务进行面部检测
在这个示例中,我们将使用基于深度学习的托管云服务来进行标签识别和面部检测系统的开发。我们将继续利用无服务器环境 AWS Lambda,并使用深度学习基础的云托管服务 AWS Rekognition 来进行面部属性识别。
执行以下步骤,使用云端托管深度学习服务在无服务器平台上构建面部检测系统:
-
首先,更新前述示例中的 IAM Lambda 执行角色,并附加一个新的托管策略AmazonRekognitionFullAccess,如下所示:
![运行云托管服务进行面部检测]()
-
接下来,创建一个新的 Lambda 函数,用于构建人脸检测服务。选择Runtime为Python 2.7,并保持所有其他设置为默认。将更新后的 IAM 角色附加到此 Lambda 函数:
![运行云托管服务进行人脸检测]()
-
然后,将以下代码粘贴到Lambda 函数代码区域。在代码中的
boto3.client部分,更新 S3桶名称和 AWS区域信息,如下所示:from __future__ import print_function import json import urllib import boto3 import base64 import io print('Loading function') s3 = boto3.client('s3') rekognition = boto3.client("rekognition", <aws-region name like us-west-1>) bucket=<Put your Bucket Name> key_path='raw-image/' def lambda_handler(event, context): output={} try: if event['operation']=='label-detect': print('Detecting label') fileName= event['fileName'] bucket_key=key_path + fileName data = base64.b64decode(event['base64Image']) image=io.BytesIO(data) s3.upload_fileobj(image, bucket, bucket_key) rekog_response = rekognition.detect_labels(Image={"S3Object": {"Bucket": bucket,"Name": bucket_key,}},MaxLabels=5,MinConfidence=90,) for label in rekog_response['Labels']: output[label['Name']]=label['Confidence'] else: print('Detecting faces') FEATURES_BLACKLIST = ("Landmarks", "Emotions", "Pose", "Quality", "BoundingBox", "Confidence") fileName= event['fileName'] bucket_key=key_path + fileName data = base64.b64decode(event['base64Image']) image=io.BytesIO(data) s3.upload_fileobj(image, bucket, bucket_key) face_response = rekognition.detect_faces(Image={"S3Object": {"Bucket": bucket, "Name": bucket_key, }}, Attributes=['ALL'],) for face in face_response['FaceDetails']: output['Face']=face['Confidence'] for emotion in face['Emotions']: output[emotion['Type']]=emotion['Confidence'] for feature, data in face.iteritems(): if feature not in FEATURES_BLACKLIST: output[feature]=data except Exception as e: print(e) raise e return output -
一旦你创建了 Lambda 函数,我们将为此服务创建一个 API 网关子资源,如下所示:
![运行云托管服务进行人脸检测]()
-
接下来,我们将向新子资源(predict)添加一个方法(此处为PUT),然后点击PUT方法的Integration Request。
![运行云托管服务进行人脸检测]()
-
现在,将先前创建的Lambda Function附加到此资源方法上。你需要选择创建 Lambda 函数的 AWSLambda Region,以便在下拉列表中获取 Lambda 函数名称:
![运行云托管服务进行人脸检测]()
-
接下来,展开Body Mapping Templates部分,并在Request body passthrough部分选择When there are no templates defined (recommended)。然后,在Content-Type中添加映射模板image/png,并将以下代码粘贴到General template区域:
{ "base64Image": "$input.body", "operation": "$input.params('activity')", "fileName": "$input.params('fileName')" }![运行云托管服务进行人脸检测]()
-
现在,通过点击Action菜单中的Deploy API,部署你的 API Gateway 资源 API。一旦资源部署完成,你将获得一个 API 网关,使用该网关可以调用人脸检测服务。我们将继续使用之前的 REST 客户端Postman(
chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en),但你也可以使用任何你喜欢的其他 REST 客户端。API 网关的 URL 将如下所示:https://<API ID>.execute-api.<AWS Region>.amazonaws.com/prod/predict -
添加Content-Type为image/png,并在请求中添加两个请求参数:activities 和 filenames。参数activity有两个取值(label-detect用于图像识别或标签检测)和(face-detect用于人脸检测)。fileName参数将用于将原始图像以该名称保存到 S3 中。
![运行云托管服务进行人脸检测]()
-
现在,调用你的服务来检测标签或人脸,并获得以下 JSON 格式的响应输出:
![运行云托管服务进行人脸检测]()
概要
到目前为止,你已经学习并实现了多种方式来部署训练好的深度模型,并为新的数据样本进行预测。你还学会了如何使用 Docker 容器将模型从本地计算机或数据中心顺利迁移到云端。希望通过本书中大量使用真实世界公共数据集的动手示例,你已经很好地理解了生成对抗网络(GANs)的概念及其变体架构(SSGAN、BEGAN、DCGAN、CycleGAN、StackGAN、DiscoGAN)。一旦你动手实验了本书中的代码和示例,我肯定会鼓励你做以下事情:
参与 Kaggle 对抗网络比赛: www.kaggle.com/c/nips-2017-defense-against-adversarial-attack。
通过参加或观看以下会议,保持你对深度学习和生成对抗网络(GANs)的知识更新:
-
神经信息处理系统 (NIPS):
nips.cc/ -
国际学习表征会议 (ICLR): HTTP://WWW.ICLR.CC/





















[i,j]表示在 VGG19 网络中第 i 个最大池化层前的第 j 个卷积层(激活后)获得的特征图。而Wi,j、Hi,j描述了VGG网络中各个特征图的维度:

所示,将返回图像的原始部分。





























浙公网安备 33010602011771号