dl-keras-merge-0

50 阅读1小时+

Keras 深度学习(一)

原文:annas-archive.org/md5/31e7ae0c4516a0dda7076f8a39dcfc99

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

深入学习与 Keras 实践是一本简明而全面的现代神经网络、人工智能和深度学习技术的入门书,专为软件工程师和数据科学家设计。

使命

本书介绍了 20 多个用 Python 编写的深度神经网络,这些网络使用 Keras——一个在 Google 的 TensorFlow 或 Lisa Lab 的 Theano 后端之上运行的模块化神经网络库。

读者将逐步接触到监督学习算法,如简单的线性回归、经典的多层感知机、更加复杂的深度卷积网络和生成对抗网络。此外,本书还涵盖了无监督学习算法,如自编码器和生成网络。书中还详细解释了递归网络和长短时记忆LSTM)网络。接下来,本书将介绍 Keras 的功能 API,并讲解如何在读者的使用场景未被 Keras 的广泛功能覆盖时进行定制。它还将探讨由先前提到的构建模块组成的更大、更复杂的系统。书的最后介绍了深度强化学习及其在构建游戏 AI 中的应用。

实际应用包括用于将新闻文章分类为预定义类别的代码、文本的句法分析、情感分析、文本的合成生成以及词性标注。还探讨了图像处理,包括手写数字图像的识别、图像分类及其相关的图像注释、以及高级物体识别。此外,还将提供面部检测的显著点识别示例。声音分析包括对多位演讲者的离散语音识别。强化学习用于构建一个深度 Q 学习网络,能够自主地玩游戏。

实验是本书的精髓。每个网络都通过多个变体进行增强,这些变体通过改变输入参数、网络形状、损失函数和用于优化的算法,逐步提高学习性能。本书还提供了在 CPU 和 GPU 上训练的多种比较。

深度学习与机器学习和人工智能有何不同

人工智能 (AI) 是一个非常广泛的研究领域,在这个领域中,机器展示了认知能力,如学习行为、与环境的主动互动、推理与演绎、计算机视觉、语音识别、问题解决、知识表示、感知等许多能力(更多信息,请参考本文:人工智能:一种现代方法,S. Russell 和 P. Norvig 著,Prentice Hall,2003 年)。更通俗地说,AI 指代任何机器模仿人类通常表现出的智能行为的活动。人工智能从计算机科学、数学和统计学等领域中汲取灵感。

机器学习 (ML) 是人工智能的一个子领域,专注于教计算机如何在不需要为特定任务编程的情况下学习(更多信息,请参见 模式识别与机器学习,C. M. Bishop 著,Springer,2006 年)。实际上,机器学习的关键思想是,能够创建从数据中学习并做出预测的算法。机器学习有三种不同的广泛分类。在监督学习中,机器被提供输入数据和期望的输出,目标是通过这些训练示例学习,使得能够对未见过的新数据做出有意义的预测。在无监督学习中,机器仅被提供输入数据,机器需要自行发现一些有意义的结构,且没有外部监督。在强化学习中,机器充当一个与环境互动的智能体,学习哪些行为会产生奖励。

深度学习 (DL) 是机器学习(ML)方法的一种特定子集,使用人工神经网络 (ANN),这些网络在某种程度上受到人脑神经元结构的启发(更多信息,请参考文章 为人工智能学习深度架构,Y. Bengio 著,Found. Trends,第 2 卷,2009 年)。非正式地说,深度一词指的是人工神经网络中存在许多层,但这一含义随着时间的推移有所变化。四年前,10 层已经足够让一个网络被视为深度网络,而如今,通常认为拥有数百层的网络才算是深度网络。

深度学习(DL)对机器学习来说简直是一场真正的海啸(更多信息请参见C.D. Manning 的《计算语言学与深度学习》,“计算语言学”,第 41 卷,2015 年),因为相对较少的聪明方法已经非常成功地应用到众多不同领域(图像、文本、视频、语音和视觉),显著提升了过去几十年间所取得的最先进成果。深度学习的成功还得益于更多训练数据的可用性(如图像领域的 ImageNet)和 GPU 低成本的可用性,使得数值计算更加高效。谷歌、微软、亚马逊、苹果、脸书等许多公司每天都在使用这些深度学习技术来分析海量数据。然而,这种专业技术已不再仅限于纯粹的学术研究领域和大型工业公司,它已成为现代软件生产的重要组成部分,因此读者应当掌握这些技术。本书不要求任何特定的数学背景,但假设读者已经是一个 Python 程序员。

本书涵盖的内容

第一章,神经网络基础,讲解了神经网络的基本知识。

第二章,Keras 安装与 API,展示了如何在 AWS、微软 Azure、谷歌云和你自己的机器上安装 Keras。此外,我们还提供了 Keras API 的概述。

第三章,使用卷积网络的深度学习,介绍了卷积网络的概念。卷积网络是深度学习中的一项基础创新,已成功应用于多个领域,从文本到视频再到语音,远远超出了最初用于图像处理的领域。

第四章,生成对抗网络与 WaveNet,介绍了生成对抗网络,用于生成类似人类生成的数据的合成数据。我们还将介绍 WaveNet,这是一种深度神经网络,用于高质量地再现人声和音乐。

第五章,词嵌入,讨论了词嵌入,这是一种深度学习方法,用于检测词与词之间的关系并将相似的词归为一类。

第六章,递归神经网络 – RNN,介绍了递归神经网络,这是一类专门优化用于处理序列数据(如文本)的网络。

第七章,其他深度学习模型,简要介绍了 Keras 函数式 API、回归网络、自编码器等内容。

第八章,AI 游戏对战,教你深度强化学习,并展示如何利用 Keras 构建可以通过奖励反馈学习玩街机游戏的深度学习网络。

附录,结论,是本书内容的简要回顾,并带领读者了解 Keras 2.0 的新功能。

本书所需的内容

为了能够顺利跟随章节,你需要以下软件:

  • TensorFlow 1.0.0 或更高版本

  • Keras 2.0.2 或更高版本

  • Matplotlib 1.5.3 或更高版本

  • Scikit-learn 0.18.1 或更高版本

  • NumPy 1.12.1 或更高版本

硬件规格如下:

  • 32 位或 64 位架构

  • 2+ GHz CPU

  • 4 GB RAM

  • 至少需要 10 GB 的可用硬盘空间

本书的目标读者

如果你是一个有机器学习经验的数据科学家,或者是一个接触过神经网络的 AI 程序员,你会发现这本书是深入了解 Keras 深度学习的有用入门书籍。此书要求具备 Python 知识。

约定

本书中有多种文本样式,用以区分不同类型的信息。以下是一些样式的示例及其含义解释。

文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账户名会以如下格式显示:“此外,我们将真实标签分别加载到 Y_trainY_test 中,并对其进行 one-hot 编码。”

代码块的格式如下:

from keras.models import Sequential
model = Sequential()
model.add(Dense(12, input_dim=8, kernel_initializer='random_uniform'))

当我们希望引起你对代码块中特定部分的注意时,相关的行或项会以粗体显示:

# 10 outputs
# final stage is softmax
model = Sequential()
model.add(Dense(NB_CLASSES, input_shape=(RESHAPED,)))
model.add(Activation('softmax'))
model.summary()

所有的命令行输入或输出都按如下方式书写:

pip install quiver_engine

新术语重要词汇以粗体显示。你在屏幕上看到的单词,例如菜单或对话框中的内容,会以这样的形式出现在文本中:“我们的简单网络的初始准确率为 92.22%,这意味着每 100 个手写字符中,大约有 8 个未能被正确识别。”

警告或重要提示会以这样的框框显示。

提示和技巧会以这样的形式出现。

读者反馈

我们欢迎读者的反馈。让我们知道你对本书的看法——你喜欢或不喜欢的部分。读者反馈对我们很重要,它帮助我们开发出更符合你需求的书籍。

若要向我们提供一般反馈,只需发送电子邮件至 feedback@packtpub.com,并在邮件主题中提及书籍标题。

如果你在某个领域有专长,并且有兴趣撰写或参与书籍的编写,请查看我们的作者指南:www.packtpub.com/authors

客户支持

现在你已经是 Packt 书籍的骄傲拥有者,我们为你提供了一些帮助,以确保你能够充分利用你的购买。

下载示例代码

您可以从www.packtpub.com上的账户下载本书的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,将文件直接发送到您的电子邮件。

您可以通过以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的官网。

  2. 将鼠标指针悬停在顶部的“SUPPORT”标签上。

  3. 点击“代码下载与勘误”。

  4. 在搜索框中输入书名。

  5. 选择您要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买此书的地方。

  7. 点击“代码下载”。

一旦文件下载完成,请确保使用最新版本的工具解压或提取文件夹:

  • Windows 的 WinRAR / 7-Zip

  • Mac 的 Zipeg / iZip / UnRarX

  • Linux 的 7-Zip / PeaZip

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Deep-Learning-with-Keras。我们还在github.com/PacktPublishing/提供了其他书籍和视频的代码包,欢迎查看!

下载本书的彩色图像

我们还为您提供了一份 PDF 文件,包含本书中使用的截图/图表的彩色图像。这些彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/DeepLearningwithKeras_ColorImages.pdf下载此文件。

勘误

尽管我们已尽全力确保内容的准确性,但错误仍然可能发生。如果您在我们的书籍中发现错误——可能是文本或代码中的错误——我们将非常感激您向我们报告。这样,您可以帮助其他读者避免困扰,并且帮助我们改进后续版本的书籍。如果您发现任何勘误,请访问www.packtpub.com/submit-errata报告,选择您的书籍,点击“勘误提交表单”链接,并输入勘误的详细信息。一旦您的勘误被验证,您的提交将被接受,勘误将上传到我们的网站,或添加到该书名的现有勘误列表中。

要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书名。所需的信息将在勘误部分下显示。

盗版

互联网上的版权侵权问题在所有媒体中都持续存在。在 Packt,我们非常重视版权和许可证的保护。如果你在互联网上发现任何形式的非法复制品,请立即提供该位置地址或网站名称,以便我们采取措施。

如发现涉嫌盗版的材料,请通过copyright@packtpub.com与我们联系,并提供相关链接。

我们感谢你在保护我们的作者及其作品的同时,帮助我们继续为你带来有价值的内容。

问题

如果你对本书的任何内容有问题,可以通过questions@packtpub.com与我们联系,我们将尽力解决问题。

第一章:神经网络基础

人工神经网络(简称神经网络)代表了一类机器学习模型,灵感来源于对哺乳动物中枢神经系统的研究。每个神经网络由多个互联的神经元组成,这些神经元按组织,当某些条件发生时,它们会交换信息(在术语中称为激活)。最初的研究始于 20 世纪 50 年代末,伴随感知机的引入(更多信息请参见文章:感知机:一种用于大脑信息存储和组织的概率模型,作者:F. 罗斯布拉特,心理学评论,卷 65,第 386 - 408 页,1958 年),这是一个用于简单操作的两层网络,随后在 20 世纪 60 年代末,通过引入反向传播算法,对多层网络的高效训练进行了扩展(参考文章:反向传播:它的作用与如何实现,作者:P. J. 韦尔博斯,IEEE 会议录,卷 78,第 1550 - 1560 页,1990 年,及深度信念网络的快速学习算法,作者:G. E. 亨顿、S. 奥辛德罗和 Y. W. 特,神经计算,卷 18,第 1527 - 1554 页,2006 年)。有些研究认为这些技术的起源比通常引用的时间更久远(更多信息请参见文章:神经网络中的深度学习:概述,作者:J. 施米德胡伯,卷 61,第 85 - 117 页,2015 年)。神经网络曾是 20 世纪 80 年代以前的学术研究重点,直到其他更简单的方法变得更为相关。然而,随着 2000 年代中期的到来,神经网络重新受到关注,这要归功于 G. 亨顿提出的突破性快速学习算法(更多信息请参见文章:反向传播的根源:从有序导数到神经网络和政治预测神经网络,作者:S. 莱文,卷 9,1996 年,及通过反向传播误差学习表示,作者:D. E. 鲁梅哈特、G. E. 亨顿和 R. J. 威廉姆斯,卷 323,1986 年),以及 2011 年左右引入的用于大规模数值计算的 GPU。

这些改进为现代深度学习开辟了道路,深度学习是一类神经网络,其特点是包含大量神经元层,能够基于逐级抽象的方式学习复杂的模型。几年前,人们称其为深度,通常只有 3 到 5 层,而现在这一层数已上升到 100 到 200 层。

这种通过渐进抽象进行的学习类似于人类大脑中经过数百万年进化的视觉模型。人类视觉系统的确被组织成不同的层级。我们的眼睛连接到大脑的一个区域,称为视觉皮层 V1,它位于大脑的后下部。这个区域对许多哺乳动物都是共有的,负责区分基本特性以及视觉方向、空间频率和颜色的微小变化。据估计,V1 包含约 1.4 亿个神经元,它们之间有 100 亿个连接。V1 随后与其他区域 V2、V3、V4、V5 和 V6 连接,进行更复杂的图像处理和对更复杂概念的识别,比如形状、面孔、动物等。这种分层组织是通过几亿年的多次尝试调优的结果。据估计,人类大脑皮层约有 160 亿个神经元,约 10%-25%的皮层区域专门负责视觉处理(更多信息请参见文章:The Human Brain in Numbers: A Linearly Scaled-up Primate Brain,S. Herculano-Houzel,第三卷,2009 年)。深度学习从人类视觉系统的这种分层组织中获得了一些灵感:早期的人工神经元层学习图像的基本属性,而较深层的神经元则学习更复杂的概念。

本书通过提供在 Keras 中编写的工作网络,涵盖了神经网络的几个主要方面。Keras 是一个简约高效的 Python 库,用于深度学习计算,支持运行在 Google 的 TensorFlow(更多信息请参见www.tensorflow.org/)或蒙特利尔大学的 Theano(更多信息请参见deeplearning.net/software/theano/)后台。因此,让我们开始吧。

在本章中,我们将讨论以下主题:

  • 感知器

  • 多层感知器

  • 激活函数

  • 梯度下降

  • 随机梯度下降

  • 反向传播

感知器

感知器是一种简单的算法,它给定一个输入向量 x,包含 m 个值(x[1]x[2],...,x[n]),通常称为输入特征或简写为特征,输出为 1(是)或 0(否)。从数学上讲,我们定义一个函数:

这里,w 是一个权重向量,wx 是点积 b 是偏置。如果你记得初等几何学,wx + b 定义了一个边界超平面,该超平面的位置会根据赋给 wb 的值变化。如果 x 位于直线以上,则答案为正,否则为负。这个算法非常简单!感知器无法表达 也许 的答案。它只能回答 1)或 0),前提是我们理解如何定义 wb,这就是训练过程,接下来我们将讨论。

Keras 代码的第一个示例

Keras 的初始构建模块是模型,而最简单的模型称为顺序模型。一个顺序的 Keras 模型是一个线性管道(堆叠)的神经网络层。以下代码片段定义了一个包含 12 个人工神经元的单层,并且它期望 8 个输入变量(也称为特征):

from keras.models import Sequential
model = Sequential()
model.add(Dense(12, input_dim=8, kernel_initializer='random_uniform'))

每个神经元可以使用特定的权重进行初始化。Keras 提供了几种选择,最常见的几种列举如下:

  • random_uniform:权重初始化为均匀随机的小值,范围在 (-0.05, 0.05) 之间。换句话说,给定区间内的任何值都有相同的概率被选中。

  • random_normal:权重根据高斯分布进行初始化,均值为零,标准差为 0.05。对于不熟悉高斯分布的朋友,可以将其想象为一个对称的钟形曲线

  • zero:所有权重初始化为零。

完整列表可以在 keras.io/initializations/ 找到。

多层感知器 —— 网络的第一个示例

在本章中,我们定义了一个具有多个线性层的网络的第一个示例。从历史上看,感知器是指具有单一线性层的模型,因此如果它具有多个层,你可以称之为多层感知器MLP)。下图表示一个典型的神经网络,包含一个输入层、一个中间层和一个输出层。

在前面的示意图中,第一层中的每个节点接收输入,并根据预定义的局部决策边界进行激活。然后,第一层的输出传递到第二层,第二层的结果传递到最终的输出层,输出层由一个单一的神经元组成。有趣的是,这种分层组织与我们早前讨论的人类视觉模式有些相似。

net 是稠密的,意味着每个层中的神经元都与前一层中的所有神经元及后一层中的所有神经元相连接。

训练感知器中的问题及其解决方案

让我们考虑一个单一的神经元;对于权重 w 和偏置 b,最佳选择是什么?理想情况下,我们希望提供一组训练示例,让计算机调整权重和偏置,使输出中产生的误差最小化。为了让这个问题更加具体,假设我们有一组猫的图片和另一组不包含猫的图片。为了简化,假设每个神经元只查看一个输入像素值。在计算机处理这些图片时,我们希望神经元调整它的权重和偏置,使得错误识别为非猫的图片越来越少。这种方法看起来非常直观,但它要求权重(和/或偏置)的小变化只会导致输出的小变化。

如果我们的输出跳跃过大,就无法逐步学习(而不是像进行穷尽搜索那样尝试所有可能的方向——这是一个在不确定是否改进的情况下进行的过程)。毕竟,孩子们是逐步学习的。不幸的是,感知器没有表现出这种逐步行为。感知器的输出要么是0,要么是1,这是一种很大的跳跃,这对学习没有帮助,正如下面的图所示:

我们需要一些不同的、更平滑的东西。我们需要一个从 0 逐渐变化到 1 的函数,且没有间断。数学上,这意味着我们需要一个连续的函数,使我们能够计算其导数。

激活函数 — sigmoid

Sigmoid 函数定义如下:

如下图所示,当输入在  中变化时,输出在 (0, 1) 范围内变化较小。数学上,这个函数是连续的。一个典型的 sigmoid 函数在下图中表示:

神经元可以使用 sigmoid 来计算非线性函数 。注意,如果  非常大且为正,那么 ,因此 ,而如果  非常大且为负,,则 。换句话说,具有 sigmoid 激活的神经元表现得类似于感知器,但其变化是渐进的,输出值,如 0.55390.123191,都是完全合法的。从这个意义上说,sigmoid 神经元可以回答也许

激活函数 — ReLU

Sigmoid 并不是唯一用于神经网络的平滑激活函数。最近,一种叫做修正线性单元ReLU)的简单函数变得非常流行,因为它能够产生非常好的实验结果。ReLU 函数简单地定义为 ,其非线性函数在下图中表示。正如你在下图中看到的,负值时该函数为零,正值时则呈线性增长:

激活函数

Sigmoid 和 ReLU 通常被称为神经网络术语中的激活函数。在Keras 中测试不同优化器一节中,我们将看到这些逐步变化,典型的 Sigmoid 和 ReLU 函数,是发展学习算法的基本构建块,这些算法逐渐减少网络所犯的错误,一点点地适应。以下是使用激活函数 σ 与输入向量 (x[1], x[2], ..., x[m])、权重向量 (w[1], w[2], ..., w[m])、偏置 b 和求和 Σ 的示例:

Keras 支持多种激活函数,完整列表请参见 keras.io/activations/

实际示例 — 识别手写数字

在这一部分,我们将构建一个可以识别手写数字的网络。为了实现这个目标,我们使用 MNIST(有关更多信息,请参见yann.lecun.com/exdb/mnist/),这是一个由 60,000 个训练样本和 10,000 个测试样本组成的手写数字数据库。训练样本由人类标注,包含正确答案。例如,如果手写数字是数字 3,那么 3 就是与该样本相关联的标签。

在机器学习中,当有正确答案的数据集时,我们称之为可以进行一种有监督学习。在这种情况下,我们可以使用训练样本来调整我们的网络。测试样本也会与每个数字相关联正确的答案。然而,在这种情况下,想法是装作标签是未知的,让网络进行预测,然后稍后重新考虑标签,以评估我们的神经网络在识别数字方面的学习效果。因此,毫不奇怪,测试样本仅用于测试我们的网络。

每个 MNIST 图像是灰度图,并由 28 x 28 个像素组成。这些数字的一部分在下面的图示中表示:

一热编码 — OHE

在许多应用中,将分类(非数值)特征转换为数值变量是很方便的。例如,具有值d的分类特征数字* [0-9] 可以编码为一个具有10个位置的二进制向量,该向量始终在除d位置外的所有位置上为0*,在d位置上为1。这种表示方式称为一热编码OHE),在数据挖掘中非常常见,尤其是当学习算法专门处理数值函数时。

在 Keras 中定义一个简单的神经网络

在这里,我们使用 Keras 定义一个识别 MNIST 手写数字的网络。我们从一个非常简单的神经网络开始,然后逐步改进它。

Keras 提供了适合的库来加载数据集,并将其划分为训练集X_train,用于对网络进行微调,以及测试集* X_test,* 用于评估性能。数据被转换为float32以支持 GPU 计算,并归一化为* [0, 1] *。此外,我们将真实标签分别加载到Y_trainY_test中,并对它们进行一热编码。我们来看一下代码:

from __future__ import print_function
import numpy as np
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.optimizers import SGD
from keras.utils import np_utils
np.random.seed(1671) # for reproducibility

# network and training
NB_EPOCH = 200
BATCH_SIZE = 128
VERBOSE = 1
NB_CLASSES = 10 # number of outputs = number of digits
OPTIMIZER = SGD() # SGD optimizer, explained later in this chapter
N_HIDDEN = 128
VALIDATION_SPLIT=0.2 # how much TRAIN is reserved for VALIDATION

# data: shuffled and split between train and test sets
#
(X_train, y_train), (X_test, y_test) = mnist.load_data()
#X_train is 60000 rows of 28x28 values --> reshaped in 60000 x 784
RESHAPED = 784
#
X_train = X_train.reshape(60000, RESHAPED)
X_test = X_test.reshape(10000, RESHAPED)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
# normalize
#
X_train /= 255
X_test /= 255
print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')
# convert class vectors to binary class matrices
Y_train = np_utils.to_categorical(y_train, NB_CLASSES)
Y_test = np_utils.to_categorical(y_test, NB_CLASSES)

输入层有一个与图像中每个像素关联的神经元,总共有28 x 28 = 784个神经元,每个像素对应 MNIST 图像中的一个像素。

通常,每个像素的值会在范围* [0, 1] *内进行归一化(这意味着每个像素的强度会除以 255,255 是最大强度值)。输出是 10 个类别,每个类别对应一个数字。

最后一层是一个具有 softmax 激活函数的单神经元,softmax 是 sigmoid 函数的推广。Softmax 压缩 一个具有 k 个维度的任意实数向量,将其映射到范围 (0, 1) 的 k 维实数向量。在我们的例子中,它将前一层提供的 10 个答案与 10 个神经元的结果进行聚合:

# 10 outputs
# final stage is softmax
model = Sequential()
model.add(Dense(NB_CLASSES, input_shape=(RESHAPED,)))
model.add(Activation('softmax'))
model.summary()

一旦我们定义了模型,就必须对其进行编译,以便它可以被 Keras 后端(无论是 Theano 还是 TensorFlow)执行。在编译过程中需要做出一些选择:

  • 我们需要选择用于更新权重的 优化器,它是训练模型时使用的特定算法。

  • 我们需要选择用于优化器的 目标函数,该目标函数用于引导权重空间(通常,目标函数也被称为 损失函数,优化过程被定义为损失的 最小化 过程)。

  • 我们需要评估训练好的模型

一些常见的目标函数选择(Keras 目标函数的完整列表可以参考 keras.io/objectives/)如下:

  • 均方误差(MSE):这是预测值和真实值之间的均方误差。从数学上讲,如果 是包含 n 个预测值的向量,而 Y 是包含 n 个观察值的向量,那么它们满足以下方程:

这些目标函数对每个预测所犯的错误进行平均,如果预测值与真实值之间的距离较远,那么通过平方操作,这个距离会更加显著。

  • 二分类交叉熵:这是二元对数损失。假设我们的模型预测值为 p,而目标为 t,那么二分类交叉熵定义如下:

这个目标函数适用于二分类标签预测。

  • 类别交叉熵:这是多类的对数损失。如果目标是 t[i,j],预测是 p[i,j],那么类别交叉熵为:

这个目标函数适用于多类标签预测。在与 softmax 激活函数一起使用时,它也是默认选择。

一些常见的度量标准选择(Keras 度量标准的完整列表可以参考 keras.io/metrics/)如下:

  • 准确率:这是预测正确的比例,与目标相比。

  • 精确度:表示在多标签分类中,有多少选定项是相关的。

  • 召回率:表示在多标签分类中,有多少选定项是相关的。

度量标准与目标函数类似,唯一的区别是它们不是用于训练模型,而仅用于评估模型。在 Keras 中编译模型是非常简单的:

model.compile(loss='categorical_crossentropy', optimizer=OPTIMIZER, metrics=['accuracy'])

一旦模型被编译,就可以使用 fit() 函数进行训练,该函数指定了一些参数:

  • epochs:这是模型暴露于训练集的次数。在每次迭代中,优化器会尝试调整权重,以最小化目标函数。

  • batch_size:这是优化器执行权重更新之前,观察到的训练实例的数量。

在 Keras 中训练模型非常简单。假设我们想要迭代 NB_EPOCH 步:

history = model.fit(X_train, Y_train,
batch_size=BATCH_SIZE, epochs=NB_EPOCH,
verbose=VERBOSE, validation_split=VALIDATION_SPLIT)

我们将部分训练集保留用于验证。关键思想是,我们保留部分训练数据用于在训练过程中评估验证集的表现。这是进行任何机器学习任务时的良好实践,我们将在所有示例中采纳这一做法。

一旦模型训练完成,我们可以在包含新未见示例的测试集上进行评估。通过这种方式,我们可以获得目标函数的最小值和评估指标的最佳值。

请注意,训练集和测试集当然是严格分开的。在已经用于训练的示例上评估模型是没有意义的。学习本质上是一个旨在概括未见观察结果的过程,而不是去记忆已知的内容:

score = model.evaluate(X_test, Y_test, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

恭喜你,你刚刚在 Keras 中定义了你的第一个神经网络。只需几行代码,你的计算机就能够识别手写数字。让我们运行代码并看看性能如何。

运行一个简单的 Keras 网络并建立基准

那么,让我们来看一下当我们运行下面截图中的代码时会发生什么:

首先,网络架构被转储,我们可以看到使用的不同类型的层、它们的输出形状、需要优化的参数数量以及它们是如何连接的。然后,网络在 48,000 个样本上进行训练,12,000 个样本保留用于验证。一旦神经网络模型构建完成,它就会在 10,000 个样本上进行测试。正如你所看到的,Keras 内部使用 TensorFlow 作为计算的后端系统。现在,我们不会深入探讨训练是如何进行的,但我们可以注意到,程序运行了 200 次迭代,每次迭代时,准确率都有所提高。当训练结束后,我们在测试集上测试模型,得到了约 92.36% 的训练准确率、92.27% 的验证准确率和 92.22% 的测试准确率。

这意味着大约每十个手写字符中就有一个没有被正确识别。我们当然可以做得更好。在下方的截图中,我们可以看到测试集上的准确率:

使用隐藏层改进 Keras 中的简单网络

我们在训练集上的基准准确率为 92.36%,在验证集上的准确率为 92.27%,在测试集上的准确率为 92.22%。这是一个很好的起点,但我们肯定可以做得更好。让我们看看如何改进。

第一个改进是向网络中添加额外的层。因此,在输入层之后,我们有一个第一个密集层,包含N_HIDDEN个神经元,并使用激活函数relu。这个附加层被视为隐藏层,因为它与输入和输出都没有直接连接。在第一个隐藏层之后,我们有第二个隐藏层,仍然包含N_HIDDEN个神经元,之后是一个输出层,包含 10 个神经元,每个神经元将在相应的数字被识别时激活。以下代码定义了这个新网络:

from __future__ import print_function
import numpy as np
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.optimizers import SGD
from keras.utils import np_utils
np.random.seed(1671) # for reproducibility
# network and training
NB_EPOCH = 20
BATCH_SIZE = 128
VERBOSE = 1
NB_CLASSES = 10 # number of outputs = number of digits
OPTIMIZER = SGD() # optimizer, explained later in this chapter
N_HIDDEN = 128
VALIDATION_SPLIT=0.2 # how much TRAIN is reserved for VALIDATION
# data: shuffled and split between train and test sets
(X_train, y_train), (X_test, y_test) = mnist.load_data()
#X_train is 60000 rows of 28x28 values --> reshaped in 60000 x 784
RESHAPED = 784
#
X_train = X_train.reshape(60000, RESHAPED)
X_test = X_test.reshape(10000, RESHAPED)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
# normalize
X_train /= 255
X_test /= 255
print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')
# convert class vectors to binary class matrices
Y_train = np_utils.to_categorical(y_train, NB_CLASSES)
Y_test = np_utils.to_categorical(y_test, NB_CLASSES)
# M_HIDDEN hidden layers
# 10 outputs
# final stage is softmax
model = Sequential()
model.add(Dense(N_HIDDEN, input_shape=(RESHAPED,)))
model.add(Activation('relu'))
model.add(Dense(N_HIDDEN))
model.add(Activation('relu'))
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()
model.compile(loss='categorical_crossentropy',
optimizer=OPTIMIZER,
metrics=['accuracy'])
history = model.fit(X_train, Y_train,
batch_size=BATCH_SIZE, epochs=NB_EPOCH,
verbose=VERBOSE, validation_split=VALIDATION_SPLIT)
score = model.evaluate(X_test, Y_test, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

让我们运行代码,看看这个多层网络的结果如何。还不错。通过添加两个隐藏层,我们在训练集上达到了 94.50%,在验证集上 94.63%,在测试集上 94.41%。这意味着,相比于之前的网络,我们在测试集上的准确率提高了 2.2%。然而,我们大幅减少了从 200 次迭代到 20 次迭代的训练周期。这是好事,但我们还想要更多。

如果你愿意,你可以自己试试,看看如果只添加一个隐藏层而不是两个,或者如果添加两个以上的层会发生什么。我将这个实验留给你做。以下截图显示了前面示例的输出:

在 Keras 中通过 dropout 进一步改进简单的网络

现在我们的基准准确率为:训练集 94.50%,验证集 94.63%,测试集 94.41%。第二个改进非常简单。我们决定通过 dropout 概率随机丢弃一些在内部密集隐藏层网络中传播的值。在机器学习中,这是一种众所周知的正则化方法。令人惊讶的是,随机丢弃一些值的想法竟然能提升我们的表现:

from __future__ import print_function
import numpy as np
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.optimizers import SGD
from keras.utils import np_utils
np.random.seed(1671) # for reproducibility
# network and training
NB_EPOCH = 250
BATCH_SIZE = 128
VERBOSE = 1
NB_CLASSES = 10 # number of outputs = number of digits
OPTIMIZER = SGD() # optimizer, explained later in this chapter
N_HIDDEN = 128
VALIDATION_SPLIT=0.2 # how much TRAIN is reserved for VALIDATION
DROPOUT = 0.3
# data: shuffled and split between train and test sets
(X_train, y_train), (X_test, y_test) = mnist.load_data()
#X_train is 60000 rows of 28x28 values --> reshaped in 60000 x 784
RESHAPED = 784
#
X_train = X_train.reshape(60000, RESHAPED)
X_test = X_test.reshape(10000, RESHAPED)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
# normalize
X_train /= 255
X_test /= 255
# convert class vectors to binary class matrices
Y_train = np_utils.to_categorical(y_train, NB_CLASSES)
Y_test = np_utils.to_categorical(y_test, NB_CLASSES)
# M_HIDDEN hidden layers 10 outputs
model = Sequential()
model.add(Dense(N_HIDDEN, input_shape=(RESHAPED,)))
model.add(Activation('relu'))
model.add(Dropout(DROPOUT))
model.add(Dense(N_HIDDEN))
model.add(Activation('relu'))
model.add(Dropout(DROPOUT))
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()
model.compile(loss='categorical_crossentropy',
optimizer=OPTIMIZER,
metrics=['accuracy'])
history = model.fit(X_train, Y_train,
batch_size=BATCH_SIZE, epochs=NB_EPOCH,
verbose=VERBOSE, validation_split=VALIDATION_SPLIT)
score = model.evaluate(X_test, Y_test, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

让我们像之前那样运行 20 次迭代,看看这个网络在训练集上达到 91.54%,在验证集上 94.48%,在测试集上 94.25%的准确率:

请注意,训练集的准确率仍然应该高于测试集准确率,否则我们就没有训练足够长的时间。因此,让我们尝试显著增加 epoch 数量,直到 250 次,我们将获得 98.1%的训练准确率,97.73%的验证准确率,和 97.7%的测试准确率:

观察随着 epoch 数量增加,训练集和测试集上的准确率是非常有用的。正如你在下面的图表中看到的,这两条曲线大约在 250 个 epoch 时交汇,因此,在这一点之后就无需继续训练:

注意,常常观察到,在内部隐藏层进行随机丢弃(dropout)的网络在测试集中的未知样本上表现更好。直观地看,可以将其理解为每个神经元变得更强大,因为它知道不能依赖于邻近的神经元。在测试时,不会有丢弃,所以此时我们使用的是所有已调优的神经元。简而言之,采用某种丢弃函数进行测试通常是检验网络性能的一个好方法。

在 Keras 中测试不同的优化器

我们已经定义并使用了一个网络;接下来可以开始介绍网络训练的直观理解。我们先聚焦于一种流行的训练技术——梯度下降GD)。假设一个通用的代价函数 C(w),它是一个关于单一变量 w 的函数,如下图所示:

梯度下降可以看作是一个登山者,目标是从山顶走到山谷。山代表代价函数 C,而山谷代表最小值 C[min]。登山者从起点 w[0] 开始,逐步向前移动。在每一步 r 中,梯度指向最大增加的方向。从数学上讲,这个方向是偏导数的值 ,其在步数 r 达到的点 w[r] 处被评估。因此,通过朝着相反方向移动 ,登山者可以朝着山谷前进。在每一步中,登山者可以决定步长。这就是梯度下降中的 学习率 。注意,如果 太小,登山者会移动得很慢。然而,如果 太大,登山者则可能会错过山谷。

现在你应该记得,sigmoid 是一个连续函数,并且可以计算其导数。可以证明,sigmoid 如下所示:

它的导数为:

ReLU 在 0 处不可导。然而,我们可以通过选择将 01 作为 0 处的导数,从而将其扩展为一个定义在整个领域上的函数。ReLU 的逐点导数 如下所示:

一旦我们得到了导数,就可以使用梯度下降技术来优化网络。Keras 使用其后台(无论是 TensorFlow 还是 Theano)来为我们计算导数,所以我们无需担心实现或计算它。我们只需选择激活函数,Keras 就会为我们计算其导数。

神经网络本质上是多个函数的组合,包含成千上万,有时甚至数百万个参数。每一层网络都会计算一个函数,其误差应当被最小化,以提高在学习阶段观察到的准确率。当我们讨论反向传播时,我们会发现最小化的过程比我们的小示例要复杂一些。然而,它仍然基于通过下降谷底的直观理解。

Keras 实现了一个快速的梯度下降变种,称为随机梯度下降SGD),以及另外两种更先进的优化技术,分别是RMSpropAdam。RMSprop 和 Adam 除了包含 SGD 的加速成分外,还引入了动量(一个速度分量)。这使得它们在加速收敛的同时,也需要更多的计算。Keras 支持的优化器完整列表可以查看keras.io/optimizers/。到目前为止,SGD 是我们的默认选择。那么现在我们来试试另外两种优化器。非常简单,我们只需要修改几行代码:

from keras.optimizers import RMSprop, Adam
...
OPTIMIZER = RMSprop() # optimizer,

就这样。让我们按以下截图所示进行测试:

正如你在之前的截图中看到的,RMSprop 比 SDG 更快,因为我们能够在训练集上获得 97.97%的准确率,在验证集上为 97.59%,在测试集上为 97.84%,仅用 20 次迭代就超越了 SDG。为了完整起见,让我们看看随着周期数的增加,准确率和损失的变化,如以下图表所示:

好的,让我们试试另一个优化器,Adam()。它非常简单,如下所示:

OPTIMIZER = Adam() # optimizer

正如我们所看到的,Adam 稍微好一点。使用 Adam 时,在 20 次迭代后,我们在训练集上的准确率为 98.28%,验证集为 98.03%,测试集为 97.93%,如以下图表所示:

这是我们的第五个变种,请记住,我们的初始基准是 92.36%。

到目前为止,我们取得了逐步的进展;然而,现在的提升变得越来越困难。请注意,我们正在以 30%的丢弃率进行优化。为了完整起见,报告不同丢弃率下的测试集准确率可能会很有帮助,且选择Adam()作为优化器,如以下图所示:

增加训练的周期数

让我们再试一次,将训练周期数从 20 增加到 200。不幸的是,这样的选择让我们的计算时间增加了 10 倍,但并没有带来任何提升。实验失败了,但我们学到了一个重要的经验:如果我们花更多时间进行学习,并不一定会有所改善。学习更多的是采用聪明的技巧,而不一定是花费的计算时间。让我们在以下图表中跟踪我们第六个变种的表现:

控制优化器学习率

还有一个尝试是改变优化器的学习参数。正如下图所示,最佳值接近0.001,这是优化器的默认学习率。很好!Adam 优化器开箱即用效果不错:

增加内部隐藏神经元的数量

我们还可以尝试另一种方法,即改变内部隐藏神经元的数量。我们报告了随着隐藏神经元数量增加而进行的实验结果。从下图中可以看到,随着模型复杂度的增加,运行时间显著增加,因为需要优化的参数越来越多。然而,随着网络的扩展,通过增加网络规模获得的收益越来越小:

在下图中,我们展示了随着隐藏神经元数量的增加,每次迭代所需的时间:

以下图表展示了随着隐藏神经元数量的增加,准确率的变化:

增加批处理计算的大小

梯度下降法试图最小化训练集提供的所有示例上的成本函数,同时也针对输入中提供的所有特征。随机梯度下降是一个更便宜的变种,它只考虑BATCH_SIZE个示例。因此,让我们通过改变这个参数来观察它的行为。正如你所看到的,最佳准确率出现在BATCH_SIZE=128时:

总结识别手写图表的实验

所以,总结一下:通过五种不同的变体,我们将性能从 92.36%提高到了 97.93%。首先,我们在 Keras 中定义了一个简单的网络层。然后,我们通过添加一些隐藏层来提高性能。接着,我们通过为网络添加随机丢弃层并实验不同类型的优化器来提高测试集上的性能。当前的结果总结在以下表格中:

模型/准确率训练验证测试
简单模型92.36%92.37%92.22%
两个隐藏层(128)94.50%94.63%94.41%
Dropout(30%)98.10%97.73%97.7%(200 次迭代)
RMSprop97.97%97.59%97.84%(20 次迭代)
Adam98.28%98.03%97.93%(20 次迭代)

然而,接下来的两个实验并没有提供显著的改进。增加内部神经元的数量会创建更复杂的模型,并需要更昂贵的计算,但它仅提供了边际性的提升。如果我们增加训练轮次,也会得到相同的结果。最后一个实验是改变优化器的BATCH_SIZE

采用正则化来避免过拟合

直观上,一个好的机器学习模型应该在训练数据上达到较低的误差。从数学上讲,这等同于最小化给定训练数据上的损失函数,这是由以下公式表示的:

然而,这可能还不足以解决问题。模型可能会变得过于复杂,以捕捉训练数据中固有的所有关系。复杂度的增加可能会带来两个负面后果。首先,复杂模型可能需要大量的时间来执行。其次,复杂模型可能在训练数据上表现非常好——因为它记住了训练数据中固有的所有关系,但在验证数据上表现不佳——因为模型无法在新的未见过的数据上进行泛化。再强调一次,学习更多的是关于泛化,而不是记忆。下图表示了一个典型的损失函数,在验证集和训练集上都逐渐下降。然而,在某一点,验证集上的损失开始增加,这是由于过拟合造成的:

一般来说,如果在训练过程中,我们发现损失在初期下降后,在验证集上反而开始增加,那么我们就遇到了过拟合问题,即模型复杂度过高导致过拟合训练数据。事实上,过拟合是机器学习中用来简洁描述这一现象的术语。

为了解决过拟合问题,我们需要一种方法来捕捉模型的复杂性,也就是说,模型的复杂度有多大。解决方案是什么呢?其实,模型不过是一个权重向量。因此,模型的复杂度可以方便地通过非零权重的数量来表示。换句话说,如果我们有两个模型,M1M2,它们在损失函数上的表现几乎相同,那么我们应该选择权重非零数量最少的最简单模型。我们可以使用一个超参数 ⅄>=0 来控制拥有简单模型的重要性,如下公式所示:

机器学习中使用的正则化方法有三种不同类型:

  • L1 正则化(也称为套索回归):模型的复杂度表现为权重绝对值的总和

  • L2 正则化(也称为岭回归):模型的复杂度表现为权重平方和的总和

  • 弹性网正则化:模型的复杂度通过前两种方法的组合来捕捉

请注意,正则化的相同理念可以独立应用于权重、模型和激活函数。

因此,调整正则化可以是提升网络性能的好方法,特别是在出现明显过拟合的情况下。这一组实验留给有兴趣的读者自行完成。

注意,Keras 支持 l1、l2 和弹性网络正则化。添加正则化非常简单;例如,在这里,我们为核(权重W)添加了一个l2正则化器:

from keras import regularizers model.add(Dense(64, input_dim=64, kernel_regularizer=regularizers.l2(0.01)))

可用参数的完整描述可见于:keras.io/regularizers/

超参数调优

前述实验让我们了解了调整网络的机会。然而,适用于此示例的内容不一定适用于其他示例。对于给定的网络,确实存在多个可以优化的参数(例如hidden neurons的数量、BATCH_SIZEepochs的数量,以及根据网络复杂性调整的更多参数)。

超参数调优是寻找那些最小化成本函数的最佳参数组合的过程。关键思想是,如果我们有n个参数,那么我们可以想象这些参数定义了一个n维的空间,目标是找到该空间中对应于成本函数最优值的点。一种实现此目标的方法是,在该空间中创建一个网格,并系统地检查每个网格顶点对应的成本函数值。换句话说,参数被分成不同的桶,然后通过暴力搜索方法检查不同的值组合。

预测输出

当网络被训练完成后,它自然可以用于预测。在 Keras 中,这非常简单。我们可以使用以下方法:

# calculate predictions
predictions = model.predict(X)

对于给定的输入,可以计算出多种类型的输出,包括一种方法:

  • model.evaluate():用于计算损失值

  • model.predict_classes():用于计算类别输出

  • model.predict_proba():用于计算类别概率

反向传播的实用概述

多层感知机通过一种叫做反向传播的过程从训练数据中学习。这个过程可以描述为一种在错误被检测到时逐步进行修正的方式。让我们来看看这是如何运作的。

记住,每个神经网络层都有一组关联的权重,用于确定给定输入集的输出值。此外,记住一个神经网络可以有多个隐藏层。

一开始,所有的权重都被随机分配。然后,网络会对训练集中的每个输入进行激活:值从输入阶段通过隐藏层传播向前,最终到达输出阶段进行预测(注意,我们通过用绿色虚线表示一些值来简化了下图,但实际上所有的值都会通过网络向前传播):

由于我们知道训练集中真实的观测值,因此可以计算预测中所犯的错误。反向传播的关键直觉是将误差反向传播,并使用适当的优化算法,如梯度下降,来调整神经网络权重,以减少误差(为了简单起见,这里仅表示一些误差值):

从输入到输出的前向传播过程以及误差的反向传播会重复多次,直到误差降到预定的阈值以下。整个过程在以下图示中表示:

特征表示输入,标签在这里用于驱动学习过程。模型以这样一种方式更新,使得损失函数逐步最小化。在神经网络中,真正重要的不是单个神经元的输出,而是每一层中调整的集体权重。因此,网络逐渐调整其内部权重,以便增加正确预测的标签数量。当然,使用正确的特征集和拥有高质量的标签数据对于最小化学习过程中的偏差至关重要。

朝着深度学习方法迈进

在进行手写数字识别时,我们得出结论:当我们接近 99%的准确率时,进一步提升变得越来越困难。如果我们想要更多的改进,显然需要一个新思路。我们缺少了什么呢?思考一下。

基本的直觉是,到目前为止,我们丢失了与图像的局部空间性相关的所有信息。特别是,这段代码将位图(表示每个手写数字)转换为一个平坦的向量,其中空间局部性已经丢失:

#X_train is 60000 rows of 28x28 values --> reshaped in 60000 x 784
X_train = X_train.reshape(60000, 784)
X_test = X_test.reshape(10000, 784)

然而,这并不是我们大脑的工作方式。记住,我们的视觉是基于多个皮层层次的,每个层次识别越来越多的结构化信息,同时仍然保留局部性。首先,我们看到单个像素,然后从中识别简单的几何形状,再然后识别更多复杂的元素,如物体、面孔、人类身体、动物等等。

在第三章,使用卷积神经网络进行深度学习,我们将看到一种特定类型的深度学习网络,称为卷积神经网络CNN),它通过考虑保留图像中的空间局部性(更一般地说,任何类型的信息)以及通过逐级抽象学习的思想发展而来:通过一层,你只能学习简单的模式;而通过多层,你可以学习多种模式。在讨论 CNN 之前,我们需要讨论 Keras 架构的一些方面,并对一些额外的机器学习概念做一个实际的介绍。接下来的章节将讨论这些内容。

总结

在本章中,您学习了神经网络的基础知识,更具体地说,了解了感知机是什么、多层感知机是什么、如何在 Keras 中定义神经网络、如何在建立良好的基线后逐步改进指标,以及如何微调超参数空间。此外,您现在还对一些有用的激活函数(sigmoid 和 ReLU)有了直观的理解,并了解了如何通过基于梯度下降、随机梯度下降或更复杂方法(如 Adam 和 RMSprop)的反向传播算法训练网络。

在下一章中,我们将学习如何在 AWS、微软 Azure、谷歌云以及您自己的机器上安装 Keras。此外,我们还将概述 Keras 的 API。

第二章:Keras 安装与 API

在上一章中,我们讨论了神经网络的基本原理,并提供了一些能够识别 MNIST 手写数字的网络示例。

本章将讲解如何安装 Keras、Theano 和 TensorFlow。我们将逐步介绍如何使环境正常运行,并在短时间内从直觉走向实际的神经网络。接下来,我们将讨论如何在基于容器的 Docker 化基础设施上安装这些工具,并在 Google GCP、Amazon AWS 和 Microsoft Azure 云平台上进行安装。此外,我们还将介绍 Keras API 的概述,以及一些常用操作,例如加载和保存神经网络的架构与权重、早期停止、历史保存、检查点以及与 TensorBoard 和 Quiver 的交互。让我们开始吧。

到本章结束时,我们将涵盖以下主题:

  • 安装和配置 Keras

  • Keras 架构

安装 Keras

在接下来的章节中,我们将展示如何在多个平台上安装 Keras。

第 1 步 — 安装一些有用的依赖项

首先,我们安装 numpy 包,它提供对大型多维数组和矩阵的支持,以及高级数学函数。然后安装 scipy,这是一个用于科学计算的库。之后,可以安装 scikit-learn,这是一个被认为是机器学习的 Python 瑞士军刀的包。在本例中,我们将用它来进行数据探索。可选地,可以安装 pillow,一个用于图像处理的库,以及 h5py,这是一个用于数据序列化的库,Keras 用它来保存模型。只需要一条命令行即可安装所需的所有内容。或者,您可以安装 Anaconda Python,它会自动安装 numpyscipyscikit-learnh5pypillow 和其他很多科学计算所需的库(更多信息请参考:批量归一化:通过减少内部协变量偏移加速深度网络训练,作者 S. Ioffe 和 C. Szegedy,arXiv.org/abs/1502.03…,2015)。您可以在 docs.continuum.io/anaconda/pkg-docs 查找 Anaconda Python 中可用的包。以下截图展示了如何为我们的工作安装这些包:

第 2 步 — 安装 Theano

我们可以使用 pip 来安装 Theano,如下图所示:

第 3 步 — 安装 TensorFlow

现在我们可以使用 TensorFlow 官方网站上的说明来安装 TensorFlow,www.tensorflow.org/versions/r0.11/get_started/os_setup.html#pip-installation。同样,我们只是使用 pip 安装正确的包,如下图所示。例如,如果我们需要使用 GPU,那么选择适当的包非常重要:

第 4 步 — 安装 Keras

现在我们可以简单地安装 Keras 并开始测试已安装的环境。非常简单;我们再次使用 pip,如下面的截图所示:

第 5 步 — 测试 Theano、TensorFlow 和 Keras

现在让我们测试一下环境。首先来看一下如何在 Theano 中定义 sigmoid 函数。如你所见,这非常简单;我们只需写出数学公式并在矩阵上按元素计算该函数。只需运行 Python Shell 并按如下截图所示写下代码,即可得到结果:

所以,Theano 可以正常工作。接下来,我们通过简单地导入 MNIST 数据集来测试 TensorFlow,如下截图所示。在第一章《神经网络基础》中,我们已经看到了几个 Keras 网络的实际示例:

配置 Keras

Keras 具有一个非常简洁的配置文件。我们通过 vi 会话加载它。参数非常简单:

参数
image_dim_ordering可以是 tf 表示 TensorFlow 的图像顺序,或者 th 表示 Theano 的图像顺序
epsilon计算过程中使用的 epsilon
floatx可以是 float32float64
backend可以是 tensorflowtheano

image_dim_orderingth 值会为你提供一个相对不直观的图像维度顺序(深度、宽度和高度),而不是 tf 的(宽度、高度和深度)。以下是我机器上的默认参数:

如果你安装了支持 GPU 的 TensorFlow 版本,那么当 TensorFlow 被选作后端时,Keras 会自动使用你配置的 GPU。

在 Docker 上安装 Keras

启动 TensorFlow 和 Keras 的最简单方法之一是运行在 Docker 容器中。一个方便的解决方案是使用社区创建的深度学习预定义 Docker 镜像,它包含所有流行的深度学习框架(TensorFlow、Theano、Torch、Caffe 等)。请参考 GitHub 仓库 github.com/saiprashanths/dl-docker 获取代码文件。假设你已经启动并运行了 Docker(有关更多信息,请参考 www.docker.com/products/overview),安装过程非常简单,如下所示:

下一个截图中显示的内容大致是:从 Git 获取图像后,我们构建 Docker 镜像:

在这个截图中,我们可以看到如何运行它:

从容器内,可以启用对 Jupyter Notebooks 的支持(有关更多信息,请参见 jupyter.org/):

从主机机器直接通过端口访问:

还可以通过下面截图中的命令访问 TensorBoard(有关更多信息,请参见 www.tensorflow.org/how_tos/summaries_and_tensorboard/),该命令将在下一部分讨论:

运行前述命令后,您将被重定向到以下页面:

在 Google Cloud ML 上安装 Keras

在 Google Cloud 上安装 Keras 非常简单。首先,我们可以安装 Google Cloud (有关可下载文件,请参见 cloud.google.com/sdk/),它是 Google Cloud Platform 的命令行界面;然后我们可以使用 CloudML,这是一个托管服务,允许我们轻松构建使用 TensorFlow 的机器学习模型。在使用 Keras 之前,让我们使用 Google Cloud 和 TensorFlow 来训练 GitHub 上提供的 MNIST 示例。代码是本地的,训练发生在云端:

在下一个截图中,您可以看到如何运行训练会话:

我们可以使用 TensorBoard 显示交叉熵如何随着迭代减少:

在下一个截图中,我们看到交叉熵的图形:

现在,如果我们想在 TensorFlow 上使用 Keras,只需从 PyPI 下载 Keras 源代码(有关可下载文件,请参见 pypi.Python.org/pypi/Keras/1.2.0 或更高版本),然后像使用 CloudML 包解决方案一样直接使用 Keras,如以下示例所示:

这里,trainer.task2.py 是一个示例脚本:

from keras.applications.vgg16 import VGG16
from keras.models import Model
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np

# pre-built and pre-trained deep learning VGG16 model
base_model = VGG16(weights='imagenet', include_top=True)
for i, layer in enumerate(base_model.layers):
  print (i, layer.name, layer.output_shape)

在 Amazon AWS 上安装 Keras

在 Amazon 上安装 TensorFlow 和 Keras 非常简单。事实上,可以使用一个名为 TFAMI.v3 的预构建 AMI,该 AMI 是开放且免费的(有关更多信息,请参见 github.com/ritchieng/tensorflow-aws-ami),如下所示:

此 AMI 在不到五分钟内运行 TensorFlow,并支持 TensorFlow、Keras、OpenAI Gym 及所有依赖项。截至 2017 年 1 月,它支持以下内容:

  • TensorFlow 0.12

  • Keras 1.1.0

  • TensorLayer 1.2.7

  • CUDA 8.0

  • CuDNN 5.1

  • Python 2.7

  • Ubuntu 16.04

此外,TFAMI.v3 可在 P2 计算实例上运行(有关更多信息,请参见 aws.amazon.com/ec2/instance-types/#p2),如以下截图所示:

P2 实例的一些特点如下:

TFAMI.v3 也适用于 G2 计算实例(更多信息,请参阅 aws.amazon.com/ec2/instance-types/#g2)。G2 实例的一些特点如下:

  • Intel Xeon E5-2670(Sandy Bridge)处理器

  • NVIDIA GPU,每个具有 1,536 个 CUDA 核心和 4 GB 显存

在 Microsoft Azure 上安装 Keras

在 Azure 上安装 Keras 的一种方法是安装 Docker 支持,然后获取包含 TensorFlow 和 Keras 的容器化版本。在线上,您也可以找到关于如何通过 Docker 安装 Keras 和 TensorFlow 的详细说明,但这本质上是我们在前面章节中已经看到的内容(更多信息,请参阅 blogs.msdn.microsoft.com/uk_faculty_connection/2016/09/26/tensorflow-on-docker-with-microsoft-azure/)。

如果您仅使用 Theano 作为后端,那么通过加载在 Cortana Intelligence Gallery 上提供的预构建包,Keras 只需点击即可运行(更多信息,请参阅 gallery.cortanaintelligence.com/Experiment/Theano-Keras-1)。

以下示例展示了如何将 Theano 和 Keras 作为 ZIP 文件直接导入 Azure ML,并在执行 Python 脚本模块中使用它们。此示例由 Hai Ning 提供(更多信息,请参阅 goo.gl/VLR25o),本质上是在 azureml_main() 方法内运行 Keras 代码:

# The script MUST contain a function named azureml_main
# which is the entry point for this module.

# imports up here can be used to
import pandas as pd
import theano
import theano.tensor as T
from theano import function
from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np
# The entry point function can contain up to two input arguments:
#   Param<dataframe1>: a pandas.DataFrame
#   Param<dataframe2>: a pandas.DataFrame
def azureml_main(dataframe1 = None, dataframe2 = None):
    # Execution logic goes here
    # print('Input pandas.DataFrame #1:rnrn{0}'.format(dataframe1))

    # If a zip file is connected to the third input port is connected,
    # it is unzipped under ".Script Bundle". This directory is added
    # to sys.path. Therefore, if your zip file contains a Python file
    # mymodule.py you can import it using:
    # import mymodule
    model = Sequential()
    model.add(Dense(1, input_dim=784, activation="relu"))
    model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
    data = np.random.random((1000,784))
    labels = np.random.randint(2, size=(1000,1))
    model.fit(data, labels, nb_epoch=10, batch_size=32)
    model.evaluate(data, labels)

    return dataframe1,

在此截图中,您看到一个使用 Microsoft Azure ML 运行 Theano 和 Keras 的示例:

Keras API

Keras 拥有模块化、简洁且易于扩展的架构。Keras 的作者 Francois Chollet 说:

该库的开发重点是实现快速实验。能够以尽可能短的延迟从想法到结果是进行良好研究的关键。

Keras 定义了在 TensorFlow(更多信息,请参阅 github.com/tensorflow/tensorflow)或 Theano(更多信息,请参阅 github.com/Theano/Theano)之上运行的高层神经网络。具体来说:

  • 模块化:模型要么是一个序列,要么是一个独立模块的图,这些模块可以像 LEGO 积木一样组合在一起,构建神经网络。即,该库预定义了大量的模块,包含不同类型的神经网络层、损失函数、优化器、初始化方案、激活函数和正则化方案。

  • 极简主义:该库是用 Python 实现的,每个模块都简洁且自描述。

  • 易于扩展:该库可以通过新功能进行扩展,正如我们将在第七章中描述的那样,附加深度学习模型

开始使用 Keras 架构

在这一部分,我们回顾了用于定义神经网络的最重要的 Keras 组件。首先,我们定义张量的概念,然后讨论组合预定义模块的不同方式,最后概述最常用的模块。

什么是张量?

Keras 使用 Theano 或 TensorFlow 执行对张量的高效计算。但张量到底是什么?张量其实就是一个多维数组或矩阵。这两个后端都能够高效地进行符号计算,张量是创建神经网络的基本构建块。

在 Keras 中组合模型

在 Keras 中有两种组合模型的方式。它们如下:

  • 顺序组合

  • 功能组合

让我们详细看看每一个。

顺序组合

第一种是顺序组合,将不同的预定义模型按顺序堆叠在一起,形成类似于堆栈或队列的线性层次结构。在第一章,神经网络基础中,我们看到了几个顺序流水线的示例。例如:

model = Sequential()
model.add(Dense(N_HIDDEN, input_shape=(784,)))
model.add(Activation('relu'))
model.add(Dropout(DROPOUT))
model.add(Dense(N_HIDDEN))
model.add(Activation('relu'))
model.add(Dropout(DROPOUT))
model.add(Dense(nb_classes))
model.add(Activation('softmax'))
model.summary()

功能组合

组合模块的第二种方式是通过功能性 API,这种方式可以定义复杂的模型,如有向无环图、具有共享层的模型或多输出模型。我们将在第七章中看到这样的示例,附加深度学习模型

预定义神经网络层概览

Keras 有许多预构建的层。让我们回顾一下最常用的层,并指出这些层在何种章节中最常使用。

常规密集

一个密集模型是一个全连接的神经网络层。我们已经在第一章,神经网络基础中看到了使用示例。这里是一个带有参数定义的原型:

keras.layers.core.Dense(units, activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)

循环神经网络——简单的、LSTM 和 GRU

循环神经网络是一类利用输入序列特性的神经网络。这类输入可以是文本、语音、时间序列或任何其他序列中元素的出现依赖于前面元素的情况。我们将在第六章,循环神经网络——RNN中讨论简单的 LSTM 和 GRU 循环神经网络。这里展示了一些带有参数定义的原型:

keras.layers.recurrent.Recurrent(return_sequences=False, go_backwards=False, stateful=False, unroll=False, implementation=0)

keras.layers.recurrent.SimpleRNN(units, activation='tanh', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0)

keras.layers.recurrent.GRU(units, activation='tanh', recurrent_activation='hard_sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0)

keras.layers.recurrent.LSTM(units, activation='tanh', recurrent_activation='hard_sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', unit_forget_bias=True, kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0)

卷积层和池化层

卷积网络(ConvNets)是一类使用卷积和池化操作,通过逐步学习基于不同抽象层次的复杂模型的神经网络。这种通过逐步抽象的学习方式,类似于人类大脑中经过数百万年演化的视觉模型。几年前,人们称其为深度,通常指的是 3 到 5 层,而现在它已经发展到 100 到 200 层。我们将在第三章,深度学习与卷积网络中讨论卷积神经网络。以下是带有参数定义的原型:

keras.layers.convolutional.Conv1D(filters, kernel_size, strides=1, padding='valid', dilation_rate=1, activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)

keras.layers.convolutional.Conv2D(filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)

keras.layers.pooling.MaxPooling1D(pool_size=2, strides=None, padding='valid')

keras.layers.pooling.MaxPooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None)

正则化

正则化是一种防止过拟合的方法。我们已经在第一章,神经网络基础中看到过使用示例。多个层有用于正则化的参数。以下是常用于全连接和卷积模块的正则化参数列表:

  • kernel_regularizer:应用于权重矩阵的正则化函数

  • bias_regularizer:应用于偏置向量的正则化函数

  • activity_regularizer:应用于层输出(激活)的正则化函数

此外,还可以使用 Dropout 进行正则化,这通常是一个非常有效的选择

keras.layers.core.Dropout(rate, noise_shape=None, seed=None)

其中:

  • rate:它是一个介于 0 和 1 之间的浮动数,表示丢弃的输入单元的比例

  • noise_shape:它是一个一维整数张量,表示将与输入相乘的二进制丢弃掩码的形状

  • seed:它是一个整数,用作随机种子

批量归一化

批量归一化(欲了解更多信息,请参考www.colwiz.com/cite-in-google-docs/cid=f20f9683aaf69ce)是一种加速学习并通常实现更高准确率的方法。我们将在第四章,生成对抗网络与 WaveNet中讨论 GAN 时展示使用示例。以下是带有参数定义的原型:

keras.layers.normalization.BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001, center=True, scale=True, beta_initializer='zeros', gamma_initializer='ones', moving_mean_initializer='zeros', moving_variance_initializer='ones', beta_regularizer=None, gamma_regularizer=None, beta_constraint=None, gamma_constraint=None)

预定义激活函数概述

激活函数包括常用的函数,如 Sigmoid、线性、双曲正切和 ReLU。我们在第一章《神经网络基础》中看到了一些激活函数的示例,更多示例将在后续章节中呈现。以下图示为 Sigmoid、线性、双曲正切和 ReLU 激活函数的示例:

Sigmoid线性
双曲正切ReLU

损失函数概述

损失函数(或目标函数,或优化得分函数;更多信息,请参见keras.io/losses/)可以分为四类:

  • 准确率用于分类问题。可选择多种方法:binary_accuracy(二分类问题中所有预测的平均准确率),categorical_accuracy(多分类问题中所有预测的平均准确率),sparse_categorical_accuracy(适用于稀疏目标),以及top_k_categorical_accuracy(当目标类别位于提供的 top_k 预测之内时成功)。

  • 错误损失,用于衡量预测值与实际观察值之间的差异。可选择多种方法:mse(预测值与目标值之间的均方误差),rmse(预测值与目标值之间的均方根误差),mae(预测值与目标值之间的均绝对误差),mape(预测值与目标值之间的均百分比误差),以及msle(预测值与目标值之间的均平方对数误差)。

  • 铰链损失,通常用于训练分类器。有两个版本:hinge 定义为 平方铰链 定义为铰链损失的平方值。

  • 类别损失用于计算分类问题的交叉熵。有多个版本,包括二元交叉熵(更多信息,请参见en.wikipedia.org/wiki/Cross_entropy),以及类别交叉熵。

我们在第一章《神经网络基础》中看到了一些目标函数的示例,更多示例将在后续章节中呈现。

度量函数概述

度量函数(更多信息,请参见keras.io/metrics/)类似于目标函数,唯一的区别是评估度量函数时得到的结果不会用于训练模型。我们在第一章《神经网络基础》中看到了一些度量函数的示例,更多示例将在后续章节中呈现。

优化器概述

优化器包括 SGD、RMSprop 和 Adam。我们在第一章《神经网络基础》中看到了几个优化器的示例,更多的示例(如 Adagrad 和 Adadelta;更多信息,请参考keras.io/optimizers/)将在后续章节中展示。

一些有用的操作

在这里,我们报告了一些可以通过 Keras API 执行的实用操作。目标是简化网络的创建、训练过程和中间结果的保存。

保存和加载模型的权重和架构

模型架构可以轻松保存和加载,如下所示:

# save as JSON json_string = model.to_json()
# save as YAML yaml_string = model.to_yaml() 
# model reconstruction from JSON: from keras.models import model_from_json model = model_from_json(json_string) # model reconstruction from YAML model = model_from_yaml(yaml_string)

模型参数(权重)可以轻松保存和加载,如下所示:

from keras.models import load_model model.save('my_model.h5')
# creates a HDF5 file 'my_model.h5' del model
# deletes the existing model
# returns a compiled model
# identical to the previous one model = load_model('my_model.h5')

用于自定义训练过程的回调函数

当某个指标停止改善时,可以使用适当的callback停止训练过程:

keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0,  
patience=0, verbose=0, mode='auto')

通过定义如下的callback,可以保存损失历史:

class LossHistory(keras.callbacks.Callback):     def on_train_begin(self, logs={}):         self.losses = []     def on_batch_end(self, batch, logs={}):         self.losses.append(logs.get('loss')) model = Sequential() model.add(Dense(10, input_dim=784, init='uniform')) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='rmsprop') history = LossHistory() model.fit(X_train,Y_train, batch_size=128, nb_epoch=20,  
verbose=0, callbacks=[history]) print history.losses

检查点

检查点是一个过程,它定期保存应用程序的状态快照,以便在失败时从最后保存的状态重新启动应用程序。这在训练深度学习模型时非常有用,因为这通常是一个耗时的任务。深度学习模型在任何时刻的状态就是该时刻的模型权重。Keras 将这些权重以 HDF5 格式保存(更多信息,请参考www.hdfgroup.org/),并通过其回调 API 提供检查点功能。

一些可能需要使用检查点的场景包括以下几点:

第一和第二种场景可以通过在每个 epoch 后保存检查点来处理,这可以通过默认使用ModelCheckpoint回调来实现。以下代码演示了如何在 Keras 中训练深度学习模型时添加检查点:

from __future__ import division, print_function 
from keras.callbacks import ModelCheckpoint 
from keras.datasets import mnist 
from keras.models import Sequential 
from keras.layers.core import Dense, Dropout 
from keras.utils import np_utils 
import numpy as np 
import os 

BATCH_SIZE = 128 
NUM_EPOCHS = 20 
MODEL_DIR = "/tmp" 

(Xtrain, ytrain), (Xtest, ytest) = mnist.load_data() 
Xtrain = Xtrain.reshape(60000, 784).astype("float32") / 255 
Xtest = Xtest.reshape(10000, 784).astype("float32") / 255 
Ytrain = np_utils.to_categorical(ytrain, 10) 
Ytest = np_utils.to_categorical(ytest, 10) 
print(Xtrain.shape, Xtest.shape, Ytrain.shape, Ytest.shape) 

model = Sequential() 
model.add(Dense(512, input_shape=(784,), activation="relu")) 
model.add(Dropout(0.2)) 
model.add(Dense(512, activation="relu")) 
model.add(Dropout(0.2)) 
model.add(Dense(10, activation="softmax")) 

model.compile(optimizer="rmsprop", loss="categorical_crossentropy", 
              metrics=["accuracy"]) 

# save best model 
checkpoint = ModelCheckpoint( 
    filepath=os.path.join(MODEL_DIR, "model-{epoch:02d}.h5")) 
model.fit(Xtrain, Ytrain, batch_size=BATCH_SIZE, nb_epoch=NUM_EPOCHS, 
          validation_split=0.1, callbacks=[checkpoint])

第三种场景涉及监控某个指标,如验证准确率或损失,并且仅在当前指标优于之前保存的检查点时才保存检查点。Keras 提供了一个额外的参数save_best_only,在实例化检查点对象时需要将其设置为true以支持此功能。

使用 TensorBoard 和 Keras

Keras 提供了一个回调函数,用于保存训练和测试指标,以及模型中不同层的激活直方图:

keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0,  
write_graph=True, write_images=False)

保存的数据可以通过在命令行启动的 TensorBoard 进行可视化:

tensorboard --logdir=/full_path_to_your_logs

使用 Quiver 和 Keras

在第三章,卷积神经网络与深度学习中,我们将讨论卷积神经网络(ConvNets),这是一种用于处理图像的高级深度学习技术。这里我们预览一下 Quiver(更多信息请参见github.com/jakebian/quiver),它是一个用于以交互方式可视化卷积神经网络特征的工具。安装过程相当简单,安装后,Quiver 可以通过一行命令来使用:

pip install quiver_engine 

from quiver_engine import server     server.launch(model)

这将在 localhost:5000 启动可视化。Quiver 允许你像下面的示例一样直观地检查神经网络:

总结

本章我们讨论了如何在以下环境中安装 Theano、TensorFlow 和 Keras:

  • 在本地机器上

  • 基于容器的 Docker 化基础设施

  • 在云端使用 Google GCP、Amazon AWS 和 Microsoft Azure

除此之外,我们还查看了几个模块,这些模块定义了 Keras API 以及一些常用操作,如加载和保存神经网络的架构和权重、早期停止、历史保存、检查点、与 TensorBoard 的交互以及与 Quiver 的交互。

在下一章中,我们将介绍卷积网络的概念,它是深度学习中的一项基础创新,已经在多个领域取得了成功应用,从文本、视频到语音,远远超出了最初在图像处理领域的应用。

第三章:使用卷积神经网络的深度学习

在前面的章节中,我们讨论了密集网络,其中每一层都与相邻的层完全连接。我们将这些密集网络应用于分类 MNIST 手写字符数据集。在那种情况下,输入图像中的每个像素都被分配给一个神经元,总共有 784 个(28 x 28 像素)输入神经元。然而,这种策略并没有利用每个图像的空间结构和关系。特别地,这段代码将每个书写数字的位图转换为一个平坦的向量,在这个过程中,空间局部性丧失:

#X_train is 60000 rows of 28x28 values --> reshaped in 60000 x 784
X_train = X_train.reshape(60000, 784)
X_test = X_test.reshape(10000, 784)
o

卷积神经网络(也称为 ConvNet)利用空间信息,因此非常适合分类图像。这些网络使用一种受生物学数据启发的特殊架构,这些数据来自对视觉皮层进行的生理实验。正如我们所讨论的,我们的视觉是基于多个皮层层级的,每一层识别越来越结构化的信息。首先,我们看到单个像素;然后从这些像素中,我们识别出简单的几何形状。接着...越来越复杂的元素,如物体、面孔、人类身体、动物等等。

卷积神经网络确实令人着迷。在短短的时间内,它们成为了一种颠覆性技术,突破了多个领域的所有最先进成果,从文本到视频,再到语音,远远超出了最初的图像处理领域。

本章将涵盖以下主题:

  • 深度卷积神经网络

  • 图像分类

深度卷积神经网络 — DCNN

深度卷积神经网络DCNN)由许多神经网络层组成。通常交替使用两种不同类型的层,卷积层和池化层。每个滤波器的深度从网络的左到右逐渐增加。最后阶段通常由一个或多个全连接层组成:

卷积神经网络之外有三个关键直觉:

  • 局部感受野

  • 共享权重

  • 池化

让我们回顾一下它们。

局部感受野

如果我们希望保持空间信息,那么用像素矩阵表示每张图像是很方便的。然后,一种简单的编码局部结构的方法是将相邻输入神经元的子矩阵连接到下一个层中的一个隐藏神经元。这一个隐藏神经元代表一个局部感受野。需要注意的是,这个操作被称为卷积,它也给这种类型的网络命名。

当然,我们可以通过使用重叠的子矩阵来编码更多的信息。例如,假设每个子矩阵的大小为 5 x 5,并且这些子矩阵与 28 x 28 像素的 MNIST 图像一起使用。这样,我们将能够在下一个隐藏层中生成 23 x 23 的局部感受野神经元。实际上,只有在滑动子矩阵 23 个位置后,才会触及图像的边界。在 Keras 中,每个子矩阵的大小称为步幅长度,这是一个可以在构建网络时进行微调的超参数。

假设我们定义从一层到另一层的特征图。当然,我们可以有多个特征图,它们各自独立地从每个隐藏层中学习。例如,我们可以从 28 x 28 的输入神经元开始处理 MNIST 图像,然后在下一个隐藏层中回调 k 个特征图,每个特征图的大小为 23 x 23 个神经元(同样步幅为 5 x 5)。

共享权重和偏置

假设我们想摆脱行像素表示,转而通过获得在输入图像中放置位置无关的相同特征的能力。一个简单的直觉是对隐藏层中的所有神经元使用相同的一组权重和偏置。这样,每一层都会学习从图像中提取的、位置无关的潜在特征。

假设输入图像的形状是 (256, 256),并且具有三个通道,在 tf(TensorFlow)顺序下表示为 (256, 256, 3)。请注意,在 th(Theano)模式下,通道维度(深度)位于索引 1 位置;而在 tf(TensorFlow)模式下,位于索引 3 位置。

在 Keras 中,如果我们想添加一个卷积层,输出维度为 32,每个滤波器的扩展为 3 x 3,我们将写道:

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(256, 256, 3))

或者,我们将写道:

model = Sequential()
model.add(Conv2D(32, kernel_size=3, input_shape=(256, 256, 3))

这意味着我们在一个 256 x 256 的图像上应用 3 x 3 的卷积,图像有三个输入通道(或输入滤波器),并且结果是 32 个输出通道(或输出滤波器)。

以下是卷积示例:

池化层

假设我们想总结一个特征图的输出。同样,我们可以利用来自单个特征图的输出的空间连续性,将子矩阵的值聚合为一个单一的输出值,这个值合成地描述了与该物理区域相关的意义

最大池化

一种简单而常见的选择是最大池化,它仅输出在该区域内观察到的最大激活值。在 Keras 中,如果我们想定义一个 2 x 2 大小的最大池化层,我们将写道:

model.add(MaxPooling2D(pool_size = (2, 2)))

以下是最大池化示例:

平均池化

另一个选择是平均池化,它仅将一个区域的激活值聚合为该区域观察到的激活值的平均值。

请注意,Keras 实现了大量的池化层,完整的池化层列表可以在以下链接找到:keras.io/layers/pooling/。简而言之,所有的池化操作无非是在给定区域上的汇总操作。

ConvNets 总结

到目前为止,我们已经描述了 ConvNets 的基本概念。卷积神经网络(CNNs)在一维(时间维度)上对音频和文本数据应用卷积和池化操作,在二维(高 × 宽)上对图像进行卷积和池化操作,在三维(高 × 宽 × 时间)上对视频进行卷积和池化操作。对于图像,将滤波器滑动在输入体积上,产生一个映射,显示每个空间位置的滤波器响应。换句话说,ConvNet 有多个滤波器堆叠在一起,它们学习识别特定的视觉特征,而不受图像中位置的影响。网络初期的视觉特征较为简单,随着网络层数加深,特征变得越来越复杂。

DCNN 示例 — LeNet

Yann le Cun 提出了一个名为 LeNet 的卷积神经网络(更多信息参见:Convolutional Networks for Images, Speech, and Time-Series,作者 Y. LeCun 和 Y. Bengio,脑理论神经网络,卷 3361,1995 年),该网络用于识别 MNIST 手写字符,具有对简单几何变换和失真的鲁棒性。这里的关键直觉是,低层交替使用卷积操作和最大池化操作。卷积操作基于精心选择的局部感受野,并为多个特征图共享权重。然后,更高层是基于传统多层感知器(MLP)结构的全连接层,其中包含隐藏层,输出层使用 softmax 激活函数。

Keras 中的 LeNet 代码

要定义 LeNet 代码,我们使用一个卷积 2D 模块,具体如下:

keras.layers.convolutional.Conv2D(filters, kernel_size, padding='valid')

在这里,filters 表示使用的卷积核数量(例如,输出的维度),kernel_size 是一个整数或一个包含两个整数的元组/列表,指定 2D 卷积窗口的宽度和高度(可以是一个单独的整数,以指定所有空间维度的相同值),而 padding='same' 表示使用了填充。这里有两个选项:padding='valid' 表示卷积仅在输入和滤波器完全重叠的地方计算,因此输出小于输入;而 padding='same' 表示输出的尺寸与输入相同,为此,输入周围的区域会用零进行填充。

此外,我们使用 MaxPooling2D 模块:

keras.layers.pooling.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))

在这里,pool_size=(2, 2) 是一个包含两个整数的元组,表示图像在垂直和水平方向上缩小的比例。因此,(2, 2) 将在每个维度上将图像减半,而 strides=(2, 2) 是处理时使用的步长。

现在,让我们回顾一下代码。首先我们导入一些模块:

from keras import backend as K
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras.datasets import mnist
from keras.utils import np_utils
from keras.optimizers import SGD, RMSprop, Adam
import numpy as np
import matplotlib.pyplot as plt

然后我们定义 LeNet 网络:

#define the ConvNet
class LeNet:
    @staticmethod
    def build(input_shape, classes):
         model = Sequential()
         # CONV => RELU => POOL

我们有一个包含 ReLU 激活的第一卷积阶段,然后是最大池化。我们的网络将学习 20 个卷积滤波器,每个大小为 5 x 5。输出维度与输入形状相同,因此为 28 x 28。请注意,由于Convolution2D是我们管道的第一个阶段,我们还需要定义它的input_shape。最大池化操作实现一个滑动窗口,在层上滑动,并在每个区域垂直和水平方向上以两个像素的步长取最大值:

model.add(Convolution2D(20, kernel_size=5, padding="same",
input_shape=input_shape))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
# CONV => RELU => POOL

然后是第二个带 ReLU 激活的卷积阶段,再次进行最大池化。在这种情况下,我们将从之前的 20 增加到 50 个学习的卷积滤波器。在深层增加滤波器的数量是深度学习中常用的技术:

model.add(Conv2D(50, kernel_size=5, border_mode="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))

然后我们有一个相当标准的扁平化和 500 个神经元的密集网络,后跟一个有 10 类的 softmax 分类器:

# Flatten => RELU layers
model.add(Flatten())
model.add(Dense(500))
model.add(Activation("relu"))
# a softmax classifier
model.add(Dense(classes))
model.add(Activation("softmax"))
return model

恭喜!您刚刚定义了第一个深度学习网络!让我们看看它的视觉效果:

现在我们需要一些额外的代码来训练网络,但这与我们已在第一章中描述的内容非常相似,神经网络基础。这次,我们还展示了打印损失的代码:

# network and training
NB_EPOCH = 20
BATCH_SIZE = 128
VERBOSE = 1
OPTIMIZER = Adam()
VALIDATION_SPLIT=0.2
IMG_ROWS, IMG_COLS = 28, 28 # input image dimensions
NB_CLASSES = 10 # number of outputs = number of digits
INPUT_SHAPE = (1, IMG_ROWS, IMG_COLS)
# data: shuffled and split between train and test sets
(X_train, y_train), (X_test, y_test) = mnist.load_data()
k.set_image_dim_ordering("th")
# consider them as float and normalize
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255
X_test /= 255
# we need a 60K x [1 x 28 x 28] shape as input to the CONVNET
X_train = X_train[:, np.newaxis, :, :]
X_test = X_test[:, np.newaxis, :, :]
print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')
# convert class vectors to binary class matrices
y_train = np_utils.to_categorical(y_train, NB_CLASSES)
y_test = np_utils.to_categorical(y_test, NB_CLASSES)
# initialize the optimizer and model
model = LeNet.build(input_shape=INPUT_SHAPE, classes=NB_CLASSES)
model.compile(loss="categorical_crossentropy", optimizer=OPTIMIZER,
metrics=["accuracy"])
history = model.fit(X_train, y_train,
batch_size=BATCH_SIZE, epochs=NB_EPOCH,
verbose=VERBOSE, validation_split=VALIDATION_SPLIT)
score = model.evaluate(X_test, y_test, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])
# list all data in history
print(history.history.keys())
# summarize history for accuracy
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
# summarize history for loss
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

现在让我们运行代码。正如您所看到的,时间显著增加,我们深度网络中的每次迭代现在需要约 134 秒,而在第一章中定义的网络,神经网络基础,每次迭代只需约 1-2 秒。然而,准确率已经达到了新的峰值 99.06%:

让我们绘制模型的准确率和损失,我们可以理解,仅需 4 - 5 次迭代即可达到类似 99.2%的准确率:

在下面的截图中,我们展示了我们模型达到的最终准确率:

让我们看看一些 MNIST 图像,以了解 99.2%的准确率有多好!例如,人们写数字 9 的方式有很多种,在以下图表中显示了其中一种。对于数字 3、7、4 和 5 也是如此。在这张图中,数字1是如此难以识别,甚至可能连人类也会有问题:

到目前为止,我们通过不同的模型总结了所有的进展,如下图所示。我们的简单网络从 92.22%的准确率开始,这意味着大约 100 个手写字符中有 8 个识别错误。然后,通过深度学习架构获得了 7%,达到了 99.20%的准确率,这意味着大约 100 个手写字符中有 1 个识别错误:

理解深度学习的力量

我们可以进行另一个测试,以更好地理解深度学习和卷积神经网络(ConvNet)的强大能力,那就是减少训练集的大小,并观察性能随之下降的情况。一种方法是将 50,000 个样本的训练集拆分成两个不同的子集:

  • 用于训练我们模型的合适训练集将逐步减少其大小(5,900、3,000、1,800、600 和 300 个样本)

  • 用于估算模型训练效果的验证集将由剩余的样本组成

我们的测试集始终固定,包含 10,000 个样本。

在这个设置下,我们将刚定义的深度学习卷积神经网络与第一章中定义的第一个神经网络示例进行比较,神经网络基础。正如我们在下面的图表中看到的那样,我们的深度网络始终优于简单网络,并且当提供的训练样本数量逐步减少时,差距越来越明显。使用 5,900 个训练样本时,深度学习网络的准确率为 96.68%,而简单网络的准确率为 85.56%。更重要的是,即使只有 300 个训练样本,我们的深度学习网络仍然能够达到 72.44%的准确率,而简单网络的准确率则显著下降至 48.26%。所有实验只进行了四次训练迭代。这证明了深度学习所带来的突破性进展。乍一看,这从数学角度来看可能会令人惊讶,因为深度网络有更多的未知数(权重),所以人们可能会认为需要更多的数据点。然而,保持空间信息,加入卷积、池化和特征图是卷积神经网络的创新,这一结构已经在数百万年的时间里得到了优化(因为它的灵感来源于视觉皮层):

关于 MNIST 的最新成果列表可访问:rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html。截至 2017 年 1 月,最佳结果的错误率为 0.21%。

使用深度学习识别 CIFAR-10 图像

CIFAR-10 数据集包含 60,000 张 32 x 32 像素、3 通道的彩色图像,分为 10 个类别。每个类别包含 6,000 张图像。训练集包含 50,000 张图像,而测试集提供 10,000 张图像。这张来自 CIFAR 数据集的图像(www.cs.toronto.edu/~kriz/cifar.html)展示了 10 个类别中的一些随机示例:

目标是识别先前未见过的图像,并将它们分配到 10 个类别中的一个。让我们定义一个合适的深度网络。

首先,我们导入一些有用的模块,定义几个常量,并加载数据集:

from keras.datasets import cifar10
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.optimizers import SGD, Adam, RMSprop
import matplotlib.pyplot as plt

# CIFAR_10 is a set of 60K images 32x32 pixels on 3 channels
IMG_CHANNELS = 3
IMG_ROWS = 32
IMG_COLS = 32

#constant
BATCH_SIZE = 128
NB_EPOCH = 20
NB_CLASSES = 10
VERBOSE = 1
VALIDATION_SPLIT = 0.2
OPTIM = RMSprop()

#load dataset
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
print('X_train shape:', X_train.shape)
print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')

现在我们进行独热编码并对图像进行归一化:

# convert to categorical
Y_train = np_utils.to_categorical(y_train, NB_CLASSES)
Y_test = np_utils.to_categorical(y_test, NB_CLASSES)

# float and normalization
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255
X_test /= 255

我们的网络将学习 32 个卷积滤波器,每个滤波器的大小为 3 x 3。输出维度与输入形状相同,因此为 32 x 32,激活函数为 ReLU,这是一种引入非线性的简单方式。之后我们会进行一个 2 x 2 大小的最大池化操作,并使用 25%的 dropout:

# network
model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',
input_shape=(IMG_ROWS, IMG_COLS, IMG_CHANNELS)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

深度管道的下一阶段是一个包含 512 个单元的密集网络,激活函数为 ReLU,随后是 50%的 dropout,最后是一个包含 10 个类别输出的 softmax 层,每个类别对应一个输出:

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()

在定义了网络后,我们可以训练模型。在这种情况下,我们将数据拆分,并计算出一个验证集,除了训练集和测试集。训练集用于构建我们的模型,验证集用于选择表现最好的方法,而测试集用于检验我们最好的模型在全新数据上的表现:

# train
model.compile(loss='categorical_crossentropy', optimizer=OPTIM,
metrics=['accuracy'])
model.fit(X_train, Y_train, batch_size=BATCH_SIZE,
epochs=NB_EPOCH, validation_split=VALIDATION_SPLIT,
verbose=VERBOSE)
score = model.evaluate(X_test, Y_test,
batch_size=BATCH_SIZE, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

在这种情况下,我们保存了我们深度网络的架构:

#save model
model_json = model.to_json()
open('cifar10_architecture.json', 'w').write(model_json)
And the weights learned by our deep network on the training set
model.save_weights('cifar10_weights.h5', overwrite=True)

让我们运行代码。我们的网络经过 20 次迭代,测试准确率达到了 66.4%。我们还打印了准确率和损失图,并使用model.summary()输出了网络结构:

在下图中,我们展示了网络在训练集和测试集上所达到的准确率和损失:

通过更深的网络提高 CIFAR-10 的性能

提高性能的一种方法是定义一个更深的网络,包含多个卷积操作。在这个示例中,我们有一个模块序列:

conv+conv+maxpool+dropout+conv+conv+maxpool

接着是一个标准的dense+dropout+dense结构。所有的激活函数均为 ReLU。

让我们看看新网络的代码:

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',
input_shape=(IMG_ROWS, IMG_COLS, IMG_CHANNELS)))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))

恭喜!你已经定义了一个更深的网络。让我们运行代码!首先我们输出网络结构,然后运行 40 次迭代,准确率达到了 76.9%:

在下图中,我们可以看到经过 40 次迭代后所达到的准确率:

所以相较于之前的简单深层网络,我们提高了 10.5%的性能。为了完整起见,我们还报告了训练过程中的准确率和损失,如下所示:

通过数据增强提高 CIFAR-10 的性能

提高性能的另一种方法是为我们的训练生成更多的图像。关键的直觉是,我们可以使用标准的 CIFAR 训练集,并通过多种类型的变换对其进行增强,包括旋转、重缩放、水平/垂直翻转、缩放、通道偏移等。让我们看看代码:

from keras.preprocessing.image import ImageDataGenerator
from keras.datasets import cifar10
import numpy as np
NUM_TO_AUGMENT=5

#load dataset
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

# augumenting
print("Augmenting training set images...")
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')

rotation_range 是一个表示随机旋转图片的度数范围(0 - 180)。width_shiftheight_shift 是表示随机平移图片的垂直或水平方向的范围。zoom_range 是用于随机缩放图片的范围。horizontal_flip 是用于随机水平翻转一半图片的选项。fill_mode 是旋转或平移后,用于填充可能出现的新像素的策略:

xtas, ytas = [], []
for i in range(X_train.shape[0]):
num_aug = 0
x = X_train[i] # (3, 32, 32)
x = x.reshape((1,) + x.shape) # (1, 3, 32, 32)
for x_aug in datagen.flow(x, batch_size=1,
save_to_dir='preview', save_prefix='cifar', save_format='jpeg'):
if num_aug >= NUM_TO_AUGMENT:
break
xtas.append(x_aug[0])
num_aug += 1

在数据增强后,我们将从标准的 CIFAR-10 数据集生成更多的训练图片:

现在,我们可以直接将这一思路应用于训练。使用之前定义的同一个卷积神经网络(ConvNet),我们只需要生成更多的增强图像,然后进行训练。为了提高效率,生成器与模型并行运行。这使得图像增强在 CPU 上进行,并且与 GPU 上的训练并行执行。以下是代码:

#fit the dataget
datagen.fit(X_train)

# train
history = model.fit_generator(datagen.flow(X_train, Y_train,
batch_size=BATCH_SIZE), samples_per_epoch=X_train.shape[0],
epochs=NB_EPOCH, verbose=VERBOSE)
score = model.evaluate(X_test, Y_test,
batch_size=BATCH_SIZE, verbose=VERBOSE)
print("Test score:", score[0])
print('Test accuracy:', score[1])

由于我们现在有更多的训练数据,每次迭代的开销更大。因此,我们只运行 50 次迭代,看看能否达到 78.3%的准确率:

我们实验中获得的结果总结在下面的图表中:

关于 CIFAR-10 的最先进结果列表可以在以下网址找到:rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html。截至 2017 年 1 月,最佳结果的准确率为 96.53%。

使用 CIFAR-10 进行预测

现在假设我们想使用刚刚为 CIFAR-10 训练的深度学习模型进行大批量图像评估。由于我们已经保存了模型和权重,我们无需每次都重新训练:

import numpy as np
import scipy.misc
from keras.models import model_from_json
from keras.optimizers import SGD

#load model
model_architecture = 'cifar10_architecture.json'
model_weights = 'cifar10_weights.h5'
model = model_from_json(open(model_architecture).read())
model.load_weights(model_weights)

#load images
img_names = ['cat-standing.jpg', 'dog.jpg']
imgs = [np.transpose(scipy.misc.imresize(scipy.misc.imread(img_name), (32, 32)),
(1, 0, 2)).astype('float32')
for img_name in img_names]
imgs = np.array(imgs) / 255

# train
optim = SGD()
model.compile(loss='categorical_crossentropy', optimizer=optim,
metrics=['accuracy'])

# predict
predictions = model.predict_classes(imgs)
print(predictions)

现在,让我们为一张 和一张 获取预测结果。

我们得到了类别 3(猫)和 5(狗)作为输出,正如预期的那样:

用于大规模图像识别的非常深的卷积神经网络

2014 年,关于图像识别的一个有趣贡献被提出(更多信息请参考:用于大规模图像识别的非常深的卷积神经网络,作者:K. Simonyan 和 A. Zisserman,2014 年)。该论文表明,通过将深度推向 16-19 层,可以显著改善先前的网络配置。论文中有一个模型被称为 D 或 VGG-16,它有 16 层深度。该模型在 Java Caffe 中实现(caffe.berkeleyvision.org/),用于在 ImageNet ILSVRC-2012 数据集上训练模型,该数据集包含 1,000 个类别的图像,并分为三个部分:训练集(130 万张图像)、验证集(5 万张图像)和测试集(10 万张图像)。每张图像的尺寸为(224 x 224),且有三个通道。该模型在 ILSVRC-2012-验证集上达到了 7.5%的 Top 5 错误率,在 ILSVRC-2012-测试集上达到了 7.4%的 Top 5 错误率。

根据 ImageNet 网站的描述:

本次竞赛的目标是估计照片的内容,用于检索和自动注释,训练数据集使用的是一个大规模手工标注的 ImageNet 子集(包含 1000 万个标注的图像,涵盖 10,000 多个物体类别)。测试图像将以没有初始注释的形式呈现——没有分割或标签——算法需要生成标签,指明图像中有哪些物体。

在 Caffe 中实现的模型学习到的权重已直接转换为 Keras(更多信息请参阅:gist.github.com/baraldilorenzo/07d7802847aaad0a35d3),并可用于预加载到 Keras 模型中,接下来按论文描述实现:

from keras.models import Sequential
from keras.layers.core import Flatten, Dense, Dropout
from keras.layers.convolutional import Conv2D, MaxPooling2D, ZeroPadding2D
from keras.optimizers import SGD
import cv2, numpy as np

# define a VGG16 network
def VGG_16(weights_path=None):
model = Sequential()
model.add(ZeroPadding2D((1,1),input_shape=(3,224,224)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(256, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(256, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(256, (3, 3), activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(512, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(512, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(512, (3, 3), activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(512, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(512, (3, 3), activation='relu'))
model.add(ZeroPadding2D((1,1)))
model.add(Conv2D(512, (3, 3), activation='relu'))
model.add(MaxPooling2D((2,2), strides=(2,2)))
model.add(Flatten())
#top layer of the VGG net
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1000, activation='softmax'))
if weights_path:
model.load_weights(weights_path)
return model

使用 VGG-16 网络识别猫

现在让我们测试一张图片!

im = cv2.resize(cv2.imread('cat.jpg'), (224, 224)).astype(np.float32)
im = im.transpose((2,0,1))
im = np.expand_dims(im, axis=0)

# Test pretrained model
model = VGG_16('/Users/gulli/Keras/codeBook/code/data/vgg16_weights.h5')
optimizer = SGD()
model.compile(optimizer=optimizer, loss='categorical_crossentropy')
out = model.predict(im)
print np.argmax(out)

当代码执行时,返回类别285,对应(更多信息请参阅:gist.github.com/yrevar/942d3a0ac09ec9e5eb3a)埃及猫:

使用 Keras 内置的 VGG-16 网络模块

Keras 应用程序是预构建和预训练的深度学习模型。权重在实例化模型时自动下载并存储在~/.keras/models/中。使用内置代码非常简单:

from keras.models import Model
from keras.preprocessing import image
from keras.optimizers import SGD
from keras.applications.vgg16 import VGG16
import matplotlib.pyplot as plt
import numpy as np
import cv2

# prebuild model with pre-trained weights on imagenet
model = VGG16(weights='imagenet', include_top=True)
sgd = SGD(lr=0.1, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(optimizer=sgd, loss='categorical_crossentropy')

# resize into VGG16 trained images' format
im = cv2.resize(cv2.imread('steam-locomotive.jpg'), (224, 224))
im = np.expand_dims(im, axis=0)

# predict
out = model.predict(im)
plt.plot(out.ravel())
plt.show()
print np.argmax(out)
#this should print 820 for steaming train

现在,让我们考虑一列火车:

它就像我祖父驾驶过的那种。如果我们运行代码,我们得到结果820,这是蒸汽火车的 ImageNet 代码。同样重要的是,所有其他类别的支持非常弱,如下图所示:

总结这一部分时,请注意,VGG-16 只是 Keras 中预构建的模块之一。Keras 模型的完整预训练模型列表可以在此处找到:keras.io/applications/

循环使用预构建的深度学习模型进行特征提取

一个非常简单的想法是使用 VGG-16,更一般地说,使用 DCNN 进行特征提取。此代码通过从特定层提取特征来实现该想法:

from keras.applications.vgg16 import VGG16
from keras.models import Model
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np

# pre-built and pre-trained deep learning VGG16 model
base_model = VGG16(weights='imagenet', include_top=True)
for i, layer in enumerate(base_model.layers):
     print (i, layer.name, layer.output_shape)

# extract features from block4_pool block
model =
Model(input=base_model.input, output=base_model.get_layer('block4_pool').output)
img_path = 'cat.jpg'
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

# get the features from this block
features = model.predict(x)

现在你可能会想,为什么我们要从 DCNN 的中间层提取特征。关键的直觉是,当网络学习将图像分类到不同类别时,每一层都会学习识别做最终分类所需的特征。较低层识别较低阶的特征,如颜色和边缘,而较高层则将这些较低阶的特征组合成较高阶的特征,如形状或物体。因此,中间层具有从图像中提取重要特征的能力,这些特征更有可能帮助进行不同种类的分类。这有多个优势。首先,我们可以依赖公开可用的大规模训练,并将这种学习迁移到新的领域。其次,我们可以节省时间,避免进行昂贵的大规模训练。第三,即使我们没有大量的训练样本,也能提供合理的解决方案。我们还可以为当前任务提供一个良好的起始网络形状,而不必盲目猜测。

用于迁移学习的非常深的 Inception-v3 网络

迁移学习是一种非常强大的深度学习技术,在不同领域有着更多的应用。其直觉非常简单,可以通过一个类比来解释。假设你想学习一门新语言,比如西班牙语,那么从你已经掌握的另一种语言(如英语)开始可能会很有帮助。

按照这个思路,计算机视觉研究人员现在通常使用预训练的 CNN 来为新任务生成表示,其中数据集可能不足以从头训练整个 CNN。另一个常用的策略是采用预训练的 ImageNet 网络,然后对整个网络进行微调,以适应新的任务。

Inception-v3 网络是 Google 开发的一个非常深的卷积神经网络。Keras 实现了下面图示的完整网络,并且它已经在 ImageNet 上进行了预训练。该模型的默认输入大小是 299 x 299,且有三个通道:

这个框架示例的灵感来自于以下方案:keras.io/applications/。我们假设在一个与 ImageNet 不同的领域中有一个训练数据集DD的输入有 1,024 个特征,输出有 200 个类别。让我们看看一个代码片段:

from keras.applications.inception_v3 import InceptionV3
from keras.preprocessing import image
from keras.models import Model
from keras.layers import Dense, GlobalAveragePooling2D
from keras import backend as K

# create the base pre-trained model
base_model = InceptionV3(weights='imagenet', include_top=False)

我们使用经过训练的 Inception-v3 模型;我们不包括顶层模型,因为我们想在D上进行微调。顶层是一个具有 1,024 个输入的全连接层,最后的输出层是一个 softmax 全连接层,输出 200 个类别。x = GlobalAveragePooling2D()(x)用于将输入转换为适合全连接层处理的正确形状。实际上,base_model.output张量的形状为dim_ordering="th"时是*(samples, channels, rows, cols),或者dim_ordering="tf"时是(samples, rows, cols, channels),但全连接层需要的是(samples, channels),而GlobalAveragePooling2D会对(rows, cols)*进行平均化。因此,如果查看最后四层(include_top=True时),你会看到这些形状:

# layer.name, layer.input_shape, layer.output_shape
('mixed10', [(None, 8, 8, 320), (None, 8, 8, 768), (None, 8, 8, 768), (None, 8, 8, 192)], (None, 8, 8, 2048))
('avg_pool', (None, 8, 8, 2048), (None, 1, 1, 2048))
('flatten', (None, 1, 1, 2048), (None, 2048))
('predictions', (None, 2048), (None, 1000))

当你设置include_top=False时,你正在移除最后三层并暴露出mixed10层,因此GlobalAveragePooling2D层将*(None, 8, 8, 2048)转换为(None, 2048),其中(None, 2048)张量中的每个元素都是(None, 8, 8, 2048)张量中对应(8, 8)*子张量的平均值:

*# add a global spatial average pooling layer* x = base_model.output
x = GlobalAveragePooling2D()(x)*# let's add a fully-connected layer as first layer* x = Dense(1024, activation='relu')(x)*# and a logistic layer with 200 classes as last layer* predictions = Dense(200, activation='softmax')(x)*# model to train* model = Model(input=base_model.input, output=predictions)

所有的卷积层都已预训练,因此在训练完整模型时我们会冻结它们:

*# that is, freeze all convolutional InceptionV3 layers* for layer in base_model.layers: layer.trainable = False

然后我们对模型进行编译并训练几个 epoch,以便训练顶层:

*# compile the model (should be done *after* setting layers to non-trainable)* model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

*# train the model on the new data for a few epochs* model.fit_generator(...)

然后我们冻结 Inception 模型的顶层,并对某些 Inception 层进行微调。在这个例子中,我们决定冻结前 172 层(这是一个需要调整的超参数):

*# we chose to train the top 2 inception blocks, that is, we will freeze* *# the first 172 layers and unfreeze the rest:* for layer in 
model.layers[:172]: layer.trainable = False for layer in 
model.layers[172:]: layer.trainable = True

然后我们重新编译模型以进行微调优化。我们需要重新编译模型,以使这些修改生效:

*# we use SGD with a low learning rate* from keras.optimizers
import SGD
model.compile(optimizer=SGD(lr=0.0001, momentum=0.9), loss='categorical_crossentropy')

*# we train our model again (this time fine-tuning the top 2 inception blocks)* *# alongside the top Dense layers* model.fit_generator(...)

现在我们有了一个新的深度网络,它重用了标准的 Inception-v3 网络,但通过迁移学习在新的领域D上进行了训练。当然,为了获得良好的准确性,有很多参数需要微调。然而,我们现在通过迁移学习重用了一个非常大的预训练网络作为起点。通过这样做,我们可以节省不需要在自己的机器上进行训练,而是重用 Keras 中已经可用的资源。

总结

在这一章中,我们学习了如何使用深度学习卷积网络(ConvNets)高精度识别 MNIST 手写字符。接着,我们使用 CIFAR 10 数据集构建了一个 10 个类别的深度学习分类器,并使用 ImageNet 数据集构建了一个 1,000 个类别的准确分类器。此外,我们还研究了如何使用像 VGG16 这样的深度学习大网络以及像 InceptionV3 这样非常深的网络。最后,本章讨论了迁移学习,旨在适应在大型数据集上训练的预构建模型,使其能够在新领域上有效工作。

在下一章中,我们将介绍生成对抗网络,它用于生成看起来像是人类生成的数据的合成数据;我们还将介绍 WaveNet,一种用于高质量重现人类声音和乐器声音的深度神经网络。

第四章:生成对抗网络与 WaveNet

在本章中,我们将讨论生成对抗网络GANs)和 WaveNet。GAN 被 Yann LeCun(深度学习的奠基人之一)称为过去 10 年中机器学习领域最有趣的想法www.quora.com/What-are-some-recent-and-potentially-upcoming-breakthroughs-in-deep-learning)。GAN 能够学习如何生成看起来真实的合成数据。例如,计算机可以学习如何绘画并创造逼真的图像。这个概念最初由 Ian Goodfellow 提出(更多信息请参见:NIPS 2016 教程:生成对抗网络,I. Goodfellow,2016);他曾与蒙特利尔大学、Google Brain 合作,最近也参与了 OpenAI 的工作(openai.com/)。WaveNet 是 Google DeepMind 提出的一种深度生成网络,用于教计算机如何再现人类语音和乐器声音,且质量令人印象深刻。

在本章中,我们将涵盖以下主题:

  • 什么是 GAN?

  • 深度卷积 GAN

  • GAN 的应用

什么是 GAN?

GAN 的关键直觉可以很容易地类比为艺术伪造,即创造那些被错误归功于其他、更著名艺术家的艺术作品的过程 (en.wikipedia.org/wiki/Art)。GAN 同时训练两个神经网络,如下图所示。生成器G(Z)进行伪造,而判别器D(Y)则根据对真实艺术品和复制品的观察判断复制品的真实性。D(Y)接受一个输入,Y,(例如一张图片),并做出判断,判定输入的真实性——一般来说,接近零的值表示真实,接近一的值表示伪造G(Z)从随机噪声Z中接收输入,并通过训练让自己欺骗D,让D误认为G(Z)生成的内容是真的。因此,训练判别器D(Y)的目标是最大化每个真实数据分布中的图像的D(Y),并最小化每个不属于真实数据分布的图像的D(Y)。因此,GD进行的是一场对立的博弈;这就是所谓的对抗训练。请注意,我们以交替的方式训练GD,每个目标都作为损失函数通过梯度下降优化。生成模型学习如何更成功地伪造,而判别模型学习如何更成功地识别伪造。判别器网络(通常是标准的卷积神经网络)试图对输入图像进行分类,判断它是现实的还是生成的。这个新颖的核心思想是通过生成器和判别器进行反向传播,以调整生成器的参数,让生成器学会如何在更多的情境下欺骗判别器。最终,生成器将学会如何生成与真实图像无法区分的伪造图像。

当然,GAN 需要在两方博弈中找到平衡。为了有效学习,需要确保如果一方成功地在一次更新中向下调整,那么另一方也必须在同样的更新中向下调整。想一想!如果造假者每次都能成功地欺骗裁判,那么造假者自己就没有更多的学习空间了。有时,两个玩家最终会达到平衡,但这并不总是能保证,两个玩家可能会继续对抗很长时间。以下图展示了来自双方学习的一个例子:

一些 GAN 应用

我们已经看到生成器学会了如何伪造数据。这意味着它学会了如何创造新的合成数据,这些数据由网络生成,看起来真实且像是由人类创造的。在深入探讨一些 GAN 代码的细节之前,我想分享一篇近期论文的结果:StackGAN: 文本到照片级图像合成的堆叠生成对抗网络,作者:Han Zhang、Tao Xu、Hongsheng Li、Shaoting Zhang、Xiaolei Huang、Xiaogang Wang 和 Dimitris Metaxas(代码可在线获取:github.com/hanzhanggit/StackGAN)。

在这里,使用 GAN 从文本描述合成伪造图像。结果令人印象深刻。第一列是测试集中的真实图像,剩下的列包含由 StackGAN 的 Stage-I 和 Stage-II 根据相同的文本描述生成的图像。更多例子可在 YouTube 查看(www.youtube.com/watch?v=SuRyL5vhCIM&feature=youtu.be):

现在让我们看看 GAN 是如何学会伪造MNIST 数据集的。在这种情况下,生成器和判别器网络结合了 GAN 和 ConvNets(更多信息请参考:无监督表示学习与深度卷积生成对抗网络,A. Radford、L. Metz 和 S. Chintala,arXiv: 1511.06434,2015 年)用于生成器和判别器网络。一开始,生成器什么都没有生成,但经过几次迭代后,合成的伪造数字逐渐变得越来越清晰。在下图中,面板按训练轮次递增排序,你可以看到面板之间质量的提升:

以下图像展示了随着迭代次数的增加,伪造的手写数字的变化:

以下图像展示了伪造的手写数字,计算结果与原始几乎无法区分:

GAN 最酷的用途之一就是对生成器的向量Z中的人脸进行算术操作。换句话说,如果我们停留在合成伪造图像的空间中,就能看到像这样的效果:

[微笑的女人] - [中立的女人] + [中立的男人] = [微笑的男人]

或者像这样:

[戴眼镜的男人] - [没有眼镜的男人] + [没有眼镜的女人] = [戴眼镜的女人]

下一张图来自文章 无监督表示学习与深度卷积生成对抗网络,作者:A. Radford、L. Metz 和 S. Chintala,arXiv: 1511.06434,2015 年 11 月:

深度卷积生成对抗网络

深度卷积生成对抗网络DCGAN)在论文中介绍:《使用深度卷积生成对抗网络进行无监督表示学习》,作者 A. Radford, L. Metz 和 S. Chintala,arXiv: 1511.06434, 2015。生成器使用一个 100 维的均匀分布空间Z,然后通过一系列卷积操作将其投影到一个更小的空间中。下图展示了一个示例:

一个 DCGAN 生成器可以通过以下 Keras 代码进行描述;它也被一个实现所描述,具体请参见:github.com/jacobgil/keras-dcgan

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((128, 7, 7), input_shape=(128*7*7,)))
    model.add(UpSampling2D(size=(2, 2)))
    model.add(Convolution2D(64, 5, 5, border_mode='same'))
    model.add(Activation('tanh'))
    model.add(UpSampling2D(size=(2, 2)))
    model.add(Convolution2D(1, 5, 5, border_mode='same'))
    model.add(Activation('tanh'))
    return model

请注意,代码使用的是 Keras 1.x 的语法。然而,由于 Keras 的遗留接口,它也可以在 Keras 2.0 中运行。在这种情况下,系统会报告一些警告,如下图所示:

现在让我们看一下代码。第一层全连接层接收一个 100 维的向量作为输入,并使用激活函数tanh输出 1,024 维。我们假设输入是从均匀分布中采样,范围为*[-1, 1]。接下来的全连接层通过批量归一化(更多信息请参见S. Ioffe 和 C. Szegedy 的《批量归一化:通过减少内部协变量偏移加速深度网络训练》,arXiv: 1502.03167, 2014*)生成 128 x 7 x 7 的输出数据,批量归一化是一种通过将每个单元的输入归一化为零均值和单位方差来帮助稳定学习的技术。批量归一化已被实验证明在许多情况下可以加速训练,减少初始化不良的问题,并且通常能产生更精确的结果。此外,还有一个Reshape()模块,它将数据转换为 127 x 7 x 7(127 个通道,7 的宽度,7 的高度),dim_ordering设置为tf,以及一个UpSampling()模块,它将每个数据重复为 2 x 2 的正方形。然后,我们有一个卷积层,在 5 x 5 卷积核上生成 64 个滤波器,激活函数为tanh,接着是一个新的UpSampling()模块和一个最终的卷积层,具有一个滤波器,使用 5 x 5 卷积核,激活函数为tanh。请注意,该卷积神经网络没有池化操作。判别器可以通过以下代码进行描述:

def discriminator_model():
    model = Sequential()
    model.add(Convolution2D(64, 5, 5, border_mode='same',
    input_shape=(1, 28, 28)))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Convolution2D(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

这段代码接收一个标准的 MNIST 图像,形状为 (1, 28, 28),并应用一个 64 个 5 x 5 滤波器的卷积,激活函数为 tanh。随后进行一个 2 x 2 的最大池化操作,接着再进行一次卷积和最大池化操作。最后两步是全连接层,其中最后一步是伪造的预测,只有一个神经元,并使用 sigmoid 激活函数。对于选定的训练轮数,生成器和判别器依次使用 binary_crossentropy 作为损失函数进行训练。在每个训练轮次中,生成器会做出若干预测(例如,它生成伪造的 MNIST 图像),而判别器则尝试在将预测与真实的 MNIST 图像混合后进行学习。经过 32 个轮次,生成器学会了伪造这一组手写数字。没有人编程让机器写字,但它已经学会了如何写出与人类手写的数字无法区分的数字。请注意,训练 GAN 可能非常困难,因为需要在两个参与者之间找到平衡。如果你对这个话题感兴趣,我建议你查看一些从业者收集的技巧系列(github.com/soumith/ganhacks):

Keras 对抗性 GAN 用于伪造 MNIST

Keras 对抗性(github.com/bstriner/keras-adversarial)是一个用于构建 GAN 的开源 Python 包,由 Ben Striner 开发(github.com/bstrinergithub.com/bstriner/keras-adversarial/blob/master/LICENSE.txt)。由于 Keras 最近刚刚升级到 2.0,我建议你下载最新的 Keras 对抗性包:

git clone --depth=50 --branch=master https://github.com/bstriner/keras-adversarial.git

并安装 setup.py

python setup.py install

请注意,Keras 2.0 的兼容性已在此问题中跟踪:github.com/bstriner/keras-adversarial/issues/11

如果生成器G和判别器D基于相同的模型,M,那么它们可以合并为一个对抗模型;它使用相同的输入,M,但为GD分开目标和度量。该库有以下 API 调用:

adversarial_model = AdversarialModel(base_model=M,
    player_params=[generator.trainable_weights, discriminator.trainable_weights],
    player_names=["generator", "discriminator"])

如果生成器G和判别器D基于两个不同的模型,则可以使用此 API 调用:

adversarial_model = AdversarialModel(player_models=[gan_g, gan_d],
    player_params=[generator.trainable_weights, discriminator.trainable_weights],
    player_names=["generator", "discriminator"])

让我们看一个关于 MNIST 的计算示例:

import matplotlib as mpl
# This line allows mpl to run with no DISPLAY defined
mpl.use('Agg')

让我们看看开源代码(github.com/bstriner/keras-adversarial/blob/master/examples/example_gan_convolutional.py)。注意,这段代码使用的是 Keras 1.x 的语法,但得益于legacy.py中包含的便捷实用函数集,它也能在 Keras 2.x 之上运行。legacy.py的代码在附录、结论中有报告,且可以在github.com/bstriner/keras-adversarial/blob/master/keras_adversarial/legacy.py找到。

首先,开源示例导入了一些模块。除了 LeakyReLU(ReLU 的一个特殊版本,当单元不活跃时允许一个小的梯度)之外,我们之前都见过这些模块。实验表明,LeakyReLU 可以在多种情况下提高 GAN 的性能(有关更多信息,请参阅:Empirical Evaluation of Rectified Activations in Convolutional Network,B. Xu, N. Wang, T. Chen 和 M. Li,arXiv:1505.00853,2014):

from keras.layers import Dense, Reshape, Flatten, Dropout, LeakyReLU,
    Input, Activation, BatchNormalization
from keras.models import Sequential, Model
from keras.layers.convolutional import Convolution2D, UpSampling2D
from keras.optimizers import Adam
from keras.regularizers import l1, l1l2
from keras.datasets import mnist

import pandas as pd
import numpy as np

然后,导入 GAN 的特定模块:

from keras_adversarial import AdversarialModel, ImageGridCallback,
    simple_gan, gan_targets
from keras_adversarial import AdversarialOptimizerSimultaneous,
    normal_latent_sampling, AdversarialOptimizerAlternating
from image_utils import dim_ordering_fix, dim_ordering_input,
    dim_ordering_reshape, dim_ordering_unfix

对抗性模型用于多玩家游戏。给定一个包含n个目标和k个玩家的基础模型,创建一个包含nk*个目标的模型,其中每个玩家在该玩家的目标上优化损失。此外,simple_gan生成一个具有给定gan_targets的 GAN。注意,在库中,生成器和判别器的标签是相反的;直观地说,这对 GAN 来说是一种标准做法:

def gan_targets(n):
    """
    Standard training targets [generator_fake, generator_real, discriminator_fake,     
    discriminator_real] = [1, 0, 0, 1]
    :param n: number of samples
    :return: array of targets
    """
    generator_fake = np.ones((n, 1))
    generator_real = np.zeros((n, 1))
    discriminator_fake = np.zeros((n, 1))
    discriminator_real = np.ones((n, 1))
    return [generator_fake, generator_real, discriminator_fake, discriminator_real]

这个示例以类似于我们之前看到的方式定义了生成器。然而,在这种情况下,我们使用了函数式语法——我们管道中的每个模块只是作为输入传递给下一个模块。因此,第一个模块是密集层,通过使用glorot_normal进行初始化。此初始化使用了通过节点的输入和输出总和缩放的高斯噪声。所有其他模块都使用相同类型的初始化。BatchNormlization函数中的mode=2参数根据每批次的统计数据进行特征标准化。从实验结果来看,这能产生更好的效果:

def model_generator():
    nch = 256
    g_input = Input(shape=[100])
    H = Dense(nch * 14 * 14, init='glorot_normal')(g_input)
    H = BatchNormalization(mode=2)(H)
    H = Activation('relu')(H)
    H = dim_ordering_reshape(nch, 14)(H)
    H = UpSampling2D(size=(2, 2))(H)
    H = Convolution2D(int(nch / 2), 3, 3, border_mode='same', 
        init='glorot_uniform')(H)
    H = BatchNormalization(mode=2, axis=1)(H)
    H = Activation('relu')(H)
    H = Convolution2D(int(nch / 4), 3, 3, border_mode='same', 
        init='glorot_uniform')(H)
    H = BatchNormalization(mode=2, axis=1)(H)
    H = Activation('relu')(H)
    H = Convolution2D(1, 1, 1, border_mode='same', init='glorot_uniform')(H)
    g_V = Activation('sigmoid')(H)
    return Model(g_input, g_V)

判别器与本章之前定义的非常相似。唯一的主要区别是采用了LeakyReLU

def model_discriminator(input_shape=(1, 28, 28), dropout_rate=0.5):
    d_input = dim_ordering_input(input_shape, name="input_x")
    nch = 512
    H = Convolution2D(int(nch / 2), 5, 5, subsample=(2, 2),
        border_mode='same', activation='relu')(d_input)
    H = LeakyReLU(0.2)(H)
    H = Dropout(dropout_rate)(H)
    H = Convolution2D(nch, 5, 5, subsample=(2, 2),
        border_mode='same', activation='relu')(H)
    H = LeakyReLU(0.2)(H)
    H = Dropout(dropout_rate)(H)
    H = Flatten()(H)
    H = Dense(int(nch / 2))(H)
    H = LeakyReLU(0.2)(H)
    H = Dropout(dropout_rate)(H)
    d_V = Dense(1, activation='sigmoid')(H)
    return Model(d_input, d_V)

然后,定义了两个简单的函数来加载和标准化 MNIST 数据:

def mnist_process(x):
    x = x.astype(np.float32) / 255.0
    return x

def mnist_data():
    (xtrain, ytrain), (xtest, ytest) = mnist.load_data()
    return mnist_process(xtrain), mnist_process(xtest)

下一步,GAN 被定义为生成器和判别器的组合,形成一个联合 GAN 模型。注意,权重通过normal_latent_sampling进行初始化,它从正态高斯分布中采样:

if __name__ == "__main__":
    # z in R¹⁰⁰
    latent_dim = 100
    # x in R^{28x28}
    input_shape = (1, 28, 28)
    # generator (z -> x)
    generator = model_generator()
    # discriminator (x -> y)
    discriminator = model_discriminator(input_shape=input_shape)
    # gan (x - > yfake, yreal), z generated on GPU
    gan = simple_gan(generator, discriminator, normal_latent_sampling((latent_dim,)))
    # print summary of models
    generator.summary()
    discriminator.summary()
    gan.summary()

之后,示例创建了我们的 GAN,并使用Adam优化器编译训练好的模型,binary_crossentropy作为损失函数:

# build adversarial model
model = AdversarialModel(base_model=gan,
    player_params=[generator.trainable_weights, discriminator.trainable_weights],
    player_names=["generator", "discriminator"])
model.adversarial_compile(adversarial_optimizer=AdversarialOptimizerSimultaneous(),
    player_optimizers=[Adam(1e-4, decay=1e-4), Adam(1e-3, decay=1e-4)],
    loss='binary_crossentropy')

用于创建看起来像真实图像的新图像的生成器被定义。每个 epoch 将在训练期间生成一张看起来像原始图像的伪造图像:

def generator_sampler():
    zsamples = np.random.normal(size=(10 * 10, latent_dim))
    gen = dim_ordering_unfix(generator.predict(zsamples))
    return gen.reshape((10, 10, 28, 28))

generator_cb = ImageGridCallback(
    "output/gan_convolutional/epoch-{:03d}.png",generator_sampler)
xtrain, xtest = mnist_data()
xtrain = dim_ordering_fix(xtrain.reshape((-1, 1, 28, 28)))
xtest = dim_ordering_fix(xtest.reshape((-1, 1, 28, 28)))
y = gan_targets(xtrain.shape[0])
ytest = gan_targets(xtest.shape[0])
history = model.fit(x=xtrain, y=y,
validation_data=(xtest, ytest), callbacks=[generator_cb], nb_epoch=100,
    batch_size=32)
df = pd.DataFrame(history.history)
df.to_csv("output/gan_convolutional/history.csv")
generator.save("output/gan_convolutional/generator.h5")
discriminator.save("output/gan_convolutional/discriminator.h5")

请注意,dim_ordering_unfix是一个实用函数,用于支持在image_utils.py中定义的不同图像排序,具体如下:

def dim_ordering_fix(x):
    if K.image_dim_ordering() == 'th':
        return x
    else:
        return np.transpose(x, (0, 2, 3, 1))

现在,让我们运行代码,查看生成器和判别器的损失情况。在下面的截图中,我们看到了判别器和生成器的网络输出:

以下截图显示了用于训练和验证的样本数量:

在经过 5-6 次迭代后,我们已经生成了可以接受的人工图像,计算机已经学会如何重现手写字符,如下图所示:

用于伪造 CIFAR 的 Keras 对抗 GAN

现在我们可以使用 GAN 方法学习如何伪造 CIFAR-10,并创建看起来真实的合成图像。让我们看看开源代码(github.com/bstriner/keras-adversarial/blob/master/examples/example_gan_cifar10.py)。再次注意,它使用的是 Keras 1.x 的语法,但得益于legacy.py中包含的一套方便的实用函数,它也能在 Keras 2.x 上运行(github.com/bstriner/keras-adversarial/blob/master/keras_adversarial/legacy.py)。首先,开源示例导入了一些包:

import matplotlib as mpl
# This line allows mpl to run with no DISPLAY defined
mpl.use('Agg')
import pandas as pd
import numpy as np
import os
from keras.layers import Dense, Reshape, Flatten, Dropout, LeakyReLU, 
    Activation, BatchNormalization, SpatialDropout2D
from keras.layers.convolutional import Convolution2D, UpSampling2D, 
    MaxPooling2D, AveragePooling2D
from keras.models import Sequential, Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard
from keras.regularizers import l1l2
from keras_adversarial import AdversarialModel, ImageGridCallback, 
    simple_gan, gan_targets
from keras_adversarial import AdversarialOptimizerSimultaneous, 
    normal_latent_sampling, fix_names
import keras.backend as K
from cifar10_utils import cifar10_data
from image_utils import dim_ordering_fix, dim_ordering_unfix, 
    dim_ordering_shape

接下来,定义了一个生成器,它使用卷积与l1l2正则化、批归一化和上采样的组合。请注意,axis=1表示首先对张量的维度进行归一化,mode=0表示采用特征归一化。这个特定的网络是许多精细调整实验的结果,但它本质上仍然是一个卷积 2D 和上采样操作的序列,在开始时使用Dense模块,结束时使用sigmoid。此外,每个卷积使用LeakyReLU激活函数和BatchNormalization

def model_generator():
    model = Sequential()
    nch = 256
    reg = lambda: l1l2(l1=1e-7, l2=1e-7)
    h = 5
    model.add(Dense(input_dim=100, output_dim=nch * 4 * 4, W_regularizer=reg()))
    model.add(BatchNormalization(mode=0))
    model.add(Reshape(dim_ordering_shape((nch, 4, 4))))
    model.add(Convolution2D(nch/2, h, h, border_mode='same', W_regularizer=reg()))
    model.add(BatchNormalization(mode=0, axis=1))
    model.add(LeakyReLU(0.2))
    model.add(UpSampling2D(size=(2, 2)))
    model.add(Convolution2D(nch / 2, h, h, border_mode='same', W_regularizer=reg()))
    model.add(BatchNormalization(mode=0, axis=1))
    model.add(LeakyReLU(0.2))
    model.add(UpSampling2D(size=(2, 2)))
    model.add(Convolution2D(nch / 4, h, h, border_mode='same', W_regularizer=reg()))
    model.add(BatchNormalization(mode=0, axis=1))
    model.add(LeakyReLU(0.2))
    model.add(UpSampling2D(size=(2, 2)))
    model.add(Convolution2D(3, h, h, border_mode='same', W_regularizer=reg()))
    model.add(Activation('sigmoid'))
    return model

然后,定义了一个判别器。再次,我们有一系列的二维卷积操作,在这种情况下我们采用SpatialDropout2D,它会丢弃整个 2D 特征图而不是单个元素。我们还使用MaxPooling2DAveragePooling2D,原因类似:

def model_discriminator():
    nch = 256
    h = 5
    reg = lambda: l1l2(l1=1e-7, l2=1e-7)
    c1 = Convolution2D(nch / 4, h, h, border_mode='same', W_regularizer=reg(),
    input_shape=dim_ordering_shape((3, 32, 32)))
    c2 = Convolution2D(nch / 2, h, h, border_mode='same', W_regularizer=reg())
    c3 = Convolution2D(nch, h, h, border_mode='same', W_regularizer=reg())
    c4 = Convolution2D(1, h, h, border_mode='same', W_regularizer=reg())
    def m(dropout):
        model = Sequential()
        model.add(c1)
        model.add(SpatialDropout2D(dropout))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(LeakyReLU(0.2))
        model.add(c2)
        model.add(SpatialDropout2D(dropout))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(LeakyReLU(0.2))
        model.add(c3)
        model.add(SpatialDropout2D(dropout))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(LeakyReLU(0.2))
        model.add(c4)
        model.add(AveragePooling2D(pool_size=(4, 4), border_mode='valid'))
        model.add(Flatten())
        model.add(Activation('sigmoid'))
        return model
    return m

现在可以生成适当的 GAN。以下函数接受多个输入,包括生成器、判别器、潜在维度的数量和 GAN 目标:

def example_gan(adversarial_optimizer, path, opt_g, opt_d, nb_epoch, generator,
        discriminator, latent_dim, targets=gan_targets, loss='binary_crossentropy'):
    csvpath = os.path.join(path, "history.csv")
    if os.path.exists(csvpath):
        print("Already exists: {}".format(csvpath))
    return

然后创建了两个 GAN,一个带有 dropout,另一个没有 dropout 用于判别器:

print("Training: {}".format(csvpath))
# gan (x - > yfake, yreal), z is gaussian generated on GPU
# can also experiment with uniform_latent_sampling
d_g = discriminator(0)
d_d = discriminator(0.5)
generator.summary()
d_d.summary()
gan_g = simple_gan(generator, d_g, None)
gan_d = simple_gan(generator, d_d, None)
x = gan_g.inputs[1]
z = normal_latent_sampling((latent_dim,))(x)
# eliminate z from inputs
gan_g = Model([x], fix_names(gan_g([z, x]), gan_g.output_names))
gan_d = Model([x], fix_names(gan_d([z, x]), gan_d.output_names))

这两个 GAN 现在被合并为一个具有独立权重的对抗性模型,模型随后被编译:

# build adversarial model
model = AdversarialModel(player_models=[gan_g, gan_d],
    player_params=[generator.trainable_weights, d_d.trainable_weights],
    player_names=["generator", "discriminator"])
model.adversarial_compile(adversarial_optimizer=adversarial_optimizer,
    player_optimizers=[opt_g, opt_d], loss=loss)

接下来,有一个简单的回调,用于采样图像,并打印定义了ImageGridCallback方法的文件:

# create callback to generate images
zsamples = np.random.normal(size=(10 * 10, latent_dim))
def generator_sampler():
    xpred = dim_ordering_unfix(generator.predict(zsamples)).transpose((0, 2, 3, 1))
    return xpred.reshape((10, 10) + xpred.shape[1:])
generator_cb =
    ImageGridCallback(os.path.join(path, "epoch-{:03d}.png"),
    generator_sampler, cmap=None)

现在,CIFAR-10 数据已经加载并且模型已拟合。如果后端是 TensorFlow,则损失信息将保存在 TensorBoard 中,以检查损失如何随着时间的推移减少。历史数据也方便地保存在 CVS 格式中,模型的权重也存储在h5格式中:

# train model
xtrain, xtest = cifar10_data()
y = targets(xtrain.shape[0])
ytest = targets(xtest.shape[0])
callbacks = [generator_cb]
if K.backend() == "tensorflow":
    callbacks.append(TensorBoard(log_dir=os.path.join(path, 'logs'),
        histogram_freq=0, write_graph=True, write_images=True))
history = model.fit(x=dim_ordering_fix(xtrain),y=y,
    validation_data=(dim_ordering_fix(xtest), ytest),
    callbacks=callbacks, nb_epoch=nb_epoch,
    batch_size=32)
# save history to CSV
df = pd.DataFrame(history.history)
df.to_csv(csvpath)
# save models
generator.save(os.path.join(path, "generator.h5"))
d_d.save(os.path.join(path, "discriminator.h5"))

最后,整个 GAN 可以运行。生成器从一个具有 100 个潜在维度的空间中采样,我们为这两个 GAN 使用了Adam优化器:

def main():
    # z in R¹⁰⁰
    latent_dim = 100
    # x in R^{28x28}
    # generator (z -> x)
    generator = model_generator()
    # discriminator (x -> y)
    discriminator = model_discriminator()
    example_gan(AdversarialOptimizerSimultaneous(), "output/gan-cifar10",
        opt_g=Adam(1e-4, decay=1e-5),
        opt_d=Adam(1e-3, decay=1e-5),
        nb_epoch=100, generator=generator, discriminator=discriminator,
        latent_dim=latent_dim)
if __name__ == "__main__":
main()

为了完整查看开源代码,我们需要包含一些简单的工具函数来存储图像的网格:

from matplotlib import pyplot as plt, gridspec
import os

def write_image_grid(filepath, imgs, figsize=None, cmap='gray'):
    directory = os.path.dirname(filepath)
    if not os.path.exists(directory):
        os.makedirs(directory)
    fig = create_image_grid(imgs, figsize, cmap=cmap)
    fig.savefig(filepath)
    plt.close(fig)

def create_image_grid(imgs, figsize=None, cmap='gray'):
    n = imgs.shape[0]
    m = imgs.shape[1]
    if figsize is None:
        figsize=(n,m)
    fig = plt.figure(figsize=figsize)
    gs1 = gridspec.GridSpec(n, m)
    gs1.update(wspace=0.025, hspace=0.025) # set the spacing between axes.
    for i in range(n):
        for j in range(m):
            ax = plt.subplot(gs1[i, j])
            img = imgs[i, j, :]
    ax.imshow(img, cmap=cmap)
    ax.axis('off')
    return fig

此外,我们需要一些工具方法来处理不同的图像排序(例如,Theano 或 TensorFlow):

import keras.backend as K
import numpy as np
from keras.layers import Input, Reshape

def dim_ordering_fix(x):
    if K.image_dim_ordering() == 'th':
        return x
    else:
        return np.transpose(x, (0, 2, 3, 1))

def dim_ordering_unfix(x):
    if K.image_dim_ordering() == 'th':
        return x
    else:
        return np.transpose(x, (0, 3, 1, 2))

def dim_ordering_shape(input_shape):
    if K.image_dim_ordering() == 'th':
        return input_shape
    else:
        return (input_shape[1], input_shape[2], input_shape[0])

def dim_ordering_input(input_shape, name):
    if K.image_dim_ordering() == 'th':
        return Input(input_shape, name=name)
    else:
        return Input((input_shape[1], input_shape[2], input_shape[0]), name=name)

def dim_ordering_reshape(k, w, **kwargs):
    if K.image_dim_ordering() == 'th':
        return Reshape((k, w, w), **kwargs)
    else:
        return Reshape((w, w, k), **kwargs)

# One more utility function is used to fix names
def fix_names(outputs, names):
    if not isinstance(outputs, list):
        outputs = [outputs]
    if not isinstance(names, list):
        names = [names]
    return [Activation('linear', name=name)(output) 
        for output, name in zip(outputs, names)]

以下截图显示了定义的网络的转储:

如果我们运行开源代码,第一次迭代将生成不真实的图像。然而,在经过 99 次迭代后,网络将学会伪造看起来像真实 CIFAR-10 图像的图像,如下所示:

在接下来的图像中,我们看到右边是真实的 CIFAR-10 图像,左边是伪造的图像:

伪造的图像真实的 CIFAR-10 图像

WaveNet——一种用于学习如何生成音频的生成模型

WaveNet 是一种深度生成模型,用于生成原始音频波形。这项突破性的技术是由 Google DeepMind(deepmind.com/)提出的,用于教用户如何与计算机对话。结果令人印象深刻,您可以在网上找到合成语音的例子,其中计算机学会了如何用名人如马特·达蒙的声音进行对话。那么,您可能会想,为什么学习合成音频如此困难呢?好吧,我们听到的每一个数字声音都是基于每秒 16,000 个样本(有时为 48,000 或更多),而构建一个预测模型,基于所有先前的样本学习如何重现一个样本,是一个非常困难的挑战。尽管如此,实验表明,WaveNet 已经改进了当前最先进的文本到语音TTS)系统,在美式英语和普通话中,将与人声的差距缩小了 50%。更酷的是,DeepMind 证明 WaveNet 还可以用来教计算机生成钢琴音乐等乐器的声音。现在是时候介绍一些定义了。TTS 系统通常分为两类:

  • 连续语音合成(Concatenative TTS):这是首先记忆单个语音片段,然后在需要重现语音时重新组合这些片段的方法。然而,这种方法无法扩展,因为只能重现已记忆的语音片段,而不能在不重新记忆的情况下重现新的发声者或不同类型的音频。

  • 参数化语音合成(Parametric TTS):这是创建模型来存储待合成音频的所有特征的过程。在 WaveNet 之前,使用参数化语音合成生成的音频比连续语音合成的音频自然度低。WaveNet 通过直接建模音频声音的生成改进了技术水平,而不是使用过去使用的中间信号处理算法。

原则上,WaveNet 可以看作是一堆 1 维卷积层(我们已经在第三章,深度学习与 ConvNets中看到了 2 维卷积用于图像),具有恒定的步幅为 1 且没有池化层。注意,输入和输出在构造上具有相同的维度,因此 ConvNet 非常适合模拟音频等序列数据。然而,已经表明,为了达到输出神经元的大接受域大小(记住神经元层的接收域是前一层提供输入的横截面),需要使用大量大滤波器或者昂贵地增加网络的深度。因此,纯粹的 ConvNets 在学习如何合成音频方面并不那么有效。WaveNet 背后的关键直觉是扩张因果卷积(更多信息请参考文章:Multi-Scale Context Aggregation by Dilated Convolutions,作者 Fisher Yu, Vladlen Koltun, 2016,可在www.semanticscholar.org/paper/Multi-Scale-Context-Aggregation-by-Dilated-Yu-Koltun/420c46d7cafcb841309f02ad04cf51cb1f190a48获取)或者有时称为空洞卷积(atrous是法语表达à trous的词语,意思是带孔的,因此空洞卷积是一种在应用卷积层的滤波器时跳过某些输入值的方式)。例如,在一维情况下,大小为 3 的滤波器w,带有膨胀率1将计算以下和:

由于引入空洞这一简单思想,能够堆叠多个膨胀卷积层,并使滤波器指数增长,从而在不需要过深网络的情况下学习长距离输入依赖关系。因此,WaveNet 是一种卷积神经网络(ConvNet),其中卷积层具有不同的膨胀因子,允许感受野随着深度的增加而指数增长,从而高效地覆盖成千上万个音频时间步长。在训练时,输入是来自人类发音者的声音。波形被量化为固定的整数范围。WaveNet 定义了一个初始卷积层,只访问当前和先前的输入。然后是一个堆叠的膨胀卷积网络层,依然只访问当前和先前的输入。最后,有一系列密集层将先前的结果结合起来,后跟一个用于分类输出的 softmax 激活函数。在每一步,网络会预测一个值并将其反馈到输入中,同时计算下一步的预测。损失函数是当前步骤输出和下一步输入之间的交叉熵。由 Bas Veeling 开发的一个 Keras 实现可以在此获取:github.com/basveeling/wavenet,并可以通过 git 轻松安装:

pip install virtualenv
mkdir ~/virtualenvs && cd ~/virtualenvs
virtualenv wavenet
source wavenet/bin/activate
cd ~
git clone https://github.com/basveeling/wavenet.git
cd wavenet
pip install -r requirements.txt

请注意,此代码与 Keras 1.x 兼容,详情请查看 github.com/basveeling/wavenet/issues/29,了解将其迁移到 Keras 2.x 上的进展情况。训练非常简单,但需要大量的计算能力(因此请确保你有良好的 GPU 支持):

$ python wavenet.py with 'data_dir=your_data_dir_name'

在训练后采样网络同样非常简单:

python wavenet.py predict with 'models/[run_folder]/config.json predict_seconds=1'

你可以在网上找到大量的超参数,用于微调我们的训练过程。正如这个内部层的转储所解释的,网络确实非常深。请注意,输入波形被划分为(fragment_length = 1152nb_output_bins = 256),这就是传递到 WaveNet 的张量。WaveNet 以重复的块组织,称为残差块,每个残差块由两个膨胀卷积模块的乘积合并组成(一个使用 sigmoid 激活,另一个使用 tanh 激活),然后是一个合并的卷积求和。请注意,每个膨胀卷积都有逐渐增大的空洞,空洞大小按指数增长(2 ** i),从 1 到 512,如下文所定义:

def residual_block(x):
    original_x = x
    tanh_out = CausalAtrousConvolution1D(nb_filters, 2, atrous_rate=2 ** i,
        border_mode='valid', causal=True, bias=use_bias,
        name='dilated_conv_%d_tanh_s%d' % (2 ** i, s), activation='tanh',
        W_regularizer=l2(res_l2))(x)
    sigm_out = CausalAtrousConvolution1D(nb_filters, 2, atrous_rate=2 ** i,
        border_mode='valid', causal=True, bias=use_bias,
        name='dilated_conv_%d_sigm_s%d' % (2 ** i, s), activation='sigmoid',
        W_regularizer=l2(res_l2))(x)
    x = layers.Merge(mode='mul',
        name='gated_activation_%d_s%d' % (i, s))([tanh_out, sigm_out])
        res_x = layers.Convolution1D(nb_filters, 1, border_mode='same', bias=use_bias,
        W_regularizer=l2(res_l2))(x)
    skip_x = layers.Convolution1D(nb_filters, 1, border_mode='same', bias=use_bias,
        W_regularizer=l2(res_l2))(x)
    res_x = layers.Merge(mode='sum')([original_x, res_x])
    return res_x, skip_x

在残差膨胀块之后,有一系列合并的卷积模块,接着是两个卷积模块,最后是 nb_output_bins 类别中的 softmax 激活函数。完整的网络结构如下:

Layer (type) Output Shape Param # Connected to
====================================================================================================
input_part (InputLayer) (None, 1152, 256) 0
____________________________________________________________________________________________________
initial_causal_conv (CausalAtrou (None, 1152, 256) 131328 input_part[0][0]
____________________________________________________________________________________________________
dilated_conv_1_tanh_s0 (CausalAt (None, 1152, 256) 131072 initial_causal_conv[0][0]
____________________________________________________________________________________________________
dilated_conv_1_sigm_s0 (CausalAt (None, 1152, 256) 131072 initial_causal_conv[0][0]
____________________________________________________________________________________________________
gated_activation_0_s0 (Merge) (None, 1152, 256) 0 dilated_conv_1_tanh_s0[0][0]
dilated_conv_1_sigm_s0[0][0]
______________________________________________________________________
_____________________________
convolution1d_1 (Convolution1D) (None, 1152, 256) 65536 gated_activation_0_s0[0][0]
____________________________________________________________________________________________________
merge_1 (Merge) (None, 1152, 256) 0 initial_causal_conv[0][0]
convolution1d_1[0][0]
____________________________________________________________________________________________________
dilated_conv_2_tanh_s0 (CausalAt (None, 1152, 256) 131072 merge_1[0][0]
____________________________________________________________________________________________________
dilated_conv_2_sigm_s0 (CausalAt (None, 1152, 256) 131072 merge_1[0][0]
____________________________________________________________________________________________________
gated_activation_1_s0 (Merge) (None, 1152, 256) 0 dilated_conv_2_tanh_s0[0][0]
dilated_conv_2_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_3 (Convolution1D) (None, 1152, 256) 65536 gated_activation_1_s0[0][0]
____________________________________________________________________________________________________
merge_2 (Merge) (None, 1152, 256) 0 merge_1[0][0]
convolution1d_3[0][0]
____________________________________________________________________________________________________
dilated_conv_4_tanh_s0 (CausalAt (None, 1152, 256) 131072 merge_2[0][0]
____________________________________________________________________________________________________
dilated_conv_4_sigm_s0 (CausalAt (None, 1152, 256) 131072 merge_2[0][0]
____________________________________________________________________________________________________
gated_activation_2_s0 (Merge) (None, 1152, 256) 0 dilated_conv_4_tanh_s0[0][0]
dilated_conv_4_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_5 (Convolution1D) (None, 1152, 256) 65536 gated_activation_2_s0[0][0]
____________________________________________________________________________________________________
merge_3 (Merge) (None, 1152, 256) 0 merge_2[0][0]
convolution1d_5[0][0]
____________________________________________________________________________________________________
dilated_conv_8_tanh_s0 (CausalAt (None, 1152, 256) 131072 merge_3[0][0]
____________________________________________________________________________________________________
dilated_conv_8_sigm_s0 (CausalAt (None, 1152, 256) 131072 merge_3[0][0]
____________________________________________________________________________________________________
gated_activation_3_s0 (Merge) (None, 1152, 256) 0 dilated_conv_8_tanh_s0[0][0]
dilated_conv_8_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_7 (Convolution1D) (None, 1152, 256) 65536 gated_activation_3_s0[0][0]
____________________________________________________________________________________________________
merge_4 (Merge) (None, 1152, 256) 0 merge_3[0][0]
convolution1d_7[0][0]
____________________________________________________________________________________________________
dilated_conv_16_tanh_s0 (CausalA (None, 1152, 256) 131072 merge_4[0][0]
____________________________________________________________________________________________________
dilated_conv_16_sigm_s0 (CausalA (None, 1152, 256) 131072 merge_4[0][0]
____________________________________________________________________________________________________
gated_activation_4_s0 (Merge) (None, 1152, 256) 0 dilated_conv_16_tanh_s0[0][0]
dilated_conv_16_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_9 (Convolution1D) (None, 1152, 256) 65536 gated_activation_4_s0[0][0]
____________________________________________________________________________________________________
merge_5 (Merge) (None, 1152, 256) 0 merge_4[0][0]
convolution1d_9[0][0]
____________________________________________________________________________________________________
dilated_conv_32_tanh_s0 (CausalA (None, 1152, 256) 131072 merge_5[0][0]
____________________________________________________________________________________________________
dilated_conv_32_sigm_s0 (CausalA (None, 1152, 256) 131072 merge_5[0][0]
____________________________________________________________________________________________________
gated_activation_5_s0 (Merge) (None, 1152, 256) 0 dilated_conv_32_tanh_s0[0][0]
dilated_conv_32_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_11 (Convolution1D) (None, 1152, 256) 65536 gated_activation_5_s0[0][0]
____________________________________________________________________________________________________
merge_6 (Merge) (None, 1152, 256) 0 merge_5[0][0]
convolution1d_11[0][0]
____________________________________________________________________________________________________
dilated_conv_64_tanh_s0 (CausalA (None, 1152, 256) 131072 merge_6[0][0]
____________________________________________________________________________________________________
dilated_conv_64_sigm_s0 (CausalA (None, 1152, 256) 131072 merge_6[0][0]
____________________________________________________________________________________________________
gated_activation_6_s0 (Merge) (None, 1152, 256) 0 dilated_conv_64_tanh_s0[0][0]
dilated_conv_64_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_13 (Convolution1D) (None, 1152, 256) 65536 gated_activation_6_s0[0][0]
____________________________________________________________________________________________________
merge_7 (Merge) (None, 1152, 256) 0 merge_6[0][0]
convolution1d_13[0][0]
____________________________________________________________________________________________________
dilated_conv_128_tanh_s0 (Causal (None, 1152, 256) 131072 merge_7[0][0]
____________________________________________________________________________________________________
dilated_conv_128_sigm_s0 (Causal (None, 1152, 256) 131072 merge_7[0][0]
____________________________________________________________________________________________________
gated_activation_7_s0 (Merge) (None, 1152, 256) 0 dilated_conv_128_tanh_s0[0][0]
dilated_conv_128_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_15 (Convolution1D) (None, 1152, 256) 65536 gated_activation_7_s0[0][0]
____________________________________________________________________________________________________
merge_8 (Merge) (None, 1152, 256) 0 merge_7[0][0]
convolution1d_15[0][0]
____________________________________________________________________________________________________
dilated_conv_256_tanh_s0 (Causal (None, 1152, 256) 131072 merge_8[0][0]
____________________________________________________________________________________________________
dilated_conv_256_sigm_s0 (Causal (None, 1152, 256) 131072 merge_8[0][0]
____________________________________________________________________________________________________
gated_activation_8_s0 (Merge) (None, 1152, 256) 0 dilated_conv_256_tanh_s0[0][0]
dilated_conv_256_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_17 (Convolution1D) (None, 1152, 256) 65536 gated_activation_8_s0[0][0]
____________________________________________________________________________________________________
merge_9 (Merge) (None, 1152, 256) 0 merge_8[0][0]
convolution1d_17[0][0]
____________________________________________________________________________________________________
dilated_conv_512_tanh_s0 (Causal (None, 1152, 256) 131072 merge_9[0][0]
____________________________________________________________________________________________________
dilated_conv_512_sigm_s0 (Causal (None, 1152, 256) 131072 merge_9[0][0]
____________________________________________________________________________________________________
gated_activation_9_s0 (Merge) (None, 1152, 256) 0 dilated_conv_512_tanh_s0[0][0]
dilated_conv_512_sigm_s0[0][0]
____________________________________________________________________________________________________
convolution1d_2 (Convolution1D) (None, 1152, 256) 65536 gated_activation_0_s0[0][0]
____________________________________________________________________________________________________
convolution1d_4 (Convolution1D) (None, 1152, 256) 65536 gated_activation_1_s0[0][0]
____________________________________________________________________________________________________
convolution1d_6 (Convolution1D) (None, 1152, 256) 65536 gated_activation_2_s0[0][0]
____________________________________________________________________________________________________
convolution1d_8 (Convolution1D) (None, 1152, 256) 65536 gated_activation_3_s0[0][0]
____________________________________________________________________________________________________
convolution1d_10 (Convolution1D) (None, 1152, 256) 65536 gated_activation_4_s0[0][0]
____________________________________________________________________________________________________
convolution1d_12 (Convolution1D) (None, 1152, 256) 65536 gated_activation_5_s0[0][0]
____________________________________________________________________________________________________
convolution1d_14 (Convolution1D) (None, 1152, 256) 65536 gated_activation_6_s0[0][0]
____________________________________________________________________________________________________
convolution1d_16 (Convolution1D) (None, 1152, 256) 65536 gated_activation_7_s0[0][0]
____________________________________________________________________________________________________
convolution1d_18 (Convolution1D) (None, 1152, 256) 65536 gated_activation_8_s0[0][0]
____________________________________________________________________________________________________
convolution1d_20 (Convolution1D) (None, 1152, 256) 65536 gated_activation_9_s0[0][0]
____________________________________________________________________________________________________
merge_11 (Merge) (None, 1152, 256) 0 convolution1d_2[0][0]
convolution1d_4[0][0]
convolution1d_6[0][0]
convolution1d_8[0][0]
convolution1d_10[0][0]
convolution1d_12[0][0]
convolution1d_14[0][0]
convolution1d_16[0][0]
convolution1d_18[0][0]
convolution1d_20[0][0]
____________________________________________________________________________________________________
activation_1 (Activation) (None, 1152, 256) 0 merge_11[0][0]
____________________________________________________________________________________________________
convolution1d_21 (Convolution1D) (None, 1152, 256) 65792 activation_1[0][0]
____________________________________________________________________________________________________
activation_2 (Activation) (None, 1152, 256) 0 convolution1d_21[0][0]
____________________________________________________________________________________________________
convolution1d_22 (Convolution1D) (None, 1152, 256) 65792 activation_2[0][0]
____________________________________________________________________________________________________
output_softmax (Activation) (None, 1152, 256) 0 convolution1d_22[0][0]
====================================================================================================
Total params: 4,129,536
Trainable params: 4,129,536
Non-trainable params: 0

DeepMind 尝试使用包含多个发言者的数据集进行训练,这显著提高了学习共享语言和语调的能力,从而使得生成的结果接近自然语音。你可以在线找到一系列合成语音的精彩例子(deepmind.com/blog/wavenet-generative-model-raw-audio/),有趣的是,音频质量在 WaveNet 使用额外文本条件时得到了提升,这些文本会被转化为语言学和语音学特征序列,并与音频波形一起使用。我的最爱例子是同一句话由网络以不同的语调发音。当然,听到 WaveNet 自己创作钢琴音乐也非常令人着迷。去网上看看吧!

总结

在这一章中,我们讨论了生成对抗网络(GAN)。一个 GAN 通常由两个网络组成;一个被训练用来伪造看起来真实的合成数据,另一个则被训练用来区分真实数据与伪造数据。这两个网络持续竞争,通过这种方式,它们相互促进和改进。我们回顾了一个开源代码,学习如何伪造看起来真实的 MNIST 和 CIFAR-10 图像。此外,我们还讨论了 WaveNet,这是一种由 Google DeepMind 提出的深度生成网络,用于教计算机如何以惊人的质量再现人类语音和乐器声音。WaveNet 通过基于扩张卷积网络的参数化语音合成方法直接生成原始音频。扩张卷积网络是一种特殊的卷积神经网络(ConvNets),其中卷积滤波器具有孔洞,这使得感受野在深度上呈指数增长,因此能够高效地覆盖成千上万的音频时间步长。DeepMind 展示了如何使用 WaveNet 合成人的声音和乐器,并在此基础上改进了之前的最先进技术。在下一章中,我们将讨论词嵌入——一组用于检测词语之间关系并将相似词语归类在一起的深度学习方法。