【DL】第 2 章:了解卷积网络

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

文章目录

了解卷积网络

了解 CNN

卷积类型

转置卷积

1×1 卷积

深度可分离卷积

空洞卷积

提高 CNN 的效率

卷积作为矩阵乘法

Winograd 卷积

可视化 CNN

引导反向传播

梯度加权类激活映射

CNN正则化

介绍迁移学习

使用 PyTorch 实现迁移学习

使用 TensorFlow 2.0 进行迁移学习

概括


本节将讨论计算机视觉领域中的深度学习DL ) 应用。我们将讨论卷积网络、对象检测和图像分割、生成模型 (GAN)和神经风格迁移。

了解卷积网络

在本章中,我们将讨论卷积神经网络CNN ) 及其在计算机视觉CV ) 中的应用。CNN 开启了现代深度学习革命。它们是几乎所有最近的 CV 进步的基础,包括生成对抗网络GAN )、对象检测、图像分割、神经风格迁移等等。出于这个原因,我们认为 CNN 值得深入了解,这超出了我们对它们的基本理解。

为此,我们将从CNN构建块的简短回顾开始,即卷积层和池化层。我们将讨论今天使用的各种类型的卷积,因为它们反映在大量的 CNN 应用程序中。我们还将学习如何可视化 CNN 的内部状态。然后,我们将专注于正则化技术并实现一个迁移学习示例。

本章将涵盖以下主题:

  • 了解 CNN
  • 介绍迁移学习

了解 CNN

第 1 章神经网络的基本要素中,我们 讨论了许多 NN 运算具有坚实的数学基础,卷积也不例外。让我们从定义数学卷积开始:

在这里,我们有以下内容:

  • 卷积运算用*表示。
  • f 和g是具有共同参数t的两个函数。
  • 卷积的结果是第三个函数s(t) (不仅仅是单个值)。

fgt值处的卷积是f(t)与g(t-τ)的反向(镜像)和移位值的乘积的积分,其中t-τ表示移位。也就是说,对于时间t的单个f值,我们将g在范围内移动,并且由于积分,我们连续计算乘积f(t) g(t-τ) 。积分(以及卷积)等价于两个函数乘积的曲线下面积。

下图最好地说明了这一点:

左:卷积,其中g被移位和反转;右图:卷积操作的逐步说明

在卷积运算中,为了保持运算的交换性, g被移位和反转。在 CNN 的上下文中,我们可以忽略这个属性,我们可以在不反转g的情况下实现它。在这种情况下,该操作称为互相关。这两个术语可以互换使用。

我们可以使用以下公式定义t的离散(整数)值的卷积(这与连续情况非常相似):

 我们还可以将其推广到具有两个共享输入参数ij的函数的卷积:

对于三个参数,我们可以以类似的方式推导出公式。

在 CNN 中,函数f是卷积操作(也称为卷积层)的输入。根据输入维度的数量,我们有 1D、2D 或 3D 卷积。- 时间序列输入是 1D 向量,图像输入是 2D 矩阵,3D 点云是 3D 张量。另一方面,函数g称为内核(或过滤器)。它具有与输入数据相同的维数,并由一组可学习的权重定义。例如,用于 2D 卷积的大小为n的滤波器是一个n×n矩阵。 下图说明了在单个3×3 切片上应用2×2 滤波器的 2D 卷积:

在单个3×3 切片上应用2×2 滤波器的 2D 卷积

卷积的工作原理如下:

  1. 我们沿着输入张量的所有维度滑动过滤器。
  2. 在每个输入位置,我们将每个滤波器权重乘以给定位置的相应输入张量单元。构成单个输出单元的输入单元称为感受野。我们将所有这些值相加以产生单个输出单元格的值。

与每个输出单元从所有输入收集信息的全连接层不同,卷积输出单元的激活由其感受野中的输入决定。这一原则最适用于分层结构的数据,例如图像。例如,相邻像素形成有意义的形状和对象,但图像一端的像素不太可能与另一端的像素有关系。使用全连接层将所有输入像素与每个输出单元连接起来,就像要求网络大海捞针一样。它无法知道输入像素是否在输出单元的感受野中。

过滤器突出了感受野中的一些特定特征。该操作的输出是张量(称为特征图),它标记了检测到特征的位置。由于我们在整个输入张量中应用相同的滤波器,因此卷积是平移不变的;也就是说,它可以检测到相同的特征,而不管它们在图像上的位置。然而,卷积既不是旋转不变的(如果它被旋转,它不能保证检测到一个特征),也不是尺度不变的(它不能保证在不同的尺度上检测到相同的伪影)。

在下图中,我们可以看到 1D 和 3D 卷积的示例(我们已经介绍了 2D 卷积的示例):

一维卷积:过滤器(用多色线表示)在单个轴上滑动;3D 卷积:滤波器(用虚线表示)在三个轴上滑动

CNN 卷积可以有多个过滤器,突出显示不同的特征,从而产生多个输出特征图(每个过滤器一个)。它还可以从多个特征图收集输入,例如,先前卷积的输出。特征图(输入或输出)的组合称为体积。在这种情况下,我们也可以将特征图称为切片。尽管这两个术语指的是同一事物,但我们可以将切片视为体积的一部分,而特征图则突出了其作为特征图的作用。

正如我们在本节前面提到的,每个体积(以及过滤器)都由一个张量表示。例如,红色、绿色和蓝色 (RGB) 图像由三个 2D 切片(每个颜色通道一个切片)的 3D 张量表示。但是在 CNN 的上下文中,我们为 mini-batch 中的样本索引增加了一个维度。在这里,1D 卷积将具有 3D 输入和输出张量。它们的轴可以是NCWNWC顺序,其中N是小批量中样本的索引,C是体积中深度切片的索引,W是每个样本的向量大小。同理,一个 2D 卷积将由NCHWNHWC表示 张量,其中 H 和 W 是切片的高度和宽度。3D 卷积将具有 NCLHW 或 NLHWC 顺序,其中 L 代表切片的深度。

我们使用 2D 卷积来处理 RGB 图像。但是,我们可以将这三种颜色视为一个附加维度,从而使 RGB 图像具有 3D 效果。那么,为什么我们不使用 3D 卷积呢?这样做的原因是,即使我们可以将输入视为 3D,但输出仍然是 2D 网格。如果我们使用 3D 卷积,输出也将是 3D,这在 2D 图像的情况下没有任何意义。

假设我们有n 个输入和m个输出切片。在这种情况下,我们将在n 个输入切片的集合中应用m个过滤器。每个过滤器将生成一个独特的输出切片,突出显示过滤器检测到的特征(nm关系)。

根据输入和输出切片的关系,我们得到跨通道和深度卷积,如下图所示:

                         左:跨通道卷积;右:深度卷积

让我们讨论它们的属性:

  • 跨通道卷积:一个输出切片接收来自所有输入切片的输入(n对一关系)。对于多个输出切片,关系变为nm。换句话说,每个输入切片对每个输出切片的输出都有贡献。每对输入/输出切片使用该对唯一的单独过滤器切片。让我们用F宽度和高度相等C in表示输入体积C out表示输出体积的深度。有了这个,我们可以计算权重的总数W, 在具有以下等式的 2D 卷积中:

                                        

 这里,+1 表示每个过滤器的偏置权重。假设我们有三个切片,并且想要对它们应用四个 5 × 5 过滤器。如果我们这样做,卷积滤波器将总共有(3*5*5 + 1) * 4 = 304个权重、四个输出切片(深度为 4 的输出体积)和每个切片一个偏差。每个输出切片的过滤器将为三个输入切片中的每一个提供三个 5 × 5 的过滤器补丁和一个偏置,总共 3*5*5 + 1 = 76 个权重。

  • 深度卷积:每个输出切片从单个输入切片接收输入。这是对前一种情况的一种逆转 。在最简单的形式中,我们在单个输入切片上应用过滤器以生成单个输出切片。在这种情况下,输入和输出体积具有相同的深度,即C。我们还可以指定一个通道乘数 (一个整数,m),我们在单个输出切片上应用m个过滤器以产生m个输出切片。这是一个一对一关系的例子。在这种情况下,输出切片的总数为n * m。我们可以计算权重的数量W ,在具有以下公式的 2D 深度卷积中:

​​​​​​​​​​​​​​        ​​​​​​​                                ​​​​​​​

 这里,m是通道乘数,+C表示每个输出切片的偏差。

卷积操作也由另外两个参数来描述:

  • S步幅 是我们在每一步中在输入切片上滑动过滤器的位置数。默认情况下,步幅为 1。如果大于 1,则我们称其为 步幅卷积。最大的步幅增加了输出神经元的感受野。步幅为 2 时,输出切片的大小将大约比输入小四倍。换句话说,一个输出神经元将覆盖该区域,与 stride 1 卷积相比,该区域大四倍。随后层中的神经元将逐渐从输入图像的较大区域捕获输入。
  • 在卷积操作之前用零的行和列填充输入切片的边缘。使用填充最常见的方法是生成与输入具有相同尺寸的输出。新填充的零将参与与切片的卷积操作,但不会影响结果。

知道输入维度和滤波器大小,我们可以计算输出切片的维度。假设输入切片的大小是I(高度和宽度相等),过滤器的大小是F,步幅是S,填充是P。在这里,输出切片的大小O由以下等式给出:

 除了步幅卷积,我们还可以使用化操作来增加更深层神经元的感受野并减小未来切片的大小。池化将输入切片分割成一个网格,其中每个网格单元代表多个神经元的感受野(就像它对卷积所做的那样)。然后,对网格的每个单元应用池化操作。与卷积类似,池化由步幅S和感受野的大小F来描述。如果输入切片的大小为I,则池化的输出大小公式如下:

        ​​​​​​​        ​​​​​​​        ​​​​​​​       

 实际上,只使用了两种组合。第一个是步长为 2 的 2 × 2 感受野,而第二个是步长为 2(重叠)的 3 × 3 感受野。最常见的池化操作如下:

  • Max pooling:传播感受野输入值的最大值。
  • 平均池化:这会传播感受野中输入的平均值。
  • 全局平均池化(GAP):这与平均池化相同,但池化区域的大小与特征图相同,I×I。GAP 执行一种极端类型的降维:输出是单个标量,表示整个特征图的平均值。

通常,我们会将一个或多个卷积层与一个池化(或跨步卷积)层交替使用。通过这种方式,卷积层可以检测感受野大小的每一层的特征,因为更深层的聚合感受野大小大于网络开始时的感受野大小。与初始层相比,更深的层也有更多的过滤器(因此,体积深度更高)。网络开头的特征检测器工作在一个小的感受野上。它只能检测有限数量的特征,例如边缘或线条,这些特征在所有类之间共享。

另一方面,更深的层会检测到更复杂和更多的特征。例如,如果我们有多个类别,例如汽车、树木或人,每个类别都会有自己的一组特征,例如轮胎、门、树叶和面孔。这将需要更多的特征检测器。通过添加一个或多个全连接层,最终卷积(或池化)的输出被“翻译”为目标标签。

现在我们已经对卷积、池化操作和 CNN 有了一个概述,在下一节中,我们将关注不同类型的卷积操作。

卷积类型

到目前为止,我们已经讨论了最常见的卷积类型。在接下来的部分中,我们将讨论它的一些变体。

转置卷积

在我们到目前为止讨论的卷积运算中,输出维度要么等于或小于输入维度。相比之下,转置卷积(首先由 Matthew D. Zeiler、Dilip Krishnan、Graham W. Taylor 和 Rob Fergus在Deconvolutional Networks中提出: https ://www.matthewzeiler.com/mattzeiler/deconvolutionalnetworks.pdf )允许我们对输入数据(它们的输出大于输入)。此操作也称为反卷积分数步幅卷积亚像素卷积. 这些名称有时会导致混淆。为了澄清事情,请注意转置卷积实际上是一个带有稍微修改的输入切片或卷积滤波器的常规卷积。

对于更长的解释,我们将从单个输入和输出切片上的一维常规卷积开始:

一维正则卷积

它使用大小为 4、步幅为 2 和填充为 2 的过滤器(在上图中用灰色表示)。输入是一个大小为 6 的向量,输出是一个大小为 4 的向量。过滤器,一个向量f = [1, 2, 3, 4],总是相同的,但是我们用不同的颜色表示每个位置应用到。相应的输出单元用相同的颜色表示。箭头显示了哪些输入单元对一个输出单元有贡献。

本节讨论的示例受论文启发;反卷积层和卷积层一样吗?https://arxiv.org/abs/1609.07009)。

接下来,我们将讨论相同的示例(一维,单个输入和输出切片,大小为 4 的过滤器,填充 2 和步长 2),但用于转置卷积。下图显示了我们可以实现它的两种方式:

 左:步长为 2 的卷积,应用转置滤波器f。输出开始和结束的2个像素被裁剪;右图:步幅为 0.5 的卷积,应用于输入数据,并用子像素填充。输入用 0 值像素(灰色)填充。

让我们详细讨论它们:

  • 在第一种情况下,我们有一个步长为 2 的常规卷积和一个表示为大小为 4 的转置行矩阵(相当于列矩阵)的滤波器:(如上图所示,左)。请注意,跨步应用于输出层,而不是常规卷积,我们在输入层上跨步。通过将步幅设置为大于 1,与输入相比,我们可以增加输出大小。这里,输入切片的大小是,滤波器的大小是,步幅是,输入填充是。因此,转置卷积的输出切片的大小O以下公式给出:

            在这种情况下,大小为 4 的输入产生大小为2*(4 - 1) + 4 - 2*2 = 6的输出。我们还在输出向              量的开头和结尾裁剪了两个单元格,因为它们只收集来自单个输入单元格的输入。

  • 在第二种情况下,输入填充了现有子像素之间的虚构 0 值子像素(如上图所示,右图)。这就是亚像素卷积这个名字的由来。 将其视为填充,但在图像本身内,而不仅仅是沿边界。一旦以这种方式对输入进行转换,就会应用常规卷积。

让我们比较两种情况下的两个输出单元1 和3。如上图所示,在任何一种情况下,1从第一个和第二个输入单元接收输入,3从第二和第三个单元接收输入。实际上,这两种情况的唯一区别就是权重的指标,它参与了计算。但是,权重是在训练期间学习的,因此,索引并不重要。因此,这两个操作是等价的。

接下来,让我们从亚像素的角度来看一个 2D 转置卷积(输入在底部)。与一维情况一样,我们在输入切片中插入 0 值像素和填充以实现上采样:

具有填充 1 和步幅 2 的 2D 转置卷积的前三个步骤:来源: https ://github.com/vdumoulin/conv_arithmetic,https: //arxiv.org/abs/1603.07285

正则卷积的反向传播操作是转置卷积。


1×1 卷积

× 1(或逐点)卷积是卷积的一种特殊情况,其中卷积滤波器的每个维度的大小为 1(2D 卷积中为 1×1,3D 中为 1 × 1 × 1)。起初,这是没有意义的——一个 1 × 1 的过滤器不会增加输出神经元的感受野大小。这种卷积的结果 将是逐点缩放。但它可能以另一种方式有用——我们可以使用它们来改变输入和输出体积之间的深度。

为了理解这一点,让我们回想一下,一般来说,我们有一个深度为D个切片的输入体和用于M个输出切片的M个过滤器。每个输出切片是通过对所有输入切片应用唯一的过滤器来生成的。如果我们使用 1 × 1 过滤器和D != M,我们将有相同大小的输出切片,但具有不同的体积深度。同时,我们不会改变输入和输出之间的感受野大小。最常见的用例是减少输出量,或D > M(降维),绰号为“瓶颈”层。

深度可分离卷积

跨通道卷积中的输出切片使用单个滤波器从所有输入切片接收输入。过滤器尝试学习 3D 空间中的特征,其中两个维度是空间维度(切片的高度和宽度),第三个维度是通道。因此,滤波器映射空间和跨通道相关性。

深度可分离卷积 (DSCXception: Deep Learning with Depthwise Separable Convolutionshttps ://arxiv.org/abs/1610.02357 )可以完全解耦跨通道和空间相关性。DSC 结合了两种操作:深度卷积和 1 × 1 卷积。在深度卷积中,单个输入切片产生单个输出切片,因此它只映射空间(而不是跨通道)相关性。对于 1 × 1 卷积,我们的情况正好相反。下图表示 DSC:

深度可分离卷积

DSC 通常在第一次(深度方向)操作后实现,没有非线性。

让我们比较标准卷积和深度可分离卷积。想象一下,我们有 32 个输入和输出通道和一个大小为 3 × 3 的滤波器。在标准卷积中,一个输出切片是对32 个输入切片中的每一个应用一个滤波器的结果,总共32 * 3 * 3 = 288 个权重(不包括偏差)。在可比较的深度卷积中,滤波器只有3 * 3 = 9权重,而 1 × 1 卷积的滤波器有32 * 1 * 1 = 32权重。权重总数为32 + 9 = 41。因此,与标准卷积相比,深度可分离卷积更快,内存效率更高。

空洞卷积

回想一下我们在快速回顾 CNN部分开头介绍的离散卷积公式。为了解释扩张卷积(Multi-Scale Context Aggregation by Dilated Convolutionshttps: //arxiv.org/abs/1511.07122 ),让我们从以下公式开始:

我们将用* l表示扩张卷积,其中l是一个正整数值,称为扩张因子。关键是我们在输入上应用过滤器的方式。我们不是在n×n感受野上应用n×n过滤器,而是在大小为(n*l-1)× (n*l-1)的感受野上稀疏地应用相同的过滤器。我们仍然将每个过滤器权重乘以一个输入切片单元,但这些单元之间的距离为l。常规卷积是l=1的扩张卷积的一种特殊情况。下图最好地说明了这一点:

扩张因子为 l=2 的扩张卷积:这里显示了操作的前两个步骤。底层是输入,顶层是输出。来源:https://github.com/vdumoulin/conv_arithmetic

空洞卷积可以以指数方式增加感受野大小,而不会损失分辨率或覆盖范围。我们还可以通过跨步卷积或池化来增加感受野,但代价是分辨率和/或覆盖率。为了理解这一点,让我们假设我们有一个步幅卷积,步幅为s>1。在这种情况下,输出切片比输入切片小s倍(分辨率损失)。如果我们进一步增加s>n ( n 是池化或卷积核的大小),我们会丢失覆盖,因为输入切片的某些区域根本不会参与输出。此外,扩张卷积不会增加计算和内存成本,因为过滤器使用与常规卷积相同数量的权重。

提高 CNN 的效率

最近深度学习 ( DL )取得进展的主要原因之一是它能够非常快速地运行神经网络( NN)。这在很大程度上是因为 NN 算法的性质与图形处理单元GPU ) 的特性之间的良好匹配。在 第 1 章中,神经网络的基本要素,我们强调了矩阵乘法在神经网络中的重要性。作为对此的证明,也可以将卷积转换为矩阵乘法。矩阵乘法是令人尴尬的并行(相信我,这是一个术语——你可以谷歌它!)。每个输出单元的计算与任何其他输出单元的计算无关。因此,我们可以并行计算所有输出。

并非巧合的是,GPU 非常适合像这样的高度并行操作。一方面,与中央处理器CPU )相比,GPU 具有大量的计算核心。即使 GPU 内核比 CPU 内核快,我们仍然可以并行计算更多的输出单元。但更重要的是,GPU 针对内存带宽进行了优化,而 CPU 针对延迟进行了优化。这意味着 CPU 可以非常快速地获取小块内存,但在获取大块时会很慢。GPU则相反。正因为如此,在神经网络的大型矩阵乘法等任务中,GPU 具有优势。

除了硬件细节外,我们还可以在算法方面优化 CNN。CNN 中的大部分计算时间都用于卷积本身。尽管卷积的实现很简单,但在实践中,有更有效的算法可以达到相同的结果。尽管 TensorFlow 或 PyTorch 等当代 DL 库使开发人员免受此类细节的影响,但在本书中,我们的目标是更深入地(双关语)理解 DL。

因此,在下一节中,我们将讨论两种最流行的快速卷积算法。

卷积作为矩阵乘法

在本节中,我们将描述用于将卷积转换为矩阵乘法的算法,就像它在 cuDNN 库中的实现方式一样(cuDNN: Efficient Primitives for Deep Learning , https://arxiv.org/abs/1410.0759) . 为了理解这一点,假设我们在 RGB 输入图像上执行跨通道 2D 卷积。卷积的参数我们看下表:

范围符号价值
小批量大小ñ1
输入特征图(体积深度)C3 个(每个 RGB 通道一个)
输入图像高度H4
输入图像宽度4
输出特征图(体积深度)ķ2
过滤器高度R2
过滤器宽度小号2
输出特征图高度2(基于输入/过滤器大小)
输出特征图宽度2(基于输入/过滤器大小)

为了简单起见,我们假设我们有零填充和步幅 1。我们将用D表示输入张量,用F表示卷积滤波器张量。矩阵卷积的工作方式如下:

  1. 我们将张量DF分别展开到矩阵中。
  2. 然后,我们将矩阵相乘得到输出矩阵

我们在第 1 章神经网络的基本要素”中讨论了矩阵乘法。现在,让我们关注在矩阵中展开张量的方式。下图显示了如何执行此操作:

                         卷积作为矩阵乘法;灵感来自 https://arxiv.org/abs/1410.0759

每个特征图都有不同的颜色(R、G、B)。在常规卷积中,滤波器具有方形形状,我们将其应用于方形输入区域。在转换中,我们将 D 的每个可能的正方形区域展开为 D m一列。然后,我们将F的每个正方形分量展开为m的一行。这样,每个输出单元的输入和过滤数据位于矩阵mm的单个列/行中。这使得可以将输出值计算为矩阵乘法。变换后的输入/过滤器/输出的维度如下:

  • 暗淡( m ) = CRS × NPQ = 12 × 4
  • 暗淡( m ) = K × CRS = 2 × 12
  • 暗淡( m ) = K × NPQ = 2 × 4

为了理解这种转换,让我们学习如何使用常规卷积算法计算第一个输出单元:

  接下来,让我们观察相同的公式,但这次是矩阵乘法形式:

如果我们比较两个方程的分量,我们会发现它们完全相同。即D [0, 0, 0, 0] = m [0,0], [0, 0, 0, 0] = F m [0,0], D [0,0,0,1 ] = m [0,1],F [0,0,0,1] = m [0,1],依此类推。我们可以对其余的输出单元做同样的事情。因此,两种方法的输出是相同的。

矩阵卷积的一个缺点是增加了内存使用量。在上图中,我们可以看到一些输入元素重复了多次(最多 RS = 4 次,如 D4)。

Winograd 卷积

与直接卷积相比,Winograd 算法(卷积神经网络的快速算法https://arxiv.org/abs/1509.09308)可以提供 2 或 3倍的加速。为了解释这一点,我们将使用在卷积中使用的相同符号作为矩阵乘法部分,但使用3×3 ( R=S=3 ) 滤波器。我们还将假设输入切片大于4×4 ( H>4, W>4 )。

以下是计算 Winograd 卷积的方法:

将输入图像划分为与步幅为 2 重叠的 4 × 4 瓦片,如下图所示:

                                         输入被分成瓦片

瓦片大小可以变化,但为了简单起见,我们只关注 4 × 4 瓦片。

  1. 使用以下两个矩阵乘法转换每个图块:

 在前面的公式中,矩阵D是输入切片(带有圆形值的那个),而B是一个特殊的矩阵,这是由 Winograd 算法的细节产生的(您可以在开头链接的论文中找到有关它们的更多信息本节)。

  1. 使用以下两个矩阵乘法转换过滤器:

 在前面的公式中,矩阵(具有点值的矩阵)是一个输入和一个输出切片之间的 3 × 3 卷积滤波器。G 和它的转置 , , 也是特殊的矩阵,它们是由 Winograd 算法的细节产生的。请注意,变换后的滤波器矩阵t与输入图块D t具有相同的维度。

  1. 将转换后的输出计算为转换后的输入和过滤器的元素乘法( 符号) :

 

  1. 将输出转换回其原始形式:

 A是一个转换矩阵,它可以转换回直接卷积产生的形式。如上式和下图所示,Winograd 卷积允许我们同时计算 2 × 2 输出图块(四个输出单元):

                         Winograd卷积允许我们同时计算四个输出单元

乍一看,Winograd 算法似乎比直接卷积执行了更多的操作。那么,如何更快呢?为了找出答案,让我们关注转型。这里的关键是我们只需要执行一次,然后D就可以参与所有K(按照符号)输出切片的输出。因此,在所有输出中摊销,它不会对性能产生太大影响。接下来,我们来看看改造。这个更好,因为一旦我们计算出t,我们就可以应用它N×P×Q 次(跨越输出切片的所有单元格和批次中的所有图像)。因此,这种转换的性能损失可以忽略不计。类似地,输出转换在输入通道 C 的数量上摊销。

 最后,我们将讨论逐元素乘法 ,它在输出切片的所有单元格上应用P×Q次,并占用大部分计算时间。它由 16 个标量乘法运算组成,并允许我们计算 2 × 2 输出图块,这导致一个输出单元的四次乘法。让我们将其与直接卷积进行比较,在直接卷积中,我们必须为单个输出执行3*3=9标量乘法(每个滤波器元素乘以每个感受野输入单元)。因此,Winograd 卷积需要9/4 = 2.25少的操作。

Winograd 卷积在使用较小的过滤器尺寸(例如,3 × 3)时具有最大的优势。具有较大滤波器(例如 11 × 11)的卷积可以通过快速傅里叶变换 (FFT) 卷积有效实现,这超出了本书的范围。

在下一节中,我们将尝试通过可视化它们的内部状态来理解 CNN 的内部工作原理。

可视化 CNN

对神经网络的批评之一是它们的结果不可解释。通常将 NN 视为一个黑匣子,其内部逻辑对我们来说是隐藏的。这可能是一个严重的问题。一方面,我们不太可能相信以我们不理解的方式工作的算法,而另一方面,如果我们不知道它们是如何工作的,就很难提高 CNN 的准确性。因此,在接下来的部分中,我们将讨论两种可视化 CNN 内部层的方法,这两种方法都将帮助我们深入了解它们的学习方式。

引导反向传播

引导式反向传播(Striving for Simplicity: The All Convolutional Net , https://arxiv.org/abs/1412.6806)允许我们可视化由 CNN 的一层的单个单元学习的特征。下图显示了该算法的工作原理:

引导反向传播可视化;灵感来自 https://arxiv.org/abs/1412.6806。

下面是分步执行:

  1. 首先,我们从带有 ReLU 激活的常规 CNN(例如,AlexNet、VGG 等)开始。
  2. 然后,我们向网络输入单个图像(0)并将其向前传播,直到到达我们感兴趣的层l。这可以是任何网络层——隐藏层或输出层,卷积层或全连接层。
  3. 将该层的输出张量f (l) 的除一个激活之外的所有激活设置为 0。例如,如果我们对分类网络的输出层感兴趣,我们将选择激活最大的单元(相当于预测class) 并且我们将其值设置为 1。所有其他单位将设置为 0。通过这样做,我们可以隔离有问题的单位,并查看输入图像的哪些部分对其影响最大。
  4. 最后,我们向后传播所选单元的激活值,直到我们到达输入层和重建图像R (0)。反向传播与常规反向传播非常相似(但不一样),即我们仍然使用转置卷积作为前向卷积的反向操作。但是,在这种情况下,我们感兴趣的是它的图像恢复属性而不是错误传播。正因为如此,我们不受传播一阶导数(梯度)的要求的限制,我们可以以改善可视化的方式修改信号。

为了理解反向传播,我们将使用一个带有单个 3 × 3 输入和输出切片的卷积示例。假设我们使用一个 1 × 1 的过滤器,其单个权重等于 1(我们重复输入)。下图显示了这种卷积,以及实现反向传播的三种不同方式:

卷积和三种不同的图像重建方式;灵感来自https://arxiv.org/abs/1412.6806。

让我们详细讨论这三种不同的方式来实现反向传递:

  • 常规反向传播:反向信号在输入图像上进行预处理,因为它还取决于前向激活(第 1 章神经网络的基本要素,在反向传播部分)。我们的网络使用 ReLU 激活函数,因此信号只会通过在前向传递中具有正激活的单元。
  • 反卷积网络deconvnethttps ://arxiv.org/abs/1311.2901 ):第 l 层后向信号仅依赖于第l+1层的后向信号。deconvnet 只会将l+1的正值路由到l,而不管前向激活是什么。理论上,信号根本不以输入图像为前提。在这种情况下,反卷积网络尝试根据其内部知识和图像类来恢复图像。然而,这并不完全正确——如果网络包含最大池化层,反卷积网络将存储所谓的开关对于每个池化层。每个开关代表具有最大前向传递激活的单元的映射。该映射决定了如何通过反向传递来路由信号(您可以在源文件中阅读更多相关信息)。
  • 引导反向传播:这是 deconvnet 和常规反向传播的组合。它只会路由在 l 中具有正前向激活和在 l+1 中具有正反向激活信号。这将来自更高层的额外引导信号(因此得名)添加到常规反向传播。从本质上讲,这一步可以防止负梯度流过反向通道。基本原理是作为我们起始单元的抑制器的单元将被阻挡,重建的图像将不受它们的影响。引导反向传播执行得非常好,它不需要使用 deconvnet 开关,而是将信号路由到每个池化区域中的所有单元。

以下屏幕截图显示了使用引导反向传播和 AlexNet 生成的重建图像:

从左到右:在 AlexNet 上使用引导反向传播的原始图像、颜色重建和灰度重建;这些图像是使用 https://github.com/utkuozbulak/pytorch-cnn-visualizations 生成的。

梯度加权类激活映射

为了理解梯度加权类激活映射(Grad-CAM: Visual Explanations from Deep Networks via Gradient-Based Localizationhttps://arxiv.org/abs/1610.02391),让我们引用源文件本身:

“Grad-CAM 使用流入最终卷积层的任何目标概念的梯度(例如,‘狗’或什至字幕的逻辑)来生成粗略的定位图,突出图像中的重要区域以预测概念。”

以下屏幕截图显示了Grad-CAM 算法:

Grad-CAM 模式;来源:https://arxiv.org/abs/1610.02391

现在,让我们看看它是如何工作的:

首先,您从分类 CNN 模型(例如,VGG)开始。

然后,您向 CNN 提供单个图像并将其传播到输出层。

就像我们在引导反向传播中所做的那样,我们采用具有最大激活的输出单元(相当于预测的类c),将其值设置为 1,并将所有其他输出设置为 0。换句话说,创建一个 one-hot 编码预测的向量y c

  1. 接下来,使用反向传播计算y c相对于最终卷积层的特征图k的梯度。ij是特征图中的单元坐标。
  2. 然后,计算标量权重 ,它衡量特征图k对预测类别c的“重要性” :
  1. 最后,计算标量权重和最终卷积层的前向激活特征图之间的加权组合,然后使用 ReLU:

请注意,我们将标量重要性权重 乘以张量特征图A k。结果是一个与特征图具有相同尺寸的热图(在 VGG 和 AlexNet 的情况下为14 × 14)。它将突出显示对c类具有最高重要性的特征图区域。ReLU 丢弃了负激活,因为我们只对增加y c的特征感兴趣。我们可以将此热图上采样回输入图像的大小,然后将其叠加在其上,如下面的屏幕截图所示:

从左到右:输入图像;上采样热图;叠加在输入(RGB)上的热图;灰度热图。这些图像是使用https://github.com/utkuozbulak/pytorch-cnn-visualizations生成的。

Grad-CAM 的一个问题是将热图从 14 × 14 上采样到 224 × 224,因为它没有为每个类提供重要特征的细粒度视角。为了缓解这个问题,论文的作者提出了 Grad-CAM 和引导反向传播的组合(在本节开头的 Grad-CAM 模式中显示)。我们采用上采样热图并将其与引导反向传播可视化和元素乘法相结合。输入图像包含两个对象:狗和猫。因此,我们可以使用两个类(图表的两行)运行 Grad-CAM。这个例子展示了不同的类如何在同一张图像中检测不同的相关特征。

在下一节中,我们将讨论如何借助正则化来优化 CNN。

CNN正则化

正如我们在第 1 章“神经网络的基本要素”中所讨论的,NN 可以逼近任何函数。但权力越大,责任越大。NN 可以学习逼近目标函数的噪声而不是其有用的分量。例如,假设我们正在训练一个神经网络来分类图像是否包含汽车,但由于某种原因,训练集主要包含红色汽车。事实证明,NN 会将红色与汽车相关联,而不是与汽车的形状相关联。现在,如果网络在推理模式下看到一辆绿色汽车,它可能无法识别它,因为颜色不匹配。这个问题被称为过度拟合,它是机器学习的核心(在深度网络中更是如此)。 在本节中,我们将讨论几种防止它的方法。这些技术统称 为正则化

在 NN 的上下文中,这些正则化技术通常会对训练过程施加一些人为的限制或障碍,以防止网络过于接近目标函数。他们试图引导网络学习目标函数的通用而不是特定的近似值,希望这种表示能够很好地概括测试数据集以前看不见的例子。您可能已经熟悉其中的许多技术,所以我们将保持简短:

  • 输入特征缩放:。此操作缩放 [0, 1] 范围内的所有输入。例如,强度为 125 的像素的缩放值为. 特征缩放快速且易于实现。  
  • 输入标准分数:。其中,μ 和 σ 是所有训练数据的均值和标准差。它们通常针对每个输入维度单独计算。例如,在 RGB 图像中,我们将计算每个通道的均值μσ。我们应该注意,必须在训练数据上计算μσ,然后将其应用于测试数据。或者,如果在整个数据集上计算它们不切实际,我们可以计算每个样本的μσ 。 
  • 数据增强:这是我们在将训练样本输入网络之前通过对训练样本应用随机修改(旋转、倾斜、缩放等)来人工增加训练集大小的地方。
  • L2 正则化(或权重衰减):在这里,我们在成本函数中添加了一个特殊的正则化项。假设我们正在使用 MSE(第 1 章神经网络的基本要素梯度下降部分)。这里,MSE+L2正则化公式如下:

这里,w jk个总网络权重之一,λ是权重衰减系数。基本原理是,如果网络权重w j很大,那么成本函数也会增加。实际上,权重衰减会惩罚大权重(因此得名)。这可以防止网络过度依赖与这些权重相关的一些特征。当网络被迫使用多个特征时,过度拟合的可能性较小。实际上,当我们计算权重衰减成本函数(上式)对每个权重的导数,然后将其传播到权重本身时,权重更新规则从 

变为。

  • Dropout:在这里,我们从网络中随机并定期删除一些神经元(以及它们的输入和输出连接)。在小批量训练期间,每个神经元都有随机丢弃的概率p。这是为了确保没有神经元最终过度依赖其他神经元,而是“学习”对网络有用的东西。
  • 批量归一化BN批量归一化:通过减少内部协变量偏移来加速深度网络训练https ://arxiv.org/abs/1502.03167 ):这是一种对隐藏层应用数据处理的方法,类似于标准分数的网络。它对每个小批量(因此得名)的隐藏层的输出进行归一化,使其平均激活值接近 0,标准差接近 1。假设是一个大小为n的小批量。D的每个样本都是一个向量,,并且是一个具有该向量的索引k的单元格。为了清楚起见,我们将省略 (k ) 下列公式中的上标;也就是说,我们会写x i,但我们的意思是。我们可以通过以下方式计算整个小批量中每个激活k的 BN:
    1. : 这是小批量的平均值。我们对所有样本的每个位置k分别计算μ 。
    2. :这是小批量标准差。我们对所有样本的每个位置k分别计算σ 。
    3. :我们对每个样本进行标准化。ε是为数值稳定性而添加的常数。
    4. γβ是可学习的参数,我们在 mini-batch 的所有样本的每个位置k ( γ (k)β (k) ) 上计算它们(同样适用于μσ )。在卷积层中,每个样本x是具有多个特征图的张量。为了保持卷积特性,我们计算所有样本的每个位置的μσ ,但我们在所有特征图的匹配位置中使用相同的μσ 。另一方面,我们计算γβ 每个特征图,而不是每个位置。

本节总结了我们对 CNN 的结构和内部工作原理的分析。此时,我们通常会继续进行某种 CNN 编码示例。但在本书中,我们想做一些不同的事情。因此,我们不会实现您以前可能已经做过的普通旧的前馈 CNN。相反,在下一节中,您将了解迁移学习技术——一种将预训练的 CNN 模型用于新任务的方法。但别担心——我们仍然会从头开始实现 CNN。我们将在第 3 章高级卷积网络中进行此操作。通过这种方式,我们将能够使用我们在该章中获得的知识来创建更复杂的网络架构。

介绍迁移学习

假设我们想要在一项任务上训练一个模型,该任务没有像 ImageNet 这样的现成可用的标记训练数据。标记训练样本可能会很昂贵、耗时且容易出错。那么,当一个不起眼的工程师想要用有限的资源解决一个真正的机器学习问题时,他们会怎么做呢?输入迁移学习TL )。

TL 是将现有训练的 ML 模型应用于新的但相关的问题的过程。例如,我们可以采用在 ImageNet 上训练的网络并将其重新用于对杂货店商品进行分类。或者,我们可以使用一个驾驶模拟器游戏来训练一个神经网络来驾驶一辆模拟汽车,然后使用该网络来驾驶一辆真正的汽车(但不要在家里尝试这个!)。TL 是适用于所有 ML 算法的通用 ML 概念,但在这种情况下,我们将讨论 CNN。这是它的工作原理。

我们从现有的预训练网络开始。最常见的场景是从 ImageNet 中获取预训练的网络,但它可以是任何数据集。TensorFlow 和 PyTorch 都有我们可以使用的流行的 ImageNet 预训练神经架构。或者,我们可以使用我们选择的数据集来训练我们自己的网络。

CNN 末端的全连接层充当网络语言(在训练期间学习的抽象特征表示)和我们的语言(每个样本的类)之间的翻译器。您可以将 TL 视为另一种语言的翻译。我们从网络的特征开始,它是最后一个卷积或池化层的输出。然后,我们将它们翻译成新任务的一组不同的类。我们可以通过移除现有预训练网络的最后一个全连接层(或所有全连接层)并将其替换为代表新问题类别的另一层来做到这一点。

让我们看一下下图所示的 TL 场景:

在 TL 中,我们可以替换预训练网络的全连接层并将其重新用于新问题

但是,我们不能机械地这样做并期望新网络能够工作,因为我们仍然必须使用与新任务相关的数据来训练新层。在这里,我们有两个选择:

  • 使用网络的原始部分作为特征提取器并仅训练新层 :在这种情况下,我们向网络提供新数据的训练批次并将其向前传播以查看网络的输出。这部分就像常规训练一样工作。但是在反向传播中,我们锁定了原始网络的权重,只更新新层的权重。当我们针对新问题的训练数据有限时,这是推荐的方法。通过锁定大部分网络权重,我们可以防止对新数据的过度拟合。
  • 微调整个网络:在这种情况下,我们将训练整个网络,而不仅仅是最后添加的层。可以更新所有网络权重,但我们也可以锁定第一层中的一些权重。这里的想法是,初始层检测与特定任务无关的一般特征,并且重用它们是有意义的。另一方面,较深的层可能会检测到特定于任务的特征,最好更新它们。当我们有更多的训练数据并且不需要担心过度拟合时,我们可以使用这种方法。

使用 PyTorch 实现迁移学习

现在我们知道 TL 是什么,让我们看看它在实践中是否有效。在本节中,我们将使用PyTorch 1.3.1和0.4.2包 在 CIFAR-10 图像上应用高级 ImageNet 预训练网络。我们将使用这两种类型的TL。最好在 GPU 上运行此示例。torchvision

此示例部分基于https://github.com/pytorch/tutorials/blob/master/beginner_source/transfer_learning_tutorial.py

让我们开始吧:

1.执行以下导入

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
from torchvision import models, transforms

2.为方便起见定义: batch_size

batch_size = 50

3.定义训练数据集。我们必须考虑几件事情:

  • CIFAR-10 图像为 32 × 32,而 ImageNet 网络需要 224 × 224 输入。由于我们使用的是基于 ImageNet 的网络,我们会将 32 × 32 CIFAR 图像上采样到 224 × 224。
  • 使用 ImageNet 均值和标准差对 CIFAR-10 数据进行标准化,因为这是网络所期望的。
  • 我们还将以随机水平或垂直翻转的形式添加一些数据增强:
# 训练数据
train_data_transform = transforms.Compose([
    transforms.Resize(224),
    transforms.RandomHorizontalFlip(),
    transforms.RandomVerticalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4821, 0.4465), (0.2470, 
    0.2435, 0.2616))
])

train_set = torchvision.datasets.CIFAR10(root='./data',
                           train=True, download=True,
                           transform=train_data_transform)

train_loader = torch.utils.data.DataLoader(train_set,
                           batch_size=batch_size,
                           shuffle=True, num_workers=2)

4.对验证/测试数据执行相同的步骤,但这次没有扩充:

val_data_transform = transforms.Compose([
    transforms.Resize(224),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4821, 0.4465), (0.2470, 0.2435, 
    0.2616))
])

val_set = torchvision.datasets.CIFAR10(root='./data',
                                  train=False, download=True,
                                  transform=val_data_transform)

val_order = torch.utils.data.DataLoader(val_set,
                                  batch_size=batch_size,
                                  shuffle=False, num_workers=2)

5.选择device,最好是具有 CPU 后备功能的 GPU:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

6.定义模型的训练。与 TensorFlow 不同,在 PyTorch 中,我们必须手动迭代训练数据。此方法在整个训练集(一个时期)上迭代一次,并在每次前向传递后应用优化器:

def train_model(model, loss_function, optimizer, data_loader):
    # 将模型设置为训练模式
    model.train()

    current_loss = 0.0
    current_acc = 0

    # 迭代训练数据
    for i, (inputs, labels) in enumerate(data_loader):
        # 将输入/标签发送到 GPU
        inputs = inputs.to(device)
        labels = labels.to(device)

        # 将参数梯度归零
        optimizer.zero_grad()

        with torch.set_grad_enabled(True):
            # 前向传播
            outputs = model(inputs)
            _, predictions = torch.max(outputs, 1)
            loss = loss_function(outputs, labels)

            # 向后传播
            loss.backward()
            optimizer.step()

        # statistics
        current_loss += loss.item() * inputs.size(0)
        current_acc += torch.sum(predictions == labels.data)

    total_loss = current_loss / len(data_loader.dataset)
    total_acc = current_acc.double() / len(data_loader.dataset)

    print('Train Loss: {:.4f}; Accuracy: {:.4f}'.format(total_loss, 
    total_acc))

7.定义模型的测试/验证。这与训练阶段非常相似,但我们将跳过反向传播部分:

def test_model(model, loss_function, data_loader):
    # 在评估模式下设置模型
    model.eval()

    current_loss = 0.0
    current_acc = 0

    # 遍历验证数据a
    for i, (inputs, labels) in enumerate(data_loader):
        # 发送GPU 的输入/标签
        inputs = inputs.to(device)
        labels = labels.to(device)

        # 前向传播
        with torch.set_grad_enabled(False):
            outputs = model(inputs)
            _, predictions = torch.max(outputs, 1)
            loss = loss_function(outputs, labels)

        # 统计
        current_loss += loss.item() * inputs.size(0)
        current_acc += torch.sum(predictions == labels.data)

    total_loss = current_loss / len(data_loader.dataset)
    total_acc = current_acc.double() / len(data_loader.dataset)

    print('Test Loss: {:.4f}; Accuracy: {:.4f}'.format(total_loss, 
    total_acc))

    return total_loss, total_acc

8.定义第一个TL场景,我们使用预训练网络作为特征提取器:

  • 我们将使用称为 ResNet-18 的流行网络。我们将在高级网络架构部分详细讨论这一点。PyTorch 将自动下载预训练的权重。
  • 将最后一个网络层替换为具有 10 个输出的新层(每个 CIFAR-10 类一个)。
  • 从反向传递中排除现有网络层,仅将新添加的全连接层传递给 Adam 优化器。
  • 在每个 epoch 之后运行训练epochs并评估网络准确性。
  • plot_accuracy在函数的帮助下绘制测试精度。它的定义很简单,你可以在本书的代码库中找到它。

以下是实现所有这些的函数: tl_feature_extractor

def tl_feature_extractor(epochs=5):
    # 加载预训练模型
    model = torchvision.models.resnet18(pretrained=True)

    #从后向传递中排除现有参数
    # for performance
    for param in model.parameters():
        param.requires_grad = False

    # 新构建的层默认具有 requires_grad=True
    num_features = model.fc.in_features
    model.fc = nn.Linear(num_features, 10)

    # 传输到 GPU(如果可用)
    model = model.to(device)

    loss_function = nn.CrossEntropyLoss()

    # 只有最后一层的参数正在优化
    optimizer = optim.Adam(model.fc.parameters())

    # train
    test_acc = list()  # collect accuracy for plotting
    for epoch in range(epochs):
        print('Epoch {}/{}'.format(epoch + 1, epochs))

        train_model(model, loss_function, optimizer, train_loader)
        _, acc = test_model(model, loss_function, val_order)
        test_acc.append(acc)

    plot_accuracy(test_acc)

9.实施微调方法。此功能类似于,但在这里,我们正在训练整个网络: tl_feature_extractor

def tl_fine_tuning(epochs=5):
    # 加载预训练模型
    model = models.resnet18(pretrained=True)

    # 替换最后一层
    num_features = model.fc.in_features
    model.fc = nn.Linear(num_features, 10)

    # 转移模型到 GPU
    model = model.to(device)

    # 损失函数
    loss_function = nn.CrossEntropyLoss()

    # 我们将优化所有参数
    optimizer = optim.Adam(model.parameters())

    # 训练
    test_acc = list()  # 收集准确率绘制
    for epoch in range(epochs):
        print('Epoch {}/{}'.format(epoch + 1, epochs))

        train_model(model, loss_function, optimizer, train_loader)
        _, acc = test_model(model, loss_function, val_order)
        test_acc.append(acc)

    plot_accuracy(test_acc)

10.最后,我们可以通过以下两种方式之一运行整个事情:

  • 呼吁对五个时期使用微调 TL 方法。 tl_fine_tuning()
  • 调用以使用特征提取器方法对网络进行五个时期的训练。 tl_feature_extractor()

这是两种情况下五个 epoch 后网络的准确度:

左:特征提取 TL 准确率;右:微调 TL 精度

由于所选ResNet18预训练模型的尺寸很大,网络在特征提取场景中开始过拟合。

使用 TensorFlow 2.0 进行迁移学习

在本节中,我们将再次实现这两个迁移学习场景,但这次使用的是TensorFlow 2.0.0 (TF)。这样,我们就可以比较两个库了。而不是ResNet18,我们将使用ResNet50V2架构(更多信息请参见第 3 章高级卷积网络)。除了 TF,这个例子还需要 TF Datasets 1.3.0包 ( https://www.tensorflow.org/datasets ),它是各种流行的 ML 数据集的集合。

此示例部分基于docs/transfer_learning.ipynb at master · tensorflow/docs · GitHub

有了这个,让我们开始吧:

1.像往常一样,首先,我们需要进行导入:

import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_datasets as tfds 

2.然后,我们将定义小批量和输入图像大小(图像大小由网络架构决定):

IMG_SIZE = 224
BATCH_SIZE = 50

3.接下来,我们将借助 TF 数据集加载 CIFAR-10 数据集。该repeat()方法允许我们在多个时期重用数据集:

data, metadata = tfds.load('cifar10', with_info=True, as_supervised=True)
raw_train, raw_test = data['train'].repeat(), data['test'].repeat()

4.然后,我们将定义train_format_sampleandtest_format_sample函数,它将输入图像转换为合适的 CNN 输入。这些函数扮演的角色与transforms.Compose对象扮演的角色相同,我们在使用 PyTorch 实现迁移学习部分中定义了这些角色。输入转换如下:

  • 图像被调整为 96 × 96,这是预期的网络输入大小。
  • 每个图像通过转换其值进行标准化,使其处于 (-1; 1) 区间。
  • 标签被转换为 one-hot 编码。
  • 训练图像在水平和垂直方向上随机翻转。

我们来看看实际的实现:

def train_format_sample(image, label):
    """Transform data for training"""
    image = tf.cast(image, tf.float32)
    image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
    image = (image / 127.5) - 1
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_flip_up_down(image)

    label = tf.one_hot(label, metadata.features['label'].num_classes)

    return image, label


def test_format_sample(image, label):
    """Transform data for testing"""
    image = tf.cast(image, tf.float32)
    image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
    image = (image / 127.5) - 1

    label = tf.one_hot(label, 
    metadata.features['label'].num_classes)

    return image, label

5.接下来是一些样板代码,将这些转换器分配给训练/测试数据集并将它们分成小批量:

# assign transformers to raw data
train_data = raw_train.map(train_format_sample)
test_data = raw_test.map(test_format_sample)

# extract batches from the training set
train_batches = train_data.shuffle(1000).batch(BATCH_SIZE)
test_batches = test_data.batch(BATCH_SIZE)

6.然后,我们需要定义特征提取模型:

  • 我们将使用 Keras 进行预训练的网络和模型定义,因为它是 TF 2.0 不可或缺的一部分。
  • 我们加载ResNet50V2预训练的网络,不包括最终的全连接层。
  • 然后,我们调用base_model.trainable = False,它冻结了所有网络权重并阻止它们进行训练。
  • 最后,我们添加一个GlobalAveragePooling2D操作,然后在网络末端添加一个新的、可训练的全连接可训练层。

以下代码实现了这一点:

def build_fe_model():
    # create the pretrained part of the network, excluding FC 
    layers
    base_model = tf.keras.applications.ResNet50V2(input_shape=(IMG_SIZE,
    IMG_SIZE, 3), include_top=False, weights='imagenet')

    # exclude all model layers from training
    base_model.trainable = False

    # create new model as a combination of the pretrained net
    # and one fully connected layer at the top
    return tf.keras.Sequential([
        base_model,
        tf.keras.layers.GlobalAveragePooling2D(),
        tf.keras.layers.Dense(
            metadata.features['label'].num_classes,
            activation='softmax')
    ])

7.接下来,我们将定义微调模型。它与特征提取的唯一区别是我们只冻结了一些底部的预训练网络层(而不是全部)。以下是实现:

def build_ft_model():
    # create the pretrained part of the network, excluding FC 
    layers
    base_model = tf.keras.applications.ResNet50V2(input_shape=(IMG_SIZE, 
    IMG_SIZE, 3), include_top=False, weights='imagenet')

    # Fine tune from this layer onwards
    fine_tune_at = 100

    # Freeze all the layers before the `fine_tune_at` layer
    for layer in base_model.layers[:fine_tune_at]:
        layer.trainable = False

    # create new model as a combination of the pretrained net
    # and one fully connected layer at the top
    return tf.keras.Sequential([
        base_model,
        tf.keras.layers.GlobalAveragePooling2D(),
        tf.keras.layers.Dense(
            metadata.features['label'].num_classes,
            activation='softmax')
    ])

8.最后,我们将实现该函数,该函数训练和评估由or函数train_model创建的模型:build_fe_modelbuild_ft_model

def train_model(model, epochs=5):
    # configure the model for training
    model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])

    # train the model
    history = model.fit(train_batches,
                        epochs=epochs,
                        steps_per_epoch=metadata.splits['train'].num_examples // BATCH_SIZE,
                        validation_data=test_batches,
                        validation_steps=metadata.splits['test'].num_examples // BATCH_SIZE,
                        workers=4)

    # plot accuracy
    test_acc = history.history['val_accuracy']

    plt.figure()
    plt.plot(test_acc)
    plt.xticks(
        [i for i in range(0, len(test_acc))],
        [i + 1 for i in range(0, len(test_acc))])
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.show()

9.我们可以使用以下代码运行特征提取或微调 TL:

  • train_model(build_ft_model())
  • train_model(build_fe_model())

如果机器 GPU 可用,TF 会自动使用;否则,它将恢复到 CPU。下图显示了两种场景在五个 epoch 后网络的准确度:

左:特征提取 TL;右:微调 TL

概括

我们在本章开始时快速回顾了 CNN,并讨论了转置、深度可分离和扩张卷积。接下来,我们讨论了通过将卷积表示为矩阵乘法或使用 Winograd 卷积算法来提高 CNN 的性能。然后,我们专注于在引导反向传播和 Grad-CAM 的帮助下可视化 CNN。接下来,我们讨论了最流行的正则化技术。最后,我们了解了迁移学习,并使用 PyTorch 和 TF 实现了相同的 TL 任务,以此来比较这两个库。

在下一章中,我们将讨论一些最流行的高级 CNN 架构。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sonhhxg_柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值