面向计算机视觉的深度学习:1~5

2023-04-23 11:24:55 浏览数 (1)

一、入门

计算机视觉是理解或操纵图像和视频的科学。 计算机视觉具有许多应用,包括自动驾驶,工业检查和增强现实。 深度学习在计算机视觉中的使用可以分为多个类别:图像和视频中的分类,检测,分割和生成。 在本书中,您将学习如何为计算机视觉应用训练深度学习模型并将其部署在多个平台上。 我们将在本书中使用 TensorFlow,这是一个用于深入学习的流行 python 库,用于示例。 在本章中,我们将介绍以下主题:

  • 深度学习的基础知识和词汇
  • 深度学习如何满足计算机视觉?
  • 设置将用于本书所涵盖示例的开发环境
  • 体验 TensorFlow 及其强大的工具,例如 TensorBoard 和 TensorFlow Serving

了解深度学习

计算机视觉作为一个领域有着悠久的历史。 随着深度学习的出现,计算机视觉已被证明可用于各种应用。 深度学习是来自人工神经网络ANN)的技术的集合,这是机器学习的一个分支。 人工神经网络以人脑为模型。 有彼此链接的节点,它们彼此传递信息。 在以下各节中,我们将通过了解常用的基本术语来详细讨论深度学习的工作原理。

感知机

人工神经元或感知机接受多个输入并执行加权求和以产生输出。 感知机的权重是在训练过程中确定的,并基于训练数据。 以下是感知机的图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WRb765o6-1681567519349)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1e292483-bf0f-4474-9ee4-9f18966861b6.png)]

如上图所示,对输入进行加权和求和。 然后,对于二分类问题,该总和然后通过单位步长函数传递。 感知机只能通过从示例中学习权重来学习简单函数。 学习权重的过程称为训练。 可以通过基于梯度的方法对感知机进行训练,这将在后面的部分中进行介绍。 感知机的输出可以通过 activation 函数或 transfer 函数传递,这将在下一部分中进行说明。

激活函数

activation函数使神经网络成为非线性。 激活函数决定是否应触发感知机。 在训练激活期间,函数在调整梯度中起着重要作用。 下一节所示的activation函数(如 Sigmoid)会衰减较大幅度的值。 activation函数的这种非线性行为为学习复杂函数提供了深层网络。 activation的大多数函数是连续和微分函数,但整流单元为 0 除外。输入的每一个小变化,连函数能的输出都会有很小的变化。 微分函数在域中的每个点都有一个导数。

为了训练神经网络,函数必须是可微的。 以下是一些activation函数。

如果您不了解诸如连续性和可微分性之类的术语,请不要担心。 在各章中将变得更加清楚。

Sigmoid

Sigmoid 可以看作是平滑的阶跃函数,因此可以微分。 Sigmoid 可用于将任何值转换为概率,并可用于二分类。 Sigmoid 映射将输入映射到 0 到 1 范围内的值,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wO8dZLOu-1681567519350)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/f171c6d5-67e5-4e9c-a168-be794c8e444b.png)]

相对于XY值的变化将很小,因此,梯度将消失。 经过一番学习之后,变化可能很小。 在下一节中介绍的另一个称为tanh的激活函数是 Sigmoid 曲线的缩放比例版本,避免了梯度消失的问题。

双曲正切函数

双曲正切函数或 tanh 是 Sigmoid 曲线的缩放形式。 像 Sigmoid 一样,它是光滑且可微分的。 tanh将输入映射到 -1 到 1 的值,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tGp85h1l-1681567519351)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/623bcab3-b4f7-4ce7-b817-a3f1bad2944d.png)]

梯度比 S 形更稳定,因此减少了消失的梯度问题。 Sigmoid 和tanh一直在发射,这使 ANN 变得很沉重。 下一节中介绍的整流线性单元ReLU)激活函数通过不触发而避免了这种陷阱。

整流线性单元(ReLU)

ReLu 可以让大量数字通过。 这会使一些神经元陈旧,并且它们不会发射。 这增加了稀疏性,因此很好。 ReLU将输入x映射到max(0, x),即,它们将负输入映射为 0,而正输入无任何变化,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OaNoqJyF-1681567519351)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/f869a450-2d05-4d78-9ad6-8cc941adcfff.png)]

由于 ReLU 不会一直触发,因此可以更快地进行训练。 由于功能简单,因此在计算上最便宜。 选择activation函数在很大程度上取决于应用。 尽管如此,ReLU 在许多问题上都运行良好。 在下一节中,您将学习如何将几个感知机堆叠在一起,这些感知机可以学习比感知机更复杂的函数。

人工神经网络(ANN)

ANN 是感知机函数的集合。 感知机连接形成隐藏的层或单元。 隐藏的单元形成了将低层空间中的输入层映射到输出层的非线性基础,这也称为人工神经网络。 ANN 是从输入到输出的映射。 该图是通过将输入与偏差进行加权相加来计算的。 权重和偏置值以及架构称为model

训练过程确定这些权重和偏置的值。 在训练开始时,使用随机值初始化模型值。 通过使用损失函数将误差与基本事实进行对比来计算误差。 根据计算出的损耗,在每一步调整权重。 如果无法进一步减少误差,则停止训练。 训练过程会在训练过程中学习特征。 这些特征比原始图像更好地表示。 以下是人工神经网络或多层感知机的示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bZj1qeQA-1681567519351)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/386212ca-7e62-482c-b6b3-d0d30426ca19.png)]

x 的多个输入通过感知机的隐藏层传递并求和。 通用逼近定理表明,这样的神经网络可以逼近任何函数。 隐藏层也可以称为密集层。 每个层都可以具有上一节中描述的activation函数之一。 可以根据问题选择隐藏层和感知机的数量。 还有更多的事情可以使此多层感知机适用于多类分类问题。 一个多类别的分类问题试图区分十多个类别。 我们将在以下各节中探讨这些术语。

单热编码

单热编码是在出现分类问题时表示目标变量或类的一种方式。 目标变量可以从字符串标签转换为一键编码的向量。 单热向量在目标类别的索引处填充有1,但在其他所有地方填充有0。 例如,如果目标类别是猫和狗,则可以分别用[1, 0][0, 1]。 对于 1,000 个类别,单热向量的大小为 1,000 整数,全为零,但有一个1。 它不假设目标变量的相似性。 通过在下一节中说明的一键编码和 softmax 的组合,可以在 ANN 中实现多类分类。

Softmax

Softmax 是一种强制神经网络输出 1 之和的方法。因此,softmax函数的输出值可以视为概率分布的一部分。 这在多类分类问题中很有用。 Softmax 是一种activation函数,其特征是输出求和为 1。通过将输出除以所有其他值的总和,可以将输出转换为概率。 欧几里德距离可以在 softmax 概率和一键编码之间进行计算,以进行优化。 但是下一部分将说明的交叉熵是一个更好的成本函数,可以进行优化。

交叉熵

交叉熵比较 softmax 和一键编码输出之间的距离。 交叉熵是一种损失函数,必须将其误差降至最低。 神经网络估计每个类别给定数据的概率。 必须将概率最大化到正确的目标标签。 交叉熵是负对数概率的总和。 对数值用于数值稳定性。 最大化一个函数等同于最小化相同函数的负数。 在下一节中,我们将看到以下正则化方法,以避免 ANN 的过拟合:

  • 丢弃法
  • 批量规范化
  • L1 和 L2 归一化

丢弃法

丢弃法是规整神经网络以避免 ANN 过拟合的有效方法。 在训练期间,丢弃层通过随机删除隐藏的单元来破坏神经网络,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jah9re7t-1681567519352)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/9eae1f1c-0308-4516-9cda-3e9531220150.png)]

请注意如何随机训练神经元。 丢弃也是组合多个神经网络的有效方法。 对于每个训练案例,我们随机选择一些隐藏的单元,以便最终为每个案例使用不同的架构。 这是装袋和模型平均的极端情况。 推断期间不应使用丢弃层,因为没有必要。

批量规范化

批量规范化或批量规范可提高神经网络训练的稳定性和表现。 它将平均值为零且标准差为 1 的层的输出归一化。这减少了过拟合,并使网络训练更快。 这对于训练复杂的神经网络非常有用。

L1 和 L2 正则化

L1 惩罚权重的绝对值,并趋于使权重为零。 L2 惩罚权重的平方值,并且在训练过程中倾向于使权重更小。 两个正则化均假设权重较小的模型更好。

训练神经网络

训练 ANN 非常棘手,因为它包含多个要优化的参数。 权重的更新过程称为反向传播。 最小化误差的过程称为优化。 我们将在下一节中详细介绍这两个方面。

反向传播

反向传播算法通常用于训练人工神经网络。 权重根据计算的误差从后向更新,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sryo3KzC-1681567519352)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/91e3b72c-6258-41b8-a8dc-3a471bd1da38.png)]

在计算了误差之后,可以使用梯度下降来计算权重更新,如下一节中所述。

梯度下降

梯度下降算法执行多维优化。 目标是达到全局最高水平。 梯度下降是许多机器学习模型中使用的一种流行的优化技术。 它用于改善或优化模型预测。 梯度下降的一种实现称为随机梯度下降SGD),在神经网络中正变得越来越流行(在下一节中说明)。 优化涉及计算误差值并更改权重以实现最小误差。 找到最小值的方向是loss函数的梯度的负值。 下图定性显示了梯度下降过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-stJY8koy-1681567519352)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/13375e63-ff4f-46e6-a64b-4f01435f18e1.png)]

学习速度决定了每个步骤应该多大。 注意,具有非线性激活的 ANN 将具有局部最小值。 SGD 在实践中更好地用于优化非凸成本函数。

随机梯度下降

SGD 与梯度下降相同,区别在于 SGD 每次仅用于部分数据训练。 该参数称为小批量大小。 从理论上讲,甚至可以使用一个示例进行训练。 在实践中,最好尝试各种数字。 在下一部分中,我们将讨论比标准 ANN 在图像数据上更有效的卷积神经网络。

访问这里,可以很好地看到凸面和非凸面的梯度下降情况。

玩转 TensorFlow 游乐场

TensorFlow 游乐场是神经网络的交互式可视化。 访问这里,方法是通过更改参数来查看前面提到的项如何协同工作。 这是操场的屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTv6hlrx-1681567519353)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/2a57f080-1ddb-4c9d-b3d0-201bc792caad.png)]

TensorFlow 游乐场中的仪表板

如前所示,读者可以更改学习率,激活,正则化,隐藏单元和层,以了解其如何影响训练过程。 您可以花费一些时间来调整参数,以直观了解神经网络如何处理各种数据。

卷积神经网络

卷积神经网络CNN)与前面各节中描述的神经网络相似。 CNN 具有权重,偏差和通过非线性激活产生的输出。 规则的神经网络接受输入,并且神经元完全连接到下一层。 同一层中的神经元不共享任何连接。 如果我们对图像使用常规的神经网络,由于神经元数量众多,它们的大小将非常大,从而导致过拟合。 我们不能将其用于图像,因为图像尺寸较大。 增加模型大小,因为它需要大量的神经元。 可以将图像视为具有高度,宽度和深度尺寸的体积。 深度是图像的通道,它是红色,蓝色和绿色。 CNN 的神经元以体积方式排列,以利用体积。 每个层都将输入体积转换为输出体积,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3DcHbpZ-1681567519353)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/42b70af2-d682-4f56-8b6c-fe90ac4a02c9.jpeg)]

卷积神经网络过滤器通过变换进行编码。 学到的滤镜可以检测图像中的特征或图案。 层越深,图案越抽象。 一些分析表明,这些层具有检测边缘,角和图案的能力。 CNN 层中的可学习参数小于上一节中描述的密集层。

内核是用于对图像进行卷积的参数卷积层。 卷积操作如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3NAHEvP-1681567519353)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/0b5c0e8a-edbf-42ed-b858-2788b7e281b9.png)]

内核有两个参数,称为步幅和大小。 大小可以是矩形的任何尺寸。 步幅是每次移动的像素数。 长度为 1 的步幅产生几乎相同大小的图像,长度为 2 的步幅产生一半大小。 填充图像将有助于获得相同的输入大小。

最大池

池化层位于卷积层之间。 合并层通过采样减小了跨层图像的大小。 通过在窗口中选择最大值来完成采样。 窗口中的平均池平均值。 池化还可以作为一种正则化技术来避免过拟合。 在特征的所有通道上进行池化。 合并也可以进行各种步骤。

窗口的大小是 CNN 接收场的量度。 下图显示了最大池化的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npTyHXoz-1681567519353)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/9fb61e22-8dbd-46cc-87a2-c325197bf504.png)]

CNN 是任何计算机视觉深度学习模型中最重要的组成部分。 毫不夸张地说,没有 CNN,任何计算机都不可能拥有视觉。 在下一部分中,我们将讨论几个可用于一些应用的高级层。

访问这里,以获取有关 CNN 和最大池操作的出色可视化。

循环神经网络(RNN)

循环神经网络RNN)可以对顺序信息进行建模。 他们不假定数据点密集。 它们从一系列序列数据的先前数据的输出执行相同的任务。 这也可以被视为记忆。 RNN 无法记住更长的序列或更长的时间。 在训练过程中将其展开,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ed6QVs1a-1681567519354)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/74621d85-d8ff-4532-a793-8eda53ef7651.png)]

如上图所示,该步骤每次都展开和训练。 在反向传播期间,梯度会随着时间消失。 为了克服此问题,可以使用较长的短期记忆来记忆较长的时间。

长短期记忆(LSTM)

长短期记忆LSTM)可以存储较长时间的信息,因此,它可以有效地捕获长期效率。 下图说明了如何设计 LSTM 单元:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KBSD6yKU-1681567519354)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/e166af85-c2c6-4046-bf30-4984795eff8f.png)]

LSTM 有几个门:忘记,输入和输出。 忘记门保持信息先前的状态。 输入门使用输入更新当前状态。 输出门决定将信息传递到下一个状态。 忘记和保留重要内容的能力使 LSTM 可以在更长的时间内记住。 您已经学习了将在整本书中使用的深度学习词汇。 在下一节中,我们将了解如何在计算机视觉的背景下使用深度学习。

用于计算机视觉的深度学习

计算机视觉在计算机上实现了人类视觉的特性。 计算机可以是智能手机,无人机,闭路电视,MRI 扫描仪等形式,并带有各种感知传感器。 传感器产生数字形式的图像,必须由计算机解释。 下一部分将说明这种解释或智能的基本构成部分。 使用深度学习技术可以有效解决计算机视觉中出现的各种问题。

分类

图像分类是充满信心地用对象或概念标记整个图像的任务。 这些应用包括给定人脸图像的性别分类,识别宠物的类型,为照片添加标签等。 以下是此类分类任务的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tb3kftVo-1681567519358)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/0ac5cf28-7641-47ed-a19f-af886508aae7.png)]

第 2 章, “图像分类”详细介绍了可用于分类任务的方法,在第 3 章“图像检索”,我们使用分类模型对深度学习模型进行可视化并检索相似的图像。

检测或定位和分割

检测或定位是一项在图像中找到对象并使用边界框定位该对象的任务。 这项任务有许多应用,例如为自动驾驶汽车寻找行人和招牌。 下图是检测的示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ta0PQiZk-1681567519359)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/eebafaa9-de29-4acc-aa17-e39010013809.png)]

分割是进行像素分类的任务。 这样可以很好地分离对象。 这对于处理医学图像和卫星图像很有用。 更多示例和说明可以在第 4 章,对象检测和第 5 章,“图像分割”中找到。

相似性学习

相似性学习是学习两个图像如何相似的过程。 可以基于语义含义在两个图像之间计算分数,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Go0oohJI-1681567519360)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/55f91a2f-3104-4dee-8feb-81aebcea95c3.png)]

从发现相似产品到执行人脸识别,此方法有多种应用。 第 6 章,“相似性学习”处理相似性学习技术。

图片字幕

图像标题是用文本描述图像的任务,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7TTgu6FV-1681567519360)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/407ec761-30e5-41da-8b72-69b17212196c.png)]

经 Vinyals 等人许可复制。

第 8 章,“图像字幕生成”详细介绍了图像字幕生成。 这是将自然语言处理NLP)和计算机视觉技术相结合的独特情况。

生成模型

生成模型在生成图像时非常有趣。 以下是样式转换应用的示例,其中使用该图像的内容和其他图像的样式生成图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zl7qboBR-1681567519360)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1a581deb-0956-44e2-bf7c-fc63e591160a.jpeg)]

经 Gatys 等人许可复制。

可以出于其他目的生成图像,例如新的训练示例,超分辨率图像等。 第 7 章“生成模型”详细介绍了生成模型。

视频分析

与以前的情况相比,视频分析处理的是整个视频,而不是图像。 它具有多种应用,例如运动跟踪,入侵检测和监视摄像机。 第 9 章“视频分类”处理特定于视频的应用。 时间数据的新维度带来了许多有趣的应用。 在下一节中,我们将看到如何设置开发环境。

开发环境设置

在本节中,我们将设置编程环境,该环境对于遵循本书其余部分中的示例非常有用。 读者可以选择以下操作系统:

  • 开发操作系统OS),例如 Mac,Ubuntu 或 Windows
  • 部署操作系统(例如 Mac,Windows,Android,iO 或 Ubuntu)安装在云平台(例如 Amazon Web ServicesAWS), 谷歌云平台GCP),Azure,Tegra,Raspberry Pi

无论使用哪种平台,本书中开发的所有代码均应运行无任何问题。 在本章中,我们将介绍开发环境的安装过程。 在第 10 章“部署”中,我们将介绍在各种其他环境(例如 AWS,GCP,Azure,Tegra 和 Raspberry Pi)中的部署安装。

硬件和操作系统 - OS

对于开发环境,您需要具有很多计算能力,因为训练在计算上非常昂贵。 Mac 用户相当受限于计算能力。 Windows 和 Ubuntu 用户可以使用更多处理器和通用图形处理单元GP-GPU),来增强其开发环境。 下一节将对此进行说明。

通用图形处理单元 - GP-GPU

GP-GPU 是一种特殊的硬件,可加快训练深度学习模型的训练过程。 NVIDIA 公司提供的 GP-GPU 由于具有完善的软件和社区支持,因此在深度学习训练和部署中非常受欢迎。 读者可以设置带有此类 GP-GPU 的机器以进行更快的训练。 有很多选择,读者可以根据预算选择一个。 选择与 GP-GPU 功率相对应的 RAM,CPU 和硬盘也很重要。 安装硬件后,必须安装以下驱动程序和库。 使用 Mac 或不使用 GP-GPU 的 Windows/Ubuntu 的读者可以跳过安装。

以下是设置环境所需的库:

  • 计算机统一设备架构CUDA
  • CUDA 深度神经网络CUDNN

计算机统一设备架构 - CUDA

CUDA 是 NVIDIA 使用 GPU 的并行特性提供的 API 层。 安装此驱动程序后,还将安装硬件驱动程序。 首先,从 NVIDIA 门户网站下载CUDA库。

按照页面上的说明进行操作,下载驱动程序,然后按照安装说明进行操作。 这是 Ubuntu CUDA 的屏幕截图和安装说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OJxFQ9hd-1681567519360)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/e042367c-385d-481f-a9f0-7b9e64db394f.png)]

这些命令将安装所需的cuda-drivers和其他 CUDA API。

您可以通过在命令提示符下键入nvidia-smi来检查驱动程序是否正确安装。

CUDA 深度神经网络 - CUDNN

CUDNN库为深度学习算法提供了原语。 由于此包由 NVIDIA 提供,因此对其硬件进行了高度优化,并且运行速度更快。 该包提供了几种用于深度学习的标准例程。 著名的深度学习库(例如tensorflowcaffe等)使用这些包。 在下一节中,将提供安装CUDNN的说明。 您可以从 NVIDIA 门户网站下载CUDNN

用户帐户是必需的(免费注册)。

将相关文件复制到CUDA文件夹,使其更快地在 GPU 上运行。 我们不会直接使用CUDACUDNN库。 Tensorflow 使用这些来优化例程在 GP-GPU 上工作。

安装包

训练有素的深度学习模型需要几个库。 我们将安装以下库,并查看在竞争包中选择以下包的原因:

  • Python 和其他依赖项
  • OpenCV
  • TensorFlow
  • Keras

Python

Python 是任何数据科学应用的实际选择。 它拥有最大的社区和库支持生态系统。 用于 Python 的 TensorFlow API 是最完整的,因此,Python 是首选的自然语言。 Python 有两个版本-Python2.x 和 Python3.x。 在本书中,我们将讨论 Python3.x。 这种选择有几个原因:

  • 到 2020 年,Python 2.x 的开发将停止,因此,Python3.x 是 Python 的未来
  • Python 3.x 避免了原始实现中的许多设计缺陷
  • 与普遍的看法相反,Python3.x 具有与 Python 2.x 一样多的数据科学支持库。

在本书中,我们将使用 Python 版本 3。 转到这里,然后根据操作系统下载版本 3。 按照下载链接中给出的步骤安装 Python。 安装 Python 后,必须安装 PIP3,以方便安装 Python 包。 然后通过输入以下命令安装几个 Python 包,以便以后可以安装OpenCVtensorflow

代码语言:javascript复制
 sudo pip3 install numpy scipy  scikit-learn  pillow  h5py 

先前安装的包的说明如下:

  • numpy 是高度优化的数值计算包。 它具有强大的 N 维封装数组对象,并且numpy库的矩阵运算针对速度进行了高度优化。 图像可以存储为 3 维numpy对象。
  • scipy 有一些用于科学和工程计算的例程。 在本书的后面,我们将使用一些优化包。
  • scikit-learn 是一个机器学习库,我们将使用其中的许多辅助函数。
  • Ppillow对于图像加载和基本操作很有用。
  • H5py包是 HDF5 二进制数据格式的 Pythonic 接口。 这是存储使用 Keras 训练的模型的格式。

开放式计算机视觉 - OpenCV

OpenCV是著名的计算机视觉库。 该库中有许多可用的图像处理例程,这些例程很有用。 以下是在 Ubuntu 中安装 OpenCV 的步骤。

代码语言:javascript复制
sudo apt-get install python-opencv

对于其他操作系统,可以在这里找到类似的步骤。 它是跨平台的,针对 CPU 密集型应用进行了优化。 它具有多种编程语言的接口,并且受 Windows,Ubuntu 和 Mac 支持。

TensorFlow 库

tensorflow是一个用于开发和部署深度学习模型的开源库。 TensorFlow 使用计算图进行数据流和数值计算。 换句话说,数据或张量流经图,因此名称为tensorflow。 该图具有可进行任何数值计算的节点,因此适用于深度学习操作。 它为各种平台和硬件提供了一个 API。 TensorFlow 在后端处理扩展和优化的所有复杂性。 它最初是为在 Google 上进行研究而开发的。 它是最著名的深度学习库,拥有大型社区,并提供用于生产中的可视化和部署的工具。

安装 TensorFlow

使用以下命令,使用 PIP3 为 CPU 安装tensorflow

代码语言:javascript复制
sudo pip3 install tensorflow 

如果您使用的是 GPU 硬件,并且已安装CUDACUDNN,请使用以下命令安装tensorflow的 GPU 版本:

代码语言:javascript复制
sudo pip3 install tensorflow-gpu

现在tensorflow已安装并可以使用。 我们将尝试一些示例以了解 TensorFlow 的工作原理。

打印 Hello,TensorFlow 的 TensorFlow 示例

我们将直接在 Python Shell 中使用 TensorFlow 进行示例。 在此示例中,我们将使用 TensorFlow 打印您好,TensorFlow 。

  1. 通过在命令提示符下键入以下命令,从 shell 调用 Python:
代码语言:javascript复制
 python3
  1. 通过输入以下命令导入tensorflow库:
代码语言:javascript复制
 >>> import tensorflow as tf
  1. 接下来,使用字符串Hello, TensorFlow定义一个常量。 这与通常的 Python 赋值操作不同,因为该值尚未初始化:
代码语言:javascript复制
 >>> hello = tf.constant('Hello, TensorFlow!') 
  1. 创建一个会话以初始化计算图,并为该会话命名:
代码语言:javascript复制
 >>> session = tf.Session() 

可以使用变量hello作为参数运行会话。

  1. 现在,该图执行并返回打印的特定变量:
代码语言:javascript复制
 >>> print(session.run(hello))

它应该打印以下内容:

代码语言:javascript复制
Hello, TensorFlow!

让我们再看一个示例,以了解会话和图的工作方式。

访问这里获取本书中所有示例的代码。 该代码将根据章节进行组织。 您可以提出问题并在存储库中获得帮助。

将两个数字相加 的 TensorFlow 示例

这是如何使用 TensorFlow 将两个数字相加的另一个简单示例。

  1. 创建一个 Python 文件并使用以下代码导入tensorflow
代码语言:javascript复制
 import tensorflow as tf

对于所有后面的示例,前面的导入都是必需的。 假定读者已经为所有示例导入了库。 可以通过以下方式定义placeholder。 占位符在分配时不会加载。 在此,将变量定义为类型为float32placeholderplaceholder是一个空声明,并且在运行会话时可以采用值。

  1. 现在我们定义一个placeholder,如以下代码所示:
代码语言:javascript复制
 x = tf.placeholder(tf.float32)
 y = tf.placeholder(tf.float32)
  1. 现在,可以将占位符的求和运算定义为通常的加法运算。 在这里,不执行操作,而只是使用以下代码定义:
代码语言:javascript复制
 z = x   y
  1. 可以如前面的示例所示创建会话。 如下所示定义时,该图即可执行计算:
代码语言:javascript复制
 session = tf.Session()
  1. 以字典格式定义placeholder的值:
代码语言:javascript复制
 values = {x: 5.0, y: 4.0}
  1. 使用变量c和值运行会话。 该图将值提供给适当的占位符,并为变量c取回值:
代码语言:javascript复制
 result = session.run([z], values) print(result)

作为添加结果,该程序应打印[ 9.0 ]。

可以理解,这不是将两个数字相加的最佳方法。 该示例旨在了解 TensorFlow 中如何定义张量和操作。 想象一下使用一万亿个数字并将它们相加会多么困难。 TensorFlow 可以使用相同的 API 轻松实现这种扩展。 在下一节中,我们将看到如何安装和使用 TensorBoard 和 TensorFlow 服务。

TensorBoard

TensorBoard 是一套可视化工具,用于使用 TensorFlow 训练基于深度学习的模型。 可以在 TensorBoard 中可视化以下数据:

  • :计算图,设备位置和张量详细信息
  • 标量:指标,例如损失,迭代精度
  • 图像:用于查看带有相应标签的图像
  • 音频:用于收听训练或生成的音频
  • 分布:用于查看某些标量的分布
  • 直方图:包括权重和偏置的直方图
  • 投影器:帮助可视化 3 维空间中的数据
  • 文本:打印训练文本数据
  • 配置文件:查看用于训练的硬件资源

Tensorboard 与 TensorFlow 一起安装。 转到 python3 提示符并输入以下命令(类似于上一个示例)以开始使用 Tensorboard:

代码语言:javascript复制
x = tf.placeholder(tf.float32, name='x')
y = tf.placeholder(tf.float32, name='y')
z = tf.add(x, y, name='sum')

请注意,已将参数名称作为占位符和操作的附加参数提供。 这些是我们可视化图时可以看到的名称。 现在我们可以在 TensorBoard 中使用以下命令将图写入特定的文件夹:

代码语言:javascript复制
session = tf.Session()
summary_writer = tf.summary.FileWriter('/tmp/1', session.graph)

此命令将图写入磁盘到参数中给定的特定文件夹中。 现在可以使用以下命令调用 Tensorboard:

代码语言:javascript复制
tensorboard --logdir=/tmp/1

可以将任何目录作为logdir选项的参数传递,该选项用于存储文件。 转到浏览器并粘贴以下 URL 以开始可视化以访问 TensorBoard:

代码语言:javascript复制
http://localhost:6006/

浏览器应显示如下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1iGq2tL-1681567519361)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/5c0dfa38-1cd4-4e1c-8d90-6e9bbc31129e.png)]

浏览器窗口中的 TensorBoard 可视化

显示加法图,并为占位符指定名称。 单击它们时,我们可以在右侧看到该操作的所有张量细节。 使自己熟悉选项卡和选项。 此窗口有几个部分。 我们将在不同的章节中了解它们。 TensorBoard 是 TensorFlow 中最好的区分工具之一,这使其比其他任何深度学习框架都更好。

TensorFlow Serving 工具

TensorFlow Serving 是 TensorFlow 中的工具,专为灵活的部署环境而开发,可提供高延迟和吞吐量的环境。 使用 TensorFlow 训练的任何深度学习模型都可以与服务一起部署。 通过运行以下命令来安装 Serving:

代码语言:javascript复制
sudo apt-get install tensorflow-model-server

有关如何使用服务的逐步说明,将在第 3 章,“图像检索”中进行介绍。 请注意,仅在 Ubuntu 中即可轻松安装 Serving; 对于其他操作系统,请参考这里。 下图说明了 TensorFlow Serving 和 TensorFlow 在生产环境中如何交互:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDv5TRod-1681567519361)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/54fe6552-5ba5-4270-ab85-74a753389c05.png)]

训练过程可以产生许多模型,Serving 会无缝切换它们,而不会造成任何停机。 除了第 3 章,“图像检索”和第 10 章,“部署”之外,以下各章均不需要 TensorFlow Serving。

Keras

Keras是一个用 Python 编写的用于深度学习的开源库。 它提供了一个简单的接口来使用 TensorFlow 作为后端。 Keras 还可以与 Theano,深度学习 4j 或 CNTK 一起用作后端。 Keras 通过专注于友好性,模块化和可扩展性而设计用于轻松快速地进行实验。 它是一个独立的框架,可以在 CPU 和 GPU 之间无缝运行。 Keras 可以单独安装,也可以使用tf.keras API 在 TensorFlow 本身中使用。 在本书中,我们将使用tf.keras API。 我们已经看到了安装开发环境所需的库的步骤。 顺利安装 CUDA,CUDNN,OpenCV,TensorFlow 和 Keras 并对以下章节至关重要。

总结

本章涵盖了深度学习的基础知识。 本章介绍的词汇将在整本书中使用,因此,您可以经常参考本章。 示例还显示了计算机视觉的应用。 还介绍了用于开发环境的各种平台的所有包的安装。

在下一章中,我们将讨论如何在数据集上使用 Keras 和 TensorFlow 训练分类模型。 我们将研究如何使用更大的模型和其他技术(例如增强和微调)来提高准确率。 然后,我们将看到由世界各地的几个人提出的几种先进模型,它们在比赛中达到了最佳准确率。

二、图像分类

图像分类是将整个图像分类为单个标签的任务。 例如,给定图像是狗还是猫,图像分类任务可以将图像标记为狗或猫。 在本章中,我们将了解如何使用 TensorFlow 建立这样的图像分类模型,并学习提高准确率的技术。

我们将在本章介绍以下主题:

  • 在 TensorFlow 中训练 MNIST 模型
  • 在 Keras 中训练 MNIST 模型
  • 其他流行的图像测试数据集
  • 更大的深度学习模型
  • 训练猫与狗的模型
  • 开发实际应用

在 TensorFlow 中训练 MNIST 模型

在本节中,我们将了解混合国家标准技术研究所数据库MNIST)的数据,并建立一个简单的分类模型。 本部分的目的是学习深度学习的通用框架,并将其用于 TensorFlow。 首先,我们将建立一个感知机或逻辑回归模型。 然后,我们将训练 CNN 以获得更高的准确率。 我们还将看到 TensorBoard 如何帮助可视化训练过程并了解参数。

MNIST 数据集

MNIST数据具有从 0 到 9 的手写数字,其中 60,000 张用于训练的图像和 10,000 张用于测试的图像。 该数据库被广泛用于尝试使用最少预处理的算法。 这是一个学习机器学习算法的好而紧凑的数据库。 这是最著名的图像分类问题数据库。 这里显示了一些示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUDyVYJ7-1681567519361)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/fe1a0746-bfbb-445f-949c-8787df44e2ec.png)]

从上图中可以看出,这些手写字符有 10 个标签。 将图像标准化为 28 个图像像素乘以 28 个图像像素的尺寸,转换为灰度尺寸,并居中为固定尺寸。 这是一个很小的数据集,可以在上面快速测试算法。 在下一节中,我们将看到如何加载此数据集以在 TensorFlow 中使用。

加载 MNIST 数据

直接从 TensorFlow 加载MNIST数据。 请注意,在加载数据时,我们指定单热编码作为参数。 标签存储为整数,但为了进行训练,应将其加载为一键编码。 从现在开始,假设读者正在使用导入了 TensorFlow tf的编辑器运行代码。 以下是要加载MNIST_data的代码段:

代码语言:javascript复制
from tensorflow.examples.tutorials.mnist import input_data
mnist_data = input_data.read_data_sets('MNIST_data', one_hot=True)

对于首次运行,将下载数据,并且可能需要一些时间。 从第二次运行开始,将使用缓存的数据。 在下一节中,我们将构建一个感知机来对数字进行分类。

建立一个感知机

感知机是单层神经网络。 本章介绍的概念(例如,完全连接的层,activation函数,随机梯度下降,logits,单热编码,softmax 和交叉熵)在这里将很有用。 您将学习如何在 TensorFlow 中定义神经网络的这些组件,并使用该网络训练MNIST数据。

为输入数据和目标定义占位符

占位符是传递数据的张量。 占位符不是特定值,但将在计算过程中接收输入。 首先声明感知机的输入大小,类数,批量大小以及迭代或批量的总数。 x_input是稍后将在其中输入图像的输入。 y_input是占位符,将在其中提供一键式标签或目标,如下所示:

代码语言:javascript复制
input_size = 784 no_classes = 10 batch_size = 100 total_batches = 200   x_input = tf.placeholder(tf.float32, shape=[None, input_size])
y_input = tf.placeholder(tf.float32, shape=[None, no_classes])

shape参数中的None表示它可以是任意大小,因为我们尚未定义批量大小。 第二个参数是x_input的张量大小和y_input的类数。 根据占位符的类型,我们以浮点数形式发送了数据。 接下来,我们可以定义感知机。

定义全连接层的变量

让我们通过解释weightsbias等变量来定义一个简单的线性分类器或感知机。 这些变量的值将在计算过程中获悉。 这些也称为模型参数。 权重变量使用具有输入大小和类数形状的正态随机分布进行初始化。 由于图像被整形为单个向量,因此输入大小为784。 类的数量是 10,它等于数据集中的位数。 偏差变量还使用大小等于类数的随机正态分布进行初始化。 weightsbias定义如下:

代码语言:javascript复制
weights = tf.Variable(tf.random_normal([input_size, no_classes]))
bias = tf.Variable(tf.random_normal([no_classes]))

变量的初始化可以为零,但随机正态分布可提供稳定的训练。 然后对输入进行加权,并加上偏置以产生logits,如下所示:

代码语言:javascript复制
logits = tf.matmul(x_input, weights)   bias

必须将感知机产生的logits与单热标签y_input进行比较。 正如在第 1 章“入门”中所了解的,最好使用 softmax 和交叉熵来比较logits和单热标签。

TensorFlow 的 tf.nn.softmax_cross_entropy_with_logits API 为我们做到了。 可以通过平均交叉熵来计算损耗。 然后,交叉熵通过tf.train.GradientDescentOptimizer完成的梯度下降优化得到馈送。 优化器接受损失,并以0.5的学习率将其最小化。 接下来显示 softmax,交叉熵,损耗,优化的计算:

代码语言:javascript复制
softmax_cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
    labels=y_input, logits=logits)
loss_operation = tf.reduce_mean(softmax_cross_entropy)
optimiser = tf.train.GradientDescentOptimizer(
    learning_rate=0.5).minimize(loss_operation)

softmax 和交叉熵是从tf.nn包一起计算的,该包还有其他几种有用的方法。 tf.train有几个优化器,在这里,我们使用原始梯度下降。 您可以访问 TensorFlow API 文档以了解其他可选参数。 到目前为止,已定义了占位符,变量和操作,但尚未填充张量。

阅读 TensorFlow 中提供的优化器列表。 Adam 优化器对于计算机视觉应用特别有用。 它通常会收敛得更快,因此我们不需要定义学习率。 有关优化器的理论总结,请访问这里。

用数据训练模型

现在,您已经定义了模型和训练操作。 下一步是开始使用数据训练模型。 在训练过程中,计算梯度并更新权重。 变量尚未初始化。 接下来,启动会话并使用全局变量初始化程序初始化变量:

代码语言:javascript复制
session = tf.Session()
session.run(tf.global_variables_initializer())

本书中的大多数示例都需要前两行。 假定读者将在需要的地方使用这两行。 现在,该图已准备好填充数据并开始训练。 通过循环,批量读取数据并训练模型。 通过使用所需的张量运行会话来进行模型训练。 为了使图更新权重,必须调用优化器:

代码语言:javascript复制
for batch_no in range(total_batches):
    mnist_batch = mnist_data.train.next_batch(batch_size)
    _, loss_value = session.run([optimiser, loss_operation], feed_dict={
        x_input: mnist_batch[0],
  y_input: mnist_batch[1]
    })
    print(loss_value)

run方法的第一个参数可以具有一个数组,要求为其提供值的输出。 我们通过损失是因为打印损失会告诉我们模型是否正在训练中。 随着我们将损失降至最低,预计损失将减少。 feed_dict是一个 Python 字典,用于直接将输入和目标标签提供给占位符。 一旦该循环结束,损耗通常应低于 0.46。 接下来,我们可以通过计算精度来评估模型的工作效果,如下所示:

代码语言:javascript复制
predictions = tf.argmax(logits, 1)
correct_predictions = tf.equal(predictions, tf.argmax(y_input, 1))
accuracy_operation = tf.reduce_mean(tf.cast(correct_predictions, 
                                            tf.float32))
test_images, test_labels = mnist_data.test.images, mnist_data.test.labels
accuracy_value = session.run(accuracy_operation, feed_dict={
    x_input: test_images,
  y_input: test_labels
})
print('Accuracy : ', accuracy_value)
session.close()

该预测应该是最大激活的索引。 应该将其与 MNIST 标签上的基本事实进行比较,以进行正确的预测。 使用正确预测的平均值计算准确率。 可以通过将测试数据作为提要字典运行会话来评估数据的准确率。 当整个程序运行时,最终应产生 90% 左右的精度。 如果没有用于训练和测试的更简单的 API,该模型的定义似乎太明确了。 此基本定义水平为 TensorFlow 赋予了表达能力。 在下一部分中,我们将看到更高级别的 API。 感知机获得的精度不是很高,在下一节中,我们将使用具有卷积层的更深的网络来提高精度。

建立多层卷积网络

在本节中,我们将看到如何在 TensorFlow 中创建多层卷积网络,并观察更深的网络如何提高分类准确率。 我们将使用 TensorFlow 层的 API 定义层,而不是从头开始定义它们。 最佳实践方法已根植于这些方法中。 可以从上一节开始导入库,数据集和占位符。 这次,我们将使用 TensorBoard 可视化训练过程。 为了可视化变量的统计信息,必须将变量统计信息的值添加到tf.summary中。

摘要将被写入 TensorBoard 可以解释的文件夹中。 我们定义一个函数来编写摘要,以便可以使用 TensorBoard 可视化它们:

代码语言:javascript复制
def add_variable_summary(tf_variable, summary_name):
  with tf.name_scope(summary_name   '_summary'):
    mean = tf.reduce_mean(tf_variable)
    tf.summary.scalar('Mean', mean)
    with tf.name_scope('standard_deviation'):
        standard_deviation = tf.sqrt(tf.reduce_mean(
            tf.square(tf_variable - mean)))
    tf.summary.scalar('StandardDeviation', standard_deviation)
    tf.summary.scalar('Maximum', tf.reduce_max(tf_variable))
    tf.summary.scalar('Minimum', tf.reduce_min(tf_variable))
    tf.summary.histogram('Histogram', tf_variable)

变量summary函数写入变量的摘要。 摘要中添加了五个统计量:平均值,标准差,最大值,最小值和直方图。 汇总可以是scalarhistogram。 当记录多个变量时,我们将看到如何在 TensorBoard 中可视化这些值。 与以前的模型不同,我们将MNIST数据的大小调整为一个正方形,并像二维图像一样使用它。 以下是将图像整形为 28 个图像像素乘 28 个图像像素的命令:

代码语言:javascript复制
x_input_reshape = tf.reshape(x_input, [-1, 28, 28, 1], 
    name='input_reshape')

尺寸-1表示批量大小可以是任何数字。 请注意,有一个名为name的自变量会在 TensorBoard 图形中反映出来,以便于理解。 我们将定义一个 2D 卷积层,其中定义了输入,过滤器,内核和激活。 可以在任何地方调用此方法以获取更多示例,并且在激活函数必须具有整流线性单元ReLU)的情况下很有用。 convolution函数层定义如下:

代码语言:javascript复制
def convolution_layer(input_layer, filters, kernel_size=[3, 3],
  activation=tf.nn.relu):
    layer = tf.layers.conv2d(
        inputs=input_layer,
  filters=filters,
  kernel_size=kernel_size,
  activation=activation,
  )
    add_variable_summary(layer, 'convolution')
    return layer

kernel_sizeactivation的默认参数。 汇总将添加到函数中的层,然后返回该层。 每当调用该函数时,都必须将input_layer作为参数传递。 这个定义将使我们的其他代码变得简单而小巧。 以非常相似的方式,我们将为pooling_layer定义一个函数,如下所示:

代码语言:javascript复制
def pooling_layer(input_layer, pool_size=[2, 2], strides=2):
    layer = tf.layers.max_pooling2d(
        inputs=input_layer,
  pool_size=pool_size,
  strides=strides
    )
    add_variable_summary(layer, 'pooling')
    return layer

该层的pool_sizestrides的默认参数分别为[2, 2]2。 这些参数通常工作良好,但可以在必要时进行更改。 也为该层添加了摘要。 接下来,我们将定义一个密集层,如下所示:

代码语言:javascript复制
def dense_layer(input_layer, units, activation=tf.nn.relu):
    layer = tf.layers.dense(
        inputs=input_layer,
  units=units,
  activation=activation
    )
    add_variable_summary(layer, 'dense')
    return layer

定义的密集层具有用于激活的默认参数,并且还添加了变量摘要。 pooling_layer从卷积层获取特征图,并通过使用池大小和跨距进行跳过来将其缩小为一半。 所有这些层均以图方式连接,并且已被定义。 没有一个值被初始化。 可以添加另一个卷积层,以将采样特征从第一卷积层转换为更好的特征。 合并后,我们可以将激活重塑为线性形式,以便通过密集的层进行馈送:

代码语言:javascript复制
convolution_layer_1 = convolution_layer(x_input_reshape, 64)
pooling_layer_1 = pooling_layer(convolution_layer_1)
convolution_layer_2 = convolution_layer(pooling_layer_1, 128)
pooling_layer_2 = pooling_layer(convolution_layer_2)
flattened_pool = tf.reshape(pooling_layer_2, [-1, 5 * 5 * 128],
  name='flattened_pool')
dense_layer_bottleneck = dense_layer(flattened_pool, 1024)

卷积层之间的唯一区别是过滤器的大小。 重要的是,各层之间的尺寸必须适当地变化。 选择内核和步幅的参数是任意的,这些数字是根据经验选择的。 定义了两个卷积层,然后可以是一个全连接层。 密集层 API 可以采用单个维的任何向量并将其映射到任意数量的隐藏单元,如本例中的1024。 隐藏层之后是 ReLU 激活 ,以使其成为非线性计算。 也为此层添加了变量摘要。 接下来是具有退出率的退出层。 保持较高水平将阻止网络学习。 根据使用的时间,可以将训练模式设置为TrueFalse。 在训练中,我们将其设置为True(默认为False)。 在计算准确率时,我们将不得不更改此设置。 因此,为此保留了一个布尔值,将在训练过程中喂入:

代码语言:javascript复制
dropout_bool = tf.placeholder(tf.bool)
dropout_layer = tf.layers.dropout(
        inputs=dense_layer_bottleneck,
        rate=0.4,
        training=dropout_bool
    )

丢弃层再次被馈送到一个密实层,这称为对率。 对率是最后一层,激活会导致类数增加。 激活将针对特定类别(即目标类别)加标,并且最多可以获得这 10 个激活的最大值:

代码语言:javascript复制
logits = dense_layer(dropout_layer, no_classes)

对率的输出与上一节中创建的模型非常相似。 现在,对数可以通过 softmax 层传递,然后像以前一样进行交叉熵计算。 在这里,我们添加了一个作用域名称,以在 TensorBoard 中获得更好的可视化效果,如下所示:

代码语言:javascript复制
with tf.name_scope('loss'):
    softmax_cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
        labels=y_input, logits=logits)
    loss_operation = tf.reduce_mean(softmax_cross_entropy, name='loss')
    tf.summary.scalar('loss', loss_operation)

可以使用tf.train API 的方法优化此loss函数。 在这里,我们将使用Adamoptimiser。 学习率无需定义,并且在大多数情况下效果良好:

代码语言:javascript复制
with tf.name_scope('optimiser'):
    optimiser = tf.train.AdamOptimizer().minimize(loss_operation)

像以前一样计算准确率,但是为正确的预测和准确率计算添加了名称范围:

代码语言:javascript复制
with tf.name_scope('accuracy'):
    with tf.name_scope('correct_prediction'):
        predictions = tf.argmax(logits, 1)
        correct_predictions = tf.equal(predictions, tf.argmax(y_input, 1))
    with tf.name_scope('accuracy'):
        accuracy_operation = tf.reduce_mean(
            tf.cast(correct_predictions, tf.float32))
tf.summary.scalar('accuracy', accuracy_operation)

还添加了精度的标量摘要。 下一步是启动会话并初始化变量,如上一节所述。 这些行在这里不再重复。 必须合并这些摘要,并且必须定义用于编写训练和测试摘要的文件:

代码语言:javascript复制
merged_summary_operation = tf.summary.merge_all()
train_summary_writer = tf.summary.FileWriter('/tmp/train', session.graph)
test_summary_writer = tf.summary.FileWriter('/tmp/test')

注意,该图只用summary_writer写入一次。 训练与之前非常相似,除了训练时的精度计算和值被添加到摘要中。 接下来,可以批量加载数据并可以开始训练:

代码语言:javascript复制
test_images, test_labels = mnist_data.test.images, mnist_data.test.labels

for batch_no in range(total_batches):
    mnist_batch = mnist_data.train.next_batch(batch_size)
    train_images, train_labels = mnist_batch[0], mnist_batch[1]
    _, merged_summary = session.run([optimiser, merged_summary_operation],
                                    feed_dict={
        x_input: train_images,
        y_input: train_labels,
        dropout_bool: True
    })
    train_summary_writer.add_summary(merged_summary, batch_no)
    if batch_no % 10 == 0:
        merged_summary, _ = session.run([merged_summary_operation,
                                         accuracy_operation], feed_dict={
            x_input: test_images,
            y_input: test_labels,
            dropout_bool: False
        })
        test_summary_writer.add_summary(merged_summary, batch_no)

每次迭代都会返回摘要以获取训练数据,并将其添加到编写器中。 对于第十次迭代,将添加测试摘要。 请注意,仅在训练期间而不是在测试期间启用丢弃。 我们已经完成了定义以及网络摘要,可以运行该网络。 要查看训练过程,我们可以按照第 1 章,“入门”中所述前往 TensorBoard。

在深度学习中使用 TensorBoard

在浏览器中打开 TensorBoard 后,转到图表选项卡。 应该显示我们定义并接受训练的图。 右键单击节点,我们可以选择要从主图中删除的操作。 对齐后,图应如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GyQa5cVl-1681567519361)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/c07978e2-e383-4124-9f9a-2be7bf0c7899.png)]

该图说明了在先前实例中经过训练和定义的图

注意我们定义的所有层的显示效果如何。 这对于检查架构的定义非常有用。 图的方向与所有细节都很好地可视化了。 通过单击每个节点,您可以看到该节点的详细信息,例如输入和输出张量形状,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okCjiykD-1681567519362)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/e96a21f1-a15b-4962-8aec-62216cd004e1.png)]

这些值可用于交叉检查层参数的定义。 请注意左下方的图例,以使自己熟悉此页面,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EInE3Spt-1681567519362)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/58e1619e-46b3-4d78-9076-dfcfad01b5be.png)]

名称范围已分组,可以通过单击节点上的加号来查看各个组件。 节点按颜色排列。 现在我们可以移至标量页面。 通过在页面上四处移动,可以发现精度图,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rQMbVfeC-1681567519362)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/dc143414-d4bc-471b-ae2f-f6831f6030db.png)]

橙色线用于训练数据,蓝色线用于测试数据。 他们大致遵循相同的模式。 表示原始值的亮线稍微少一些,而亮线是平滑的曲线。 可以在 UI 中选择平滑系数。 测试数据的准确率已达到 97% 以上。 以下是损失摘要中的图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1j9JCoT-1681567519363)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/590361f9-9421-41be-b2a2-d2aafe0e1d6d.png)]

在训练过程中,训练和测试数据的损失都在稳步减少,这是一个好兆头。 在训练过程中将刷新所有摘要的数据,我们可以见证准确率的提高和损失的减少,从而获得 97.38% 的出色测试精度。

这可以帮助您查看模型是否正在学习并且正在朝着更好的方向发展。 其他汇总(例如最小值,最大值,平均值和标准差)也很有用。 以下是密集层的图形:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzMpMpAt-1681567519363)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/c57d3b2a-1f10-49e0-9daf-08e6ce170d0b.png)]

这些摘要对于注意权重的变化很有用。 这些分布也可以显示为直方图,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AY2EwxAv-1681567519363)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/04d8485e-8078-4516-8203-27c99d3f2e66.png)]

这些是对数的权重分布。 这些是 TensorBoard 可能提供的美丽可视化效果,并且在训练中非常有帮助。 通过使模型更深入,我们可以见证准确率的巨大提高。 在下一节中,我们将看到如何使用 Keras API 训练相同的模型。 现在您可以看到 TensorBoard 在检查深度学习模型和训练过程中的特征。

在 Keras 中训练 MNIST 模型

在本节中,我们将使用通过 tf.keras API 定义的与上一节相同的模型。 最好从 TensorFlow 学习 Keras 和Layer包,因为它们可以在几个开源代码中看到。 本书的目的是使您了解 TensorFlow 的各种产品,以便您可以在其之上构建产品。

“读取代码的次数多于写入代码的次数。”

牢记前面的引用,向您展示了如何使用各种 AP​​I 实现相同的模型。 任何最新算法实现的开放源代码都将是这些 API 的组合。 接下来,我们将从 Keras 实现开始。

准备数据集

Keras 提供MNIST数据。 首先,导入tensorflow。 然后定义一些常量,例如批量大小,类和周期数。 可以根据计算机上可用的 RAM 选择批次大小。 批量大小越大,所需的 RAM 越多。 批次大小对准确率的影响很小。 此处的类数等于 10,并且针对不同的问题而有所不同。 周期的数量决定了训练必须经过整个数据集的次数。 如果在所有周期结束时减少损失,则可以将其设置为较高的数字。 在某些情况下,训练时间较长可以提高准确率。 现在让我们看一下创建数据集的步骤:

  1. 设置输入图像的尺寸,如下所示:
代码语言:javascript复制
        batch_size = 128
        no_classes = 10
        epochs = 2
        image_height, image_width = 28, 28
  1. 使用 Keras 工具将数据从磁盘加载到内存:
代码语言:javascript复制
        (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
  1. 将向量重塑为图像格式,并使用给定的代码定义卷积的输入尺寸:
代码语言:javascript复制
        x_train = x_train.reshape(x_train.shape[0], image_height, image_width, 1)
        x_test = x_test.reshape(x_test.shape[0], image_height, image_width, 1)
        input_shape = (image_height, image_width, 1)
  1. 如下将数据类型转换为float
代码语言:javascript复制
        x_train = x_train.astype('float32')
        x_test = x_test.astype('float32')
  1. 通过减去数据均值来归一化数据:
代码语言:javascript复制
        x_train /= 255
        x_test /= 255
  1. 将分类标签转换为单热编码:
代码语言:javascript复制
        y_train = tf.keras.utils.to_categorical(y_train, no_classes)
        y_test = tf.keras.utils.to_categorical(y_test, no_classes)

这与 TensorFlow 编写代码的方式非常不同。 数据已加载到内存中,此处Placeholders的概念均不存在。

建立模型

在本节中,我们将使用一些卷积层,然后是全连接层,以训练前面的数据集。 构造一个简单的顺序模型,该模型具有两个卷积层,然后是池化层,丢弃层和密集层。 顺序模型具有add方法,可以将多个层堆叠在一起。 第一层具有 64 个过滤器,第二层具有 128 个过滤器。 所有过滤器的内核大小均为 3。 在卷积层之后应用最大池。 卷积层的输出被展平,并通过丢包连接连接到一对全连接层。

最后一层连接到 softmax,因为这是一个多类分类问题。 以下代码显示了如何定义模型:

代码语言:javascript复制
def simple_cnn(input_shape):
    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.Conv2D(
        filters=64,
        kernel_size=(3, 3),
        activation='relu',
        input_shape=input_shape
    ))
    model.add(tf.keras.layers.Conv2D(
        filters=128,
        kernel_size=(3, 3),
        activation='relu'
    ))
    model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
    model.add(tf.keras.layers.Dropout(rate=0.3))
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(units=1024, activation='relu'))
    model.add(tf.keras.layers.Dropout(rate=0.3))
    model.add(tf.keras.layers.Dense(units=no_classes, activation='softmax'))
    model.compile(loss=tf.keras.losses.categorical_crossentropy,
                  optimizer=tf.keras.optimizers.Adam(),
                  metrics=['accuracy'])
    return model
simple_cnn_model = simple_cnn(input_shape)

该模型刚刚定义,必须进行编译。 在编译损失期间,必须定义优化器和指标。 损失将是交叉熵,并通过 Adam 算法进行了优化,我们将以准确率作为度量标准。 使用加载的数据,训练和评估数据。 使用训练参数加载训练数据并拟合模型:

代码语言:javascript复制
simple_cnn_model.fit(x_train, y_train, batch_size, epochs, (x_test, y_test))
train_loss, train_accuracy = simple_cnn_model.evaluate(
    x_train, y_train, verbose=0)
print('Train data loss:', train_loss)
print('Train data accuracy:', train_accuracy)

使用 Keras API 时,不会创建会话。 然后按以下方式评估测试数据:

代码语言:javascript复制
test_loss, test_accuracy = simple_cnn_model.evaluate(
    x_test, y_test, verbose=0)
print('Test data loss:', test_loss)
print('Test data accuracy:', test_accuracy)

评估也可以在不显式创建会话的情况下创建。 运行完成后,结果应类似于以下内容:

代码语言:javascript复制
Loss for train data: 0.0171295607952
Accuracy of train data: 0.995016666667
Loss for test data: 0.0282736890309
Accuracy of test data: 0.9902

这样可以使测试数据的准确率达到 99%。 请注意,训练精度高于测试数据,并且始终打印它们都是一个好习惯。 精度的差异是由于迭代次数造成的。 由于数据集的差异,准确率比 TensorFlow 中创建的先前模型要高一些。

其他流行的图像测试数据集

MNIST数据集是用于测试算法的最常用数据集。 但是还有其他数据集可用于测试图像分类算法。

CIFAR 数据集

加拿大高级研究机构CIFAR-10)数据集包含 60,000 张图像,其中 50,000 张图像用于训练,10,000 张图像用于测试。 类的数量是 10。图像尺寸是 32 像素 x 32 像素。 以下是从每个类别中随机选择的图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZYZ8hU5j-1681567519363)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/50a4ca9c-f6dc-413c-9cb1-1691ccc0680c.png)]

这些图像很小,仅包含一个对象。 CIFAR-100数据集包含相同数量的图像,但具有 100 个类别。 因此,每个类别只有 600 张图像。 每个图像都带有一个超级标签和一个精美标签。 如果您想进行实验,可以在tf.keras.datasets上找到此数据集。

Fashion-MNIST 数据集

Fashion-MNIST是替代MNIST数据集而创建的数据集。 创建为MNIST的此数据集被认为太简单了,可以直接用MNIST代替。

以下是在执行主成分分析PCA)之后从数据集中随机选择的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEy0UYdC-1681567519364)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/da904c50-2447-4537-833a-d31218cbfe5a.png)]

数据集大小,标签数量和图像大小类似于MNIST。 可以在这个页面上找到更多详细信息。 您可以运行先前学习的模型并检查准确率。

ImageNet 数据集和竞赛

ImageNet 是具有 14,197,122 图像,21,841 个同义词集索引的计算机视觉数据集。 同义词集是 WordNet 层次结构中的一个节点,而节点又是一组同义词。 每年都会举办一次比赛,其中有 1000 个此类数据集。 它已成为评估图像分类算法表现的标准基准。

在 2013 年,基于深度学习的计算机视觉模型获得了第一名。 从那时起,只有深度学习模型赢得了竞争。 以下是多年来在比赛中排名前五位的错误率:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qN6vsGHQ-1681567519364)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/06f1198b-fb2d-44ab-baed-b2cff9e113ed.png)]

您会注意到,多年来精度以及层的深度一直在增加。 接下来,我们将了解该图中存在的模型。

更大的深度学习模型

我们将审视几种模型定义,这些模型定义在 ImageNet 竞赛中取得了最新的成果。 我们将在以下主题中单独研究它们。

AlexNet 模型

Krizhevsky 等人提出了 AlexNet(是第一个引起人们对计算机视觉深度学习的广泛兴趣的作品),它一直是这个领域里的先驱和影响力。 该模型赢得了 ImageNet 2013 挑战。 错误率是 15.4%,明显优于下一个。 该模型是具有五个卷积层的相对简单的架构。 面临的挑战是对 1,000 种对象进行分类。 图像和数据包含 1500 万条带标注的图像,其中包含 22,000 多个类别。 其中,只有 1,000 个类别用于比赛。 AlexNet 使用 ReLU 作为激活函数,发现它的训练速度比其他激活函数快几倍。 该模型的架构如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHr6Ducz-1681567519364)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/ee056ac4-3d0d-47df-ac96-5e2f6988c91b.png)]

复制自 Krizhevsky 等人。

本文还使用了数据增强技术,例如图像翻译,水平翻转和随机裁剪。 漏失层防止过拟合 。 该模型使用原始随机梯度下降SGD)进行训练。 仔细选择 SGD 的参数进行训练。 学习率在一组固定的训练迭代中变化。 动量和权重衰减采用固定值进行训练。 本文介绍了一种称为局部响应规范化LRN)的概念。 LRN 层对滤镜上的每个像素进行归一化,以避免在特定滤镜中发生巨大的激活。

不再使用该层,因为最近的研究表明,由于 LRN,没有太大的改进。 AlexNet 总共有 6000 万个参数。

VGG-16 模型

VGG 模型代表牛津大学的, 视觉几何组 。 该模型非常简单,并且比 AlexNet 具有更大的深度。 该纸有两个模型,深度分别为 16 和 19 层。 所有的 CNN 层都使用3 x 3步幅的滤镜和 1 尺寸的垫,以及 2 步幅的最大合并尺寸 2。这导致参数数量减少。 尽管由于最大池化而减小了大小,但过滤器的数量却随着层的增加而增加。 16 层深度模型的架构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vOtdWKKG-1681567519364)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/776596c6-d2f7-4656-bb91-85d84253f33c.png)]

该模型具有 1.38 亿个参数,是此处描述的所有模型中最大的。 但是参数的一致性很好。 其特征是,随着网络的深入,图像的尺寸越小,过滤器的数量就越多。 所使用的数据增强技术之一是规模抖动。 比例抖动是一种增强技术,其中具有随机大小的一侧被认为会改变比例。

Google Inception-V3 模型

Inception-V3 是 Szegedy 等人提出的,并介绍了具有更好泛化方法的初始概念。 该架构在 2014 年赢得了 ImageNet 竞赛的冠军。它旨在提高速度和尺寸的效率。 它的参数比 AlexNet 小 12 倍。 初始阶段是构建宏架构的微架构。 每个隐藏层都有一个较高级别的图像表示。 在每一层,我们可以选择使用池化或其他层。 初始使用多个内核,而不是使用一种类型的内核。 平均池之后是各种大小的卷积,然后将它们合并在一起。

可以基于数据学习内核参数。 使用多个内核,该模型可以检测较小的特征以及较高的抽象度。 1 x 1卷积将减少特征,从而减少计算量。 这将在推理过程中占用较少的 RAM。 以下是最简单形式的启动模块,其中包含具有各种内核大小和池化的卷积选项:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DghbPfcl-1681567519364)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/e38ca5d2-f9c5-4581-b5be-1a5a30c5d57b.png)]

请注意,与 AlexNet 或 VGG 相反,操作是并行进行的。 输出量巨大,因此引入了1 x 1的过滤器以降低尺寸。 将缩小的尺寸添加到架构后,它将变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCXpYQpR-1681567519365)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/cc7e385f-0826-4d07-99d7-9116a649e402.png)]

该模型的整个架构如下,包括所有的风吹草动:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LZtcTDpa-1681567519365)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/bef2324f-3a22-4f92-8af9-a6e90300e66c.png)]

该图说明了 Google Inception V3 模型架构[经 Szegedy 等人的许可复制]

有 9 个初始模块,共 100 层,它们具有良好的表现。

Microsoft ResNet-50 模型

ResNet 是 He 等人提出的,并在 2015 年赢得了 ImageNet 竞赛。此方法表明可以训练更深的网络。 网络越深,精度变得越饱和。 这甚至不是由于过拟合或由于存在大量参数,而是由于减少了训练误差。 这是由于无法反向传播梯度。 可以通过以下方法将梯度直接发送到带有残差块的更深层来克服:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bExL1IuH-1681567519365)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/ff1c06f1-c254-4602-9b50-91cf9402df05.png)]

每两层相连,形成一个残留块。 您可以看到训练是在各层之间传递的。 通过这种技术,反向传播会将误差带到较早的层。

可以使用来自这里的模型定义,它定义了模型中的每一层,并且提供ImageNet数据集上的预训练权重。

SqueezeNet 模型

Iandola 等人介绍了 SqueezeNet 模型,以减少模型尺寸和参数数量。

通过使用1 x 1过滤器替换3 x 3过滤器,使网络变得更小,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1mgGbC2a-1681567519365)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/61941eba-0b03-4a6d-93ce-69132bedbe96.png)]

经 Iandola 等人许可复制。

3 x 3过滤器的输入数量也减少了在较高级别发生时各层的下采样,从而提供了较大的激活图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2UNrsTeu-1681567519366)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/12771464-5c49-48ae-b9e4-f512ed8f42aa.png)]

经 Iandola 等人许可复制

空间转换器网络

Jaderberg 等人提出的空间转换器网络尝试在传递到 CNN 之前对图像进行转换。 这与其他网络不同,因为它尝试在卷积之前修改图像。 该网络学习参数以变换图像。 学习用于仿射变换的参数。 通过应用仿射变换,可以实现空间不变性。 在以前的网络中,空间不变性是通过最大池化层实现的。 空间转换器网络的位置如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JYj9KOv1-1681567519367)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/177e14e5-4bc8-4b5c-9e0f-754188739941.png)]

经 Jaderberg 等人许可复制

DenseNet 模型

DenseNet 是 Huang 等人提出的 ResNet 的扩展。 在 ResNet 块中,上一层通过求和合并到下一层。 在 DenseNet 中,上一层通过连接合并到下一层。 DenseNet 将所有层连接到上一层,将当前层连接到下一层。

在下图中,可以看出特征图是如何作为输入提供给其他层的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTepA1c8-1681567519367)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/2ff8e417-48ae-491e-9abc-78bccdcdc61a.png)]

经 Huang 等人许可复制

这样,它提供了多个优点,例如更平滑的梯度,特征变换等。 这也减少了参数的数量:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4XgLR16-1681567519367)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/feec4f72-5ef2-4eb0-a647-21f56d254fef.png)]

经 Huang 等人许可复制

我们已经介绍了图像分类任务的所有最新算法。 任何架构均可用于图像分类任务。 在下一节中,我们将看到如何使用这些先进的架构训练模型来预测宠物,并提高准确率。

训练猫与狗的模型

在本部分中,我们将准备和训练用于预测猫与狗的模型,并了解一些可提高准确率的技术。 大多数图像分类问题都属于这种范例。 本节介绍的技术,例如扩充和迁移学习,对于一些问题很有用。

准备数据

为了进行分类,我们将从 kaggle 下载数据并以适当的格式存储。 注册并登录 Kaggle 并转到猫狗大战。 从该页面下载train.ziptest1.zip文件。 train.zip文件包含 25,000 张宠物数据图像。 我们将仅使用部分数据来训练模型。 具有更多计算能力的读者,例如图形处理单元GPU),可以使用比建议的更多的数据。 运行以下脚本以重新排列图像并创建必要的文件夹:

代码语言:javascript复制
import os
import shutil

work_dir = '' # give your correct directory
image_names = sorted(os.listdir(os.path.join(work_dir, 'train')))

def copy_files(prefix_str, range_start, range_end, target_dir):
    image_paths = [os.path.join(work_dir, 'train', prefix_str   '.'   str(i)   '.jpg')
                   for i in range(range_start, range_end)]
    dest_dir = os.path.join(work_dir, 'data', target_dir, prefix_str)
    os.makedirs(dest_dir)
    for image_path in image_paths:
        shutil.copy(image_path, dest_dir)

copy_files('dog', 0, 1000, 'train')
copy_files('cat', 0, 1000, 'train')
copy_files('dog', 1000, 1400, 'test')
copy_files('cat', 1000, 1400, 'test')

对于我们的实验,我们将仅使用 1000 张猫和狗的图像。 因此,将图像 0–999 从下载的文件夹复制到cats下新创建的train 文件夹。 同样,将 1,000–1,400 复制到data/test/cat,将train/dogs中的 0–999 和data/test/dog中的 1,000–1,400 复制,这样我们每个类别都有 1,000 个训练示例和 400 个验证示例。

使用简单的 CNN 进行基准测试

让我们在该数据集上运行先前的simple_cnn模型,并查看其表现。 该模型的表现将成为我们判断其他技术的基本基准。 我们将为数据加载和训练定义一些变量,如下所示:

代码语言:javascript复制
image_height, image_width = 150, 150
train_dir = os.path.join(work_dir, 'train')
test_dir = os.path.join(work_dir, 'test')
no_classes = 2
no_validation = 800
epochs = 2
batch_size = 200
no_train = 2000
no_test = 800
input_shape = (image_height, image_width, 3)
epoch_steps = no_train // batch_size
test_steps = no_test // batch_size

该常数用于本节中的训练猫和狗模型的讨论中讨论的技术。 在这里,我们正在使用 2,800 张图像进行训练和测试,这对于个人计算机的 RAM 是合理的。 但这对于更大的数据集是不可持续的。 最好一次只加载一批图像进行训练和测试。 为此,tf.keras具有称为ImageDataGenerator的类,可在必要时读取图像。 假定从上一节中导入了simple_cnn模型。 以下是使用生成器加载图像的示例:

代码语言:javascript复制
generator_train = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255)
generator_test = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255)

加载时,此定义还会重新缩放图像。 接下来,我们可以使用flow_from_directory方法从目录中读取图像,如下所示:

代码语言:javascript复制
train_images = generator_train.flow_from_directory(
    train_dir,
    batch_size=batch_size,
    target_size=(image_width, image_height))

test_images = generator_test.flow_from_directory(
    test_dir,
    batch_size=batch_size,
    target_size=(image_width, image_height))

加载图像的目录,批量大小和图像的目标大小作为参数传递。 此方法执行重新缩放,并分批传递数据以拟合模型。 该生成器可直接用于拟合模型。 该模型的方法 fit_generator可以按以下方式使用:

代码语言:javascript复制
simple_cnn_model.fit_generator(
    train_images,
    steps_per_epoch=epoch_steps,
    epochs=epochs,
    validation_data=test_images,
    validation_steps=test_steps)

该模型适合来自训练图像生成器的数据。 从训练中定义周期数,并传递验证数据以获取模型过度训练的表现。 该fit_generator支持并行处理数据和模型训练。 CPU 执行重新缩放,而 GPU 可以执行模型训练。 这使得计算资源的效率很高。 经过 50 个周期后,该模型的准确率应为 60%。 接下来,我们将看到如何扩充数据集以获得改进的表现。

扩充数据集

数据扩充提供了增加数据集大小的方法。 数据扩充会在训练期间引入噪声,从而在模型中为各种输入生成鲁棒性。 该技术在数据集较小且可以组合并与其他技术一起使用的情况下很有用。 接下来,我们将看到不同类型的扩充。

增强技术

可以通过多种方式来增强图像,如下所述:

  • 翻转:图像在水平或垂直方向上被镜像或翻转
  • 随机裁剪:裁剪随机部分,因此该模型可以处理遮挡
  • 剪切:变形图像来影响对象的形状
  • 缩放:训练图像的缩放部分来处理不同比例的图像
  • 旋转:旋转对象来处理对象中各种程度的变化
  • 增白:增白是通过仅保留重要数据的主成分分析完成的
  • 归一化:通过标准化均值和方差来归一化像素
  • 通道偏移:更改颜色通道来使模型对各种伪像引起的颜色变化具有鲁棒性

所有这些技术都在ImageDataGenerator中实现,以增加数据集的大小。 以下是generator_train的修改版本,其中包含前面讨论的一些增强技术:

代码语言:javascript复制
generator_train = tf.keras.preprocessing.image.ImageDataGenerator(
    rescale=1. / 255,
    horizontal_flip=True,
    zoom_range=0.3,
    shear_range=0.3,)

替换前面代码中的generator_train将使精度提高到 90%。 更改扩充的参数,并注意更改。 在下一节中,我们将讨论一种称为迁移学习的技术,该技术有助于以更少的数据训练更大的模型。

模型的迁移学习或微调

迁移学习是从预先训练的模型中学习的过程,该模型在较大的数据集上进行了训练。 用随机初始化训练模型通常需要时间和精力才能获得结果。 使用预训练的模型初始化模型可以加快收敛速度​​,并节省时间和能源。 这些经过预训练的模型通常使用精心选择的超参数进行训练。

可以直接使用预训练模型的几层,而无需进行任何修改,也可以对其进行位训练以适应变化。 在本节中,我们将学习如何对在ImageNet数据集上具有数百万个类别的模型进行调整或迁移学习。

瓶颈特征训练

上一节中介绍的模型很简单,因此准确率可能较低。 应该从它们构建复杂的模型。 它们不能从头开始构建。 因此,提取瓶颈特征并对它们进行分类器训练。 瓶颈特征是训练数百万张图像的复杂架构所产生的特征。 图像是通过前进完成的,并存储了最终层的特征。 从这些中,训练了一个简单的逻辑分类器进行分类。 提取瓶颈层,如下所示:

代码语言:javascript复制
generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255)

model = tf.keras.applications.VGG16(include_top=False)

train_images = generator.flow_from_directory(
    train_dir,
    batch_size=batch_size,
    target_size=(image_width, image_height),
    class_mode=None,
    shuffle=False
)
train_bottleneck_features = model.predict_generator(train_images, epoch_steps)

test_images = generator.flow_from_directory(
    test_dir,
    batch_size=batch_size,
    target_size=(image_width, image_height),
    class_mode=None,
    shuffle=False
)

test_bottleneck_features = model.predict_generator(test_images, test_steps)

将采用 VGG 模型并将其用于预测图像。 标签分配如下:

代码语言:javascript复制
train_labels = np.array([0] * int(no_train / 2)   [1] * int(no_train / 2))
test_labels = np.array([0] * int(no_test / 2)   [1] * int(no_test / 2))

使用瓶颈特征构建,编译和训练具有两层的顺序模型,并且可以使用以下给出的代码来实现:

代码语言:javascript复制
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=train_bottleneck_features.shape[1:]))
model.add(tf.keras.layers.Dense(1024, activation='relu'))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.Dense(1, activation='softmax'))
model.compile(loss=tf.keras.losses.categorical_crossentropy,
              optimizer=tf.keras.optimizers.Adam(),
              metrics=['accuracy'])

使用以下所示的代码对这些瓶颈特征进行模型训练:

代码语言:javascript复制
model.fit(
    train_bottleneck_features,
    train_labels,
    batch_size=batch_size,
    epochs=epochs,
    validation_data=(test_bottleneck_features, test_labels))

这提供了一种不同的方法来训练模型,并且在训练数据较少时很有用。 这通常是训练模型的更快方法。 仅使用预训练模型的最终激活来适应新任务。 这个想法可以扩展为微调几层,如下所示:

在深度学习中微调几层

可以加载预训练的模型,并且仅可以训练几层。 当给定的问题与模型所训练的图像非常不同时,此方法会更好地工作。 微调是深度学习中的常见做法。 当数据集较小时,这具有优势。 优化也可以更快地获得。

在小型数据集上训练深度网络会导致过拟合。 使用微调程序也可以避免这种过拟合。 在较大的数据集上训练的模型也应该相似,因为我们希望激活和特征与较小的数据集相似。 您可以从存储的权重路径开始,如下所示:

代码语言:javascript复制
top_model_weights_path = 'fc_model.h5'

加载视觉几何组VGG)模型,并将初始层设置为不可训练。 下一部分将详细介绍 VGG 模型。 目前,将 VGG 视为适用于图像数据的大型深度学习模型。 使用以下给出的代码,用新的可训练层替换全连接层:

代码语言:javascript复制
model = tf.keras.applications.VGG16(include_top=False)

可以在 VGG 模型的顶部构建一个小型的两层前馈网络,通常具有隐藏的单元,激活和退出,如下所示:

代码语言:javascript复制
model_fine_tune = tf.keras.models.Sequential()
model_fine_tune.add(tf.keras.layers.Flatten(input_shape=model.output_shape))
model_fine_tune.add(tf.keras.layers.Dense(256, activation='relu'))
model_fine_tune.add(tf.keras.layers.Dropout(0.5))
model_fine_tune.add(tf.keras.layers.Dense(no_classes, activation='softmax'))

顶级模型还必须装有经过充分训练的砝码。 然后可以将顶级模型添加到卷积基础中:

代码语言:javascript复制
model_fine_tune.load_weights(top_model_weights_path)
model.add(model_fine_tune)

我们可以将前 25 个层设置为不可训练,直到最后一个卷积块,这样它们的权重才会被更新。 仅其余层将被更新:

代码语言:javascript复制
for vgg_layer in model.layers[:25]:
    vgg_layer.trainable = False

使用梯度下降优化器以缓慢的学习率(4 量级)编译模型:

代码语言:javascript复制
model.compile(loss='binary_crossentropy',
  optimizer=tf.keras.optimizers.SGD(lr=1e-4, momentum=0.9),
  metrics=['accuracy'])

我们可以将之前介绍的增强技术与剪切,缩放和翻转结合使用。 可以从目录中将流与训练和验证数据集一起添加到生成器。 现在可以将模型与数据增强结合起来进行微调。 这种训练方式比以前的所有方法都具有更好的准确率。 以下是转学的指南:

数据大小

相似数据集

不同的数据集

较小的数据

微调输出层

微调更深层

更大的数据

微调整个模型

从头开始训练

根据数据大小,可以确定要微调的层数。 数据越少,需要调整的层数就越少。 我们已经看到了如何使用迁移学习技术来提高模型的准确率。

开发实际应用

识别猫和狗是一个很酷的问题,但不太可能是重要的问题。 产品中使用的图像分类的实际应用可能会有所不同。 您可能有不同的数据,目标等。 在本节中,您将学习解决这些不同设置的提示和技巧。 解决新问题时应考虑的因素如下:

  • 目标数量。 是 10 类问题还是 10,000 类问题?
  • 类内差异有多大? 例如,是否必须在一个类别标签下标识不同类型的猫?
  • 类间差异有多大? 例如,是否需要识别不同的猫?
  • 数据有多大?
  • 数据的平衡程度如何?
  • 是否已经有一个用很多图像训练的模型?
  • 部署推断时间和模型大小需要什么? 在 iPhone 上是 50 毫秒还是在 Google Cloud Platform 上是 10 毫秒? 可以消耗多少 RAM 来存储模型?

处理图像分类问题时,请尝试回答这些问题。 根据答案,您可以设计训练架构并提高准确率,如下一节所述。

选择合适的模型

架构有很多选择。 根据部署的灵活性,可以选择模型。 请记住,卷积较小且较慢,但是密集层较大且较快。 在大小,运行时间和准确率之间需要权衡。 建议在最终决定之前测试所有架构。 根据应用,某些模型可能比其他模型更好。 您可以减小输入大小以加快推理速度。 可以根据以下部分所述的指标来选择架构。

解决欠拟合和过拟合的方案

对于该问题,模型有时可能太大或太小。 可以将其分别分类为欠拟合或过拟合。 当模型太小时会发生拟合不足,而在训练精度较低时可以进行测量。 当模型太大并且训练和测试精度之间存在较大差距时,就会发生过拟合。 拟合不足可以通过以下方法解决:

  • 获取更多数据
  • 尝试更大的模型
  • 如果数据很小,请尝试使用迁移学习技术或进行数据扩充

过拟合可以通过以下方法解决:

  • 将丢弃法和批量规范化等技术用于正则化
  • 扩充数据集

时刻提防损失。 损耗应随着迭代次数的减少而减少。 如果损失没有减少,则表明训练已停止。 一种解决方案是尝试使用其他优化器。 类别失衡可以通过加权损失函数来解决。 始终使用 TensorBoard 观看摘要。 很难估计需要多少数据。 本部分是训练任何深度学习模型的最佳课程。 接下来,我们将介绍一些特定于应用的指南。

人脸性别和年龄检测

应用可能需要从人脸检测性别和年龄。 人脸图像可以是通过人脸检测器获取的 。 可以将经过裁剪的人脸图像作为训练数据提供,并且应该给出相似的经过裁剪的人脸以进行推断。 根据所需的推理时间,可以选择 OpenCV 或 CNN 人脸检测器。 对于训练,可以使用 Inception 或 ResNet。 如果由于是视频而所需的推理时间要少得多,则最好使用三个卷积,然后是两个全连接层。 请注意,年龄数据集通常存在巨大的类别失衡,因此使用不同的度量标准(如准确率和召回率)将有所帮助。

自定义模型的微调

自定义模型的微调是一个不错的选择。 在这里,具有多个对属性进行分类的 softmax 层将很有用。 这些属性可以是图案,颜色等。

品牌安全

使用支持向量机SVM)来训练瓶颈层是一个不错的选择,因为各个类别的图像可能会完全不同。 通常将其用于内容审核,以帮助避免显示露骨的图像。 您已经了解了如何解决图像分类中的新问题。

总结

我们已经介绍了用于训练分类任务的基本但有用的模型。 我们看到了使用 Keras 和 TensorFlow API 的 MNIST 数据集的简单模型。 我们还看到了如何利用 TensorBoard 观看训练过程。 然后,我们讨论了一些特定应用的最新架构。 还介绍了几种提高准确率的方法,例如数据增强,瓶颈层训练和微调预训练模型。 还介绍了为新模型训练模型的提示和技巧。

在下一章中,我们将看到如何可视化深度学习模型。 我们还将在本章中部署经过训练的模型以进行推断。 我们还将看到如何将训练有素的层用于通过应用进行图像搜索。 然后,我们将了解自编码器的概念并将其用于特征的维数。

三、图像检索

深度学习也可以称为表示学习,因为模型的特征或表示是在训练期间学习的。 在隐藏层的训练过程中生成的视觉特征可用于计算距离度量。 这些模型学习如何根据分类任务在各个层上检测边缘,图案等。 在本章中,我们将研究以下内容:

  • 如何从经过分类训练的模型中提取特征
  • 如何使用 TensorFlow Serving 在生产系统中进行更快的推断
  • 如何使用这些特征计算查询图像和目标集之间的相似度
  • 将分类模型用于排名
  • 如何提高检索系统的速度
  • 从整体上看系统的架构
  • 当目标图像过多时,使用自编码器学习紧凑的描述
  • 训练去噪自编码器

了解视觉特征

深度学习模型经常因无法解释而受到批评。 基于神经网络的模型通常被认为像黑匣子,因为人类很难推理出深度学习模型的工作原理。 由于激活函数,深度学习模型对图像进行的层转换是非线性的,因此不容易可视化。 已经开发出了通过可视化深层网络的层来解决对不可解释性的批评的方法。 在本节中,我们将研究可视化深层的尝试,以便了解模型的工作原理。

可视化可以使用模型的激活和梯度来完成。 可以使用以下技术可视化激活:

  • 最近邻:可以对图像进行层激活,并且可以一起看到该激活的最近图像。
  • 降维:激活的尺寸可以通过主成分分析PCA)或 T 分布随机邻居嵌入t-SNE),可在二维或三维中可视化。 PCA 通过将值投影到最大方差方向来减小尺寸。 t-SNE 通过将最接近的点映射到三个维度来减小维度。 降维的使用及其技术超出了本书的范围。 建议您参考基本的机器学习材料,以了解有关降维的更多信息。

维基百科是了解降维技术的良好来源。 您可以参考以下链接:

  • https://en.wikipedia.org/wiki/Dimensionality_reduction
  • https://en.wikipedia.org/wiki/Principal_component_analysis
  • https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding
  • https://en.wikipedia.org/wiki/Locality-sensitive_hashing
  • 最大补丁:激活一个神经元,并捕获最大激活的相应补丁。
  • 遮挡:在各个位置遮挡(遮挡)图像,并且激活以热图显示,以了解图像的哪些部分很重要。

在以下各节中,我们将看到如何实现这些特征的可视化。

可视化深度学习模型的激活

任何层的过滤器都可以可视化任何模型架构。 使用该技术只能理解初始层。 最后一层对于最近邻方法很有用。 当ImageNet数据集与最近邻排列在一起时,其外观如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-goSZJ7mv-1681567519367)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/f13aef48-ee95-48ba-a9d8-87149edc5ea7.jpg)]

查看此图像,您可以看到相同的对象一起出现。 有趣的事情之一是,诸如狗,猴子和猎豹之类的动物虽然没有经过一个标签的训练却同时出现。 当对象相似时,图像的最近邻可视化非常有用,因此,我们可以了解模型的预测。 最后一层也可以通过降维技术(例如主成分分析和 t-SNE)进行可视化。 在下一节中,我们将看到使用降维的可视化实现。

嵌入可视化

可以使用 TensorBoard 以二维或三维可视化嵌入层(即预最终层)。 假定本节中的代码段位于图像分类一章中训练的卷积神经网络模型之后。 首先,我们需要一个元数据文件,它是一个制表符分隔的文件。 元数据文件的每一行都应具有将要可视化的图像标签。 需要一个新变量来存储在会话创建和初始化之间定义的嵌入,如以下代码所示:

代码语言:javascript复制
no_embedding_data = 1000 embedding_variable = tf.Variable(tf.stack(
    mnist.test.images[:no_embedding_data], axis=0), trainable=False)

我们将获取 MNIST 测试数据,并创建用于可视化的元数据文件,如下所示:

代码语言:javascript复制
metadata_path = '/tmp/train/metadata.tsv'   with open(metadata_path, 'w') as metadata_file:
 for i in range(no_embedding_data):
 metadata_file.write('{}n'.format(
 np.nonzero(mnist.test.labels[::1])[1:][0][i]))

如上代码所示,应通过设置参数使嵌入变量不可训练。 接下来,必须定义投影仪配置。 它必须具有tensor_name,它是嵌入变量名称,元数据文件的路径和子画面图像。 子画面图像是一个带有小图像的图像,表示要通过嵌入可视化的标签。 以下是用于定义嵌入投影的代码:

代码语言:javascript复制
from tensorflow.contrib.tensorboard.plugins import projector
projector_config = projector.ProjectorConfig()
embedding_projection = projector_config.embeddings.add()
embedding_projection.tensor_name = embedding_variable.name
embedding_projection.metadata_path = metadata_path
embedding_projection.sprite.image_path = os.path.join(work_dir   '/mnist_10k_sprite.png')
embedding_projection.sprite.single_image_dim.extend([28, 28])

必须指定子画面图像尺寸。 然后,可以使用投影机通过摘要编写器和配置来可视化嵌入,如以下代码所示:

代码语言:javascript复制
projector.visualize_embeddings(train_summary_writer, projector_config)
tf.train.Saver().save(session, '/tmp/train/model.ckpt', global_step=1)

然后,将模型与会话一起保存。 然后转到 TensorBoard 查看以下可视化效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tozBkczK-1681567519368)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/12a15ca8-cf56-4557-8f9c-6ee86903ed4f.png)]

TensorBoard 说明了代码的输出

您必须通过按钮选择 T-SNE 和颜色,如屏幕截图所示,以获得类似的可视化效果。 您可以看到数字如何一起出现。 该可视化对于检查数据和经过训练的嵌入非常有用。 这是 TensorBoard 的另一个强大功能。 在下一部分中,我们将实现可视化的引导反向传播。

引导反向传播

直接将特征可视化可能会减少信息量。 因此,我们使用反向传播的训练过程来激活滤镜以实现更好的可视化。 由于我们选择了要激活的神经元以进行反向传播,因此称为引导反向传播。 在本节中,我们将实现引导式反向传播以可视化特征。

我们将定义大小并加载 VGG 模型,如下所示:

代码语言:javascript复制
image_width, image_height = 128, 128 vgg_model = tf.keras.applications.vgg16.VGG16(include_top=False)

层由以层名称作为键的字典组成,模型中的层以权重作为键值,以方便访问。 现在,我们将从第五个块 block5_conv1 中获取第一卷积层,以计算可视化效果。 输入和输出在此处定义:

代码语言:javascript复制
input_image = vgg_model.input
vgg_layer_dict = dict([(vgg_layer.name, vgg_layer) for vgg_layer in vgg_model.layers[1:]])
vgg_layer_output = vgg_layer_dict['block5_conv1'].output

我们必须定义损失函数。 损失函数将最大化特定层的激活。 这是一个梯度上升过程,而不是通常的梯度下降过程,因为我们正在尝试使损失函数最大化。 对于梯度上升,平滑梯度很重要。 因此,在这种情况下,我们通过归一化像素梯度来平滑梯度。 该损失函数快速收敛而不是。

应该对图像的输出进行归一化以可视化,在优化过程中使用 g 辐射上升来获得函数的最大值。 现在,我们可以通过定义评估器和梯度来开始梯度上升优化,如下所示。 现在,必须定义损失函数,并要计算的梯度。 迭代器通过迭代计算损耗和梯度值,如下所示:

代码语言:javascript复制
filters = []
for filter_idx in range(20):
    loss = tf.keras.backend.mean(vgg_layer_output[:, :, :, filter_idx])
    gradients = tf.keras.backend.gradients(loss, input_image)[0]
    gradient_mean_square = tf.keras.backend.mean(tf.keras.backend.square(gradients))
    gradients /= (tf.keras.backend.sqrt(gradient_mean_square)   1e-5)
    evaluator = tf.keras.backend.function([input_image], [loss, gradients])

输入是随机的灰度图像,并添加了一些噪声。 如此处所示,将生成随机图像并完成缩放。

代码语言:javascript复制
 gradient_ascent_step = 1.
    input_image_data = np.random.random((1, image_width, image_height, 3))
    input_image_data = (input_image_data - 0.5) * 20   128  

现在开始对损失函数进行优化,对于某些过滤器,损失值可能为 0,应将其忽略,如下所示:

代码语言:javascript复制
 for i in range(20):
        loss_value, gradient_values = evaluator([input_image_data])
        input_image_data  = gradient_values * gradient_ascent_step
        # print('Loss :', loss_value)
  if loss_value <= 0.:
            break

优化之后,通过均值减去并调整标准差来完成归一化。 然后,可以按比例缩小滤镜并将其裁剪到其梯度值,如下所示:

代码语言:javascript复制
 if loss_value > 0:
        filter = input_image_data[0]
        filter -= filter.mean()
        filter /= (filter.std()   1e-5)
        filter *= 0.1
  filter  = 0.5
  filter = np.clip(filter, 0, 1)
        filter *= 255
  filter = np.clip(filter, 0, 255).astype('uint8')
        filters.append((filter, loss_value))

这些过滤器是随机选择的,并在此处可视化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r6RVCSWQ-1681567519368)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/f741f405-d2e9-4598-822b-d269d004e882.png)]

如图所示,用于缝合图像并产生输出的代码与代码束一起提供。 由于修道院的接受区域变大,因此可视化在以后的层变得复杂。 一些滤镜看起来很相似,但只是旋转而已。 在这种情况下,可视化的层次结构可以清楚地看到,如 Zeiler 等人所示。 下图显示了不同层的直接可视化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0ljuHqt-1681567519368)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/810e2adf-2cf5-44d2-b729-91dfd56cadb1.png)]

经 Zeiler 等人许可复制。

前两层看起来像边缘和角落检测器。 类似于 Gabor 的滤镜仅出现在第三层中。 Gabor 过滤器是线性的,传统上用于纹理分析。 我们已经直接通过引导反向传播看到了特征的可视化。 接下来,我们将看到如何实现 DeepDream 进行可视化。

DeepDream

可以在网络中的某些层上放大神经元激活,而不是合成图像。 放大原始图像以查看特征效果的概念称为 DeepDream。 创建 DeepDream 的步骤是:

  1. 拍摄图像并从 CNN 中选择一个层。
  2. 在特定的层进行激活。
  3. 修改梯度,以使梯度和激活相等。
  4. 计算图像和反向传播的梯度。
  5. 必须将正则化用于图像的抖动和归一化。
  6. 像素值应修剪。
  7. 为了实现分形效果,对图像进行了多尺度处理。

让我们从导入相关的包开始:

代码语言:javascript复制
import os
import numpy as np
import PIL.Image
import urllib.request
from tensorflow.python.platform import gfile
import zipfile

初始模型在Imagenet数据集和 Google 提供的模型文件上进行了预训练。 我们可以下载该模型并将其用于本示例。 模型文件的 ZIP 归档文件已下载并解压缩到一个文件夹中,如下所示:

代码语言:javascript复制
model_url = 'https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip'   file_name = model_url.split('/')[-1]

file_path = os.path.join(work_dir, file_name)

if not os.path.exists(file_path):
    file_path, _ = urllib.request.urlretrieve(model_url, file_path)

zip_handle = zipfile.ZipFile(file_path, 'r')
zip_handle.extractall(work_dir)
zip_handle.close()

这些命令应该在工作目录中创建了三个新文件。 可以将此预训练的模型加载到会话中,如下所示:

代码语言:javascript复制
graph = tf.Graph()
session = tf.InteractiveSession(graph=graph)
model_path = os.path.join(work_dir, 'tensorflow_inception_graph.pb')
with gfile.FastGFile(model_path, 'rb') as f:
    graph_defnition = tf.GraphDef()
    graph_defnition.ParseFromString(f.read())

会话从图初始化开始。 然后,将下载的模型的图定义加载到内存中。 作为预处理步骤,必须从输入中减去ImageNet平均值,如下所示。 预处理后的图像随后被馈送到该图,如下所示:

代码语言:javascript复制
input_placeholder = tf.placeholder(np.float32, name='input')
imagenet_mean_value = 117.0 preprocessed_input = tf.expand_dims(input_placeholder-imagenet_mean_value, 0)
tf.import_graph_def(graph_defnition, {'input': preprocessed_input})

现在,会话和图已准备好进行推断。 双线性插值需要resize_image函数。 可以添加resize函数方法,该函数通过 TensorFlow 会话来调整图像的大小,如下所示:

代码语言:javascript复制
def resize_image(image, size):
    resize_placeholder = tf.placeholder(tf.float32)
    resize_placeholder_expanded = tf.expand_dims(resize_placeholder, 0)
    resized_image = tf.image.resize_bilinear(resize_placeholder_expanded, size)[0, :, :, :]
    return session.run(resized_image, feed_dict={resize_placeholder: image})

可以将工作目录中的图像加载到内存中并转换为浮点值,如下所示:

代码语言:javascript复制
image_name = 'mountain.jpg' image = PIL.Image.open(image_name)
image = np.float32(image)

此处显示了已加载的图像,供您参考:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KbKLjTPw-1681567519368)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/a6cf4c5d-de28-4bec-995d-07b57bfbe560.jpg)]

音阶空间的八度音阶数,大小和音阶在此处定义:

代码语言:javascript复制
no_octave = 4 scale = 1.4 window_size = 51

这些值在此处显示的示例中效果很好,因此需要根据其大小调整其他图像。 可以选择一个层来做梦,该层的平均平均值将是objective函数,如下所示:

代码语言:javascript复制
score = tf.reduce_mean(objective_fn)
gradients = tf.gradients(score, input_placeholder)[0]

计算图像的梯度以进行优化。 可以通过将图像调整为各种比例并找到差异来计算八度图像,如下所示:

代码语言:javascript复制
octave_images = []
for i in range(no_octave - 1):
    image_height_width = image.shape[:2]
    scaled_image = resize_image(image, np.int32(np.float32(image_height_width) / scale))
    image_difference = image - resize_image(scaled_image, image_height_width)
    image = scaled_image
    octave_images.append(image_difference)

现在可以使用所有八度图像运行优化。 窗口在图像上滑动,计算梯度激活以创建梦,如下所示:

代码语言:javascript复制
for octave_idx in range(no_octave):
    if octave_idx > 0:
        image_difference = octave_images[-octave_idx]
        image = resize_image(image, image_difference.shape[:2])   image_difference

    for i in range(10):
        image_heigth, image_width = image.shape[:2]
        sx, sy = np.random.randint(window_size, size=2)
        shifted_image = np.roll(np.roll(image, sx, 1), sy, 0)
        gradient_values = np.zeros_like(image)

        for y in range(0, max(image_heigth - window_size // 2, window_size), window_size):
            for x in range(0, max(image_width - window_size // 2, window_size), window_size):
                sub = shifted_image[y:y   window_size, x:x   window_size]
                gradient_windows = session.run(gradients, {input_placeholder: sub})
                gradient_values[y:y   window_size, x:x   window_size] = gradient_windows

        gradient_windows = np.roll(np.roll(gradient_values, -sx, 1), -sy, 0)
        image  = gradient_windows * (1.5 / (np.abs(gradient_windows).mean()   1e-7))

现在,创建 DeepDream 的优化已完成,可以通过剪切值来保存,如下所示:

代码语言:javascript复制
image /= 255.0 image = np.uint8(np.clip(image, 0, 1) * 255)
PIL.Image.fromarray(image).save('dream_'   image_name, 'jpeg')

在本节中,我们已经看到了创建 DeepDream 的过程。 结果显示在这里:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oajlSeNr-1681567519369)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1c9b8a0a-3621-4644-97e5-1c0158a051dd.jpg)]

如我们所见,狗到处都被激活。 您可以尝试其他各种层并查看结果。 这些结果可用于艺术目的。 类似地,可以激活其他层以产生不同的伪像。 在下一节中,我们将看到一些对抗性示例,这些示例可能会欺骗深度学习模型。

对抗性示例

在几个数据集上,图像分类算法已达到人类水平的准确率。 但是它们可以被对抗性例子轻易地欺骗。 对抗示例是合成图像,它们使模型无法产生所需的结果。 拍摄任何图像,然后选择不正确的随机目标类别。 可以用噪声修改该图像,直到网络被 Goodfellow 等人所欺骗。 该模型的对抗攻击示例如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46OUGKMg-1681567519369)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/5fc3a7dd-4e49-4215-a821-e026811ca3fd.png)]

经 Goodfellow 等人许可复制。

在此图中,左侧显示的图像具有特定标签的 58% 可信度。 左边的图像与中间显示的噪声结合在一起时,在右边形成图像。 对于人来说,带有噪点的图像看起来还是一样。 但是带有噪点的图像可以通过具有 97% 置信度的其他标签来预测。 尽管图像具有非常不同的对象,但仍将高置信度分配给特定示例。 这是深度学习模型的问题,因此,您应该了解这在哪里适用:

  • 甚至可以在不访问模型的情况下生成对抗性示例。 您可以训练自己的模型,生成对抗性示例,但仍然可以欺骗其他模型。
  • 在实践中这种情况很少发生,但是当有人试图欺骗系统来发送垃圾邮件或崩溃时,这将成为一个真正的问题。
  • 所有机器学习模型都容易受到此问题的影响,而不仅仅是深度学习模型。

您应该考虑对抗性示例,了解在安全关键系统上部署深度学习模型的后果。 在下一节中,我们将看到如何利用 TensorFlow Serving 获得更快的推断。

模型推理

任何新数据都可以传递给模型以获取结果。 从图像获取分类结果或特征的过程称为推理。 训练和推理通常在不同的计算机上和不同的时间进行。 我们将学习如何存储模型,运行推理以及如何使用 TensorFlow Serving 作为具有良好延迟和吞吐量的服务器。

导出模型

训练后的模型必须导出并保存。 权重,偏差和图都存储用于推断。 我们将训练 MNIST 模型并将其存储。 首先使用以下代码定义所需的常量:

代码语言:javascript复制
work_dir = '/tmp' model_version = 9 training_iteration = 1000 input_size = 784 no_classes = 10 batch_size = 100 total_batches = 200

model_version可以是一个整数,用于指定我们要导出以供服务的模型。 feature config存储为具有占位符名称及其对应数据类型的字典。 应该映射预测类及其标签。 身份占位符可与 API 配合使用:

代码语言:javascript复制
tf_example = tf.parse_example(tf.placeholder(tf.string, name='tf_example'),
  {'x': tf.FixedLenFeature(shape=[784], dtype=tf.float32), })
x_input = tf.identity(tf_example['x'], name='x')

可以使用以下代码使用权重,偏差,对数和优化器定义一个简单的分类器:

代码语言:javascript复制
y_input = tf.placeholder(tf.float32, shape=[None, no_classes])
weights = tf.Variable(tf.random_normal([input_size, no_classes]))
bias = tf.Variable(tf.random_normal([no_classes]))
logits = tf.matmul(x_input, weights)   bias
softmax_cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_input, logits=logits)
loss_operation = tf.reduce_mean(softmax_cross_entropy)
optimiser = tf.train.GradientDescentOptimizer(0.5).minimize(loss_operation)

训练模型,如以下代码所示:

代码语言:javascript复制
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
for batch_no in range(total_batches):
    mnist_batch = mnist.train.next_batch(batch_size)
    _, loss_value = session.run([optimiser, loss_operation], feed_dict={
        x_input: mnist_batch[0],
  y_input: mnist_batch[1]
    })
    print(loss_value)

定义预测签名,并导出模型。 将模型保存到持久性存储中,以便可以在以后的时间点进行推理。 这将通过反序列化导出数据,并将其存储为其他系统可以理解的格式。 具有不同变量和占位符的多个图可用于导出。 它还支持signature_defs 和素材。 signature_defs指定了输入和输出,因为将从外部客户端访问输入和输出。 素材是将用于推理的非图组件,例如词汇表等。

分类签名使用对 TensorFlow 分类 API 的访问权限。 输入是强制性的,并且有两个可选输出(预测类别和预测概率),其中至少一个是强制性的。 预测签名提供输入和输出数量的灵活性。 可以定义多个输出并从客户端显式查询。 signature_def显示在此处:

代码语言:javascript复制
signature_def = (
      tf.saved_model.signature_def_utils.build_signature_def(
          inputs={'x': tf.saved_model.utils.build_tensor_info(x_input)},
  outputs={'y': tf.saved_model.utils.build_tensor_info(y_input)},
  method_name="tensorflow/serving/predict"))

最后,使用预测签名将元图和变量添加到构建器中:

代码语言:javascript复制
model_path = os.path.join(work_dir, str(model_version))
saved_model_builder = tf.saved_model.builder.SavedModelBuilder(model_path)
saved_model_builder.add_meta_graph_and_variables(
      session, [tf.saved_model.tag_constants.SERVING],
  signature_def_map={
          'prediction': signature_def
      },
  legacy_init_op=tf.group(tf.tables_initializer(), name='legacy_init_op'))
saved_model_builder.save()

该构建器已保存,可以由服务器使用。 所示示例适用于任何模型,并可用于导出。 在下一部分中,我们将服务并查询导出的模型。

服务训练过的模型

可以使用以下命令通过 TensorFlow Serving 服务上一节中导出的模型:

代码语言:javascript复制
tensorflow_model_server --port=9000  --model_name=mnist --model_base_path=/tmp/mnist_model/

model_base_path 指向导出模型的目录。 现在可以与客户端一起测试服务器。 请注意,这不是 HTTP 服务器,因此需要此处显示的客户端而不是 HTTP 客户端。 导入所需的库:

代码语言:javascript复制
from grpc.beta import implementations
import numpy
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2

添加并发常数,测试数量和工作目录。 定义了一个类,用于对返回的结果进行计数。 定义了远程过程调用RPC)回调,并带有用于对预测计数的计数器,如下所示:

代码语言:javascript复制
concurrency = 1 num_tests = 100 host = '' port = 8000 work_dir = '/tmp'     def _create_rpc_callback():
  def _callback(result):
      response = numpy.array(
        result.result().outputs['y'].float_val)
      prediction = numpy.argmax(response)
      print(prediction)
  return _callback

根据您的要求修改 hostport_callback方法定义了从服务器返回响应时所需的步骤。 在这种情况下,将计算最大概率。 通过调用服务器来运行推断:

代码语言:javascript复制
test_data_set = mnist.test
test_image = mnist.test.images[0]

predict_request = predict_pb2.PredictRequest()
predict_request.model_spec.name = 'mnist' predict_request.model_spec.signature_name = 'prediction'   predict_channel = implementations.insecure_channel(host, int(port))
predict_stub = prediction_service_pb2.beta_create_PredictionService_stub(predict_channel)

predict_request.inputs['x'].CopyFrom(
    tf.contrib.util.make_tensor_proto(test_image, shape=[1, test_image.size]))
result = predict_stub.Predict.future(predict_request, 3.0)
result.add_done_callback(
    _create_rpc_callback())

反复调用推理以评估准确率,延迟和吞吐量。 推断错误率应该在 90% 左右,并且并发性应该很高。 导出和客户端方法可用于任何模型,以从模型获得结果和特征。 在下一节中,我们将构建检索流水线。

基于内容的图像检索

基于内容的图像检索CBIR)的技术将查询图像作为输入,并对目标图像数据库中的图像进行排名,从而产生输出。 CBIR 是具有特定目标的图像到图像搜索引擎。 要检索需要目标图像数据库。 返回距查询图像最小距离的目标图像。 我们可以直接将图像用于相似性,但是问题如下:

  • 图像尺寸巨大
  • 像素中有很多冗余
  • 像素不携带语义信息

因此,我们训练了一个用于对象分类的模型,并使用该模型中的特征进行检索。 然后,我们通过相同的模型传递查询图像和目标数据库以获得特征。 这些模型也可以称为编码器,因为它们对特定任务的图像信息进行编码。 编码器应该能够捕获全局和局部特征。 我们可以使用我们在图像分类一章中研究过的模型,这些模型经过训练可以进行分类任务。 由于强力扫描或线性扫描速度较慢,因此图像搜索可能会花费大量时间。 因此,需要一些用于更快检索的方法。 以下是一些加快匹配速度的方法:

  • 局部敏感哈希LSH):LSH 将特征投影到其子空间,并可以向候选对象提供列表,并在以后进行精细特征排名。 这也是我们本章前面介绍的降维技术,例如 PCA 和 t-SNE。 它具有较小尺寸的铲斗。
  • 多索引哈希:此方法对特征进行哈希处理,就像信鸽拟合一样,可以使其更快。 它使用汉明距离来加快计算速度。 汉明距离不过是以二进制表示的数字的位置差异的数量。

这些方法更快,需要更少的内存,但要权衡准确率。 这些方法也没有捕获语义上的差异。 可以根据查询对匹配结果进行重新排名以获得更好的结果。 重新排序可以通过对返回的目标图像重新排序来改善结果。 重新排序可以使用以下技术之一:

  • 几何验证:此方法将几何图形和目标图像与仅返回相似几何图形的目标图像进行匹配。
  • 查询扩展:这将扩展目标图像列表并详尽搜索它们。
  • 相关性反馈:此方法从使用中获取反馈并返回结果。 根据用户输入,将进行重新排名。

这些技术已针对文本进行了很好的开发,可用于图像。 在本章中,我们将重点介绍提取特征并将其用于 CBIR。 在下一节中,我们将学习如何进行模型推断。

建立检索流水线

从查询图像的目标图像中获得最佳匹配的步骤序列称为检索流水线。 检索流水线具有多个步骤或组件。 图像数据库的特征必须脱机提取并存储在数据库中。 对于每个查询图像,必须提取特征并且必须在所有目标图像之间计算相似度。 然后,可以对图像进行排名以最终输出。 检索流水线如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xvtT4VFM-1681567519369)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/c9ea2c8e-b149-4890-8a5e-ba4d03ade21e.png)]

特征提取步骤必须快速,为此可以使用 TensorFlow Serving。 您可以根据应用选择使用哪些特征。 例如,当需要基于纹理的匹配时可以使用初始层,而当必须在对象级别进行匹配时可以使用更高的层。 在下一部分中,我们将看到如何从预训练的初始模型中提取特征。

提取图像的瓶颈特征

瓶颈特征是在预分类层中计算的值。 在本节中,我们将看到如何使用 TensorFlow 从预训练的模型中提取瓶颈特征。 首先,使用以下代码导入所需的库:

代码语言:javascript复制
import os
import urllib.request
from tensorflow.python.platform import gfile
import tarfile

然后,我们需要下载带有图定义及其权重的预训练模型。 TensorFlow 已使用初始架构在ImageNet数据集上训练了一个模型,并提供了该模型。 我们将使用以下代码下载该模型并将其解压缩到本地文件夹中:

代码语言:javascript复制
model_url = 'http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz' file_name = model_url.split('/')[-1]
file_path = os.path.join(work_dir, file_name)

if not os.path.exists(file_path):
    file_path, _ = urllib.request.urlretrieve(model_url, file_path)
tarfile.open(file_path, 'r:gz').extractall(work_dir)

仅当模型不存在时,这会创建一个文件夹并下载模型。 如果重复执行代码,则不会每次都下载模型。 该图以协议缓冲区protobuf)格式存储在文件中。 必须将其读取为字符串,然后传递给tf.GraphDef()对象以将其带入内存:

代码语言:javascript复制
model_path = os.path.join(work_dir, 'classify_image_graph_def.pb')
with gfile.FastGFile(model_path, 'rb') as f:
    graph_defnition = tf.GraphDef()
    graph_defnition.ParseFromString(f.read())

在初始模型中,瓶颈层名为pool_3/_reshape:0,并且该层的尺寸为 2,048。 输入的占位符名称为DecodeJpeg/contents:0,调整大小张量名称为ResizeBilinear:0。 我们可以使用tf.import_graph_def和所需的返回张量导入图定义,以进行进一步的操作:

代码语言:javascript复制
bottleneck, image, resized_input = (
    tf.import_graph_def(
        graph_defnition,
  name='',
  return_elements=['pool_3/_reshape:0',
  'DecodeJpeg/contents:0',
  'ResizeBilinear:0'])
)

进行查询和目标图像并将其加载到内存中。 gfile函数提供了一种更快的方式将图像加载到内存中。

代码语言:javascript复制
query_image_path = os.path.join(work_dir, 'cat.1000.jpg')
query_image = gfile.FastGFile(query_image_path, 'rb').read()
target_image_path = os.path.join(work_dir, 'cat.1001.jpg')
target_image = gfile.FastGFile(target_image_path, 'rb').read()

让我们定义一个使用session和图像从图像中提取瓶颈特征的函数:

代码语言:javascript复制
def get_bottleneck_data(session, image_data):
    bottleneck_data = session.run(bottleneck, {image: image_data})
    bottleneck_data = np.squeeze(bottleneck_data)
    return bottleneck_data

启动会话,并传递图像以运行前向推理,以从预先训练的模型中获取瓶颈值:

代码语言:javascript复制
query_feature = get_bottleneck_data(session, query_image)
print(query_feature)
target_feature = get_bottleneck_data(session, target_image)
print(target_feature)

运行上面的代码应显示如下:

代码语言:javascript复制
[ 0.55705792 0.36785451 1.06618118 ..., 0.6011821 0.36407694
 0.0996572 ]
[ 0.30421323 0.0926369 0.26213276 ..., 0.72273785 0.30847171
 0.08719242]

该计算特征的过程可以按比例缩放以获取更多目标图像。 使用这些值,可以在查询图像和目标数据库之间计算相似度,如以下部分所述。

计算查询图像与目标数据库之间的相似度

NumPy 的linalg.norm可用于计算欧几里德距离。 可以通过计算特征之间的欧几里得距离来计算查询图像与目标数据库之间的相似度,如下所示:

代码语言:javascript复制
dist = np.linalg.norm(np.asarray(query_feature) - np.asarray(target_feature))
print(dist)

运行此命令应打印以下内容:

代码语言:javascript复制
16.9965

这是可用于相似度计算的度量。 查询与目标图像之间的欧几里得距离越小,图像越相似。 因此,计算欧几里得距离是相似度的量度。 使用特征来计算欧几里得距离是基于这样的假设:在训练模型的过程中学习了这些特征。 将这种计算扩展成数百万个图像效率不高。 在生产系统中,期望以毫秒为单位返回结果。 在下一节中,我们将看到如何提高检索效率。

高效检索

检索可能很慢,因为它是蛮力方法。 使用近似最近邻可以使匹配更快。 维度的诅咒也开始出现,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NHXd7Ou5-1681567519369)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/d3c91098-f5f8-42ed-9fac-3ee18ba2baee.png)]

随着维数的增加,复杂度也从二维维增加到三个维。 距离的计算也变慢。 为了使距离搜索更快,我们将在下一部分中讨论一种近似方法。

使用近似最近邻更快地匹配

近似最近邻ANNOY)是一种用于更快进行最近邻搜索的方法。 ANNOY 通过随机投影来构建树。 树结构使查找最接近的匹配更加容易。 您可以创建ANNOYIndex以便快速检索,如下所示:

代码语言:javascript复制
def create_annoy(target_features):
    t = AnnoyIndex(layer_dimension)
    for idx, target_feature in enumerate(target_features):
        t.add_item(idx, target_feature)
    t.build(10)
    t.save(os.path.join(work_dir, 'annoy.ann'))

create_annoy(target_features)

创建索引需要特征的尺寸。 然后将项目添加到索引并构建树。 树木的数量越多,在时间和空间复杂度之间进行权衡的结果将越准确。 可以创建索引并将其加载到内存中。 可以查询 ANNOY,如下所示:

代码语言:javascript复制
annoy_index = AnnoyIndex(10)
annoy_index.load(os.path.join(work_dir, 'annoy.ann'))
matches = annoy_index.get_nns_by_vector(query_feature, 20)

匹配项列表可用于检索图像详细信息。 项目的索引将被返回。

请访问这里以获取ANNOY的完整实现,以及其在准确率和速度方面与其他近似最近邻算法的基准比较。

ANNOY 的优点

使用 ANNOY 的原因很多。 主要优点如下:

  • 具有内存映射的数据结构,因此对 RAM 的占用较少。 因此,可以在多个进程之间共享同一文件。
  • 可以使用曼哈顿,余弦或欧几里得等多种距离来计算查询图像和目标数据库之间的相似度。

原始图像的自编码器

自编码器是一种用于生成有效编码的无监督算法。 输入层和目标输出通常相同。 减少和增加之间的层以下列方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4HS6l2e-1681567519370)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/2cc688ac-9e9f-447e-b46e-295eea60e0e9.png)]

瓶颈层是尺寸减小的中间层。 瓶颈层的左侧称为编码器,右侧称为解码器。 编码器通常减小数据的尺寸,而解码器增大尺寸。 编码器和解码器的这种组合称为自编码器。 整个网络都经过重建误差训练。 从理论上讲,可以存储瓶颈层,并可以通过解码器网络重建原始数据。 如下所示,这可以减小尺寸并易于编程。 使用以下代码定义卷积,解卷积和全连接层:

代码语言:javascript复制
def fully_connected_layer(input_layer, units):
    return tf.layers.dense(
        input_layer,
  units=units,
  activation=tf.nn.relu
    )

def convolution_layer(input_layer, filter_size):
    return tf.layers.conv2d(
        input_layer,
  filters=filter_size,
  kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
  kernel_size=3,
  strides=2
  )

def deconvolution_layer(input_layer, filter_size, activation=tf.nn.relu):
    return tf.layers.conv2d_transpose(
        input_layer,
  filters=filter_size,
  kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
  kernel_size=3,
  activation=activation,
  strides=2
  )

定义具有五层卷积的会聚编码器,如以下代码所示:

代码语言:javascript复制
input_layer = tf.placeholder(tf.float32, [None, 128, 128, 3])
convolution_layer_1 = convolution_layer(input_layer, 1024)
convolution_layer_2 = convolution_layer(convolution_layer_1, 512)
convolution_layer_3 = convolution_layer(convolution_layer_2, 256)
convolution_layer_4 = convolution_layer(convolution_layer_3, 128)
convolution_layer_5 = convolution_layer(convolution_layer_4, 32)

通过展平第五个卷积层来计算瓶颈层。 再次将瓶颈层重新成形为卷积层,如下所示:

代码语言:javascript复制
convolution_layer_5_flattened = tf.layers.flatten(convolution_layer_5)
bottleneck_layer = fully_connected_layer(convolution_layer_5_flattened, 16)
c5_shape = convolution_layer_5.get_shape().as_list()
c5f_flat_shape = convolution_layer_5_flattened.get_shape().as_list()[1]
fully_connected = fully_connected_layer(bottleneck_layer, c5f_flat_shape)
fully_connected = tf.reshape(fully_connected,
  [-1, c5_shape[1], c5_shape[2], c5_shape[3]])

计算可以重建图像的发散或解码器部分,如以下代码所示:

代码语言:javascript复制
deconvolution_layer_1 = deconvolution_layer(fully_connected, 128)
deconvolution_layer_2 = deconvolution_layer(deconvolution_layer_1, 256)
deconvolution_layer_3 = deconvolution_layer(deconvolution_layer_2, 512)
deconvolution_layer_4 = deconvolution_layer(deconvolution_layer_3, 1024)
deconvolution_layer_5 = deconvolution_layer(deconvolution_layer_4, 3,
  activation=tf.nn.tanh)

该网络经过训练,可以快速收敛。 传递图像特征时可以存储瓶颈层。 这有助于减少可用于检索的数据库的大小。 仅需要编码器部分即可为特征建立索引。 自编码器是一种有损压缩算法。 它与其他压缩算法不同,因为它从数据中学习压缩模式。 因此,自编码器模型特定于数据。 自编码器可以与 t-SNE 结合使用以获得更好的可视化效果。 自编码器学习的瓶颈层可能对其他任务没有用。 瓶颈层的大小可以大于以前的层。 在这种分叉和收敛连接的情况下,稀疏的自编码器就会出现。 在下一节中,我们将学习自编码器的另一种应用。

使用自编码器进行降噪

自编码器也可以用于图像去噪。 去噪是从图像中去除噪点的过程。 去噪编码器可以无监督的方式进行训练。 可以在正常图像中引入噪声,并针对原始图像训练自编码器。 以后,可以使用完整的自编码器生成无噪声的图像。 在本节中,我们将逐步说明如何去噪 MNIST 图像。 导入所需的库并定义占位符,如下所示:

代码语言:javascript复制
x_input = tf.placeholder(tf.float32, shape=[None, input_size])
y_input = tf.placeholder(tf.float32, shape=[None, input_size])

x_inputy_input的形状与自编码器中的形状相同。 然后,定义一个密集层,如下所示,默认激活为tanh激活函数。 add_variable_summary方法是从图像分类章节示例中导入的。 密集层的定义如下所示:

代码语言:javascript复制
def dense_layer(input_layer, units, activation=tf.nn.tanh):
    layer = tf.layers.dense(
        inputs=input_layer,
  units=units,
  activation=activation
    )
    add_variable_summary(layer, 'dense')
    return layer

接下来,可以定义自编码器层。 该自编码器仅具有全连接层。 编码器部分具有减小尺寸的三层。 解码器部分具有增加尺寸的三层。 编码器和解码器都是对称的,如下所示:

代码语言:javascript复制
layer_1 = dense_layer(x_input, 500)
layer_2 = dense_layer(layer_1, 250)
layer_3 = dense_layer(layer_2, 50)
layer_4 = dense_layer(layer_3, 250)
layer_5 = dense_layer(layer_4, 500)
layer_6 = dense_layer(layer_5, 784)

隐藏层的尺寸是任意选择的。 接下来,定义lossoptimiser。 这里我们使用 Sigmoid 代替 softmax 作为分类,如下所示:

代码语言:javascript复制
with tf.name_scope('loss'):
    softmax_cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(
        labels=y_input, logits=layer_6)
    loss_operation = tf.reduce_mean(softmax_cross_entropy, name='loss')
    tf.summary.scalar('loss', loss_operation)

with tf.name_scope('optimiser'):
    optimiser = tf.train.AdamOptimizer().minimize(loss_operation)

TensorBoard 提供了另一种称为image,的摘要,可用于可视化图像。 我们将使用输入layer_6并将其重塑形状以将其添加到摘要中,如下所示:

代码语言:javascript复制
x_input_reshaped = tf.reshape(x_input, [-1, 28, 28, 1])
tf.summary.image("noisy_images", x_input_reshaped)

y_input_reshaped = tf.reshape(y_input, [-1, 28, 28, 1])
tf.summary.image("original_images", y_input_reshaped)

layer_6_reshaped = tf.reshape(layer_6, [-1, 28, 28, 1])
tf.summary.image("reconstructed_images", layer_6_reshaped)

图像数量默认限制为三张,并且可以更改。 这是为了限制其将所有图像都写入摘要文件夹。 接下来,合并所有摘要,并将图添加到摘要编写器,如下所示:

代码语言:javascript复制
merged_summary_operation = tf.summary.merge_all()
train_summary_writer = tf.summary.FileWriter('/tmp/train', session.graph)

可以将正常的随机噪声添加到图像中并作为输入张量馈入。 添加噪声后,多余的值将被裁剪。 目标将是原始图像本身。 此处显示了噪声和训练过程的附加信息:

代码语言:javascript复制
for batch_no in range(total_batches):
    mnist_batch = mnist_data.train.next_batch(batch_size)
    train_images, _ = mnist_batch[0], mnist_batch[1]
    train_images_noise = train_images   0.2 * np.random.normal(size=train_images.shape)
    train_images_noise = np.clip(train_images_noise, 0., 1.)
    _, merged_summary = session.run([optimiser, merged_summary_operation],
  feed_dict={
        x_input: train_images_noise,
  y_input: train_images,
  })
    train_summary_writer.add_summary(merged_summary, batch_no)

开始此训练后,可以在 TensorBoard 中查看结果。 损失显示在此处: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QCH9KKaB-1681567519370)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/2ed9d697-3908-4169-927f-99769d2f7bdf.png)]

Tensorboard 说明了输出图

损耗稳步下降,并将在迭代过程中保持缓慢下降。 这显示了自编码器如何快速收敛。 接下来,原始图像显示三位数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RnO5pii-1681567519370)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/4e4b465b-0039-41c7-826e-67ba07141257.png)]

以下是添加了噪点的相同图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9w94lC0Z-1681567519370)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/16852acb-354f-43a2-832c-78fb55edb1ff.png)]

您会注意到有很大的噪音,这是作为输入给出的。 接下来,是使用去噪自编码器重建的相同编号的图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5zGECT4h-1681567519371)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/fcaa79e6-c455-46a6-8d49-1788aa7be7b6.png)]

您会注意到,去噪自编码器在消除噪声方面做得非常出色。 您可以在测试图像上运行它,并可以看到质量得到保持。 对于更复杂的数据集,可以使用卷积神经网络以获得更好的结果。 该示例展示了计算机视觉深度学习的强大功能,因为它是在无监督的情况下进行训练的。

总结

在本章中,您学习了如何从图像中提取特征并将其用于 CBIR。 您还学习了如何使用 TensorFlow Serving 来推断图像特征。 我们看到了如何利用近似最近邻或更快的匹配而不是线性扫描。 您了解了散列如何仍可以改善结果。 引入了自编码器的概念,我们看到了如何训练较小的特征向量以进行搜索。 还显示了使用自编码器进行图像降噪的示例。 我们看到了使用基于位的比较的可能性,该比较可以将其扩展到数十亿张图像。

在下一章中,我们将看到如何训练对象检测问题的模型。 我们将利用开源模型来获得良好的准确率,并了解其背后的所有算法。 最后,我们将使用所有想法来训练行人检测模型。

四、对象检测

对象检测是在图像中找到对象位置的动作。 在本章中,我们将通过了解以下主题来学习对象检测技术和实现行人检测:

  • 基础知识以及定位和检测之间的区别
  • 各种数据集及其描述
  • 用于对象定位和检测的算法
  • TensorFlow API 用于对象检测
  • 训练新的对象检测模型
  • 基于 YOLO 算法的移动汽车行人检测

检测图像中的对象

近年来,对象检测在应用和研究方面都出现了爆炸式增长。 对象检测是计算机视觉中的重要问题。 与图像分类任务相似,更深的网络在检测方面表现出更好的表现。 目前,这些技术的准确率非常好。 因此,它被用于许多应用中。

图像分类将图像标记为一个整体。 除了标记对象外,找到对象的位置也称为对象定位。 通常,对象的位置由直角坐标定义。 在图像中使用直角坐标查找多个对象称为检测。 这是对象检测的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arqd7MRT-1681567519371)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/07fc811c-af39-4bb1-b78d-16a2914667c2.png)]

该图显示了带有边界框的四个对象。 我们将学习可以执行查找框任务的算法。 这些应用在自动驾驶汽车和工业对象等机器人视觉领域具有巨大的应用前景。 我们可以将定位和检测任务概括为以下几点:

  • 定位检测标签内图像中的一个对象
  • 检测可找到图像中的所有对象以及标签

区别在于对象的数量。 在检测中,存在可变数量的对象。 在设计与定位或检测有关的深度学习模型的架构时,此小差异会带来很大的不同。 接下来,我们将看到可用于任务的各种数据集。

探索数据集

可用于对象定位和检测的数据集很多。 在本节中,我们将探索研究社区用来评估计法的数据集。 有些数据集带有不同数量的对象,这些对象中标注的范围从 20 到 200 不等,这使得对象检测变得困难。 与其他数据集(每个图像仅包含一个对象)相比,某些数据集在一个图像中包含的对象太多。 接下来,我们将详细查看数据集。

ImageNet 数据集

ImageNet 具有用于评估分类,定位和检测任务的数据。 第 2 章,“图像分类”详细讨论了分类数据集。 与分类数据类似,定位任务有 1,000 个类别。 准确率是根据前五次检测得出的。 所有图像中至少会有一个边界框。 有 470,000 张图像的检测问题有 200 个对象,每个图像平均有 1.1 个对象。

PASCAL VOC 挑战

PASCAL VOC 挑战赛于 2005 年至 2012 年进行。该挑战赛被认为是对象检测技术的基准。 数据集中有 20 个类别。 该数据集包含用于训练和验证的 11,530 张图像,以及针对兴趣区域的 27,450 条标注。 以下是数据集中存在的二十个类:

  • 人: 人
  • 动物: 鸟,猫,牛,狗,马,绵羊
  • 车辆: 飞机,自行车,轮船,公共汽车,汽车,摩托车,训练
  • 室内: 水壶,椅子,餐桌,盆栽,沙发,电视/显示器

您可以从这里下载数据集。 每个图像平均有 2.4 个对象。

COCO 对象检测挑战

上下文中的通用对象COCO)数据集具有 200,000 张图像,其中 80 个类别的标注超过 500,000 个。 它是最广泛的公开可用的对象检测数据库。 下图显示了数据集中存在的对象的列表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aTEbjpud-1681567519371)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/277bcdb3-8b17-4c4a-829a-e79e857a354c.png)]

每个图像的平均对象数为 7.2。 这些是对象检测挑战的著名数据集。 接下来,我们将学习如何针对这些数据集评估计法。

使用指标评估数据集

指标对于深度学习任务中的理解至关重要。 由于人工标注,对象检测和定位的度量是特殊的。 人类可能已经标注了一个名为真实情况的框。 真实性不一定是绝对真理。 此外,盒子的像素可能因人而异。 因此,算法很难检测到人类绘制的确切边界框。 交并比IoU)用于评估定位任务。 平均精确度平均值mAP)用于评估检测任务。 我们将在下一部分中看到指标的描述。

交并比

IoU 是真实情况与预测面积的重叠面积与总面积之比。 这是该指标的直观说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ew5OgZt3-1681567519371)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/6d0305cb-779a-42f0-8bfe-6834eecdca89.png)]

这两个正方形代表真实情况和预测的边界框。 IoU 计算为重叠面积与并集面积之比。 这是给定地面真理和预测边界框的 IoU 计算脚本:

代码语言:javascript复制
def calculate_iou(gt_bb, pred_bb):
    '''
  :param gt_bb: ground truth bounding box  :param pred_bb: predicted bounding box '''  gt_bb = tf.stack([
        gt_bb[:, :, :, :, 0] - gt_bb[:, :, :, :, 2] / 2.0,
  gt_bb[:, :, :, :, 1] - gt_bb[:, :, :, :, 3] / 2.0,
  gt_bb[:, :, :, :, 0]   gt_bb[:, :, :, :, 2] / 2.0,
  gt_bb[:, :, :, :, 1]   gt_bb[:, :, :, :, 3] / 2.0])
    gt_bb = tf.transpose(gt_bb, [1, 2, 3, 4, 0])
    pred_bb = tf.stack([
        pred_bb[:, :, :, :, 0] - pred_bb[:, :, :, :, 2] / 2.0,
  pred_bb[:, :, :, :, 1] - pred_bb[:, :, :, :, 3] / 2.0,
  pred_bb[:, :, :, :, 0]   pred_bb[:, :, :, :, 2] / 2.0,
  pred_bb[:, :, :, :, 1]   pred_bb[:, :, :, :, 3] / 2.0])
    pred_bb = tf.transpose(pred_bb, [1, 2, 3, 4, 0])
    area = tf.maximum(
        0.0,
  tf.minimum(gt_bb[:, :, :, :, 2:], pred_bb[:, :, :, :, 2:]) -
        tf.maximum(gt_bb[:, :, :, :, :2], pred_bb[:, :, :, :, :2]))
    intersection_area= area[:, :, :, :, 0] * area[:, :, :, :, 1]
    gt_bb_area = (gt_bb[:, :, :, :, 2] - gt_bb[:, :, :, :, 0]) * 
                 (gt_bb[:, :, :, :, 3] - gt_bb[:, :, :, :, 1])
    pred_bb_area = (pred_bb[:, :, :, :, 2] - pred_bb[:, :, :, :, 0]) * 
                   (pred_bb[:, :, :, :, 3] - pred_bb[:, :, :, :, 1])
    union_area = tf.maximum(gt_bb_area   pred_bb_area - intersection_area, 1e-10)
    iou = tf.clip_by_value(intersection_area / union_area, 0.0, 1.0)
    return iou

真实情况和预测的边界框堆叠在一起。 然后在处理负面积的情况下计算面积。 当边界框坐标不正确时,可能会出现负区域。 框的右侧坐标很多发生在从左到左的坐标上。 由于没有保留边界框的结构,因此必然会出现负区域。 计算联合和交叉区域,然后进行最终的 IoU 计算,该计算是真实情况和预测的重合面积与总面积之比。 IoU 计算可以与算法结合使用,以训练定位问题。

平均精度均值

mAP 用于评估检测算法。 mAP 度量是检测到的边界框的精度和召回率的乘积。 mAP 值的范围是 0 到 100。数字越大,则越好。 可以通过分别为每个类别计算平均精度AP),然后计算该类别的平均值来计算 mAP。 仅当 mAP 高于 0.5 时,检测结果才被视为真正的阳性。 通过绘制每个类别的绘制精度/召回曲线,可以合并来自测试图像的所有检测。 曲线下的最终区域可用于算法比较。 mAP 是衡量网络灵敏度的一种很好的方法,同时不会引发很多错误警报。 我们已经了解了数据集的评估计法。 接下来,我们将研究定位任务的算法。

定位算法

定位算法是在第 2 章,“图像分类”和第 3 章,“图像检索”中学习的材料的扩展。 在图像分类中,图像经过 CNN(卷积神经网络)的多层。 CNN 的最后一层输出属于每个标签的概率值。 可以扩展它以定位对象。 我们将在以下各节中看到这些想法。

使用滑动窗口定位对象

定位的一种直观方法是使用对象预测图像的多个裁剪部分。 可以通过在图像上移动一个窗口并为每个窗口进行预测来完成图像的裁剪。 移动比图像小的窗口并根据窗口大小裁剪图像的方法称为滑动窗口。 可以对图像的每个裁剪窗口进行预测,这称为滑动窗口对象检测。

可以通过针对紧密裁剪的图像进行图像分类问题训练的深度学习模型来完成预测。 近距离修剪意味着在整个图像中只会找到一个对象。 窗口在整个图像上的移动必须一致。 图像的每个部分都会通过模型以找到分类。 这种方法有两个问题。

  • 它只能找到与窗口大小相同的对象。 如果对象大小大于窗口大小,则滑动窗口将丢失对象。 为了克服这个问题,我们将使用标度空间的概念。
  • 另一个问题是,将窗口移到像素上方可能会导致丢失一些对象。 在每个像素上移动窗口会导致大量额外的计算,因此会降低系统速度。 为避免这种情况,我们将在卷积层中加入一个技巧。

在下一节中,我们将介绍这两种技术。

比例空间概念

比例空间是使用各种大小的图像的概念。 图像会缩小为较小的尺寸,因此可以在相同尺寸的窗口中检测到较大的对象。 可以使用减小的尺寸将图像调整为某些尺寸。 通过删除替代像素或插值来调整图像大小可能会留下一些伪像。 因此,图像被平滑并迭代调整大小。 通过平滑和调整大小获得的图像是比例空间。

窗口在每个刻度上滑动以定位对象。 运行多个比例相当于使用更大的窗口运行图像。 在多个规模上运行的计算复杂度很高。 可以通过以准确率为代价进行快速移动来加快定位速度。 复杂性使解决方案无法在生产中使用。 滑动窗口的思想可以通过完全卷积的滑动窗口实现而变得高效。

将全连接层训练为卷积层

滑动窗口的问题是计算复杂度。 复杂性是因为对每个窗口都进行了预测。 已针对重叠区域的每个窗口计算了深度学习特征。 可以减少裁剪窗口中重叠区域的特征计算。 解决方案是使用仅计算一次特征的全卷积网络。 为了理解全卷积网络,让我们首先看看如何将全连接层转换为convolution_layer。 内核更改为相同的大小,并使用与神经元数量相同的过滤器数量。 也可以对其他层重复此操作。 更改内核大小是将全连接层转换为convolution_layer的简便方法:

代码语言:javascript复制
convolution_layer_1 = convolution_layer(x_input_reshape, 64)
pooling_layer_1 = pooling_layer(convolution_layer_1)
convolution_layer_2 = convolution_layer(pooling_layer_1, 128)
pooling_layer_2 = pooling_layer(convolution_layer_2)
dense_layer_bottleneck = convolution_layer(pooling_layer_2, 1024, [5, 5])
logits = convolution_layer(dense_layer_bottleneck, no_classes, [1, 1])
logits = tf.reshape(logits, [-1, 10])

密集层表示为卷积层。 这个想法在各种情况下都是强大而有用的。 我们将扩展此思想,以将滑动窗口表示为完整的卷积网络。

滑动窗口的卷积实现

在这种技术中,最终目标不是滑动,而是变成一些需要深度的目标,并需要多个框作为窗口。 Sermanet 等人使用完全卷积实现来克服滑动窗口的这一问题。 这是滑动窗口的这种卷积实现的说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SEv6j1Cv-1681567519371)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/615888e7-8aac-425c-83be-0802095b2cd8.png)]

经 Sermanet 等人许可复制。

在示例的上部,常规分类表示为完全卷积层。 在该图的下部,相同的内核应用于更大的图像,最后生成2x2而不是 1。最后一层表示这些边界框的输出中的四个。 具有用于预测的体积可以提高效率,但是盒子仍然存在准确定位的问题。 因此不需要滑动窗口,因此解决了复杂性。 纵横比总是在变化,必须在多个比例尺上看到。 通过完全卷积方法生成的边界框不是很准确。 仅针对额外区域进行额外计算。 可以想像,这些盒子仅限于经过训练的盒子的数量。 接下来,我们将看到一种更准确地检测边界框位置的方法。

将定位视为回归问题

思考定位的一种基本方法是将问题建模为回归问题。 边界框是四个数字,因此可以通过回归设置以直接方式进行预测。 我们还需要预测标签,这是一个分类问题。

有不同的参数化可用于定义边界框。 边界框通常有四个数字。 表示形式之一是坐标的中心以及边界框的高度和宽度。 通过删除全连接层并用回归编码器替换它,可以使用预训练的模型。 必须使用 L2 损失对回归进行正则化,而 L2 损失在异常值方面表现不佳。 L1 的损失比 L1 好。 用平滑化的正则化交换回归更好。 对模型进行微调可提供良好的准确率,而对整个网络进行训练只会带来微不足道的表现改善。 在训练时间和准确率之间进行权衡。 接下来,我们将看到使用卷积网络进行回归的不同应用。

将回归应用于其他问题

回归图像坐标适用于其他几种应用,例如姿态检测基准点检测。 姿势检测是在人体中发现关节位置的动作,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d1XCHgr2-1681567519372)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/9e37fff6-eae3-4729-8cf6-c015a4e0b85f.png)]

在上一个图像中,检测到多个位置,例如头部,颈部,肩膀,脚踝和手。 这可以扩展到所有人类部分。 我们了解到的回归可以用于此应用。 这是基准点检测的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZFIj48q-1681567519372)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/6bfd58bb-e261-4dee-8b30-25c3f884e729.png)]

基准点是人脸相对于眼睛,鼻子和嘴唇的位置的地标。 找到这些地标对于基于人脸的增强现实应用至关重要。 人脸识别中还有更多地标可用,将在第 6 章,“相似性学习”中详细介绍。

结合回归与滑动窗口

为滑动窗口方法或完全卷积方法中的每个窗口计算分类分数,以了解该窗口中存在哪些对象。 代替预测用于检测对象的每个窗口的分类得分,可以利用分类得分来预测每个窗口本身。 结合滑动窗口,比例空间,完全卷积和回归之类的所有想法,比任何单独的方法都具有更好的结果。 以下是各种网络使用回归方法在ImageNet数据集上获得的前五个定位错误率:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4oO52SeV-1681567519372)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/e4493c04-62ab-4564-9465-cb134209a626.png)]

上图显示网络越深,结果越好。 对于 AlexNet,定位方法未在本文中描述。 OverFeat 使用带有框合并的多尺度卷积回归。 VGG 使用了定位,但比例尺和位置较少。 这些收益归因于深层特征。 ResNet 使用不同的定位方法和更深入的特征。

回归编码器和分类编码器独立运行。 因此,有可能预测边界框的标签不正确。 通过在不同的层上附加回归编码器可以解决此问题。 该方法也可以用于多个对象,从而解决了对象检测问题。 给定一个图像,找到其中的所有实例。 很难将检测视为回归,因为输出的数量是可变的。 一个图像可能有两个对象,而另一个可能有三个或更多。 在下一节中,我们将看到更有效地处理检测问题的算法。

检测对象

对象检测算法有多种变体。 这里讨论了对象检测 API 附带的一些算法。

卷积神经网络(R-CNN)的区域

该系列的第一个工作是 Girshick 等人提出的 CNN 区域。 它提出了一些框,并检查是否有任何框对应于基本事实。 选择性搜索用于这些地区提案。 选择性搜索通过对各种大小的窗口的颜色/纹理进行分组来建议区域。 选择性搜索寻找斑点样的结构。 它以一个像素开始,并在更大范围内产生斑点。 它产生了大约 2,000 个区域提案。 与所有可能的滑动窗口相比,该区域建议更少。

调整提案的大小并通过标准的 CNN 体​​系结构,例如 Alexnet/VGG/Inception/ResNet。 CNN 的最后一层是通过 SVM 进行训练的,该 SVM 使用无对象类来标识对象。 通过拉紧图像周围的框可以进一步改善框。 使用对象区域建议训练用于预测更近边界框的线性回归模型。 R-CNN 的架构如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vloYVAlp-1681567519372)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/4f45a709-739b-41a7-969e-56589fecb96f.png)]

经 Girshick 等人许可复制。

编码器可以是标准深度学习模型的预训练模型。 从训练数据为所有区域计算特征。 存储特征,然后训练 SVM。 接下来,用标准化坐标训练边界框。 在图像坐标之外可能会有一些建议,因此将其标准化以进行训练和推理。

这种方法的缺点是:

  • 通过选择性搜索形成了多个建议,因此必须计算许多推论,通常约为 2,000
  • 必须对三个分类器进行训练,这会增加参数的数量
  • 没有端到端的训练

Fast R-CNN

Girshick 等人提出的 Fast R-CNN 方法仅运行一次 CNN 推理,因此减少了计算量。 CNN 的输出用于建议网络并选择边界框。 它介绍了一种称为兴趣区域池的技术。 兴趣区域池采用 CNN 特征,并根据区域将它们合并在一起。 合并使用 CNN 进行推理后获得的特征,并选择区域,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5KceIdjb-1681567519373)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/9607861d-014a-4574-980d-90fb23f91ae4.png)]

经 Girshick 等人许可复制。

这样,执行端到端训练,避免了多个分类器。 请注意,SVM 被 softmax 层替换,并且框回归器由边界框回归器代替。 仍然存在的缺点是选择性搜索,这需要一些时间。

Faster R-CNN

Ren 等人提出了 Faster R-CNN。 Faster R-CNN 和 Fast R-CNN 方法之间的区别在于,Faster R-CNN 使用诸如 VGG 和 Inception 等架构的 CNN 特征来提案而不是选择性搜索。 CNN 特征进一步通过区域提议网络传递。 滑动窗口通过具有潜在边界框和分数的特征传递,并输出一些直观的长宽比,模型输出边界框和分数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mqzq2vzY-1681567519373)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/ea103e8f-b553-4d9d-89cd-077888fb7db0.png)]

经 Ren 等人许可复制。

更快的 R-CNN 比快速 R-CNN 更快,因为它通过仅计算一次特征来节省计算量。

单发多框探测器

SSD(单发多框)是所有方法中最快的。 此方法同时预测对象并找到边界框。 在训练期间,可能会有很多负面因素,因此很难否定地挖掘类别失衡。 CNN 的输出具有各种大小的特征。 这些被传递到3x3卷积过滤器以预测边界框。

此步骤将预测对象和边界框:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HS216cpY-1681567519373)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/28e92ddf-38ea-4b41-80e6-add3753d03d2.png)]

经 Liu 等人许可复制。

这些是可用于对象检测的算法,我们将在下一节中学习如何实现它们。

对象检测 API

Google 发布了经过预先训练的模型,并在COCO数据集上对各种算法进行了训练,以供公众使用。 该 API 建立在 TensorFlow 之上,旨在用于构建,训练和部署对象检测模型。 这些 API 支持对象检测和定位任务。 预训练模型的可用性可对新数据进行微调,从而加快训练速度。 这些不同的模型在速度和准确率之间进行权衡。

安装与配置

使用以下命令安装协议缓冲区(protobuf)编译器。 为 protobuf 创建目录并直接下载该库:

代码语言:javascript复制
mkdir protoc_3.3
cd protoc_3.3
wget https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip

更改文件夹的权限并提取内容,如下所示:

代码语言:javascript复制
chmod 775 protoc-3.3.0-linux-x86_64.zip
unzip protoc-3.3.0-linux-x86_64.zip

协议缓冲区(protobuf)是 Google 的语言无关,平台无关的可扩展机制,用于序列化结构化数据。 它用于 XML 的使用,但是更加简单快捷。 模型通常在 TensorFlow 中导出为这种格式。 一个人可以定义一次数据结构,但可以用多种语言进行读写。 然后运行以下命令来编译 protobuf。 返回到工作文件夹,然后从这里克隆存储库,并将它们移至以下文件夹:

代码语言:javascript复制
git clone https://github.com/tensorflow/models.git

现在,使用以下代码将模型移至研究文件夹:

代码语言:javascript复制
cd models/research/
~/protoc_3.3/bin/protoc object_detection/protos/*.proto --python_out=.

TensorFlow 对象检测 API 使用 protobuf 导出模型权重和训练参数。 TensorFlow ,模型,研究和苗条目录应通过以下命令附加到PYTHONPATH

代码语言:javascript复制
export PYTHONPATH=.:./slim/

使用前面的命令添加到 python 路径仅一次。 对于下一个,该命令必须再次运行。 可以通过运行以下代码来测试安装:

代码语言:javascript复制
python object_detection/builders/model_builder_test.py

此代码的输出在此处给出:

代码语言:javascript复制
Ran 7 tests in 0.022s

OK

可以从这里获得有关安装的更多信息。 现在安装已完成并经过测试。

预训练模型

有几种模型已经过预先训练并可以使用。 所有这些模型都在COCO数据集上进行了训练,可用于检测COCO数据集中可用的对象,例如人和汽车。 这些模型对于新任务(例如交通标志检测)的迁移学习也很有用。 此处显示了经过预训练的模型的表格,其中包含COCO数据集上的相对速度和 mAP。 使用不同的 CNN 训练了各种算法,并在名称中进行了描述:

模型名称

速度

COCO MAP

ssd_mobilenet_v1_coco

21

ssd_inception_v2_coco

24

rfcn_resnet101_coco

30

faster_rcnn_resnet101_coco

32

faster_rcnn_inception_resnet_v2_atrous_coco

37

根据需求,可以从模型中进行选择。 下载在 Mobilenet 上训练的 SSD 模型,并通过转到工作目录将其提取,如下所示:

代码语言:javascript复制
mkdir Chapter04 && cd Chapter04
wget http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017.tar.gz
tar -xzvf ssd_mobilenet_v1_coco_11_06_2017.tar.gz

Chapter04文件夹中将包含各种文件,这些文件在此处列出:

  • 这是图的原始定义-graph.pbtxt
  • 图的权重已冻结,可以用于推断-frozen_inference_graph.pb
  • 检查点文件
    • model.ckpt.data-00000-of-00001
    • model.ckpt.meta
    • model.ckpt.index

下一部分将使用此模型执行检测任务。

重新训练对象检测模型

使用相同的 API,我们可以为自定义数据集重新训练模型。 定制数据的训练涉及数据集的准备,选择算法以及执行微调。 整个流水线可以作为参数传递给训练脚本。 训练数据必须转换为 TensorFlow 记录。 TensorFlow 记录是 Google 提供的一种文件格式,可以使数据读取比常规文件更快。 现在,我们将逐步进行训练。

Pet 数据集的数据准备

本示例使用Oxford-IIIT Pet数据集。 使用这些命令从Chapter04目录下载图像和标注。

代码语言:javascript复制
wget http://www.robots.ox.ac.uk/~vgg/data/pets/daimg.tar.gz
wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz

提取图像和标注,如下所示:

代码语言:javascript复制
tar -xvf images.tar.gz
tar -xvf annotations.tar.gz

创建pet_tf记录文件以在tf记录中创建数据集,因为它们是对象检测训练器的必需输入。 可在object_detection/data/pet_label_map.pbtxt找到Pet数据集的label_map。 移至research文件夹并运行以下命令:

代码语言:javascript复制
python object_detection/create_pet_tf_record.py 
 --label_map_path=object_detection/data/pet_label_map.pbtxt 
 --data_dir=~/chapter4/. 
 --output_dir=~/chapter4/.

您可以在研究目录pet_train.recordpet_val.record中看到两个.record文件。

目标检测的训练流水线

训练 protobuf 必须配置为进行训练。 在此过程中,以下五件事很重要:

  • 具有模型类型的模型配置
  • train_config用于标准训练参数
  • 必须报告的指标的eval_config
  • 数据集的train_input_配置
  • 评估数据集的eval_input_配置

我们将使用这个页面中的配置文件。 通过运行以下命令将其下载到Chapter04文件夹。 打开config文件并编辑以下行:

代码语言:javascript复制
fine_tune_checkpoint: "~/Chapter04/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt"

train_input_reader: {
  tf_record_input_reader {
    input_path: "~/Chapter04/pet_train.record"
  }
  label_map_path: "~/model/research/object_detection/data/pet_label_map.pbtxt"
}

eval_input_reader: {
  tf_record_input_reader {
    input_path: "~/Chapter04/pet_val.record"
  }
  label_map_path: "~/model/research/object_detection/data/pet_label_map.pbtxt"
}

保存config文件。 文件中有各种参数会影响模型的准确率。

训练模型

现在,API,数据和配置文件已准备好进行重新训练。 可以通过以下命令触发训练:

代码语言:javascript复制
PYTHONPATH=.:./slim/. python object_detection/train.py 
 --logtostderr 
 --pipeline_config_path=~/chapter4/ssd_mobilenet_v1_pets.config 
 --train_dir=~/Chapter04

训练将从大约 140 的损失开始,并将持续减少。 训练将永远进行,必须使用Ctrl C命令手动将其终止。 训练期间创建的检查点可在以后用于推理。

使用 TensorBoard 监控损失和准确率

训练损失和准确率可以使用 TensorBoard 进行监视。 使用以下命令运行 TensorBoard:

代码语言:javascript复制
tensorboard --logdir=/home/ubuntu/Chapter04

训练和评估都可以在 TensorBoard 中可视化。

训练自动驾驶汽车的行人检测

可以在这个页面中找到用于训练行人对象检测的数据集。 可以在这个页面中找到检测行人的步骤。 可以从这个页面和这个页面下载用于训练符号检测器的数据集。 对于无人驾驶汽车,图像中将有四个类别用于标记:行人,汽车,摩托车和背景。 当不存在任何后台类时,必须检测到该后台类。 训练深度学习分类模型的一个假设是,至少一个对象将出现在图像中。 通过添加background类,我们克服了这个问题。 神经网络还可以根据标签生成对象的边界框。

YOLO 对象检测算法

最近的对象检测算法是你只需看一次YOLO)。 图像分为多个网格。 图像的每个网格单元都运行相同的算法。 让我们通过定义带有初始化器的层来开始实现:

代码语言:javascript复制
def pooling_layer(input_layer, pool_size=[2, 2], strides=2, padding='valid'):
    layer = tf.layers.max_pooling2d(
        inputs=input_layer,
  pool_size=pool_size,
  strides=strides,
  padding=padding
    )
    add_variable_summary(layer, 'pooling')
    return layer

def convolution_layer(input_layer, filters, kernel_size=[3, 3], padding='valid',
  activation=tf.nn.leaky_relu):
    layer = tf.layers.conv2d(
        inputs=input_layer,
  filters=filters,
  kernel_size=kernel_size,
  activation=activation,
  padding=padding,
  weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
  weights_regularizer=tf.l2_regularizer(0.0005)
    )
    add_variable_summary(layer, 'convolution')
    return layer

def dense_layer(input_layer, units, activation=tf.nn.leaky_relu):
    layer = tf.layers.dense(
        inputs=input_layer,
  units=units,
  activation=activation,
  weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
  weights_regularizer=tf.l2_regularizer(0.0005)
    )
    add_variable_summary(layer, 'dense')
    return layer

可以注意到,激活层为leaky_relu,权重以截断的正态分布初始化。 这些修改的层可用于构建模型。 该模型如下创建:

代码语言:javascript复制
yolo = tf.pad(images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]), name='pad_1')
yolo = convolution_layer(yolo, 64, 7, 2)
yolo = pooling_layer(yolo, [2, 2], 2, 'same')
yolo = convolution_layer(yolo, 192, 3)
yolo = pooling_layer(yolo, 2, 'same')
yolo = convolution_layer(yolo, 128, 1)
yolo = convolution_layer(yolo, 256, 3)
yolo = convolution_layer(yolo, 256, 1)
yolo = convolution_layer(yolo, 512, 3)
yolo = pooling_layer(yolo, 2, 'same')
yolo = convolution_layer(yolo, 256, 1)
yolo = convolution_layer(yolo, 512, 3)
yolo = convolution_layer(yolo, 256, 1)
yolo = convolution_layer(yolo, 512, 3)
yolo = convolution_layer(yolo, 256, 1)
yolo = convolution_layer(yolo, 512, 3)
yolo = convolution_layer(yolo, 256, 1)
yolo = convolution_layer(yolo, 512, 3)
yolo = convolution_layer(yolo, 512, 1)
yolo = convolution_layer(yolo, 1024, 3)
yolo = pooling_layer(yolo, 2)
yolo = convolution_layer(yolo, 512, 1)
yolo = convolution_layer(yolo, 1024, 3)
yolo = convolution_layer(yolo, 512, 1)
yolo = convolution_layer(yolo, 1024, 3)
yolo = convolution_layer(yolo, 1024, 3)
yolo = tf.pad(yolo, np.array([[0, 0], [1, 1], [1, 1], [0, 0]]))
yolo = convolution_layer(yolo, 1024, 3, 2)
yolo = convolution_layer(yolo, 1024, 3)
yolo = convolution_layer(yolo, 1024, 3)
yolo = tf.transpose(yolo, [0, 3, 1, 2])
yolo = tf.layers.flatten(yolo)
yolo = dense_layer(yolo, 512)
yolo = dense_layer(yolo, 4096)

dropout_bool = tf.placeholder(tf.bool)
yolo = tf.layers.dropout(
        inputs=yolo,
  rate=0.4,
  training=dropout_bool
    )
yolo = dense_layer(yolo, output_size, None)

堆叠了几个卷积层,生成了 YOLO 网络。 该网络用于创建用于实时检测的对象检测算法。

总结

在本章中,我们了解了对象定位和检测任务之间的区别。 讨论了几个数据集和评估标准。 讨论了各种解决定位问题和算法的方法,例如用于检测的 R-CNN 和 SSD 模型的变体。 涵盖了在开源存储库中执行检测的过程。 我们使用该技术训练了行人检测模型。 我们还了解了在训练此类模型时需要进行的各种取舍。

在下一章中,我们将学习语义分割算法。 我们将使用该知识来实现​​医学成像和卫星成像问题的分割算法。

五、语义分割

在本章中,我们将学习各种语义分割技术并为其训练模型。 分割是逐像素分类任务。 解决分割问题的思想是对对象检测问题的扩展。 分割在医学和卫星图像理解等应用中非常有用。

本章将涵盖以下主题:

  • 学习语义分割和实例分割之间的区别
  • 分割数据集和指标
  • 语义分割算法
  • 分割在医学和卫星图像中的应用
  • 实例分割算法

预测像素

图像分类是预测标签或类别的任务。 对象检测是预测几种基于深度学习的算法及其相应边界框的列表的任务。 边界框可能在其中包含除检测到的对象以外的其他对象。 在某些应用中,将每个像素标记到标签很重要,而不是可能包含多个对象的边框。 “语义分割”是预测逐像素标签的任务。

这是图像及其对应的语义分割的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2khSNZCr-1681567519373)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/ecd4a853-47b0-4b64-959d-e659b7a506a8.jpeg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DPnUSec5-1681567519374)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/f3d07c15-d6fe-4677-9b43-9628b38bc9d4.jpg)]

如图中所示,使用每个像素的标签预测输入图像。 标签可以是天空,树木,人,山和桥。 标签不是分配给整个图像,而是分配给每个像素。 语义分割独立标记像素。 您会注意到每个人都没有区别。 图像中的所有人员均以相同的方式标记。

这是区分相同标签的每个实例的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GfTycHfZ-1681567519374)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1c6a1b46-8d59-4882-99a5-cdf1ddfc4ef0.jpg)]

用像素标记分割每个实例的任务称为实例分割。 实例分割可以被认为是具有像素级标签的对象检测的扩展。 语义分段和实例分段的应用非常广泛,下一部分将提供一些应用。

诊断医学图像

可以使用分割技术来诊断医学图像。 现代医学成像技术,例如磁共振成像MRI),计算机断层扫描CT)和视网膜病变创建高质量的图像。 可以将通过这种技术生成的图像划分为多个区域,以检测来自脑部扫描的肿瘤或来自视网膜扫描的斑点。 一些设备提供体积图像,这些图像也可以通过分段进行分析。 分割视频以进行机器人手术,使医生能够在机器人协助的手术中仔细查看区域。 在本章的后面,我们将看到如何分割医学图像。

通过卫星图像了解地球

卫星图像最近变得越来越丰富。 卫星捕获的图像提供了地球整个表面的高分辨率视图。 通过分析卫星图像,我们可以了解有关地球的几件事,例如:

  • 衡量与经济增长相关的国家的架构率
  • 测量油箱
  • 规划和组织交通
  • 计算森林砍伐及其影响
  • 通过对动物进行计数并跟踪其运动来帮助保护野生动植物
  • 发现考古遗址
  • 绘制自然灾害造成的损坏区域

卫星图像还有更多的应用可能。 对于上述大多数问题,解决方案始于卫星图像的分割。 在本章的后面,我们将看到如何分割卫星图像。

使机器人拥有视觉

分割场景对于机器人看清周围世界并与之互动至关重要。 工业和家用机器人必须处理这些对象。 一旦根据对象跨越了机器人的视野,就可以进行处理。 还有更多值得一提的应用:

  • 对缺陷进行分割的工业检查工具
  • 时装行业的色彩诊断; 可以将图像与各种时尚对象进行分割并将其用于颜色解析
  • 区分背景与前景来应用人像效果

在下一部分中,我们将学习一些用于评估分割算法的公共数据集。

数据集

第 4 章,“对象检测”中提到的PASCALCOCO数据集也可以用于分割任务。 标注是不同的,因为它们是按像素标记的。 新算法通常以COCO数据集为基准。 COCO还具有诸如草,墙和天空之类的东西数据集。 像素精度属性可用作评估计法的指标。

除了上面提到的那些以外,在医学影像和卫星影像领域还有其他几个数据集。 这里提供了指向其中一些链接的供您参考:

  • http://www.cs.bu.edu/~betke/BiomedicalImageSegmentation/
  • https://www.kaggle.com/c/intel-mobileodt-cervical-cancer-screening/data
  • https://www.kaggle.com/c/diabetic-retinopathy-detection
  • https://grand-challenge.org/all_challenges/
  • http://www.via.cornell.edu/databases/
  • https://www.kaggle.com/c/dstl-satellite-imagery-feature-detection
  • https://aws.amazon.com/public-datasets/spacenet/
  • https://www.iarpa.gov/challenges/fmow.html
  • https://www.kaggle.com/c/planet-understanding-the-amazon-from-space

为分割任务创建训练数据非常昂贵。 有在线工具可用于标注数据集。 麻省理工学院(MIT)提供的 LabelMe 移动应用非常适合标注,可以从这里下载。

语义分割算法

提出了几种基于深度学习的算法来解决图像分割任务。 可以在像素级别应用滑动窗口方法进行分割。 滑动窗口方法会拍摄图像并将图像分成较小的作物。 图像的每种裁剪都被分类为标签。 这种方法昂贵且效率低下,因为它不会重用重叠补丁之间的共享特征。 在以下各节中,我们将讨论一些可以克服此问题的算法。

全卷积网络

全卷积网络FCN)引入了端到端卷积网络的思想。 通过删除全连接层,可以将任何标准的 CNN 架构用于 FCN,其实现在第 4 章,“对象检测”中进行了显示。 全连接层被卷积层代替。 最终层的深度较高,尺寸较小。 因此,可以执行一维卷积以达到所需的标签数量。 但是对于分割,必须保留空间尺寸。 因此,构建完整的卷积网络时没有最大池,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pMaEzKdm-1681567519374)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/3b75d8a1-168a-4625-b727-169ebb0425f4.png)]

该网络的损耗是通过平均每个像素和小批量的交叉熵损耗来计算的。 最后一层的深度等于类数。 FCN 与对象检测相似,只是保留了空间尺寸。 由于某些像素可能会被错误预测,因此该架构产生的输出将很粗糙。 计算量很大,在下一节中,我们将看到如何解决此问题。

SegNet 架构

SegNet 具有编码器和解码器方法。 编码器具有各种卷积层,而解码器具有各种解卷积层。 SegNet 改进了 FCN 产生的粗略输出。 因此,它的内存占用较少。 当特征尺寸减小时,通过反卷积将其再次上采样至图像大小,从而反转了卷积效果。 反卷积学习用于上采样的参数。 由于池层中的信息丢失,这种架构的输出将很粗糙。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A2MvqXve-1681567519374)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/0bca20d8-04c9-48cb-8029-e8af3258c09f.png)]

现在,让我们学习几个新概念,这些概念称为上采样,无规则卷积和转置卷积,它们将帮助我们更好地理解该网络。

通过池化对层进行上采样

在第 1 章“入门”中,我们讨论了最大池化。 最大池化是一种从窗口中选取最大值的采样策略。 对于上采样,可以相反。 每个值都可以用零包围,以对该层进行上采样,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7aGz3skF-1681567519375)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/6c6fa429-432a-4bc4-af17-015e872d5490.png)]

将零添加到与上采样数字相同的位置。 通过记住下采样的位置并将其用于上采样,可以改善反池化,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iRne1NGy-1681567519375)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/7793c888-12eb-40b1-944f-85470767c43c.png)]

从索引角度来看,上采样比附加零产生更好的结果。 这种通过池对层进行上采样的方法是无法学到的,并且可以按原样工作。 接下来,我们将看到如何使用可学习的参数进行上采样和下采样。

通过卷积对层进行采样

可以使用卷积直接对层进行升采样或降采样。 可以增加用于卷积的步幅以引起下采样,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xc87BkKm-1681567519375)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/d2cd8c90-7707-4786-a5bc-e40b08a8a35d.png)]

通过卷积的下采样被称为无孔卷积扩张卷积大卷积。 类似地,可以通过学习内核将其反转为升采样,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1GdfQfrj-1681567519375)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/9e542e78-5c13-4d7f-8d96-e5cedc35bdec.png)]

直接使用卷积的上采样可以称为转置卷积。 其他一些同义词是反卷积分数步卷积上卷积。 现在,了解了升采样的过程。 这是描述先前算法的代码片段:

代码语言:javascript复制
input_height = 360 input_width = 480 kernel = 3 filter_size = 64 pad = 1 pool_size = 2

输入之后,它遵循大小逐渐减小的通常的卷积神经网络,可以称为编码器。 以下代码可用于定义编码器:

代码语言:javascript复制
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Layer(input_shape=(3, input_height, input_width)))

# encoder model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(filter_size, kernel, kernel,
  border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(pool_size, pool_size)))

model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(128, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(pool_size, pool_size)))

model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(256, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(pool_size, pool_size)))

model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(512, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))

可以使用以下代码将编码器的输出以增大的尺寸馈入解码器:

代码语言:javascript复制
# decoder model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(512, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())

model.add(tf.keras.layers.UpSampling2D(size=(pool_size, pool_size)))
model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(256, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())

model.add(tf.keras.layers.UpSampling2D(size=(pool_size, pool_size)))
model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(128, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())

model.add(tf.keras.layers.UpSampling2D(size=(pool_size, pool_size)))
model.add(tf.keras.layers.ZeroPadding2D(padding=(pad, pad)))
model.add(tf.keras.layers.Conv2D(filter_size, kernel, kernel, border_mode='valid'))
model.add(tf.keras.layers.BatchNormalization())

model.add(tf.keras.layers.Conv2D(nClasses, 1, 1, border_mode='valid', ))

解码图像的大小与输入的大小相同,并且可以使用以下代码来训练整个模型:

代码语言:javascript复制
model.outputHeight = model.output_shape[-2]
model.outputWidth = model.output_shape[-1]

model.add(tf.keras.layers.Reshape((nClasses, model.output_shape[-2] * model.output_shape[-1]),
  input_shape=(nClasses, model.output_shape[-2], model.output_shape[-1])))

model.add(tf.keras.layers.Permute((2, 1)))
model.add(tf.keras.layers.Activation('softmax'))

model.compile(loss="categorical_crossentropy", optimizer=tf.keras.optimizers.Adam, metrics=['accuracy'])

这种对图像进行编码和解码的方式克服了基于 FCN 的模型的缺点。 接下来,我们将看到具有膨胀卷积的不同概念。

用于更好训练的跳跃连接

分割输出的粗糙程度可以通过跳过架构来限制,并且可以获得更高的分辨率。 另一种替代方法是按比例放大最后三层并将其平均,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u2T0Dhno-1681567519376)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1cf06efe-233a-40d0-9fa5-7f53ab8f18dc.png)]

此算法将在后面的部分中用作卫星图像的示例。

膨胀卷积

逐像素分类和图像分类在结构上不同。 因此,减少信息的池化层将产生粗略的分段。 但是请记住,池化对于拥有更广阔的视野并允许采样至关重要。 引入了一种名为扩张卷积的新想法,以解决该问题,从而在进行更广阔视野的同时减少损耗。 扩大的卷积本质上是通过跳过窗口中的每个像素来进行卷积,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Je8hdvQ9-1681567519376)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1caea247-6635-401e-a844-b023c12f9366.png)]

膨胀距离随层而变化。 这样的分割结果的输出被放大以得到更精细的分辨率。 训练了一个单独的网络以进行多尺度聚合。

DeepLab

Chen 等人提出的 DeepLab 在多个尺度上执行卷积,并使用来自各种尺度的特征获得分数图。 然后对得分图进行插值,并通过条件随机场CRF)进行最终分割。 图像的这种比例处理可以通过使用其自己的 CNN 处理各种大小的图像,或者通过具有不同水平的卷积卷积的并行卷积来执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MCkWW7u-1681567519376)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/c958122d-5ce1-4d41-89a1-d2203bb2c7f5.png)]

经 Chen 等人许可复制。

RefiNet

膨胀卷积需要更大的输入,因此占用大量内存。 使用高分辨率图片时,这会带来计算问题。 里德等人提出了一种称为 RefiNet 的方法来克服此问题,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NwtrqS3Q-1681567519376)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/db354e6d-4923-4bbc-947a-44c5ba5be7c7.png)]

经 Reid 等人许可复制。

RefiNet 使用编码器,然后使用解码器。 CNN 的编码器输出。 解码器连接各种大小的特征:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3aGdMD7L-1681567519376)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/836b137d-c3de-4c2c-baa2-c6e86dd1b6fc.png)]

经 Reid 等人许可复制。

连接完成后会放大低维特征。

PSPnet

Zhoa 等人介绍的 PSPnet 中使用了全局内容。 方法是增加池化层的内核大小。 池化以金字塔的方式进行。 金字塔同时覆盖图像的各个部分和大小。 架构之间存在损失,因此无法进行适当的监管。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XDRPq2OQ-1681567519377)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/1a1c9433-d75c-4c5d-add8-1a1c9fc9ec8b.png)]

经 Zhao 等人许可复制。

大内核很重要

Peng 等人展示了大内核的重要性。 大内核比小内核具有更大的接受范围。 这些大内核的计算复杂度可用于以较小的内核来克服。 最后有一个边界优化网络。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEsYOtq8-1681567519377)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/3f5d488c-04a0-4396-a2a8-c1fb2bcbf1aa.png)]

经 Peng 等人许可复制。

DeepLab v3

Chen 等人在论文中使用批量归一化以提高表现。 特征的多尺度以级联方式编码,以提高表现:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RfmnVaqA-1681567519377)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/301dc866-0bfd-4010-a5f1-df4e33e12e34.png)]

经 Chen 等人许可复制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2WLqoNV-1681567519377)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/65958e56-9dfc-4075-8de4-d31f9bd2662e.png)]

经 Chen 等人许可复制。

我们已经看到了几种架构可以使用深度学习提高图像分割的准确率。 接下来,我们将看到在医学成像中的应用。

超神经分割

Kaggler 是一个组织进行预测建模和分析竞赛的组织。 Kagglers 曾经受到挑战,要从颈部超声图像中分割神经结构。 可以从这里下载有关该数据的数据。 Ronneberger 等人提出的 UNET 模型类似于自编码器,但具有卷积而不是全连接层。 这里有一个编码部分,其卷积减小,而解码器部分的卷积增大,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c2JGuGJk-1681567519377)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/d5f0233d-03ef-4826-9cba-8a3bbf2f4eb3.png)]

该图说明了 UNET 模型的架构[经 Ronneberger 等人许可复制]

相似大小的编码器和解码器部分的卷积通过跳过连接来学习。 模型的输出是一个介于 0 到 1 之间的掩码。让我们从导入函数开始,借助以下代码:

代码语言:javascript复制
import os
from skimage.transform import resize
from skimage.io import imsave
import numpy as np
from data import load_train_data, load_test_data

在所有导入之后,我们现在将使用以下代码定义大小:

代码语言:javascript复制
image_height, image_width = 96, 96 smoothness = 1.0 work_dir = ''

现在我们将定义dice_coefficient及其损失函数。 在这种情况下,dice_coefficient也是度量标准:

代码语言:javascript复制
def dice_coefficient(y1, y2):
    y1 = tf.flatten(y1)
    y2 = tf.flatten(y2)
    return (2. * tf.sum(y1 * y2)   smoothness) / (tf.sum(y1)   tf.sum(y2)   smoothness)

def dice_coefficient_loss(y1, y2):
    return -dice_coefficient(y1, y2)

UNET 模型可以定义如下:

代码语言:javascript复制
def preprocess(imgs):
    imgs_p = np.ndarray((imgs.shape[0], image_height, image_width), dtype=np.uint8)
    for i in range(imgs.shape[0]):
        imgs_p[i] = resize(imgs[i], (image_width, image_height), preserve_range=True)
    imgs_p = imgs_p[..., np.newaxis]
    return imgs_p

def covolution_layer(filters, kernel=(3,3), activation='relu', input_shape=None):
    if input_shape is None:
        return tf.keras.layers.Conv2D(
            filters=filters,
  kernel=kernel,
  activation=activation)
    else:
        return tf.keras.layers.Conv2D(
            filters=filters,
  kernel=kernel,
  activation=activation,
  input_shape=input_shape)

def concatenated_de_convolution_layer(filters):
    return tf.keras.layers.concatenate([
        tf.keras.layers.Conv2DTranspose(
            filters=filters,
  kernel=(2, 2),
  strides=(2, 2),
  padding='same'
  )],
  axis=3
  )

所有层都已连接并使用,如以下代码所示:

代码语言:javascript复制
unet = tf.keras.models.Sequential()
inputs = tf.keras.layers.Input((image_height, image_width, 1))
input_shape = (image_height, image_width, 1)
unet.add(covolution_layer(32, input_shape=input_shape))
unet.add(covolution_layer(32))
unet.add(pooling_layer())

unet.add(covolution_layer(64))
unet.add(covolution_layer(64))
unet.add(pooling_layer())

unet.add(covolution_layer(128))
unet.add(covolution_layer(128))
unet.add(pooling_layer())

unet.add(covolution_layer(256))
unet.add(covolution_layer(256))
unet.add(pooling_layer())

unet.add(covolution_layer(512))
unet.add(covolution_layer(512))

这些层是连接在一起的,并使用了反卷积层:

代码语言:javascript复制
unet.add(concatenated_de_convolution_layer(256))
unet.add(covolution_layer(256))
unet.add(covolution_layer(256))

unet.add(concatenated_de_convolution_layer(128))
unet.add(covolution_layer(128))
unet.add(covolution_layer(128))

unet.add(concatenated_de_convolution_layer(64))
unet.add(covolution_layer(64))
unet.add(covolution_layer(64))

unet.add(concatenated_de_convolution_layer(32))
unet.add(covolution_layer(32))
unet.add(covolution_layer(32))

unet.add(covolution_layer(1, kernel=(1, 1), activation='sigmoid'))

unet.compile(optimizer=tf.keras.optimizers.Adam(lr=1e-5),
  loss=dice_coefficient_loss,
  metrics=[dice_coefficient])

接下来,可以通过使用以下代码对模型进行图像训练:

代码语言:javascript复制
x_train, y_train_mask = load_train_data()

x_train = preprocess(x_train)
y_train_mask = preprocess(y_train_mask)

x_train = x_train.astype('float32')
mean = np.mean(x_train)
std = np.std(x_train)

x_train -= mean
x_train /= std

y_train_mask = y_train_mask.astype('float32')
y_train_mask /= 255.   unet.fit(x_train, y_train_mask, batch_size=32, epochs=20, verbose=1, shuffle=True,
  validation_split=0.2)

x_test, y_test_mask = load_test_data()
x_test = preprocess(x_test)

x_test = x_test.astype('float32')
x_test -= mean
x_test /= std

y_test_pred = unet.predict(x_test, verbose=1)

for image, image_id in zip(y_test_pred, y_test_mask):
    image = (image[:, :, 0] * 255.).astype(np.uint8)
    imsave(os.path.join(work_dir, str(image_id)   '.png'), image)

图像可以进行预处理和使用。 现在可以进行图像的训练和测试了。 训练模型后,分割会产生良好的结果,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXnekW1M-1681567519378)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/92227668-9c13-4160-b879-f011ded4bb4c.png)]

我们已经训练了可以分割医学图像的模型。 该算法可以在几种用例中使用。 在下一节中,我们将看到如何分割卫星图像。

分割卫星图像

在本节中,我们将使用国际摄影测量与遥感学会ISPRS)提供的数据集。 数据集包含 5 毫米分辨率的德国波茨坦的卫星图像。 这些图像带有红外和图像高度轮廓的附加数据。 与图像相关联的六个标签是:

  • 架构
  • 植被
  • 树木
  • 巢穴
  • 杂物
  • 硬质

总共提供了 8,000 x 6,000 色块的 38 张图像。 请转到页面并填写表格。 之后,在表单上选择以下选项:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RB9IELmY-1681567519378)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/3272ab24-2a2e-4ebe-bf40-91d3fd5d9eb0.png)]

发布表格后,将向您发送电子邮件,从中可以下载数据。

为分割建模 FCN

导入库并获得输入的形状。 标签数定义为6

代码语言:javascript复制
from .resnet50 import ResNet50
nb_labels = 6   img_height, img_width, _ = input_shape
input_tensor = tf.keras.layers.Input(shape=input_shape)
weights = 'imagenet'

在 ImageNet 上预先训练的ResNet模型将用作基本模型。 以下代码可用于使用ResNet定义基本模型:

代码语言:javascript复制
resnet50_model = ResNet50(
    include_top=False, weights='imagenet', input_tensor=input_tensor)

现在,我们将使用以下代码从ResNet中获取最后三层:

代码语言:javascript复制
final_32 = resnet50_model.get_layer('final_32').output
final_16 = resnet50_model.get_layer('final_16').output
final_x8 = resnet50_model.get_layer('final_x8').output

必须压缩每个跳过连接以匹配等于标签数的通道:

代码语言:javascript复制
c32 = tf.keras.layers.Conv2D(nb_labels, (1, 1))(final_32)
c16 = tf.keras.layers.Conv2D(nb_labels, (1, 1))(final_16)
c8 = tf.keras.layers.Conv2D(nb_labels, (1, 1))(final_x8)

可以使用双线性插值来调整压缩跳过连接的输出大小。 可以通过使用可以计算 TensorFlow 操作的Lambda层来实现插值。 以下代码段可用于使用 lambda 层进行插值:

代码语言:javascript复制
def resize_bilinear(images):
    return tf.image.resize_bilinear(images, [img_height, img_width])

r32 = tf.keras.layers.Lambda(resize_bilinear)(c32)
r16 = tf.keras.layers.Lambda(resize_bilinear)(c16)
r8 = tf.keras.layers.Lambda(resize_bilinear)(c8)

使用以下代码,可以通过添加三个值来合并我们定义的三层:

代码语言:javascript复制
m = tf.keras.layers.Add()([r32, r16, r8])

可以使用 softmax 激活来应用模型的概率。 在应用 softmax 之前和之后调整模型大小:

代码语言:javascript复制
x = tf.keras.ayers.Reshape((img_height * img_width, nb_labels))(m)
x = tf.keras.layers.Activation('img_height')(x)
x = tf.keras.layers.Reshape((img_height, img_width, nb_labels))(x)

fcn_model = tf.keras.models.Model(input=input_tensor, output=x)

已经定义了一个简单的 FCN 层,经过训练后,它会产生以下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YRRmh8Jt-1681567519378)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/c09f3fc7-b5d0-4eb1-9b22-e68707be3f96.png)]

您会看到六个标签的预测是合理的。 接下来,我们将学习分割实例。

分割实例

在分析图像时,我们的兴趣只会吸引到图像中的某些实例。 因此,它不得不从图像的其余部分中分割这些实例。 从其余信息中分离所需信息的过程被广泛称为分割实例。 在此过程中,首先拍摄输入图像,然后将边界框与对象一起定位,最后,将为每个类别预测逐像素掩码。 对于每个对象,都将计算像素级精度。 有几种用于分割实例的算法。 最近的算法之一是 He 等人提出的 Mask RCNN 算法。 下图描绘了 Mask R-CNN 的架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRmnkb8X-1681567519378)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/608bc4e7-8b4b-46b9-98d9-1626226bc859.png)]

经 He 等人许可复制。

该架构看起来与 R-CNN 类似,但增加了分段。 这是一个具有端到端训练的多阶段网络。 学习了区域提案。 该网络分为两个部分,一个用于检测,另一个用于分类评分。 结果非常好,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqEcEyx1-1681567519379)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-cv/img/a0697d7c-9d03-46fd-9ae2-ff00ff587825.png)]

该图说明了分割实例的过程,请注意,准确地检测了对象并进行了相应的分割。

同一网络还可以预测人的姿势。 分割和检测这两个任务是并行处理的。

总结

在本章中,我们学习了各种分割算法。 我们还看到了用于基准测试的数据集和指标。 我们将学到的技术应用于卫星和医学图像的分割。 最后,我们谈到了 Mask R-CNN 算法的实例分割。

在下一章中,我们将学习相似性学习。 相似性学习模型学习两个图像之间的比较机制。 对于人脸识别等多种应用很有用。 我们将学习几种可用于相似性学习的模型架构。

0 人点赞