提交 1d75f63e 编写于 作者: W wizardforcel

2020-09-12 13:36:44

上级 eb6875fb
# 前言
本书旨在为您提供实用的机器学习动手入门,其目的是使任何人都可以在该领域开始工作。 我们将主要关注深度学习方法以及如何将其用于解决重要的计算机视觉问题,但是此处获得的知识可以转移到许多不同的领域。 在此过程中,读者还将掌握如何使用流行的深度学习库 TensorFlow。
# 这本书是给谁的
任何对实用的机器学习指南(特别是深度学习和计算机视觉)感兴趣的人都将从阅读本书中受益。 此外,以下人员也将受益:
* 机器学习工程师
* 数据科学家
* 对学习深度学习和计算机视觉领域感兴趣的开发人员
* 学生学习机器学习
# 本书涵盖的内容
[第 1 章](../Text/1.xhtml)*Tensorflow 的设置和简介,*涵盖了 TensorFlow 的设置和安装,以及编写用于机器学习的简单 Tensorflow 模型。
[第 2 章](../Text/2.xhtml)*深度学习和卷积神经网络*向您介绍了机器学习,人工智能以及人工神经网络以及如何对其进行训练。 它还涵盖了 CNN 以及如何使用 TensorFlow 训练自己的 CNN。
[第 3 章](../Text/3.xhtml)*Tensorflow* 中的图像分类,讨论了如何构建 CNN 模型以及如何训练它们以对 CIFAR10 数据集进行分类。 它还探讨了通过谈论初始化和正则化的不同方法来帮助提高我们训练后的模型的质量的方法。
[第 4 章](../Text/4.xhtml)*对象检测和分割*教授对象定位,检测和分割的基础知识以及与这些主题相关的最著名的算法。
[第 5 章](../Text/5.xhtml)*VGG,接收模块,残差和 MobileNets* 向您介绍了不同的卷积神经网络设计,例如 VGGNet,GoggLeNet 和 MobileNet。
[第 6 章](../Text/6.xhtml)*自动编码器,变体自动编码器和生成对抗网络,*向您介绍生成模型,生成对抗网络和不同类型的编码器。
[第 7 章](../Text/7.xhtml)*转移学习,*涵盖了转移学习的用法并在我们自己的任务中实现。
[第 8 章](../Text/8.xhtml)*机器学习最佳实践和* *故障排除,*向我们介绍了如何准备并将数据集拆分为子集并执行有意义的测试。 本章还讨论了过拟合和过拟合以及解决这些问题的最佳实践。
[第 9 章](../Text/9.xhtml)*大规模培训,*教您如何在多个 GPU 和机器上训练 TensorFlow 模型。 它还涵盖了存储数据并将其输入模型的最佳实践。
# 充分利用这本书
为了充分利用本书,读者应该对 Python 编程语言以及如何安装一些必需的软件包有所了解。 本书将以简单的语言介绍所有其他内容。 安装说明将在本书和存储库中给出。
# 下载示例代码文件
您可以从 [www.packtpub.com](http://www.packtpub.com) 的帐户中下载本书的示例代码文件。 如果您在其他地方购买了此书,则可以访问 [www.packtpub.com/support](http://www.packtpub.com/support) 并注册以将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
1. 登录或注册 [www.packtpub.com](http://www.packtpub.com/support)
2. 选择支持选项卡。
3. 单击代码下载和勘误。
4. 在搜索框中输入书籍的名称,然后按照屏幕上的说明进行操作。
下载文件后,请确保使用以下最新版本解压缩或解压缩文件夹:
* Windows 的 WinRAR / 7-Zip
* Mac 版 Zipeg / iZip / UnRarX
* 适用于 Linux 的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 的 [https://github.com/PacktPublishing/Hands-on-Convolutional-Neural-Networks-with-Tensorflow](https://github.com/PacktPublishing/Hands-on-Convolutional-Neural-Networks-with-Tensorflow) 上。 如果代码有更新,它将在现有 GitHub 存储库上进行更新。
我们还从 **[https://github.com/PacktPublishing/](https://github.com/PacktPublishing/)** 提供了丰富的书籍和视频目录中的其他代码包。 去看一下!
# 使用约定
本书中使用了许多文本约定。
`CodeInText`:指示文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字。 这是一个示例:“将下载的`WebStorm-10*.dmg`磁盘映像文件安装为系统中的另一个磁盘。”
代码块设置如下:
```py
import tensorflow as tf
# XOR dataset
XOR_X = [[0, 0], [0, 1], [1, 0], [1, 1]]
XOR_Y = [[0], [1], [1], [0]]
```
当我们希望引起您对代码块特定部分的注意时,相关的行或项目以粗体显示:
```py
import tensorflow as tf
# XOR dataset
XOR_X = [[0, 0], [0, 1], [1, 0], [1, 1]]
XOR_Y = [[0], [1], [1], [0]]
```
任何命令行输入或输出的编写方式如下:
```py
$ pip install numpy
$ pip install scipy
```
**粗体**:表示新术语,重要单词或您在屏幕上看到的单词。 例如,菜单或对话框中的单词会出现在这样的文本中。 这是一个示例:“从管理面板中选择系统信息。”
警告或重要提示如下所示。
提示和技巧如下所示。
# 保持联系
始终欢迎读者的反馈。
**一般反馈**:给`feedback@packtpub.com`发送电子邮件,并在邮件主题中提及书名。 如果您对本书的任何方面有疑问,请通过`questions@packtpub.com`向我们发送电子邮件。
**勘误表**:尽管我们已尽一切努力确保内容的准确性,但还是会发生错误。 如果您在这本书中发现错误,请向我们报告,我们将不胜感激。 请访问 [www.packtpub.com/submit-errata](http://www.packtpub.com/submit-errata) ,选择您的图书,点击勘误提交表格链接,然后输入详细信息。
**盗版**:如果您在互联网上以任何形式看到我们的作品的任何非法复制品,请向我们提供位置地址或网站名称,我们将不胜感激。 请通过`copyright@packtpub.com`与我们联系,并提供材料链接。
**如果您有兴趣成为作者**:如果您有某个专业知识并且有兴趣撰写或撰写书籍,请访问 [authors.packtpub.com](http://authors.packtpub.com/) ]。
# 评论
请留下评论。 阅读和使用本书后,为什么不在您购买本书的网站上留下评论? 然后,潜在的读者可以查看并使用您的公正意见做出购买决定,Packt 的我们可以理解您对我们产品的看法,我们的作者可以在书中看到您的反馈。 谢谢!
有关 Packt 的更多信息,请访问 [packtpub.com](https://www.packtpub.com/)
\ No newline at end of file
# TensorFlow 的设置和介绍
TensorFlow 是 Google 创建的开源软件库,可让您构建和执行数据流图以进行数值计算。 在这些图中,每个节点表示要执行的某些计算或功能,连接节点的图边缘表示它们之间流动的数据。 在 TensorFlow 中,数据是称为 **Tensors** 的多维数组。 张量围绕图流动,因此命名为 TensorFlow。
**机器学习****ML** )模型,例如卷积神经网络,可以用这些图表示,而这正是 TensorFlow 最初设计的目的。
在本章中,我们将介绍以下主题:
* 了解 TensorFlow 的思维方式
* 设置和安装 TensorFlow
* TensorFlow API 级别简介
* 在 TensorFlow 中构建和训练线性分类器
* 评估训练好的模型
# TensorFlow 的思维方式
使用 TensorFlow 所需的编程方法与您可能习惯使用的方法略有不同,因此让我们探究有什么不同之处。
所有 TensorFlow 程序的核心都有两个主要部分:
* 构造称为`tf.Graph`的计算图
* 使用`tf.Session`运行计算图
在 TensorFlow 中,计算图是安排成图结构的一系列 TensorFlow 操作。 TensorFlow 图包含两种主要类型的组件:
* **操作**:更通常称为 **ops** ,这些是图中的节点。 Ops 执行需要在图形中进行的任何计算。 通常,它们消耗并产生张量。 一些操作很特殊,运行时可能会有某些副作用。
* **张量**:这是图形的边缘; 它们将节点连接起来并表示流经节点的数据。 大多数 TensorFlow op 将产生并消耗这些`tf.Tensors`
在 TensorFlow 中,您使用的主要对象称为 Tensor。 张量是向量和矩阵的一般化。 即使向量是一维的,矩阵是二维的,张量也可以是 *n* -维。 TensorFlow 将 Tensor 表示为用户指定数据类型的 *n* 维数组,例如`float32`
TensorFlow 程序通过首先构建计算图来工作。 该图将产生一些`tf.Tensor`输出。 要评估此输出,您必须通过在输出 Tensor 上调用`tf.Session.run`来在`tf.Session`中运行*它。 当您执行此操作时,TensorFlow 将执行图形中需要执行的所有部分,以评估您要求其运行的`tf.Tensor`。*
# 设置和安装 TensorFlow
TensorFlow 在最新版本的 Ubuntu 和 Windows 上受支持。 Windows 上的 TensorFlow 仅支持使用 Python 3,而在 Ubuntu 上使用则允许同时使用 Python 2 和 3。我们建议使用 Python 3,这就是本书中用于代码示例的内容。
您可以通过多种方式在系统上安装 TensorFlow,这里我们将介绍两种主要方式。 最简单的方法就是使用 pip 软件包管理器。 从终端发出以下命令会将 TensorFlow 的仅 CPU 版本安装到您​​的系统 Python 中:
```py
$ pip3 install --upgrade tensorflow
```
要安装支持使用您的 Nvidia GPU 的 Tensorflow 版本,只需键入以下内容:
```py
$ pip3 install --upgrade tensorflow-gpu
```
TensorFlow 的优势之一是它允许您编写可以直接在 GPU 上运行的代码。 除了少数例外,TensorFlow 中的几乎所有主要操作都可以在 GPU 上运行以加快其执行速度。 我们将看到,这对于训练本书稍后描述的大型卷积神经网络将至关重要。
# 康达环境
使用 pip 可能是上手最快的方法,但是我发现最方便的方法是使用 conda 环境。
Conda 环境允许您创建隔离的 Python 环境,该环境与系统 Python 或任何其他 Python 程序完全独立。 这样一来,您的 TensorFlow 安装就不会与已经安装的任何内容发生混乱,反之亦然。
要使用 conda,您必须从此处下载 Anaconda: [https://www.anaconda.com/download/](https://www.anaconda.com/download/) 。 这将包括 conda。 一旦安装了 Anaconda,即可通过在命令提示符中输入某些命令来完成 TensorFlow 的安装。 首先,输入以下内容:
```py
$ conda create -n tf_env pip python=3.5
```
这将创建名称为`tf_env`的 conda 环境,该环境将使用 Python 3.5,并且还将安装`pip`供我们使用。
创建此环境后,您可以通过在 Windows 上输入以下内容来开始使用它:
```py
$ activate tf_env
```
如果您使用的是 Ubuntu,请输入以下命令:
```py
$ source activate tf_env
```
现在,它应该在命令提示符旁边显示`(tf_env)`。 要安装 TensorFlow,我们只需像以前一样进行点安装,具体取决于您是否仅需要 CPU 还是需要 GPU 支持:
```py
(tf_env)$ pip install --upgrade tensorflow
(tf_env)$ pip install --upgrade tensorflow-gpu
```
# 检查您的安装是否正常
现在您已经安装了 TensorFlow,让我们检查一下它是否正常运行。 在命令提示符中,如果尚未激活环境,请再次激活它,然后输入以下命令来运行 Python:
```py
(tf_env)$ python
```
现在,在 Python 解释器中输入以下行以测试 TensorFlow 是否已正确安装:
```py
>>>> import tensorflow as tf
>>>> x = tf.constant('Tensorflow works!')
>>>> sess = tf.Session()
>>>> sess.run(x)
```
如果一切都正确安装,您应该看到以下输出:
`**b'Tensorflow works!'**`
您刚刚输入的是 TensorFlow 的`Hello World`。 您创建了一个包含单个`tf.constant`的图,该图只是一个常数 Tensor。 将字符串传递给 Tensor 时,推断其为 string 类型。 然后,您创建了一个 TensorFlow 会话,这是运行图形所必需的,并将您创建的 Tensor 上的会话告知`run`。 然后打印出 Session 运行的结果。 那里有一个额外的`b`,因为它是一个字节流。
如果没有看到上述内容并且出现一些错误,则最好的选择是检查以下页面,以获取安装时遇到的常见问题的解决方案:Ubuntu: [https://www.tensorflow.org/install/install_linux #common_installation_problems](https://www.tensorflow.org/install/install_linux#common_installation_problems) Windows: [https://www.tensorflow.org/install/install_windows#common_installation_problems](https://www.tensorflow.org/install/install_windows#common_installation_problems)
# TensorFlow API 级别
在我们开始编写 TensorFlow 代码之前,了解 TensorFlow 在 Python 中提供的 API 抽象层次的不同很重要。 这样,我们可以了解编写代码时可以使用的功能,还可以为工作选择正确的功能或操作。 很多时候,几乎不需要从头开始重写已经可以在 TensorFlow 中使用的东西。
TensorFlow 提供了三层 API 抽象来帮助编写您的代码,这些可以在下图中可视化:
![](img/c1eed86e-95f5-4967-a2d3-eafe4e96d79a.png)
在最低级别上,您具有基本的 TensorFlow 操作,例如`tf.nn.conv2d``tf.nn.relu`。 使用 TensorFlow 时,这些低级原语为用户提供了最大的控制权。 但是,使用它们的代价是在构造图形和编写更多样板代码时必须自己动手做很多事情。
现在不用担心理解以下任何代码示例,我保证很快就会出现。 现在只是在这里展示 TensorFlow 中的不同 API 级别。
因此,例如,如果我们想创建一个卷积层以在我们的 ML 模型中使用,那么它可能类似于以下内容:
```py
def my_conv_2d(input, weight_shape, num_filters, strides):
my_weights = tf.get_variable(name="weights", shape=weight_shape)
my_bias = tf.get_variable(name="bias", shape=num_filters)
my_conv = tf.nn.conv2d(input, my_weights, strides=strides, padding='same', name='conv_layer1')
my_conv = tf.nn.bias_add(my_conv, my_bias)
conv_layer_out = tf.nn.relu(my_conv)
return conv_layer_out
```
这个示例比您实际实现的简单得多,但是您已经可以看到开始建立代码的行数,以及必须注意的事情,例如构造权重和添加偏见项。 一个模型也将具有许多不同种类的层,而不仅仅是卷积层,所有这些层都必须以与此非常相似的方式来构造。
因此,不仅要为模型中所需的每种新层将这些内容写出来都非常费力,而且还引入了更多的区域,在这些区域中,错误可能会潜入您的代码中,这从来都不是一件好事。
对我们来说幸运的是,TensorFlow 具有第二层抽象,可以帮助您简化构建 TensorFlow 图时的工作。 这种抽象级别的一个示例是 Layers API。 图层 API 使您可以轻松处理许多机器学习任务中常见的许多构建基块。
层 API 的工作方式是包装我们在上一个示例中编写的所有内容并将其抽象出来,因此我们不必再为它担心。 例如,我们可以压缩前面的代码以将卷积层构造为一个函数调用。 与以前建立相同的卷积层现在看起来像这样:
```py
def my_conv_2d(input, kernel_size, num_filters, strides):
conv_layer_out = tf.layers.conv2d(input, filters=num_filters, kernel_size=kernel_size, strides=strides, padding='same', activation=tf.nn.relu, name='conv_layer1')
return conv_layer_out
```
还有两个与各层一起工作的 API。 第一个是数据集 API,可轻松将数据加载和馈送到 TensorFlow 图。 第二个是指标 API,它提供工具来测试您训练有素的机器学习模型的运行状况。 我们将在本书的后面部分中学习所有这些内容。
API 堆栈的最后一层是 TensorFlow 提供的最高抽象层,这称为估计器 API。 就像使用`tf.layers`来构造权重并为单个层添加偏差一样,estimators API 封装了许多层的结构,以便我们可以将一个由多个不同层组成的整体模型定义为一个 函数调用。
本书不会介绍 estimators API 的用法,但是如果读者希望了解有关 estimator 的更多信息,可以在 TensorFlow 网站上找到一些有用的教程。
本书将重点介绍如何使用低级 API 以及图层,数据集和指标 API 来构建,训练和评估自己的 ML 模型。 我们相信,通过使用这些较低级别的 API,读者将对 TensorFlow 的幕后工作方式有更深入的了解,并有能力更好地应对可能需要使用这些较低级别 API 的各种未来问题 级功能。
# 急于执行
在撰写本文时,Google 刚刚将急切的执行 API 引入了 TensorFlow。 急切执行是 TensorFlow 对另一个名为 PyTorch 的深度学习库的回答。 它允许您绕过通常的 TensorFlow 工作方式,在这种方式下,您必须首先定义计算图,然后执行该图以获得结果。 这称为静态图计算。 相反,现在您可以使用 Eager Execution 创建在运行程序时动态定义的所谓动态图。 使用 TensorFlow 时,这允许使用更传统的命令式编程方式。 不幸的是,急切的执行仍在开发中,缺少某些功能,因此在本书中不作介绍。 可以在 TensorFlow 网站上找到有关急切执行的更多信息。
# 建立您的第一个 TensorFlow 模型
事不宜迟,让我们开始在 TensorFlow 中构建您的第一个 ML 模型。
我们将在本章中解决的问题是从四个给定的特征值正确识别虹膜花的种类。 这是一个非常容易解决的经典 ML 问题,但它将为我们提供一种很好的方式来介绍在 TensorFlow 中构建图形,馈送数据和训练 ML 模型的基础知识。
Iris 数据集由 150 个数据点组成,每个数据点具有四个相应的特征:长度,花瓣宽度,萼片长度和萼片宽度以及目标标签。 我们的任务是建立一个模型,仅给出这四个特征就可以推断出任何虹膜的目标标签。
让我们开始加载数据并进行处理。 TensorFlow 具有内置功能,可以为我们导入此特定数据集,因此让我们继续使用它。 由于我们的数据集很小,因此将整个数据集加载到内存中是可行的。 但是,不建议将其用于较大的数据集,并且在接下来的章节中,您将学到更好的处理此问题的方法。 以下代码块将为我们加载数据,然后对其进行解释。
```py
import tensorflow as tf
import numpy as np
# Set random seed for reproducibility.
np.random.seed(0)
data, labels = tf.contrib.learn.datasets.load_dataset("iris")
num_elements = len(labels)
# Use shuffled indexing to shuffle dataset.
shuffled_indices = np.arange(len(labels))
np.random.shuffle(shuffled_indices)
shuffled_data = data[shuffled_indices]
shuffled_labels = labels[shuffled_indices]
# Transform labels into one hot vectors.
one_hot_labels = np.zeros([num_elements,3], dtype=int)
one_hot_labels[np.arange(num_elements), shuffled_labels] = 1
# Split data into training and testing sets.
train_data = shuffled_data[0:105]
train_labels = shuffled_labels[0:105]
test_data = shuffled_data[105:]
test_labels = shuffled_labels[105:]
```
让我们再次看一下这段代码,看看到目前为止我们做了什么。 导入 TensorFlow 和 Numpy 之后,我们将整个数据集加载到内存中。 我们的数据由表示为矢量的四个数值特征组成。 我们总共有 150 个数据点,因此我们的数据将是形状为 150 x 4 的矩阵,其中每一行代表不同的数据点,每一列代表不同的要素。 每个数据点还具有与之关联的目标标签,该目标标签存储在单独的标签向量中。
接下来,我们重新整理数据集; 这一点很重要,因此,当我们将其分为训练集和测试集时,我们在这两个集之间平均分配,并且最终不会在一组集中获得所有一种类型的数据。
# 一热载体
改组后,我们对数据标签进行一些预处理。 随数据集加载的标签只是一个 150 长度的整数向量,表示每个数据点所属的目标类,在这种情况下为 1、2 或 3。 在创建机器学习模型时,我们希望将标签转换为一种新的形式,通过执行一种称为“一键编码”的方式可以更轻松地使用它。
不是使用单个数字作为每个数据点的标签,而是使用向量。 每个向量将与您拥有的不同目标类别的数目一样长。 因此,例如,如果您有 5 个目标类,则每个向量将有 5 个元素;例如, 如果您有 1,000 个目标类别,则每个向量将具有 1,000 个元素。 向量中的每一列代表我们的目标类别之一,我们可以使用二进制值来确定向量是其标签的类别。 可以通过将所有值设置为 0 并将 1 放入我们希望向量标签表示的类的列中来完成。
用一个例子很容易理解。 对于这个特定问题的标签,转换后的向量将如下所示:
```py
1 = [1,0,0]
2 = [0,1,0]
3 = [0,0,1]
```
# 分为训练和测试集
最后,我们将数据集的一部分放到一边。 这就是我们的测试集,在我们训练模型之后我们才接触它。 该集合用于评估我们训练有素的模型对从未见过的新数据的性能。 有许多方法可以将数据分为训练集和测试集,我们将在本书的后面详细介绍它们。
但就目前而言,我们将进行一个简单的 70:30 拆分,因此我们仅使用总数据的 70%来训练我们的模型,然后对剩余的 30%进行测试。
# 创建 TensorFlow 图
现在我们的数据都已经设置好了,我们可以构建模型来学习如何对鸢尾花进行分类。 我们将构建最简单的机器学习模型之一-线性分类器,如下所示:
![](img/dffe1e52-9e6f-40af-a66a-2cb494926289.jpg)
线性分类器通过计算输入特征向量 *x* 和权重向量 *w* 之间的点积来工作。 在计算出点积之后,我们向结果添加一个值,称为偏差项 *b* 。 在我们的例子中,我们有三种可能的类别,任何输入特征向量都可能属于该类别,因此我们需要使用 *w <sup>1</sup>**w <sup>2 计算三种不同的点积</sup> ,**w <sup>3</sup>* 以查看其属于哪个类别。 但是,我们不必写出三个单独的点积,而只需在形状[3,4]的权重矩阵与输入向量之间做一个矩阵乘法。 在下图中,我们可以更清楚地看到它的外观:
![](img/e8b1acac-e39a-48e8-ac38-61fde8e1812f.png)
我们还可以将该方程简化为更紧凑的形式,如下所示,其中我们的权重矩阵为 *W* ,偏差为 *b**x* 是我们的输入特征 向量,结果输出为 *s*
![](img/e32b3460-ee85-420f-8eac-61314d7678c4.png)
# 变数
我们如何在 TensorFlow 代码中全部写出来? 让我们开始创建权重和偏见。 在 TensorFlow 中,如果我们想创建一些可以被我们的代码操纵的 Tensor,那么我们需要使用 TensorFlow 变量。 TensorFlow 变量是`tf.Variable`类的实例。 `tf.Variable`类表示`tf.Tensor`对象,可以通过在其上运行 TensorFlow 操作来更改其值。 变量是类似于 Tensor 的对象,因此它们可以以与 Tensor 相同的方式传递,并且可以与 Tensor 一起使用的任何操作都可以与变量一起使用。
要创建变量,我们可以使用`tf.get_variable()`。 调用此函数时,必须提供变量的名称。 此函数将首先检查图形上是否没有其他具有相同名称的变量,如果没有,则它将创建新变量并将其添加到 TensorFlow 图形。
您还必须指定变量要具有的形状,或者,可以使用`tf.constant`张量来初始化变量。 变量将采用您的常数 Tensor 的值,并且形状将自动推断。 例如,以下代码将产生一个包含值 21 和 25 的 1x2 张量:
```py
my_variable = tf.get_variable(name= "my_variable", initializer=tf.constant([21, 25]))
```
# 运作方式
在图中有变量很好,但我们也想对它们做点什么。 我们可以使用 TensorFlow 操作来操作我们的变量。
如前所述,我们的线性分类器只是一个矩阵乘法运算,因此您将要使用的第一个运算很有趣地成为矩阵乘法运算。 只需在您要相乘的两个张量上调用`tf.matmul()`,结果将是您传入的两个张量的矩阵相乘。简单!
在整本书中,您将了解需要使用的许多不同的 TensorFlow 操作。
现在,您希望对变量和操作有所了解,让我们构建线性模型。 我们将在函数中定义模型。 该函数将以 N 个特征向量为输入,或更准确地说,以 N 个大小为一批。由于我们的特征向量的长度为 4,所以我们的批次将是[N,4]形状张量。 然后该函数将返回线性模型的输出。 在下面的代码中,我们编写了线性模型函数,该函数应该可以自我解释,但是如果您还没有完全理解它,请继续阅读。
```py
def linear_model(input):
# Create variables for our weights and biases
my_weights = tf.get_variable(name="weights", shape=[4,3])
my_bias = tf.get_variable(name="bias", shape=[3])
# Create a linear classifier.
linear_layer = tf.matmul(input, my_weights)
linear_layer_out = tf.nn.bias_add(value=linear_layer, bias=my_bias)
return linear_layer_out
```
在此处的代码中,我们创建了将存储权重和偏差的变量。 我们给他们起名字并提供所需的形状。 请记住,我们使用变量是因为我们想通过操作来操纵它们的值。
接下来,我们创建一个`tf.matmul`节点,将我们的输入特征矩阵和权重矩阵作为参数。 可以通过我们的`linear_layer` Python 变量访问此操作的结果。 然后将该结果传递给另一个运算符`tf.nn.bias_add`。 该运算来自 **NN****神经网络**)模块,在我们希望向计算结果中添加偏差矢量时使用。 偏差必须是一维张量。
# 使用占位符馈送数据
占位符是类似 Tensor 的对象。 它们是您与 TensorFlow 之间的合同,该合同规定,当您在会话中运行计算图时,您将提供*数据或将*数据馈入该占位符,以便您的图可以成功运行。
它们就像张量一样,就像张量一样,意味着您可以将它们传递到放置张量的地方。
通过使用占位符,我们可以向图形中提供外部输入,这些输入可能会在每次运行图形时更改。 它们的自然用法是将数据和标签提供到模型中的一种方式,因为每次我们要运行图形时,我们提供的数据和标签通常都会有所不同。
创建占位符时,我们必须提供将要填充的数据类型。
我们将使用两个占位符将数据和标签提供到图形中。 我们还提供了馈入这些占位符的任何数据都必须采用的形状。 我们使用`None`表示该特定尺寸的大小可以为任何值。 这样,我们就可以批量输入大小不同的数据。 接下来,我们将看到如何在 TensorFlow 中为我们的问题定义占位符。
```py
x = tf.placeholder(tf.float32, shape=[None, 4], name="data_in")
y = tf.placeholder(tf.int32, shape=[None, 3], name="target_labels")
```
现在,我们在图形中创建了占位符,因此我们也可以在图形上构造线性模型。 我们调用之前定义的函数,并提供数据占位符`x`作为输入。 请记住,占位符的行为类似于张量,因此它们也可以像它们一样被传递。 在以下代码中,我们使用占位符作为输入参数来调用 linear_model 函数。
```py
model_out = linear_model(x)
```
当我们调用函数时,函数中的所有内容都会执行,所有操作和变量都将添加到 TensorFlow 图中。 我们只需要这样做一次。 如果我们再次尝试调用函数,则会收到一条错误消息,说明我们已尝试向图添加变量,但变量已经存在。
占位符是向我们的图中提供外部数据的最简单,最快的方法,因此很高兴了解它们。 稍后,我们将看到使用数据集 API 提供数据的更好方法,但是就目前而言,占位符是一个不错的起点。
# 初始化变量
在我们能够在图形中使用变量之前,我们必须对其进行初始化。 我们需要创建一个图形节点来为我们做到这一点。 使用`tf.global_variables_initializer`将向我们的图形添加一个初始化器节点。 如果我们在会话中运行该节点,那么图形中的所有变量都将被初始化,以便我们可以使用它们。 因此,现在,让我们创建一个初始化器节点,如下所示:
```py
initializer = tf.global_variables_initializer()
```
正如我们没有明确说明要对变量使用哪种初始化一样,TensorFlow 将使用默认的一种称为 Glorot Normal Initializer 的方法,也称为 Xavier Initialization。
# 训练我们的模型
我们已经构建了线性模型的图,并且可以向其中提供数据。 如果我们创建一个会话并在提供一些输入数据的同时运行`model_out` Tensor,那么我们将得到一个结果。 但是,我们得到的输出将完全是垃圾。 我们的模型尚未训练! 当我们使用初始化节点初始化变量时,权重和偏差的值仅具有默认值。
# 损失函数
要训​​练我们的模型,我们必须定义一些称为损失函数的函数。 损失函数将告诉我们我们的模型目前做得如何好坏。
损失可在`tf.losses`模块中找到。 对于此模型,我们将使用铰链损耗。 铰链损耗是创建**支持向量机****SVM** )时使用的损耗函数。 铰链损失严重惩罚了错误的预测。 对于一个给定的示例![](img/e4655c62-f4c7-4ac5-b435-8c8f4081016c.png),其中![](img/815f18aa-759e-4460-ba09-b6ade1d1caf5.png)是数据点的特征向量,![](img/f58f1ac7-cc4b-4fac-b17b-8f5d92f64c49.png)是其标记,其铰链损耗如下:
![](img/18f2de01-7b99-4a9d-a016-fd44db07e0b5.png)
为此,以下内容将适用:
![](img/9265c022-14e9-4820-b475-097c854e3aa9.png)
简而言之,该方程式采用分类器的原始输出。 在我们的模型中,这是三个输出分数,并确保目标类别的分数至少比其他类别的分数大至少 1。 对于每个分数(目标类别除外),如果满足此限制,则将损失加 0,否则,将增加罚款:
![](img/ac5b44f4-5e15-41b7-90d1-139736012555.png)
这个概念实际上是非常直观的,因为如果我们的权重和偏见得到了正确的训练,那么所产生的三个得分中的最高得分就可以自信地表明输入示例所属的正确类别。
由于在培训期间,我们会一次输入许多培训示例,因此,我们将获得多个需要平均的损失。 因此,需要最小化的总损耗方程如下:
![](img/5a3e2008-78d9-46de-b9a2-52e4d5d540d2.png)
在我们的代码中,损失函数将带有两个参数:logits 和 label。 在 TensorFlow 中,logits 是我们的模型产生的原始值的名称。 在我们的例子中,这是`model_out`,因为这是我们模型的输出。 对于标签,我们使用标签占位符`y`。 请记住,占位符将在运行时为我们填充:
```py
loss = tf.reduce_mean(tf.losses.hinge_loss(logits=model_out, labels=y))
```
由于我们也想对整个输入数据的损失进行平均,因此我们使用`tf.reduce_mean`将所有损失平均为一个损失值,将其最小化。
有许多不同类型的损失函数可供我们使用,这些函数对于不同的机器学习任务都是有益的。 在阅读本书时,我们将学习更多这些内容以及何时使用不同的损失函数。
# 优化
现在我们定义了要使用的损失函数; 我们可以使用这个损失函数来训练我们的模型。 如前面的方程式所示,损耗函数是权重和偏差的函数。 因此,我们要做的就是详尽地搜索权重和偏差的空间,并查看哪种组合最大程度地减少了损失。 当我们具有一维或二维权向量时,此过程可能还可以,但是当权向量空间太大时,我们需要一个更有效的解决方案。 为此,我们将使用一种名为**梯度下降**的优化技术。
通过使用损失函数和演算,梯度下降法可以看到如何调整模型权重和偏差的值,以使损失值减小。 这是一个迭代过程,需要多次迭代才能针对我们的训练数据对权重和偏差的值进行适当调整。 这个想法是,通过相对于目标函数![](img/9530e4e9-229a-4d8a-9314-de81cd1259bc.png) w 的梯度的相反方向更新参数,可以最小化由权重 *w* 参数化的损失函数 *L* 。 参数。 权重和偏差的更新功能如下所示:
![](img/56711c81-eedc-45e4-82ae-b0bcacc0d52c.png)
![](img/e4fc664a-e5c2-4fc9-b18e-678055a6782a.png)
在这里,![](img/580f5aaf-7c8b-432c-977a-4b6f774462b4.png)是迭代次数,![](img/4f52cf0a-7b12-4259-b6bd-5c208e68a6ad.png)是称为学习率的超参数。
由两个变量 *w1**w2* 参数化的损失函数将如下图所示:
![](img/638e8ecb-3cc1-4a6d-a972-2f29b4a517b4.jpg)
上图显示了椭圆抛物面的水平曲线。 这是一个碗形的表面,碗的底部位于中心。 从图中可以看出,在点 *a* (黑色直箭头)处的梯度矢量垂直于通过 *a* 的水平曲线。 实际上,梯度矢量指向损失函数最大增加率的方向。
因此,如果我们从*点*开始并朝与相反的*方向将权重更新为梯度矢量,那么我们将下降至 *b* 点,然后在下一个 迭代到 *c* ,依此类推,直到达到最小值。 选择使损失函数最小的参数来表示最终的训练线性模型。*
TensorFlow 的好处在于,它使用其内置的优化器(称为**自动微分**)为我们计算了所有所需的梯度。 我们要做的就是选择一个梯度下降优化器,并告诉它最小化我们的损失函数。 TensorFlow 将自动计算所有梯度,然后使用这些梯度为我们更新权重。
我们可以在`tf.train`模块中找到优化程序类。 现在,我们将使用`GradientDescentOptimizer`类,它只是基本的梯度下降优化算法。 创建优化器时,我们必须提供学习率。 学习速率的值是`hyperparameter`,用户必须通过反复试验和实验来对其进行调整。 0.5 的值应该可以很好地解决此问题。
优化器节点具有一种称为`minimize`的方法。 在您提供的损失函数上调用此方法会做两件事。 首先,针对您的整个图形计算与该损耗有关的梯度。 其次,这些梯度用于更新所有相关变量。
创建我们的优化器节点将如下所示:
```py
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.5).minimize(loss)
```
与损失函数一样,有很多不同的梯度下降优化器需要学习。 这里介绍的是最基本的一种,但是再次,我们将在以后的章节中学习和使用不同的类型。
# 评估训练好的模型
我们汇总了训练模型所需的所有零件。 开始训练之前的最后一件事是,我们想在图形中创建一些节点,这些节点将使我们能够在完成训练后测试模型的执行情况。
我们将创建一个节点来计算模型的准确性。
`Tf.equal`将返回一个布尔列表,指示两个提供的列表在哪里相等。 在找到最大值的索引之后,在这种情况下,我们的两个列表将是模型的标签和输出:
```py
correct_prediction = tf.equal(tf.argmax(model_out,1), tf.argmax(y,1))
```
然后,我们可以再次使用`reduce_mean`来获得正确预测的平均数量。 不要忘记将我们的`boolean correct_prediction`列表投射回`float32`
```py
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
```
# 会议
现在,我们已经构造了计算图的所有部分。 我们需要做的最后一件事是创建一个`tf.Session`并运行我们的图形。 TensorFlow 会话是一种将用 Python 编写的 TensorFlow 程序与为 TensorFlow 供电的 C ++运行时连接的一种方式。 该会话还使 TensorFlow 可以访问本地或远程计算机上存在的设备,例如 CPU 和 GPU。 另外,该会话将缓存有关构造图的信息,因此可以有效地多次运行计算。
创建会话的标准方法是使用 Python 上下文管理器:`with`语句块:
```py
with tf.Session() as sess:.
```
原因是创建会话时,它可以控制计算机上的 CPU,内存和 GPU 资源。 使用完会话后,您希望再次释放所有这些资源,最简单的方法是使用`with`语句来确保这一点。
创建会话后,我们要做的第一件事是运行初始化程序 op。 通过在要评估的图形对象上调用`tf.Session.run`,可以使用会话来评估图形中的节点和张量。 当您将图形的一部分提供给`session.run`时,TensorFlow 将在整个图形中进行工作,评估所提供的图形部分所依赖的所有内容以产生结果。
因此,在我们的示例中,调用`sess.run(initializer)`将在图形中进行搜索,查找执行初始化程序所需的所有内容,然后按顺序执行这些节点。 在这种情况下,初始化程序节点没有任何连接,因此它将简单地执行一个初始化所有变量的节点。
现在我们的变量已初始化,我们开始训练循环。 我们将训练 1000 个步骤或迭代,因此我们将在其中创建训练步骤的 for 循环。 训练的步骤数是`hyperparameter`。 这是我们在训练模型时需要决定的事情。 您可以选择与您选择的值进行权衡,这将在以后的章节中进行讨论。 对于此问题,1000 个步骤将足以获得所需的结果。
我们获取了一批训练数据和标签,并将它们输入到图形中。 接下来,我们再次调用`session.run`。 这次,我们将其称为损失和优化器两件事。 通过将它们放在我们提供给`session.run`的列表中,我们可以提供想要评估的事物。 TensorFlow 将足够聪明,如果不需要,它不会多次评估图形,并且它将重用已经计算出的结果。 我们提供的这个列表称为我们的提取; 它是我们要评估和获取的图中的节点。
在提取列表之后,我们提供了`feed_dict`或 Feed 字典。 这是一个字典,其中的每个键都是图表中的张量,我们将向该张量输入值(在本例中为占位符),而对应的值就是将被输入到它的值。
`session.run`的返回值对应于我们的提取列表中的每个值。 我们的第一个获取是图中的损失 Tensor,因此第一个 return 参数来自此。 第二个获取是优化器节点。 我们不在乎从该节点返回什么,因为我们只在乎优化器节点的计算结果,因此我们将其对应的返回值留空:
```py
with tf.Session() as sess:
sess.run(initializer)
for i in range(1000):
batch_x, batch_y = train_data[:,:], train_labels[:,:]
loss_val, _ = sess.run([loss, optimizer], feed_dict={x : batch_x, y: batch_y})
print("Train Accuracy:", sess.run(accuracy, feed_dict={x: train_data, y: train_labels}))
print("Test Accuracy:", sess.run(accuracy, feed_dict={x: test_data, y: test_labels}))
```
运行 1000 次迭代后,我们使用另一个`session.run`调用来获取精度节点的输出。 我们执行两次,一次输入我们的训练数据以获取训练集的准确性,一次输入我们保留的测试数据以获取测试集的准确性。 您应该从`0.977778`中打印出测试精度,这意味着我们的模型可以正确分类 45 个测试集中的 44 个,一点也不差!
# 摘要
在本章中,我们已经说明了使用 TensorFlow 进行编程的方式以及如何为使用 TensorFlow 设置工作环境。 我们还研究了如何使用 TensorFlow 对虹膜花朵进行分类来构建,训练和评估自己的线性模型。 在此过程中,我们简要介绍了损失函数和梯度下降优化器。
在下一章中,我们将更多地了解一些关键的深度学习概念,包括卷积神经网络。 我们还将研究如何使用 TensorFlow 来构建和训练深度神经网络。
\ No newline at end of file
# References
# Chapter 1
Here we're going to learn how to install and the basics of Tensorflow, as references we have:
* [https://www.tensorflow.org/tutorials/](https://www.tensorflow.org/tutorials/)
* [https://www.tensorflow.org/install/](https://www.tensorflow.org/install/)
# Chapter 2
This chapter will tackle the principals of Machine Learning and Neural Networks with emphasis on Computer Vision with Convolutional Neural Networks. The references for the chapter are:
* [https://en.wikipedia.org/wiki/Artificial_neural_network](https://en.wikipedia.org/wiki/Artificial_neural_network)
* [https://en.wikipedia.org/wiki/Timeline_of_machine_learning](https://en.wikipedia.org/wiki/Timeline_of_machine_learning)
* [http://ais.informatik.uni-freiburg.de/teaching/ss11/ki/slides/ai12_acting_under_uncertainty_handout_4up.pdf](http://ais.informatik.uni-freiburg.de/teaching/ss11/ki/slides/ai12_acting_under_uncertainty_handout_4up.pdf)
* [http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf](http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf)
* [https://www.facebook.com/yann.lecun/posts/10152820758292143](https://www.facebook.com/yann.lecun/posts/10152820758292143)
* [https://towardsdatascience.com/types-of-convolutions-in-deep-learning-717013397f4d](https://towardsdatascience.com/types-of-convolutions-in-deep-learning-717013397f4d)
* [http://cs231n.stanford.edu/](http://cs231n.stanford.edu/)
* [http://cs224d.stanford.edu/](http://cs224d.stanford.edu/)
# Chapter 3
This chapter will be cover image classification using Deep learning, and why CNNs disrupted the way we do computer vision now. The references for this chapter are:
* [Learning Multiple Layers of Features from Tiny Images](https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf), Alex Krizhevsky, 2009
* An excellent flashback on image representation techniques can be found in the *Computer Vision: Algorithms and Applications*, Richard Szeliski, 2010
* [http://www.vision.caltech.edu/Image_Datasets/Caltech101/](http://www.vision.caltech.edu/Image_Datasets/Caltech101/)
* Griffin, Gregory and Holub, Alex and Perona, Pietro (2007) Caltech–256 *Object Category Dataset*
* Everingham, M., Van Gool, L., Williams, C. K. I., Winn, J. and Zisserman, *A International Journal of Computer Vision*, 88(2), 303-338, 2010
* *ImageNet Large Scale Visual Recognition Challenge*, IJCV, 2015
* [https://wordnet.princeton.edu/](https://wordnet.princeton.edu/)
* *What Does Classifying More Than 10,000 Image Categories Tell Us?* Jia Deng, Alexander C. Berg, Kai Li, and Li Fei-Fei
* Olga Russakovsky, Jia Deng et al. (2015) *ImageNet Large Scale Visual Recognition Challenge*, [https://arxiv.org/pdf/1409.0575.pdf](https://arxiv.org/pdf/1409.0575.pdf)
* Alex Krizhevsky, Ilya Sutskever and Geoffrey Hinton, *ImageNet Classification with Deep Convolutional Neural Networks*, 2012
* [https://arxiv.org/pdf/1311.2901.pdf](https://arxiv.org/pdf/1311.2901.pdf)
* *Going deeper with convolutions* by Christian Szegedy Google Inc. et al
* *Deep Residual Learning for Image Recognition*, Kaiming He et al.
* [https://arxiv.org/pdf/1709.01507.pdf](https://arxiv.org/pdf/1709.01507.pdf)
* The batch norm paper is a really well written paper that is easy to understand and explains the concept in much more detail, [https://arxiv.org/pdf/1502.03167.pdf](https://arxiv.org/pdf/1502.03167.pdf)
# Chapter 4
In this chapter we will learn about object detection and segmentation. The references for this chapter are: 
* [https://arxiv.org/pdf/1311.2524.pdf](https://arxiv.org/pdf/1311.2524.pdf) (*Rich feature hierarchies for accurate object detection and semantic segmentation*)
* [https://arxiv.org/pdf/1504.08083.pdf](https://arxiv.org/pdf/1504.08083.pdf) (*Fast RCNN*)
* [https://arxiv.org/pdf/1506.01497.pdf](https://arxiv.org/pdf/1506.01497.pdf) (*Faster RCNN Towards Real-Time Object Detection with Region Proposals*)
* [https://www.youtube.com/watch?v=v5bFVbQvFRk](https://www.youtube.com/watch?v=v5bFVbQvFRk)
* [https://arxiv.org/pdf/1506.02640.pdf](https://arxiv.org/pdf/1506.02640.pdf) (*You Only Look Once: Unified, Real-Time Object Detection* )
* [https://coursera.org/specializations/deep-learning](https://coursera.org/specializations/deep-learning) (*Deep Learning* course by Andrew Ng)
* [https://people.eecs.berkeley.edu/~jonlong/long_shelhamer_fcn.pdf](https://people.eecs.berkeley.edu/~jonlong/long_shelhamer_fcn.pdf) (*Fully Convolutional Neural Network for Semantic Segmentation*)
* [https://arxiv.org/pdf/1606.00915.pdf](https://arxiv.org/pdf/1606.00915.pdf) ( Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs)
# Chapter 5
In this chapter we will learn about some common CNN architectures (that is, VGG, ResNet, GoogleNet). The references for this chapter are: 
* Simonyan, K. and Zisserman, A., 2014, *Very Deep Convolutional Networks for Large-Scale Image Recognition*[arXiv preprint arXiv:1409.1556](https://arxiv.org/abs/1409.1556)
* *Going Deeper With Convolutions,* [https://arxiv.org/abs/1409.4842](https://arxiv.org/abs/1409.4842)
* *Deep Residual Learning for Image Recognition*, Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun, Microsoft Research
* *Mobilenets: Efficient Convolutional Neural Networks for Mobile Vision Applications*[https://arxiv.org/abs/1704.04861](https://arxiv.org/abs/1704.04861)
* [https://arxiv.org/pdf/1801.04381.pdf](https://arxiv.org/pdf/1801.04381.pdf), MobileNets V2
# Chapter 7
This chapter will discuss transfer learning and how we can take advantage of other people's model training to help us train our own networks. The references for this chapter are:
* [https://www.cse.ust.hk/~qyang/Docs/2009/tkde_transfer_learning.pdf](https://www.cse.ust.hk/~qyang/Docs/2009/tkde_transfer_learning.pdf)
* [ftp://ftp.cs.wisc.edu/machine-learning/shavlik-group/torrey.handbook09.pdf](ftp://ftp.cs.wisc.edu/machine-learning/shavlik-group/torrey.handbook09.pdf)
* [https://arxiv.org/pdf/1403.6382.pdf](https://arxiv.org/pdf/1403.6382.pdf) (*CNN Features off-the-shelf: an Astounding Baseline for Recognition*)
* [https://arxiv.org/pdf/1310.1531.pdf](https://arxiv.org/pdf/1310.1531.pdf) (*DeCAF: A Deep Convolutional Activation Feature for Generic Visual Recognition*)
# Chapter 9
In the last chapter of this book we will learn how to take advantage of parallel cluster of computers in the cloud to accelerate model training. The references for this chapter are:
* [https://www.oreilly.com/ideas/distributed-tensorflow](https://www.oreilly.com/ideas/distributed-tensorflow)
* [https://research.fb.com/wp-content/uploads/2017/06/imagenet1kin1h5.pdf](https://research.fb.com/wp-content/uploads/2017/06/imagenet1kin1h5.pdf)
* [https://learningtensorflow.com/lesson11/](https://learningtensorflow.com/lesson11/)
* [https://www.tensorflow.org/deploy/distributed](https://www.tensorflow.org/deploy/distributed)
* [https://www.tensorflow.org/programmers_guide/low_level_intro](https://www.tensorflow.org/programmers_guide/low_level_intro)
* [https://github.com/tmulc18/Distributed-TensorFlow-Guide](https://github.com/tmulc18/Distributed-TensorFlow-Guide)
* [https://clusterone.com/blog/2017/09/13/distributed-tensorflow-clusterone/](https://clusterone.com/blog/2017/09/13/distributed-tensorflow-clusterone/)
* [https://www.youtube.com/watch?v=-h0cWBiQ8s8&](https://www.youtube.com/watch?v=-h0cWBiQ8s8&)
* [https://www.youtube.com/watch?v=uIcqeP7MFH0](https://www.youtube.com/watch?v=uIcqeP7MFH0)
* [https://www.youtube.com/watch?v=bRMGoPqsn20](https://www.youtube.com/watch?v=bRMGoPqsn20)
* [https://www.youtube.com/watch?v=1cHx1baKqq0](https://www.youtube.com/watch?v=1cHx1baKqq0)
\ No newline at end of file
# 深度学习和卷积神经网络
在开始本章之前,我们需要讨论一下 AI 和**机器学习****ML** )以及这两个组件如何组合在一起。 术语“人工”是指不真实或自然的事物,而“智能”是指能够理解,学习或能够解决问题的事物(在极端情况下,具有自我意识)。
正式地,人工智能研究始于 1956 年的达特茅斯会议,其中定义了 AI 及其使命。 在接下来的几年中,每个人都很乐观,因为机器能够解决代数问题和学习英语,并且在 1972 年制造了第一台机器人。但是在 1970 年代,由于过分的承诺但交付不足,出现了所谓的 AI 冬季 人工智能研究有限且资金不足。 此后,尽管 AI 通过专家系统重生,但可以显示人类水平的分析技能。 之后,第二次 AI 冬季机器学习在 1990 年代被认可为一个单独的领域,当时概率理论和统计学开始得到利用。
计算能力的提高和解决特定问题的决心促使 IBM 的 Deep Blue 的发展在 1997 年击败了国际象棋冠军。 迅速发展,时下的 AI 领域涵盖了许多领域,包括机器学习,计算机视觉,自然语言处理,计划调度和优化,推理/专家系统和机器人技术。
在过去的 10 年中,我们目睹了机器学习和一般 AI 的强大功能。 主要感谢深度学习。
在本章中,我们将涵盖以下主题:
* AI 和 ML 概念的一般说明
* 人工神经网络和深度学习
* **卷积神经网络****CNN** )及其主要构建模块
* 使用 TensorFlow 构建 CNN 模型以识别数字图像
* Tensorboard 简介
# AI 和 ML
就本书而言,将**人工智能****AI** )视为计算机科学领域,负责制造可解决特定问题的代理(软件/机器人)。 在这种情况下,“智能”表示代理是灵活的,并且它通过传感器感知环境,并将采取使自身在某个特定目标成功的机会最大化的措施。
我们希望 AI 最大化名为**期望效用**的东西,或者通过执行动作获得某种满足的概率。 一个简单易懂的例子是上学,您将最大程度地获得工作的期望效用。
人工智能渴望取代在完成繁琐的日常任务中涉及的易于出错的人类智能。 AI 旨在模仿(以及智能代理应该具有)的人类智能的一些主要组成部分是:
* **自然语言处理****NLP** ):能够理解口头或书面人类语言,并能自然回答问题。 NLP 的一些示例任务包括自动旁白,机器翻译或文本摘要。
* **知识和推理**:开发并维护代理周围世界的最新知识。 遵循人类推理和决策来解决特定问题并对环境的变化做出反应。
* **规划和解决问题的方法**:对可能采取的措施进行预测,并选择能最大化预期效用的方案,换句话说,为该情况选择最佳措施。
* **感知**:代理程序所配备的传感器向其提供有关代理程序所处世界的信息。这些传感器可以像红外传感器一样简单,也可以像语音识别麦克风那样复杂。 或相机以实现机器视觉。
* **学习**:要使代理开发世界知识,它必须使用感知来通过观察来学习。 学习是一种知识获取的方法,可用于推理和制定决策。 AI 的无需处理某些明确编程即可处理从数据中学习算法的子字段,称为机器学习。
ML 使用诸如统计分析,概率模型,决策树和神经网络之类的*工具*来有效地处理大量数据,而不是人类。
例如,让我们考虑以下手势识别问题。 在此示例中,我们希望我们的机器识别正在显示的手势。 系统的输入是手部图像,如下图所示,输出是它们所代表的数字。 解决该问题的系统需要使用视觉形式的感知。
![](img/1b20cfee-c32d-4d9d-836c-e39b8fa2fc4c.png)
仅将原始图像作为输入输入到我们的机器将不会产生合理的结果。 因此,应对图像进行预处理以提取某种可解释的抽象。 在我们的特定情况下,最简单的方法是根据颜色对手进行分割,然后进行垂直投影,将 *x* 轴上的非零值相加。 如果图像的宽度为 100 像素,则垂直投影将形成一个向量,该向量长 100 个元素(100 维),并且在展开的手指位置具有最高的值。 我们可以将提取的任何特征向量称为**特征向量**
假设对于我们的手部数据,我们有 1000 张不同的图像,现在我们已经对其进行处理以提取每个图像的特征向量。 在机器学习阶段,所有特征向量都将被提供给创建模型的机器学习系统。 我们希望该模型能够推广并能够预测未经过系统训练的任何未来图像的数字。
ML 系统的组成部分是评估。 在评估模型时,我们会看到模型在特定任务中的表现。 在我们的示例中,我们将研究它可以多么准确地从图像中预测数字。 90%的准确度意味着正确预测了 100 张给定图像中的 90 张。 在接下来的章节中,我们将更详细地讨论机器培训和评估过程。
# ML 的类型
机器学习问题可以分为三大类,具体取决于我们可以使用哪种数据以及我们想要完成什么:
**监督学习**:我们可以使用输入和所需的输出或标签。 手势分类是一种有监督学习问题的示例,其中为我们提供了手势和相应标签的图像。 我们想要创建一个模型,该模型能够在输入手形图像的情况下输出正确的标签。
监督技术包括 SVM,LDA,神经网络,CNN,K-NN,决策树等。
**无监督学习**:只有输入可用,没有标签,我们不一定知道我们想要模型做什么。 例如,如果我们得到一个包含手的图片但没有标签的大型数据集。 在这种情况下,我们可能知道数据中存在某些结构或关系,但是我们将其留给算法来尝试在我们的数据中找到它们。 我们可能希望我们的算法在数据中找到相似手势的簇,因此我们不必手动标记它们。
无监督学习的另一种用途是找到方法,以减少我们正在使用的数据的维度,这又是通过找到数据中的重要特征并丢弃不重要的特征来实现的。
无监督技术包括 PCA,t-SNE,K-means,自动编码器,深度自动编码器等。
下图说明了分类和聚类之间的区别(当我们需要在非监督数据上查找结构时)。
![](img/9b300c7e-a825-46fe-aebc-12d8367fdeef.jpg)
**强化学习**:第三种是关于培训代理在环境中执行某些操作的全部。 我们知道理想的结果,但不知道如何实现。 我们没有给数据加标签,而是给代理提供反馈,告诉它完成任务的好坏。 强化学习超出了本书的范围。
# 新旧机器学习
ML 工程师可能会遵循的典型流程来开发预测模型,如下所示:
1. 收集资料
2. 从数据中提取相关特征
3. 选择 ML 体系结构(CNN,ANN,SVM,决策树等)
4. 训练模型
5. 评估模型并重复步骤 3 至 5,直到找到满意的解决方案
6. 在现场测试模型
如前一节所述,机器学习的思想是拥有足够灵活的算法来学习数据背后的基础过程。 可以这么说,许多经典的 ML 方法不够强大,无法直接从数据中学习。 他们需要在使用这些算法之前以某种方式准备数据。
我们之前曾简要提到过,但是准备数据的过程通常称为特征提取,其中一些专家会过滤掉我们认为与其基础过程有关的所有数据细节。 此过程使所选分类器的分类问题更加容易,因为它不必处理数据中不相关的变量,否则这些变量可能会显得很重要。
ML 的新型深度学习方法具有的最酷的功能是,它们不需要(或需要更少)特征提取阶段。 相反,使用足够大的数据集,模型本身就能够直接从数据本身中学习代表数据的最佳特征! 这些新方法的示例如下:
* 深度 CNN
* 深度自动编码器
* **生成对抗网络****GAN** s)
所有这些方法都是深度学习过程的一部分,在该过程中,大量数据暴露于多层神经网络。 但是,这些新方法的好处是有代价的。 所有这些新算法都需要更多的计算资源(CPU 和 GPU),并且比传统方法需要更长的训练时间。
# 人工神经网络
**人工神经网络****ANNs** )受存在于我们大脑中的神经元生物网络的启发非常模糊,它由一组名为**人工神经元**的单元组成, 分为以下三种类型的层:
* 输入层
* 隐藏层
* 输出层
基本的人工神经元通过计算输入及其内部*权重*之间的点积来工作(参见下图),然后将结果传递给非线性激活函数 *f* (S 型) ,在此示例中)。 然后将这些人工神经元连接在一起以形成网络。 在训练该网络期间,目标是找到合适的权重集,这些权重将有助于我们希望网络执行的任何任务:
![](img/7ceed94c-24a7-4e1b-8225-5786d5c92da7.jpg)
接下来,我们有一个 2 层前馈人工神经网络的示例。 想象一下,神经元之间的联系就是训练过程中将学习的权重。 在此示例中,层 *L1* 将是输入层, *L2* 隐藏层,而 *L3* 将是输出层。 按照惯例,在计算层数时,我们仅包括权重可学习的层; 因此,我们不包括输入层。 这就是为什么它只是一个 2 层网络:
![](img/996c3a5a-a193-414b-b66b-fadb01b58569.png)
一层以上的神经网络是非线性假设的示例,在该模型中,模型可以学习比线性分类器更复杂的关系。 实际上,它们实际上是通用逼近器,能够逼近任何连续函数。
# 激活功能
为了使 ANN 模型能够解决更复杂的问题,我们需要在神经元点积之后添加一个非线性块。 然后,如果将这些非线性层级联起来,它将使网络将不同的概念组合在一起,从而使复杂的问题更易于解决。
在神经元中使用非线性激活非常重要。 如果我们不使用非线性激活函数,那么无论我们层叠了多少层,我们都只会拥有行为类似于线性模型的东西。 这是因为线性函数的任何线性组合都会分解为线性函数。
我们的神经元可以使用多种不同的激活功能,此处显示了一些; 唯一重要的是函数是非线性的。 每个激活功能都有其自身的优点和缺点。
历史上,神经网络选择的激活函数是 Sigmoid 和 *TanH* 。 但是,这些功能对于可靠地训练神经网络不利,因为它们具有不希望的特性,即它们的值在任一端都饱和。 这将导致这些点处的梯度为零,我们将在后面找到,并且在训练神经网络时不是一件好事。
![](img/11ef12ae-e5d9-458a-a00a-3be49a15fbb1.png)
结果,更流行的激活功能之一是 ReLU 激活或**整流线性单元**。 ReLU 只是输入和 0-max(x,0)之间的最大运算。 它具有理想的特性,即梯度(至少在一端)不会变为零,这极大地有助于神经网络训练的收敛速度。
该激活功能用于帮助训练深层的 CNN 之后,变得越来越流行。 它的简单性和有效性使其成为通常使用的激活功能。
# XOR 问题
为了解释深度在 ANN 中的重要性,我们将研究一个 ANN 能够解决的非常简单的问题,因为它具有多个层。
在使用人工神经元的早期,人们并不像我们在人工神经网络中那样将各层级联在一起,因此我们最终得到了一个称为感知器的单层:
**![](img/89dc6f56-f3bb-4c95-8a60-eb17810aa8e3.png)**
感知器实际上只是输入和一组学习的权重之间的点积,这意味着它实际上只是线性分类器。
大约在第一个 AI 冬季,人们意识到了感知器的弱点。 由于它只是线性分类器,因此无法解决简单的非线性分类问题,例如布尔异或(XOR)问题。 为了解决这个问题,我们需要更深入地研究。
在此图中,我们看到了一些不同的布尔逻辑问题。 线性分类器可以解决 AND 和 OR 问题,但不能解决 XOR:
![](img/128893a5-b03a-4c11-8743-f4858861f877.png)
这使人们有了将使用非线性激活的神经元层级联在一起的想法。 一层可以根据上一层的输出来创建非线性概念。 这种“概念的组合”使网络变得更强大,并能代表更困难的功能,因此,它们能够解决非线性分类问题。
# 训练神经网络
那么,我们该如何在神经网络中设置权重和偏差的值,从而最好地解决我们的问题呢? 好吧,这是在训练阶段完成的。 在此阶段中,我们希望使神经网络从训练数据集中“学习”。 训练数据集由一组输入(通常表示为 X)以及相应的所需输出或标签(通常表示为 Y)组成。
当我们说网络学习时,所发生的就是网络参数被更新,网络应该能够为训练数据集中的每个 X 输出正确的 Y。 期望的是,在对网络进行培训之后,它将能够针对培训期间未看到的新输入进行概括并表现良好。 但是,为了做到这一点,您必须有一个足够具有代表性的数据集,以捕获要输出的内容。 例如,如果要分类汽车,则需要具有不同类型,颜色,照度等的数据集。
通常,当我们没有足够的数据或者我们的模型不够复杂以至于无法捕获数据的复杂性时,就会出现训练机器学习模型的一个常见错误。 这些错误可能导致过度拟合和拟合不足的问题。 在以后的章节中,您将学习如何在实践中处理这些问题。
在训练期间,*以两种不同的模式执行网络***
* **正向传播**:我们通过网络向前工作,为数据集中的当前给定输入生成输出结果。 然后评估损失函数,该函数告诉我们网络在预测正确输出方面的表现如何。
* **向后传播**:我们通过网络进行反向计算,计算每个权重对产生网络电流损耗的影响。
此图显示了训练时网络运行的两种不同方式。
![](img/660dd742-938f-439b-bc48-87698285e311.jpg)
当前,使神经网络“学习”的主力军是与基于梯度的优化器(例如梯度下降)结合的反向传播算法。
反向传播用于计算梯度,该梯度告诉我们每个权重对产生电流损耗有什么影响。 找到梯度后,可以使用诸如梯度下降之类的优化技术来更新权重,以使损失函数的值最小化。
谨在最后一句话:TensorFlow,PyTorch,Caffe 或 CNTK 之类的 ML 库将提供反向传播,优化器以及表示和训练神经网络所需的所有其他功能,而无需您自己重写所有这些代码。
# 反向传播和链式规则
反向传播算法实际上只是微积分中可信赖链规则的一个示例。 它说明了如何找到某个输入对由多个功能组成的系统的影响。 因此,例如在下图中,如果您想知道 *x* 对函数 *g* 的影响,我们只需将 *f* 对 g 的影响乘以 *x**f* 的影响:
![](img/85cb6c18-9cbc-44ff-9db6-c7d90d3c7cf0.png)
同样,这意味着如果我们想实现自己的深度学习库,则需要定义层的正常计算(正向传播)以及此计算块相对于其输入的影响(导数)。
下面我们给出一些常见的神经网络操作及其梯度。
![](img/d775beec-f84f-4f5b-9d7b-bc8452b35175.jpg)
![](img/d3436da8-9781-4459-bed0-8921e134236c.png)
# 分批
对于大型数据集而言,将整个数据集存储在内存中以训练网络的想法,例如[第 1 章](../Text/1.xhtml)*设置和 TensorFlow 简介*中的示例。 人们在实践中所做的是,在训练期间,他们将数据集分成小块,称为迷你批次(通常称为批次)。 然后,依次将每个微型批次装入并馈送到网络,在网络中将计算反向传播和梯度下降算法,然后更新权重。 然后,对每个小批量重复此操作,直到您完全浏览了数据集。
为小批量计算的梯度是对整个训练集的真实梯度的噪声估计,但是通过反复获取这些小的噪声更新,我们最终仍将收敛到足够接近损失函数的最小值。
较大的批次大小可以更好地估计真实梯度。 使用较大的批次大小将允许较大的学习率。 权衡是在训练时需要更多的内存来保存此批次。
当模型看到您的整个数据集时,我们说一个纪元已经完成。 由于训练的随机性,您将需要针对多个时期训练模型,因为您的模型不可能只在一个时期内收敛。
# 损失函数
在训练阶段,我们需要使用当前的权重正确预测训练集; 此过程包括评估我们的训练集输入 *X* ,并与所需的输出 *Y* 进行比较。 需要某种机制来量化(返回标量数)我们当前的权重在正确预测我们所需的输出方面有多好。 该机制称为**损失函数。**
反向传播算法应返回每个参数相对于损失函数的导数。 这意味着我们将发现更改每个参数将如何影响损耗函数的值。 然后,优化算法的工作就是最小化损失函数,换句话说,就是在训练时减小训练误差。
一个重要方面是为工作选择合适的损失函数。 一些最常见的损失函数及其用途是在此处给出的:
* **对数丢失**-仅具有两个可能结果的分类任务(从有限集中返回标签)
* **交叉熵损失**-具有两个以上结果的分类任务(从有限集返回标签)
* **L1 损失**-回归任务(返回实数值)
* **L2 损失**-回归任务(返回实数值)
* **Huber 损失**-回归任务(返回实数值)
在本书中,我们将看到损失函数的不同示例。
损失函数的另一个重要方面是它们必须是可区分的。 否则,我们不能将它们与反向传播一起使用。 这是因为反向传播要求我们能够采用损失函数的导数。
在下图中,您可以看到损失函数连接在神经网络的末端(模型输出),并且基本上取决于模型的输出和数据集所需的目标。
![](img/d524acf3-04c6-4d31-b85f-9db358a0b344.png)
TensorFlow 的以下代码行也显示了这一点,因为损失仅需要标签和输出(此处称为 logits)。
```py
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
```
您可能会注意到第三个箭头也连接到损失功能。 这与名为正则化的东西有关,将在[第 3 章](../Text/3.xhtml)*TensorFlow* 中的图像分类中进行探讨; 因此,现在您可以放心地忽略它。
# 优化器及其超参数
如前所述,优化器的工作是以一种使训练损失误差最小的方式来更新网络权重。 在所有 TensorFlow 之类的深度学习库中,实际上只使用了一个优化器系列,即梯度下降式优化器系列。
其中最基本的简称为梯度下降(有时称为香草梯度下降),但已经尝试开发出更复杂的梯度下降方法。 一些受欢迎的是:
* 带动量的梯度下降
* RMSProp
* 亚当
TensorFlow 的所有不同优化器都可以在`tf.train`类中找到。 例如,可以通过调用`tf.train.AdamOptimizer()`使用 Adam 优化器。
您可能会怀疑,它们都有可配置的参数来控制它们的工作方式,但是通常最需要注意和更改的参数如下:
* 学习率:控制优化器尝试最小化损失函数的速度。 将其设置得太高,您将无法收敛到最小。 将其设置得太小,将永远收敛或陷于不良的局部最小值中。
下图显示了学习率选择错误可能带来的问题:
![](img/9b567add-923e-49f9-a994-3c69e7f8372c.png)
学习率的另一个重要方面是,随着训练的进行和错误的减少,您在训练开始时选择的学习率值可能会变得太大,因此您可能会开始超出最小值。
要解决此问题,您可以安排学习速率衰减,以免在训练时降低学习速率。 这个过程称为**学习率调度**,我们将在下一章中详细讨论几种流行的方法。
另一种解决方案是使用自适应优化器之一,例如 Adam 或 RMSProp。 这些优化器经过精心设计,可在您训练时自动调整和衰减所有模型参数的学习率。 这意味着从理论上讲,您不必担心安排自己的学习率下降。
最终,您希望选择优化器,以最快,最准确的方式训练您的网络。 下图显示了优化器的选择如何影响网络收敛的速度。 不同的优化器之间可能会有相当大的差距,并且可能因不同的问题而有所不同,因此理想情况下,如果可以的话,您应该尝试所有的优化器,并找到最适合您的问题的解决方案。
![](img/fa8a1f2d-f08a-4486-bc94-89b344fc33dd.png)
但是,如果您没有时间执行此操作,那么下一个最佳方法是首先尝试将 Adam 用作优化器,因为它通常在很少调整的情况下效果很好。 然后,如果有时间,请尝试使用 Momentum SGD; 这将需要更多的参数调整,例如学习率,但是如果调整得很好,通常会产生非常好的结果。
# 拟合不足与拟合过度
在设计用于解决特定问题的神经网络时,我们可能有很多活动部件,并且必须同时处理许多事情,例如:
* 准备数据集
* 选择层数/神经元数
* 选择优化器超参数
如果我们专注于第二点,它使我们了解选择或设计神经网络架构/结构时可能发生的两个问题。
这些问题中的第一个是模型对于训练数据的数量或复杂性而言是否太大。 由于模型具有如此众多的参数,因此即使在数据中存在噪声的情况下,它也可以轻松轻松地准确地学习其在训练集中看到的内容。 这是一个问题,因为当向网络提供的数据与训练集不完全相同时,网络将无法正常运行,因为它过于精确地了解了数据的外观,而错过了其背后的全局。 这个问题称为**过度拟合**或具有**高变异性**
另一方面,您可能选择的网络规模不足以捕获数据复杂性。 现在,我们遇到了相反的问题,由于您的模型没有足够的能力(参数)来充分学习,因此您的模型无法充分捕获数据集背后的基础结构。 网络将再次无法对新数据执行良好的操作。 这个问题称为**不足**或具有**高偏置**
您可能会怀疑,在模型复杂性方面,您总是会寻求适当的平衡,以避免这些问题。
在后面的章节中,我们将看到如何检测,避免和补救这些问题,但是仅出于介绍的目的,这些是解决这些问题的一些经典方法:
* 获取更多数据
* 当检测到测试数据的错误开始增长时停止(提前停止)
* 尽可能简单地开始模型设计,并且仅在检测到拟合不足时才增加复杂性
# 功能缩放
为了简化优化程序算法的工作,在培训和测试之前,有一些技术可以并且应该应用到您的数据中。
如果输入向量的不同维度上的值彼此不成比例,则损失空间将以某种方式扩大。 这将使梯度下降算法难以收敛,或者至少使其收敛较慢。
当数据集的要素超出比例时,通常会发生这种情况。 例如,关于房屋的数据集在输入向量中的一个特征可能具有“房间数”,其值可能在 1 到 4 之间,而另一个特征可能是“房屋面积”,并且可能在 1000 到 10000 之间。 ,它们彼此之间严重超出比例,这可能会使学习变得困难。
在下面的图片中,我们看到一个简单的示例,说明如果我们的输入要素未全部按比例缩放,则损失函数的外观以及正确缩放比例后的外观。 当数据缩放不正确时,梯度下降很难达到损失函数的最小值。
![](img/01d83517-803f-4442-b37d-a508371fdd0b.jpg)
通常,您将对数据进行一些标准化,例如在使用数据之前减去平均值并除以数据集的标准偏差。 对于 RGB 图像,通常只需从每个像素值中减去 128 即可使数据居中于零附近。 但是,更好的方法是为数据集中的每个图像通道计算平均像素值。 现在,您具有三个值,每个图像通道一个,现在从输入图像中删除这些值。 我们一开始就不必担心缩放问题,因为所有功能一开始的缩放比例都相同(0-255)。
要记住非常重要的一点-如果您在训练时对数据进行了一些预处理,则必须在测试时进行完全相同的预处理,否则可能会得到不好的结果!
# 全连接层
组成我们之前看到的 ANN 的神经元层通常称为密集连接层,或**完全连接的****FC** )层,或简称为线性层。 诸如 Caffe 之类的一些深度学习库实际上会将它们视为点乘积运算,非线性层可能会或可能不会跟随它们。 它的主要参数将是输出大小,基本上是其输出中神经元的数量。
[第 1 章](../Text/1.xhtml)*设置和 TensorFlow* 简介中,我们创建了自己的致密层,但是您可以使用`tf.layers`来更轻松地创建它,如下所示:
```py
dense_layer = tf.layers.dense(inputs=some_input_layer, units=1024, activation=tf.nn.relu)
```
在这里,我们定义了一个具有 1,024 个输出的完全连接层,随后将激活 ReLU。
重要的是要注意,该层的输入必须仅具有二维,因此,如果您的输入是空间张量,例如形状为[28 * 28 * 3]的图像,则必须在输入之前将其重整为矢量 它:
```py
reshaped_input_to_dense_layer = tf.reshape(spatial_tensor_in, [-1, 28 * 28 * 3])
```
# 针对 XOR 问题的 TensorFlow 示例
在这里,我们将到目前为止已经了解的一些知识放在一起,并将使用 TensorFlow 解决布尔 XOR 问题。 在此示例中,我们将创建一个具有 S 型激活函数的三层神经网络。 我们使用对数丢失,因为网络 0 或 1 仅有两种可能的结果:
```py
import tensorflow as tf
# XOR dataset
XOR_X = [[0, 0], [0, 1], [1, 0], [1, 1]]
XOR_Y = [[0], [1], [1], [0]]
num_input = 2
num_classes = 1
# Define model I/O (Placeholders are used to send/get information from graph)
x_ = tf.placeholder("float", shape=[None, num_input], name='X')
y_ = tf.placeholder("float", shape=[None, num_classes], name='Y')
# Model structure
H1 = tf.layers.dense(inputs=x_, units=4, activation=tf.nn.sigmoid)
H2 = tf.layers.dense(inputs=H1, units=8, activation=tf.nn.sigmoid)
H_OUT = tf.layers.dense(inputs=H2, units=num_classes, activation=tf.nn.sigmoid)
# Define cost function
with tf.name_scope("cost") as scope:
cost = tf.losses.log_loss( labels=y_, predictions=H_OUT)
# Add loss to tensorboard
tf.summary.scalar("log_loss", cost)
# Define training ops
with tf.name_scope("train") as scope:
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cost)
merged_summary_op = tf.summary.merge_all()
# Initialize variables(weights) and session
init = tf.global_variables_initializer()
sess = tf.Session()
# Configure summary to output at given directory
writer = tf.summary.FileWriter("./logs/xor_logs", sess.graph)
sess.run(init)
# Train loop
for step in range(10000):
# Run train_step and merge_summary_op
_, summary = sess.run([train_step, merged_summary_op], feed_dict={x_: XOR_X, y_: XOR_Y})
if step % 1000 == 0:
print("Step/Epoch: {}, Loss: {}".format(step, sess.run(cost, feed_dict={x_: XOR_X, y_: XOR_Y})))
# Write to tensorboard summary
writer.add_summary(summary, step)
```
如果运行此脚本,则应该期望获得以下损耗图。 我们可以看到损失已经为零,这表明模型已经过训练并解决了问题。 您可以重复此实验,但现在只有一层致密层; 正如我们所说,您应该注意到该模型无法解决问题
![](img/75567914-f4cf-459e-9955-ab4c20c97299.png)
为了能够查看图形,可以在脚本提示符下的命令提示符下运行以下命令。 这将为我们启动 tensorboard。 我们将在本章的后面找到关于 tensorboard 的更多信息。
```py
$ tensorboard --logdir=./logs/xor_logs
```
# 卷积神经网络
现在,我们将讨论另一种类型的神经网络,该网络专门设计用于处理具有某些空间特性的数据,例如图像。 这种类型的神经网络称为**卷积神经网络****CNN** )。
CNN 主要由称为**卷积层**的层组成,这些层对其层输入进行过滤以在这些输入中找到有用的特征。 这种过滤操作称为卷积,从而产生了这种神经网络的名称。
下图显示了对图像的二维卷积运算及其结果。 重要的是要记住,过滤器内核的深度与输入的深度相匹配(在这种情况下为 3):
![](img/d5583198-d0bf-4e6f-981b-6067cb0a6b63.png)
同样重要的是要清楚卷积层的输入不必是 1 或 3 通道图像。 卷积层的输入张量可以具有任意数量的通道。
很多时候,在谈论 CNN 中的卷积层时,人们都喜欢将卷积这个词简称为 conv。 这是非常普遍的做法,我们在本书中也会做同样的事情。
# 卷积
卷积运算是由星号表示的线性运算,它将两个信号合并:
![](img/0d17debb-60ee-488e-abc1-6debc0704782.png)
二维卷积在图像处理中用于实现图像过滤器,例如,查找图像上的特定补丁或查找图像中的某些特征。
在 CNN 中,卷积层使用称为**内核**的小窗口,以类似于瓦片的方式过滤输入张量。 内核精确定义了卷积运算将要过滤的内容,并且在找到所需内容时会产生强烈的响应。
下图显示了将图像与称为 Sobel 滤波器的特定内核进行卷积的结果,该内核非常适合在图像中查找边缘:
![](img/263bc17f-fc97-42c2-a99a-8bc089aa9453.png)
您可能已经猜到了,在卷积层中要学习的参数是该层内核的权重。 在 CNN 训练期间,这些过滤器的值会自动调整,以便为手头任务提取最有用的信息。
在传统的神经网络中,我们将必须将任何输入数据转换为单个一维向量,从而在将该向量发送到完全连接的层后丢失所有重要的空间信息。 此外,每个像素每个神经元都有一个参数,导致输入大小或输入深度较大的模型中参数数量激增。
但是,在卷积层的情况下,每个内核将在整个输入中“滑动”以搜索特定补丁。 CNN 中的内核很小,并且与它们所卷积的大小无关。 结果,就参数而言,使用 conv 层的开销通常比我们之前了解的传统密集层要少得多。
下图显示了传统的完全连接层和卷积(局部连接)层之间的区别。 注意参数的巨大差异:
![](img/d2abbff0-7660-4e55-82dc-dac6c50a0c38.png)
现在,也许我们希望卷积层在其输入中查找六种不同的事物,而不仅仅是寻找一种。 在这种情况下,我们将只给卷积层六个相同大小的过滤器(在这种情况下为 5x5x3),而不是一个。 然后,每个转换滤波器都会在输入中查找特定的模式。
下图显示了此特定的六个滤波器卷积层的输入和输出:
![](img/8dc41ddc-7a2e-404d-80f6-7833073a6e0f.png)
控制卷积层行为的主要超参数如下:
* **内核大小(K)**:滑动窗口的像素大小。 小通常更好,通常使用奇数,例如 1、3、5,有时很少使用 7。
* **跨度(S)**:内核窗口在卷积的每个步骤中将滑动多少像素。 通常将其设置为 1,因此图像中不会丢失任何位置,但是如果我们想同时减小输入大小,则可以增加位置。
* **零填充(填充)**:要放在图像边框上的零数量。 使用填充使内核可以完全过滤输入图像的每个位置,包括边缘。
* **过滤器数(F)**:我们的卷积层将具有多少个过滤器。 它控制卷积层将要查找的图案或特征的数量。
在 TensorFlow 中,我们将在`tf.layers`模块中找到 2-D 卷积层,可以将其添加到模型中,如下所示:
```py
conv1 = tf.layers.conv2d(
inputs=input_layer,
filters=32,
kernel_size=[5, 5],
padding="same",
activation=tf.nn.relu)
```
# 输入填充
如果我们什么都不做,那么卷积运算将输出一个在空间上小于输入结果的结果。 为了避免这种影响并确保卷积核查看每个图像位置,我们可以在输入图像的边界上放置零。 当我们这样做时,据说我们要填充图像:
![](img/cb2f3ad9-7e24-40fe-862b-a18b9ed5fec0.png)
TensorFlow 卷积操作为您提供了两种用于填充需求的选项:相同和有效。
* 有效-TensorFlow 不填充图像。 卷积内核将仅进入输入中的“有效”位置。
* 相同-如果我们假设步幅为 1,则在这种情况下,TensorFlow 将足够填充输入,以便输出空间大小与输入空间大小相同。
如果您确实希望对填充有更多控制,则可以在图层的输入上使用`tf.pad()`,以用所需的零个位数来填充输入。
通常,我们可以使用以下公式计算卷积运算的输出大小:
![](img/0ef1adda-27e2-4f34-8ecf-c6290ffff544.png),![](img/67287ebe-dbe6-4c4c-aaf7-5f0e3eee0428.png)
(这里,pad 是添加到每个边框的零的数量。)
但是在 TensorFlow 中,由于有效和相同填充选项的性质,公式如下所示:
```py
# Same padding
out_height = ceil(float(in_height) / float(strides[1]))
out_width = ceil(float(in_width) / float(strides[2]))
# Valid padding
out_height = ceil(float(in_height - filter_height + 1) / float(strides[1]))
out_width = ceil(float(in_width - filter_width + 1) / float(strides[2]))
```
# 计算参数数量(权重)
在这里,我们将展示如何计算卷积层使用的参数数量。 计算卷积层中参数数量(包括偏差)的公式如下:
![](img/e1f79e43-c892-4ef4-ad5a-10645558544d.png)
我们将用一个简单的例子来说明:
```py
Input: [32x32x3] input tensor
Conv layer: Kernel:5x5
numFilters:10
```
![](img/2b389e68-88be-45b4-95df-a668889ff0be.png)
另一方面,完全连接的层中的参数数量(包括偏置)如下:
![](img/1bdd4a61-a6cd-4e05-ba1d-da98caa3801c.png)
如前所述,如果直接在图像上使用传统的人工神经网络,则所有空间信息都将丢失,并且每个参数每个神经元每个像素只有一个参数,因此会有大量的参数。 使用前面提到的相同示例,并在密集的 10 个输出神经元层中,我们得到以下数字:
![](img/80d84de5-af55-48ee-ae2c-f58fb0a73be6.png)
这证明了这两种层类型之间参数的数量级差异。
# 计算操作数
现在,我们对计算特定卷积层的计算成本感兴趣。 如果您想了解如何实现有效的网络结构(例如在移动设备中速度是关键时),则此步骤很重要。 另一个原因是要查看在硬件中实现特定层需要多少个乘法器。 现代 CNN 架构中的卷积层最多可负责模型中所有计算的 90%!
这些是影响 MAC(乘加累加器)/操作数量的因素:
* 卷积核大小(F)
* 过滤器数量(M)
* 输入要素图的高度和宽度(高,宽)
* 输入批量(B)
* 输入深度大小(通道)(C)
* 卷积层步幅(S)
MAC 的数量可以计算为:
#MAC = [F * F * C *(H + 2 * P-FS + 1)*(W + 2 * P-FS + 1)* M] * B
例如,让我们考虑一个具有输入 224 x 224 x 3,批处理大小为 1,内核为 3x3、64 个过滤器,跨度为 1 和填充 1 的转换层:
#MAC = 3 * 3 *(224 + 2-31 + 1)*(224 + 2-31 + 1)* 3 * 64 * 1 = 9,462,528
相反,完全连接的层具有以下操作数:
#MAC = [H * W * C * Outputneurons] * B
让我们重用相同的示例,但现在有 64 个神经元的密集层:
#MAC = [224 * 224 * 3 * 64] * 1 = 9,633,792
(我们已排除了所有运维计算的偏差,但不应增加太多成本。)
通常,在 CNN 中,早期的 conv 层贡献了大部分计算成本,但参数最少。 在网络的末尾,相反的情况是后面的层具有更多的参数,但计算成本却较低。
# 将卷积层转换为完全连接的层
实际上,我们可以将完全连接的层视为卷积层的子集。 如果我们将内核大小设置为与输入大小匹配,则可以将 CNN 层转换为完全连接的层。 设置过滤器的数量与设置完全连接层中输出神经元的数量相同。 检查一下自己,在这种情况下,操作将是相同的。
例:
考虑具有 4,096 个输出神经元和输入大小为 7x7x512 的 FC 层,转换为:
转换层:内核:7x7,填充:0:步幅:1,过滤器:4,096。
使用公式来计算输出大小,我们得到大小为 1 x 1 x 4096 的输出。
这样做的主要原因之一是使您的网络完全卷积。 当网络完全卷积时,决定使用比输入的图像更大的输入大小图像并不重要,因为您没有任何需要固定输入大小的完全连接的层。
# 池化层
**池化层**用于减少 CNN 中我们的激活张量的空间尺寸,而不是体积深度。 它们是执行此操作的非参数方式,这意味着池化层中没有权重。 基本上,以下是从使用池中获得的收益:
* 在输入张量中汇总空间相关信息的便宜方法
* 通过减少空间信息,您可以获得计算性能
* 您的网络中存在一些翻译不变性
但是,池化的最大优点之一是它无需学习任何参数,这也是它的最大缺点,因为池化最终可能会丢掉重要的信息。 结果,现在开始在 CNN 中使用池的频率降低了。
在此图中,我们显示了最大池化池的最常见类型。 它像普通的卷积一样滑动一个窗口,然后在每个位置将窗口中的最大值设置为输出:
![](img/a9aa1a7f-a548-44e7-a033-3a9abdc34f9b.png)
在 TensorFlow 中,我们可以这样定义池层:
```py
tf.layers.max_pooling2d(inputs=some_input_layer, pool_size=[2, 2], strides=2)
```
# 1x1 卷积
这种卷积起初看起来可能有些奇怪,但是 1x1 卷积实际上是通过合并深度来适应深度的,而不更改空间信息。 当需要在不损失空间信息的情况下将一个体积深度转换为另一个体积深度(称为压缩或扩展)时,可以使用这种类型的卷积:
![](img/88fb04f4-1cdb-45d7-8ecb-5b38bc80215e.png)
# 计算感受野
接收场是特定卷积窗口“看到”其输入张量的程度。
有时,确切了解激活中每个特定像素在输入图像中“看到”了多少像素可能很有用; 这在对象检测系统中尤其重要,因为我们需要以某种方式查看某些层激活如何映射回原始图像大小。
在下图中,我们可以看到三个连续的 3x3 卷积层的接收场与一个 7x7 卷积层的接收场相同。 在设计新的更好的 CNN 模型时,此属性非常重要,我们将在后面的章节中看到。
![](img/25c4fa50-dafd-4851-a3f1-d20150dc5d3c.png)
接收场可以计算为:
![](img/de62d0a3-1282-4fe4-a7b3-c454f4f0f4b7.png)
在这里,组件如下:
* ![](img/a7f7c3ff-5d04-4af7-9f6c-33f23f3424e5.png):k 层的接收场
* ![](img/7766947c-888c-43ea-b00e-9c23280dee08.png):第 k 层的内核大小
* ![](img/383c173c-2090-4aa3-bb63-86181ec0e729.png):从第 i 层(1..k-1)跨步
* ![](img/89e2bd31-53be-4660-a85b-0938aa3276bf.png):所有步长的乘积直到第 k-1 层(所有先前的层,而不是当前的一层)
仅对于第一层,接收域就是内核大小。
这些计算与是否使用卷积或池化层无关,例如,步幅为 2 的 conv 层将与步幅为 2 的池化层具有相同的接收场。
例如,给定以下图层之后的 14x14x3 图像,这将适用:
* 转换:S:1,P:0,K:3
* 转换:S:1,P:0,K:3
* 最大池:S:2,P:0,K2
* 转换:S:1,P:0,K:3
![](img/1ad5b39a-c730-4006-987b-fc280e34940f.jpg)
# 在 TensorFlow 中构建 CNN 模型
在开始之前,有个好消息:使用 TensorFlow,您无需担心编写反向传播或梯度下降代码,而且所有常见类型的层都已实现,因此事情应该更轻松。
在此处的 TensorFlow 示例中,我们将根据您在[第 1 章](../Text/1.xhtml)*设置和 TensorFlow* 简介中学到的内容进行一些更改,并使用`tf.layers` API 创建整个 我们的网络轻松自如:
```py
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
# MNIST data input (img shape: 28*28)
num_input = 28*28*1
# MNIST total classes (0-9 digits)
num_classes = 10
# Define model I/O (Placeholders are used to send/get information from graph)
x_ = tf.placeholder("float", shape=[None, num_input], name='X')
y_ = tf.placeholder("float", shape=[None, num_classes], name='Y')
# Add dropout to the fully connected layer
is_training = tf.placeholder(tf.bool)
# Convert the feature vector to a (-1)x28x28x1 image
# The -1 has the same effect as the "None" value, and will
# be used to inform a variable batch size
x_image = tf.reshape(x_, [-1, 28, 28, 1])
# Convolutional Layer #1
# Computes 32 features using a 5x5 filter with ReLU activation.
# Padding is added to preserve width and height.
# Input Tensor Shape: [batch_size, 28, 28, 1]
# Output Tensor Shape: [batch_size, 28, 28, 32]
conv1 = tf.layers.conv2d(inputs=x_image, filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# Pooling Layer #1
# First max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 28, 28, 32]
# Output Tensor Shape: [batch_size, 14, 14, 32]
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
# Convolutional Layer #2
# Computes 64 features using a 5x5 filter.
# Input Tensor Shape: [batch_size, 14, 14, 32]
# Output Tensor Shape: [batch_size, 14, 14, 64]
conv2 = tf.layers.conv2d( inputs=pool1, filters=64, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# Pooling Layer #2
# Second max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 14, 14, 64]
# Output Tensor Shape: [batch_size, 7, 7, 64]
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
# Flatten tensor into a batch of vectors
# Input Tensor Shape: [batch_size, 7, 7, 64]
# Output Tensor Shape: [batch_size, 7 * 7 * 64]
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
# Dense Layer
# Densely connected layer with 1024 neurons
# Input Tensor Shape: [batch_size, 7 * 7 * 64]
# Output Tensor Shape: [batch_size, 1024]
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
# Add dropout operation; 0.6 probability that element will be kept
dropout = tf.layers.dropout( inputs=dense, rate=0.4, training=is_training)
# Logits layer
# Input Tensor Shape: [batch_size, 1024]
# Output Tensor Shape: [batch_size, 10]
logits = tf.layers.dense(inputs=dropout, units=10)
# Define a loss function (Multinomial cross-entropy) and how to optimize it
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y_))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(logits,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# Build graph
init = tf.global_variables_initializer()
# Avoid allocating the whole memory
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.333)
sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))
sess.run(init)
# Train graph
for i in range(2000):
# Get batch of 50 images
batch = mnist.train.next_batch(50)
# Print each 100 epochs
if i % 100 == 0:
# Calculate train accuracy
train_accuracy = accuracy.eval(session=sess, feed_dict={x_: batch[0], y_: batch[1], is_training: True})
print("step %d, training accuracy %g" % (i, train_accuracy))
# Train actually here
train_step.run(session=sess, feed_dict={x_: batch[0], y_: batch[1], is_training: False})
print("Test Accuracy:",sess.run(accuracy, feed_dict={x_: mnist.test.images, y_: mnist.test.labels, is_training: False}))
```
# 张量板
TensorBoard 是 TensorFlow 随附的基于 Web 的实用程序,可让您可视化构造的 TensorFlow 图。 最重要的是,它使您能够跟踪大量的统计数据或变量,这些数据或变量可能对训练模型很重要。 您可能希望跟踪的此类变量的示例包括训练损失,测试集准确性或学习率。 前面我们看到,我们可以使用张量板可视化损失函数的值。
要运行 TensorBoard,请打开一个新终端并输入以下内容:
```py
$ tensorboard --logdir=/somepath
```
在这里,`somepath`指向您的训练代码保存张量板日志记录数据的位置。
在代码内部,您需要通过为每个张量创建一个`tf.summary`来定义要可视化的张量。 因此,例如,如果我们要检查所有可训练变量和损失,则需要使用以下代码:
```py
with tf.Session() as sess:
"""Create your model"""
# Add all trainable variables to tensorboard
for var in tf.trainable_variables():
tf.summary.histogram(var.name, var)
# Add loss to tensorboard
tf.summary.scalar("softmax_cross_entropy", loss)
# Merge all summaries
merged_summary = tf.summary.merge_all()
# Initialize a summary writer
train_writer = tf.summary.FileWriter( /tmp/summarys/ , sess.graph)
train_writer.add_summary(merged_summary, global_step)
"""Training loop"""
```
我们需要创建一个`tf.summar.FileWriter`,它负责创建一个目录,该目录将存储我们的摘要日志。 如果在创建 FileWriter 时传递图形,则该图形也将显示在 TensorBoard 中。 通过传入 sess.graph,我们提供了会话正在使用的默认图。 在 TensorBoard 中显示图形的结果可能看起来像这样:
![](img/a500985f-bedd-457d-82bc-f0dec7d4ebb3.png)
# 其他类型的卷积
本章的目的是让您了解 CNN 是什么,它们的用途以及如何在 TensorFlow 中构造它们。 但是,在这一点上值得一提的是,当今还有其他类型的卷积运算用于不同的目的,我们将在后面的章节中更详细地介绍其中的一些。
现在,我们将仅按名称和使用位置提及它们:
* **深度卷积**:用于 MobileNets,旨在使卷积对移动平台友好
* **膨胀卷积(Atrous 卷积)**:它们具有称为膨胀率的额外参数,可让您以相同的计算成本获得更大的视野(例如 3x3 CONV 可以具有相同的视野 作为 5x5 CONV)
* **转置卷积(Deconvolutions)**:通常用于 CNN 自动编码器和语义分割问题
# 摘要
在本章中,我们向您介绍了机器学习和人工智能。 我们研究了什么是人工神经网络以及如何对其进行训练。 在此之后,我们研究了 CNN 及其主要组成部分。 我们介绍了如何使用 TensorFlow 训练您自己的 CNN 以识别数字。 最后,我们对 Tensorboard 进行了介绍,并了解了如何在 TensorFlow 中训练模型时如何使用它来帮助可视化重要的统计数据。
在下一章中,我们将更仔细地研究图像分类的任务,以及如何使用 CNN 和 TensorFlow 来解决此任务。
\ No newline at end of file
# TensorFlow 中的图像分类
图像分类是指根据图像内容将图像分类的问题。 让我们从分类的示例任务开始,其中图片可能是狗的图像,也可能不是。 某人可能要完成此任务的一种简单方法是,像在[第 1 章](../Text/1.xhtml)中所做的那样,获取输入图像,将其重塑为矢量,然后训练线性分类器(或其他某种分类器)。 ],设置和 TensorFlow 简介。 但是,您很快就会发现此主意不好,原因有几个。 除了不能很好地缩放到输入图像的大小之外,线性分类器将很难将一个图像与另一个图像分开。
与可以在图像中看到有意义的图案和内容的人类相反,计算机只能看到从 0 到 255 的数字数组。对于同一类的不同图像,这些数字在相同位置的广泛波动导致无法直接使用它们 作为分类器的输入。 从**加拿大高级研究学院****CIFAR** )数据集中获取的这 10 张示例狗图像完美地说明了此问题。 狗的外观不仅有所不同,而且它们在镜头前的姿势和位置也有所不同。 对于机器来说,每个图像一目了然,完全没有共同点,而我们人类却可以清楚地看到它们都是狗:
| ![](img/b2fdaadf-a05d-427b-9d59-32dfd2b8f848.png) | ![](img/6d49a152-47c2-4061-b439-058cf72e0f0e.png) | ![](img/506c2d82-6a12-490e-8d6f-8e27cfb1581e.png) | ![](img/9452b952-9a71-4f6f-91da-14e8b8bd8ab3.png) | ![](img/4be49236-884e-48e2-84b0-6f978c3f2b6b.png) | ![](img/70dcaa26-f2c5-4d15-8936-503d25614742.png) | ![](img/9c808ac0-8f30-47c7-88cb-d2af50925ed8.png) | ![](img/57205127-d998-4078-94a4-6b5ad5f34fd3.png) | ![](img/951cd609-983a-47bc-af4a-8cba9afdfad7.png) | ![](img/ff665bba-d432-4464-a9e1-723096fce210.png) |
一个更好的解决方案是告诉计算机从输入图像中提取一些有意义的特征,例如常见的形状,纹理或颜色。 然后,我们可以使用这些功能而不是原始输入图像作为分类器的输入。 现在,我们正在寻找图像中这些功能的存在,以告诉我们图像是否包含我们要识别的对象。
这些提取的特征在我们看来将仅仅是一个高维向量(但通常比原始图像空间要小得多),可以用作分类器的输入。 多年来开发的一些著名的特征提取方法是**尺度不变特征****SIFT** ),**最大稳定的末端区域****MSER [** ),**本地二进制模式****LBP** )和定向梯度( **HOG** )的**直方图。**
当使用卷积神经网络进行图像分类时,2012 年是计算机视觉(以及随后的其他机器学习领域)最大的转折点之一,这标志着如何解决这一任务(以及许多其他问题)的方式发生了转变。 我们不是专注于手工制作更好的特征以从图像中提取,而是使用数据驱动的方法来找到代表问题数据集的最佳特征集。 CNN 将使用大量训练图像,并自己学习代表数据的最佳特征,以解决分类任务。
在本章中,我们将介绍以下主题:
* 看一下用于分类的损失函数
* Imagenet 和 CIFAR 数据集
* 训练 CNN 对 CIFAR 数据集进行分类
* 数据 API 简介
* 如何初始化体重
* 如何规范化模型以获得更好的结果
# CNN 模型架构
图像分类模型的关键部分是其 CNN 层。 这些层将负责从图像数据中提取特征。 这些 CNN 图层的输出将是一个特征向量,就像以前一样,我们可以将其用作所选分类器的输入。 对于许多 CNN 模型,分类器将只是连接到我们 CNN 输出的完全连接层。 如[第 1 章](../Text/1.xhtml)*设置和 TensorFlow* 简介中所示,我们的线性分类器只是一个完全连接的层; 除了层的大小和输入会有所不同之外,这里就是这种情况。
重要的是要注意,分类或回归问题(例如本地化)(或其他使用图像的其他问题)所使用的 CNN 架构在本质上是相同的。 唯一真正的不同是,在 CNN 层完成特征提取之后会发生什么。 例如,一个差异可能是用于不同任务的损失函数,如下图所示:
![](img/55354505-d9ef-4239-9e38-332e7c1e3cd7.jpg)
当我们着眼于 CNN 可以解决的各种问题时,您会在本书中看到重复出现的模式。 显然,可以使用 CNN 从输入数据中提取一些有意义的特征向量来解决许多涉及图像的任务,然后根据任务以某种方式对其进行处理并将其馈入不同的损失函数。 现在,让我们通过查看常用的损失函数来开始并专注于图像分类任务。
# 交叉熵损失(对数损失)
图像分类的最简单形式是二进制分类。 在这里,我们有一个分类器,该分类器只有一个要分类的对象,例如 dog / no dog。 在这种情况下,我们可能使用的损失函数是二进制交叉熵损失。
真实标签 *p* 与模型预测 *q* 之间的交叉熵函数定义为:
![](img/d779d726-0be1-497d-9c99-80d12f4bd263.png)
*i* 是我们标签和预测的每个可能元素的索引。
但是,当我们处理只有两个可能结果 y = 1 和 y = 0 的二元情况时,可以简化 p ![](img/3650d030-8116-4a21-a375-705a9246f7b0.png) {![](img/0afb24f4-1a7e-4684-8840-d68759f8c533.png)}和 q ![](img/e65b665e-d509-4b75-a50c-b59e17395df2.png) {![](img/d7ae08fd-fd8d-4d8b-8683-4a14dcb66466.png)} 我们得到:
![](img/5ec5f2c3-86c8-406a-938a-ec371e7919d5.png)
这是等效的
迭代![](img/b2408251-310a-4215-8cad-5e4da25c52d0.png)训练示例,将要最小化的成本函数 *L* 变为:
![](img/07a806c2-2a49-41a4-ba69-c3adee95a705.png)
这在直觉上是正确的,因为当![](img/c4e8a23d-48cb-41ef-83b8-b99014f01caf.png)时,我们要最小化需要大![](img/c99b624f-2c53-4830-b652-097ff277404e.png)的![](img/b3c76e2e-6082-4ee3-9f9e-e4c9729fc551.png);当![](img/820eae1c-4566-4245-83a6-ed72af7690fd.png)时,我们要最小化![](img/723646a7-5c3e-4876-a5d5-118bd4e1fc44.png)的我们要最小化![](img/a2de4135-a00f-402a-952f-0a52075e3943.png)
在 TensorFlow 中,可以在`tf.losses`模块中找到二进制交叉熵损失。 知道我们模型的原始输出![](img/811ffc49-acd0-4f5c-a4e4-eab74c6e581c.png)的名称是 logits 很有用。 在将其传递给交叉熵损失之前,我们需要对其应用 **Sigmoid** 函数,以便我们的输出在 0 到 1 之间缩放。TensorFlow 实际上将所有这些步骤组合为一个操作,如 下面的代码。 TensorFlow 还将为我们平均分批处理损失。
```py
loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=labels_in, logits=model_prediction)
```
# 多类交叉熵损失
多类交叉熵损失用于多类分类中,例如[第 2 章](../Text/2.xhtml)*深度学习和卷积神经网络*中的 MNIST 数字分类问题。 像上面一样,我们使用交叉熵函数,经过几次计算,我们为每个训练示例获得了多类交叉熵损失 *L*
![](img/b53e5bab-5f7a-4cf0-b749-92b36255f365.png)
在此,![](img/66307b67-080c-4c61-ae4a-3fa14f58bdef.png)为 0 或 1,表示类别标签![](img/5e2d48c5-bbb7-4d73-a35f-21f619196f87.png)是否是用于预测![](img/1e880ac2-aa6e-4739-9ca9-c9b6f5a86036.png)的正确分类。 要使用此损耗,我们首先需要向模型中最终 FC 层的输出![](img/ee64b3f7-99fb-4146-b6cc-816dd90b96ae.png)添加 softmax 激活。 交叉熵与 softmax 的组合如下所示:
![](img/208109f6-4aed-4f92-bbfc-eb093331aadd.png)
知道我们模型的原始输出![](img/1a632480-8569-43ce-a34a-1475707ce340.png)的名称是 logits 很有用。 Logits 是传递给 softmax 函数的内容。 softmax 函数是 S 型函数的多类版本。 一旦通过 softmax 函数,我们就可以使用我们的多类交叉熵损失。 TensorFlow 实际上将所有这些步骤组合为一个操作,如下所示:
```py
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=model_logits, labels=labels_in))
```
我们必须使用`tf.reduce_mean`,因为我们将获得批次中每个图像的损失值。 我们使用`tf.reduce_mean`来获取批次的平均损失。
我们可以像上面一样再次使用`tf.losses`模块,特别是`tf.losses.softmax_cross_entropy`模块,然后我们不需要`tf.reduce_mean`,但我们决定向您展示一种不同的方式,以便您可以看到有很多方法可以使用 在 TensorFlow 中也是如此。 随着 TensorFlow 的发展,实现相同结果的不同方法也越来越多,而且通常没有比其他方法差很多的方法。
# 训练/测试数据集拆分
暂时请注意,我们需要将数据集分为两组:训练和测试。 正如[第 1 章](../Text/1.xhtml)*设置和 TensorFlow 简介,*中所述,这是因为我们需要以某种方式检查模型是否能够从其自身的训练样本中进行概括(是否 能够正确识别训练中从未见过的图像)。 如果我们的模型不能做到这一点,对我们来说就没有太大用处。
还有一些其他要记住的重要点:
* 训练和测试数据必须来自相同的分布(因此在拆分之前合并并混洗所有数据)
* 训练集通常大于测试集(例如,训练:占总数的 70%,测试:占总数的 30%)。
对于我们在前几章中要处理的示例,这些基础知识就足够了,但是在随后的章节中,我们将更详细地介绍如何为大型项目正确设置数据集。
# 数据集
在本节中,我们将讨论图像分类中使用的最重要和最著名的最新数据集。 这是必要的,因为对计算机视觉的任何细读都可能与它们重叠(包括本书!)。 在卷积神经网络到来之前,研究界在图像分类比赛中使用的两个主要数据集是 Caltech 和 PASCAL 数据集。
加州理工学院的数据集由加州理工学院建立,并发布了两个版本。 Caltech-101 于 2003 年发布,包含 101 个类别,每个类别约 40 至 800 张图像; Caltech-256 于 2007 年发布,具有 256 个对象类别,总共包含 30607 张图像。 这些图片是从 Google 图片和 PicSearch 收集的,其大小约为 300x400 像素。
Pascal **视觉对象类****VOC** )挑战成立于 2005 年。每年组织到 2012 年,它为*图像提供了广泛的自然图像的著名基准数据集。 类别分类,对象检测,分割和操作分类*。 它是一个多样化的数据集,包含来自各种大小,姿势,方向,照明和遮挡的 flickr 的图像。 从 2005 年(仅四个类别:自行车,汽车,摩托车和人,火车/验证/测试:包含 5 个图像的 2578 个注释对象的 1578 张图像)到 2012 年(二十个类别,火车/验证数据具有 11,530 张图片,包含 27,450 个 ROI 注释对象和 6,929 个细分)。
重大变化来自 PASCAL(VOC)2007 挑战赛,当时班级的数量从 4 个增加到 20 个,并且此后一直固定。 分类任务的评估指标已更改为平均精度。 仅在 VOC 2007 挑战赛之前提供测试数据的注释。
随着更复杂的分类方法的出现,前面的数据集是不够的,以下几节中介绍的 ImageNet 数据集和 CIFAR 数据集成为分类测试的新标准。
# 影像网
ImageNet 数据集由 Alex Berg(哥伦比亚大学),Jia Deng(普林斯顿大学)和 Lii-Fei Li(斯坦福大学)在 2010 年共同创建,旨在进行大规模视觉识别的测试比赛, *PASCAL 可视对象类挑战*,2010 年。数据集是代表 WordNet 内容的图像的集合。 WordNet 是英语的词汇数据库。 它以分层结构将英语单词分成称为**同义词集**的同义词集。 以下屏幕截图显示了名词的 WordNet 结构。 括号中的数字是子树中的同义词集的数量。
![](img/f6aecc46-d9e7-4334-8bb2-f26ac43a6fea.png)
图像分类算法的发展几乎解决了现有数据集上的分类难题,因此需要一个新的数据集,以实现大规模图像分类。 这更接近现实情况,在这种情况下,我们希望机器描述模拟人的能力的任意图像的内容。 与上一代产品的分类数量在 100 年代相比,ImageNet 提供了超过 1000 万个高质量图像,覆盖了 10,000 多个类。 这些类别中的许多类别是相互关联的,这使分类任务更具挑战性,例如,区分许多品种的狗。 由于数据集非常庞大,因此很难使用其中存在的所有类别对每个图像进行注释,因此按照惯例,每个图像仅被标记为一个类别。
自 2010 年以来,一年一度的 ImageNet 大规模视觉识别挑战赛(ILSVRC)挑战集中于图像分类,单对象定位和检测。 对象分类挑战的数据包括 120 万张图像(来自 1000 个类别/同义词),训练数据,50,000 张验证数据图像和 100,000 张测试数据图像。
在分类挑战中,用于评估算法的主要指标是前 5 位错误率。 该算法允许给出五个预测类别,并且如果至少一个预测类别与地面真相标签匹配,则不会受到惩罚。
正式地,如果我们让![](img/98d6f30f-1c72-4cec-92cf-f6ccd95c271d.png)为图像,让![](img/3c40eb89-0f79-41d8-8b51-dcbb7b3bbe2a.png)为地面真相标签。 然后,我们有了预测的标签![](img/55a0bddf-7126-4132-b169-819ad4a62bdd.png),其中至少一个等于![](img/537d3e8a-b1b3-4d82-97d5-3404bd605635.png)才能将其视为成功的预测。 考虑预测误差如下:
![](img/6436a6ce-fd4b-463c-b195-3c34040e17df.png)
那么,算法的最终错误就是测试图像上出错的比例,如下所示:
![](img/bd6af112-f4ec-4983-96cf-5b293eeb3e44.png)
Imagenet 是近年来深度学习蓬勃发展的主要原因之一。 在深度学习开始流行之前,ILSVRC 的前五位错误率大约为 28%,并且丝毫没有下降太多。 但是,在 2012 年,挑战赛的冠军 SuperVision 将前 5 名的分类错误降低到了 16.4%。 团队模型(现在称为 AlexNet)是一个深度卷积神经网络。 这项巨大的胜利唤醒了人们使用 CNN 的力量,它成为许多现代 CNN 体系结构的垫脚石。
在接下来的几年中,CNN 模型继续占主导地位,前 5 个错误率持续下降。 2014 年冠军 GoogLeNet 将错误率降低到 6.7%,而 ResNet 在 2015 年将错误率再次降低了一半,降至 3.57%。 此后,2017 年的赢家“ WMW 挤压和激励网络”产生了 2.25%的误差,出现了较小的改进。
# CIFAR
CIFAR-10 和 CIFAR-100 数据集是 Alex Krizhevsky,Vinod Nair 和 Geoffrey Hinton 收集的小型(与现代标准相比)图像数据集。 这些数据集被研究界广泛用于图像分类任务。 它们被认为具有挑战性,因为图像质量非常低并且图像中的对象有时是部分可见的。 同时,由于图像较小,因此数据集很方便,因此研究人员可以快速在它们上产生结果。 CIFAR-100 增加了挑战,因为每个类别的图像数量很少,并且类别的数量也很大。 CIFAR10 和 CIFAR100 数据集每个包含 60,000 张图像。 两个数据集中的图像均为 32x32x3 RGB 彩色图像。
在 CIFAR-10 中,有 10 个类别,每个类别有 6,000 张图像。 数据集分为 50,000 个训练图像和 10,000 个测试图像。 以下是 CIFAR-10 数据集的类列表和每个类的一些随机图像,因此您可以看到其外观:
![](img/ebce31e0-9bce-4667-971c-53b650f46389.jpg)
CIFAR-100 具有 100 个类别,每个类别 600 张图像。 这 100 个类别分为 20 个超类。 每个图像都有一个**精细**标签(它属于的类)和一个**粗糙**标签(它属于的超类)。 CIFAR-100 中的类和超类的列表可在 [https://www.cs.toronto.edu/~kriz/cifar.html](https://www.cs.toronto.edu/~kriz/cifar.html) 中找到。 将类别的数量从粗糙(20)增加到精细(100)有助于最大程度地提高类别间的可变性。 这意味着我们希望模型考虑图像中两个看起来相似的对象属于不同的类。 例如,一张床和一张沙发看起来相似但不完全相同,将它们放在单独的类中将确保它们与受训模型看起来不同。
CIFAR 的算法评估过程与 ImageNet 中的相同。 据 Saining Xie 等人报道,CIFAR-10 的报告最好的 top-1 误差为 3.58%,而 CIFAR-100 的误差为 17.31%。 深入神经网络的*聚合残差转换中,他们介绍了新颖的 ResNeXt 体系结构。 可以在[中找到有关在 CIFAR-10 和 CIFAR-100](http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html)* 上使用*深度学习结果进行图像分类的最新技术 http://rodrigob.github.io/are_we_there_yet/ build / classification_datasets_results.html 和 [https://github.com/RedditSota/state-of-the-art-result-for-machine-learning-problems](https://github.com/RedditSota/state-of-the-art-result-for-machine-learning-problems) 。*
# 加载 CIFAR
可以从前面提到的 Python,Matlab 和二进制版本的官方网站下载数据集。 有多种加载和读取这些数据集的方法。 实际上,在我们的 TensorFlow 实现中,我们使用 Keras 库( [https://keras.io/](https://keras.io/datasets/) )加载它,该库现在是`tf.keras`模块中 TensorFlow 的一部分。 在这里,我们提供了一些示例代码来加载 CIFAR-10 数据集,但是 CIFAR-100 数据集的加载方式几乎相同:
```py
import tensorflow as tf
from tf.keras.datasets import cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
print('x_train shape:',x_train.shape)
print('y_train shape:',y_train.shape)
print('x_test shape:',x_test.shape)
print('y_test shape:',y_test.shape)
print('x_train.shape[0]:',training samples)
print('x_test.shape[0]:',test samples)
# Convert class vectors to binary class matrices
y_train = tf.keras.utils.to_categorical(y_train,10)
y_test = tf.keras.utils.to_categorical(y_test,10)
```
此代码返回两个元组:
```py
x_train, x_test: uint8 array of RGB image data with shape (num_samples, 3, 32, 32)
y_train, y_test: uint8 array of category labels (integers in range 0-9) with shape (num_samples,)
```
前面代码的打印语句的输出如下:
```py
x_train shape:(50000,32,32,3
y_train shape:(50000,1)
x_test shape:(10000,32,32,3)
y_test shape:(10000,1)
```
同样,使用以下命令加载 CIFAR-100 数据集:
```py
from tf.keras.datasets import cifar100
(x_train, y_train), (x_test, y_test) = cifar100.load_data(label_mode='fine')
```
# 使用 TensorFlow 进行图像分类
在本节中,我们将向您展示如何实现相对简单的 CNN 架构。 我们还将研究如何训练它对 CIFAR-10 数据集进行分类。
首先导入所有必需的库:
```py
import fire
import numpy as np
import os
import tensorflow as tf
from tf.keras.datasets import cifar10
```
我们将定义一个将实现训练过程的 Python 类。 类名是`Train`,它实现两种方法:`build_graph``train`。 当执行主程序时,将触发`train`功能:
```py
class Train:
__x_ = []
__y_ = []
__logits = []
__loss = []
__train_step = []
__merged_summary_op = []
__saver = []
__session = []
__writer = []
__is_training = []
__loss_val = []
__train_summary = []
__val_summary = []
def __init__(self):
pass
def build_graph(self):
[...]
def train(self, save_dir='./save', batch_size=500):
[...]
if __name__ == '__main__':
cnn= Train()
cnn.train
```
# 建立 CNN 图
让我们通过`build_graph`函数进行详细介绍,该函数包含网络定义,损失函数和所使用的优化器。 首先,我们通过为输入定义占位符来启动函数。 我们将使用两个占位符在图表中提供数据和标签:`__x_``__y_`。 占位符`__x_`将保存我们输入的 RGB 图像,而占位符`__y_` 存储一个对应类别的热门标签。 在定义占位符形状的 *N* 部分时,我们使用`None`,因为这告诉 TensorFlow 该值可以是任何值,并且在执行图形时将提供该值:
```py
def build_graph(self):
self.__x_ = tf.placeholder("float", shape=[None, 32, 32, 3], name='X')
self.__y_ = tf.placeholder("int32", shape=[None, 10], name='Y')
self.__is_training = tf.placeholder(tf.bool)
```
然后,我们将在`name_scope`模型中定义我们的网络。 `Name_scope`返回定义 TensorFlow 操作时使用的上下文管理器。 该上下文管理器验证变量是否来自同一图,将该图设为默认图,并在该图中推送名称范围。
对于此模型,我们将构建一个具有三个卷积层,三个池化层和两个完全连接层的简单 CNN。 我们使用`tf.layers` API 来构建 CNN 层。 `tf.reshape`函数将张量从最后一个池化层重塑为一维张量,以匹配密集层期望接收的量。 最后一层的输出分配给`self.__logits`,它是将作为输入传递到我们的损失函数的张量:
```py
with tf.name_scope("model") as scope:
conv1 = tf.layers.conv2d(inputs=self.__x_, filters=64, kernel_size=[5, 5],
padding="same", activation=tf.nn.relu)
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
conv2 = tf.layers.conv2d(inputs=pool1, filters=64, kernel_size=[5, 5],
padding="same", activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
conv3 = tf.layers.conv2d(inputs=pool2, filters=32, kernel_size=[5, 5],
padding="same", activation=tf.nn.relu)
pool3 = tf.layers.max_pooling2d(inputs=conv3, pool_size=[2, 2], strides=2)
pool3_flat = tf.reshape(pool3, [-1, 4 * 4 * 32])
# FC layers
FC1 = tf.layers.dense(inputs=pool3_flat, units=128, activation=tf.nn.relu)
FC2 = tf.layers.dense(inputs=FC1, units=64, activation=tf.nn.relu)
self.__logits = tf.layers.dense(inputs=FC2, units=10)
```
下一步是在名称范围`loss_func`中定义损失函数。 此处使用的损失函数是 softmax 交叉熵,如前所述,我们使用`tf.reduce_mean`对整个批次的损失进行平均。 我们创建变量来保存训练`loss __loss`和验证损失`__loss_val`,并将这些标量添加到 TensorFlow 摘要数据中,以便稍后在 TensorBoard 中显示:
```py
with tf.name_scope("loss_func") as scope:
self.__loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.__logits,
labels=self.__y_))
self.__loss_val = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.__logits,
labels=self.__y_))
# Add loss to tensorboard
self.__train_summary = tf.summary.scalar("loss_train", self.__loss)
self.__val_summary = tf.summary.scalar("loss_val", self.__loss_val)
```
定义模型和损失函数后,我们需要指定将用于最小化损失的优化函数。 我们在这里选择的优化函数是 Adam 优化器,它在名称范围*优化器*中定义。
# 学习率安排
在上一章中,我们简要提到了在训练过程中保持恒定的学习率可能会出现的问题。 随着我们模型的开始学习,我们的初始学习率很可能会变得太大而无法继续学习。 梯度下降更新将开始超出或绕过我们的最小值; 结果,损失函数的值不会降低。 为了解决这个问题,我们可以不时降低学习率的值。 这个过程称为学习率调度,有几种流行的方法。
第一种方法是在训练过程中的固定时间步长(例如,当训练完成 33%和 66%时)降低学习率。 通常,当达到这些设置时间时,您会将学习率降低 10 倍。
第二种方法涉及根据时间步长的指数或二次函数降低学习率。 可以执行此操作的函数的示例如下:
```py
decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
```
通过使用这种方法,学习率会随着训练时间的推移而平稳降低。
最后一种方法是使用我们的验证集,并查看验证集上的当前准确性。 在验证准确性不断提高的同时,我们对学习率无能为力。 一旦验证准确性停止增加,我们就将学习率降低某种程度。 重复此过程,直到训练结束。
所有方法都可以产生良好的结果,当您进行培训以查看哪种方法更适合您时,可能值得尝试所有这些不同的方法。 对于此特定模型,我们将使用第二种方法,即学习速率呈指数衰减。 我们使用 TensorFlow 操作`tf.train.exponential_decay`来执行此操作,该操作遵循前面显示的公式。 作为输入,它采用当前的学习率,全局步长,衰减之前的步数和衰减率。
在每次迭代中,当前的学习速率都会提供给我们的 Adam Optimizer,后者使用`minimize`函数,该函数使用梯度下降来使损失最小化并将`global_step`变量增加 1。 最后,在训练期间,将`learning_rate``global_step`添加到摘要数据以在 TensorBoard 上显示:
```py
with tf.name_scope("optimizer") as scope:
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 1e-3
# decay every 10000 steps with a base of 0.96 function
learning_rate = tf.train.exponential_decay(starter_learning_rate, global_step,
1000, 0.9, staircase=True)
self.__train_step = tf.train.AdamOptimizer(learning_rate).minimize(self.__loss,
global_step=global_step)
tf.summary.scalar("learning_rate", learning_rate)
tf.summary.scalar("global_step", global_step)
```
尽管 Adam 优化器会自动为我们调整和降低学习率,但我们仍然发现,采用某种形式的学习率调度也可以改善结果。
一旦定义了图的所有组件,就将图中收集的所有摘要合并到`__merged_summary_op`中,并通过`tf.global_variables_initializer()`初始化图的所有变量。
自然,在训练模型时,我们希望将网络权重存储为二进制文件,以便我们可以将其加载回去以执行正向传播。 TensorFlow 中的那些二进制文件称为检查点,它们将变量名称映射到张量值。 要在检查点之间保存和还原变量,我们使用`Saver`类。 为避免填满磁盘,保护程序会自动管理检查点文件。 例如,他们每训练一次 *N* 小时,就只能保留 *N* 个最新文件或一个检查点。 在我们的例子中,我们将`max_to_keep`设置为`None`,这意味着将保留所有检查点文件:
```py
# Merge op for tensorboard
self.__merged_summary_op = tf.summary.merge_all()
# Build graph
init = tf.global_variables_initializer()
# Saver for checkpoints
self.__saver = tf.train.Saver(max_to_keep=None)
```
另外,我们可以指定`tf.GPUOptions`要使用的 GPU 内存比例。 对象会话封装了执行 op 和评估张量的环境。 创建`FileWriter`对象以将摘要和事件存储到文件后,`__session.run(init)`方法运行 TensorFlow 计算的一个步骤,方法是运行必要的图形片段以执行每个操作并评估在 init 中初始化的每个张量作为部分 图的:
```py
# Avoid allocating the whole memory
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.6)
self.__session = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))
# Configure summary to output at given directory
self.__writer = tf.summary.FileWriter("./logs/cifar10", self.__session.graph)
self.__session.run(init)
```
# tf.data API 简介
在继续之前,我们将看一下 TensorFlow 处理数据输入到我们可能训练的任何模型的方式。 TensorFlow `tf.data` API 为我们提供了轻松构建复杂的输入管道可能需要的所有工具。 您可能通常会构建的一个管道将涉及加载原始训练数据,对其进行一些预处理,改组,然后将其分批准备进行训练。 `tf.data` API 使我们能够使用简单且可重复使用的代码段轻松地完成所有这些步骤。
您需要了解`tf.data` API 的两个主要组件。 首先是`tf.data.Dataset`; 这就是您的原始数据。 更具体地说,它包含一系列元素,其中每个元素包含一个或多个张量对象。 对于图像分类任务,一个元素将是一个训练示例,并且它将由两个张量组成-一个张量用于图像,一个张量用于其相应的标签。
第二个成分是`tf.data.Iterator`。 这些允许您从数据集中提取元素,并充当数据集和模型代码之间的连接。 TensorFlow 中有几种不同类型的迭代器,它们都有不同的用途,涉及不同的使用难度。
创建数据集可以通过两种方式完成。 第一种方法是通过创建数据源。 一个简单的例子是使用`tf.data.Dataset.from_tensor_slices()`,它将根据一个或多个 Tensor 对象的切片创建一个数据集。 产生数据集的另一种方法是在现有数据集上使用数据集转换。 这样做将返回合并了所应用转换的新数据集。 重要的是要了解所有输入管道必须以数据源开头。 一旦有了`Dataset`对象,通常对它应用所有链接在一起的多个转换。
目前,一些简单的转换示例包括`Dataset.batch()``Dataset.repeat()`,它们从`Dataset`对象返回一批具有指定大小的批次,当`Dataset`内容到达末尾时,它将继续重复该内容。 一种可以多次遍历数据集的简单方法(count 参数)。
现在我们已经建立了数据集,我们可以使用`tf.data.Iterators`进行迭代并从中提取元素。 同样,有几种不同的迭代器可供使用,但是我们将使用的最简单的迭代器是一发迭代器。 该迭代器仅支持一次浏览数据集,但是设置非常简单。 我们通过在数据集上调用`make_one_shot_iterator()`方法并将结果分配给变量来创建它。 然后,我们可以在创建的迭代器上调用`get_next()`,并将其分配给另一个变量。
现在,无论何时在会话中运行此操作,我们都将遍历数据集一次,并将提​​取一个新批次以使用:
```py
def train(self, save_dir='./save', batch_size=500):
# Use keras to load the complete cifar dataset on memory (Not scalable)
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# Convert class vectors to binary class matrices.
y_train = tf.keras.utils.to_categorical(y_train, 10)
y_test = tf.keras.utils.to_categorical(y_test, 10)
# Using Tensorflow data Api to handle batches
dataset_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
dataset_train = dataset_train.shuffle(buffer_size=10000)
dataset_train = dataset_train.repeat()
dataset_train = dataset_train.batch(batch_size)
dataset_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
dataset_test = dataset_test.repeat()
dataset_test = dataset_test.batch(batch_size)
# Create an iterator
iter_train = dataset_train.make_one_shot_iterator()
iter_train_op = iter_train.get_next()
iter_test = dataset_test.make_one_shot_iterator()
```
```py
iter_test_op = iter_test.get_next()
# Build model graph
self.build_graph()
```
# 主要训练循环
一旦检索到数据并构建了图形,就可以开始我们的主要训练循环,该循环将继续进行 20,000 多次迭代。 在每次迭代中,都使用 CPU 设备获取一批训练数据,并调用`AdamOptimizer`对象的`__train_step.run`方法向前运行一次,向后运行一次。 每进行 100 次迭代,我们就会对当前的培训和测试批次进行一次前向传递,以收集培训和验证损失以及其他汇总数据。 然后,`FileWriter`对象的`add_summary`方法将提供的 TensorFlow 摘要:`summary_1``summary_2`包装在事件协议缓冲区中,并将其添加到事件文件中:
```py
# Train Loop
for i in range(20000):
batch_train = self.__session.run([iter_train_op])
batch_x_train, batch_y_train = batch_train[0]
# Print loss from time to time
if i % 100 == 0:
batch_test = self.__session.run([iter_test_op])
batch_x_test, batch_y_test = batch_test[0]
loss_train, summary_1 = self.__session.run([self.__loss, self.__merged_summary_op],
feed_dict={self.__x_: batch_x_train,
self.__y_: batch_y_train, self.__is_training: True})
loss_val, summary_2 = self.__session.run([self.__loss_val, self.__val_summary],
feed_dict={self.__x_: batch_x_test,
self.__y_: batch_y_test, self.__is_training: False})
print("Loss Train: {0} Loss Val: {1}".format(loss_train, loss_val))
# Write to tensorboard summary
self.__writer.add_summary(summary_1, i)
self.__writer.add_summary(summary_2, i)
# Execute train op
self.__train_step.run(session=self.__session, feed_dict={
self.__x_: batch_x_train, self.__y_: batch_y_train, self.__is_training: True})
```
训练循环结束后,我们将最终模型存储在带有`op __saver.save`的检查点文件中:
```py
# Save model
if not os.path.exists(save_dir):
os.makedirs(save_dir)
checkpoint_path = os.path.join(save_dir, "model")
filename = self.__saver.save(self.__session, checkpoint_path)
print("Model saved in file: %s" % filename)
```
# 模型初始化
随着我们向模型中添加越来越多的图层,使用反向传播训练它们的难度越来越大。 通过模型传递回去以更新权重的误差值随着我们的深入而变得越来越小。 这被称为消失梯度问题。
因此,在开始训练模型之前,需要注意的重要一件事是将权重初始化为什么值。 错误的初始化会使模型收敛非常慢,或者甚至根本不会收敛。
尽管我们不确切知道训练后我们的权重最终会变成什么样的值,但我们可以合理地预期,其中的一半将为正值,而另一半将为负值。
# 不要用零初始化所有权重
现在,我们可能倾向于认为将所有权重设置为零将实现最大的对称性。 但是,这实际上是一个非常糟糕的主意,并且我们的模型永远不会学到任何东西。 这是因为当您进行前向通过时,每个神经元都会产生相同的结果。 因此,在反向传播步骤中,所有权重将以相同的方式更新。 这意味着模型永远无法学习丰富的功能,因此请不要像这样初始化。
# 用平均零分布初始化
一个更好的主意是使用所有以零为中心的较小随机值初始化权重。 为此,我们可以使用均值为零和单位方差为零的正态分布的随机值,然后将其按某个较小的值进行缩放,例如 0.01。
这样做会破坏权重的对称性,因为它们都是随机且唯一的,这是一件好事。 计算向前和向后通过时,我们的模型神经元现在将以不同的方式进行更新。 这将使他们有机会学习许多不同的功能,这些功能将作为大型神经网络的一部分协同工作。
然后唯一需要担心的是我们设定的体重值有多小。 如果设置得太小,反向传播更新也将非常小,这可能会在更深层的网络中消失梯度问题。
下图显示了权重的要求之一(零均值):
![](img/e9dac409-e1b4-426d-a79c-ef52073c047c.png)
# Xavier-Bengio 和初始化器
*在了解训练深度前馈神经网络*的难度时,Xavier Glorot 和 Yoshua Bengio 表明,如果从均匀分布![](img/5a4de500-3124-48fe-95cc-3334edf40879.png)初始化每一层的权重,其中![](img/52111d38-7c16-45b3-98e6-8be01332fad5.png)是大小 在上一层中,对于乙状结肠激活函数,顶层(更靠近输出)的神经元迅速饱和为 0。我们知道,由于乙状结肠函数的形式,激活值 0 表示权重非常大,并且 反向传播的梯度接近零。 较小的梯度值会减慢学习过程,因为早期图层中的权重停止更新或停止学习。
因此,我们想要的是使权重在最初确定的时间间隔内均匀分布,也就是说,权重的方差应该在我们从底层移动到顶层时保持不变。 这将使错误平稳地流到顶层,从而使网络在训练期间收敛更快。
为了实现这一点,Glorot 和 Bengio 证明了对于单位导数为 0 的对称激活函数 f,每一层的权重方差必须如下:![](img/dc439c6f-2535-4f33-9412-d579615c20bb.png)
在此,![](img/51a6011e-63da-404f-9e0e-675dd447cab4.png)是到所讨论的层的单元数,![](img/0c0533e4-5f13-4662-a3f6-1aa67b730048.png)是在下一层的单元数。 这意味着权重必须从以下均匀分布中采样:
![](img/b802c1b0-4ed0-4d2f-87c4-57fd4027e470.png)
我们还可以从零均值和前面的方差的正态分布中采样权重。 对于 ReLu 激活功能,He 等人证明了这一点。 方差应该改为![](img/f093a9d2-6556-437c-b66d-541edccd858f.png)
因此,作者使用零均值高斯分布初始化其权重,其标准偏差(std)为![](img/54a6c0be-d7b4-4bc7-a075-16da0f29d2f0.png)。 然后将该初始化称为 He 初始化。
默认情况下,TensorFlow 的大部分`tf.layers`都使用 Glorot(xavier)初始化程序,但是我们可以覆盖它并指定我们自己的初始化。 在这里,我们展示了一个如何覆盖`conv2d`层的默认初始化器的示例:
```py
conv1 = tf.layers.conv2d(inputs=self.__x_, filters=64, kernel_size=[5, 5],
padding="same", activation=None,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.01),
bias_initializer=tf.zeros_initializer())
```
# 通过规范化来提高概括性
到目前为止,在本章中,我们已经看到了如何使用 TensorFlow 训练卷积神经网络来完成图像分类任务。 训练完模型后,我们将其遍历测试集,并从一开始就将其存储起来,以查看其在从未见过的数据上的性能如何。 在测试集上评估模型的过程向我们表明了在部署模型时模型将泛化的程度。 能够很好地概括的模型显然是理想的属性,因为它可以在许多情况下使用。
我们使用的 CNN 架构是可以提高模型泛化能力的方法之一。 要记住的一种简单技术是从设计模型开始就尽可能简单地使用很少的图层或滤镜。 由于非常小的模型很可能无法适应您的数据,因此您可以慢慢增加复杂性,直到不再发生适应不足的情况为止。 如果您以这种方式设计模型,则将限制过拟合的可能性,因为您不允许自己拥有的模型对于数据集而言过大。
但是,在本节中,我们将探索我们可以做的其他一些事情,以建立更好的机器学习模型,以及如何将它们纳入我们的培训过程中。 以下方法旨在防止过度拟合,并通过这样做,有助于创建更强大的模型,并能更好地进行泛化。 防止模型过度拟合的过程称为**正则化**
另一个可能也会发生并且看起来与过度拟合非常相似的问题是,如果您的训练数据集没有捕获您想要分类的所有事物。 例如,如果您正在训练狗分类器,但是您的训练图像仅包含贵宾犬的图像。 如果要在 Labradors 上测试此训练有素的分类器,则可能无法分类。 这种数据不平衡是一个单独的问题,将在后面的章节中解决。
# L2 和 L1 正则化
创建更强大模型的第一种方法是使用 L1 或 L2 正则化。 到目前为止,这些是最常见的正则化方法。 基本思想是在训练模型时,我们积极尝试使用这些权重的 L1 或 L2 范数对模型权重的值施加一些约束。
为此,我们在使用的任何损失函数中增加了一个额外的项。 对于 L1 正则化,我们添加的术语是![](img/c268cf6e-ae76-4959-97fa-cc3cec5a6481.png),对于 L2 正则化,我们添加的术语是![](img/ed95b274-32f1-4c6a-80fc-3843d1c55f48.png)。 在前面的术语中,![](img/1104730d-78f3-469a-bc47-be1042611784.png)是我们网络中的所有权重,![](img/325652fe-77e5-434c-ae47-8c4d05c6daa0.png)是称为**正则化强度**的超参数。 通过添加该术语,我们可以防止权重值变得太大。
因此,用于[L1 正则化]的[第 1 章](../Text/1.xhtml)*设置和 TensorFlow 简介*的 SVM 丢失函数,即![](img/cfe9a672-085c-42e6-a838-2f8620ea6afb.png)变为:
![](img/77172207-ce67-4347-b51e-bf6565a195fd.png)
在这里,我们添加了对网络所有权重求和的正则化项。 此处,![](img/79c3facc-6820-4331-81de-13ffd441a44e.png)是层索引,![](img/a768718b-3d2d-4584-825b-4b7007032c3b.png)是每一层的权重矩阵的索引。 L2 正则化的表达式看起来类似。
对于 L1 正则化,此额外项鼓励权重向量变得稀疏,这意味着许多权重值变为零。 结果,该模型变得不受噪声输入的影响,因为权重向量将仅使用重要输入的子集,这有助于避免过度拟合。
对于 L2 正则化,除了保持权重之和较低之外,这个额外的项还强制权重值均匀分布在权重向量上,以便模型稍微使用所有权重,而不是大量使用权重。 由于输入和权重之间的乘法交互作用,从直觉上讲,这是一个理想的属性,可帮助模型避免过度拟合。 L2 正则化有时也称为权重衰减; 这是因为在训练期间,您的所有权重都会因该项(L2 正则化项的导数)而线性减少或“衰减”。
请注意,在正则化期间我们不包括偏差项,而仅包括权重。 这是因为偏差项并不会真正影响模型的过拟合,因为它们以累加的方式影响输出,只是向上或向下移动而不是改变函数的形状。 包含它们没有害处,但是也没有好处,因此包含它们没有意义。
在下图中,您可能会注意到增加正则强度 lambda 会减少过度拟合。 高正则化项意味着网络变得接近线性,并且无法塑造复杂的决策边界。
![](img/4d15337d-40dd-44ad-8e45-39df3674a015.png)
我们可以通过获取所有权重并对每个权重应用 l2 范数,然后将它们全部加在一起来手动实现 L2 / L1 正则化,但是这对于大型模型来说很快就变得很乏味。 幸运的是,如果我们使用`tf.layers`,那么 TensorFlow 中有一种更简单的方法。 首先,我们设置了正则化器,如下所示:
```py
l2_reg = tf.contrib.layers.l2_regularizer(scale=0.001)
```
scale 参数是我们通常需要通过交叉验证找到并设置自己的 lambda。 如果将其设置为 0,则不会进行任何正则化。 现在,当我们创建任何图层时,我们会将正则化函数作为参数传递。 TensorFlow 将进行计算以获取我们需要添加到损失函数中的所有正则化项:
```py
# Example of adding passing regularizer to a conv layer.
reg_conv_layer = tf.layers.conv2d( inputs, filters, kernel_size, kernel_regularizer=l2_reg)
```
要添加我们的正则化术语,我们首先需要将它们全部收集起来。 幸运的是,TensorFlow 会自动为我们将所有正则化术语放到一个集合中,以便我们可以轻松访问它们。 TensorFlow 在`tf.GraphKeys`内存储一些与您创建的图相关的重要集合,例如可训练变量,汇总和正则化损失。 我们可以使用`tf.get_collection()`访问这些集合,并提供要获取的集合的名称。 例如,为了得到正则化损失,我们将编写以下内容:
```py
reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
```
这将返回一个列表,其中包含此集合中存储的所有张量。
您也可以使用`tf.get_collection(key='my_collection')`创建自己的集合,然后使用`tf.add_to_collection(name='my_collection', value=some_variable_to_add)`向其中添加变量。 如果使用提供的键已存在一个集合,则`tf.get_collection`将返回该集合而不是创建它。
现在我们有了正则化损失项,我们可以像这样将它们添加到我们通常的训练损失中,然后优化组合损失:
```py
train_loss=[...] # Training loss
combined_loss = tf.n_add(train_loss, reg_losses)
```
# 退出
我们将要讨论的另一种用于正则化的技术是一种称为 Dropout 的东西。 辍学是由 G.E. Hinton 于 2012 年提出的,它是一种简单的正则化方法,可带来很好的效果。 辍学背后的想法是,在每次训练迭代中,一层中的所有神经元都可以以随机概率(通常为 50%)打开和关闭。
这种打开和关闭迫使网络学习与往常相同的概念,但是要通过多个不同的路径。 训练后,所有神经元都保持打开状态,这些路径的行为就像是多个网络的集合,用于平均最终结果,从而提高了泛化能力。 它迫使权重分布在整个网络中,并且像正则化一样将权重保持在较低水平。
理解这个概念的另一种方法是建立一个由多个人共享相似知识的团队。 他们每个人都会对如何解决特定问题有自己的想法,这些经验的结合提供了解决问题的更好方法:
![](img/cce137ca-2838-44ac-b275-7e83d01a5ddb.png)
在下图中,我们显示了模型测试错误。 显然,通过辍学,测试集上的错误会减少。 请记住,与所有正则化一样,与不使用正则化相比,使用辍学会使您的训练损失增加,但是到最后,我们只对模型测试错误率降低(泛化)感兴趣:
![](img/eef8f1a2-e6e1-40aa-9c0e-8ef692a60665.png)
通常,辍学仅适用于完全连接的层,但也可以适用于卷积/池化层。 如果这样做,则将使用较低的 *p* (掉线的可能性),接近 0.2。 同样,您将辍学层放置在激活层之后。
要在 TensorFlow 模型中使用 dropout,我们在希望将 dropout 应用到的输入层上调用`tf.layers.dropout()`。 我们还必须指定我们要使用的辍学率,更重要的是,使用布尔值让 TensorFlow 知道我们的模型是否在训练中。 请记住,当我们在测试时使用模型时,我们会关闭辍学,而这个布尔值将为我们做到这一点。 因此,带有辍学的代码将如下所示:
```py
# Fully connected layer (in tf contrib folder for now)
fc1 = tf.layers.dense(fc1, 1024)
# Apply Dropout (if is_training is False, dropout is not applied)
fc1 = tf.layers.dropout(fc1, rate=dropout, training=is_training)
```
# 批处理规范层
之前,我们已经完成了权重的初始化工作,以使梯度下降优化器的工作更加轻松。 但是,好处仅在培训的早期阶段才能看到,并不能保证在后期阶段有所改善。 那就是我们转向另一个称为批处理规范层的伟大发明的地方。 在 CNN 模型中使用批处理规范层产生的效果与[第 2 章](../Text/2.xhtml)*深度学习和卷积神经网络*中看到的输入标准化大致相同。 现在唯一的区别是,这将在模型中所有卷积层和完全连接层的输出处发生。
批处理规范层通常将附加到每个完全连接或卷积层的末端,但是在激活功能之前,它将对层输出进行规范化,如下图所示。 它通过获取图层输出(一批激活)并减去批次平均值并除以批次标准偏差来执行此操作,因此图层输出具有零均值和单位标准偏差。 请注意,在激活函数之前或之后放置 batchnorm 是一个引起激烈争论的话题,但是两者都应该起作用。
![](img/3e1365ac-328e-48f0-8077-920bb837324e.png)
进行此标准化之后,批处理规范层还具有两个可学习的参数,这些参数将按比例缩放标准化的激活并将其转移到模型认为最有助于其学习的内容。 整个过程通过消除消失的梯度问题来帮助训练。 反过来,这又允许模型在训练时使用更高的学习率,因此可以减少迭代次数。
在训练过程中,记录平均值和标准偏差值的移动平均值。 然后在测试时使用这些值,而不是计算批次统计信息。
批处理规范层的一些优点如下:
* 改善梯度流动,允许训练更深层的网络(解决消失的梯度问题)
* 允许更高的学习率,使培训更快
* 减少对良好权重初始化的依赖(更简单的随机初始化)
* 给您的模型某种正则化效果
* 使得可以使用饱和非线性,例如 S 型
对于更多的数学读者,可以在批处理规范论文“批处理规范化:通过减少内部协变量偏移来加速深层网络训练”中找到更为正式的定义,这是一篇写得很好的论文,易于理解和解释。 更详细的概念。 如果假设我们有一个仅具有完全连接的层的简单神经网络,则正如我们在[第 1 章](../Text/1.xhtml)*设置和 TensorFlow* 简介中所看到的,每一层的激活将是 ![](img/9d0ff275-b662-433d-b367-ac3ac9b46b54.png)表格。
假设![](img/c0d727f5-4add-40b7-95df-07d67752638f.png)是非线性的,例如 S 型或 ReLU,然后将批量归一化![](img/fb58ac38-9b3e-4f29-b02e-ee58df896884.png)直接应用于每个单元,例如:
![](img/f6f795f4-9365-4b91-a0de-82a9af9f7fca.png)
在这里,可以忽略偏差,因为它将通过均值减法消除。 如果我们的批次大小为![](img/c70951bb-1f7b-4c20-ab3c-0bc131baabde.png),则标准化激活![](img/9ea2becf-9055-4ac0-af74-61edb68ed2d2.png)的计算如下:
![](img/56f97aa9-6ca0-4d54-b7ce-09718a1ec857.png)
![](img/f154fc9b-a832-4df3-b832-a13f4d407993.png)
![](img/6d1b8316-5196-494e-a9d4-f0c4362e4217.png)
![](img/beb96e74-a946-4584-ae02-9f859031dc96.png)
其中![](img/7efb4c88-3a90-42ce-9099-4f26eac1c76b.png)和![](img/8adf7c5a-5001-4ad1-9cf6-f9e470d6a2b7.png)是可学习的参数,它们将缩放并移动您的标准化激活。 网络可以使用这些参数来决定是否需要标准化以及需要多少标准化。 这是正确的,因为如果我们设置![](img/53c19172-7f05-423d-ad0a-ad31b5197e5b.png)和![](img/4fc9cbe4-89e5-4b0a-9e8e-c5751d18dc5e.png),则设置![](img/f646cc18-2504-47fd-9a31-90b5aeaaf880.png)
最后,这是一个如何在本章开始的分类示例代码中使用批处理规范层的示例。 在这种情况下,我们将 batchnorm 层放在卷积层之后和激活函数之前:
```py
conv3 = tf.layers.conv2d(inputs=pool2, filters=32, kernel_size=[5, 5],padding="same", activation=None)
conv3_bn = tf.layers.batch_normalization(inputs=conv3, axis=-1, momentum=0.9, epsilon=0.001, center=True,scale=True, training=self.__is_training, name='conv3_bn')
conv3_bn_relu = tf.nn.relu(conv3_bn)
pool3 = tf.layers.max_pooling2d(inputs=conv3_bn_relu, pool_size=[2, 2], strides=2)
```
# 摘要
在本章中,我们了解了 CNN 模型的构建方式,包括使用哪些损失函数。 我们研究了 CIFAR 和 Imagenet 数据集,并了解了如何训练 CNN 来对 CIFAR10 数据集进行分类。 为此,我们被引入了 TensorFlow 数据 API,这使加载和转换数据的任务变得更加容易。 最后,我们讨论了通过谈论初始化和正则化的不同方法来提高训练模型的质量的方法。
在下一章中,我们将解决更困难的对象检测,语义和实例分割任务。
\ No newline at end of file
# 目标检测与分割
从上一章我们知道,当我们在输入图像中只有一个类的实例时,图像分类才真正处理这种情况。 即使那样,它也只能为我们提供粗略的输出,让我们知道图像中存在什么对象,但不知道它在哪里。 一个更有趣的情况是,当我们想查找一个类的所有实例,甚至多个不同的类在输入图像中的位置时。
为了解决这个更具挑战性的问题,需要进行对象检测和分割。 这些是计算机视觉领域,直到最近仍然非常具有挑战性。 然而,将卷积神经网络应用于这些问题近年来引起了很多关注,因此,在大多数情况下,现在可以考虑解决这些问题。 在本章中,我们将看到 CNN 如何很好地解决这些困难的任务。
下图显示了不同解决方案分段,本地化,检测和实例分段之间的区别:
![](img/6fcfed2a-e8e5-4678-8b77-f062f301eb31.png)
在开始讨论对象检测之前,我们需要了解另一个重要概念-本地化。 它是改善分类和启用检测的关键构建块。 我们将看到这三个概念彼此密切相关,这是因为我们从图像分类到具有定位的分类,最后是对象检测。
在本章中,我们将学习以下有趣的主题:
* 图像分类与本地化
* 物体检测
* 语义分割
* 实例细分
* 如何构建卷积神经网络来执行所有这些任务
# 图像分类与本地化
在上一章学习了图像分类之后,我们现在知道对图像进行分类时,我们只是试图在该图像内输出对象的类标签。 通常,为了简化任务,图像中将只有一个对象。
展望未来,在许多情况下,我们也有兴趣在图像中找到对象的位置。 定位对象这一任务的名称称为 **localization** 。 在这种情况下,我们要产生的输出是围绕对象的盒子的坐标。 此框的名称是边界框或边界矩形。 关于定位的重要细节是,每个图像只能定位一个对象。
当我们建立一个负责预测类别标签以及感兴趣对象周围的边界框的模型时,称为带有局部化的**图像分类。**
# 本地化为回归
可以使用与我们在[第 3 章](../Text/3.xhtml)*TensorFlow* 中的图像分类中了解的网络架构相似的网络架构来实现本地化。
除了预测类标签外,我们还将输出一个标志,指示对象的存在以及对象边界框的坐标。 边界框坐标通常是四个数字,分别代表左上角的 *x**y* 坐标,以及框的高度和宽度。
例如,在这种情况下,我们有两个类别(C1(汽车)和 C2(人))进行预测。 我们网络的输出如下所示:
![](img/88cbda34-8fc1-42a5-8ad4-77f52816a8ea.png)
该模型的工作原理如下:
1. 我们将输入图像输入到 CNN。
2. CNN 产生一个特征向量,该特征向量被馈送到三个不同的 FC 层。 这些不同的 FC 层(或负责人)中的每一个都将负责预测不同的事物:对象存在,对象位置或对象类。
3. 训练中使用了三种不同的损耗:每个头部一个。
4. 计算当前训练批次的比率,以权衡给定物体的存在对分类和位置损失的影响。 例如,如果批次中只有 10%的物体图像,那么这些损失将乘以 0.1。
提醒一下:输出数字(即 4 个边界框坐标)称为**回归**
请注意,分类和回归之间的重要区别是分类时,我们获得离散/分类输出,而回归提供连续值作为输出。 我们在图中显示模型如下:
![](img/6a08d3d3-b216-46bf-9d8a-6ca5e8c715ff.png)
从图中可以清楚地看到三个完全连接的层,每个层都输出不同的损耗(状态,类和框)。 使用的损失是逻辑回归/对数损失,交叉熵/ softmax 损失和 Huber 损失。 胡贝尔损失是我们从未见过的损失。 这是用于回归的损失,是 L1 和 L2 损失的一种组合。
局部化的回归损失给出了图像中对象的地面真实边界框坐标与模型预测的边界框坐标之间的某种相似度度量。 我们在这里使用 Huber 损失,但是可以使用各种不同的损失函数,例如 L2,L1 或平滑 L1 损失。
分类损失和局部损失被合并并通过标量比加权。 此处的想法是,如果首先存在一个对象,则我们只对反向传播分类和边界框损失感兴趣。
此模型的完整损失公式如下:
![](img/23f8f2a5-2aef-4ef5-a0ab-b25718aa8330.png)
# TensorFlow 实施
现在,我们将介绍如何在 TensorFlow 中实现这种模型。 它与分类模型极为相似,不同之处在于,我们在末尾有多个输出层而不是只有一个,并且每个层都有自己的损失函数:
```py
def build_graph(self):
self.__x_ = tf.placeholder("float", shape=[None, 240, 320, 3], name='X')
self.__y_box = tf.placeholder("float", shape=[None, 4], name='Y_box')
self.__y_obj = tf.placeholder("float", shape=[None, 1], name='Y_obj')
# Training flag for dropout in the fully connected layers
self.__is_training = tf.placeholder(tf.bool)
with tf.name_scope("model") as scope:
conv1 = tf.layers.conv2d(inputs=self.__x_, filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
conv2 = tf.layers.conv2d(inputs=pool1, filters=64, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
conv3 = tf.layers.conv2d(inputs=pool2, filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
pool3 = tf.layers.max_pooling2d(inputs=conv3, pool_size=[2, 2], strides=2)
pool3_flat = tf.reshape(pool3, [-1, 40 * 30 * 32])
# 2 Head version (has object head, and bounding box)
self.__model_box = tf.layers.dense(inputs=pool3_flat, units=4)
self.__model_has_obj = tf.layers.dense(inputs=pool3_flat, units=1, activation=tf.nn.sigmoid)
with tf.name_scope("loss_func") as scope:
loss_obj = tf.losses.log_loss(labels=self.__y_obj, predictions=self.__model_has_obj)
loss_bbox = tf.losses.huber_loss(labels=self.__y_box, predictions=self.__model_box)
# Get ratio of samples with objects
batch_size = tf.cast(tf.shape(self.__y_obj)[0], tf.float32)
num_objects_label = tf.cast(tf.count_nonzero(tf.cast(self.__y_obj > 0.0, tf.float32)), tf.float32)
ratio_has_objects = (num_objects_label * tf.constant(100.0)) / batch_size
# Loss function that has an "ignore" factor on the bbox loss when objects is not detected
self.__loss = loss_obj + (loss_bbox*ratio_has_objects)
# Add loss to tensorboard
tf.summary.scalar("loss", self.__loss)
tf.summary.scalar("loss_bbox", loss_bbox)
tf.summary.scalar("loss_obj", loss_obj)
with tf.name_scope("optimizer") as scope:
self.__train_step = tf.train.AdamOptimizer(1e-4).minimize(self.__loss)
# Merge op for tensorboard
self.__merged_summary_op = tf.summary.merge_all()
# Build graph
init = tf.global_variables_initializer()
# Saver for checkpoints
self.__saver = tf.train.Saver(max_to_keep=None)
# Avoid allocating the whole memory
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.6)
self.__session = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))
# Configure summary to output at given directory
self.__writer = tf.summary.FileWriter("./logs/loc_logs", self.__session.graph)
self.__session.run(init)
```
# 本地化的其他应用
使用 CNN 在图像中输出兴趣点坐标的想法可以扩展到许多其他应用程序。 其中一些包括人体姿势估计( *DeepPose:通过深度神经网络进行人体姿势估计)*,如下所示:
![](img/301f40a0-4a5d-4355-bcc2-118d1c180495.png)
为训练图像中的对象定义了关键点/地标。 对于所有训练图像中的特定对象,这些关键点位置必须一致。
例如,在面部关键点检测中,比如说我们有兴趣定位眼睛,鼻子和嘴巴,我们必须在所有训练面部图像的眼睛,鼻子和嘴巴周围定义多个关键点。 然后,就像前面的图像一样,我们训练 CNN 以输出预测的关键点位置,然后对这些输出关键点坐标应用回归损失以训练 CNN。 在测试时,将输入图像馈入 CNN 以预测所有关键点位置。 下图显示了面部关键点检测:
![](img/aba4ce64-8355-48f9-ac0f-d5273a05bb1c.png)
# 对象检测作为分类–滑动窗口
对象检测与定位是一个不同的问题,因为我们可以在图像中包含数量可变的对象。 因此,如果我们将检测视为像定位一样简单的回归问题,处理可变数量的输出将变得非常棘手。 因此,我们将检测视为分类问题。
长期使用的一种非常常见的方法是使用滑动窗口进行对象检测。 想法是在输入图像上滑动固定大小的窗口。 然后,将窗口中每个位置的内容发送到分类器,该分类器将告诉我们该窗口是否包含感兴趣的对象。
为此,人们可以首先训练一个 CNN 分类器,其中包含我们想要检测的物体的小幅裁剪图像-调整大小与窗口大小相同。 汽车。 在测试时,固定大小的窗口会在要检测对象的整个图像中以滑动的方式移动。然后,我们的 CNN 会为每个窗口预测是否是一个对象(在这种情况下是汽车)。
仅使用一种尺寸的滑动窗口,我们只能检测一种尺寸的对象。 因此,要查找更大或更小的对象,我们还可以在测试时使用更大或更小的窗口,并在将其发送到分类器之前调整内容的大小。 或者,您可以调整整个输入图像的大小,并仅使用一个尺寸的滑动窗口,该窗口也将在这些调整大小的图像上运行。 两种方法都可以使用,但其想法是产生所谓的“比例尺金字塔”,以便我们可以检测图像中不同尺寸的物体。
这种方法的最大缺点是,各种比例的大量窗口可能会通过 CNN 进行预测。 这使得将 CNN 用作分类器在计算上非常昂贵。 同样对于大多数这些窗口,它们将始终不包含任何对象。
为了克服这个问题,已经进行了许多改进。 在以下各节中,我们将介绍为解决该问题而创建的各种技术和算法,以及较之以前的技术和算法如何进行了改进。
# 使用启发式技术指导我们(R-CNN)
为了避免在输入图像上每个可能的位置(大多数都不会包含对象)运行分类器,我们可以使用一些外部方法向我们建议可能的区域。 一种可以做到这一点的方法称为**选择搜索**
区域提议方法将在图像中提供类似斑点的矩形​​区域,这些区域可能包含感兴趣的对象。 这些区域是存在感兴趣对象的候选区域。 然后,仅将 CNN 分类器应用于这些建议的区域。 与滑动窗口方法相比,这大大减少了发送到 CNN 进行分类的农作物的数量。
该特定方法在 2013 年提出,并被称为 *R-CNN:具有 CNN* 的区域。 下图描述了 R-CNN 的过程:
![](img/686ba064-f451-4a2d-b691-320dc2bc08b9.png)
# 问题
R-CNN 在计算上仍然很昂贵,因为您必须对大约 2,000 个单独的区域提案运行 CNN。 结果,培训和测试都非常慢。 CNN 分类器依赖于通过选择性搜索进行检测而生成的固定数量的矩形候选窗口。 这种方法并不是最快的方法,而且由于无法从训练数据中了解提案区域,因此它们可能不是针对任务的最佳选择。
# 快速 R-CNN
2015 年,提出了快速 R-CNN 来解决 R-CNN 的速度问题。 在此方法中,主要的变化是我们在管道中获取投标区域的位置。 首先,我们通过 CNN 运行整个输入图像,而不是从输入图像中直接获取它们,并提取靠近网络末端的生成的特征图。 接下来,再次使用区域提议方法,以与 R-CNN 类似的方式从该特征图中提取候选区域。
以这种方式获取建议有助于重用和共享昂贵的卷积计算。 网络中位于网络下方的完全连接的层将分类并另外定位,仅接受固定大小的输入。 因此,使用称为 **RoI 池**的新层将特征图中建议的区域扭曲为固定大小(在下一节中进一步讨论)。 RoI 池会将区域大小调整为最后一个 FC 层所需的大小。 下图显示了整个过程:
![](img/067ed6db-52ab-4dff-9eac-8bb0f36c174a.jpg)
R-CNN 与 FastRCNN 的比较表明,后者在训练时快约 10 倍,而在测试时快约 150 倍(使用 VGG 架构作为主要 CNN 时)。
# 更快的 R-CNN
这项技术在 2015 年 Fast R-CNN 之后不久提出,解决了使用外部区域建议方法的需求,并消除了与之相关的计算成本。
该算法的主要区别在于,不是使用外部算法(例如选择性搜索)来创建提案,而是使用称为**区域提案网络****RPN** )的子网 为我们学习并提出建议。 在此屏幕快照中显示:
![](img/126d2b47-420b-482c-a03f-a43408f3fd04.jpg)
# 区域提案网
RPN 的工作是预测我们称为锚点的对象(本质上只是一个边界框)是否包含对象或仅是背景,然后完善此边界框的位置。
基本上,RPN 通过在最后一个 CNN 特征图上滑动一个小窗口(3 x 3)来做到这一点(同一特征图 Fast R-CNN 从中获得建议)。 对于每个滑动窗口中心,我们创建 *k* 固定锚框,并将这些框分类为是否包含对象:
![](img/7c57ffe8-af41-47a0-b295-5aeb633e866f.png)
在内部,在训练过程中,我们选择 IoU 最大的锚定边界框和地面真实边界框进行反向传播。
# RoI 汇聚层
RoI 池层只是最大池的一种,池的大小取决于输入的大小。 这样做可以确保输出始终具有相同的大小。 使用该层是因为完全连接的层始终期望输入大小相同,但是 FC 层的输入区域可能具有不同的大小。
RoI 层的输入将是建议和最后的卷积层激活。 例如,考虑以下输入图像及其建议:
![](img/fd24096b-b00d-449f-8124-137e82e2a96f.png)
这里,我们有一个表格,总结了方法之间的差异:
| | **R-CNN** | **快速 R-CNN** | **更快的 R-CNN** |
| 每个图像的测试时间 | 50 秒 | 2 秒 | 0.2 秒 |
| 加速 | 1 倍 | 25 倍 | 250 倍 |
| 准确性 | 66% | 66.9% | 66.9% |
# 从传统的 CNN 转换为完全卷积网络
对于有效的对象检测器而言,非常重要的一点是提高卷积,从而提高计算的重用性​​。 为此,我们将所有 FC 层转换为 CONV 层,如下图所示。
以这种方式实现我们的网络的目的是,他们可以使用比其最初设计的图像更大的图像作为输入,同时共享计算以使其效率更高。 将所有 FC 层都转换为 CONV 层的这种类型的网络的名称称为完全卷积网络(FCN)。
将 FC 层转换为 CONV 层的基本技术是使用与输入空间尺寸一样大的内核大小,并使用过滤器数来匹配 FC 层上的输出数。 在此示例中,我们期望输入图像为 14x14x3。
![](img/dd1a5963-27dc-4ede-b0a9-4671fdf0a655.jpg)
以我们为例,用 100 x 100 的输入补丁训练一个全卷积网络,并用 2,000 x 2,000 的输入图像进行测试,结果将是在 2000 x 2000 图像上运行 100 x 100 的滑动窗口 。 当使用较大的输入体积(如本例中所示)时,FCN 的输出将是一个体积,其中每个单元格对应于原始输入图像上 100x100 窗口补丁的一张幻灯片。
现在,每次我们使用比原始训练输入大的输入图像时,效果都将像我们实际上在整个图像上滑动分类器,但计算量却减少了。 通过这种方式,我们通过 CNN 的前向传递一步一步地使滑动窗口卷积:
![](img/df71541c-cdad-4bda-ae6e-5c8062047d8d.png)
# 单发检测器–您只看一次
在本节中,我们将继续介绍一种稍有不同的对象检测器,称为单发检测器。 单发检测器尝试将对象检测伪装为回归问题。 此类别下的主要架构之一是 YOLO 架构(您只看一次),我们现在将对其进行详细介绍。
YOLO 网络的主要思想是在不使用任何滑动窗口的情况下优化输入图像中各个位置的预测计算。为实现此目的,网络以大小为![](img/a70666c6-6cd7-4919-8f13-5e4a4ea873b5.png)单元格的网格形式输出特征图。
每个单元格都有 B * 5 + C 条目。 其中“ B”是每个单元格的边界框的数量,C 是类概率的数量,而 5 是每个边界框的元素(x,y:边界框相对于其所在单元格的中心点坐标 , *w* -相对于原始图像的边界框的宽度, *h* -相对于原始图像的边界框的高度,置信度:边界中对象存在的可能性 框)。
我们将置信度得分定义为:
![](img/f044a469-78d5-4625-bbcc-c6fa810e8d88.png)
如果单元格中没有对象,则将为零。 否则将等于地面真值框与预测框之间的 IOU。
请注意,网格的每个单元格都负责预测固定数量的边界框。
下图描述了作为 YOLO 网络输出的单元格条目的样子,它预测了形状的张量(N,N,B * 5 + C)。 网络的最后一个 conv 层将输出与栅格尺寸相同大小的要素图。
![](img/c0865621-4388-4c74-80d1-33cfc363f626.png)
中心坐标以及边界框的高度和宽度在[0,1]之间进行归一化。 下图显示了如何计算这些坐标的示例:
![](img/71f35c09-6130-40da-91af-468ba0a6d69f.png)
网络为每个单元格预测类别概率,边界框和这些框的置信度。
实际的 YOLO 网络具有 24 个卷积层,其后是 2 个完全连接的层。 但是,Fast YOLO 网络是 9 层,如下所示:
![](img/ba26c46a-1c3a-44c6-b82f-88cf0070814e.png)
另一个重要的一点是,即使每个对象似乎位于多个像元上,也将单独将其分配给一个栅格像元(基于此中心和像元距离)。
目前,我们可以想象在图像上可以检测到的对象数量将是网格大小。 稍后,我们将看到如何处理每个网格单元的多个对象。 (锚盒)
# 创建用于 Yolo 对象检测的训练集
为了创建 YOLO 的训练集,将与 YOLO 网络的输出特征图预测相同大小的网格放置在每个训练输入图像上。 对于网格中的每个像元,我们创建一个目标向量 Y,其长度为 B * 5 + C(即与上一节中的输出特征图网格像元大小相同)。
让我们以训练图像为例,看看如何为图像上的网格中的单元创建目标向量:
![](img/d9c09c60-4119-4811-afc0-dbee68ad7af2.png)
在上图中,考虑我们根据对象中心的最短距离来选择单元(在图像中,后车的中心最靠近绿色单元)。 如果我们看一下上面的训练图像,我们会注意到感兴趣的对象仅存在于一个单元格编号为 8 的单元格中。其余的单元格 1-7 和 9 没有任何感兴趣的对象。 每个单元的目标向量将具有 16 个条目,如下所示:
![](img/1240f8dd-6aba-4dde-b00a-384cac6c7295.png)
第一个条目是类别![](img/d1365f23-8d32-4254-ab51-1f228e448671.png)存在的置信度得分,对于没有对象的单元格中的两个锚定框,该得分均为 0。 其余值将是*无关*。 单元格编号 8 有一个对象,并且对象的边界框具有较高的 IOU。
对于大小为 NxM 的输入训练图像,训练后从 ConvNet 输出的目标向量的最终体积将为 3x3x16(在此玩具示例中)
数据集中每个图像的标签信息将仅包括对象的中心坐标及其边界框。 实施代码以使其与网络的输出向量相匹配是您的责任; 这些任务包括以下所列的任务:
1. 将每个中心点的图像空间转换为网格空间
2. 将图像空间上的边界框尺寸转换为网格空间尺寸
3. 查找图像空间上最接近对象的单元格
如果我们将每个单元格类别的概率乘以每个边界框的置信度,我们将获得一些可以用另一种算法(非最大值抑制)过滤的检测结果。
让我们将置信度定义为反映单元格上任何类对象是否存在的事物。 (请注意,如果单元格上没有对象,则置信度应为零,如果有对象,则置信度应为 IoU):
![](img/e0d3c104-c2bd-41f8-a3f7-7ad0d63c1e6c.png)
我们还需要定义一个条件类别概率; 给定对象 P(class | Pr)的存在,我们想要这样做是因为我们不希望损失函数在单元格上没有对象的情况下惩罚错误的类预测。 该网络仅预测每个单元格的一组类别概率,而不考虑框数*B。*
# 评估检测(并口交集)
在继续进行之前,我们需要知道如何衡量我们的模型是否正确检测到物体。 为此,我们计算会返回一个数字的联合交叉点(IoU),根据某个参考(地面真相)告诉我们检测的效果如何。 IoU 的计算方法是:将检测和地面真理框彼此重叠的区域除以检测和地面真理框所覆盖的总面积:
![](img/8a083f5a-2925-4206-abc5-7cdfc4a3ba0b.png)
这是一个糟糕,良好和出色的 IoU 的示例:
![](img/c0a67b71-e1f0-408e-a81e-0557ee553aa6.png)
按照惯例,如果 IoU 大于 0.5,我们认为这两个方框都匹配,并且在这种情况下,检测为真阳性。
IoU 为零表示框不相交,IoU 为 1 表示完美匹配。
在我们的检测器上,如果一个单元有多个锚定框,则 IoU 会帮助选择哪个对目标负责。我们选择具有最高实测值的 IoU 最高的锚定。
这是 IoU 的 Python 代码:
```py
def iou_non_vectorized(box1, box2):
# If one of the rects are empty return 0 (No intersect)
if box1 == [] or box2 == []:
return 0
# size of intersect divided by size of union of 2 rects
# Get rectangle areas format (left,top,right,bottom)
box_1_area = (box1[2] - box1[0] + 1) * (box1[3] - box1[1] + 1)
box_2_area = (box2[2] - box2[0] + 1) * (box2[3] - box2[1] + 1)
# Get the intersection coordinates (x1,y1,x2,y2)
intersect_x1 = max(box1[0], box2[0])
intersect_y1 = max(box1[1], box2[1])
intersect_x2 = min(box1[2], box2[2])
intersect_y2 = min(box1[3], box2[3])
# Calculate intersection area
intersect_area = (intersect_x2 - intersect_x1 + 1) * (intersect_y2 - intersect_y1
+ 1)
return intersect_area / float(box_1_area + box_2_area - intersect_area)
We can also change this to a vectorized form on Tensorflow
def tf_iou_vectorized(self, box_vec_1, box_vec_2):
def run(tb1, tb2):
# Break the boxes rects vector in sub-vectors
b1_x1, b1_y1, b1_x2, b1_y2 = tf.split(box_vec_1, 4, axis=1)
b2_x1, b2_y1, b2_x2, b2_y2 = tf.split(box_vec_2, 4, axis=1)
# Get rectangle areas format (left,top,right,bottom)
box_vec_1_area = (b1_x2 - b1_x1 + 1) * (b1_y2 - b1_y1 + 1)
box_vec_2_area = (b2_x2 - b2_x1 + 1) * (b2_y2 - b2_y1 + 1)
xA = tf.maximum(b1_x1, tf.transpose(b2_x1))
yA = tf.maximum(b1_y1, tf.transpose(b2_y1))
xB = tf.minimum(b1_x2, tf.transpose(b2_x2))
yB = tf.minimum(b1_y2, tf.transpose(b2_y2))
interArea = tf.maximum((xB - xA + 1), 0) * tf.maximum((yB - yA + 1), 0)
iou = interArea / (box_vec_1_area + tf.transpose(box_vec_2_area) - interArea)
return iou
op = run(self.tf_bboxes1, self. tf_bboxes2)
self.sess.run(op, feed_dict={self.tf_bboxes1: box_vec_1, self.tf_bboxes2: box_vec_2})
tic = time()
self.sess.run(op, feed_dict={self.tf_bboxes1: box_vec_1, self.tf_bboxes2: box_vec_2})
toc = time()
return toc - tic
```
我们也可以在 TensorFlow 上将其更改为矢量化形式,如下所示:
```py
def tf_iou_vectorized(self, box_vec_1, box_vec_2):
def run(tb1, tb2):
# Break the boxes rects vector in sub-vectors
b1_x1, b1_y1, b1_x2, b1_y2 = tf.split(box_vec_1, 4, axis=1)
b2_x1, b2_y1, b2_x2, b2_y2 = tf.split(box_vec_2, 4, axis=1)
# Get rectangle areas format (left,top,right,bottom)
box_vec_1_area = (b1_x2 - b1_x1 + 1) * (b1_y2 - b1_y1 + 1)
box_vec_2_area = (b2_x2 - b2_x1 + 1) * (b2_y2 - b2_y1 + 1)
xA = tf.maximum(b1_x1, tf.transpose(b2_x1))
yA = tf.maximum(b1_y1, tf.transpose(b2_y1))
xB = tf.minimum(b1_x2, tf.transpose(b2_x2))
yB = tf.minimum(b1_y2, tf.transpose(b2_y2))
interArea = tf.maximum((xB - xA + 1), 0) * tf.maximum((yB - yA + 1), 0)
iou = interArea / (box_vec_1_area + tf.transpose(box_vec_2_area) - interArea)
return iou
op = run(self.tf_bboxes1, self. tf_bboxes2)
self.sess.run(op, feed_dict={self.tf_bboxes1: box_vec_1, self.tf_bboxes2: box_vec_2})
tic = time()
self.sess.run(op, feed_dict={self.tf_bboxes1: box_vec_1, self.tf_bboxes2: box_vec_2})
toc = time()
return toc - tic
```
# 过滤输出
实际上,您的模型通常会返回同一对象的多个检测窗口。 为了解决这个问题,我们使用一种称为非最大抑制的算法。 该算法使用“ IoU 和对象的存在”作为启发式过滤这些多个框。 运作方式如下:
1. 丢弃所有包含物体的可能性低的框(pc <0.6)
2. 选择最有可能出现物体的盒子(标签上的 pc)
3. 丢弃与所选框高度重叠的所有框(IoU> 0.5)
4. 重复步骤 2 和 3,直到所有检测都被放弃或选择为止
我们将在检测器的预测时间上使用非最大抑制:
![](img/5b81db0b-6e3e-43c1-a9b2-38a8aea6eb73.png)
Tensorflow 已经具有实现非最大值抑制算法的功能,称为`tf.image.non_max_suppression`
# 锚盒
锚框预定义的模板框,具有一定的高宽比。 这些在 YOLO 中用于帮助检测单个网格单元中的多个对象。 我们根据可以检测到的物体类型的大致几何形状定义盒子的形状。
目前,正如所解释的,我们的模型将只能在每个网格单元中检测到一个对象,但是在大多数情况下,每个网格中可能有多个对象。 请记住,我们认为最靠近对象的像元是中心:
![](img/6b9c73cf-b45d-483f-ad70-bcd54d7462d6.png)
为了解决这个问题,我们需要锚点。 基本上,我们将在输出深度体积中添加预定义的边界框; 然后,在训练过程中,我们选择中心最接近特定单元格的对象,并选择与锚框具有最大 IoU 的边界框。 实际上,由于多个子网将负责在同一单元中查找其他对象,因此,锚定框的想法使网络更好地概括了检测范围。
# 在 Yolo 中进行测试/预测
现在将先前汽车图像中的图像视为我们的测试图像。 每个像元的预测向量的输出为:
![](img/a6b0764f-f0af-4fd1-bbc3-00fd2958c7fc.png)
请注意,**…**条目表示即使对于没有对象的单元格,预测向量中也会有一些随机值。 但是,在单元格 8 中,x,y,h,w 的预测值有望接近准确。
在最后阶段,我们可以使用非最大值抑制算法过滤每个像元中的多个预测边界框。
# 检测器损耗功能(YOLO 损耗)
作为定位器,YOLO 损失函数分为三个部分:负责查找边界框坐标,边界框分数预测和类分数预测的部分。 它们都是均方误差损失,并由预测和地面真实情况之间的一些标量元参数或 IoU 得分进行调制:
![](img/7b0edf3c-b27d-4d1a-952d-ad10176656fd.png)
成员 *1ij* obj 成员用于基于特定单元 i,j 上对象的存在来调制损耗:
* 如果在网格单元格 i 和第 i 个边界框中具有最高 IoU 的对象存在:1
* 否则:0
同样, *1ij noobj* 正好相反。
# 损失第 1 部分
![](img/957dd494-38eb-4b48-9961-30a36494c0dd.png)第一部分计算与预测的边界框位置坐标![](img/0d6e23ab-6be1-47e0-9001-cf98da676c4d.png)相关的损耗。 ![](img/dc710dce-5f37-4916-bcbf-4a3a46644025.png)是训练集中地面真实数据的边界框坐标。
![](img/87ac2a79-feb5-4ee8-aa26-a5339f3f683a.png)表示一个常数,当有错误时,该常数将给予更多的补偿。 B 是边界框的数量。 ![](img/7999da7f-1eb2-4a09-b5bc-5671ec7fa796.png)是网格中的单元数。
使用类似的公式来处理边界框的宽度/高度
![](img/61bd650c-ca68-4241-b81f-72e31d0d6616.png)
损失函数方程中宽度和高度的平方根用来反映小盒子中的小偏差比大盒子中的重要。 一般而言,这部分损失会对边界框的高度和宽度不正确进行惩罚。
# 损失第 2 部分
损失函数的这一部分计算与每个边界框预测变量的置信度得分相关的损失。
![](img/a96483b7-4839-448c-b67c-09b8c05d871c.png)
![](img/c4e8c779-49ae-4ec8-8edf-4f4ba443f7e0.png)是置信度分数(受物体的存在调制的项)。![](img/169b79a0-8a0a-4973-90ee-31786959345d.png)是带有地面真值框的预测边界框的 IOU。 参数![](img/36df6474-b17d-4fb0-b24b-f6ea0233f835.png)用于使无对象时的丢失关注度降低。
# 损失第 3 部分
分类损失是损失函数的最后一部分。
![](img/28ad37ab-56de-4f07-86d2-c4bcd0fc5dfb.png)
该损失是分类误差损失平方的总和。 同样,当单元上有一个对象时,术语![](img/f02c17f3-a6f0-4ec8-a662-039aa884c958.png)为 1,否则为 0。 我们的想法是,当存在对象时,我们不考虑分类错误。
![](img/da6e4b6e-cf0a-484e-8e34-1aa6aed0cade.png)这些术语可以掩盖我们在地面真相上有一个对象而在特定单元的模型输出中有一个对象的情况下的损失。 当地面真相与模型输出不匹配时,也是如此。
因此,例如,当特定单元格不匹配时,我们的损失将是:
![](img/37ea80d4-abb2-4e5c-b16a-bb1de4520ae5.png)
当我们有比赛时:
![](img/aea81ee0-d79f-4213-9343-be0e90e8c96e.png)
在实践中的实践中,您将尝试矢量化这种损失并避免 for 循环并提高性能,这对于 Tensorflow 之类的库尤其如此。
这是 YOLO 损失的 TensorFlow 实现:
```py
def loss_layer(self, predicts, labels, scope='loss_layer'):
with tf.variable_scope(scope):
predict_classes = tf.reshape(predicts[:, :self.boundary1], [self.batch_size, self.cell_size, self.cell_size, self.num_class])
predict_scales = tf.reshape(predicts[:, self.boundary1:self.boundary2], [self.batch_size, self.cell_size, self.cell_size, self.boxes_per_cell])
predict_boxes = tf.reshape(predicts[:, self.boundary2:], [self.batch_size, self.cell_size, self.cell_size, self.boxes_per_cell, 4])
response = tf.reshape(labels[:, :, :, 0], [self.batch_size, self.cell_size, self.cell_size, 1])
boxes = tf.reshape(labels[:, :, :, 1:5], [self.batch_size, self.cell_size, self.cell_size, 1, 4])
boxes = tf.tile(boxes, [1, 1, 1, self.boxes_per_cell, 1]) / self.image_size
classes = labels[:, :, :, 5:]
offset = tf.constant(self.offset, dtype=tf.float32)
offset = tf.reshape(offset, [1, self.cell_size, self.cell_size, self.boxes_per_cell])
offset = tf.tile(offset, [self.batch_size, 1, 1, 1])
predict_boxes_tran = tf.stack([(predict_boxes[:, :, :, :, 0] + offset) / self.cell_size,
(predict_boxes[:, :, :, :, 1] + tf.transpose(offset,
(0, 2, 1, 3))) / self.cell_size,
tf.square(predict_boxes[:, :, :, :, 2]),
tf.square(predict_boxes[:, :, :, :, 3])])
predict_boxes_tran = tf.transpose(predict_boxes_tran, [1, 2, 3, 4, 0])
iou_predict_truth = self.tf_iou_vectorized(predict_boxes_tran, boxes)
# calculate I tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
object_mask = tf.reduce_max(iou_predict_truth, 3, keep_dims=True)
object_mask = tf.cast((iou_predict_truth >= object_mask), tf.float32) * response
# calculate no_I tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
noobject_mask = tf.ones_like(object_mask, dtype=tf.float32) - object_mask
boxes_tran = tf.stack([boxes[:, :, :, :, 0] * self.cell_size - offset,
boxes[:, :, :, :, 1] * self.cell_size - tf.transpose(offset, (0, 2, 1, 3)),
tf.sqrt(boxes[:, :, :, :, 2]),
tf.sqrt(boxes[:, :, :, :, 3])])
boxes_tran = tf.transpose(boxes_tran, [1, 2, 3, 4, 0])
# class_loss
class_delta = response * (predict_classes - classes)
class_loss = tf.reduce_mean(tf.reduce_sum(tf.square(class_delta), axis=[1, 2, 3]), name='class_loss') * self.class_scale
# object_loss
object_delta = object_mask * (predict_scales - iou_predict_truth)
object_loss = tf.reduce_mean(tf.reduce_sum(tf.square(object_delta), axis=[1, 2, 3]), name='object_loss') * self.object_scale
# noobject_loss
noobject_delta = noobject_mask * predict_scales
noobject_loss = tf.reduce_mean(tf.reduce_sum(tf.square(noobject_delta), axis=[1, 2, 3]), name='noobject_loss') * self.noobject_scale
# coord_loss
coord_mask = tf.expand_dims(object_mask, 4)
boxes_delta = coord_mask * (predict_boxes - boxes_tran)
coord_loss = tf.reduce_mean(tf.reduce_sum(tf.square(boxes_delta), axis=[1, 2, 3, 4]), name='coord_loss') * self.coord_scale
```
# 语义分割
在语义分割中,目标是根据像素所属的对象类别标记图像的每个像素。 最终结果是一个位图,其中每个像素将属于某个类:
**![](img/5c1dcbee-f8a6-4a9f-a2c2-09d6ea91d4cc.png)**
有几种流行的 CNN 架构已被证明在分割任务中表现出色。 它们中的大多数是称为自动编码器的一类模型的变体,我们将在[第 6 章](../Text/6.xhtml)*自动编码器,变体自动编码器和生成模型*中详细介绍。 现在,他们的基本思想是首先在空间上将输入量减小为某种压缩形式,然后恢复原始的空间大小:
![](img/5043cb38-9cbf-4b58-b8b7-529a4e52031d.png)
为了增加空间大小,使用了一些常用的操作,其中包括:
* 最大分拆
* 去卷积/转置卷积
* 扩张/房曲卷积
我们还将学习语义分割任务中使用的 softmax 的新变体,称为**空间 softmax**
在本节中,我们将学习两个流行的模型,它们在语义分割上表现良好,并且具有非常简单的架构可供理解。 它们如下所示:
* FCN(全卷积网络)
* 保佑
需要解决的其他一些实现细节是:
* 最终的升采样层(Deconv)需要具有与分类一样多的过滤器,并且您的标签“颜色”需要与最后一层中的索引匹配,否则在训练过程中可能会遇到 NaN 问题
* 我们需要一个 Argmax 层来选择输出张量上概率最大的像素(仅在预测时间内)
* 我们的损失需要考虑输出张量上的所有像素
# 最大分拆
取消池操作用于恢复最大池操作的效果。 这个想法只是充当上采样器。 此操作已在一些较早的论文上使用,并且不再使用,因为您还需要 CONV 层来修补(低通滤波器)上采样的结果:
![](img/f32f83c1-2c6a-462c-b053-811239923262.png)
# 反卷积层(转置卷积)
这个运算相当不好地称为反卷积,这意味着它是卷积的逆运算,但实际上并非如此。 更恰当的名称是转置卷积或分数步卷积。
此图层类型为您提供了一种对输入体积进行升采样的学习方法,并且可以在每次需要将输入要素地图智能地投影到更高的空间时使用。 一些用例包括以下内容:
* 上采样(条纹转置卷积)== UNPOOL + CONV
* 可视化显着图
* 作为自动编码器的一部分
![](img/8cc1410a-dc11-4f64-9d0f-8a25950c7cdd.png)
在 Tensorflow 中,我们可以访问`tf.layers`中的转置卷积。 下面的示例将采用一个空间大小为 14 x 14 的输入,并使其通过`conv2d_transpose`层,其中输出空间大小为 28 x 28:
```py
# input_im has spatial dimensions 14x14 in this example
output = tf.layers.conv2d_transpose(inputs=input_im, filters=1, kernel_size=4, strides=2, padding='same')
```
选择`kernel_size`,步幅和填充方案时必须小心,因为它们都会影响输出空间大小。
# 损失函数
如前所述,分割模型的损失函数基本上是分类损失的扩展,但在整个输出向量中在空间上起作用:
![](img/10b33794-4d9c-44f6-bc61-d62a58737cd3.png)
```py
# Segmentation problems often uses this "spatial" softmax (Basically we want to classify each pixel)
with tf.name_scope("SPATIAL_SOFTMAX"):
loss = tf.reduce_mean((tf.nn.sparse_softmax_cross_entropy_with_logits(
logits=model_out,labels=tf.squeeze(labels_in, squeeze_dims=[3]),name="spatial_softmax")))
```
下图描述了用于语义分割的完全卷积网络的实现:
![](img/31176579-914b-4341-acb9-3a04313e89f4.png)
下图显示了 SegNet 架构:
![](img/29548f5a-df13-4d4a-a7d5-94861ec81a7a.png)
# 标签
如前所述,分割问题中的标签是一维图像,每个像素处的值与输出体积深度的索引匹配:
![](img/25e44ecf-5080-42ab-b130-4c8e6f1b6bb5.png)
# 改善结果
通常,一种用于改善分割输出结果的技术是在后期处理阶段使用条件随机场(CRF),其中要考虑图像的纯 RGB 特征和我们的网络所产生的概率:
![](img/752b45a8-16ce-48eb-99b7-a1ca80b40d40.png)
# 实例细分
实例分割是我们在本章中要讨论的最后一件事。 在许多方面,可以将其视为对象检测和语义分段的融合。 但是,与这两个问题相比,这绝对是难度增加。
通过实例分割,其思想是找到图像中一个或多个所需对象的每次出现,即所谓的实例。 找到这些实例后,即使它们属于同一类对象,我们也希望将它们彼此分开。 换句话说,标签既是类别感知的(例如汽车,标志或人),又是实例感知的(例如汽车 1,汽车 2 或汽车 3)。
实例分割的结果将如下所示:
![](img/34c0e8e4-24ad-4c4e-a582-62904b5bc9ae.png)
这与语义分割之间的相似性很明显; 我们仍然根据像素所属的对象来标记像素。 但是,尽管语义分割不知道某个对象在图像实例中出现了多少次,但是分割却知道。
这种知道图像中有多少个对象实例的能力也使该问题类似于对象检测。 但是,对象检测产生的对象边界要粗糙得多,这意味着被遮挡的对象更容易被遗漏,实例分割不会发生这种情况。
# 面具 R-CNN
Mask R-CNN 是一种最近的网络体系结构,通过提供简单,灵活的模型体系结构可以使此问题更易于解决。 该架构于 2017 年发布,旨在扩展更快的 R-CNN 的功能:
![](img/c3b0ddb7-49dc-4277-beee-9fa64ceb8bbb.png)
它采用现有的更快的 R-CNN 模型,并尝试通过向模型中添加一个分支来解决实例分割问题,该分支负责预测与分类和边界框回归头平行的对象蒙版。 在发布时,该架构被证明是有效的,并且在所有 COCO 挑战中均获得了最高荣誉。
# 摘要
在本章中,我们学习了对象定位,检测和分段的基础知识。 我们还讨论了与这些主题相关的最著名的算法。
在下一章中,我们将讨论一些常见的网络体系结构。
\ No newline at end of file
# VGG,启动模块,残差和 MobileNets
到目前为止,我们已经讨论了所有必要的构建块,以便能够实现常见问题(例如图像分类和检测)的解决方案。 在本章中,我们将讨论一些通用模型体系结构的实现,这些体系结构在许多常见任务中都表现出了很高的性能。 自从最初创建以来,这些体系结构一直很流行,并且在今天继续被广泛使用。
在本章的最后,您将对现有的不同类型的 CNN 模型及其在各种不同的计算机视觉问题中的用例进行了解。 在实现这些模型时,您将学习如何设计这些模型以及它们各自的优点。 最后,我们将讨论如何修改这些架构,以使培训和性能/效率更好。
总之,本章将涵盖以下主题:
* 如何提高参数效率
* 如何在 TensorFlow 中实现 VGG 网络
* 如何在 TensorFlow 中实现 Inception 网络
* 如何在 TensorFlow 中实施残留网络
* 如何实现对移动设备更友好的体系结构
# 替代大卷积
在开始之前,我们将首先学习可以减少模型使用的参数数量的技术。 首先,这很重要,因为它可以提高网络的泛化能力,因为使用该模型中存在的参数数量将需要较少的训练数据。 其次,较少的参数意味着更高的硬件效率,因为将需要更少的内存。
在这里,我们将从解释减少模型参数的重要技术入手,将几个小卷积级联在一起。 在下图中,我们有两个 3x3 卷积层。 如果回头看图右侧的第二层,可以看到第二层中的一个神经元具有 3x3 的接收场:
![](img/5eaf2681-cfa7-4907-b95f-c6204b5c4721.png)
当我们说“感受野”时,是指它可以从上一层看到的区域。 在此示例中,需要一个 3x3 区域来创建一个输出,因此需要一个 3x3 的接收场。
回溯到另一层,该 3x3 区域的每个元素在输入端也具有 3x3 接收场。 因此,如果我们将所有这 9 个元素的接受场组合在一起,那么我们可以看到在输入上创建的总接受场大小为 5x5。
因此,用简单的话来说,将较小的卷积级联在一起可以获得与使用较大卷积相同的接收场。 这意味着我们可以用级联的小卷积代替大卷积。
请注意,由于第一卷积层和输入文件深度之间的深度不匹配(输出的深度需要保持一致),因此无法在作用于输入图像的第一个卷积层上进行此替换:还应在图像上观察我们如何 计算每层参数的数量。
![](img/b41b8fa0-33ef-41f1-bf8e-7b9d3a4ede88.png)
在上图中,我们用三个 3x3 卷积替换了一个 7x7 卷积。 让我们自己计算一下,以减少使用的参数。
想象一下,在形状为 WxHxC 的输入体积上使用 C 滤波器进行 7x7 大小的卷积。 我们可以计算过滤器中的权数,如下所示:
![](img/65e0fde3-6ba1-4588-93b6-a945414cdfef.png)
现在,相反,如果我们层叠三个 3x3 卷积(代之以 7x7 卷积),我们可以如下计算其权重数:
![](img/6f819b52-e9db-4868-b320-cbbeb722b935.png)
在这里,我们可以看到参数比以前更少!
还要注意,在这三个卷积层的每一个之间,我们放置了 ReLu 激活。 与仅使用单个大卷积层相比,这样做会给模型带来更多的非线性。 增加的深度(和非线性)是一件好事,因为这意味着网络可以将更多的概念组合在一起,并提高其学习能力!
大多数新的成功模型的趋势是用许多级联在一起的较小卷积(通常为 3x3 大小)替换所有大型滤波器。 如前所述,这样做有两个巨大的好处。 它不仅减少了参数的数量,而且还增加了网络中非线性的深度和数量,这对于增加其学习能力是一件好事。
# 替代 3x3 卷积
也可以通过称为瓶颈的机制来简化 3x3 卷积。 与早期相似,这将具有正常 3x3 卷积的相同表示,但参数更少,非线性更多。
瓶颈通过使用以下 C 滤镜替换 3x3 卷积层而起作用:
* 具有 C / 2 滤波器的 1x1 卷积
* 具有 C / 2 滤波器的 3x3 卷积
* 带 C 滤镜的 1x1 卷积
这里给出一个实际的例子:
![](img/31cf352c-348d-47c5-8e03-5a7b48f04120.png)
从此示例中,我们将计算参数数量以显示该瓶颈的减少量。 我们得到以下内容:
![](img/d3fa1a7c-dbd1-47c7-bd95-9df99f61b68b.png)
这比仅使用 3x3 卷积层时得到的参数要少:
![](img/2b7ce629-e563-477b-9552-cf8a85339dfb.png)
一些网络体系结构,例如残差网络(我们将在后面看到),使用瓶颈技术再次减少了参数数量并增加了非线性。
# 虚拟网
VGGNet 由牛津大学的**视觉几何组****VGG** )创建,是真正引入堆叠更多层的想法的首批架构之一。 虽然 AlexNet 最初以其七层出现时被认为很深,但与 VGG 和其他现代体系结构相比,这现在已经很小了。
与只有 11x11 的 AlexNet 相比,VGGNet 仅使用空间大小为 3x3 的非常小的滤镜。 这些 3x3 卷积滤波器经常散布在 2x2 最大池化层中。
使用如此小的滤波器意味着可见像素的邻域也非常小。 最初,这可能给人的印象是,本地信息是模型所考虑的全部内容。 但是,有趣的是,通过依次堆叠小型滤波器,它提供了与单个大型滤波器相同的“接收场”。 例如,堆叠三批 3x3 滤镜将具有与一个 7x7 滤镜相同的接收场。
堆叠过滤器的这种洞察力带来了能够拥有更深的结构(我们通常会看到更好的结构)的优点,该结构保留了相同的接收场大小,同时还减少了参数数量。 本章后面将进一步探讨这个想法。
# 建筑
接下来,我们将看到 VGGNet 的体系结构,特别是包含 16 层的 VGG-16 风味。 所有卷积层都有空间大小为 3x3 的滤镜,并且随着我们深入网络,卷积层中滤镜的数量从 64 个增加到 512 个。
堆叠两个或三个卷积层然后合并的简单模块化设计使网络的大小易于增加或减小。 结果,VGG 成功创建并测试了具有 11、13 和 19 层的版本:
![](img/57103ce4-379c-47c9-914b-ab5c12c4b9f6.png)
# 参数和内存计算
VGG 最酷的功能之一是,由于其在 conv 层中的内核较小,因此使用的参数数量很少。 如果我们从[第 2 章](../Text/2.xhtml)*深度学习和卷积神经网络*记住,卷积层中的参数数量(减去偏差)可以计算如下:
![](img/ec08db79-cccc-4e58-88d5-167580308795.png)
因此,例如,第一层将具有以下参数:
![](img/5aa777f7-072b-48ea-a7cd-f3447c9f6347.png)
但是要注意,当涉及到模型末端的完全连接(密集)层时,这种数量很少的参数并不是这种情况,通常这是我们可以找到许多模型参数的地方。 如果像在 VGGNet 中一样,一个接一个地堆叠多个密集层,则尤其如此。
例如,第一个密集层将具有以下数量的参数:
![](img/33beb295-8ebe-4b4a-85c9-c104704b2ed2.png)
![](img/4ca076a4-7112-4d07-bdc1-044bf5fc8740.png)
到那时为止,这是所有参数的六倍以上!
如前所述,您需要在训练数据集中使用大量样本来消耗模型参数,因此最好避免过度使用完全连接的层来避免参数爆炸。 幸运的是,人们发现,如果最后只有一层而不是三层,那么 VGGNet 的工作原理几乎相同。 因此,删除这些完全连接的层会从模型中删除大量参数,而不会大大降低性能。 因此,如果您决定实施 VGGNet,我们建议您也这样做。
# 码
接下来,我们介绍负责在 Tensorflow 中构建 VGG-16 模型图的函数。 像本章中的所有模型一样,VGGNet 旨在对 Imagenet 挑战的 1,000 个类别进行分类,这就是为什么该模型输出大小为 1000 的向量的原因。 显然,可以为您自己的数据集轻松更改此设置,如下所示:
```py
def build_graph(self):
self.__x_ = tf.placeholder("float", shape=[None, 224, 224, 3], name='X')
self.__y_ = tf.placeholder("float", shape=[None, 1000], name='Y')
with tf.name_scope("model") as scope:
conv1_1 = tf.layers.conv2d(inputs=self.__x_, filters=64, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv2_1 = tf.layers.conv2d(inputs=conv1_1, filters=64, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
pool1 = tf.layers.max_pooling2d(inputs=conv2_1, pool_size=[2, 2], strides=2)
conv2_1 = tf.layers.conv2d(inputs=pool1, filters=128, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv2_2 = tf.layers.conv2d(inputs=conv2_1, filters=128, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
pool2 = tf.layers.max_pooling2d(inputs=conv2_2, pool_size=[2, 2], strides=2)
conv3_1 = tf.layers.conv2d(inputs=pool2, filters=256, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv3_2 = tf.layers.conv2d(inputs=conv3_1, filters=256, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv3_3 = tf.layers.conv2d(inputs=conv3_2, filters=256, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
pool3 = tf.layers.max_pooling2d(inputs=conv3_3, pool_size=[2, 2], strides=2)
conv4_1 = tf.layers.conv2d(inputs=pool3, filters=512, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv4_2 = tf.layers.conv2d(inputs=conv4_1, filters=512, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv4_3 = tf.layers.conv2d(inputs=conv4_2, filters=512, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
pool4 = tf.layers.max_pooling2d(inputs=conv4_3, pool_size=[2, 2], strides=2)
conv5_1 = tf.layers.conv2d(inputs=pool4, filters=512, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv5_2 = tf.layers.conv2d(inputs=conv5_1, filters=512, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
conv5_3 = tf.layers.conv2d(inputs=conv5_2, filters=512, kernel_size=[3, 3],
padding="same", activation=tf.nn.relu)
pool5 = tf.layers.max_pooling2d(inputs=conv5_3, pool_size=[2, 2], strides=2)
pool5_flat = tf.reshape(pool5, [-1, 7 * 7 * 512])
# FC Layers (can be removed)
fc6 = tf.layers.dense(inputs=pool5_flat, units=4096, activation=tf.nn.relu)
fc7 = tf.layers.dense(inputs=fc6, units=4096, activation=tf.nn.relu)
# Imagenet has 1000 classes
fc8 = tf.layers.dense(inputs=fc7, units=1000)
self.predictions = tf.nn.softmax(self.fc8, name='predictions')
```
# 关于 VGG 的更多信息
2014 年,VGG 在 Imagenet 分类挑战中获得第二名,在 Imagenet 本地化挑战中获得第一名。 正如我们所看到的,VGGNet 的设计选择是堆叠许多小的卷积层,从而可以实现更深的结构,同时具有更少的参数(如果我们删除了不必要的完全连接的层),则性能更好。 这种设计选择在创建强大而高效的网络方面非常有效,以至于几乎所有现代体系结构都复制了这种想法,并且很少(如果有的话)使用大型过滤器。
事实证明,VGG 模型可以在许多任务中很好地工作,并且由于其简单的体系结构,它是开始尝试或适应问题需求的理想模型。 但是,它确实有以下问题需要注意:
* 通过仅使用 3x3 层,尤其是在第一层,计算量不适用于移动解决方案
* 如前几章所述,由于逐渐消失的梯度问题,甚至更深的 VGG 结构也无法正常工作
* 原始设计中大量的 FC 层在参数方面是过大的,这不仅减慢了模型的速度,而且更容易出现过拟合的问题
* 使用许多池化层,目前认为这不是好的设计
# GoogLeNet
虽然 VGGNet 在 2014 年 Imagenet 分类挑战赛中排名第二,但我们将要讨论的下一个模型 GoogLeNet 在那一年是赢家。 它是由 Google 创建的,它引入了一种重要的方法来使网络更深,同时减少参数数量。 他们称他们提出了`Inception` 模块。 该模块填充了大部分 GoogLeNet 模型。
GoogLeNet 具有 22 层,参数几乎比 AlexNet 少 12 倍。 因此,除了更加精确之外,它还比 AlexNet 快得多。 创建`Inception`模块的动机是制作更深的 CNN,以便获得高度准确的结果,并使该模型可在智能手机中使用。 为此,在预测阶段,计算预算大约需要增加 15 亿次:
![](img/a792bf21-afb7-4617-90ee-9eaf988cddd9.png)
# 起始模块
起始模块(或层的块)旨在覆盖较大的区域,但也保持较高的分辨率,以便也可以在图像中查看重要的本地信息。 除了创建更深的网络外,起始块还引入了并行卷积的思想。 我们的意思是在前一层的输出上执行不同大小的并行卷积。
初始层的幼稚视图可以在这里看到:
![](img/5ea6732d-fe36-4d0d-8272-8e15beb3ac2c.png)
基本上,初始块的想法是使用所有可用的内核大小和操作来覆盖尽可能多的信息,并让反向传播根据您的数据决定使用什么。 在上图中看到的唯一问题是计算成本,因此该图在实践中会有所不同。
考虑我们之前看到的 5x5 分支,让我们检查一下它的计算成本:
![](img/b9e05229-a800-45dc-b675-4fad355b3c54.png)
![](img/564ae665-af37-4088-8ab8-2975d8c16ec8.png)
现在,考虑以下更改; 我们添加一个 1x1 CONV 来将 5x5 CONV 输入深度从 192 转换为 16:
![](img/8cc4d625-1193-4cd4-a91f-55adc41c642a.png)
![](img/a0d5631a-32bc-4136-ac2a-a12452ce341c.png)
如果您观察到,现在计算效率提高了 10 倍。 1x1 层会挤压大量深度(瓶颈),然后发送到 5x5 CONV 层。
考虑到这一瓶颈变化,真正的初始层要复杂一些:
![](img/ca3da890-3a5d-4c96-8320-af7cbc197cfd.png)
此外,在某些实现中,您可能会注意到有人试图在初始代码块中使用 Batchnorm 或 Dropout。
Googlenet 将只是许多级联的启动块。 在这段代码中,我们展示了如何创建一个起始块:
```py
# Reference: https://github.com/khanrc/mnist/blob/master/inception.py
import tensorflow as tf
def inception_block_a(x, name='inception_a'):
# num of channels: 384 = 96*4
with tf.variable_scope(name):
# Pooling part
b1 = tf.layers.average_pooling2d(x, [3,3], 1, padding='SAME')
b1 = tf.layers.conv2d(inputs=b1, filters=96, kernel_size=[1, 1], padding="same", activation=tf.nn.relu)
# 1x1 part
b2 = tf.layers.conv2d(inputs=x, filters=96, kernel_size=[1, 1], padding="same", activation=tf.nn.relu)
# 3x3 part
b3 = tf.layers.conv2d(inputs=x, filters=64, kernel_size=[1, 1], padding="same", activation=tf.nn.relu)
b3 = tf.layers.conv2d(inputs=b3, filters=96, kernel_size=[3, 3], padding="same", activation=tf.nn.relu)
# 5x5 part
b4 = tf.layers.conv2d(inputs=x, filters=64, kernel_size=[1, 1], padding="same", activation=tf.nn.relu)
# 2 3x3 in cascade with same depth is the same as 5x5 but with less parameters
# b4 = tf.layers.conv2d(inputs=b4, filters=96, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
b4 = tf.layers.conv2d(inputs=b4, filters=96, kernel_size=[3, 3], padding="same", activation=tf.nn.relu)
b4 = tf.layers.conv2d(inputs=b4, filters=96, kernel_size=[3, 3], padding="same", activation=tf.nn.relu)
concat = tf.concat([b1, b2, b3, b4], axis=-1)
return concat
```
# 有关 GoogLeNet 的更多信息
GoogLeNet 的主要优点是,它比 VGG 更为准确,同时使用的参数更少,计算能力也更低。 主要的缺点仍然是,如果我们开始堆叠很多初始层,梯度将消失,而且整个网络具有多个分支和多个损耗的设计相当复杂。
# 残留网络
在前面的部分中,已经证明了网络的深度是有助于提高准确性的关键因素(请参见 VGG)。 TensorFlow 中的[第 3 章](../Text/3.xhtml)*图像分类中也显示,可以通过正确的权重初始化和批处理归一化来缓解深度网络中梯度消失或爆炸的问题。 但是,这是否意味着我们添加的层越多,我们得到的系统就越准确? 亚洲研究机构 Microsoft 的*用于图像识别的深度残差学习*的作者发现,只要网络深度达到 30 层,准确性就会达到饱和。 为了解决此问题,他们引入了一个称为残差块的新层块,该块将上一层的输出添加到下一层的输出中(请参见下图)。 Residual Net 或 ResNet 在非常深的网络(甚至超过 100 层!)中都显示了出色的结果,例如 152 层的 ResNet 赢得了 2015 LRVC 图像识别挑战,其前 5 个测试错误为 3.57。 事实证明,诸如 ResNets 之类的更深层网络要比包括 Inception 模块(例如 GoogLeNet)在内的更广泛的网络更好地工作。*
![](img/d8ad63df-15dd-4d20-b918-a3f18767a9c8.png)
让我们更详细地了解残差块的外观以及其功能背后的直觉。 如果我们有一个输入![](img/54cb6769-dfa9-4ed2-8a98-04904ad5ed2e.png)和一个输出![](img/0df834df-790e-4fd2-b5fd-f6d8afbd04fe.png),则存在一个将![](img/ac3e4947-4de7-4f5d-9b05-9a892b4aa2b8.png)映射到![](img/429cb4f4-0cb2-4408-80d7-8185e46ae29c.png)的非线性函数![](img/2bc69477-f997-406b-9f65-4cd1d59170df.png)。 假设函数![](img/2bc69477-f997-406b-9f65-4cd1d59170df.png)可以由两个堆叠的非线性卷积层近似。 然后,残差函数![](img/04c577da-ec4e-42e8-8a2e-659b7a2570bd.png)也可以近似。 我们可以等效地写为![](img/6ff3c055-c8f0-41e4-adbb-655f94f1f14f.png),其中![](img/076d8c2c-5fd2-4cd0-90ee-002c1b80fd2b.png)表示两个堆叠的非线性层,![](img/8b38461a-1ec6-4f8f-a0d2-0977201c727c.png)标识函数(输入=输出)。
更正式地说,对于通过网络的前向传递,如果![](img/48890ce8-4d40-4ced-9c20-0f0fe7258f49.png)是来自![](img/3cbd4bac-33c6-44e9-855c-4559561e7400.png)层的张量,并且![](img/cf39e814-f34f-4ee6-8669-04d386f69236.png)和![](img/290b2b0d-9cc0-433f-9797-31ecf9cf0db5.png)是当前层和先前层的权重矩阵,则输入![](img/bca942f2-dddf-4e8d-aaf4-48722f648d63.png)到 下一层![](img/32d5f07f-1c7c-40ab-9cbf-695177d3cfd2.png)
![](img/2a75b154-8239-4fce-968d-441d25d69a27.png)
其中![](img/9848c713-134c-4eb9-9f35-3d3076854a6d.png)是非线性激活函数,例如 ReLu 和![](img/75a30ef9-ee69-49eb-92e5-738eeef18b10.png),即两层堆叠卷积。 ReLu 函数可以在添加 x 之前或之后添加。 剩余的块应由 2 层或更多层组成,因为一层的块没有明显的好处。
为了理解该概念背后的直觉,我们假设我们有一个经过训练的浅层 CNN,其更深的对应层具有与浅层 CNN 相同的层,并且在它们之间随机插入了一些层。 为了拥有一个与浅层模型至少具有相似性能的深层模型,附加层必须近似标识函数。 但是,要学习具有 CONV 层堆栈的标识函数比将残差函数推为零要困难得多。 换句话说,如果单位函数是最优解,则很容易实现![](img/54b2d5db-424c-4399-b2d3-081897501f57.png),因此很容易实现![](img/ffc658b8-7e88-4b94-8f69-53d3904a01fc.png)
另一种思考的方式是,在训练期间,特定的层不仅会从上一层学习一个概念,还会从它之前的其他层学习一个概念。 这比只从上一层学习概念要好。
在实现方面,我们应注意确保![](img/269bb145-9df7-475f-b3cb-5bdd3993537b.png)和![](img/47cbd75c-bae1-4ed3-86b7-ad75747293b1.png)的大小相同。
查看残差块的重要性的另一种方法是,我们将为梯度创建一个“高速公路”(加法块),以避免随着梯度的增加而消失的梯度问题!
以下代码将向您展示如何创建残差块,它是残差网络的主要构建块:
```py
# Reference
# https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/learn/resnet.py
import tensorflow as tf
from collections import namedtuple
# Configurations for each bottleneck group.
BottleneckGroup = namedtuple('BottleneckGroup',
['num_blocks', 'num_filters', 'bottleneck_size'])
groups = [
BottleneckGroup(3, 128, 32), BottleneckGroup(3, 256, 64),
BottleneckGroup(3, 512, 128), BottleneckGroup(3, 1024, 256)
]
# Create the bottleneck groups, each of which contains `num_blocks`
# bottleneck groups.
for group_i, group in enumerate(groups):
for block_i in range(group.num_blocks):
name = 'group_%d/block_%d' % (group_i, block_i)
# 1x1 convolution responsible for reducing dimension
with tf.variable_scope(name + '/conv_in'):
conv = tf.layers.conv2d(
net,
filters=group.num_filters,
kernel_size=1,
padding='valid',
activation=tf.nn.relu)
conv = tf.layers.batch_normalization(conv, training=training)
with tf.variable_scope(name + '/conv_bottleneck'):
conv = tf.layers.conv2d(
conv,
filters=group.bottleneck_size,
kernel_size=3,
padding='same',
activation=tf.nn.relu)
conv = tf.layers.batch_normalization(conv, training=training)
# 1x1 convolution responsible for restoring dimension
with tf.variable_scope(name + '/conv_out'):
input_dim = net.get_shape()[-1].value
conv = tf.layers.conv2d(
conv,
filters=input_dim,
kernel_size=1,
padding='valid',
activation=tf.nn.relu)
conv = tf.layers.batch_normalization(conv, training=training)
# shortcut connections that turn the network into its counterpart
# residual function (identity shortcut)
net = conv + net
```
# 移动网
我们将以一个新的 CNN 系列结束本章,该系列不仅具有较高的准确性,而且更轻巧,并且在移动设备上的运行速度更快。
由 Google 创建的 MobileNet 的关键功能是它使用了不同的“三明治”形式的卷积块。 它不是通常的(`CONV``BATCH_NORM,RELU`),而是将 3x3 卷积拆分为 3x3 深度卷积,然后是 1x1 点向卷积。他们称此块为深度可分离卷积。
这种分解可以减少计算量和模型大小:
![](img/2e2654a7-06d4-4019-8fbc-91babc5c5ce4.png)
# 深度可分离卷积
这个新的卷积块(`tf.layers.separable_conv2d`)由两个主要部分组成:深度卷积层,然后是 1x1 点式卷积层。 该块与普通卷积有以下几种不同:
* 在正常卷积层中,每个滤波器 F 将同时应用于输入通道上的所有通道(F 应用于每个通道然后求和)
* 这个新的卷积 F 分别应用于每个通道,并且结果被级联到某个中间张量(多少由深度倍数 DM 参数控制)
相对于标准卷积,深度卷积非常有效。 但是,它仅过滤输入通道,并且不将它们组合以创建新功能。
现在,将使用 1x1 转换层将深度输出张量映射到某些所需的输出通道深度,该转换层将在通常在标准卷积层中发生的通道之间进行混合。 区别在于 DM 参数可用于丢弃一些信息。 同样,1x1 转换仅用于调整音量大小。
# 控制参数
MobileNets 使用两个超参数来帮助控制精度和速度之间的折衷,从而使网络适合您要定位的任何设备。 这两个超参数如下:
* **宽度倍增器**:通过统一减少整个网络中使用的滤波器数量,控制深度 CONV 精度
* **分辨率倍增器**:只需将输入图像缩小到不同大小
# 有关 MobileNets 的更多信息
对于任何神经网络设计,MobileNets 都具有一些最佳的精度,速度和参数比率。
但是,目前尚无良好(快速)的深度卷积实现可在 GPU 上运行。 结果,训练可能会比使用正常的卷积运算慢。 但是,此网络目前真正发挥作用的地方是小型 CPU 设计,提高的效率更加明显。
# 摘要
在本章中,我们向您介绍了各种卷积神经网络设计,这些设计已经证明了它们的有效性,因此被广泛使用。 我们首先介绍牛津大学 VGG 的 VGGNet 模型。 接下来,在最终讨论微软的 Residual Net 之前,我们先使用 Google 的 GoogLeNet。 此外,我们还向您展示了一种更高级的新型卷积,该模型在名为 MobileNet 的模型设计中具有特色。 在整个过程中,我们讨论了使每个网络如此出色的不同属性和设计选择,例如跳过连接,堆叠小型过滤器或启动模块。 最后,给出了代码,向您展示了如何在 TensorFlow 中写出这些网络。
在下一章中,我们将讨论一种称为生成模型的新型模型,该模型将使我们能够生成数据。
\ No newline at end of file
# 自动编码器,变体自动编码器和生成对抗网络
本章将介绍一种与到目前为止所看到的模型稍有不同的模型。 到目前为止提供的所有模型都属于一种称为判别模型的模型。 判别模型旨在找到不同类别之间的界限。 他们对找到 *P(Y | X)*-给定某些输入 *X* 的输出 *Y* 的概率感兴趣。 这是用于分类的自然概率分布,因为您通常要在给定一些输入 *X* 的情况下找到标签 *Y,*
但是,还有另一种类型的模型称为生成模型。 建立了生成模型以对不同类的分布进行建模。 他们对找到 *P(Y,X)*-输出 *Y* 和输入 *X* 一起出现的概率分布感兴趣。 从理论上讲,如果您可以捕获数据中类别的概率分布,则将了解更多信息,并且可以使用贝叶斯规则来计算 *P(Y | X)*
生成模型属于无监督学习算法的类别。 无监督意味着我们不需要标签数据。
本章列出了一些我们将要学习的关键主题:
* 汽车编码器
* 变体自动编码器
* 生成对抗网络
* 实施各种生成模型以生成手写数字
# 为什么要生成模型
在下图中,我们可以看到生成模型和判别模型之间的主要区别。 使用判别模型,我们通常尝试找到在数据中不同类别之间进行区分或“区分”的方法。 但是,使用生成模型,我们尝试找出数据的概率分布。 在图示中,分布由包含较小圆圈的蓝色和黄色大斑点表示。 如果我们从数据中学到这种分布,我们将能够采样或“生成”应该属于它的新数据点,例如红色三角形。
![](img/b588c84d-43c3-4d91-a8b4-9f39f4608318.png)
尝试捕获数据集的概率分布具有以下用例:
* 使用未标记的数据预训练模型
* 扩展数据集(理论上,如果您捕获数据的概率分布,则可以生成更多数据)
* 压缩数据(有损)
* 创建某种模拟器(例如,可以通过四个输入来控制四轴飞行器;如果捕获此数据并在其上训练生成模型,则可以学习无人机的动态)
使用生成模型时的期望是,如果我们能够创建类似于原始输入数据的新数据,则我们的模型必须了解一些有关数据分布的知识。
训练了生成神经网络模型,以产生类似于训练集的数据样本。 由于模型参数的数量小于训练数据的维数,因此迫使模型发现有效的数据表示形式。
# 汽车编码器
我们将要看到的第一个生成模型是自动编码器模型。 自动编码器是一个简单的神经网络,由两部分组成:**编码器****解码器**。 这个想法是编码器部分会将您的输入压缩到较小的尺寸。 然后,从这个较小的维度尝试使用模型的解码器部分重建输入。 通常用许多名称来称呼这种较小的尺寸,例如潜在空间,隐藏空间,嵌入或编码。
如果自动编码器能够再现其输入,则从理论上讲,该潜在空间应该对表示原始数据所需的所有重要信息进行编码,但其优点是尺寸小于输入。 编码器可以被认为是一种压缩输入数据的方式,而解码器是一种将其解压缩的方式。 在下图中,我们可以看到一个简单的自动编码器的外观。 我们的潜在空间或编码是中间标记为 *z 的部分。*
![](img/bace7dc4-4820-4dcd-934d-7215a3d8e4ce.png)
传统上,在自动编码器中,构成网络的层只是完全连接的层,但是通过使用卷积层,自动编码器也可以扩展到图像。 与之前一样,编码器会将输入图像压缩为较小的表示形式,而解码器将尽最大努力恢复信息。 区别在于,编码器现在是将数据压缩为特征向量的 CNN,而不是具有完全连接的层的 ANN,并且解码器将使用转置的卷积层从编码中重新生成图像。
此处提供了一个自动编码器处理图像的示例。 对于解码器部分
![](img/460f5473-9a64-4f05-8479-e9f9f36b5eb4.png)
对于任何自动编码器,损失函数都会引导编码器和解码器重建输入。 使用的常见损耗是自动编码器的输出与网络输入之间的 L2 损耗。 我们现在应该问自己一个问题:“使用 L2 损失比较图像是一个好主意吗?”。 如果您拍摄以下图像,即使它们看起来截然不同,它们实际上彼此之间的距离 *L2* 也相同:
![](img/1d80926b-a372-459a-8c13-ab3f74dd6fc5.png)
这表明当您使用 L2 损失比较图像时,并非总是可以依靠它,因此在使用它时应牢记这一点。
# 卷积自动编码器示例
以下 TensorFlow 代码将为 MNIST 数据集构建卷积自动编码器模型。 代码的第一部分将构建模型,编码器和解码器的图形。 在代码中,我们突出显示模型的一部分,其输出将是我们的潜在向量:
```py
class CAE_CNN(object):
def __init__(self, img_size = 28, latent_size=20):
self.__x = tf.placeholder(tf.float32, shape=[None, img_size * img_size], name='IMAGE_IN')
self.__x_image = tf.reshape(self.__x, [-1, img_size, img_size, 1])
with tf.name_scope('ENCODER'):
##### ENCODER
# CONV1: Input 28x28x1 after CONV 5x5 P:2 S:2 H_out: 1 + (28+4-5)/2 = 14, W_out= 1 + (28+4-5)/2 = 14
self.__conv1_act = tf.layers.conv2d(inputs=self.__x_image, strides=(2, 2),
filters=16, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# CONV2: Input 14x14x16 after CONV 5x5 P:0 S:2 H_out: 1 + (14+4-5)/2 = 7, W_out= 1 + (14+4-5)/2 = 7
self.__conv2_act = tf.layers.conv2d(inputs=self.__conv1_act, strides=(2, 2),
filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
with tf.name_scope('LATENT'):
# Reshape: Input 7x7x32 after [7x7x32]
self.__enc_out = tf.reshape(self.__conv2_act, [tf.shape(self.__x)[0], 7 * 7 * 32])
self.__guessed_z = tf.layers.dense(inputs=self.__enc_out,
units=latent_size, activation=None, name="latent_var")
tf.summary.histogram("latent", self.__guessed_z)
with tf.name_scope('DECODER'):
##### DECODER (At this point we have 1x18x64
self.__z_develop = tf.layers.dense(inputs=self.__guessed_z,
units=7 * 7 * 32, activation=None, name="z_matrix")
self.__z_develop_act = tf.nn.relu(tf.reshape(self.__z_develop, [tf.shape(self.__x)[0], 7, 7, 32]))
# DECONV1
self.__conv_t2_out_act = tf.layers.conv2d_transpose(inputs=self.__z_develop_act,
strides=(2, 2), kernel_size=[5, 5], filters=16,
padding="same", activation=tf.nn.relu)
# DECONV2
# Model output
self.__y = tf.layers.conv2d_transpose(inputs=self.__conv_t2_out_act,
strides=(2, 2), kernel_size=[5, 5], filters=1,
padding="same", activation=tf.nn.sigmoid)
# We want the output flat for using on the loss
self.__y_flat = tf.reshape(self.__y, [tf.shape(self.__x)[0], 28 * 28])
```
与卷积自动编码器丢失有关的代码段如下:
```py
with tf.name_scope("CAE_LOSS"):
# L2 loss
loss = tf.losses.mean_squared_error(labels=model_in, predictions=model_out_flat)
# Solver configuration
with tf.name_scope("Solver"):
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)
```
# 自动编码器的用途和局限性
自动编码器的简单性很酷,但是在功能上有所限制。 他们的一个潜在用途是预训练模型(假设您将模型作为编码器部分,并且能够创建反模型作为解码器)。 使用自动编码器可以很好地进行预训练,因为您可以获取数据集并训练自动编码器以对其进行重构。 培训后,您可以使用编码器的权重,然后将其微调到您要执行的任务。
如果不太复杂,则另一种用途是作为数据压缩形式。 您可以使用自动编码器将维数减小到两维或三维,然后尝试在潜在空间中可视化您的输入以查看它是否对您有用。
但是,自动编码器的局限性在于它们不能用于为我们生成更多数据。 这是因为我们不知道如何创建新的潜在向量来馈送到解码器。 唯一的方法是在输入数据上使用编码器。 现在,我们将研究对自动编码器的修改,以帮助解决此问题。
# 变体自动编码器
我们第一个可以生成更多类似于训练数据的真实生成模型,将是**变体自动编码器****VAE** )。 VAE 看起来像正常的自动编码器,但有一个新的约束,它将迫使我们的压缩表示(潜伏空间)遵循零均值和单位方差高斯分布。
在潜在空间上施加此约束的想法是,当我们想使用 VAE 生成新数据时,我们可以创建来自单位高斯分布的样本矢量,并将其提供给经过训练的解码器。 VAE 和常规自动编码器之间的差异就是对潜在空间矢量的约束。 这个约束条件使我们可以创建一种新的潜在矢量,然后再将其馈送到解码器以生成数据。
下图显示,VAE 在结构上与自动编码器完全相同,除了对隐藏空间的约束之外:
![](img/4ea0bf91-eefc-4fd8-bc77-b9205cc8309e.png)
# 定义正态分布的参数
我们需要两个参数来跟踪并强制我们的 VAE 模型在潜在空间中产生正态分布:
* **平均值**(应为零)
* **标准偏差**(应为 1)
在下图中,我们给出了具有不同均值和标准偏差值的正态分布示例。 仅使用这两个值,我们就可以产生一个正态分布,可以从中采样:
![](img/ce4aa377-1bf4-483a-ab86-23ab2b9dc716.jpg)
# VAE 损失功能
在 VAE 中,损失函数由两部分组成:
* **生成损失**:此损失将模型输出与模型输入进行比较。 这可能是我们在自动编码器中使用的损耗,例如 L2 损耗。
* **潜在损失**:此损失将潜在向量与零均值,单位方差高斯分布进行比较。 我们在这里使用的损失将是 KL 散度损失。 如果 VAE 开始产生不是来自所需分布的潜在向量,则该损失项将对 VAE 造成不利影响。
以下屏幕截图显示了 VAE 的损失,它是生成损失和潜在空间损失的组合:
![](img/f07350d3-1230-4c3f-aae8-41f1511e8195.jpg)
# Kullback-Leibler 散度
KL 散度损失将产生一个数字,该数字指示两个分布彼此之间的接近程度。
两个分布之间的距离越近,损耗就越低。 在下图中,蓝色分布正在尝试对绿色分布进行建模。 随着蓝色分布越来越接近绿色分布,KL 散度损失将接近于零。
![](img/3f81672f-26ea-446b-97b4-2b6119614be7.png)
# 培训 VAE
为了训练 VAE 并使用 KL 散度损失,我们首先需要研究如何生成潜矢量。 我们将使编码器产生两个向量,而不是让编码器直接精确地产生一个潜在向量。 第一个是平均值的向量**μ**,第二个是标准偏差值的向量**σ**。 根据这些,我们可以创建第三个向量,其中使用**μ****的 *i* th 值从高斯分布中采样 *i* 元素 σ**向量作为该高斯分布的均值和标准差。 然后,该第三采样矢量被发送到解码器。
现在,我们的模型如下所示:
![](img/944095bc-4bde-4e04-86e3-1ccc3e0b1c04.png)
上图中的均值和标准偏差块将只是正常的全连接层,它们将通过 KL 损失函数来学习如何返回所需的值。 更改我们如何获得潜矢量的原因是因为它使我们能够轻松计算 KL 散度损失。 KL 损失现在如下:`latent_mean`**μ**`latent_stddev`**σ**
```py
0.5 * tf.reduce_sum(tf.square(latent_mean) + tf.square(latent_stddev) - tf.log(tf.square(latent_stddev)) - 1, 1)
```
不幸的是,有一个 **Sample** 块,您可以将其视为随机生成器节点,无法区分。 这意味着我们不能使用反向传播来训练我们的 VAE。 我们需要一种称为“重新参数化”技巧的东西,该技巧将从反向传播流中取出采样。
# 重新参数化技巧
重新参数化技巧的想法是从反向传播循环中取出随机样本节点。 它是通过从高斯分布中获取样本ε,然后将其乘以我们的标准偏差矢量**σ**的结果,然后加上**μ**来实现的。 现在,我们的潜在向量的公式是:
![](img/16915d9b-677d-4f06-b381-1fe5e7353406.jpg)
![](img/ae58ed7b-25c5-447d-bb71-54d32dc9ed15.jpg)
产生的潜矢量将与以前相同,但是现在进行此更改可以使梯度流回到 VAE 的编码器部分。 下图显示了在进行重新参数化之前的 VAE 模型,在左侧进行了重新参数化之后。 蓝色框是损失函数的两个部分。 查看该图,您可以看到我们的渐变现在可以向后流动,因为我们不再具有红色框(示例节点)来挡路了:
![](img/412bfece-2f71-4956-a5fe-3ae19cce2b6b.png)
这是 TensorFlow 中的重新参数化形式:
```py
# Add linear ops to produce mean and standard devation vectors
fc_mean = tf.layers.dense(self.__enc_out, units=latent_size, activation=None, name="w_mean")
fc_stddev = tf.layers.dense(self.__enc_out, units=latent_size, activation=None, name="w_stddev")
# Generate normal distribution with dimensions [Batch, latent_size]
sample_block = tf.random_normal([tf.shape(X)[0], latent_size], 0, 1, dtype=tf.float32)
latent_z = fc_mean + (fc_stddev * sample_block)
```
# 卷积变分自动编码器代码
现在我们可以将所有内容组合在一起,并提供 TensorFlow 代码,这些代码将为 MNIST 数据集构建卷积 VAE。 我们为 VAE 模型创建一个类,然后将该模型放入`__init__`方法中。 第一部分是我们的模型的编码器,由两个转换层组成:
```py
class VAE_CNN(object):
def __init__(self, img_size=28, latent_size=20):
self.__x = tf.placeholder(tf.float32, shape=[None, img_size * img_size], name='IMAGE_IN')
self.__x_image = tf.reshape(self.__x, [-1, img_size, img_size, 1])
with tf.name_scope('ENCODER'):
##### ENCODER
# CONV1: Input 28x28x1 after CONV 5x5 P:2 S:2 H_out: 1 + (28+4-5)/2 = 14, W_out= 1 + (28+4-5)/2 = 14
self.__conv1_act = tf.layers.conv2d(inputs=self.__x_image, strides=(2, 2),
filters=16, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# CONV2: Input 14x14x16 after CONV 5x5 P:0 S:2 H_out: 1 + (14+4-5)/2 = 7, W_out= 1 + (14+4-5)/2 = 7
self.__conv2_act = tf.layers.conv2d(inputs=self.__conv1_act, strides=(2, 2),
filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
```
接下来是 VAE 的一部分,该部分负责使用我们之前的新重新参数化技巧来创建潜在矢量。 我们添加了对最终潜在向量的记录,以检查它是否按照我们期望的那样遵循单位高斯分布产生向量:
```py
with tf.name_scope('LATENT'):
# Reshape: Input 7x7x32 after [7x7x32]
self.__enc_out = tf.reshape(self.__conv2_act, [tf.shape(self.__x)[0], 7 * 7 * 32])
# Add linear ops for mean and variance
self.__w_mean = tf.layers.dense(inputs=self.__enc_out,
units=latent_size, activation=None, name="w_mean")
self.__w_stddev = tf.layers.dense(inputs=self.__enc_out,
units=latent_size, activation=None, name="w_stddev")
# Generate normal distribution with dimensions [B, latent_size]
self.__samples = tf.random_normal([tf.shape(self.__x)[0], latent_size], 0, 1, dtype=tf.float32)
self.__guessed_z = self.__w_mean + (self.__w_stddev * self.__samples)
tf.summary.histogram("latent_sample", self.__guessed_z)
```
之后,我们添加网络的解码器部分,该部分包括一个完全连接的层,然后是两个转置的卷积层:
```py
with tf.name_scope('DECODER'):
##### DECODER
# Linear layer
self.__z_develop = tf.layers.dense(inputs=self.__guessed_z,
units=7 * 7 * 32, activation=None, name="z_matrix")
self.__z_develop_act = tf.nn.relu(tf.reshape(self.__z_develop, [tf.shape(self.__x)[0], 7, 7, 32]))
# DECONV1
self.__conv_t2_out_act = tf.layers.conv2d_transpose(inputs=self.__z_develop_act,
strides=(2, 2), kernel_size=[5, 5], filters=16,
padding="same", activation=tf.nn.relu)
# DECONV2
# Model output
self.__y = tf.layers.conv2d_transpose(inputs=self.__conv_t2_out_act,
strides=(2, 2), kernel_size=[5, 5], filters=1,
padding="same", activation=tf.nn.sigmoid)
# Model output
self.__y_flat = tf.reshape(self.__y, [tf.shape(self.__x)[0], 28 * 28])
```
与我们的模型分开,我们需要写出最终损失函数,该函数将用于训练 VAE。 然后,我们可以将这种损失传递给我们选择的优化器,以创建我们的训练步骤:
```py
# Loss function
with tf.name_scope("VAE_LOSS"):
# L2 loss (generative loss)
generation_loss = tf.losses.mean_squared_error(labels=model_in, predictions= model_out_flat)
# KL Loss (latent loss)
latent_loss = 0.5 * tf.reduce_sum(tf.square(z_mean) + tf.square(z_stddev) - tf.log(tf.square(z_stddev)) - 1, 1)
# Merge the losses
```
```py
loss = tf.reduce_mean(generation_loss + latent_loss)
# Solver
with tf.name_scope("Solver"):
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)
```
# 产生新数据
训练完 VAE 模型后,我们可以将其解码器部分截断,并用作生成器为我们生成新数据。 它将通过向它提供来自单位高斯分布的新潜在矢量来工作。
我们在 TensorFlow 中提供负责构建此生成的 VAE 图的代码,如下所示:
```py
class VAE_CNN_GEN(object):
def __init__(self, img_size=28, latent_size=20):
self.__x = tf.placeholder(tf.float32, shape=[None, latent_size], name='LATENT_IN')
with tf.name_scope('DECODER'):
# Linear layer
self.__z_develop = tf.layers.dense(inputs=self.__x,
units=7 * 7 * 32, activation=None, name="z_matrix")
self.__z_develop_act = tf.nn.relu(tf.reshape(self.__z_develop, [tf.shape(self.__x)[0], 7, 7, 32]))
# DECONV1
self.__conv_t2_out_act = tf.layers.conv2d_transpose(inputs=self.__z_develop_act,
strides=(2, 2), kernel_size=[5, 5], filters=16,
padding="same", activation=tf.nn.relu)
# DECONV2
# Model output
self.__y = tf.layers.conv2d_transpose(inputs=self.__conv_t2_out_act,
strides=(2, 2), kernel_size=[5, 5], filters=1,
padding="same", activation=tf.nn.sigmoid)
@property
def output(self):
return self.__y
@property
def input(self):
return self.__x
```
# 生成对抗网络
**生成对抗网络****GAN** )是另一种非常新的生成模型,由于其令人印象深刻的结果而受到关注。 GAN 由两个网络组成:生成器网络和鉴别器网络。 在训练过程中,他们俩都玩零和游戏,其中鉴别器网络试图发现输入到其中的图像是真实的还是伪造的。 同时,生成器网络尝试创建足以欺骗鉴别器的伪造图像。
想法是经过一段时间的训练,鉴别器和生成器都非常擅长于他们的任务。 结果,生成器被迫尝试创建看起来越来越接近原始数据集的图像。 为此,它必须捕获数据集的概率分布。
下图概述了此 GAN 模型的外观:
![](img/98fcbf21-46e3-45f0-8a2d-a7004af773c3.png)
鉴别器和发生器都将具有自己的损耗函数,但是它们的损耗都相互依赖。
让我们总结一下 GAN 模型的两个主要模块或网络:
* **生成器**:使用大小为 N 的一维矢量作为输入,创建类似于*真实图像*数据集的图像(选择 N 取决于我们)
* **鉴别符**:验证提供给它的图像是真实的还是伪造的
GAN 的一些实际用法如下:
* 使用鉴别器网络权重作为不同任务的初始化,类似于我们对自动编码器可以执行的操作
* 使用生成器网络创建新图像,可能会扩大您的数据集,就像我们可以使用经过训练的 VAE 解码器一样
* 将鉴别器用作损失函数(对于图像,可能优于 L1 / L2),并且也可以在 VAE 中使用
* 通过将生成的数据与标记的数据混合来进行半监督学习
现在我们将向您展示如何在 TensorFlow 中实现非常简单的 GAN。 一旦经过培训,我们的 GAN 的生成器部分就可以用于根据 100 个长随机噪声矢量创建 MNIST 手写数字。 让我们开始吧!
# 鉴别器
我们需要做的第一件事就是创建我们的区分网络。 为此,我们将几个完全连接的层堆叠在一起。 鉴别器将 784 个长度向量作为输入,这是我们的 28x28 MNIST 图像变平。 每个图像的输出将只是一个数字,这是鉴别者对该图像为真实图像的信心程度的分数。 我们使用 Leaky ReLu 作为激活功能,以防止 ReLu 单元死亡。
我们返回原始 logit,因为损失函数将为我们应用 S 型激活函数,以确保鉴别器输出在 0 到 1 之间:
```py
def discriminator(x):
with tf.variable_scope("discriminator"):
fc1 = tf.layers.dense(inputs=x, units=256, activation=tf.nn.leaky_relu)
fc2 = tf.layers.dense(inputs=fc1, units=256, activation=tf.nn.leaky_relu)
logits = tf.layers.dense(inputs=fc2, units=1)
return logits
```
# 发电机
现在我们创建发电机网络。 生成器的工作是将随机噪声的矢量作为输入,并从中生成输出图像。 在此示例中,我们再次使用完全连接的层,这些层最后将产生 784 个长向量的输出,我们可以对其进行整形以获得 28x28 的图像:
```py
def generator(z):
with tf.variable_scope("generator"):
fc1 = tf.layers.dense(inputs=z, units=1024, activation=tf.nn.relu)
fc2 = tf.layers.dense(inputs=fc1, units=1024, activation=tf.nn.relu)
img = tf.layers.dense(inputs=fc2, units=784, activation=tf.nn.tanh)
return img
```
我们在输出上使用 tanh 激活来将生成的图像限制在-1 到 1 的范围内。
现在已经定义了模型,我们可以看看 GAN 训练所需的损失函数。
# GAN 损失函数
如前所述,鉴别器和生成器都有自己的损耗函数,这些函数取决于彼此网络的输出。 我们可以将 GAN 视为在鉴别器和生成器之间玩 minimax 游戏,如下所示:
![](img/1d24b043-c4b8-42f0-8f9c-8f2c7db22ec3.png)
在这里,D 是我们的鉴别符,G 是我们的生成器,z 是输入到生成器的随机矢量,x 是真实图像。 尽管我们在此处给出了 GAN 损失的总和,但实际上更容易分别考虑这两种优化。
为了训练 GAN,我们将在鉴别器和生成器之间交替进行梯度步骤更新。 在更新鉴别符时,我们要尝试使**最大化**鉴别符做出**正确选择**的概率。 在更新生成器时,我们想尝试使**最小化**鉴别器做出**正确选择**的可能性。
但是,为了实际实现,我们将与之前给出的内容相比,稍微改变 GAN 损失函数; 这样做是为了帮助培训收敛。 变化是,当更新生成器时,而不是**最小化**鉴别器做出**正确选择**的可能性; 我们改为**最大化**判别器做出**错误选择**的概率:
![](img/5222b764-d50d-4fbc-9747-47a475579e50.png)
更新鉴别符时,我们尝试使**最大化**,它对真实数据和伪数据均做出正确选择的可能性:
![](img/82987b0d-594f-4f08-9c76-436c0ec89fbc.png)
# 发电机损耗
生成器想要欺骗鉴别器,换句话说,使鉴别器输出 *1* 用于生成的图像 *G(z)*。 发生器损失只是施加到发生器结果的鉴别器输出的二项式交叉熵损失的负值。 请注意,由于生成器始终尝试生成“真实”图像,因此交叉熵损失可简化为:
![](img/1761cebc-4eca-4a0f-81cf-9a128632ee81.png)
在这里,每个术语的含义如下:
* **m** :批量
* **D** :鉴别器
* **G** :生成器
* **z** :随机噪声向量
我们想在训练 GAN 时最大化损失函数。 当损失最大化时,这意味着生成器能够生成可能使鉴别器蒙蔽的图像,并且鉴别器针对生成的图像输出 1。
# 鉴别器损失
鉴别者希望能够区分真实图像和生成图像。 它想为真实图像输出 1,为生成图像输出 0。 鉴别器损失函数具有以下公式,由于 GAN 训练和标记的工作方式而再次简化:![](img/6afbb7c8-d706-49dc-b74b-f8d9f894e8d3.png)
此损失函数有两个术语:
* 应用于鉴别器模型的二项式交叉熵产生了一些真实数据 *x*
* 将二项式交叉熵应用于所生成数据 *G(z)*的鉴别器模型结果
如前所述,我们采取这些不利条件,并希望在训练 GAN 时最大化此损失函数。 当这种损失最大化时,这意味着鉴别器能够区分实际输出和生成的输出。 注意,当鉴别器对于真实图像输出 1 而对于所生成图像输出 0 时,该损失最大。
# 综合损失
在 TensorFlow 中,我们可以实现整个 GAN 损失,如以下代码所示。 作为输入,我们从鉴别器的输出中获取来自生成器的一批伪图像和来自我们的数据集的一批真实图像:
```py
def gan_loss(logits_real, logits_fake):
# Target label vectors for generator and discriminator losses.
true_labels = tf.ones_like(logits_real)
fake_labels = tf.zeros_like(logits_fake)
# DISCRIMINATOR loss has 2 parts: how well it classifies real images and how well it
# classifies fake images.
real_image_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_real, labels=true_labels)
fake_image_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_fake, labels=fake_labels)
# Combine and average losses over the batch
discriminator_loss = tf.reduce_mean(real_image_loss + fake_image_loss)
# GENERATOR is trying to make the discriminator output 1 for all its images.
# So we use our target label vector of ones for computing generator loss.
generator_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_fake, labels=true_labels)
# Average generator loss over the batch.
generator_loss = tf.reduce_mean(G_loss)
return discriminator_loss , generator_loss
```
您可能已经注意到,不可能同时最大化鉴别器损耗和发生器损耗。 这就是 GAN 的优点,因为在训练时,该模型有望达到某种平衡,在这种情况下,生成器必须生成真正高质量的图像,以欺骗鉴别器。
TensorFlow 仅允许其优化器最小化而不是最大化。 结果,我们实际上采用了前面所述的损失函数的负值,这意味着我们从最大化损失变为最小化损失。 不过,我们无需执行任何其他操作,因为`tf.nn.sigmoid_cross_entropy_with_logits()`会为我们解决此问题。
# 训练 GAN
因此,现在有了生成器,鉴别器和损失函数,剩下的就是训练! 我们将在 TensorFlow 中给出如何执行此操作的草图,因为这部分没有花哨的内容。 就像我们之前所做的那样,它只是将上一节中的内容以及加载和馈送 MNIST 图像拼凑在一起。
首先,设置两个求解器:一个用于鉴别器,一个用于生成器。 已显示`AdamOptimizer`的较小值`beta1`,因为它已显示出可帮助 GAN 训练收敛:
```py
discriminator_solver = tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.5)
generator_solver = tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.5)
```
接下来,创建一个随机噪声矢量; 这可以通过`tf.random_uniform`完成。 这被馈送到生成器网络以创建一批生成的图像:
```py
z = tf.random_uniform(maxval=1,minval=-1,shape=[batch_size, dim])
generator_sample = generator(z)
```
然后,我们将一批真实图像和一批生成的样本提供给鉴别器。 我们在这里使用变量范围来重用我们的模型变量,并确保不创建第二个图:
```py
with tf.variable_scope("") as scope:
logits_real = discriminator(x)
# We want to re-use the discriminator weights.
scope.reuse_variables()
logits_fake = discriminator(generator_sample )
```
由于需要分别更新它们,因此我们将区分符和生成器的权重分开:
```py
discriminator_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'discriminator')
generator_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'generator')
```
最后,我们计算损失并将其与相关权重一起发送给优化器以进行更新:
```py
discriminator_loss, generator_loss = gan_loss(logits_real, logits_fake)
# Training steps.
discriminator_train_step = discriminator_solver.minimize(discriminator_loss, var_list=discriminator_vars )
generator_train_step = generator_solver.minimize(generator_loss , var_list=generator_vars )
```
这些是训练 GAN 的主要步骤。 剩下的就是创建一个训练循环,遍历大量数据。 如果这样做,您应该能够像训练中那样输入任何随机噪声矢量,并生成图像。
如下图所示,创建的图像开始类似于 MNIST 数字:
![](img/b6e289a6-0c13-4ee0-bb09-78bd4387231b.png)
# Deep convolutional GAN
**深度卷积 GAN****DCGAN** )是我们之前看到的普通 GAN 的扩展。 我们不是使用完全连接的层,而是使用卷积层。 想法是使用卷积层可以帮助生成器形成更好的图像。 以下是这种模型的示例:
![](img/b9cf2f8e-db74-44ab-8090-d032d5a82d24.jpg)
DCGAN 的示例实现与之前训练普通 GAN 相同,只是简单地将鉴别器和生成器网络换成一些卷积架构,如以下代码所示。 请注意,生成器将使用转置卷积来上采样:
```py
def discriminator(x):
with tf.variable_scope("discriminator"):
unflatten = tf.reshape(x, shape=[-1, 28, 28, 1])
conv1 = tf.layers.conv2d(inputs=unflatten, kernel_size=5, strides=1, filters=32 ,activation=leaky_relu)
maxpool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=2, strides=2)
conv2 = tf.layers.conv2d(inputs=maxpool1, kernel_size=5, strides=1, filters=64,activation=leaky_relu)
maxpool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=2, strides=2)
flatten = tf.reshape(maxpool2, shape=[-1, 1024])
fc1 = tf.layers.dense(inputs=flatten, units=1024, activation=leaky_relu)
logits = tf.layers.dense(inputs=fc1, units=1)
return logits
```
```py
def generator(z):
with tf.variable_scope("generator"):
fc1 = tf.layers.dense(inputs=z, units=1024, activation=tf.nn.relu)
bn1 = tf.layers.batch_normalization(inputs=fc1, training=True)
fc2 = tf.layers.dense(inputs=bn1, units=7*7*128, activation=tf.nn.relu)
bn2 = tf.layers.batch_normalization(inputs=fc2, training=True)
reshaped = tf.reshape(bn2, shape=[-1, 7, 7, 128])
conv_transpose1 = tf.layers.conv2d_transpose(inputs=reshaped, filters=64, kernel_size=4, strides=2, activation=tf.nn.relu,
padding='same')
bn3 = tf.layers.batch_normalization(inputs=conv_transpose1, training=True)
conv_transpose2 = tf.layers.conv2d_transpose(inputs=bn3, filters=1, kernel_size=4, strides=2, activation=tf.nn.tanh,
padding='same')
img = tf.reshape(conv_transpose2, shape=[-1, 784])
return img
```
只需换出生成器和鉴别器网络以使用卷积运算,我们就能生成如下图像:
![](img/ada0a5b7-1268-4f61-820a-3aca71dba80f.png)
现在产生的质量非常好,几乎与真实数据没有区别。 另外,请注意,图像确实非常清晰,并且没有像我们之前那样模糊且几乎没有伪影。
需要注意的几点是:
* 对于鉴别器:再次使用泄漏的 relu,不要使用最大池。 仅使用跨步卷积或平均池。
* 对于生成器:在最后一层使用 relu 和 tanh。
* 通常,最佳实践是在生成器和鉴别器上都使用 batchnorm 层。 它们将始终设置为训练模式。
* 有时,人们运行生成器优化器的次数是运行鉴别器优化器的两倍。
这是一个简单的 DCGAN 在生成人脸图像时可以达到的质量的示例:
![](img/a71cc7eb-9f5c-4bbc-b4f2-9bb2631bd7c8.jpg)
# WGAN
Wasserstein GAN 是 GAN 的另一种变体,它解决了训练 GAN 时可能发生的问题,即模式崩溃。 此外,其目的在于给出一种度量,该度量指示 GAN 何时收敛,换句话说,损失函数具有该值的含义。
重要的更改是从损失中删除对数并修剪鉴别器权重。
此外,请按照下列步骤操作:
* 火车鉴别器比发电机更多
* 减少鉴别器的重量
* 使用 RMSProp 代替 Adam
* 使用低学习率(0.0005)
WGAN 的缺点是训练起来较慢:
![](img/6cfab511-a579-49c1-96bb-64870fb2cfd0.png)
![](img/8a6bd68f-88df-4180-a3fd-18487b4701a5.png)
![](img/df33e809-e094-4a71-acf8-1ebe3beb0afc.png)
WGAN 产生的图像结果仍然不是很好,但是该模型确实有助于解决模式崩溃问题。
# 开始
BEGAN 的主要思想是在鉴别器上使用自动编码器,这将有其自身的损失,该损失会衡量自动编码器对某些图像(生成器或真实数据)的重构程度:
![](img/1a6f3311-6e16-492c-860d-45414eedbc67.jpg)
BEGAN 的一些优点如下:
* 高分辨率(128x128)人脸生成(2017 最新技术)。
* 提供一种衡量收敛的方法。
* 即使没有批处理规范和辍学也有良好的结果。
* 超参数可控制世代多样性与质量。 更高的质量也意味着更多的模式崩溃。
* 不需要两个单独的优化器。
这是 BEGAN 在生成人脸任务时可以生成的图像质量的示例:
![](img/bf3a0b45-8535-40ec-93ed-fb4ca994202c.jpg)
# 有条件的 GAN
条件 GAN 是普通 GAN 的扩展,其中鉴别器和生成器都被设置为某些特定类别 *y。* 这具有有趣的应用程序,因为您可以将生成器固定到特定的类,然后使其生成我们选择的特定同一类的多个不同版本。 例如,如果将 *y* 设置为与 MNIST 中的数字 7 对应的标签,则生成器将仅生成 7 的图像。
使用条件 GAN,minimax 游戏变为:
![](img/ade35614-04cc-4447-b1a0-b8e94dc0ce7a.png)
在这里,我们依赖于额外输入 *y* ,它是输入图像的类标签。
合并 *x**y,**z**y,*的最简单方法是将它们串联在一起,这样我们的输入向量就是 更长。 这将创建一个更加受控制的数据集扩充系统。 这是 TensorFlow 代码中的一个示例:
![](img/63ca28dd-5605-482d-95b3-4e1c027f3c89.jpg)
# GAN 的问题
GAN 当前最大的问题是,它们很难训练。 幸运的是,有一些技术可以使事情变得容易,这是目前非常活跃的研究领域。
在这里,我们将介绍培训 GAN 的一些问题以及如何解决它们。
# 损失可解释性
训练 GAN 时的问题之一是,生成器损耗和鉴别器损耗的值都没有明显的影响。 这不像训练分类器,只是等待损失下降以查看模型是否在训练。
对于 GAN,损失值的下降并不一定意味着该模型正在训练中:
![](img/c8fb861d-7d21-463d-bf33-1b1398362b00.jpg)
通过许多人的实验和研究,以下是有关如何使用 GAN 损耗值的一些提示:
* 您不希望鉴别器的损失下降得很快,因为它将无法向生成器提供反馈以改善它。
* 如果发电机损耗迅速下降,则意味着它发现了一个鉴别器弱点,并一次又一次地利用了这一弱点。 如果发生这种情况,则称为**模式折叠**
损失实际上仅对查看培训中是否出现问题有好处。 因此,没有很好的方法知道培训已经收敛。 通常,最好的办法是继续查看发电机的输出。 确保输出看起来与您的期望接近,并且输出种类丰富。
# 模式崩溃
这可能是您在训练 GAN 时遇到的第一个问题。 当生成器找到一组特定的输入来欺骗鉴别器时,就会发生模式崩溃,并且它会继续利用这种故障情况并将潜伏 *Z* 空间中的许多值折叠为相同的值。
解决此问题的一种方法是使用“迷你批处理功能”或“展开甘斯”,或者完全停止训练,然后在生成器开始创建非常狭窄的输出分布时重新开始:
![](img/febfacea-0ff5-4ee3-9fec-729292f70e27.jpg)
![](img/418601cc-31d2-4de7-b124-9d1ead1cf29a.jpg)
# 改善 GAN 的可训练性的技术
在这里,我们将介绍一些在训练 GAN 时使生活更轻松的技术:
* 归一化-1/1 之间的输入
* 使用 BatchNorm
* 使用 Leaky Relu(判别器)
* 在发电机输出上使用 Relu(发电机),tanh
* 对于下采样,请使用平均池化或跨步卷积
* 使用 Adam 优化器
* 如果鉴别器损失迅速下降,则说明存在问题
* 在发电机上使用压降(在训练阶段和测试阶段)
# 小批量鉴别器
用于改善模式崩溃的一些技术如下:
* 取得鉴别器某层的输出
* 将鉴别器的输入重塑为矩阵
* 计算 L1 距离
* 计算 L1 距离的指数和
* 将结果连接到输入(鉴别器的某些层)
![](img/9f716a8f-5a7f-40fc-94fc-ae5f004e4724.jpg)
![](img/375e4e8f-7e97-4d2a-b8f1-b197a83021a2.jpg)
# 摘要
在本章中,我们了解了生成模型及其与判别模型的不同之处。 我们还讨论了各种自动编码器,包括深度,变体和卷积。 此外,我们了解了一种新型的生成模型,称为生成对抗网络(GAN)。 在了解了所有这些生成模型之后,我们看到了如何在 TensorFlow 中自己训练它们以生成手写数字,并看到了它们可以产生的不同质量的图像。
[第 7 章](../Text/7.xhtml)*转移学习*中,我们将学习转移学习及其如何帮助我们加快培训速度。
\ No newline at end of file
# 转移学习
转移学习的作用恰如其名。 这个想法是将从一项任务中学到的东西转移到另一项任务上。 为什么? 实际上,每次都从头开始训练整个模型的效率很低,其成功取决于许多因素。 另一个重要原因是,对于某些应用程序,公开可用的数据集不够大,无法训练出像 AlexNet 或 ResNet 这样的深层架构而又不会过度拟合,这意味着无法推广。 示例应用程序可以从用户给出的一些示例中进行在线学习,也可以是细粒度的分类,其中类别之间的差异很小。
一个非常有趣的观察结果是,由于您冻结了所有其余部分(无论是检测还是分类),最终的层可以用于完成不同的任务,最终权重看起来非常相似。
这导致了转移学习的想法。 这意味着在大量数据上训练的深度架构(例如 ImageNet)可以很好地概括化,以至于其卷积权重可以充当特征提取器,类似于常规的视觉表示,并且可以用于训练线性分类器以用于 各种任务。
本章旨在教读者如何在 TensorFlow 中采用现成的训练有素的模型,更改其结构以及为特定任务重新训练某些层。 我们将看到转移学习将如何帮助改善结果并加快培训时间。
本章涵盖的主要主题如下:
* 使用来自另一个训练过的模型的权重预先初始化一个模型
* 在需要时使用 TensorFlow 加载模型并冻结/解冻图层
# 什么时候?
研究表明,在 ImageNet 上训练的卷积网络权重中的特征提取优于常规特征提取方法,例如 SURF,[H​​TG0]可变形部分描述符( **DPD** s),**定向梯度直方图****HOG** )和**一袋单词****BoW** )。 这意味着无论常规视觉表示如何工作,卷积特征都可以同样好地使用,唯一的缺点是更深的架构可能需要更长的时间来提取特征。
当在 ImageNet 上训练深层卷积神经网络时,第一层中的卷积滤波器的可视化(请参见下图)显示,他们学习了*低层*特征,类似于边缘检测滤波器,而卷积滤波器 在最后一层学习*高级*功能,这些功能捕获特定于类的信息。 因此,如果我们在第一个池化层之后提取 ImageNet 的特征并将其嵌入 2D 空间(例如,使用 t-SNE),则可视化将显示数据中存在一些无政府状态,而如果执行 在完全连接的层上相同,我们将注意到具有相同语义信息的数据被组织成簇。 这意味着网络可以在更高层次上很好地概括,并且有可能将这种知识转移到看不见的类别中。
![](img/b7b186d6-7803-4517-add9-f559586b2576.png)
根据对与 ImageNet 相似度较小的数据集进行的实验,在以下任务上,基于 ImageNet 训练的基于卷积神经网络权重的特征比常规特征提取方法的性能更好:
* **对象识别**:此 CNN 特征提取器可以成功地对其他类别不可见的数据集执行分类任务。
* **域适应**:这是当训练和测试数据来自不同的分布,而标签和类别数相同时。 不同的域可以考虑使用不同的设备或在不同的设置和环境条件下捕获的图像。 具有 CNN 功能的线性分类器可以在不同域中成功地将具有相同语义信息的图像聚类,而 SURF 功能则可以针对特定领域的特征进行过拟合。
* **精细**-**精细分类**:这是我们要在同一高级类中的子类之间进行分类的时候。 例如,我们可以对鸟类进行分类。 尽管没有对细粒度数据进行训练,但 CNN 功能以及逻辑回归功能的性能优于基线方法。
* **场景识别**:在这里,我们需要对整个图像的场景进行分类。 在对象分类数据库上经过训练的 CNN 特征提取器,顶部带有一个简单的线性分类器,其性能优于应用于识别数据的传统特征提取器的复杂学习算法。
这里提到的某些任务与图像分类没有直接关系,图像分类是 ImageNet 培训的主要目标,因此有人希望 CNN 功能无法推广到看不见的场景。 但是,这些功能与简单的线性分类器相结合,性能优于手工制作的功能。 这意味着 CNN 的学习权重是可重用的。
那么什么时候应该使用迁移学习呢? 当我们有一个任务时,由于问题的性质,可用数据集很小(例如对蚂蚁/蜜蜂进行分类)。 在这种情况下,我们可以在包含相似语义信息的较大数据集上训练我们的模型,然后用较小的数据集仅训练最后一层(线性分类器)。 如果我们只有足够的可用数据,并且有一个更大的相似数据集,则对该相似数据集进行预训练可能会导致模型更健壮。 通常情况下,我们使用随机初始化的权重来训练模型,在这种情况下,将使用在其他数据集上训练过的权重来初始化模型。 这将有助于网络更快地融合并更好地推广。 在这种情况下,仅微调模型顶端的几层是有意义的。
经验法则是,从网络顶部开始,可用数据越多,可以训练的层就越多。 通过预训练(例如在 ImageNet 上)模型初始化模型权重。
# 怎么样? 概述
我们应该如何使用转学? 有两种典型的解决方法。 第一种不太及时的方法是使用所谓的预训练模型,即预先在大型数据集(例如 ImageNet 数据集)上训练过的模型。 这些经过预先训练的模型可以在不同的深度学习框架中轻松获得,并且通常被称为“模型动物园”。 预训练模型的选择在很大程度上取决于当前要解决的任务是什么,以及数据集的大小。 选择模型后,我们可以使用全部或部分模型作为要解决的实际任务的初始化模型。
深度学习的另一种不太常见的方式是自己预先训练模型。 当可用的预训练网络不适合解决特定问题时,通常会发生这种情况,我们必须自己设计网络体系结构。 显然,这需要更多的时间和精力来设计模型和准备数据集。 在某些情况下,用于进行网络预训练的数据集甚至可以是合成的,可以从计算机图形引擎(例如 3D Studio Max 或 Unity)或其他卷积神经网络(例如 GAN)生成。 可以对虚拟数据进行预训练的模型在真实数据上进行微调,并且可以与仅对真实数据进行训练的模型一起很好地工作。
例如,如果我们想区分猫和狗,而我们没有足够的数据,则可以从“模型动物园”下载在 ImageNet 上训练的网络,并使用除最后一层以外的所有层的权重。 最后一层必须调整为具有与班级数量相同的大小,在本例中为两个,并且权重需要重新初始化和训练。 这样,通过将这些层的学习率设置为零或非常小的值(请参见下图),我们将冻结那些不需训练的层。 如果有更大的数据集,我们可以训练最后三个完全连接的层。 有时,预训练网络只能用于初始化权重,然后再进行正常训练。
迁移学习之所以有效,是因为在初始层计算出的特征更通用并且看起来很相似。 在顶层提取的特征对于我们要解决的问题变得更加具体。
为了进一步研究如何使用迁移学习,以及对该主题的更深刻理解,让我们看一下代码。
![](img/a6a25c2f-05f4-4396-8db5-9a2fe9c555dd.png)
# 怎么样? 代码示例
在本节中,我们将学习在 TensorFlow 中进行迁移学习所需的实用技能。 更具体地说,我们将学习如何从检查点选择要加载的层,以及如何指示我们的求解器仅优化特定的层而冻结其他层。
# TensorFlow 有用的元素
由于转移学习是关于训练一个网络的权重,而该网络已从另一个训练后的模型中获取了权重,因此我们将需要找到一个。 在我们的示例中,我们将使用预训练卷积自动编码器的编码部分,该部分在[第 6 章](../Text/6.xhtml)中进行了说明。 使用自动编码器的优点是我们不需要标记的数据,也就是说,可以完全不受监督地对其进行训练。
# 没有解码器的自动编码器
包含两个卷积层和一个完全连接层的编码器(不带解码器部分的自动编码器)如下所示。 父自动编码器在 MNIST 数据集上进行了训练。 因此,网络将大小为 28x28x1 的图像作为输入,并在潜在空间将其编码为 10 维矢量,每个类别一维:
```py
# Only half of the autoencoder changed for classification
class CAE_CNN_Encoder(object):
......
def build_graph(self, img_size=28):
self.__x = tf.placeholder(tf.float32, shape=[None, img_size * img_size], name='IMAGE_IN')
self.__x_image = tf.reshape(self.__x, [-1, img_size, img_size, 1])
self.__y_ = tf.placeholder("float", shape=[None, 10], name='Y')
with tf.name_scope('ENCODER'):
##### ENCODER
# CONV1: Input 28x28x1 after CONV 5x5 P:2 S:2 H_out: 1 + (28+4-5)/2 = 14,
# W_out= 1 + (28+4-5)/2 = 14
self.__conv1_act = tf.layers.conv2d(inputs=self.__x_image, strides=(2, 2), name='conv1',
filters=16, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# CONV2: Input 14x14x16 after CONV 5x5 P:0 S:2 H_out: 1 + (14+4-5)/2 = 7,
# W_out= 1 + (14+4-5)/2 = 7
self.__conv2_act = tf.layers.conv2d(inputs=self.__conv1_act, strides=(2, 2),
name='conv2', filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
with tf.name_scope('LATENT'):
# Reshape: Input 7x7x32 after [7x7x32]
self.__enc_out = tf.layers.flatten(self.__conv2_act, name='flatten_conv2')
self.__dense = tf.layers.dense(inputs=self.__enc_out, units=200, activation=tf.nn.relu, name='fc1')
self.__logits = tf.layers.dense(inputs=self.__dense, units=10, name='logits')
def __init__(self, img_size=28):
if CAE_CNN_Encoder.__instance is None:
self.build_graph(img_size)
@property
def output(self):
return self.__logits
@property
def labels(self):
return self.__y_
@property
def input(self):
return self.__x
@property
def image_in(self):
return self.__x_image
```
# 选择图层
一旦定义了模型`model = CAE_CNN_Encoder` `()`,选择将要使用预训练权重初始化的图层就很重要。 请注意,两个网络的结构(要初始化的网络和提供训练后的权重的网络)必须相同。 因此,例如,以下代码片段将选择名称为`convs``fc`的所有层:
```py
from models import CAE_CNN_Encoder
model = CAE_CNN_Encoder()
list_convs = [v for v in tf.global_variables() if "conv" in v.name]
list_fc_linear = [v for v in tf.global_variables() if "fc" in v.name or "output" in v.name]
```
请注意,这些列表是从`tf.global_variables()`填充的; 如果选择打印其内容,则可能会发现它包含所有模型变量,如下所示:
```py
[<tf.Variable 'conv1/kernel:0' shape=(5, 5, 1, 16) dtype=float32_ref>,
<tf.Variable 'conv1/bias:0' shape=(16,) dtype=float32_ref>,
<tf.Variable 'conv2/kernel:0' shape=(5, 5, 16, 32) dtype=float32_ref>,
<tf.Variable 'conv2/bias:0' shape=(32,) dtype=float32_ref>,
<tf.Variable 'fc1/kernel:0' shape=(1568, 200) dtype=float32_ref>,
<tf.Variable 'fc1/bias:0' shape=(200,) dtype=float32_ref>,
<tf.Variable 'logits/kernel:0' shape=(200, 10) dtype=float32_ref>,
<tf.Variable 'logits/bias:0' shape=(10,) dtype=float32_ref>]
```
将定义图的图层分为卷积和完全连接两个列表后,您将使用`tf.Train.Saver`加载所需的权重。 首先,我们需要创建一个 saver 对象,将要从检查点加载的变量列表作为输入,如下所示:
```py
# Define the saver object to load only the conv variables
saver_load_autoencoder = tf.train.Saver(var_list=list_convs)
```
除了`saver_load_autoencoder`,我们还需要创建另一个`saver`对象,该对象将允许我们将要训练的网络的所有变量存储到检查点中。
```py
# Define saver object to save all the variables during training
saver = tf.train.Saver()
```
然后,在使用`init = tf.global_variables_initializer()`初始化图形并创建会话之后,我们可以使用`saver_load_autoencoder`从检查点恢复卷积层,如下所示:
```py
# Restore only the weights (From AutoEncoder)
saver_load_autoencoder.restore(sess, "../tmp/cae_cnn/model.ckpt-34")
```
请注意,调用`restore`会覆盖`global_variables_initializer`,所有选定的权重都将替换为检查点的权重。
# 仅训练一些层次
迁移学习的另一个重要部分是冻结不需要训练的图层的权重,同时允许对某些图层(通常是最后一层)进行训练。 在 TensorFlow 中,我们可以仅将要优化的层传递给求解器(在此示例中,仅将 FC 层传递给):
```py
train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss, var_list=list_fc_linear)
```
# 完整资料
在此示例中,我们将从 MNIST 卷积自动编码器示例中加载权重。 我们将仅恢复编码器部分的权重,冻结 CONV 层,并训练 FC 层以执行数字分类:
```py
import tensorflow as tf
import numpy as np
import os
from models import CAE_CNN_Encoder
SAVE_FOLDER='/tmp/cae_cnn_transfer'
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
model = CAE_CNN_Encoder(latent_size = 20)
model_in = model.input
model_out = model.output
labels_in = model.labels
# Get all convs weights
list_convs = [v for v in tf.global_variables() if "conv" in v.name]
# Get fc1 and logits
list_fc_layers = [v for v in tf.global_variables() if "fc" in v.name or "logits" in v.name]
# Define the saver object to load only the conv variables
saver_load_autoencoder = tf.train.Saver(var_list=list_convs)
# Define saver object to save all the variables during training
saver = tf.train.Saver()
# Define loss for classification
with tf.name_scope("LOSS"):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=model_out, labels=labels_in))
correct_prediction = tf.equal(tf.argmax(model_out,1), tf.argmax(labels_in,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# Solver configuration
with tf.name_scope("Solver"):
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss, var_list=list_fc_layers)
# Initialize variables
init = tf.global_variables_initializer()
# Avoid allocating the whole memory
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.200)
sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))
sess.run(init)
# Restore only the CONV weights (From AutoEncoder)
saver_load_autoencoder.restore(sess, "/tmp/cae_cnn/model.ckpt-34")
# Add some tensors to observe on tensorboad
tf.summary.image("input_image", model.image_in, 4)
tf.summary.scalar("loss", loss)
merged_summary = tf.summary.merge_all()
writer = tf.summary.FileWriter(SAVE_FOLDER)
writer.add_graph(sess.graph)
#####Train######
num_epoch = 200
batch_size = 10
for epoch in range(num_epoch):
for i in range(int(mnist.train.num_examples / batch_size)):
# Get batch of 50 images
batch = mnist.train.next_batch(batch_size)
# Dump summary
if i % 5000 == 0:
# Other summaries
s = sess.run(merged_summary, feed_dict={model_in:batch[0], labels_in:batch[1]})
writer.add_summary(s,i)
# Train actually here (Also get loss value)
_, val_loss, t_acc = sess.run((train_step, loss, accuracy), feed_dict={model_in:batch[0], labels_in:batch[1]})
print('Epoch: %d/%d loss:%d' % (epoch, num_epoch, val_loss))
print('Save model:', epoch)
saver.save(sess, os.path.join(SAVE_FOLDER, "model.ckpt"), epoch)
```
# 摘要
在本章中,我们学习了如何,何时以及为什么使用迁移学习。 这被认为是一个非常强大的工具,因为它使我们能够使用从其他领域学到的功能来以较少的数据很好地概括。 我们看了一些示例,现在应该清楚如何在自己的任务中实施转移学习。
在下一章中,我们将看到如何组织我们的数据以及如何扩展 CNN 架构,以构建准确而实用的机器学习系统。
\ No newline at end of file
# 机器学习最佳实践和故障排除
在机器学习工程中,至关重要的是要知道如何在系统开发过程中进行操作,以避免陷阱并解决常见问题。 创建机器学习系统(最节省时间和金钱)的最简单方法是重用已应用于您自己的类似问题的代码和预先训练的模型。 如果这不能满足您的需求,那么您可能需要训练自己的 CNN 体​​系结构,因为有时这可能是解决问题的最佳方法。 但是,面临的最大挑战之一是找到针对您的问题量身定制的大规模,公开可用的数据集。 因此,通常情况下,您可能需要创建自己的数据集。 创建自己的数据集时,至关重要的是适当组织它,以确保成功进行模型训练。
在本章中,我们将介绍并讨论日常工作流程,这些工作流程将帮助您回答以下问题:
* 我应该如何分割数据集?
* 我的数据集足以代表我的问题吗?
* 我的模型要有效且准确应该有多复杂?
* 评估模型的最佳方法是什么?
* 我应该如何构造我的代码?
# 建立机器学习系统
为了构建机器学习系统,建议从一个新的小项目开始并逐步改进它:
1. 查找与您类似的问题并下载代码(并测试模型以检查结果)
2. 根据需要找到扩展计算的方法(即 AWS / Google Cloud)
3. 从较小的数据集开始,以避免浪费时间等待一个纪元
4. 从简单的架构开始
5. 使用可视化/调试(例如,TensorBoard)
6. 微调模型,微调超参数,深度,架构,层和损失函数
7. 扩展数据集并确保其尽可能干净
8. 将您的数据集分为训练,开发和测试集
9. 评估模型
# 资料准备
所有机器学习算法的骨干都是数据。 机器学习算法学习的一切都来自数据。 因此,至关重要的是向算法提供代表问题陈述的正确数据。 就像已经看到的那样,深度学习特别需要大量数据用于训练模型。 有时我们可以说一定数量的数据足以解决问题,但是却永远不够! 多多益善。 能够正确训练的模型的复杂度与训练模型上的数据量成正比。 有限的数据将为该问题的模型体系结构选择设定上限。 在考虑可用数据量时,还值得注意的是,其中一部分也需要用于验证和测试目的。
下一节将讨论数据分区及其对任何机器学习任务进度的重要性。
# 训练/开发/测试集的划分
在讨论将数据划分到其中之前,让我们定义“训练集”,“开发集”和“测试集”。
* **训练集**:用于训练机器学习算法的数据/示例集。 在机器学习中,此数据用于查找模型/分类器的“最佳”权重。 通常,使用的大多数数据都进入训练集。
* **开发(开发)** / **验证集**:用于在训练的中间阶段评估模型/分类器的数据部分。 该集合用于微调超参数和评估具有各种配置的模型体系结构。 它在模型开发期间使用,而不是在最终模型评估中使用。
* **测试集**:模型经过微调和充分训练(我们对训练/开发集的损失感到满意),我们认为它已完全训练。 然后评估该模型。 对其进行评估的数据称为测试集。 测试集由数据的看不见部分组成,因此提供了最终模型性能的无偏估计。
为了获得高性能的神经网络,将数据集正确划分为训练集,开发集和测试集非常重要。 它有助于更​​快地迭代。 另外,它允许更有效地测量算法的偏差和方差,以便我们可以选择有效方式进行改进的方法。
在以前的时代,我们拥有较小的数据集(例如最多 10,000 个示例)和简单的分类器,我们会将数据集拆分为训练和测试集。 通常将训练集分成较小的集,以使用称为交叉验证的技术来训练分类器。 优良作法是按 60/20/20 的比例拆分数据集(即 60%的训练数据,20%的开发数据,20%的测试数据)。 但是,大数据的现代时代已经改变了这一经验法则。 如果我们有 1,000,000 个示例,则拆分比例已更改为 98/1/1(即 98%的培训数据,1%的 dev 数据,1%的测试数据)。
随着我们拥有更多的数据,开发和测试集的比例将变小。
![](img/6b559cef-7224-4438-b1d1-48dcb61c69d5.png)
# 开发和测试集不匹配
除了拆分数据之外,数据的分布还对神经网络的性能产生巨大影响。 应用深度学习中的大多数问题来自开发人员和测试集数据分布的不匹配。 我们需要记住,开发和测试数据应该来自类似的分发。 例如,如果我们以如下方式收集和分割人员检测数据,即从网页上收集人员的训练图像,而使用移动电话收集测试集图像,则会出现分布不匹配的情况。 这里的问题是,在训练模型时,我们会根据其在 dev 数据上的性能来微调网络的参数和体系结构,如果 dev 数据与训练数据相似且与测试数据不同,则 dev 数据中存在很高的偏差 对训练集。 在开发集上获得良好的评估结果并不一定意味着该模型可以很好地推广。 在这种情况下,对分布完全不同的集合进行测试可能会导致不良结果。 这是浪费时间和精力。 解决方案是首先合并开发集和测试集,随机将它们洗牌,最后将洗过的数据再次拆分为开发集和测试集。 这有助于在将机器学习算法成功培训到最终应用程序方面取得更快的进展。
# 何时更改开发/测试集
根据评估指标对开发/测试集执行良好但不满足客户要求(即在部署时执行不佳)的算法,表明我们在数据集中缺少正确的目标数据。 在这种情况下,我们需要对数据集进行更改,因为它对于目标应用程序而言不够代​​表性。 考虑对猫图像进行分类。 如果训练/开发/测试集使用的是高分辨率,高质量的图像(姿势完美的猫),而目标应用正在查看具有不同视角的猫或运动中的图像(模糊),则可以期望 算法在部署时表现不佳。
![](img/d33ade2f-7c16-473c-974d-7ba1a204b47d.png)
# 偏差和方差
[第 2 章](../Text/2.xhtml)*深度学习和卷积神经网络*中所讨论的,方差和偏差分别表示过拟合和欠拟合。 我们可以使用训练集,开发集和测试集错误来诊断“拟合不足”和“过度拟合”的问题。
考虑以下场景,其中我们的数据来自两个不同的分布,分别称为分布 1 和分布 2。分布 2 表示我们关心的目标应用程序。 问题是,我们如何在这种分布上定义训练,开发和测试集。
![](img/00d0d711-3009-4542-bb6e-3988f1007b50.png)
最好的方法是根据上图将其拆分。 分配 1 被拆分为训练集,其一部分用作开发集。 在这里,我们称其为“ Train-Dev 集”(因为开发集与火车集具有相同的分布)。 分布 1 主要用于训练,因为它是一个大型数据集。 发行版 2 分为测试集和开发集,它们与发行版 1 中的任一集无关。这里要强调的一点是,测试和开发集应来自同一发行版,并且属于我们实际上关心的应用程序,即 目标应用程序。 开发集和测试集通常是小的数据集,因为它们的目的是给出模型/算法的无偏性能估计。
模型在不同数据集分区上的误差差异,以及查看人为误差可为我们提供诊断偏见和方差问题的见解
下表显示了当左列中的集合之间存在误差时,应如何诊断。 N.B. 人为水平误差是此分析的基准,它为比较模型提供了基准。
![](img/43b92390-d3d8-4104-a0e8-0b0e03958557.png)
下表可以更好地说明这一点。 在这些示例中,我们假设在所有情况下的最佳/人为错误均最小,即 1%。 通常,深度学习模型的准确性与人类相似,因此将其作为比较可帮助您找到良好的架构。
* 高偏差/拟合不足
| 人为/最佳错误 | 1% |
| 训练错误 | 15% |
与人员水平的表现相比,训练误差较大,这意味着该模型甚至无法拟合数据。 训练有素,因此欠拟合/高偏见。 但是,当我们在这种情况下查看 dev 错误时,它可以很好地概括,因此不会丢失所有内容。
* 高差异/过度拟合
| 训练错误 | 1.5% |
| 火车开发错误 | 30% |
在这种情况下,该模型在看不见的数据上表现不佳,该数据与训练集属于同一分布,但不是训练的一部分。 这意味着该模型无法概括,因此会过度拟合训练数据。
* 高方差和高偏见
| 训练错误 | 20% |
| 火车开发错误 | 40% |
这种情况是最坏的情况,因为我们观察到该模型无法正确拟合训练数据,并且不能很好地概括。 这可以通过更改模型架构来解决。
* 数据不匹配
| 火车开发错误 | 2% |
| 开发错误 | 15% |
当模型很好地适合来自与训练集相同分布的开发集,并且对来自不同分布的开发集表现不佳时,这会导致数据不匹配问题,如本章前面所述。
* 过度开发集
| 开发错误 | 2% |
| 测试错误 | 15% |
下图以流程图的形式介绍了解决上述问题的解决方案/指南:
![](img/641fa640-9e3d-40c0-b72f-027ecc050fff.png)
ML 基本配方
有用的图表说明了测试和训练误差如何随模型复杂性而变化,如下所示。 一方面,当模型过于复杂时,往往会过度拟合训练数据,因此,训练误差减小而测试误差增大。 另一方面,较简单的模型往往不适合并且无法推广。 模型复杂度的理想范围是在“测试误差”开始增加之前以及“训练误差”接近零时的某个位置。
![](img/3b7eb077-6c15-4a77-989a-40b12e4b70db.png)
# 资料不平衡
我们已经看到了数据表示和分布在解决偏差和方差问题中的重要性。 我们遇到的另一个相关问题是分类任务中各个类之间的数据分配不均。 这称为数据不平衡。 例如,如果我们有一个二进制分类问题,并且其中一个类别有 50000 张图像,而另一个类别只有 1000 张图像,这可能会导致训练算法的性能出现巨大问题。 我们必须通过以下方法解决数据不平衡的问题:
# 收集更多数据
是的,最好使类数据分布相等。 收集尽可能多的数据,并用较少的样本填充类。 为此,您可以在互联网上搜索与您的问题相似的数据库并将其包括在内。 简单的网络搜索还可以带来许多由各种来源上传的图像。 有时您会发现,使用更多数据不会提高模型性能。 这表明您的模型可能已达到极限。
# 查看您的效果指标
分类准确性不是一个很好的衡量标准,尤其是当我们的数据不平衡时。 这种准确性将更倾向于具有更多数据的类。 有许多良好的性能评估指标可以真实地描述算法的执行方式,例如混淆矩阵,接收器**工作特性曲线**(ROC),**精确调用**(PR)曲线 和 F1 分数。 这些将在本章稍后详细说明。
# 数据综合/扩充
在无法从其他资源收集数据,数据集太小或收集的数据不能很好地表示的情况下,我们需要以某种方式自行生成数据。 这称为数据增强。 智能生成的数据可以解决许多问题,包括数据集不平衡,训练数据不足和过度拟合。
数据扩充通常是作为输入数据管道的一部分来完成的,该管道在训练时为模型提供数据。 随机地,而不是提供原始训练图像,您将应用一些增强来更改它。 有很多方法可以进行数据扩充,但是一些示例是:
* 增加噪音
* 应用几何变换
* 交换颜色通道
* 随机颜色扰动
* 调整亮度/对比度/色相
* 只需添加类似于网络存在问题的增强,例如,您的模型不适用于黑白图像; 只需将其添加为新的扩充
# 重采样数据
这是关于改变我们建立培训批次的方式。 我们通过更改选择特定类别的可能性来做到这一点。 例如,如果您有两个类 A 和 B,其中我们的 A 实例比 B 多得多,则可以更改采样系统以选择比 A 多的 B。
# 损失函数加权
我们还可以处理不平衡类,并通过将损失权重包括在内来处理不平衡数据的分类问题。 这种惩罚或权重迫使模型更多地关注少数群体(样本较少的类别)。 在前面的章节中讨论过的惩罚型 SVM 和焦点损失检测器算法就是这样的例子。
Tensorflow 已经具有其损失功能,并内置了加权选项:
* `tf.losses.sparse_softmax_cross_entropy(labels=label, logits=logits, weights=weights)`
* `Tf.nn.weighted_cross_entropy_with_logits`
例如,如果您尝试对 A,B,C 三个类别进行分类,其中 A 为 10%,B 为 45%,C 为 45%,则可以将`tf.losses.sparse_softmax_cross_entropy`用于以下权重:[1.0、0.3, 0.3]。
# 评估指标
在为模型选择评估指标时,我们还需要小心。 假设对于狗/非狗分类问题,我们有两种算法的准确度分别为 98%和 96%。 乍一看,这些算法看起来都具有相似的性能。 让我们记住,分类准确度定义为做出的正确预测数除以做出的预测总数。 换句话说,True Positive(TP)和 True Negative(TN)预测数除以预测总数。 但是,可能出现的情况是,随着狗图像的出现,我们还会得到大量被错误分类为狗的背景或类似外观的物体,通常称为误报(FP)。 另一个不良行为可能是许多狗图像被错误分类为负片或假阴性(FN)。 显然,根据定义,分类准确性不能捕获误报或误报的概念。 因此,需要更好的评估指标。
第一步,我们将构建一个混淆矩阵,该矩阵总结最后显示的段落:
![](img/645c3d53-fa90-4fd7-8d1e-51fd5188aa5f.png)
根据此表,我们可以定义四个其他指标,这些指标可以使我们更好地了解已实现的结果。 这些是:
* **真实阳性率(TPR)或灵敏度或召回率**:当物体存在时测试结果为阳性的概率(真实阳性率,以百分比表示)= TP /(TP + FN)
* **假阳性率(FPR)**:是特定测试= FP /(FP + TN)错误拒绝实际阴性的概率
* **阳性预测值(PPV)或精度**:当检测结果为阳性(以百分比表示)时该对象存在的概率= TP /(TP + FP)
* **负预测值(NPV)**:测试阴性(表示为百分比)时不存在对象的概率= TN /(TN + FN)
为了更好地了解这些指标的实用性,我们以两种不同算法的以下两个混淆矩阵为例,并计算前面的指标。
范例 1:
| | 正 | 负 | |
| 预测积极 | 10(TP) | 13(FP) | 23 |
| 预测为负 | 75(FN) | 188(田纳西州) | 263 |
| | 85 | 201 | 286 |
精度:(TP + TN)/(TP + TN + FP + FN)= 198/286 = 0.69 TPR:TP /(TP + FN)= 10/85 = 0.11 FPR:FP /(FP + TN)= 13 / 201 = 0.06 PPV:TP /(TP + FP)= 10/23 = 0.43 NPV:TN /(TN + FN)= 188/263 = 0.71
范例 2:
| | 正 | 负 | |
| 预测积极 | 0(TP) | 0(FP) | 0 |
| 预测为负 | 85(FN) | 201(田纳西州) | 286 |
| | 85 | 201 | 286 |
精度:(TP + TN)/(TP + TN + FP + FN)= 201/286 = 0.70 TPR:TP /(TP + FN)= 0/85 = 0 FPR:FP /(FP + TN)= 0 / 201 = 0 PPV:TP /(TP + FP)= 0/0 = 0 NPV:TN /(TN + FN)= 201/286 = 0.70
在第一个示例中,我们可以得到 69%的正确精度,但是在第二个示例中,通过仅对每个示例进行预测,我们实际上将我们的精度提高到 70%! 显然,仅预测所有事物为负类的模型并不是一个很好的模型,这就是我们所说的准确性悖论。 简单来说,“准确性悖论”说,即使模型可能具有更高的准确性,但实际上可能并不是更好的模型。
如前面的示例中所示,当类别不平衡变大时,更可能发生这种现象。 鼓励读者对包含 85 个正样本和 85 个负样本的平衡数据集重复上述测试。 如果假阳性与假阴性的比例与前面的示例相同,则这将导致第一个示例的分类准确度为 52%,第二个示例的分类准确度为 50%,这表明准确性悖论不适用于平衡数据集。
为了能够正确评估算法,我们需要查看其他评估指标,例如 TPR 和 FPR。 我们可以看到在第二个示例中它们都为零,这表明算法根本无法检测到所需的正向对象。
使用精度度量的不平衡数据集的另一种情况是癌症测试,其中生病的人数大大少于健康的人数。 以下是为此解决的示例。
| | 生病 | 健康 | 总 |
| 测试结果肯定 | 99(TP) | 999(FP) | 1,098 |
| 测试结果阴性 | 1(FN) | 98,901(田纳西州) | 98,902 |
| 总 | 100 | 99,900 | 100,000 |
精度:(TP + TN)/(TP + TN + FP + FN)= 0.99 TPR:TP /(TP + FN)= 0.99 FPR:FP /(FP + TN)= 0.01 PPV:TP /(TP + FP) = 0.09 净现值:TN /(TN + FN)= 0.99
此处的测试似乎表现不错,因为准确性为 99%。 但是,如果您被诊断出患有癌症,这并不意味着您患该病的可能性为 99%。 应该注意的是,在 1098 个测试阳性的患者中,只有 99 个患有该疾病。 这意味着,如果您获得了阳性测试,那么对于准确度高达 99%的测试,您实际患病的可能性仅为 9%。
这些示例很好地警告了我们的目标是在测试数据中进行均衡分配,尤其是当您使用准确性指标比较不同模型的有效性时。
比较不同算法的其他有用方法是精确调用和接收器操作特性曲线。 如果我们针对不同的阈值计算上述指标,则可以绘制这些图。 如果我们算法的输出不是二进制的(0 表示负数,1 表示正数),但分数在测试为正时接近 1,而在测试为负时接近零,那么 TP,TN,FP,FN 的数量将取决于 在我们选择的阈值上。
让我们以图像中猫检测为例。 对于每个区域,分类器输出一个分数,该分数显示出它对检测的信心。 如果将阈值设置为 0.5,则 0.6 的分数表示检测为阳性,而 0.45 的分数表示阴性。 如果阈值降低到 0.4,则两次检测都将变为阳性。 下表说明了前面的指标随阈值而变化的情况。
| 阈 | FPR | TPR | PPV | TP | TN | FN | FP |
| 0.72 | 1 | 0.98 | 0.33 | 487 | 0 | 7 | 990 |
| 0.88 | 0.5 | 0.97 | 0.46 | 485 | 430 | 9 | 560 |
| 0.97 | 0.1 | 0.94 | 0.8 | 464 | 878 | 30 | 112 |
| 0.99 | 0.05 | 0.93 | 0.87 | 460 | 923 | 34 | 67 |
| 1.06 | 0.01 | 0.87 | 0.96 | 430 | 976 | 64 | 14 |
| 1.08 | 0.005 | 0.84 | 0.98 | 416 | 985 | 78 | 5 |
| 1.16 | 0.001 | 0.69 | 0.99 | 344 | 989 | 150 | 1 |
如果将 FPR 与 TPR 作图,我们将得到所谓的 ROC(接收机操作员特性)曲线,如下所示:
![](img/a38d1883-1a58-4a94-9a77-67facda77864.png)
要获得精确召回(PR)曲线,我们需要针对精确度/ PPV 绘制召回率/ TPR。 下图显示了该曲线的示例。 建议读者进一步研究如何解释 ROC 和 PR 曲线。
![](img/6db1bd89-3d34-4435-980d-ef49716d192f.png)
# 代码结构最佳实践
在前面的章节中,我们将张量流图封装到一个类中,而无需进一步讨论。 这个想法本身已经是很好的编码实践。 有一个班级负责构建图形并仅公开对使用模型有用的东西(即输入/输出)是一种很好的编程习惯,可以节省大量时间。
# 单例模式
使用设计模式来解决一些软件设计问题也是一种常见的做法。 python 中最简单,最有用的设计模式之一就是单例模式。 当您只想将一个类的实例强制仅用于一个对象时,可以使用它,因此,即使您在项目中的多个不同位置多次实例化该类,也将引用同一个对象。 在我们的情况下,如果我们要求 TensorFlow 创建具有相同名称的多个节点或图形,则会引发错误。 因此,我们在创建图形时使用单例模式,以避免生成两次。
在下面的示例中,我们总结了一个简单的分类模型,同时还确保不会多次构建图形(也称为 Singleton 模式)。
注意`__new__`类方法的定义。 在 Python 中,当我们创建一个类的新实例时,将调用`__new__`
```py
class CAE_CNN_Encoder(object):
__instance = None
# Singleton pattern
def __new__(cls):
if CAE_CNN_Encoder.__instance is None:
# First time new is called
CAE_CNN_Encoder.__instance = object.__new__(cls)
CAE_CNN_Encoder.__instance.build_graph()
return CAE_CNN_Encoder.__instance
def build_graph(self, img_size=28):
self.__x = tf.placeholder(tf.float32, shape=[None, img_size * img_size], name='IMAGE_IN')
self.__x_image = tf.reshape(self.__x, [-1, img_size, img_size, 1])
self.__y_ = tf.placeholder("float", shape=[None, 10], name='Y')
with tf.name_scope('ENCODER'):
##### ENCODER
# CONV1: Input 28x28x1 after CONV 5x5 P:2 S:2 H_out: 1 + (28+4-5)/2 = 14, W_out= 1 + (28+4-5)/2 = 14
self.__conv1_act = tf.layers.conv2d(inputs=self.__x_image, strides=(2, 2), name='conv1',
filters=16, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# CONV2: Input 14x14x16 after CONV 5x5 P:0 S:2 H_out: 1 + (14+4-5)/2 = 7, W_out= 1 + (14+4-5)/2 = 7
self.__conv2_act = tf.layers.conv2d(inputs=self.__conv1_act, strides=(2, 2), name='conv2',
filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
with tf.name_scope('LATENT'):
# Reshape: Input 7x7x32 after [7x7x32]
self.__enc_out = tf.layers.flatten(self.__conv2_act, name='flatten_conv2')
self.__dense = tf.layers.dense(inputs=self.__enc_out, units=200, activation=tf.nn.relu, name='fc1')
self.__logits = tf.layers.dense(inputs=self.__dense, units=10, name='logits')
def __init__(self, img_size=28):
if CAE_CNN_Encoder.__instance is None:
self.build_graph(img_size)
@property
def output(self):
return self.__logits
@property
def labels(self):
return self.__y_
@property
def input(self):
return self.__x
@property
def image_in(self):
return self.__x_image
```
# CNN 创建食谱
以下几点基于我们在训练神经网络方面的经验以及该领域研究人员认为的当前最佳实践。 希望如果您需要从头开始设计自己的 CNN 架构,他们将为您提供帮助。 但是,在尝试设计自己的 CNN 之前,您应该查看其他现成的体系结构以从中学习,并检查它们是否已经为您完成了工作。
1. 使用内核大小为 3x3 的卷积层。 就参数和计算而言,较大的内核更昂贵。 最重要的是,如我们在前面的章节中所看到的,您可以堆叠卷积层以产生更大的接收场,并受益于更多的非线性激活。
2. 第一层卷积通常应至少具有 32 个过滤器。 这样,更深的层不受第一层提取的特征数量的限制。
3. 尽可能避免使用池化层。 相反,请使用步长为 2 的卷积层。这将像池化那样对输入进行下采样,但它不会像池化那样丢弃宝贵的信息。 同样,使用跨步卷积就像将 conv 和合并在一层中一样。
4. 减小要素地图的空间大小时,应增加使用的过滤器数量,以免丢失过多信息。 在深度网络中,请避免在第一层中过快减小空间大小。
5. 请遵循本章中有关从小规模开始网络设计,然后逐渐增加复杂性的建议,以避免出现过大的问题。
6. 使用 batchnorm。 确实有助于培训您的网络!
7. 随着您对网络的深入了解,逐渐减小要素地图的空间大小。
8. 最小化 FC 层的数量(在最后一层之前使用 dropout)。 仅在最终需要连接某些标量特征时才使用 FC。 (您甚至可以通过在输入通道上进行编码来避免这种情况)
9. 如果您需要较大的接收场(物体大小接近总图像大小的检测或分类),请尝试对每层使用具有指数膨胀因子的膨胀卷积。 这样,您将在保持少量参数的同时非常迅速地扩大接收范围。
10. 如果网络变深并且训练损失没有减少,请考虑使用剩余连接。
11. 在使网络精度在期望值之内并且如果计算成本成为问题之后,您可能会根据使用情况,研究深度卷积,瓶颈模块之类的技术,或现场出现的任何技术。
请记住,CNN 的培训和设计是一门经验丰富的科学,因此请始终注意,被视为最佳实践的内容会迅速发生变化。
# 摘要
在本章中,我们了解到遵循最佳实践将对作为机器学习工程师的日常活动有所帮助。 我们已经看到了如何准备数据集并将其拆分为子集,以促进对网络的正确训练和微调。 此外,我们还研究了执行有意义的测试,其中获得的结果代表了将模型部署到目标应用程序时所看到的结果。 涉及的另一个主题是对数据的过度拟合和不足,以及为了解决这些问题而应遵循的最佳实践。 此外,解决了数据集不平衡的问题,我们已经看到了一个简单的示例,该示例可能在哪里找到(疾病诊断)。 为了解决这个问题,建议收集更多的数据,扩充数据集并选择不平衡数据集不变的评估指标。 最后,展示了如何构造代码以使其更具可读性和重用性。
在下一章中,我们将看到如何管理大型数据集以及如何将训练过程扩展到多个 GPU 和系统。
\ No newline at end of file
# 大规模培训
到目前为止,在本书中,我们使用或查看的数据集的大小从数万个(MNIST)样本到略超过一百万个(ImageNet)。 尽管所有这些数据集在刚推出时都被认为是巨大的,并且需要使用最先进的机器,但是 GPU 和云计算等技术的迅捷发展现已使它们易于培训。 由功率较低的机器的人。
但是,深度神经网络的强大功能来自其随输入的数据量进行扩展的能力。 简而言之,这意味着您可以用来训练模型的数据越好,越干净,结果越好。 研究人员已经意识到了这一点,我们可以看到,新的公共数据集中的训练样本数量一直在增加。
结果,很有可能,如果您开始研究行业中的问题,甚至只是最近的 Kaggle 竞赛,您很有可能将使用可能包含数百万个元素的数据集。 如何处理如此庞大的数据集,以及如何有效地训练模型,就成为一个现实问题。 差异可能意味着要等待三天而不是 1 个月的时间来完成模型的训练,因此这不是您想出错的事情。
在本章中,您将学习一些解决以下问题的方法:
* 数据集太大而无法放入内存
* 如何在多台机器上扩展训练范围
* 数据过于复杂而无法在普通目录文件夹和子文件夹中进行组织
# 在 TFRecords 中存储数据
让我们从训练网络进行图像分类的示例开始。 在这种情况下,我们的数据将是带有相关标签的图像集合。 我们存储数据的一种方法是在类似目录的文件夹结构中。 对于每个标签,我们将有一个文件夹,其中包含该标签的图像:
```py
-Data
- Person
-im1.png
- Cat
-im2.png
- Dog
-im3.png
```
尽管这似乎是存储数据的一种简单方法,但一旦数据集大小变得太大,它就会具有一些主要缺点。 当我们开始加载它时,一个很大的缺点就来了。
打开文件是一项耗时的操作,必须多次打开数百万个文件,这会增加大量的培训时间开销。 最重要的是,由于我们已将所有数据拆分开,因此不会将其存储在一个漂亮的内存块中。 硬盘驱动器将不得不做更多的工作来尝试查找和访问所有硬盘。
解决办法是什么? 我们将它们全部放入一个文件中。 这样做的好处是,您的所有数据将在计算机内存中更好地对齐以便读取,这将加快处理速度。 将所有内容都保存在一个文件中也意味着我们不必花费时间来加载数百万个文件,这将非常缓慢且效率低下。
我们可以根据需要使用几种不同的格式来存储数据,例如 HDF5 或 LMDB。 但是,当我们使用 TensorFlow 时,我们将继续使用其自己的内置格式 TFRecords。 TFRecords 是 TensorFlow 自己的标准文件格式,用于存储数据。 它是一种二进制文件格式,提供对其内容的顺序访问。 它足够灵活,可以存储复杂的数据集和标签以及我们可能想要的任何元数据。
# 制作 TFRecord
在开始之前,让我们分解一下 TFRecord 的工作方式。 打开 TFRecord 文件进行写入后,创建一个称为 Example 的内容。 这只是一个协议缓冲区,我们将使用它填充要保存在其中的所有数据。 在示例中,我们将数据存储在 Feature 中。 功能是描述示例中数据的一种方式。 功能可以是以下三种类型之一:字节列表,浮点列表或 int64 列表。 将所有数据放入功能部件并将它们写入示例缓冲区后,我们会将整个协议缓冲区序列化为字符串,然后将其写入 TFRecord 文件。
让我们看看这在实践中如何工作。 我们将继续使用前面的图像分类示例,并创建一个 TFRecord 来存储相关数据。
首先,我们创建文件,这还将返回给我们一种写入文件的方式:
```py
writer = tf.python_io.TFRecordWriter('/data/dataset.tfrecord')
```
接下来,我们假设图像已经加载并且已经作为`numpy`数组存储在内存中; 我们将在以后看到如何存储编码图像:
```py
# labels is a list of integer labels.
# image_data is an NxHxWxC numpy array of images
for index in range(len(labels)):
image_raw = image_data[index, ...].tobytes()
# Create our feature.
my_features= {
'image_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])), 'label':
tf.train.Feature(int64_list=tf.train.Int64List(value=[labels[index]]))}
# The Example protocol buffer.
example = tf.train.Example(features=tf.train.Features(feature=my_features)
writer.write(example.SerializeToString())
writer.close() # Close our tfrecord file after finishing writing to it.
```
我们遍历标签列表,将每个图像数组一次转换为原始字节。
要在示例中存储数据,我们需要向其添加功能。 我们将功能存储在字典中,其中每个键都是我们选择的某些字符串名称,例如`label`,值是`tf.train.Feature`,这就是我们的数据。
必须使用`tf.train.BytesList``tf.train.Int64List``tf.train.FloatList`将进入`tf.train.Feature`的数据转换为期望的正确类型。
接下来,我们创建一个`tf.train.Example`协议缓冲区并将功能传递给它。 最后,我们将 Example 序列化为字符串并将其写入 TFRecord 文件。 一旦遍历了整个图像阵列,就必须记住关闭文件进行写入。
# 存储编码图像
优化内存使用率的一种方法是使用某种压缩方式(即 PNG)对图像进行编码,在这种情况下 TFRecord 会更小,但是您仍需要在使用之前解压缩数据,这可能需要一些时间。 在实践中要做的是使用另一个 CPU 内核来减轻计算量。
# 分片
尽管我们说最好将所有数据保存在一个文件中,但实际上并非 100%正确。 由于 TFRecords 是按顺序读取的,因此,如果仅使用一个文件,我们将无法重新整理数据集。 经过一段时间的训练之后,每次到达 TFRecord 的末尾时,您都将返回到数据集的开头,但是不幸的是,每次浏览文件时,数据的顺序都相同。
为了允许我们随机播放数据,我们可以做的一件事是通过创建多个 TFRecord 文件并将数据散布到这些多个文件中来*分片*我们的数据。 这样,我们可以在每个纪元处重新整理加载 TFRecord 文件的顺序,因此我们在训练时将为我们有效地整理数据。 每 100 万张图像需要 1000 个碎片,这是可以遵循的良好基准。
在下一节中,我们将看到如何使用 TFRecords 建立有效的数据馈送管道。
# 建立高效的管道
当我们处理较小的数据集时,仅将整个数据集加载到计算机内存中就足够了。 如果您的数据集足够小,这很简单并且可以正常工作; 但是,在很多时候,情况并非如此。 现在我们将研究如何克服这个问题。
为了避免一次加载所有数据,我们将需要创建一个数据管道以将我们的训练数据馈入模型。 除其他事项外,该管道将负责从存储中加载一批元素,对数据进行预处理,最后将数据提供给我们的模型。 幸运的是,这一切都可以使用 TensorFlow 数据 API 轻松完成。
对于这些示例,我们将假定已将数据保存到多个(在本例中为两个)TFRecord 文件中,如先前所述。 如果您有两个以上,则没有区别; 您只需在设置内容时包括所有名称即可。
我们首先从所有 TFRecord 文件名的列表创建 TFRecord 数据集:
```py
# Create a TFRecord dataset that reads all of the Examples from
two files.
train_filenames= ["/data/train1.tfrecord", "/data/train2.tfrecord"]
train_dataset = tf.data.TFRecordDataset(filenames)
```
接下来,我们必须解码 TFRecords。 为此,我们编写了一个函数,该函数将接受 TFRecord,对其进行解码,然后返回输入图像及其对应的标签:
```py
# Function for decoding our TFRecord. We assume our images are fixed size 224x224x3
def decode_tfrec(proto_in):
my_features = {'image_raw': tf.FixedLenFeature([], tf.string),
'Label': tf.FixedLenFeature([], tf.int64)}
parsed_features = tf.parse_single_example(proto_in, features=my_features)image = tf.decode_raw(parsed_features['image_raw'], tf.uint8)
image = tf.cast(image, tf.float32) # Tensorflow data needs to be float32.
image = tf.reshape(image, [224,224,3]) # Need to reshape your images.
label = tf.cast(parsed_features['label'], tf.int32) # Labels need to be int32
label = tf.one_hot(label, depth=...) # Convert our labels to one hot.
return image, label
```
然后,我们将此函数传递给`dataset.map()`方法,该方法将为我们执行:
```py
train_dataset = train_dataset.map(decode_tfrec, num_parallel_calls=4)
```
# 并行调用地图转换
默认情况下,您在数据集上调用的任何地图转换都仅作用于数据集的单个元素,并且将按顺序处理元素。 要加快速度并使用所有 CPU 功能,最简单的方法是将`num_parallel_calls`参数设置为可用的 CPU 内核数。 这样,我们就不会浪费任何可用的 CPU 能力。 但是,警告您不要将其设置为高于可用内核的数量,因为由于调度效率低下,这实际上可能会降低性能。
您想要对数据进行的任何转换(例如数据扩充)也可以编写为函数,然后像以前一样传递给 map 方法,以将其应用于数据集。 例如,请注意以下代码:
```py
train_dataset = train_dataset.map(decode_tfrec, num_parallel_calls=4) # Decode tfrecord.
train_dataset = train_dataset.map(data_augmentation,
num_parallel_calls=4) # Augment data.
```
# 批处理
您希望在管道末尾做的最后一件事是生成一批准备发送到 GPU 进行训练的数据。 这可以通过批处理方法简单地完成,并传入所需的批处理大小:
```py
train_dataset = train_dataset.batch(128) # Take a batch of 128 from the dataset.
```
当试图使我们的管道尽可能高效时,批次的大小是一个重要的参数。 尽可能大可能并不总是最好的。 例如,如果您的图像上有很多预处理步骤,那么当 CPU 对大量图像进行预处理时,GPU 可能会处于空闲状态。
# 预取
我们能够建立有效数据管道的另一种方法是始终准备好一批数据准备发送到 GPU。 理想情况下,在训练模型时,我们希望 GPU 的使用率始终保持在 100%。 这样,我们可以最大程度地利用昂贵的硬件,该硬件可以在训练时有效地计算前进和后退的传球次数。
为此,我们需要 CPU 加载并准备一批图像,以准备在向前和向后传递模型的过程中传递给 GPU。 幸运的是,在收集批处理之后,我们可以使用简单的预取转换轻松完成此操作,如下所示:
```py
train_dataset= train_dataset.batch(128).prefetch(1)
```
使用预取将确保我们的数据管道在进行训练时为我们准备一整批数据,准备将其加载到 GPU 中以进行下一次迭代。 这样做可以确保我们的管道在等待一批批次收集之前不会减慢速度,并且如果获取批次所需的时间少于模型的前后传递时间,那么我们的管道将尽可能高效。
要清楚的是,此处使用`prefetch(1)`表示我们`prefetch`整批数据。 这就是为什么我们将批处理作为流水线的最后一步,并在此处使用预取功能,因为这样做最有效。
# 追踪图形
TensorFlow 提供了一种很好的方式来分析并查看整个图形通过其时间轴跟踪工具执行所需的时间。 这是查看图表的哪些部分正在减慢训练速度并发现数据管道中任何低效率的好工具。
我们将从为您提供如何跟踪图形的示例开始。 这非常简单:您只需在常规代码中添加几行,就会生成一个 JSON 文件,我们可以将该文件加载到 Google Chrome 浏览器中,以查看图形执行的所有时间:
```py
from tensorflow.python.client import timeline
.... # Your model and training code here
with tf.Session() as sess:
# We set some options to give to the session so graph execution is profiled.
options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
run_metadata = tf.RunMetadata()
# Run your graph and supply the options we set.
sess.run(model_output, options=options, run_metadata=run_metadata)
# We create the Timeline object here then write it to json file.
created_timeline = timeline.Timeline(run_metadata.step_stats)
chome_readable_trace = created_timeline.generate_chrome_trace_format()
with open('my_timeline.json', 'w') as file:
file.write(chome_readable_trace)
```
在此代码中,我们导入 TensorFlow 时间轴模块,然后设置两个选项以启用图形跟踪并将其提供给`Session.run()`。 运行图之后,我们创建`Timeline`对象,该对象将包含对图执行进行性能分析的结果。 然后,我们将其转换为 Chrome 跟踪格式,最后将其写入 JSON 文件。
要查看结果,您需要打开一个新的 Chrome 窗口。 然后,在地址栏中输入`chrome://tracing`并按*输入*。 左上角将有一个加载按钮。 使用它来加载刚刚保存的 JSON 文件。
现在将显示跟踪图形的结果。 查看此内容将告诉您图形的每个部分执行所需的时间。 您应该特别注意存在大块空白的地方。 这些空白表示设备(例如您的 GPU)正坐在那里等待数据,以便它们可以执行计算。 您应该尝试通过优化数据馈送方式来消除这些问题。
但是请注意,您的管道可能已完全优化,但是您没有 CPU 周期来足够快地处理管道。 检查您的 CPU 使用情况,看看是否是这种情况。
# TensorFlow 中的分布式计算
在本节中,您将学习如何在 TensorFlow 中分配计算; 强调如何做到这一点的重要性如下:
* 并行运行更多实验(即,找到超参数,例如 gridsearch)
* 在多个 GPU(在多个服务器上)上分配模型训练,以减少训练时间
一个著名的用例是,Facebook 发布了一篇论文,该论文能够在 1 小时(而不是几周)内训练 ImageNet。 基本上,它在 256 个 GPU 上的 ImageNet 上训练了 ResNet-50,该 GPU 分​​布在 32 台服务器上,批量大小为 8,192 张图像。
# 模型/数据并行
实现并行性和在多台服务器中扩展任务的方法主要有两种:
* **模型并行性**:当模型不适合 GPU 时,您需要在不同服务器上计算层。
* **数据并行性**:当我们在不同的服务器上分布相同的模型但处理不同的批次时,每个服务器将具有不同的梯度,并且我们需要在服务器之间进行某种同步。
在本节中,我们将重点介绍易于实现的数据并行性:
![](img/d768ce6b-5760-4e1e-87e6-4ac7904b55be.png)
# 同步/异步 SGD
如前所述,在数据并行性中,每个模型都会从训练集中获取一些数据并计算自己的梯度,但是考虑到每个工作人员都将拥有相同的模型,我们需要在更新模型之前以某种方式进行同步。
在同步 SGD 中,所有工作人员都会计算一个梯度并等待计算所有梯度,然后将模型更新并再次分发给所有工作人员:
![](img/f8da6b99-2533-4df2-87c9-2ba3c2165494.png)
# 当数据不适合在一台计算机上时
可能出现的一个问题是,我们根本无法将数据存储在一台计算机上和/或我们仍然需要在该数据集上进行搜索。 为了解决此类问题,我们可能不仅需要分布式**分布式 SQL****NoSQL** )数据库,例如 Cassandra。 Cassandra 支持在可用性和性能至关重要的多个系统上进行数据分发:
![](img/fbc95570-cec1-4bbb-b2a1-04cd9aca41bd.png)
![](img/39ffcbf8-2596-4a24-8a6b-2c4b661edaa2.png)
卡桑德拉(Cassandra)尽最大努力避免出现单点故障。 例如,所有节点都将像一种主节点一样工作(没有实际的主节点),因此,在某种类型的高可用性备份中,所有节点都有责任处理请求并自动在节点之间分配数据。
# NoSQL 系统的优势
与关系数据库(例如旧版本的 MySQL 和 PostgreSQL)相比,NoSQL 数据库在数据量太大以及不需要关系数据库的功能(例如触发器或存储过程)时会发光。 。
在继续之前,让我们列出 NoSQL 系统的优点:
* 水平缩放; 要获得更高的性能,只需添加更多机器
* 我们不需要事先知道表之间的关系
* 允许在整个工作期间更改表结构
* 更快(没有复杂的关系数据库机制)
* 数据通常保存在分布式文件系统上,因此,例如,将图像存储在 NoSQL 数据库中就可以了
![](img/d618b557-f396-4253-8a9c-6b1805c45120.png)
# 安装 Cassandra(Ubuntu 16.04)
安装 Oracle Java 1.8:
* ``sudo apt-get update​``
* `sudo add-apt-repository ppa:webupd8team/java​`
* `sudo apt-get update​`
* `sudo apt-get -y install oracle-java8-installer​`
安装 Cassandra:
* `echo "deb http://www.apache.org/dist/Cassandra/debian 310x main" | sudo tee -a /etc/apt/sources.list.d/cassandra.sources.list​`
* `curl https://www.apache.org/dist/Cassandra/KEYS | sudo apt-key add -​`
* `sudo apt-get update​`
* `sudo apt-get install cassandra`
* `sudo service cassandra status`
* `sudo nodetool status​`
# CQLSH 工具
CQLSH 是允许您向 Cassandra 节点发出 SQL 命令的工具:
![](img/6bce090b-199b-46e8-a34a-9e2e3e4d6f4e.jpg)
对于图形用户界面,有一个很好的工具叫做 DBWeaver,它也可以完成此工作:
![](img/0f086a20-5dfe-4e60-9def-f15577d50f49.png)
DBWeaver 示例
# 创建数据库,表和索引
1. 首先,我们需要创建数据库(键空间)并选择节点如何复制数据:
```py
CREATE KEYSPACE mydb WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };
```
2. 现在,我们创建一个表:
```py
CREATE TABLE tb_drive ( id uuid PRIMARY KEY, wheel_angle float, acc float, image blob );
```
3. 如下添加一些数据:
```py
INSERT INTO tb_drive (id,wheel_angle,acc) VALUES (now(),0.2,0.5);
INSERT INTO tb_drive (id,wheel_angle,acc) VALUES (now(),0.1,0.5);
INSERT INTO tb_drive (id,wheel_angle,acc) VALUES (now(),0.0,0.5);
```
4. (在任何时间点)创建要查询的所有列的索引(这就是为什么要快)
```py
CREATE INDEX idxAngle ON tb_drive (wheel_angle);
CREATE INDEX idxAcc ON tb_drive (acc);
```
# 用 Python 进行查询
首先,在开始玩之前,我们需要安装 Python 驱动程序`pip install cassandra-driver​`; 以下代码片段仅列出了 Cassandra 集群中表的内容:
```py
from cassandra.cluster import Cluster
import cassandra.util
import uuid
import numpy as np
# Considering that the cluster is on localhost​
cluster = Cluster()
# Other option if you know the IPs​
# cluster = Cluster(['192.168.0.1', '192.168.0.2'])​
# Get a session to the database​
session = cluster.connect('mydb')
# Doing a query​
rows = session.execute('SELECT * FROM tb_drive limit 5')
print('Columns:',rows.column_names)
for row in rows:
print(row.id, row.acc, row.wheel_angle)
```
# 在 Python 中填充表格
在以下示例中,我们将填充表格,包括一个存储图像的字段:
```py
insert_string = """​INSERT INTO tb_drive (id, wheel_angle, acc, image)​ VALUES (%s, %s, %s, %s)​"""
for data in dataset:
# Split from dataset the image path, steering angle, and acceleration​
img_path, steering_angle, acc = data
# Load image (png compressed)​
with open(img_path, 'rb') as f:
content_file = f.read()
# Insert into database​
session.execute(insert_string,(uuid.uuid1(), steering_angle, acc, content_file))
```
# 做备份
对于备份(快照):结果存储在`var/lib/cassandra/data/)​`中:
```py
nodetool -h localhost snapshot mydb
```
要还原数据(可能需要截断/删除表),请执行以下操作:
然后我们复制在目录`/var/lib/Cassandra/data/keyspace/table_name-UUID`之前创建的快照(数据库备份文件),然后:
```py
nodetool refresh
```
# 在云中扩展计算
在您作为机器学习工程师的设计周期和生命周期中,您可能会遇到这样的情况,即您办公室中可用的计算能力根本不够,并且您不能等待 IT 团队为您购买新服务器。 因此,例如,如果您能负担得起每小时 24.48 美元的价格,则可能拥有一个 p3.16xlarge,带有 8 个 GPU Nvidia V100、64 核和 488 GB RAM。
在本部分中,您将学习有关可帮助您解决计算能力不足问题的 Amazon AWS 服务的信息。
您将了解以下 Amazon Cloud Services:
* **弹性计算云****EC2**
* S3
* 贤者
# EC2
这是我们创建服务器的服务,您基本上可以在其中创建任何服务器来完成工作:
![](img/9289b8b0-dd5e-414d-92ed-6d86b641fdbe.png)
在这里,您可以配置诸如访问服务器的方式(通常使用私钥):
![](img/932ef3e1-6117-4d2d-ae95-364bd962c0f2.png)
在这里,我们配置所需的磁盘空间:
![](img/02449f1f-b4a5-474b-b101-b945c1050b8f.png)
在这里,我们配置将可用的端口:
![](img/889e9ca5-5aec-4f15-ba71-5835459a79dc.png)
可用端口
# 哪一个
这是 AWS 中最酷的功能之一,它使您可以从所有数据和已安装的工具(从一个服务器实例到另一个服务器实例)创建映像。 因此,您可以仅使用所有工具,驱动程序等配置一台服务器,以后再使用具有相同映像的另一台服务器:
![](img/c1d35dc4-78cb-492e-b80e-bb18f768cc69.png)
# 储存(S3)
Amazon S3 是存储系统,您可以在其中从常规 HTTP 请求上传/下载文件。 S3 的构想是*存储桶*,您可以从中存储/下载文件。 另外,有些插件可让您将 S3 直接映射到您的 EC2 实例,例如某些远程文件夹(不建议使用):
![](img/b80eb463-7348-4012-a829-cf7cc4e310ab.png)
以下屏幕截图显示了如何创建存储桶:
![](img/f9fc72c1-0b23-4c61-bf05-a8e941efa887.png)
S3 系统可以进行公共配置,因此人们可以从任何地方下载/上传内容。
# 贤者
SageMaker 提供了一种在云中训练/部署机器学习模型的简便方法。 SageMaker 将提供 Jupyter 笔记本,您可以在其中可视化/训练模型并直接连接到 S3 中的数据(提供了访问 S3 的 API):
![](img/267b6c45-039c-4978-a801-025f9f562e00.png)
在这里,我们显示了创建笔记本实例的默认选项:
![](img/111c47f0-b5d3-4192-a4e8-9f15918348bf.png)
这是笔记本面板:
![](img/34fd1923-47ac-424f-b30b-4e4602f4bac3.png)
并且,这是笔记本(您可以查看训练模型的示例来检查 API 的工作方式):
![](img/5a8a5836-b47a-4691-afff-476fa5e867c8.png)
# 摘要
在本章中,您学习了如何处理数据集太大而无法由普通台式计算机处理的数据。 我们看到了如何在多个 GPU 和机器之间训练 TensorFlow 模型,最后,我们研究了用于存储数据并将其有效地馈送到模型的最佳实践。
在本书的学习过程中,我们研究了计算机视觉中当前流行的许多问题,以及如何使用深度学习解决所有这些问题。 我们还提供了有关如何在 TensorFlow 中实现这些功能的见解。 在此过程中,我们介绍了如何使用 TensorFlow。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册