//2022.1.15日上午11:02阅读笔记
到目前为止,我们对机器学习和神经网络的全部回顾都指向了这一点:理解卷积神经网络(Convolutional neural networks, cnn)及其在深度学习中的作用。
在传统的前馈神经网络中(就像我们在第10章中学习的那些),输入层的每个神经元与下一层的每个输出神经元相连——我们称之为全连接(FC)层。然而,在cnn中,我们直到网络的最后一层才使用FC层。因此,我们可以将CNN定义为一种神经网络,它在一个专门的“卷积”层中交换,而不是在网络[10]中的至少一个层中交换“完全连接”层。
一个非线性激活函数,如ReLU,然后应用这些隆起和卷积的过程的输出= >激活持续(连同其他层类型的混合物的宽度和高度来减少输入量和帮助减少过度拟合)直到我们最终到达的网络和应用一个或两个FC层,我们可以获得ourfinal输出分类。
//截止到2022.1.15日上午11:30
//2022.1.15日晚上21:48开始阅读
CNN中的每一层都应用不同的过滤器集,通常是成百上千个,并将结果组合起来,将输出输入到网络中的下一层。在训练过程中,CNN自动学习这些过滤器的值。
CNN可以学到:
CNN的最后一层使用这些高级特征对图像的内容进行预测。在实践中,cnn给了我们两个关键的好处:局部不变性和组合性。局部不变性的概念允许我们将图像归类为包含特定对象的图像,而不管该对象出现在图像的哪个位置。我们通过使用“池化层”(在本章后面讨论)来获得这种局部不变量,它确定了我们的输入量的区域,对特定的过滤器有高响应。
第二个好处是组合性。每个过滤器由一个由较低级别特性组成的局部补丁组成转化为更高层次的表示,类似于我们如何在之前的函数(f (g(x(h(x)))的输出基础上构建一组数学函数:f (g(x(h(x))),这种组合允许我们的网络在更深的层次上学习更丰富的特征。例如,我们的网络可以从像素构建边缘,从边缘构建形状,然后从形状构建复杂的对象——所有这些都是在训练过程中自然发生的自动化方式。从低级特征构建高级特征的概念正是cnn在计算机视觉中如此强大的原因。
在本章的其余部分,我们将详细讨论卷积是什么以及它们在深度学习中的作用。然后,我们将继续介绍cnn的构建块:层,以及用于构建自己的cnn的各种类型的层。我们将通过查看常用模式来结束本章,这些模式用于堆叠这些构建块,以创建CNN架构,该架构在不同的图像分类任务集上表现良好。
在回顾了本章之后,我们将(1)对卷积神经网络和构建卷积神经网络的思维过程有一个很好的理解,(2)我们可以用来构建我们自己的网络架构的一些CNN“食谱”。在下一章中,我们将使用这些基本原理和配方来训练我们自己的cnn。
本节需要解决的问题:
曾经应用模糊或平滑的图像?对,这是一个卷积。那么边缘检测呢?是的,卷积。你是否打开Photoshop或GIMP来锐化图像?你猜对了,卷积。卷积是计算机视觉和图像处理中最关键、最基本的组成部分之一。
在深度学习方面,一个(图像)卷积是两个矩阵的元素级乘法,后面跟着一个和。
卷积:
然而,几乎所有的机器学习和深度学习库都使用了简化的互相关函数:
所有这些数学计算都是在我们如何访问图像I的坐标时的符号改变(即,当应用互相关时,我们不需要相对于输入“翻转”内核)。
同样,许多深度学习库使用简化的互相关操作并称之为卷积——我们在这里将使用相同的术语。如果读者有兴趣了解更多关于卷积和互相关背后的数学知识,请参阅Szelski的《计算机视觉:算法和应用》第三章[119]。
图像是一个多维矩阵。我们的图像有宽度(列数#)和高度(行数#),就像一个矩阵一样。但与你在小学时使用的传统矩阵不同,图像也有深度——图像中通道的数量。
对于一个标准的RGB图像,我们有一个深度为3 -红、绿、蓝通道分别为一个通道。有了这些知识,我们可以把图像想象成一个大矩阵,而核矩阵或卷积矩阵则是一个用于模糊、锐化、边缘检测和其他处理功能的小矩阵。本质上,这个微小的内核位于大图像的顶部,并从左到右、从上到下进行滑动,在原始图像的每个(x, y)坐标处应用数学运算(即卷积)。
为了获得各种图像处理功能,通常需要手工定义核。事实上,你可能已经熟悉了模糊(平均平滑、高斯平滑、中值平滑等)、边缘检测(Laplacian、Sobel、Scharr、Prewitt等)和锐化——所有这些操作都是手工定义的核的形式,它们是专门为执行特定功能而设计的。
这就提出了一个问题:有没有一种方法可以自动学习这些类型的过滤器?甚至使用这些过滤器来进行图像分类和目标检测?当然有。但在此之前,我们需要对核和卷积有更多的了解。
再一次,让我们把图像看作一个大矩阵,而把内核看作一个小矩阵(至少就原始的“大矩阵”图像而言),如图11.1所示。如图所示,我们正在沿着原始图像从左到右和从上到下滑动内核(红色区域)。在原始图像的每个(x, y)坐标处,我们停下来并检查位于图像内核中心的像素的邻域。然后,我们取像素的这个邻域,将它们与内核卷积,并获得单个输出值。输出值存储在与内核中心相同的(x, y)坐标的输出图像中。
如果这听起来令人困惑,不用担心,我们将在下一节中回顾一个示例。但在我们深入研究一个例子之前,让我们看看内核是什么样子的(图11.3):
上面我们定义了一个3 × 3的正方形核(猜猜这个核是用来做什么的?)核可以是任意矩形大小的MxN,前提是M和N都是奇数。
大多数应用于深度学习和cnn的核都是N × N个方阵,这使得我们能够利用优化的线性代数库,这些库在方阵上运行效率最高。
我们使用奇数的内核大小来确保在图像的中心有一个有效的整数坐标(x, y)(图11.2)。在左边,我们有一个3 × 3矩阵。矩阵的中心位于x = 1, y = 1处,其中矩阵的左上角用作原点,我们的坐标为0。但是在右边,我们有一个2 × 2的矩阵。这个矩阵的中心位于x = 0.5, y = 0.5。
但正如我们所知,如果不应用插值,就不存在像素位置(0.5,0.5)这样的东西——我们的像素坐标必须是整数!这正是我们使用奇内核大小的原因:始终确保在内核中心有一个有效的(x, y)坐标。
卷积需要三个分量:
卷积过程:
下面你可以找到一个卷积的例子(在数学上表示为?算子)图像的3 × 3区域,带有用于模糊的3 × 3核:
在应用这个卷积之后,我们将位于输出图像O的坐标(i, j)的像素设置为Oi, j = 132。
这就是所有的事情!卷积仅仅是核与邻域之间的按元素计算的矩阵乘法的和,该邻域由核覆盖输入图像。
卷积函数需要两个参数:我们想要与核函数卷积的(灰度)图像。给定我们的图像和内核(我们假设它们是NumPy数组),然后我们确定每个的空间维度(即宽度和高度)(第10行和第11行)。
回想一下,我们的计算是围绕着内核当前所在的输入图像的中心(x, y)坐标“居中”的。这种定位意味着沿着图像边界的像素不存在所谓的“中心”像素(因为内核的角会“悬挂”在图像上,值是未定义的),如图11.3所示。
空间维数的降低只是对图像应用卷积的一个副作用。有时这种效果是可取的,有时则不然,这完全取决于您的应用程序。
然而,在大多数情况下,我们希望输出图像与输入图像具有相同的尺寸。为了确保尺寸相同,我们应用填充(第15-18行)。在这里,我们只是沿着图像的边界复制像素,这样输出图像将与输入图像的尺寸匹配。
还有其他的填充方法,包括零填充(用零填充边界——这在构建卷积神经网络时很常见)和绕边填充(通过检查图像的对边确定边界像素)。在大多数情况下,您将看到复制或零填充。复制填充通常用于美学方面,而零填充则是提高效率的最佳方法。
现在我们准备将实际的卷积应用到我们的图像上:
如果我们尝试在位于(0,0)的像素处应用卷积,那么我们的3 × 3核将“挂起”在图像的边缘。注意,内核的第一行和第一列没有输入图像像素值。因此,我们总是(1)在第一个有效的位置开始卷积,或者(2)应用零填充(本章后面会讲到)。
以下为卷积过程:
第22和23行在我们的图像上循环,从左到右和从上到下“滑动”内核,每次一个像素。第27行使用NumPy数组切片从图像提取感兴趣区域(Region of Interest, ROI)。roi将以图像的当前(x, y)坐标为中心。roi的大小也将与我们的内核相同,这对下一步至关重要。
卷积在第32行执行,在roi和核之间进行逐元素乘法,然后对矩阵中的项求和。然后存储输出值k在output内存中。
当处理图像时,我们通常处理的像素值在[0,255]范围内。然而,当应用卷积时,我们可以很容易地得到超出这个范围的值。为了使输出图像回到范围[0,255],我们应用scikit-image的rescale_intensity函数(第39行)。
我们还在第40行将图像转换回无符号8位整数数据类型(以前,输出图像是浮点类型,以便处理范围[0,255]之外的像素值)。最后,将输出图像返回给第43行上的调用函数。
既然我们已经定义了卷积函数,让我们继续脚本的驱动程序部分。我们程序的这一部分将处理解析命令行参数,定义一系列我们将应用到我们的图像的内核,然后显示输出结果:
然后我们可以定义用于模糊和平滑图像的两个核:
为了使你自己相信这个核正在进行模糊处理,注意核中的每一项都是1/S的平均值,其中S是矩阵中的总条目数。因此,该内核将每个输入像素乘以一个小分数并取其和——这正是平均值的定义。
然后我们有一个负责锐化图像的内核:
那么用于检测类边缘区域的拉普拉斯核:
Sobel核可分别用于检测x轴和y轴上的类边区域:
最后,我们定义emboss内核:
要了解如何用数学方法构造核并证明其执行给定的图像处理操作,请参考Szeliksi(第3章)[119]。我还推荐使用Setosa的这个优秀的内核可视化工具。io[120]。
考虑到所有这些内核,我们可以将它们合并成一组元组,称为“内核库”:
构建这个内核列表可以循环使用它们,并以一种高效的方式可视化它们的输出,如下面的代码块所示:
第99和100行从磁盘加载图像并将其转换为灰度。卷积运算符可以应用于RGB或其他多通道体积,但为了简单起见,我们只将我们的过滤器应用于灰度图像。
我们在第103行开始对kernelBank中的核集进行循环,然后通过调用在脚本前面定义的函数convolve方法,将当前内核应用于第104行中的灰度图像。
作为一个完整的检查,我们也调用cv2。filter2D,它也将我们的内核应用到灰度图像。cv2。filter2D函数是OpenCV对卷积函数的优化版本。我在这里包含这两种方法的主要原因是为了检查我们的自定义实现。
最后,第111-115行在屏幕上显示每种内核类型的输出图像。
卷积结果
运行以下命令:
然后,您将看到将smallBlur内核应用到输入映像的结果,如图11.4所示。左边是原始图像。然后,在中心,我们得到了卷积函数的结果。右边是来自cv2.filter2D的结果。快速的视觉检查将发现我们的输出与cv2匹配。,表示我们的卷积函数工作正常。此外,我们的图像现在看起来“模糊”和“平滑”,这多亏了平滑核。
让我们应用一个更大的模糊,结果可以在图11.5(左上角)中看到。这次我省略了cv2。filter2D结果以节省空间。比较图11.5和图11.4的结果,注意随着平均内核大小的增加,输出图像中的模糊程度也会增加。
我们还可以锐化我们的图像(图11.5,上-中),并通过拉普拉斯算子(上-右)检测边缘类区域。
左图:原始输入图像。中心:使用我们的自定义卷积函数应用7 × 7的平均模糊。右:使用OpenCV的cv2应用相同的7 × 7模糊。filter2D—注意这两个函数的输出是如何相同的,这意味着我们的卷积方法得到了正确的实现。
SobelX内核用于查找图像中的垂直边(图11.5,左下),而sobelely内核则显示水平边(从下到中)。最后,我们可以在左下角看到浮雕内核的结果。
左上角:应用21 × 21的平均模糊。请注意,这个图像比图11.4中的更加模糊。中上:使用锐化内核来增强细节。右上:通过拉普拉斯算子检测类边操作。左下:使用Sobel-X内核计算垂直边。从下到中:使用Sobel-Y内核寻找水平边。右下角:应用一个浮雕内核。
通过应用卷积过滤器、非线性激活函数、池化和反向传播,cnn能够学习能够检测网络底层边缘和斑点状结构的过滤器——然后使用边缘和结构作为“构建块”,最终检测高级对象(例如,人脸、猫、狗、杯子、等等)在网络的更深层。
使用低级别的层来学习高级的特性的过程正是我们前面提到的cnn的组合性。但cnn究竟是如何做到这一点的呢?答案是有目的地堆叠特定的层。在下一节中,我们将讨论这些类型的层,然后检查在许多图像分类任务中广泛使用的常见层叠加模式。
//截止到2022.1.15日晚上23:55
//2022.1.16日上午11:47开始阅读
正如我们在第10章中所学到的,神经网络接受一个输入图像/特征向量(每个条目一个输入节点),并通过一系列隐藏层对其进行转换,通常使用非线性激活函数。每个隐藏层也由一组神经元组成,其中每个神经元与前一层的所有神经元完全连接。神经网络的最后一层(即“输出层”)也是完全连接的,代表了网络的最终输出分类。正如我们在第10章中所学到的,神经网络接受一个输入图像/特征向量(每个条目一个输入节点),并通过一系列隐藏层对其进行转换,通常使用非线性激活函数。每个隐藏层也由一组神经元组成,其中每个神经元与前一层的所有神经元完全连接。神经网络的最后一层(即“输出层”)也是完全连接的,代表了网络的最终输出分类。
然而,正如第10.1.4节的结果所示,神经网络直接在原始像素强度上运行:
为了说明标准神经网络在图像大小增加时如何不能很好地扩展,让我们再次考虑CIFAR-10数据集。CIFAR-10中的每幅图像都是32 × 32,带有红、绿、蓝通道,向我们的网络输出的总输入量为32 × 32 × 3 = 3,072;
总共072输入似乎并没有什么了不起,但想想如果我们使用250×250像素的图像-输入和权值的总数将跃升至250×250×3 = 187,500,这个数字仅为输入层单独!当然,我们想要添加多个隐藏层,每个层的节点数量都不同——这些参数可以快速叠加,并且考虑到标准神经网络在原始像素强度上的糟糕性能,这种膨胀是不值得的。
相反,我们可以使用利用输入的卷积神经网络(Convolutional Neural Networks, cnn)图像结构,并以更合理的方式定义网络架构。与标准的神经网络不同,CNN的各层以三维方式排列在一个三维体中:宽度、高度和深度(其中深度指的是体积的第三维,如图像中的通道数量或一层中的过滤器数量)。
为了使这个示例更具体,再次考虑CIFAR-10数据集:输入体积的尺寸为32 × 32 × 3(分别为宽度、高度和深度)。后续各层的神经元只会连接到前一层的一小块区域(而不是标准神经网络的全连接结构)——我们称之为局部连接,它使我们能够在网络中保存大量参数。最后,输出层将是一个1 × 1 × N的体积,表示图像提炼成一个单一的类别分数向量。以CIFAR-10为例,给定10类,N = 10,产生1 × 1 × 10体积。
最可能遇到的层:
以特定的方式叠加一系列的这些层会生成CNN。我们经常使用简单的文本图来描述CNN: INPUT => CONV => RELU => FC => SOFTMAX。
在这里,我们定义了一个简单的CNN,它接受一个输入,应用一个卷积层,然后是一个激活层,然后是一个全连接层,最后是一个softmax分类器来获得输出分类概率。SOFTMAX激活层通常在网络图中被忽略,因为它被认为直接遵循最终的FC。
在这些层类型中,CONV和FC(在较小程度上,BN)是唯一包含在训练过程中学习的参数的层。激活和退出层本身并不被认为是真正的“层”,但是经常包含在网络图中以使体系结构显式地清晰。与CONV和FC同等重要的池化层(POOL)也包含在网络图中,因为当图像在CNN中移动时,池化层对图像的空间维度有重大影响。
CONV、POOL、RELU和FC在定义实际网络架构时是最重要的。这并不是说其他层不重要,而是说在定义实际的体系结构本身时,这四个层是最重要的。
激活函数本身实际上被假定为架构的一部分,当定义CNN架构时,我们经常从表格/图表中忽略激活层以节省空间;然而,激活层被隐式地假定为体系结构的一部分。
在本节的其余部分中,我们将详细回顾这些层类型,并讨论与每个层相关的参数(以及如何设置它们)。在本章的后面,我将更详细地讨论如何正确地堆叠这些层来构建您自己的CNN架构。
CONV层是卷积神经网络的核心构件。CONV层参数由一组K个可学习滤波器(即“核”)组成,其中每个滤波器都有宽度和高度,而且几乎总是正方形的。这些滤镜很小(就其空间尺寸而言),但延伸到整个体量的整个深度。
卷积过程:
左:在CNN的每一个卷积层,都有K个kernel应用到输入的volume上。中:每一个K核与输入体积卷积。右:每个内核产生一个2D输出,称为激活映射。
在获得K激活图后,将它们堆叠在一起,形成网络中到下一层的输入量。
因此,输出体积中的每一项都是一个神经元的输出,它只“观察”输入的一个小区域。通过这种方式,网络“学习”了过滤器,当它们在输入量中给定的空间位置看到特定类型的特征时,就会激活过滤器。在网络的较低层,当他们看到边缘或角状区域时,过滤器可能会被激活。
然后,在网络的更深层次,过滤器可能会在高级特征出现时激活,比如脸部的某些部分、狗的爪子、汽车的引擎盖等。这个激活的概念回到我们在第10章中的神经网络类比——当这些神经元看到输入图像中的特定模式时,它们会变得“兴奋”和“活跃”。
在卷积神经网络中,将小滤波器与大(r)输入体积卷积的概念具有特殊意义——具体来说,是神经元的局部连接性和接收域。在处理图像时,将当前卷中的神经元连接到前一卷中的所有神经元通常是不切实际的——因为有太多的连接和太多的权值,使得在具有大空间维度的图像上训练深层网络是不可能的。相反,当使用cnn时,我们选择将每个神经元只连接到输入体积的一个局部区域——我们称这个局部区域的大小为神经元的接受域(或简单地说,变量F)。
为了明确这一点,让我们回到我们的CIFAR-10数据集,其中输入体积为32 × 32 × 3。因此,每个图像的宽度为32像素,高度为32像素,深度为3(每个RGB通道为1)。如果我们receptivefield大小3×3,然后CONV层中的每个神经元将连接到一个3×3图像的局部区域总共3×3×3 = 27权重(记住,thefilters的深度是三个,因为他们通过完整的输入图像的深度扩展,在这种情况下,三个通道)。
现在,让我们假设我们的输入体积的空间维度已经减小到更小的尺寸,但是我们的深度现在变大了,这是因为在网络中使用了更多的过滤器,这样体积大小现在是16 × 16 × 94。同样,如果我们假设一个大小为3 × 3的接收域,那么CONV层中的每个神经元将有3 × 3 × 94 = 846个连接到输入体积。简单地说,接受域F是滤波器的大小,产生一个与输入体积卷积的F × F核。
深度
输出音量的深度控制CONV层中连接到输入音量局部区域的神经元(即滤波器)的数量。每个滤镜生成一个激活图,在有方向的边缘、斑点或颜色出现时激活。
对于给定的CONV层,激活图的深度将是K,或者简单地说,我们在当前层中学习的过滤器的数量。“查看”输入相同(x, y)位置的set过滤器称为depth列。
步长
在上面关于卷积的第11.1.5节中,我们只在每个顶部采取一个像素的步骤。在cnn环境中,同样的原理也可以应用——对于每一步,我们围绕图像的局部区域创建一个新的深度列,在该区域中,我们将每个k过滤器与该区域进行卷积,并将输出存储在一个3D体中。当创建CONV层时,我们通常使用S = 1或S = 2的步幅。
更小的跨步将导致接收域重叠和更大的输出量。相反,更大的跨距将导致接收域重叠更少,输出量更小。为了使卷积大步的概念更具体,考虑表11.1,其中我们有一个5 × 5的输入图像(左)和一个3 × 3的拉普拉斯核(右)。
使用S = 1,我们的内核从左到右、从上到下滑动,一次一个像素,产生以下输出(图11.2,左)。但是,如果我们要应用相同的操作,只有这次的步幅为S = 2,我们一次跳过两个像素(x轴两个像素,y轴两个像素),产生较小的输出量(右图)。
因此,我们可以看到卷积层是如何通过改变内核的步幅来减少输入体积的空间维度的。正如我们将在本节后面看到的,卷积层和池化层是减少空间输入大小的主要方法。池层部分还将提供一个更直观的示例,说明不同的stride大小将如何影响输出大小。
0填充
正如我们在第11.1.5节中所知道的,当应用卷积时,我们需要“填充”图像的边界以保留原始图像的大小——对于CNN内部的过滤器也是如此。使用零填充,我们可以沿着边界“填充”我们的输入,这样我们的输出体积大小匹配我们的输入体积大小。我们应用的填充量由参数P控制。
当我们开始研究将多个卷积滤波器相互叠加在一起的深层CNN架构时,这种技术尤为关键。为了可视化零填充,请再次参考表11.1,在其中,我们将3 × 3的拉普拉斯核应用于一个跨距为S = 1的5 × 5输入图像。
从表11.3(左)可以看出,由于卷积运算的性质,输出体积(3 × 3)小于输入体积(5 × 5)。如果我们设置P = 1,我们可以用0(中间)填充我们的输入体积,以创建一个7 × 7的体积,然后应用卷积运算,导致输出体积大小匹配原始输入体积大小为5 × 5(右边)。
如果没有零填充,输入体积的空间维度会下降得太快,我们将无法训练深层网络(因为输入体积太小,无法从中学习任何有用的模式)。
一起把所有这些参数,我们可以计算出一个输出音量的大小作为输入音量大小的函数(W,假设输入的图像是广场,他们几乎总是),F receptivefield大小,跨步年代和补零p .构建一个有效的CONV层,我们需要确保下面的方程是一个整数:
如果它不是一个整数,那么stride设置不正确,神经元不能平铺,这样它们就不能以对称的方式适应输入体积。
以AlexNet架构的第一层为例,它赢得了2012年ImageNet分类挑战,是当前深度学习应用于图像分类热潮的主要原因。在他们的论文中,Krizhevsky等人[94]根据图11.8记录了他们的CNN架构。
注意,第一层声称输入图像的大小是224 × 224像素。然而,如果我们使用11 × 11的过滤器,步幅为4,并且没有填充,那么这就不可能是正确的:
但是这可能是这篇论文中的一个小错误。
这样的错误比您想象的更常见,因此在通过出版物实现cnn时,一定要自己检查参数,而不是简单地假设列出的参数是正确的。由于CNN中有大量的参数,在编写架构文档时很容易犯排版错误(我自己就做过很多次)。
CONV卷积层:
在CNN的每个CONV层之后,我们应用一个非线性激活函数,例如ReLU, ELU,或任何其他在第10章中提到的Leaky ReLU变体。在网络图中,我们通常将激活层表示为RELU,因为RELU激活是最常用的,我们也可以简单地状态为ACT——在任何一种情况下,我们都清楚地表明在网络架构中应用了一个激活函数。
从技术上讲,激活层并不是“层”(由于在激活层中没有学习参数/权值的事实),有时会从网络架构图中省略,因为它假设一个激活紧跟在卷积之后。
在这种情况下,出版物的作者将在论文的某个地方提到在每个CONV层之后他们正在使用哪个激活函数。以如下组网结构为例:INPUT => CONV => RELU => FC。
为了使这个图更简洁,我们可以简单地删除RELU组件,因为它假设一个激活总是遵循一个卷积:INPUT => CONV => FC。我个人并不喜欢这样,而是选择在网络图中明确包含激活层,以明确我在网络中应用什么时候以及什么激活功能。
激活层接受大小为Winput × Hinput × Dinput的输入量,然后应用给定的激活函数(图11.9)。由于激活函数是按元素的方式应用的,激活层的输出总是与输入维相同,Winput = Wout put, Hinput = Hout put, Dinput = Dout put。
有两种方法可以减小输入量的大小——带stride > 1的CONV层(我们已经看到了)和POOL层。在连续的CONV卷积层之间插入POOL层是很常见的。
带有Pooling层的网络架构:
INPUT => CONV => RELU => POOL => CONV => RELU => POOL => FC
POOL层的主要功能是逐步减小输入体积的空间大小(即宽度和高度)。这样做可以减少网络中的参数量和计算量,池也可以帮助我们控制过拟合。
POOL层使用max或average函数分别对输入的每个深度切片进行操作。最大池化通常在CNN架构的中间完成,以减少空间大小,而平均池化通常被用作网络的最后一层(例如,GoogLeNet, SqueezeNet, ResNet),我们希望完全避免使用FC层。POOL层最常见的类型是max pooling,尽管这种趋势正在随着更多奇异的微架构的引入而改变。
通常我们将使用2 × 2的池大小,尽管使用更大的输入图像(> 200像素)的深层cnn可能在网络架构的早期使用3 × 3的池大小。我们通常也将步幅设置为S = 1或S = 2。图11.10(深受Karpathy等人的启发[121])是一个使用2 × 2池大小和跨度S = 1的最大池化示例。注意,对于每2 × 2块,我们只保留最大的值,采取单一步骤(如滑动窗口),并再次应用操作-从而产生3 × 3的输出体积大小。
我们可以通过增加步幅来进一步减小输出量的大小——在这里,我们对相同的输入应用S = 2(图11.10,底部)。对于输入的每2 × 2块,我们只保留最大的值,然后取2个像素步,再次进行操作。这个池允许我们将宽度和高度减少2倍,有效地丢弃了前一层75%的激活。
总而言之,POOL层接受大小为Winput × Hinput × Dinput的输入量。然后它们需要两个参数:
应用POOL操作得到一个大小为Wout put × Hout put × Dout put的输出量,其中:
两种不同的Max pooling方法:
左:我们的输入4 × 4体积。右图:在S = 1的步幅上应用2 × 2的最大池化。底部:应用2 × 2 max pooling (S = 2)——这极大地减少了我们输入的空间维度。
对于接受较小输入图像(在32 - 64像素范围内)的网络架构,您可能还会看到F = 2, S = 1。
使用CONV卷积还是Pooling层?
Springenberg等人在2014年的论文《力求简单:The All Convolutional Net》[122]中建议完全抛弃POOL层,而简单地依赖步幅较大的CONV层来处理volume的空间维度的下采样。他们的工作表明,这种方法在各种数据集上都能很好地工作,包括CIFAR-10(小图像,低数量的类)和ImageNet(大输入图像,1000个类)。ResNet架构[96]延续了这一趋势,该架构也使用CONV层进行下采样。
FC层中的神经元完全连接到前一层中的所有激活,正如我们在第10章中讨论的前馈神经网络的标准一样。FC层总是放置在网络的末端(即,我们不应用一个CONV层,然后一个FC层,然后是另一个CONV)层。
在应用softmax分类器之前,通常会使用一个或两个FC层,如下(简化)架构所示:
在这里,我们在(隐含的)softmax分类器之前应用两个完全连接的层,该分类器将计算每个类的最终输出概率。
首次引入约飞和Szgedy在他们一份2015年的论文,批量标准化:加速深层网络训练通过减少内部协变量[123]转变,一批标准化层(简称BN),顾名思义,是用来规范给定输入的激活体积在网络中传递到下一层。
如果我们认为x是我们的小批量激活,那么我们可以通过以下公式计算标准化的ˆx:
在训练过程中,我们计算每个小批量β的µβ和σβ,其中:
我们设置ε等于一个小的正值,例如1e-7,以避免取零的平方根。应用这个方程意味着,离开批处理规范化层的激活将具有大约为零的平均值和单位方差(即,以零为中心)。
在测试时,我们用训练过程中计算的µ- β和σ - β的运行平均值来代替小批量的µ- β和σ - β。这确保了我们可以通过我们的网络来传递图像,并且仍然可以获得准确的预测,而不会受到训练时通过网络的最终小批量图像的µβ和σβ的影响。
批处理归一化已经被证明在减少训练神经网络所需的时间方面是非常有效的。批处理标准化还具有帮助“稳定”训练的额外好处,允许更大的学习速率和正则化强度。当然,使用批处理规范化并不会减少对这些参数的调优需求,但它会使学习速率和正则化的波动更小,调优更简单,从而使您的工作更轻松。当您在网络中使用批处理规范化时,您还会注意到较低的最终损耗和更稳定的损耗曲线。
批处理标准化的最大缺点是,由于每批统计数据和标准化的计算,它实际上会将训练网络所需的时间(尽管您需要更少的时间来获得合理的准确性)降低2-3倍。
也就是说,我建议在几乎所有情况下都使用批处理规范化,因为它确实会产生显著的差异。正如我们在本书后面会看到的,将批处理归一化应用到我们的网络架构中可以帮助我们防止过拟合,并允许我们在更少的时间内获得显著更高的分类精度,相比之下,相同的网络架构没有批处理归一化。
批处理正则化层放在那?
您可能已经注意到,在我对批处理规范化的讨论中,我遗漏了在网络体系结构中放置批处理规范化层的确切位置。根据Ioffe和Szegedy的原始论文[123],他们将批归一化(BN)置于激活之前:
我们通过将x = Wu + b归一化,在非线性之前加上BN变换。
使用这个方案,一个利用批处理标准化的网络架构看起来像这样:
然而,这种批处理规范化的观点从统计的角度来看是没有意义的。在这种情况下,BN层归一化来自CONV层的特征分布。其中一些特征可能是负面的,在这种情况下,它们将被非线性激活函数(如ReLU)箝位(即设置为零)。
如果我们在激活之前进行标准化,我们本质上是在标准化中包含负值。我们的零中心特性然后通过ReLU,在那里我们杀死任何小于零的激活(包括在归一化之前可能不是负的特性)——这一层排序完全破坏了应用批处理归一化的目的。
相反,如果我们将批处理归一化放在ReLU之后,我们将对正值特征进行归一化,而不会用那些本来不会进入下一CONV层的特征对它们进行统计偏倚。事实上,Keras的创建者和维护者François Chollet证实了这一点,声明BN应该在激活之后出现:
我可以保证最近由Christian [Szegedy,来自BN论文]编写的代码适用于BN之前的relu。不过,它偶尔仍是一个争论的话题。
尚不清楚为什么Ioffe和Szegedy在他们的论文中建议将氮化硼层放在活化之前,但进一步的实验[125]以及其他深度学习研究人员的轶事证据[126]证实,在非线性激活后放置批量归一化层,几乎在所有情况下都能获得更高的准确性和更低的损失。
在网络架构中,激活后放置BN看起来是这样的:
我可以确认,在我使用cnn进行的几乎所有实验中,将BN放在RELU之后会产生更高的精度和更低的损耗。说,注意“几乎”这个词有一个非常小的数量的情况下将激活前的BN更好的工作,这意味着你应该默认激活后将BN,但可能想奉献(最多)前一个实验,将BN激活和注意的结果。
在运行了一些这样的实验之后,您很快就会意识到激活后的BN性能更好,而且您的网络中还有更多重要的参数需要调优以获得更高的分类精度。我将在本章后面的11.3.2节中对此进行更详细的讨论。
我们要讨论的最后一种图层类型是dropout。Dropout实际上是正则化的一种形式,旨在通过提高测试准确度来帮助防止过拟合,这可能会以牺牲训练准确度为代价。对于我们训练集中的每个小批处理,退出层随机断开网络体系结构中前一层到下一层的输入,其概率为p。
图11.11可视化了这个概念,我们以p = 0.5的概率随机断开给定的小批处理中两个FC层之间的连接。再次注意,对于这个小批处理,一半的连接是如何被切断的。在为小批处理计算向前和向后传递之后,我们重新连接已删除的连接,然后对另一组要删除的连接进行示例。
图11.11可视化了这个概念,我们以p = 0.5的概率随机断开给定的小批处理中两个FC层之间的连接。再次注意,对于这个小批处理,一半的连接是如何被切断的。在为小批处理计算向前和向后传递之后,我们重新连接已删除的连接,然后对另一组要删除的连接进行示例。
我们使用dropout的原因是通过在训练时明确地改变网络架构来减少过拟合。随机删除连接可以确保当呈现给定的模式时网络中没有单个节点被删除从而负责“激活”。相反,dropout确保有多个冗余节点,当出现类似的输入时,这些节点会被激活——这反过来帮助我们的模型一般化。
在架构的FC层之间放置p = 0.5的dropout层是最常见的,其中最终的FC层被假设为我们的softmax分类器:
然而,正如我在第11.3.2节中所讨论的,我们也可以在网络的早期层中以较小的概率(即p = 0.10 - 0.25)应用dropout(通常是通过最大池化或卷积进行下采样操作)。
正如我们在本章中所看到的,卷积神经网络由四个主要层组成:CONV、POOL、RELU和FC。将这些层以特定的模式叠加在一起可以生成CNN架构。
CONV层和FC层(和BN层)是网络中唯一真正学习参数的层——其他层只是负责执行一个给定的操作。激活层(ACT),如RELU和dropout,在技术上并不是层,但通常包含在CNN架构图中,以使操作顺序显式地清晰——在本节中我们也将采用相同的约定。
到目前为止,CNN架构最常见的形式是堆叠几个CONV和RELU层,然后在它们后面执行一个POOL操作。我们重复这个序列,直到体积宽度和高度很小,这时我们应用一个或多个FC层。因此,我们可以使用以下模式得出最常见的CNN架构:
这里,*操作符表示一个或多个?可选操作。每个声誉的常见选择包括:
下面我们可以看到一些CNN架构遵循这种模式的例子:
类似于AlexNet架构的CNN层次结构:
对于更深层次的网络架构,如VGGNet[95],我们将在每个POOL层之前堆叠两个(或更多)层:
通常,当我们(1)有大量标注的训练数据,(2)分类问题足够具有挑战性时,我们会应用更深层次的网络架构。在应用POOL层之前叠加多个CONV层可以让CONV层在破坏性的池操作执行之前开发出更复杂的特性。
正如我们将在本书的ImageNet Bundle中发现的那样,还有更多的“奇异的”网络架构,它们偏离了这些模式,并相应地创建了自己的模式。一些架构完全删除了POOL操作,依赖于CONV层对体积进行下采样——然后,在网络的末端,应用平均池而不是FC层来获得softmax分类器的输入。
GoogLeNet、ResNet和SqueezeNet[96、97、127]等网络架构就是这种模式的很好例子,并演示了如何去除FC层,从而减少参数和缩短训练时间。
这些类型的网络架构也“堆叠”和连接过滤器跨越通道维度:googlet应用1 × 1,3 × 3和5 × 5过滤器,然后将它们跨越通道维度连接在一起,以学习多层次特征。同样,这些体系结构被认为是更“新奇”和更先进的技术。
如果你对这些更高级的CNN架构感兴趣,请参考ImageNet Bundle;否则,在学习深度学习的基础知识之前,您会希望继续使用基本的层叠加模式。
在本节中,我将回顾构建您自己的cnn时的常见经验规则。首先,呈现给输入层的图像应该是方形的。使用平方输入允许我们利用线性代数优化库的优势。常用的输入层尺寸包括32 × 32,64 × 64、96 × 96、224 × 224、227 × 227和229 × 229(为了便于表示,省去了通道数)。
其次,输入层也应该在第一次CONV操作后被2倍整除。你可以通过调整滤镜的大小和步幅来做到这一点。“被二整除规则”使得我们的网络中的空间输入可以方便地通过POOL操作以高效的方式下采样。
一般来说,你的CONV层应该使用较小的过滤器尺寸,如3 × 3和5 × 5。微小的1 × 1滤波器用于学习局部特性,但只在更先进的网络架构中使用。较大的滤波器尺寸,如7 × 7和11 × 11可以用作网络中的第一个CONV层(为了减少空间输入尺寸,只要你的图像足够大于> 200 × 200像素);然而,在这个初始的CONV层之后,过滤器的尺寸应该急剧下降,否则你会很快地减少你的体积的空间尺寸。
通常你也会在CONV层中使用S = 1的stride,至少对于较小的空间输入量(接受较大输入量的网络,在第一个CONV层中使用S >= 2的stride)。使用S = 1的stride使我们的CONV层学习滤波器,而POOL层负责下采样。然而,请记住,并不是所有的网络架构都遵循这种模式——有些架构完全跳过了max pooling,而依赖于CONV stride来减少容量大小。
我个人的偏好是在CONV层上应用零填充,以确保输出维度大小与输入维度大小匹配——这个规则的唯一例外是,如果我想通过卷积有意地减少空间维度。在实践中,当多个CONV层相互叠加时,应用零填充也被证明可以提高分类精度。正如我们将在本书后面看到的,诸如Keras这样的库可以自动为您计算零填充,使构建CNN架构更加容易。
我个人的第二个建议是使用POOL层(而不是CONV层)减少输入的空间维度,至少在您对构建自己的CNN架构更有经验之前是这样。一旦你达到了这一点,你应该开始尝试使用CONV层来减少空间输入大小,并尝试从你的架构中移除最大的池化层。
最常见的是,你会看到最大池化应用于2 × 2的接受域大小和S = 2的跨度。在网络架构的早期,您可能还会看到一个3 × 3的接收域,以帮助减小图像大小。接收域大于3的情况非常少见,因为这些操作对它们的输入有很大的破坏性。
批处理规范化是一个昂贵的操作,它可以花费两倍或三倍的时间来训练你的CNN;然而,我建议在几乎所有的情况下使用BN。虽然BN确实减慢了训练时间,但它也倾向于“稳定”训练,使它更容易调优其他超参数(当然,也有一些例外——我详细介绍了ImageNet Bundle中的一些“异常架构”)。
我还将批量标准化放在激活之后,这在深度学习社区中已经变得很普遍,尽管它违背了最初的Ioffe和Szegedy的论文[123]。将BN插入到上面的常用层架构中,它们变成:
在softmax分类器之前,你不能应用批量归一化,因为在这一点上,我们假设我们的网络已经在体系结构的早期学习了它的鉴别特征。
Dropout (DO)通常应用于FC层之间,Dropout概率为50%——你应该考虑在几乎所有你构建的架构中应用Dropout。虽然并不总是执行,但我也喜欢在POOL和CONV层之间包含退出层(概率非常小,10-25%)。由于CONV层的局部连通性,这里的dropout效果不太好,但我经常发现它在对抗过度合身时很有帮助。
记住这些规则的经验,你可以减少你的头痛当构建CNN架构因为你CONV层将保留输入大小而减少的池层照顾卷的空间维度,最终导致FC层和最后输出分类。
一旦你掌握了构建卷积神经网络的“传统”方法,你就应该开始探索完全不使用最大池操作,只使用CONV层来减少空间维度,最终导致平均池而不是FC层——这些更高级的架构技术在ImageNet Bundle中被涵盖。
为了回答这个问题,我们首先需要区分网络中的个体过滤器和最终训练的网络。CNN中的单个过滤器并不是不随图像旋转方式的变化而变化的——我们在ImageNet Bundle的第12章中演示了这一点,在那里我们使用从CNN中提取的特征来确定图像的方向。
作为一个整体,CNN会学习过滤器,当一个模式以特定的方向呈现时,过滤器就会启动。在左边,数字9已旋转≈10◦。这种旋转类似于节点3,它已经知道了数字9以这种方式旋转时的样子。这个节点将比其他两个节点有更高的激活率——max池操作将检测到这一点。在右边我们有第二个例子,只是这次9被旋转≈−45◦,导致第一个节点有最高的激活(图深受Goodfellow等人的启发。[10])。
//截止到2022.1.16日晚上17:39
截止到P196
//2022.1.17日上午10:59开始
然而,作为一个整体,CNN可以学习过滤器,当一个模式以特定的方向呈现时,过滤器就会启动。例如,考虑图11.12,它由Goodfellow等人[10]改编并启发于深度学习。
在这里,我们看到数字“9”(底部)与CNN已经学会的一套滤镜(中间)一起呈现给CNN。由于CNN内部有一个过滤器,它已经“了解”了“9”是什么样子,旋转10度,它就会发射并发出强烈的激活。这个大的激活是在池化阶段捕获的,并最终报告为最终的分类。
第二个示例也是如此(图11.12,左)。在这里,我们看到“9”被旋转了- 45度,由于CNN有一个过滤器,它知道了“9”被旋转了- 45度时是什么样子,神经元就被激活和触发了。同样,这些过滤器本身并不是旋转不变的——只是CNN已经知道了在训练集中存在的小旋转下的“9”是什么样子。
除非您的训练数据包括在整个360度范围内旋转的数字,否则您的CNN不是真正的旋转不变量(再次说明,这一点在ImageNet Bundle的第12章中演示了)。
同样的道理也适用于缩放——过滤器本身并不是缩放不变的,但是很有可能你的CNN已经学会了一套过滤器,当模式存在于不同的尺度时,就会触发它。我们还可以通过在测试时在不同的尺度和作物下向cnn展示我们的示例图像来“帮助”cnn保持比例不变,然后对结果进行平均(有关作物平均以提高分类精度的更多细节,请参阅实践者Bundle的第10章)。
平移不变性;然而,这是CNN所擅长的。请记住,filter在输入时从左到右和从上到下滑动,当它遇到特定的边缘区域、角或颜色斑点时将被激活。在池操作期间,会发现这个较大的响应,因此会有更大的激活,从而“打败”它的所有邻居。因此,CNN可以被视为“不关心”一个激活在哪里被激活,简单地说,它确实被激活了——并且,通过这种方式,我们自然地在CNN内部处理翻译。
在本章中,我们参观了卷积神经网络的概念(cnn)。我们从讨论什么是卷积和互相关开始,以及这些术语在深度学习文献中是如何互换使用的。
为了更深入地理解卷积,我们使用Python和OpenCV手工实现了卷积。然而,传统的图像处理操作需要我们手工定义我们的核,并且是特定于给定的图像处理任务(例如,平滑,边缘检测等)。使用深度学习,我们可以学习这些类型的过滤器,然后堆叠在一起,自动发现高级概念。我们将这种基于低级输入的高级特征的叠加和学习称为卷积神经网络的组成性。
cnn是通过堆叠一系列层来构建的,其中每一层负责一个给定的任务。CONV层将学习一组K个卷积滤波器,每个滤波器的大小为F × F像素。然后,我们在CONV层之上应用激活层,以获得非线性转换。当输入量流经网络时,池层有助于减少输入量的空间维度。
一旦输入量足够小,我们就可以应用FC层,这是第12章中我们传统的点积层,最终将其输入到softmax分类器中,用于我们最终的输出预测。
批处理标准化层用于通过计算小批处理的平均值和标准偏差来对CONV或激活层的输入进行标准化。然后,dropout层可以应用于随机断开节点从给定输入到输出的连接,这有助于减少过拟合。
最后,通过回顾您可以用来实现自己的网络的常见CNN架构,我们结束了本章。在下一章中,我们将基于上面提到的层模式,在Keras中实现你的第一个CNN,即ShallowNet。以后的章节将讨论更深层次的网络架构,如具有创新意义的LeNet架构[19]和VGGNet架构的变体[95]。
现在我们已经回顾了卷积神经网络的基本原理,我们准备使用Python和Keras实现我们的第一个CNN。在本章的开始,我们将快速回顾一下Keras配置,您在构建和训练自己的cnn时应该牢记这些配置。
然后我们将实现ShallowNet,顾名思义,它是一个非常浅的CNN,只有一个CONV层。然而,不要让这个简单的网络欺骗了你——正如我们的结果所显示的那样,在CIFAR-10和Animals数据集上,与我们在本书中所介绍的任何其他方法相比,ShallowNet能够获得更高的分类精度。
12.1 Keras配置和转换图像到数组
在我们实现sharallownet之前,我们首先需要回顾一下keras。json配置文件,以及该文件中的设置将如何影响你如何实现自己的cnn。我们还将实现第二个图像预处理程序ImageToArrayPreprocessor,它接受输入的图像,然后将其转换为Keras可以使用的NumPy数组。
12.1.1 理解keras。json配置文件
当你第一次将Keras库导入你的Python shell/执行一个导入Keras的Python脚本时,在幕后Keras会生成一个Keras。您的主目录中的Jsonfile文件。你可以在∼/.keras/keras.json中找到这个配置文件。
json文件内容:
您将注意到这个json编码的字典有四个键和四个对应的值。在Keras库的不同位置使用epsilon值,以防止被零除错误。使用默认值1e-07,不应更改。然后我们有定义浮点精度的floatx值——将该值保留在float32是安全的
最后两个配置image_data_format和backend是非常重要的。默认情况下,Keras库使用TensorFlow数值计算后端。我们也可以简单地使用Theano代替tensorflow来使用Theano。
在开发自己的深度学习网络以及将它们部署到其他机器时,您需要记住这些后端。Keras在抽象后端方面做得很好,允许您编写与任意后端兼容的深度学习代码(当然将来还会有更多的后端),在大多数情况下,您会发现两个计算后端都能给出相同的结果。如果你发现你的结果不一致或者你的代码返回奇怪的错误,首先检查你的后端,并确保设置是你所期望的。
最后,我们有image_data_format,它可以接受两个值:channels_last或channels_first。正如我们在本书的前几章中所知道的,通过OpenCV加载的图像以(行,列,通道)顺序表示,这是Keras调用的channels_last,因为通道是数组中的最后一个维度。
或者,我们可以将image_data_format设置为channels_first,其中输入图像表示为(channels,行,列)—注意通道的数量是数组中的第一个维度。
为什么是两个设置?在Theano社区,用户倾向于使用频道优先排序。然而,当TensorFlow发布时,他们的教程和示例使用了通道的最后排序。当使用Keras作为与Theano兼容的代码时,这种差异造成了一些问题,因为它可能与TensorFlow不兼容,这取决于程序员如何构建他们的网络。因此,Keras引入了一个名为img_to_array的特殊函数,它接受一个输入图像,然后根据image_data_format设置正确地排列通道。
通常,你可以将image_data_format设置为channels_last,而不管后端是什么,Keras会为你处理维度排序;然而,我想提醒您注意这种情况,以防您正在使用遗留的Keras代码,并注意到使用了不同的图像通道顺序。
12.1.2 图像到数组的预处理器
如上所述,Keras库提供了img_to_array函数,该函数接受输入图像,然后根据image_data_format设置对通道进行正确排序。我们将把这个函数包装在一个名为ImageToArrayPreprocessor的新类中。创建一个带有特殊预处理函数的类,就像我们在第7章中创建SimplePreprocessor来调整图像大小那样,将允许我们创建预处理“链”来有效地为训练和测试准备图像。
然后在第5-7行为ImageToArrayPreprocessor类定义构造函数。构造函数接受一个名为dataFormat的可选参数。此值默认为None,表示keras内部的设置。应该使用Json。我们也可以显式地提供一个channels_first或channels_last字符串,但最好让Keras根据配置文件选择使用哪个图像尺寸顺序。
上述代码中的预处理函数:
定义一个类来处理这种类型的图像预处理,而不是简单地在每个图像上调用img_to_array的好处是,我们现在可以在从磁盘加载数据集时将预处理程序链接在一起。
请注意我们的图像预处理程序是如何链接在一起的,并将按顺序应用。对于数据集中的每一张图像,我们首先应用SimplePreprocessor将其大小调整为32 × 32像素。一旦图像被调整大小,ImageToArrayPreprocessor将被应用于处理图像通道的排序。这个图像处理管道如图12.1所示。
以这种方式将简单的预处理器链接在一起,每个预处理器负责一个小任务,这是一种构建可扩展的深度学习库的简单方法,用于对图像进行分类。我们将在下一节中使用这些预处理器,并在实践者Bundle和ImageNet Bundle中定义更高级的预处理器。
在本节中,我们将实现浅网架构。顾名思义,ShallowNet架构只包含几个层——整个网络架构可以总结为:INPUT => CONV => RELU => FC
这个简单的网络架构将允许我们使用Keras库来实现卷积神经网络。在实现了浅网之后,我将把它应用到Animals和CIFAR-10数据集上。正如我们的结果将证明的那样,cnn能够显著地优于本书中讨论的先前的图像分类方法。
第2-7行导入所需的Python包。Conv2D类是在11.1节中讨论的卷积层的Keras实现。然后是Activation类,顾名思义,它处理对输入应用激活函数。Flatten类在将输入输入到Dense层(即完全连接的)之前,将我们的多维体“扁平化”为一维数组。
在第9行,我们定义了ShallowNet类,然后在第11行定义了一个构建方法。我们在本书中实现的每个CNN都有一个构建方法——这个函数将接受一些参数,构建网络架构,然后将其返回给调用函数。在本例中,我们的构建方法需要四个参数:
然后,我们在第15行将inputShape初始化为网络,假设“通道最后”排序。第18和19行检查Keras后端是否设置为“channelsfirst”,如果设置为“channelsfirst”,则更新inputShape。通常的做法是为您构建的几乎每个CNN都包含第15-19行,从而确保无论用户如何订购其图像的频道,您的网络都将正常工作。
开始构建ShallowNet浅层架构:
在第22行,我们定义了第一个(也是唯一的一个)卷积层。这一层将有32个滤镜(K),每个都是3 × 3(即正方形F ×滤镜)。我们将应用相同的填充以确保卷积操作的输出大小与输入匹配(使用相同的填充并不是严格必要的对于这个例子来说,这是一个从现在开始养成的好习惯)。卷积之后,我们在第24行应用ReLU激活。
为了应用我们的全连接层,我们首先需要将多维表示扁平化为一维列表。扁平化操作由第27行上的Flatten调用处理。然后,使用与输出类标签相同数量的节点创建稠密层(第28行)。第29行应用softmax激活函数,它将为我们提供每个类的类标签概率。浅网架构在第32行返回给调用函数。
既然已经定义了ShallowNet,我们可以继续创建实际的“驱动脚本”,用于加载数据集、预处理数据集,然后训练网络。我们将看两个利用了浅网的例子- Animals和CIFAR-10。
略
第2-8行导入所需的Python包。然后,我们加载CIFAR-10数据集(预分裂到训练和测试集),然后将图像像素强度缩放到范围[0,1]。因为CIFAR-10的图像经过预处理,并且通道排序是在cifar10内部自动处理的。Load_data,我们不需要应用任何定制的预处理类。
然后,我们的标签在第18-20行被一热编码为向量。我们还在第23行和第24行上初始化CIFAR-10数据集的标签名。
然后训练网络:
第28行以0.01的学习速率初始化SGD优化器。然后在第29行使用宽度为32,高度为32,深度为3(因为cifar10图像有三个通道)构建浅网。我们将classes设为10,因为,顾名思义,在CIFAR-10数据集中有10个类。模型在第30行和第31行上编译,然后在第35行和第36行上训练了40个时代。
对浅网进行评估的方式与我们之前使用动物数据集的例子完全相同:
略
同样,由于较浅的网络架构和相对较小的数据集,epoch非常快。使用我的GPU,我获得了5秒的epoch,而我的CPU为每个epoch花费了22秒。
经过40个时代的评估,我们发现它在测试集上获得了60%的准确率,比之前使用简单神经网络的57%的准确率有所提高。
更重要的是,在图12.3中绘制我们的损失和准确性,让我们对训练过程有了一些了解,证明了我们的验证损失不会急剧上升。我们的训练和测试的损失/准确性开始偏离过去的时代10。再一次地,这可以归因于更大的学习率以及我们没有使用方法去对抗过拟合的事实(正则化参数,退出,数据扩充等)。
众所周知,由于低分辨率训练样本的数量有限,CIFAR-10数据集很容易过拟合。随着我们越来越习惯构建和训练我们自己的自定义卷积神经网络,我们将发现提高CIFAR-10分类精度的方法,同时减少过拟合。
12.2 总结
在本章中,我们实现了我们的第一个卷积神经网络架构,ShallowNet,并在Animals和CIFAR-10数据集上训练它。ShallowNet在动物分类上获得了71%的准确率,比我们之前使用简单的前馈神经网络的最佳分类准确率提高了12%。
当应用于cifar10时,使用简单的多层神经网络时,浅网达到了60%的准确率,比之前最好的准确率提高了57%(而且没有显著的过拟合)。
ShallowNet是一个非常简单的CNN,它只使用了一个CONV层-进一步的精度可以通过使用多组CONV => RELU => POOL操作来训练更深层次的网络。
上述实验代码详见本人github地址:
TheWangYang/Code_For_Deep_Learning_for_Computer_Vision_with_Python: A code repository for Deep Learning for Computer Vision with Python. (github.com)