深度学习与计算机视觉教程-全-
深度学习与计算机视觉教程(全)
一、计算机视觉和深度学习简介
远见是上帝给人类最好的礼物。
从我们出生开始,视觉就允许我们发展有意识的思维。颜色、形状、物体和面孔都是我们世界的组成部分。这种自然的礼物对我们的感官来说非常重要。
计算机视觉是允许机器复制这种能力的能力之一。利用深度学习,我们正在增强我们的指挥能力,并在这一领域取得进展。
这本书将从深度学习的角度审视计算机视觉的概念。我们将研究神经网络的基本构件,通过基于案例研究的方法开发实用的用例,并比较和对比各种解决方案的性能。我们将讨论最佳实践,分享行业中遵循的技巧和见解,让您了解常见的陷阱,并开发设计神经网络的思维过程。
在整本书中,我们引入了一个概念,详细探讨了它,然后围绕它开发了一个 Python 用例。由于一章首先建立深度学习的基础,然后是它的实用用法,完整的知识使你能够设计一个解决方案,然后开发神经网络,以更好地做出决策。
为了更好地理解,需要一些 Python 和面向对象编程概念的知识。对数据科学有基本到中级的理解是可取的,尽管不是必要的要求。
在这一介绍性章节中,我们将使用 OpenCV 和深度学习来开发图像处理的概念。OpenCV 是一个很棒的库,广泛应用于机器人、人脸识别、手势识别、增强现实等等。此外,深度学习为开发图像处理用例提供了更高的复杂性和灵活性。我们将在本章中讨论以下主题:
-
使用 OpenCV 进行图像处理
-
深度学习的基础
-
深度学习是如何工作的
-
流行的深度学习库
1.1 技术要求
整本书我们都在用 Python 开发所有的解决方案;因此,需要安装最新版本的 Python。
所有的代码、数据集和各自的结果都被检查到位于 github 的代码库中。com/a press/computer-vision-using-deep-learning/tree/main/chapter 1
。建议您与我们一起运行所有代码并复制结果。这将加强你对概念的理解。
1.2 使用 OpenCV 进行图像处理
图像也像任何其他数据点一样。在我们的电脑和手机上,它作为一个对象或图标出现在。jpeg,。bmp 还有。png 格式。因此,对于人类来说,以行列结构来可视化它变得很困难,就像我们可视化任何其他数据库一样。因此,它通常被称为非结构化数据。
为了让我们的计算机和算法分析图像并对其进行处理,我们必须以整数的形式表示图像。因此,我们一个像素一个像素地处理图像。数学上,表示每个像素的方法之一是 RGB(红、绿、蓝)值。我们使用这些信息来进行图像处理。
Info
获取任何颜色的 RGB 最简单的方法是在 Windows 操作系统中用画图打开它。将鼠标悬停在任何颜色上,获取相应的 RGB 值。在 Mac OS 中,您可以使用数码色度计。
深度学习允许我们开发使用传统图像处理技术解决的更加复杂的用例。例如,使用 OpenCV 也可以检测人脸,但要能够识别人脸需要深度学习。
在使用深度学习开发计算机视觉解决方案的过程中,我们首先准备我们的图像数据集。在准备过程中,我们可能需要对图像进行灰度处理,检测轮廓,裁剪图像,然后将它们输入神经网络。
OpenCV 是这类任务最著名的库。作为第一步,让我们开发这些图像处理方法的一些构件。我们将使用 OpenCV 创建三个解决方案。
Note
转到 www.opencv.org
并按照那里的说明在你的系统上安装 OpenCV。
用于解决方案的图像是常见的图像。建议您检查代码,并遵循完成的一步一步实现。我们将检测图像中的形状、颜色和人脸。
让我们一起进入令人兴奋的图像世界吧!
1.2.1 使用 OpenCV 进行颜色检测
当我们想到一幅图像时,它是由形状、大小和颜色组成的。具有检测图像中的形状、大小或颜色的能力可以使许多过程自动化并节省大量工作。在第一个例子中,我们将开发一个“颜色检测系统”
颜色检测在制造、汽车、电力、公用事业等领域和行业中具有广泛的用途。颜色检测可用于寻找正常行为的中断、失败和中断。我们可以训练传感器根据颜色做出特定的决定,并在需要时发出警报。
图像用像素表示,每个像素由 0 到 255 范围内的 RGB 值组成。我们将使用这个属性来识别图像中的蓝色(图 1-1 )。您可以更改蓝色的相应值,并检测任何选择的颜色。
请遵循以下步骤:
-
打开 Python Jupyter 笔记本。
-
首先加载必要的库,
numpy
和OpenCV
。import numpy as np import cv2
-
加载图像文件。
image = cv2.imread('Color.png')
图 1-1
用于颜色检测的原始图像。显示的图像有四种不同的颜色,OpenCV 解决方案将分别检测它们
-
现在让我们将原始图像转换成 HSV(色调饱和度值)格式。它使我们能够从饱和和伪照明中分离出来。允许我们这样做。
hsv_convert = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
-
在此定义颜色的上限和下限。我们正在检测蓝色。从
numpy
库中,我们给出了蓝色各自的范围。lower_range = np.array([110,50,50]) upper_range = np.array([130,255,255])
-
现在,让我们检测蓝色,并将其从图像的其余部分分离出来。
mask_toput = cv2.inRange(hsv_convert, lower_range, upper_range) cv2.imshow('image', image) cv2.imshow('mask', mask_toput) while(True): k = cv2.waitKey(5)& 0xFF if k== 27: break
该代码的输出将如图 1-2 所示。
图 1-2
颜色检测系统的输出。我们想要检测蓝色,蓝色被检测并与图像的其余部分分离
可以看到,蓝色以白色突出显示,而图像的其余部分为黑色。通过在步骤 5 中更改范围,您可以检测自己选择的不同颜色。
完成颜色后,是时候检测图像中的形状了;我们开始吧!
1.3 使用 OpenCV 的形状检测
就像我们在上一节中检测蓝色一样,我们将检测图像中的三角形、正方形、矩形和圆形。形状检测允许您分离图像中的部分并检查图案。颜色和形状检测使解决方案变得非常具体。可用性存在于安全监控、生产线、汽车中心等等。
对于形状检测,我们得到每个形状的轮廓,检查元素的数量,然后进行相应的分类。例如,如果这个数字是三,它就是一个三角形。在本解决方案中,您还将观察如何对图像进行灰度处理并检测轮廓。
按照以下步骤检测形状:
-
首先导入库。
import numpy as np import cv2
-
加载如图 1-3 所示的原始图像。
shape_image = cv2.imread('shape.png')
图 1-3
用于检测圆形、三角形和矩形三种形状的原始输入图像
-
接下来将图像转换为灰度。因为 RGB 是三维的,而灰度是二维的,所以进行灰度缩放是为了简单,并且转换为灰度简化了解决方案。这也使得代码高效。
gray_image = cv2.cvtColor(shape_image, cv2.COLOR_BGR2GRAY) ret,thresh = cv2.threshold(gray_image,127,255,1)
-
找到图像中的轮廓。
-
使用
approxPolyDP
尝试逼近每个轮廓。此方法返回检测到的轮廓中的元素数量。然后,我们根据轮廓中元素的数量来决定形状。如果值为三,则为三角形;如果它是四,它是正方形;诸如此类。for cnt in contours: approx = cv2.approxPolyDP(cnt,0.01*cv2.arcLength(cnt,True),True) print (len(approx)) if len(approx)==3: print ("triangle") cv2.drawContours(shape_image,[cnt],0,(0,255,0),-1) elif len(approx)==4: print ("square") cv2.drawContours(shape_image,[cnt],0,(0,0,255),-1) elif len(approx) > 15: print ("circle") cv2.drawContours(shape_image,[cnt],0,(0,255,255),-1) cv2.imshow('shape_image',shape_image) cv2.waitKey(0) cv2.destroyAllWindows()
-
The output of the preceding code is shown in Figure 1-4.
图 1-4
颜色检测系统的输出。圆形以黄色显示,正方形以红色显示,三角形以绿色显示
contours,h = cv2.findContours(thresh,1,2)
现在,您可以检测任何图像中的形状。我们已经检测到一个圆形、一个三角形和一个正方形。一个很好的挑战将是检测五边形或六边形;你愿意吗?
现在让我们做一些更有趣的事情吧!
1.3.1 使用 OpenCV 的人脸检测
人脸检测并不是一项新功能。每当我们看一张照片时,我们都能很容易地认出一张脸。我们的手机摄像头会在一张脸周围画出方框。或者在社交媒体上,围绕一张脸创建一个方形框。叫做人脸检测。
人脸检测是指在数字图像中定位人脸。人脸检测不同于人脸识别。在前一种情况下,我们只检测图像中的人脸,而在后一种情况下,我们也给人脸起名字,也就是说,照片中的人是谁。
大多数现代相机和手机都有检测人脸的内置功能。使用 OpenCV 可以开发类似的解决方案。它更容易理解和实现,并且是使用Haar-cascade
算法构建的。在 Python 中使用这种算法时,我们将突出照片中的人脸和眼睛。
Haar-cascade classifier
用于检测图像中的人脸和其他面部属性,如眼睛。这是一种机器学习解决方案,其中对大量图像进行训练,这些图像中有人脸和没有人脸。分类器学习各自的特征。然后我们使用相同的分类器为我们检测人脸。我们不需要在这里做任何训练,因为分类器已经训练好了,可以使用了。也省时省力!
Info
使用基于 Haar 的级联分类器的对象检测是由 Paul Viola 和 Michael Jones 在 2001 年他们的论文“使用简单特征的增强级联的快速对象检测”中提出的。建议您浏览这篇开创性的论文。
按照以下步骤检测人脸:
-
首先导入库。
import numpy as np import cv2
-
加载分类器 xml 文件。的。xml 文件由 OpenCV 设计,由叠加在正面图像上的负面人脸的训练级联生成,因此可以检测人脸特征。
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
-
接下来,加载图像(图 1-5 )。
img = cv2.imread('Vaibhav.jpg')
图 1-5
使用 Haar-cascade 解决方案进行人脸检测时使用的人脸原始输入图像
-
将图像转换为灰度。
-
执行以下代码来检测图像中的人脸。如果找到任何人脸,我们将检测到的人脸的位置作为 Rect(x,y,w,h)返回。随后,在脸部检测眼睛。
faces = face_cascade.detectMultiScale(gray, 1.3, 5) for (x,y,w,h) in faces: image = cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,0),2) roi_gr = gray[y:y+h, x:x+w] roi_clr = img[y:y+h, x:x+w] the_eyes = eye_cascade.detectMultiScale(roi_gr) for (ex,ey,ew,eh) in the_eyes: cv2.rectangle(roi_clr,(ex,ey),(ex+ew,ey+eh),(0,255,0),2) cv2.imshow('img',image) cv2.waitKey(0) cv2.destroyAllWindows()
-
The output is shown in Figure 1-6. Have a look how a blue box is drawn around the face and two green small boxes are around the eyes.
图 1-6
在图像中检测到的脸部和眼睛;眼睛周围有一个绿色方框,面部周围有一个蓝色方框
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
人脸检测允许我们在图像和视频中找到人脸。这是人脸识别的第一步。它广泛用于安全应用、考勤监控等。我们将在后续章节中使用深度学习开发人脸检测和识别。
我们已经学习了图像处理的一些概念。是时候检查和学习深度学习的概念了。这些是你踏上旅程的基石。
1.4 深度学习的基础
深度学习是机器学习的一个子领域。深度学习中的“深度”是具有连续的表现层;因此,模型的深度指的是人工神经网络(ANN)模型中的层数,本质上称为深度学习。
这是一种新颖的方法来分析历史数据,并从不断增加的有意义的表示的连续层中学习。深度学习项目中的典型过程类似于机器学习项目,如下所述,如图 1-7 所示。
图 1-7
从数据发现到最终开发解决方案的端到端机器学习过程。这里详细讨论了所有步骤,并在本书的第八章再次讨论
-
数据接收:原始数据文件/图像/文本等被接收到系统中。它们用作训练和测试网络的输入数据。
-
数据清理:在这一步,我们清理数据。通常,结构化数据集中存在太多噪音,如垃圾值、重复值、空值和离群值。所有这些数据点都必须在这个阶段进行处理。对于图像,我们可能需要去除图像中不必要的噪声。
-
数据准备:我们为训练准备好数据。在这一步中,可能需要新的派生变量,或者如果我们正在处理图像数据集,我们可能需要旋转/裁剪图像。
-
探索性数据分析:我们执行初步分析,以快速了解我们的数据集。
-
网络设计和训练模型:我们在这里设计我们的神经网络,并决定隐藏层、节点、激活函数、损失函数等的数量。然后训练网络。
-
检查准确性和迭代:我们测量网络的准确性。我们用混淆矩阵、AUC 值、精确度、召回率等等来衡量。然后我们调整超参数并进一步调整。
-
最终的模型呈现给企业,我们得到反馈。
-
我们迭代该模型,并根据收到的反馈对其进行改进,并创建最终解决方案。
-
该模型被部署到生产中。然后定期对其进行维护和刷新。
在机器学习项目中,通常会遵循这些步骤。我们将在本书的最后一章中详细研究所有这些步骤。现在是了解神经网络的好时机。
1.4.1 神经网络背后的动机
人工神经网络(ann)据说是受人脑工作方式的启发。当我们看到一幅图片时,我们会把它和一个标签联系起来。我们训练我们的大脑和感官,当我们再次看到一张图片时,能够识别它,并正确地给它贴上标签。
ANN 通过学习或接受培训来学习执行类似的任务。这是通过查看各种历史数据点(如交易数据或图像)的示例来完成的,并且大多数情况下没有针对特定规则进行编程。例如,为了区分一辆汽车和一个人,一个人工神经网络在开始时对每一类的属性没有预先的理解和知识。然后,它从训练数据中生成属性和识别特征。然后,它学习这些属性,并在以后使用它们进行预测。
正式地说,人工神经网络环境中的“学习”是指调整网络内部的权重和偏差,以提高网络的后续准确性。一个显而易见的方法是减少误差项,它只不过是实际值和预测值之间的差异。为了测量错误率,我们定义了一个成本函数,在网络的学习阶段对其进行严格评估。我们将在下一节详细研究所有这些术语。
典型的神经网络如图 1-8 所示。
图 1-8
具有输入层、隐藏层和输出层的典型神经网络。每一层里面都有一些神经元。隐藏层充当网络的心脏和灵魂。输入层接受输入数据,输出层负责生成最终结果
前面显示的神经网络有三个输入单元、两个各有四个神经元的隐藏层和一个最终输出层。
现在让我们在随后的章节中讨论神经网络的各种组件。
1.4.2 神经网络中的层
基本的神经网络架构主要由三层组成:
-
输入层:顾名思义,它接收输入数据。考虑将原始图像/处理过的图像输入到输入层。这是神经网络的第一步。
-
隐藏层:它们是网络的核心和灵魂。所有的处理、特征提取、学习和训练都在这些层中完成。隐藏层将原始数据分解为属性和要素,并了解数据的细微差别。这种学习稍后在输出层中用于做出决定。
-
输出层:网络中的决策层和最终部分。它接受来自前面隐藏层的输出,然后对最终分类做出判断。
网络中最精细的构件是神经元。神经元是整个魔法发生的地方,这是我们接下来要讨论的。
1.4.3 神经元
神经元或人工神经元是神经网络的基础。整个复杂的计算只发生在一个神经元中。网络中的一层可以包含一个以上的神经元。
神经元接收来自先前层或输入层的输入,然后处理信息并共享输出。输入数据可以是来自前一个神经元的原始数据或经过处理的信息。然后,神经元将输入与它们自己的内部状态相结合,并使用激活函数达到一个值(我们稍后将讨论激活函数)。随后,使用 output 函数生成输出。
一个神经元可以被认为是图 1-9 ,它接收各自的输入并计算输出。
图 1-9
一种神经元的表示,它接收来自前几层的输入,并使用激活函数来处理和给出输出。它是神经网络的组成部分
神经元的输入是从其前身的输出和它们各自的连接中接收的。接收到的输入被计算为加权和,并且通常还添加了偏差项。这是一个传播函数的功能。如图 1-9 所示,f 为激活函数,w 为权重项,b 为偏差项。计算完成后,我们接收输出。
例如,输入训练数据将具有原始图像或经处理的图像。这些图像将被输入到输入层。数据现在传输到隐藏层,在那里进行所有的计算。这些计算是由每一层的神经元完成的。
输出是需要完成的任务,例如,识别一个对象或者如果我们想要分类一个图像等等。
正如我们所讨论的,在很大程度上,神经网络能够自己提取信息,但我们仍然必须为训练网络的过程初始化一些参数。它们被称为超参数,我们接下来将对其进行讨论。
超参数
在训练网络期间,算法不断学习原始数据的属性。但是有一些参数是网络不能自己学习的,需要初始设置。超参数是人工神经网络不能自己学习的那些变量和属性。这些是决定神经网络结构的变量,以及对训练网络有用的各个变量。
超参数是在网络的实际训练之前设置的。学习速率、网络中隐藏层的数量、每层中神经元的数量、激活函数、时期的数量、批量大小、丢失和网络权重初始化都是超参数的例子。
调整超参数是基于其性能为超参数选择最佳值的过程。我们在验证集上测量网络的性能,然后调整超参数,然后重新评估和重新弱化,这个过程继续进行。我们将在下一节研究各种超参数。
1.4.5 安的连接和重量
人工神经网络由各种连接组成。每个连接都旨在接收输入并提供计算的输出。这个输出作为下一个神经元的输入。
此外,每个连接被分配一个代表其各自重要性的权重。值得注意的是,一个神经元可以有多个输入和输出连接,这意味着它可以接收输入和传递多个信号。
下一项也是一个重要的组成部分——偏差项。
偏置项
偏差就像给线性方程加上一个截距值。它是网络中额外的或附加的参数。
理解偏差的最简单方法是根据下面的等式:
y = mx + c
如果我们没有常数项 c,方程将通过(0,0)。如果我们有常数项 c,我们可以期待一个更好的拟合机器学习模型。
正如我们在图 1-10 中看到的,在左边,我们有一个没有偏置项的神经元,而在右边,我们增加了一个偏置项。因此,偏置允许我们调整输出以及输入的加权和。
图 1-10
偏差项有助于更好地拟合模型。左边没有偏差项,右边有偏差项。请注意,偏差项具有与之相关的权重
因此,偏差项类似于线性方程中的常数项,有助于更好地拟合数据。
我们现在将在下一节研究神经网络中最重要的属性之一——激活函数。
激活功能
激活函数的主要作用是决定神经元/感知器是否应该激活。它们在稍后阶段的网络训练期间在调整梯度方面起着核心作用。激活功能如图 1-9 所示。它们有时被称为传递函数。
激活函数的非线性行为允许深度学习网络学习复杂的行为。你将在第二章中考察什么是非线性行为。我们现在将研究一些常用的函数。
1.4.7.1 Sigmoid 函数
Sigmoid 函数是一个有界的单调数学函数。它是一个具有 S 形曲线的可微函数,它的一阶导数函数是钟形的。它有一个非负导数函数,是为所有实输入值定义的。如果神经元的输出值在 0 和 1 之间,则使用 Sigmoid 函数。
数学上,sigmoid 函数如等式 1-1 所示。
(方程式 1-1)
在图 1-11 中可以看到一个 sigmoid 函数的图形。注意函数的形状以及最大值和最小值。
图 1-11
一个 s 形函数;请注意,它不是以零为中心,其值介于 0 和 1 之间。乙状结肠面临着梯度消失的问题
Sigmoid 函数在复杂的学习系统中有其应用。当特定的数学模型不适合时,您通常会发现使用了 Sigmoid 函数。它通常用于二进制分类和网络的最终输出层。一个 Sigmoid 函数有一个消失梯度的问题,我们将在后面的章节中讨论。
1.4.7.2 双曲正切函数
在数学中,正切双曲函数是可微的双曲函数。它是 Sigmoid 函数的缩放版本。这是一个平滑函数,其输入值在–1 到+1 的范围内。
与 Sigmoid 函数相比,它具有更稳定的梯度,更少的消失梯度问题。双曲正切函数可以表示为图 1-12 并且可以在等式 1-2 中看到。
(方程式 1-2)
还显示了 tanh 的图形表示。注意 Sigmoid 函数和 tanh 函数的区别。观察 tanh 如何是 Sigmoid 函数的缩放版本。
图 1-12
双曲正切函数;注意,它通过零点,是 sigmoid 函数的缩放版本。其值介于–1 和+1 之间。与 sigmoid 类似,tanh 也有渐变消失的问题
双曲正切函数通常用于隐藏层。它使得平均值更接近于零,这使得网络中的下一层的训练更容易。这也被称为以数据为中心的。双曲正切函数可以从 Sigmoid 函数导出,反之亦然。与 sigmoid 类似,双曲正切函数也存在梯度消失的问题,我们将在后续章节中讨论。
我们现在研究最流行的激活函数——ReLU。
1.4.7.3 校正线性单位
校正线性单元或 ReLU 是一个激活函数,它定义了一个自变量的正值。
ReLU 是一个简单的函数,计算成本最低,训练速度也快得多。它是无界的,并且不以零为中心。它在除零以外的所有地方都是可微的。由于 ReLU 函数可以更快地被训练,你会发现它被更频繁地使用。
我们可以检查 ReLU 函数和图 1-13 中的图形。该等式可以在等式 1-3 中看到。请注意,即使是负值,该值也是 0,从 0 开始,该值开始倾斜。
F(x) = max (0,x),即如果为正,则输出为 x,否则为 0(等式 1-3)
图 1-13
ReLU 函数;注意,计算起来很简单,因此训练起来更快。它用于网络的隐藏层。ReLU 比 sigmoid 和 tanh 训练起来更快
由于 ReLU 函数不太复杂,计算成本较低,因此广泛用于隐藏层以更快地训练网络,我们在设计网络时也将使用 ReLU。现在,我们研究用于网络最后一层的 softmax 函数。
1.4.7.4 软件最大值函数
softmax 函数用于神经网络的最后一层,以生成网络输出。输出可以是针对不同类别的图像的最终分类。
softmax 函数计算所有可能性中每个目标类的概率。它是一个激活函数,对于多类分类问题很有用,并强制神经网络输出 1 的和。
例如,如果输入是[1,2,3,4,4,3,2,1],我们取一个 softmax,那么相应的输出将是[0.024,0.064,0.175,0.475,0.024,0.064,0.175]。该输出将最高权重分配给最高值,在本例中为 4。因此它可以用来突出显示最高值。一个更实际的例子是,如果图像的不同类别的数量是汽车、自行车或卡车,softmax 函数将为每个类别生成三个概率。获得最高概率的类别将是预测的类别。
我们已经研究了重要的活化函数。但是也可以有其他的激活函数,比如漏 ReLU、eLU 等等。我们将在整本书中遇到这些激活函数。表 1-1 显示了激活功能的汇总,以供快速参考。
表 1-1
主要激活功能及其各自的详细信息
|激活功能
|
值
|
阳性
|
挑战
|
| --- | --- | --- | --- |
| 乙状结肠的 | [0,1] | ⑴非线性(2)易于使用(3)连续可微(4)单调性并且不会破坏激活 | (1)输出不在零中心(2)消失梯度问题(3)训练缓慢 |
| 双曲正切 | [-1,1] | (1)类似于 sigmoid 函数(2)梯度更强,但优于乙状结肠 | (1)消失梯度问题 |
| 线性单元 | [0,inf] | (1)非线性(2)易于计算,因此训练速度快(3)解决了梯度消失的问题 | (1)仅用于隐藏层(2)可以炸毁激活(3)对于 x<0 的区域,梯度将为零。因此,权重不会得到更新(垂死的 ReLU 问题) |
| 李奇注意到了 | 最大(0,x) | (ReLU 的变体(2)固定死亡继发问题 | (1)不能用于复杂的分类 |
| 埃卢 | [0,inf] | (ReLU 的替代方案(2)输出更加平滑 | (1)可以炸毁激活 |
| Softmax(软件最大值) | 计算概率 | 一般用于输出层 | |
激活功能构成了网络的核心构件。在下一节中,我们将讨论指导网络如何学习和优化训练的学习率。
学习率
对于神经网络,学习速率将定义模型为减少误差而采取的校正步骤的大小。较高的学习率具有较低的准确度,但训练时间较短,而较低的学习率将需要较长的训练时间,但准确度较高。你必须达到它的最佳价值。
具体来说,学习率将决定在网络训练期间对权重的调整。学习速率将直接影响网络收敛并达到全局最小值所需的时间。在大多数情况下,0.01 的学习率是可以接受的。
我们现在将探索训练过程中可能最重要的过程——反向传播算法。
反向传播
我们在上一节学习了学习率。训练过程的目标是减少预测中的误差。虽然学习率定义了减少误差的校正步骤的大小,但是反向传播用于调整连接权重。这些权重基于误差向后更新。随后,重新计算误差,计算梯度下降,并调整各自的权重。
图 1-14 显示了信息从输出层流回隐藏层的反向传播过程。注意,与信息从左向右流动的前向传播相比,信息的流动是向后的。
图 1-14
神经网络中的反向传播。基于该误差,信息从输出反向流动,并且随后重新计算权重
让我们更详细地探索这个过程。
一旦网络做出了预测,我们就可以计算出预期值和预测值之间的误差。这被称为成本函数。基于成本值,神经网络然后调整其权重和偏差,以最接近实际值,或者换句话说,最小化误差。这是在反向传播期间完成的。
Note
建议你刷新微分学,更好的理解反向传播算法。
在反向传播期间,连接的参数被重复和迭代地更新和调整。调整的水平由成本函数相对于这些参数的梯度决定。梯度将告诉我们应该在哪个方向上调整权重以最小化成本。这些梯度是用链式法则计算的。使用链规则,一次计算一个图层的梯度,从最后一个图层到第一个图层反向迭代。这样做是为了避免链规则中中间项的冗余计算。
有时,我们在训练神经网络时会遇到梯度消失的问题。梯度消失问题是当梯度变得接近零时网络的初始层停止学习的现象。它使得网络不稳定,并且网络的初始层将不能学习任何东西。我们将在第六章和第八章中再次探索消失渐变。
我们现在讨论过度配合的问题,这是训练中最常见的问题之一。
过度装配
您知道网络使用训练数据来学习属性和模式。但我们希望我们的机器学习模型在看不见的数据上表现良好,这样我们就可以用它来进行预测。
为了衡量机器学习的准确性,我们必须评估训练和测试数据集的性能。通常,网络可以很好地模拟训练数据,并获得良好的训练准确性,而在测试/验证数据集上,准确性会下降。这叫做过拟合。简单地说,如果网络在训练数据集上工作得很好,但在看不见的数据集上不太好,这被称为过度拟合。
过度拟合是一件讨厌的事,我们必须与之斗争,对吗?为了解决过度拟合问题,您可以使用更多的训练数据来训练您的网络。或者降低网络的复杂性。通过降低复杂性,我们建议减少权重的数量、权重的值或网络本身的结构。
批量标准化和剔除是另外两种减轻过拟合问题的技术。
批量归一化是一种正则化方法。这是对平均值为零、标准差为一的图层的输出进行归一化的过程。它减少了对权重初始化的强调,从而减少了过拟合。
辍学是另一种解决过度适应问题的方法。这是一种正则化方法。在训练过程中,某些层的输出被随机丢弃或忽略。其效果是,对于每种组合,我们得到不同的神经网络。这也使得训练过程很吵。图 1-15 表示辍学的影响。左边的第一个图(图 1-15(i) )是一个标准的神经网络。右边的(图 1-15(二))是退学后的结果。
图 1-15
在退出之前,网络可能会过度拟合。在丢失之后,随机连接和神经元被移除,因此网络不会遭受过度拟合
借助 dropout,我们可以解决网络中的过度拟合问题,并获得一个更强大的解决方案。
接下来,我们将研究使用梯度下降的优化过程。
梯度下降
机器学习解决方案的目的是为我们的。我们希望减少训练阶段的损失,或者最大化精确度。梯度下降有助于达到这个目的。
梯度下降用于寻找函数的全局最小值或全局最大值。这是一种常用的优化技术。我们沿着最陡下降的方向迭代前进,最陡下降的方向由梯度的负值定义。
但是梯度下降在非常大的数据集上运行会很慢。这是因为梯度下降算法的一次迭代预测了训练数据集中的每个实例。因此,很明显,如果我们有成千上万的记录,这将需要很多时间。对于这样的情况,我们有随机梯度下降。
在随机梯度下降中,而不是在批次结束时,为每个训练实例更新系数,因此花费的时间较少。
图 1-16 显示了梯度下降的工作方式。请注意我们是如何朝着全局最小值向下发展的。
图 1-16
梯度下降的概念;注意它是如何达到全局最小值的。训练网络的目的是最小化遇到的错误
我们研究了如何使用梯度下降优化函数。检查机器学习模型有效性的方法是测量预测值与实际值有多远或多近。这是用我们现在讨论的损失来定义的。
损失函数
损失是衡量我们模型准确性的标准。简单来说就是实际值和预测值的差异。用于计算该损失的函数被称为损失函数。
不同的损失函数对相同的损失给出不同的值。由于损耗不同,相应型号的性能也会不同。
对于回归和分类问题,我们有不同的损失函数。交叉熵用于定义优化的损失函数。为了测量实际输出和期望输出之间的误差,通常使用均方误差。一些研究人员建议使用交叉熵误差来代替均方误差。表 1-2 给出了不同损失函数的快速总结。
表 1-2
各种损失函数,可与它们各自的方程和用法一起使用
|损失函数
|
损失方程
|
用于
|
| --- | --- | --- |
| 交叉熵 | -y(log(p) + (1-y) log(1-p)) | 分类 |
| 铰链损耗 | max(0,1- y *f(x)) | 分类 |
| 绝对误差 | y - f(x)| | 回归 |
| 平方误差 | (y - f(x)) 2 | 回归 |
| 胡伯损失 | l=半(y-f(x)),如果|y-f(x) <else′y-f(x)|-2′??〖2〗 | 回归 |
我们现在已经检查了深度学习的主要概念。现在让我们研究一下神经网络是如何工作的。我们将理解不同的层如何相互作用,以及信息如何从一层传递到另一层。
我们开始吧!
1.5 深度学习如何工作?
你现在知道深度学习网络有不同的层次。你也经历了深度学习的概念。你可能想知道这些片段是如何组合在一起并协调整个学习过程的。可以对整个过程进行如下检查:
第一步
您可能想知道图层实际上做什么,图层实现什么,以及图层各自的权重中存储了什么。它只不过是一组数字。从技术上讲,由层实现的变换必须通过其权重来参数化,权重也被称为层的参数。
而学习是什么意思是下一个问题。神经网络的学习是为网络的所有层找到最佳的组合和权重值,以便我们可以达到最佳的准确性。由于深度神经网络可以有许多这样的参数值,我们必须找到所有参数的最佳值。考虑到改变一个价值会影响其他价值,这似乎是一项艰巨的任务。
让我们以图表的方式展示神经网络中的过程(图 1-17 )。检查我们的输入数据层。两个数据变换层各自具有与其相关联的权重。然后我们有了对目标变量 y 的最终预测。
图像被输入到输入层,然后在数据变换层进行变换。
图 1-17
输入数据在数据变换层中进行变换,定义权重,并进行初始预测
第二步
我们已经在第一步创建了一个基本的框架。现在我们必须衡量这个网络的准确性。
我们想要控制神经网络的输出;我们必须比较和对比输出的准确性。
Info
准确性将指我们的预测与实际值的差距。简而言之,我们的预测与真实值的差距是衡量准确性的标准。
精度测量由网络的损耗函数完成,也称为目标函数。损失函数采用网络预测值和真实或实际目标值。这些实际值就是我们期望网络输出的值。损失函数计算距离得分,捕捉网络的表现。
让我们通过添加损失函数和相应的损失分数来更新我们在步骤 1 中创建的图表(图 1-18 )。它有助于衡量网络的准确性。
图 1-18
增加一个损失函数来衡量精度;损失是实际值和预测值之间的差异。在这个阶段,我们根据误差项知道了网络的性能
第三步
如前所述,我们必须最大限度地提高精度或降低损耗。这将使解决方案在预测中更加稳健和准确。
为了不断降低损失,分数(预测-实际)然后被用作反馈信号,以稍微调整权重值,这是由优化器完成的。这项任务由反向传播算法完成,该算法有时被称为深度学习中的中心算法。
最初,一些随机值被分配给权重,因此网络正在实现一系列随机变换。从逻辑上讲,输出与我们预期的完全不同,因此损失分数非常高。毕竟,这是第一次尝试!
但这将会改善。在训练神经网络时,我们会不断遇到新的训练示例。对于每一个新的例子,权重会在正确的方向上调整一点,随后损失分数会降低。我们多次迭代这个训练循环,并且它产生最小化损失函数的最佳权重值。
然后我们实现我们的目标,这是一个训练有素的网络,损失最小。这意味着实际值和预测值非常接近。为了实现完整的解决方案,我们扩大了该机制,如图 1-19 所示。
请注意优化器,它提供定期和连续的反馈以达到最佳解决方案。
图 1-19
优化器给出反馈并优化权重;这就是反向传播的过程。这确保了误差被迭代地减小
一旦我们为我们的网络实现了最佳价值,我们就称我们的网络现在已经训练好了。我们现在可以用它对看不见的数据集进行预测。
现在你已经理解了深度学习的各个组成部分是什么,以及它们如何协同工作。现在是时候检查深度学习的所有工具和库了。
1.5.1 流行的深度学习库
有相当多的深度学习库可用。这些包允许我们以最小的努力更快地开发解决方案,因为大部分繁重的工作都是由这些库完成的。
我们在这里讨论最受欢迎的图书馆。
TensorFlow :由 Google 开发的 TensorFlow (TF)可以说是最流行、应用最广泛的深度学习框架之一。它于 2015 年推出,此后被全球许多企业和品牌使用。
Python 多用于 TF,但 C++、Java、C#、JavaScript、Julia 也可以。您必须在您的系统上安装 TF 库并导入该库。而且已经可以使用了!
Note
进入 www.tensorflow.org/install
,按照说明安装 TensorFlow。
如果对模型架构进行任何修改,必须对 TF 模型进行重新培训。它使用静态计算图进行操作,这意味着我们首先定义图,然后运行计算。
它很受欢迎,因为它是由谷歌开发的。它也可以在 iOS 和 Android 等移动设备上运行。
Keras :对于初学者来说,这是最简单的深度学习框架之一,对于简单概念的理解和原型制作来说,这是非常棒的。Keras 最初于 2015 年发布,是最值得推荐的了解神经网络细微差别的库之一。
Note
进入 https://keras.io
,按照说明安装 Keras。Tf.keras 可以作为 API,在本书中会经常用到。
它是一个成熟的 API 驱动的解决方案。Keras 中的原型制作被简化到了极限。使用 Python 生成器的序列化/反序列化 API、回调和数据流已经非常成熟。Keras 中的大规模模型被简化为单行函数,这使得它成为一个不太可配置的环境。
PyTorch :脸书的大脑儿童 PyTorch 于 2016 年发布,是广受欢迎的深度学习库之一。我们可以在 PyTorch 中使用调试器,比如 pdb 或者 PyCharm。PyTorch 使用动态更新的图形进行操作,并允许数据并行和分布式学习模型。对于小型项目和原型开发,PyTorch 应该是您的选择;然而,对于跨平台解决方案,TensorFlow 是众所周知的更好。
十四行诗 : DeepMind 的十四行诗是使用并在 TF 之上开发的。Sonnet 是为复杂的神经网络应用和架构设计的。
Sonnet 创建对应于神经网络(NN)特定部分的主要 Python 对象。在这之后,这些 Python 对象被独立地连接到计算张量流图。它简化了设计,这是由于创建对象和将它们与图形相关联的过程的分离。此外,拥有高级面向对象库的能力是有利的,因为当我们开发机器学习算法时,它有助于抽象。
MXNet : Apache 的 MXNet 是一个高可扩展性的深度学习工具,简单易用,有详细的文档。MXNet 支持大量的语言,如 C++、Python、R、Julia、JavaScript、Scala、Go 和 Perl。
还有其他的框架,像 Swift、Gluon、Chainer、DL4J 等等;然而,我们在这本书里只讨论了流行的。表 1-3 给出了所有框架的概述。
表 1-3
主要的深度学习框架及其各自的属性
|框架
|
来源
|
属性
|
| --- | --- | --- |
| TensorFlow | 开放源码 | 最流行的,也可以在手机上工作,TensorBoard 提供可视化 |
| 硬 | 开放源码 | API 驱动的成熟解决方案,非常好用 |
| PyTorch | 开放源码 | 允许数据并行,非常适合快速产品构建 |
| 十四行诗 | 开放源码 | 简化设计,创建高级对象 |
| mxnet 系统 | 开放源码 | 高度可扩展,易于使用 |
| 矩阵实验室 | 得到许可的 | 高度可配置,提供部署功能 |
1.6 摘要
深度学习是一种持续的学习体验,需要自律、严谨和投入。你已经迈出了学习旅程的第一步。在第一章中,我们学习了图像处理和深度学习的概念。它们是整本书和你未来道路的基石。我们使用 OpenCV 开发了三种解决方案。
在下一章中,您将深入研究 TensorFlow 和 Keras。您将使用卷积神经网络开发您的第一个解决方案。从设计网络、培训网络到实施网络。所以保持专注!
Review Exercises
-
图像处理的各个步骤是什么?
-
使用 OpenCV 开发一个对象检测解决方案。
-
训练一个深度学习网络的过程是怎样的?
-
什么是过度拟合,我们如何解决它?
-
什么是各种激活功能?
进一步阅读
-
OpenCV 简介:
https://ieeexplore.ieee.org/document/6240859
。 -
用于计算机视觉应用的 OpenCV:
www.researchgate.net/publication/301590571_OpenCV_for_Computer_Vision_Applications
。 -
OpenCV 文档可以在
https://docs.opencv.org/
访问。 -
浏览以下文件:
二、计算机视觉深度学习的具体细节
头脑不是一个需要被填满的容器,而是一团需要被点燃的火。—普鲁塔克
我们人类被赋予了非凡的思维能力。这些力量允许我们区分和辨别,发展新技能,学习新艺术,做出理性的决定。我们的视觉能力没有极限。不管姿势和背景如何,我们都能识别人脸。我们可以区分汽车、狗、桌子、电话等物体,而不管它们的品牌和类型。我们可以识别颜色和形状,并清楚而容易地区分它们。这种力量是周期性和系统性发展的。在我们年轻的时候,我们不断地学习物体的属性,发展我们的知识。这些信息被安全地保存在我们的记忆中。随着时间的推移,这种知识和学习提高。这是一个如此惊人的过程,反复训练我们的眼睛和头脑。经常有人认为,深度学习起源于一种模仿这些非凡能力的机制。在计算机视觉中,深度学习正在帮助我们发现可以用来帮助组织将计算机视觉用于生产目的的能力。深度学习已经发展了很多,仍然有很大的进一步发展空间。
在第一章中,我们从深度学习的基础开始。在第二章中,我们将在这些基础上更深入地理解神经网络的各个层次,并使用 Keras 和 Python 创建一个深度学习解决方案。
我们将在本章中讨论以下主题:
-
什么是张量,如何使用张量流
-
揭秘卷积神经网络
-
卷积神经网络的组件
-
发展用于图像分类的 CNN 网络
2.1 技术要求
本章的代码和数据集上传到本书的 GitHub 链接 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter2
。我们将使用朱庇特笔记本。对于这一章,CPU 足以执行代码,但如果需要,您可以使用谷歌合作实验室。如果你不能自己设置 Google Colab,你可以参考本书末尾的参考资料。
2.2 使用 TensorFlow 和 Keras 的深度学习
现在让我们简单地研究一下张量流和 Keras。它们可以说是最常见的开源库。
TensorFlow (TF)是 Google 推出的机器学习平台。 Keras 是在其他 DL 工具包如 TF、Theano、CNTK 等之上开发的框架。它内置了对卷积和递归神经网络的支持。
Tip
Keras 是一个 API 驱动的解决方案;大部分繁重的工作已经在喀拉斯完成。它更容易使用,因此推荐给初学者。
TF 中的计算是使用数据流图来完成的,其中数据由边(只不过是张量或多维数据数组)和表示数学运算的节点来表示。那么,张量到底是什么?
2.3 什么是张量?
回忆一下高中数学中的标量 ?? 和 ?? 向量 ??。向量可以被视为有方向的标量。例如,50 公里/小时的速度是一个标量,而北方向的 50 公里/小时是一个矢量。这意味着向量在给定方向上是标量大小。另一方面,一个张量会在多个方向上,也就是在多个方向上的标量大小。
根据数学定义,张量是可以在两个代数对象之间提供线性映射的对象。这些对象本身可以是标量或向量,甚至是张量。
张量可以在向量空间图中可视化,如图 2-1 所示。
图 2-1
用向量空间图表示的张量。张量是多个方向上的标量大小,用于提供两个代数对象之间的线性映射
正如你在图 2-1 中看到的,张量有多个方向的投影。张量可以被认为是一个数学实体,用分量来描述。它们是参照一个基来描述的,如果这个相关的基改变了,张量本身也要改变。一个例子是坐标变化;如果在此基础上进行变换,张量的数值也会改变。TensorFlow 使用这些张量进行复杂的计算。
现在让我们开发一个基本的检查,看看你是否已经正确安装了 TF。我们将两个常数相乘,检查安装是否正确。
Info
如果您想知道如何安装 TensorFlow 和 Keras,请参考第一章。
-
让我们导入 TensorFlow:
-
初始化两个常数:
import tensorflow as tf
- 将两个常数相乘:
a = tf.constant([1,1,1,1])
b = tf.constant([2,2,2,2])
- 打印最终结果:
product_results = tf.multiply(a, b)
print(product_results)
如果你能得到结果,恭喜你一切就绪!
现在让我们详细研究一个卷积神经网络。之后,您将准备好创建您的第一个图像分类模型。
很刺激,对吧?
2.3.1 什么是卷积神经网络?
当我们人类看到一幅图像或一张脸时,我们能够立即识别它。这是我们拥有的基本技能之一。这个识别过程是大量小过程的融合,是我们视觉系统各种重要组成部分之间的协调。
卷积神经网络或 CNN 能够使用深度学习来复制这种惊人的能力。
考虑一下这个。我们必须创建一个解决方案来区分猫和狗。使它们不同的属性可以是耳朵、胡须、鼻子等等。细胞神经网络有助于提取对图像有意义的图像属性。或者换句话说,CNN 将提取区别猫和狗的特征。CNN 在图像分类、目标检测、目标跟踪、图像字幕、人脸识别等方面非常强大。
让我们深入了解 CNN 的概念。我们将首先研究卷积。
2.3.2 什么是卷积?
卷积过程的主要目的是提取对图像分类、对象检测等重要的特征。这些特征将是边缘、曲线、色差、线条等等。一旦该过程被很好地训练,它将在图像中的重要点学习这些属性。然后它可以在图像的任何部分检测到它。
假设你有一张 32x32 的图片。这意味着如果它是彩色图像(记住 RGB),它可以表示为 32x32x3 像素。现在让我们在这张图片上移动(或回旋)一个 5x5 的区域,覆盖整个图片。这个过程叫做卷积。从左上角开始,这个区域覆盖了整个图像。你可以参考图 2-2 来看看一个 32x32 的图像是如何被一个 5x5 的滤镜旋绕的。
图 2-2
卷积过程:输入层在左边,输出在右边。32x32 的图像被 5x5 大小的滤镜进行了卷积
在整个图像上通过的 5x5 区域被称为滤波器,它有时被称为内核或特征检测器。图 2-2 中突出显示的区域被称为过滤器的感受野。因此,我们可以说,过滤器只是一个矩阵,其值称为权重。这些权重在模型训练过程中被训练和更新。该过滤器在图像的每个部分上移动。
我们可以通过图 2-3 所示完整过程的例子来理解卷积过程。原图 5x5 大小,滤镜 3x3 大小。过滤器在整个图像上移动,并继续产生输出。
图 2-3
卷积是按元素进行乘积和加法的过程。在第一幅图像中,输出为 3,在第二幅图像中,过滤器向右移动了一个位置,输出为 2
在图 2-3 中,3x3 滤波器对整个图像进行卷积。过滤器检查它想要检测的特征是否存在。该滤波器执行卷积过程,它是两个度量之间的元素级乘积和总和。如果存在某个特征,滤波器和图像部分的卷积输出将产生一个较高的数值。如果该功能不存在,输出将会很低。因此,该输出值表示过滤器对图像中存在特定特征的置信度。
我们在整个图像上移动这个过滤器,产生一个输出矩阵,称为特征图或激活图。该特征图将具有整个图像上的滤波器的卷积。
假设输入图像的维数是(n,n),滤波器的维数是(x,x)。
所以,CNN 层之后的输出是 ((n-x+1),(n-x+1)) 。
因此,在图 2-3 的例子中,输出是(5-3+1,5-3+1) = (3,3)。
还有一个叫做通道的组件非常有趣。通道是卷积过程中矩阵的深度。该滤镜将应用于输入图像中的每个通道。我们再次在图 2-4 中展示了流程的输出。输入图像的大小是 5x5x5,我们有一个 3x3x3 的过滤器。因此,输出变成大小为(3x3)的图像。我们应该注意到,滤波器的通道数应该与输入图像的通道数完全相同。在图 2-4 中,为 3。它允许度量之间的逐元素乘法。
图 2-4
滤镜的通道数与输入图像的通道数相同
还有一个我们应该注意的因素。过滤器可以以不同的间隔在输入图像上滑动。它被称为步幅值,并且它建议滤波器在每一步应该移动多少。其过程如图 2-5 所示。在左边的第一幅图中,我们有一个单一的步幅,而在右边,显示了两个步幅的运动。
图 2-5
Stride 建议过滤器在每一步应该移动多少。该图显示了步幅对卷积的影响。在第一张图中,我们的步幅为 1,而在第二张图中,我们的步幅为 2
你会同意,在卷积过程中,我们会很快丢失周边的像素。正如我们在图 2-3 中看到的,一个 5x5 的图像变成了一个 3x3 的图像,这种损失会随着层数的增加而增加。为了解决这个问题,我们有了填充的概念。在填充中,我们给正在处理的图像添加一些像素。例如,我们可以用零填充图像,如图 2-6 所示。填充的使用导致卷积神经网络的更好的分析和更好的结果。
图 2-6
零填充已添加到输入图像。卷积由于这个过程减少了像素的数量,填充使我们能够解决这个问题
现在我们已经理解了 CNN 的主要组成部分。让我们结合这些概念,创建一个小流程。如果我们有一个大小为(nxn)的图像,我们应用大小为“f”的过滤器,步长为“s”,填充为“s”,那么该过程的输出将是
((n+2p–f)/s+1),(n+2p–f)/s+1))(等式 2-1)
我们可以理解如图 2-7 所示的过程。我们有一个大小为 37x37 的输入图像和一个大小为 3x3 的滤波器,滤波器数量为 10,跨距为 1,填充为 0。根据等式 2-1,输出将为 35x35x10。
图 2-7
一种卷积过程,其中我们有一个大小为 3x3 的滤波器,步长为 1,填充为 0,滤波器数量为 10
卷积帮助我们提取图像的重要属性。更靠近原点(输入影像)的网络图层学习低级特征,而最终图层学习高级特征。在网络的初始层中,边缘、曲线等特征被提取出来,而更深的层将从这些低级特征(如面、对象等)中了解最终的形状。
但是这个计算看起来很复杂,不是吗?随着网络的深入,这种复杂性将会增加。那么我们该如何应对呢?池层是答案。我们来了解一下。
2.3.3 什么是池层?
我们研究的 CNN 层产生了输入的特征图。但是随着网络变得更深,这种计算变得复杂。这是由于随着每一层和每一个神经元,网络中的维数增加。因此网络的整体复杂性增加。
还有一个挑战:任何图像增强都会改变特征图。例如,旋转将改变图像中特征的位置,因此相应的特征图也将改变。
Note
通常,您会面临原始数据不可用的情况。图像增强是为您创建新图像的推荐方法之一,这些新图像可用作训练数据。
特征图中的这种变化可以通过下采样来解决。在缩减像素采样中,输入图像的分辨率会降低,而合并图层可以帮助我们做到这一点。
在卷积层之后添加一个汇集层。每个特征地图被单独操作,我们得到一组新的汇集的特征地图。此操作过滤器的大小小于要素地图的大小。
通常在卷积层之后应用汇集层。具有 3x3 像素和跨距的池化图层将要素地图的大小缩小了 2 倍,这意味着每个维度减半。例如,如果我们将池图层应用于 8x8 (64 像素)的要素地图,输出将是 4x4 (16 像素)的要素地图。
有两种类型的池层。
平均池和最大池。前者计算特征图每个值的平均值,后者得到特征图每个面片的最大值。让我们如图 2-8 所示检查它们。
图 2-8
右边的数字是最大汇集,而底部是平均汇集
如图 2-8 所示,平均池层计算四个数字的平均值,而最大池层从四个数字中选择最大值。
关于全连接层,还有一个更重要的概念,在准备创建 CNN 模型之前,您应该知道。让我们检查一下,然后你就可以走了。
2.3.4 什么是全连接层?
完全连接的层从前一层的输出(高级特征的激活图)获取输入,并输出 n 维向量。这里,n 是不同类的数量。
例如,如果目标是确定图像是否是一匹马,则完全连接的层将在激活图中具有像尾巴、腿等高级特征。完全连接的层如图 2-9 所示。
图 2-9
这里描述了一个完全连接的层
完全连接的图层将查看与特定类最接近且具有特定权重的要素。当我们得到权重和前一层之间的乘积时,这样做是为了得到不同类别的正确概率。
现在你已经了解了 CNN 及其组成部分。是时候打代码了。你将创建你的第一个深度学习解决方案来对猫和狗进行分类。祝一切顺利!
2.4 使用 CNN 开发 DL 解决方案
我们现在将使用 CNN 创建一个深度学习解决方案。深度学习的“你好世界”通常被称为 MNIST 数据集。它是手写数字的集合,如图 2-10 所示。
图 2-10
MNIST 数据集:用于图像识别的“Hello World”
有一篇关于识别 MNIST 图像的著名论文(在该章的末尾给出了描述)。为了避免重复,我们将全部代码上传到 GitHub。建议您检查代码。
我们现在将开始创建图像分类模型!
在第一个深度学习解决方案中,我们希望根据猫和狗的图像来区分它们。数据集在www.kaggle.com/c/dogs-vs-cats
可用。
下面列出了要遵循的步骤:
-
首先,让我们构建数据集:
-
从 Kaggle 下载数据集。解压缩数据集。
-
您会发现两个文件夹:test 和 train。删除测试文件夹,因为我们将创建自己的测试文件夹。
-
在 train 和 test 文件夹中,创建两个子文件夹——猫和狗——并将图像放在各自的文件夹中。
-
从“火车>猫”文件夹中取一些图片(我取了 2000 张)放到“测试>猫”文件夹中。
-
从“火车>狗”文件夹中取一些图片(我取了 2000 张)放到“测试>狗”文件夹中。
-
您的数据集已经可以使用了。
-
-
现在导入所需的库。我们将从
keras
导入sequential
、pooling
、activation
和flatten
图层。也导入numpy
。注意在本书的参考资料中,我们提供了每一层的描述及其各自的功能。
from keras.models import Sequential from keras.layers import Conv2D,Activation,MaxPooling2D,Dense,Flatten,Dropout import numpy as np
-
初始化一个模型,这里的
catDogImageclassifier
变量:catDogImageclassifier = Sequential()
-
现在,我们将添加层到我们的网络。
Conv2D
将增加一个二维卷积层,它将有 32 个过滤器。3,3 代表过滤器的大小(3 行,3 列)。以下输入图像形状为 64 * 64 * 3–高宽RGB。每个数字代表像素强度(0–255)。catDogImageclassifier.add(Conv2D(32,(3,3),input_shape=(64,64,3)))
-
最后一个图层的输出将是一个要素地图。训练数据将对其进行处理,并获得一些特征图。
-
让我们添加激活功能。我们在这个例子中使用
ReLU
(整流线性单位)。在前一层输出的特征图中,激活函数将所有负像素替换为零。注从 ReLU 的定义中回忆;它是 max(0,x)。ReLU 允许正值,而将负值替换为 0。一般来说,ReLU 只用于隐藏层。
-
现在我们添加最大池层,因为我们不希望我们的网络在计算上过于复杂。
catDogImageclassifier.add(MaxPooling2D(pool_size =(2,2)))
-
接下来,我们将所有三个卷积块相加。每个块都有一个 Cov2D、ReLU 和 Max 池层。
catDogImageclassifier.add(Conv2D(32,(3,3))) catDogImageclassifier.add(Activation('relu')) catDogImageclassifier.add(MaxPooling2D(pool_size =(2,2))) catDogImageclassifier.add(Conv2D(32,(3,3))) catDogImageclassifier.add(Activation('relu')) catDogImageclassifier.add(MaxPooling2D(pool_size =(2,2))) catDogImageclassifier.add(Conv2D(32,(3,3 catDogImageclassifier.add(Activation('relu')) catDogImageclassifier.add(MaxPooling2D(pool_size =(2,2)))
-
现在,让我们展平数据集,该数据集会将汇集的要素地图矩阵转换为一列。
catDogImageclassifier.add(Flatten())
-
Add the dense function now followed by the ReLU activation:
catDogImageclassifier.add(Dense(64)) catDogImageclassifier.add(Activation('relu'))
你认为我们为什么需要非线性函数,比如 tanh,ReLU 等等?如果只使用线性函数,输出也将是线性的。因此,我们在隐藏层中使用非线性函数。
-
过度合身是一件讨厌的事。接下来,我们将添加 Dropout 层来克服过度拟合:
catDogImageclassifier.add(Dropout(0.5))
-
再添加一个完全连接的层,以获得 n 维类的输出(输出将是一个矢量)。
catDogImageclassifier.add(Dense(1))
-
添加 Sigmoid 函数以转换为概率:
catDogImageclassifier.add(Activation('sigmoid'))
-
让我们打印一份网络摘要。
catDogImageclassifier.summary()
catDogImageclassifier.add(Activation('relu'))
我们在下图中看到了整个网络:
我们可以看到网络中的参数数量为 36,961。建议您尝试不同的网络结构,并评估其影响。
-
让我们现在编译网络。我们使用使用梯度下降的优化器
rmsprop
,然后我们添加损失或成本函数。catDogImageclassifier.compile(optimizer ='rmsprop', loss ='binary_crossentropy', metrics =['accuracy'])
-
现在我们在这里做数据增强(缩放,比例等。).这也有助于解决过度拟合的问题。我们使用
ImageDataGenerator
函数来做这件事:from keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator(rescale =1./255, shear_range =0.25,zoom_range = 0.25, horizontal_flip =True) test_datagen = ImageDataGenerator(rescale = 1./255)
-
加载训练数据:
training_set = train_datagen.flow_from_directory('/Users/DogsCats/train',target_size=(64,6 4),batch_size= 32,class_mode='binary')
-
加载测试数据:
test_set = test_datagen.flow_from_directory('/Users/DogsCats/test', target_size = (64,64), batch_size = 32, class_mode ='binary')
-
让我们现在开始训练。
from IPython.display import display from PIL import Image catDogImageclassifier.fit_generator(training_set, steps_per_epoch =625, epochs = 10, validation_data =test_set, validation_steps = 1000)
每个时期的步数是 625,时期数是 10。如果我们有 1000 个图像,批量大小为 10,所需的步骤数将是 100 (1000/10)。
Info
历元的数量意味着通过全部训练数据集的完整次数。批次是一个批次中训练样本的数量,而迭代是完成一个历元所需的批次数量。
根据网络的复杂性、给定的历元数等,编译将需要时间。测试数据集在这里作为validation_data
传递。
输出如图 2-11 所示。
图 2-11
输出 10 个时期的训练结果
从结果中可以看出,在最后一个时期,我们得到了 82.21%的验证准确率。我们还可以看到,在纪元 7 中,我们获得了 83.24%的精度,这比最终精度更好。
然后,我们希望使用在 Epoch 7 中创建的模型,因为它的精度是最好的。我们可以通过在训练和保存版本之间提供检查点来实现。我们将在后续章节中查看创建和保存检查点的过程。
我们已经将最终模型保存为文件。然后,在需要时,可以再次加载该模型。模型将被保存为一个HDF5
文件,以后可以重复使用。
-
使用 load_model:
from keras.models import load_model catDogImageclassifier = load_model('catdog_cnn_model.h5')
加载保存的模型
-
检查模型如何预测一个看不见的图像。
catDogImageclassifier.save('catdog_cnn_model.h5')
这是我们用来做预测的图像(图 2-12 )。请随意使用不同的图像测试解决方案。
图 2-12
一张狗的图片来测试模型的准确性
在下面的代码块中,我们使用我们训练的模型对前面的图像进行预测。
-
从文件夹中加载库和图像。您必须在下面的代码片段中更改文件的位置。
import numpy as np from keras.preprocessing import image an_image =image.load_img('/Users/vaibhavverdhan/Book Writing/2.jpg',target_size =(64,64))
图像现在被转换成一组数字:
an_image =image.img_to_array(an_image)
现在让我们扩大图像的维度。它将提高模型的预测能力。它用于扩展数组的形状,并插入一个新的轴,该轴出现在扩展后的数组形状的轴的位置上。
an_image =np.expand_dims(an_image, axis =0)
现在是时候调用 predict 方法了。我们将概率阈值设置为 0.5。建议您测试多个阈值,并检查相应的准确性。
verdict = catDogImageclassifier.predict(an_image) if verdict[0][0] >= 0.5:
prediction = 'dog'
else:
prediction = 'cat'
-
让我们打印我们的最终预测。
print(prediction)
该模型预测图像是一只“狗”
在这个例子中,我们使用 Keras 设计了一个神经网络。我们使用猫和狗的图像来训练图像,并对其进行测试。如果我们能得到每一类的图像,也有可能训练一个多分类器系统。
恭喜你!你刚刚完成了使用深度学习的第二个图像分类用例。使用它来训练您自己的影像数据集。甚至可以创建一个多类分类模型。
现在你可能会想,你将如何使用这个模型进行实时预测。编译后的模型文件(例如,'catdog_cnn_model.h5')
)将被部署到服务器上以进行预测。我们将在本书的最后一章详细讨论模型部署。
就这样,我们结束了第二章。你现在可以开始总结了。
2.5 总结
图像是信息和知识的丰富来源。通过分析图像数据集,我们可以解决很多业务问题。CNN 正在引领人工智能革命,尤其是在图像和视频领域。它们被用于医疗行业、制造业、零售业、BFSI 等等。相当多的研究正在使用 CNN 进行。
基于 CNN 的解决方案非常创新和独特。在设计基于 CNN 的创新解决方案时,有许多挑战需要解决。层数、每层中神经元的数量、要使用的激活函数、损失函数、优化器等的选择并不简单。这取决于业务问题的复杂性、手头的数据集以及可用的计算能力。该解决方案的功效在很大程度上取决于可用的数据集。如果我们有一个明确定义的可衡量、精确和可实现的业务目标,如果我们有一个有代表性的完整数据集,如果我们有足够的计算能力,许多业务问题都可以使用深度学习来解决。
在本书的第一章,我们介绍了计算机视觉和深度学习。在第二章中,我们学习了卷积层、池层和全连接层的概念。您将在整个旅程中使用这些概念。而且你还利用深度学习开发了一个图像分类模型。
从下一章开始,当我们从网络体系结构开始时,难度将会增加。网络体系结构使用我们在前两章中学习过的构建模块。它们是由跨组织和大学的科学家和研究人员开发的,用于解决复杂的问题。我们将研究这些网络并开发 Python 解决方案。所以保持饥饿!
你现在应该能回答练习中的问题了!
Review Questions
建议您解决以下问题:
-
CNN 中的卷积过程是怎样的,输出是如何计算的?
-
为什么我们需要隐藏层中的非线性函数?
-
最大池和平均池的区别是什么?
-
你说退学是什么意思?
-
从
www.kaggle.com/puneet6060/intel-image-classification
下载世界各地自然场景的图像数据,利用 CNN 开发图像分类模型。 -
从
https://github.com/zalandoresearch/fashion-mnist
下载时尚 MNIST 数据集,开发图像分类模型。
进一步阅读
建议您浏览以下文件:
-
在
https://arxiv.org/pdf/1811.08278.pdf
浏览“在手写数字识别数据集(MNIST)上评估四个神经网络”。 -
在
https://arxiv.org/pdf/1901.06032.pdf
学习研究论文《深度卷积神经网络近期架构综述》。 -
在
https://arxiv.org/pdf/1609.04112.pdf
浏览研究论文《用数学模型理解卷积神经网络》。 -
在
http://ais.uni-bonn.de/papers/icann2010_maxpool.pdf
浏览“对象识别卷积架构中的池操作评估”。
三、基于 LeNet 的图像分类
千里之行始于足下。—老子
你已经迈出了学习深度学习的非凡一步。深度学习是一个不断发展的领域。从基本的神经网络,我们现在已经发展到复杂的架构,解决大量的商业问题。深度学习驱动的图像处理和计算机视觉能力使我们能够创建更好的癌症检测解决方案,降低污染水平,实施监控系统,并改善消费者体验。有时,业务问题需要定制的方法。我们可能会设计自己的网络,以适应手头的业务问题,并基于我们可用的图像质量。网络设计还将考虑训练和执行网络的可用计算能力。跨组织和大学的研究人员花费了大量时间来收集和管理数据集,清理和分析它们,设计架构,培训和测试它们,并迭代以提高性能。做出开创性的解决方案需要大量的时间和极大的耐心。
在前两章中,我们讨论了神经网络的基础知识,并使用 Keras 和 Python 创建了一个深度学习解决方案。从本章开始,我们将开始讨论复杂的神经网络结构。我们将首先介绍 LeNet 架构。我们将讨论网络设计、各种层次、激活功能等等。然后,我们将开发图像分类用例的模型。
特别是,我们将在本章中讨论以下主题:
-
LeNet 架构及其变体
-
LeNet 架构的设计
-
MNIST 数字分类
-
德国交通标志分类
-
摘要
欢迎阅读第三章,祝一切顺利!
3.1 技术要求
本章的代码和数据集上传到本书的 GitHub 链接 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter3
。我们将使用朱庇特笔记本。对于这一章,CPU 足以执行代码,但如果需要,您可以使用谷歌合作实验室。谷歌 Colab 可以参考该书的参考资料。
让我们在下一部分继续深入学习架构。
3.2 深度学习架构
当我们讨论深度学习网络时,我们脑海中会立即浮现出几个组件——神经元数量、层数、使用的激活函数、损耗等等。所有这些参数在网络设计及其性能中起着至关重要的作用。当我们提到神经网络中的深度时,它是网络中隐藏层的数量。随着计算能力的提高,网络变得更深,对计算能力的需求也增加了。
Info
虽然您可能认为增加网络的层数会提高精度,但事实并非总是如此。这正是一个叫做 ResNet 的新网络产生的原因。
使用这些基础组件,我们可以设计自己的网络。全球的研究人员和科学家花费了大量的时间和精力来开发不同的神经网络架构。最流行的架构有 LeNet-5 、 AlexNet 、 VGGNet 、 GoogLeNet 、 ResNet 、 R-CNN(基于区域的 CNN) 、、【你只看一次】、、 SqueezeNet 、 SegNet 、 GAN(生成式对抗网络)这些网络使用不同数量的隐藏层、神经元、激活函数、优化方法等等。根据手头的业务问题,有些架构比其他架构更适合。
在本章中,我们将详细讨论 LeNet 架构,然后开发一些用例。我们从 LeNet 开始,因为它是更容易理解的框架之一,也是深度学习架构的先驱之一。我们还将检查各种超参数的变化对模型性能的影响。
3.3 LeNet 体系结构
正如我们在上一节中讨论的,LeNet 是我们在书中讨论的第一个体系结构。这是较简单的 CNN 架构之一。它之所以具有重要意义,是因为在它被发明之前,字符识别是一个既麻烦又耗时的过程。LeNet 体系结构于 1998 年推出,在美国,当它被用于对银行支票上的手写数字进行分类时,它开始流行起来。
LeNet 架构有几种形式——LeNet-1、 LeNet-4 和 LeNet-5 ,这是被引用最多、最著名的一种。他们是由 Yann LeCun 在一段时间内开发的。出于篇幅的考虑,我们正在详细研究 LeNet-5,其余的架构可以使用相同的方法来理解。
在下一节中,我们将从基本的 LeNet-1 架构开始。
3.4 LeNet-1 体系结构
LeNet-1 架构很容易理解。让我们看看它的层的尺寸。
-
第一层:28x28 输入图像
-
第二层:四个 24x24 卷积层(5x5 大小)
-
第三层:平均池层(2×2 大小)
-
第四层:八个 12×12 卷积层(5×5 大小)
-
第五层:平均池层(2×2 大小)
最后,我们有输出层。
Info
引入 LeNet 时,研究人员并没有提出最大池化;相反,使用了平均池。建议您同时使用平均池和最大池来测试解决方案。
LeNet-1 架构如图 3-1 所示。
图 3-1
LeNet-1 架构是第一个被概念化的 LeNet。我们应该注意第一层是一个卷积层,接着是池,另一个卷积层和一个池层。最后,我们在最后有一个输出层
这里可以看到,我们有输入层、卷积层、池层、卷积层、池层,最后是输出层。根据配置,图像在整个网络中进行转换。我们已经详细解释了所有层的功能和各自的输出,同时我们在随后的部分中讨论 LeNet-5。
3.5 LeNet-4 体系结构
LeNet-4 架构比 LeNet-1 略有改进。多了一个完全连接的图层和更多的要素地图。
-
第一层:32x32 输入图像
-
第二层:四个 24x24 卷积层(5x5 大小)
-
第三层:平均池层(2×2 大小)
-
第四层:十六个 12×12 卷积层(5×5 大小)
-
第五层:平均池层(2×2 大小)
输出完全连接到 120 个神经元,这些神经元进一步完全连接到 10 个神经元作为最终输出。
LeNet-4 架构如图 3-2 所示。
图 3-2
LeNet-4 是对 LeNet-1 架构的改进。其中引入了一个更完全连接的层
要详细了解所有层的功能,请参考下一节讨论 LeNet-5 的内容。
3.6 LeNet-5 体系结构
在所有三种 LeNet 架构中,被引用最多的架构是 LeNet-5,它是解决业务问题时通常使用的架构。
LeNet-5 架构如图 3-3 所示。它最初在 Y. LeCun 等人的论文“应用于文档识别的基于梯度的学习”中讨论过。该论文可在 http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf
访问。
图 3-3
LeNet architecture–图片取自 http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf
让我们详细讨论它的各个层,因为它是最常用的架构:
-
第一层:LeNet-5 的第一层是 32x32 的输入图像层。它是一个灰度图像,通过一个卷积块,该卷积块有六个大小为 5x5 的滤波器。结果尺寸为 28x28x1,32x32x1。这里,1 代表通道;它是 1,因为它是灰度图像。如果是 RGB,应该是三个通道。
-
第二层:汇集层也称为子采样层,其过滤器大小为 2x2,跨距为 2。图像尺寸缩小到 14x14x6。
-
第三层:这也是一个卷积层,具有 16 个特征图,大小为 5×5,步幅为 1。注意,在该层中,16 个特征地图中只有 10 个连接到前一层的 6 个特征地图。它给了我们几个明显的优势:
-
计算成本较低。这是因为连接数量从 240,000 减少到 151,600。
-
该层的训练参数总数是 1516,而不是 2400。
-
它还打破了体系结构的对称性,因此网络中的学习更好。
-
-
第四层:它是一个池层,过滤器大小为 2x2,跨距为 2,输出为 5x5x16。
-
第五层:它是一个全连接的卷积层,有 120 个特征图,每个图的大小为 1x1。120 个单元中的每一个都连接到前一层中的 400 个节点。
-
第六层:是全连通层,有 84 个单元。
-
最后,输出层是一个 softmax 层,每个数字对应十个可能的值。
LeNet-5 架构的总结如表 3-1 所示。
表 3-1
整个 LeNet 架构的总结
| -  |LeNet 是一个非常容易理解的小型架构。然而,它足够大和成熟,可以产生好的结果。它也可以在 CPU 上执行。同时,用不同的架构测试解决方案,并测试准确性以选择最佳的架构,这将是一个好主意。
我们有一个增强的 LeNet 架构,这将在下面讨论。
3.7 增强的 LeNet-4 架构
Boosting 是一种集成技术,通过从上一次迭代中不断改进,将弱学习者组合成强学习者。在 Boosted LeNet-4 中,将体系结构的输出相加,具有最高值的输出是预测类。
增强的 LeNet-4 架构如图 3-4 所示。
图 3-4
Boosted LeNet-4 架构结合弱学习者进行改进。最终输出更加准确和健壮
在前面描述的网络体系结构中,我们可以看到弱学习者是如何组合起来形成强预测解决方案的。
LeNet 是少数几个变得流行并用于解决深度学习问题的架构中的第一个。随着更多的进步和研究,我们已经开发了先进的算法,但 LeNet 仍然保持着特殊的地位。
是时候我们使用 LeNet 架构创建第一个解决方案了。
3.8 使用 LeNet 创建图像分类模型
现在我们已经理解了 LeNet 架构,是时候用它开发实际的用例了。我们将使用 LeNet 架构开发两个用例。LeNet 是一个简单的架构,所以代码可以在你的 CPU 上编译。我们在激活功能方面对体系结构做了细微的调整,使用最大池功能代替平均池功能等等。
Info
与平均池相比,最大池提取了重要的特征。平均池使图像平滑,因此可能无法识别清晰的特征。
在我们开始编写代码之前,有一个小的设置我们应该知道。张量中通道的位置决定了我们应该如何重塑我们的数据。这可以在以下案例研究的步骤 8 中观察到。
每个图像可以由高度、宽度和通道数或通道数、高度和宽度来表示。如果通道位于输入数组的第一个位置,我们使用 channels_first 条件进行整形。这意味着通道位于张量(n 维数组)的第一个位置。反之亦然,对于 channels_last 也是如此。
3.9 使用 LeNet 的 MNIST 分类
此使用案例是我们在上一章中使用的 MNIST 数据集的延续。代码可以在本章开头给出的 GitHub 链接中找到
-
首先,导入所有需要的库。
import keras from keras.optimizers import SGD from sklearn.preprocessing import LabelBinarizer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from sklearn import datasets from keras import backend as K import matplotlib.pyplot as plt import numpy as np
-
接下来,我们导入数据集,然后从 Keras 导入一系列图层。
from keras.datasets import mnist ## Data set is imported here from keras.models import Sequential from keras.layers.convolutional import Conv2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Flatten from keras.layers.core import Dense from keras import backend as K
-
定义超参数。这一步类似于上一章中我们开发 MNIST 和狗/猫分类的那一步。
image_rows, image_cols = 28, 28 batch_size = 256 num_classes = 10 epochs = 10
-
现在加载数据集。MNIST 是默认情况下添加到库中的数据集。
(x_train, y_train), (x_test, y_test) = mnist.load_data()
-
将图像数据转换为浮点型,然后将其规范化。
x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255
-
让我们打印训练和测试数据集的形状。
print('x_train shape:', x_train.shape) print(x_train.shape[0], 'train samples') print(x_test.shape[0], 'test samples')
-
在下一个代码块中,我们将变量转换为一键编码。我们使用 Keras 的 to _ categorical 方法。
y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes)
Tip
我们使用打印语句来分析每个步骤的输出。如果需要,它允许我们在稍后阶段进行调试。
-
让我们相应地重塑我们的数据。
if K.image_data_format() == 'channels_first': x_train = x_train.reshape(x_train.shape[0], 1, image_rows, image_cols) x_test = x_test.reshape(x_test.shape[0], 1, image_rows, image_cols) input_shape = (1, image_rows, image_cols) else: x_train = x_train.reshape(x_train.shape[0], image_rows, image_cols, 1) x_test = x_test.reshape(x_test.shape[0], image_rows, image_cols, 1) input_shape = (image_rows, image_cols, 1)
是时候创建我们的模型了!
-
首先添加一个顺序层,然后是卷积层和最大池层。
model = Sequential() model.add(Conv2D(20, (5, 5), padding="same",input_shape=input_shape)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
-
我们现在将添加多层卷积层、最大池层和数据扁平化层。
model.add(Conv2D(50, (5, 5), padding="same")) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Flatten()) model.add(Dense(500)) model.add(Activation("relu"))
-
我们添加一个密集层,然后是 softmax 层。Softmax 用于分类模型。之后,我们编译我们的模型。
model.add(Dense(num_classes)) model.add(Activation("softmax")) model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy'])
该模型已准备好接受训练和调整。
-
我们可以看到每个历元对精确度和损失的影响。我们鼓励您尝试不同变化的超参数,如 epoch 和 batch size。取决于超参数,网络将需要时间来处理。
theLeNetModel = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test))
这里,我们可以分析损耗和精度如何随每个历元而变化。
十个历元后,验证准确率为 99.07%。
现在,让我们想象一下结果。
-
We will be plotting the training and testing accuracy in the next code block.
import matplotlib.pyplot as plt f, ax = plt.subplots() ax.plot([None] + theLeNetModel.history['acc'], 'o-') ax.plot([None] + theLeNetModel.history['val_acc'], 'x-') ax.legend(['Train acc', 'Validation acc'], loc = 0) ax.set_title('Training/Validation acc per Epoch') ax.set_xlabel('Epoch') ax.set_ylabel('acc')
In the graph in Figure 3-5, we can see that with each subsequent epoch, the respective accuracy parameters for both training and validation continue to increase. After epoch 7/8, the accuracy stabilizes. We can test this with different values of hyperparameters.
图 3-5
此处显示了训练和验证准确性。在历元 7/8 之后,精确度已经稳定
-
Let’s analyze the loss:
import matplotlib.pyplot as plt f, ax = plt.subplots() ax.plot([None] + theLeNetModel.history['loss'], 'o-') ax.plot([None] + theLeNetModel.history['val_loss'], 'x-') ax.legend(['Train loss', 'Validation loss'], loc = 0) ax.set_title('Training/Validation loss per Epoch') ax.set_xlabel('Epoch') ax.set_ylabel('acc')
In the graph in Figure 3-6, we can see that with each subsequent epoch, the respective loss measures for both training and validation continue to decrease. After epoch 7/8, the accuracy stabilizes. We can test this with different values of hyperparameters.
图 3-6
此处显示了培训和验证损失。在 7/8 个周期之后,损失已经稳定,并且没有观察到太多的减少
太好了!现在我们有了一个工作的 LeNet 模型来对图像进行分类。
在本练习中,我们使用 LeNet-5 架构训练了一个图像分类模型。
Info
如果你在计算效率上面临任何挑战,请使用谷歌合作实验室。
3.10 使用 LeNet 识别德国交通标志
第二个用例是德国交通标志识别。它可以用于自动驾驶解决方案。
在这个用例中,我们将使用 LeNet-5 架构构建一个深度学习模型。
-
我们将在整本书中遵循一个相似的过程,首先导入库。
import keras from keras.optimizers import SGD from sklearn.preprocessing import LabelBinarizer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from sklearn import datasets from keras import backend as K import matplotlib.pyplot as plt import numpy as np
-
导入 Keras 库以及创建地块所需的所有包。
from keras.models import Sequential from keras.layers.convolutional import Conv2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Flatten from keras.layers.core import Dense from keras import backend as K
-
然后导入 numpy、matplotlib、os、OpenCV 之类的通用库。
import glob import pandas as pd import matplotlib import matplotlib.pyplot as plt import random import matplotlib.image as mpimg import cv2 import os from sklearn.model_selection import train_test_split from sklearn.metrics import confusion_matrix from sklearn.utils import shuffle import warnings from skimage import exposure # Load pickled data import pickle %matplotlib inline matplotlib.style.use('ggplot') %config InlineBackend.figure_format = 'retina'
-
数据集以 pickle 文件的形式提供,并保存为 train.p 和 test.p 文件。数据集可以在
www.kaggle.com/ meowmeowmeowmeowmeow/gtsrb-german-traffic-sign
从 Kaggle 下载。training_file = "train.p" testing_file = "test.p"
-
打开文件并保存训练和测试变量中的数据。
with open(training_file, mode="rb") as f: train = pickle.load(f) with open(testing_file, mode="rb") as f: test = pickle.load(f)
-
将数据集分为测试和训练。我们在这里采用了 4000 的测试规模,但是您可以自由尝试不同的测试规模。
X, y = train['features'], train['labels'] x_train, x_valid, y_train, y_valid = train_test_split(X, y, stratify=y, test_size=4000, random_state=0) x_test,y_test=test['features'],test['labels']
-
让我们来看看这里的一些样本图像文件。我们使用了随机函数来选择随机图像;因此,如果您的输出与我们的不同,请不要担心。
figure, axiss = plt.subplots(2,5, figsize=(15, 4)) figure.subplots_adjust(hspace = .2, wspace=.001) axiss = axiss.ravel() for i in range(10): index = random.randint(0, len(x_train)) image = x_train[index] axiss[i].axis('off') axiss[i].imshow(image) axiss[i].set_title( y_train[index])
这里是如图 3-7 所示的输出。
图 3-7
此处显示了德国交通标志分类数据集的一些示例
-
接下来,让我们选择我们的超参数。不同类别的数量为 43。我们已经开始使用十个纪元,但是我们鼓励您使用不同的纪元值来检查性能。批量大小也是如此。
image_rows, image_cols = 32, 32 batch_size = 256 num_classes = 43 epochs = 10
-
现在,我们将执行一些探索性的数据分析。这样做是为了查看我们的图像数据集看起来如何,以及直方图中各种类的频率分布是什么。
histogram, the_bins = np.histogram(y_train, bins=num_classes) the_width = 0.7 * (the_bins[1] - the_bins[0]) center = (the_bins[:-1] + the_bins[1:]) / 2 plt.bar(center, histogram, align="center", width=the_width) plt.show()
输出如图 3-8 所示。我们可以观察到,类的例子数量是有差异的。一些阶层得到了很好的代表,而一些阶层则没有。在现实世界的解决方案中,我们希望有一个平衡的数据集。我们会在本书的第八章讨论更多。
图 3-8
各类别频率分布。有的类例子比较多,有的代表性不多。理想情况下,我们应该为代表性较低的类收集更多的数据
-
现在,让我们检查一下不同类别之间的分布情况。这是 NumPy 库中的一个常规直方图函数。
train_hist, train_bins = np.histogram(y_train, bins=num_classes) test_hist, test_bins = np.histogram(y_test, bins=num_classes) train_width = 0.7 * (train_bins[1] - train_ bins[0]) train_center = (train_bins[:-1] + train_bins[1:]) / 2 test_width = 0.7 * (test_ bins[1] - test_bins[0]) test_center = (test_ bins[:-1] + test_bins[1:]) / 2
-
现在,绘制直方图;对于训练数据集和测试数据集,颜色分别设置为红色和绿色。
plt.bar(train_center, train_hist, align="center", color="red", width=train_width) plt.bar(test_center, test_hist, align="center", color="green", width=test_width) plt.show()
这里是如图 3-9 所示的输出。
图 3-9
各种类别的频率分布以及在训练和测试数据集之间的分布。列车显示为红色,而测试显示为绿色
这里我们来分析一下分布;查看前面直方图中训练与测试的比例差异。
-
将图像数据转换为浮点型,然后将其规范化。
x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 print('x_train shape:', x_train.shape) print(x_ train.shape[0], 'train samples') print(x_test. shape[0], 'test samples')
因此,我们有 35209 个训练数据点和 12630 个测试数据点。下一步,将类别向量转换为二进制类别矩阵。这与我们开发 MNIST 分类的上一个示例中的步骤类似。
y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes)
下面的代码块与前面开发的 MNIST 分类中描述的代码块相同。这里,channels_first 表示通道位于数组的第一个位置。我们正在根据 channels_first 的位置更改 input_shape。
if K.image_data_format() == 'channels_first':
input_shape = (1, image_rows, image_cols)
else:
input_shape = (image_rows, image_cols, 1)
让我们现在开始创建神经网络架构。这些步骤类似于前面的用例。
-
添加一个顺序层,后跟一个卷积层。
model = Sequential() model.add(Conv2D(16,(3,3),input_shape=(32,32,3)))
-
添加池层,然后添加卷积层,依此类推。
model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Conv2D(50, (5, 5), padding="same")) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) model.add(Flatten()) model.add(Dense(500)) model.add(Activation("relu")) model.add(Dense(num_classes)) model.add(Activation("softmax"))
-
让我们打印模型摘要。
model.summary()
这是输出。
-
模型已准备好进行编译;让我们训练它。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy']) theLeNetModel = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test))
这是如图 3-10 所示的输出。
图 3-10
相对于每个历元的精度和损失移动。我们应该注意到,从第一个历元到最后一个历元,精确度是如何提高的
经过十个历元后,验证准确率为 91.16%。
现在让我们想象一下结果。
-
我们将首先绘制网络的训练和测试精度。
import matplotlib.pyplot as plt f, ax = plt.subplots() ax.plot([None] + theLeNetModel.history['acc'], 'o-') ax.plot([None] + theLeNetModel. history['val_acc'], 'x-') ax.legend(['Train acc', 'Validation acc'], loc = 0) ax.set_ title('Training/Validation acc per Epoch') ax.set_xlabel('Epoch') ax.set_ylabel('acc')
这是结果图,如图 3-11 所示。
图 3-11
此处显示了训练和验证准确性。在历元 5/6 之后,精确度已经稳定
-
让我们画出训练和测试数据的损失。
import matplotlib.pyplot as plt f, ax = plt.subplots() ax.plot([None] + theLeNetModel.history['loss'], 'o-') ax.plot([None] + theLeNetModel. history['val_loss'], 'x-') ax.legend(['Train loss', 'Validation loss'], loc = 0) ax.set_ title('Training/Validation loss per Epoch') ax.set_xlabel('Epoch') ax.set_ylabel('acc')
由此产生的图可以在图 3-12 中看到。
图 3-12
此处显示了培训和验证损失。在 5/6 个周期之后,损失已经稳定,并且没有观察到太多的减少
生成精度和损失函数图来测量模型的性能。这些曲线与 MNIST 分类模型中的曲线相似。
Info
所有模型的性能参数都在 NetModel.model 或 NetModel.model.metrics 中。
在这个例子中,我们还采取了一个额外的步骤,为预测创建混淆矩阵。为此,我们必须首先对测试集进行预测,然后将预测与图像的实际标签进行比较。
-
使用预测函数进行预测。
-
现在,让我们创建混淆矩阵。它可以在 scikit-learn 库中找到。
from sklearn.metrics import confusion_matrix import numpy as np confusion = confusion_matrix(y_test, np.argmax(predictions,axis=1))
-
现在让我们创建一个名为 cm 的变量,它只不过是混淆矩阵。
predictions = theLeNetModel.model.predict(x_test)
请随意打印并分析结果。
-
现在让我们开始混淆矩阵的可视化。Seaborn 是 matplotlib 的另一个库,用于可视化。
import seaborn as sn df_cm = pd.DataFrame(cm, columns=np.unique(y_test), index = np.unique(y_test)) df_cm.index.name = 'Actual' df_cm.columns.name = 'Predicted' plt.figure(figsize = (10,7)) sn.set(font_scale=1.4)#for label size sn.heatmap(df_cm, cmap="Blues", annot=True,annot_kws={"size": 16})# font size
cm = confusion_matrix(y_test, np.argmax(predictions,axis=1))
输出如下所示。由于维数的原因,混淆矩阵在图 3-13 中并不清晰可见,所以在下一个代码块中让它稍微好一点。
图 3-13
图中显示了混淆矩阵,但是由于维数的原因,输出结果不是很清楚,我们将在下图中对此进行改进
-
这里,我们再次绘制混淆矩阵。请注意,我们已经定义了一个函数 plot_confusion_matrix,它将混淆矩阵作为输入参数。然后,我们使用常规的 matplotlib 库及其函数来绘制混淆矩阵。您也可以将此功能用于其他解决方案。
def plot_confusion_matrix(cm): cm = [row/sum(row) for row in cm] fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(111) cax = ax.matshow(cm, cmap=plt.cm.Oranges) fig.colorbar(cax) plt.title('Confusion Matrix') plt.xlabel('Predicted Class IDs') plt.ylabel('True Class IDs') plt.show() plot_confusion_matrix(cm)
这里是显示混淆矩阵的图(图 3-14 )。
图 3-14
为所有类别生成混淆矩阵。有几节课,成绩不是很好。寻找错误分类并分析原因是明智的
我们可以在这里看到,有些课程我们确实取得了很好的成绩。建议您分析结果并使用超参数进行迭代以查看影响。应该对有错误分类的观测值进行分析,找出原因。例如,在数字分类的情况下,算法可能会在 1 和 7 之间混淆。因此,一旦我们测试了模型,我们就应该寻找错误的分类并找出原因。一个潜在的解决方案是从训练数据集中移除混淆的图像。提高图像质量和增加错误分类类别的数量也有助于解决这个问题。
至此,我们完成了两个利用 LeNet 进行图像分类的案例研究。我们即将结束这一章。你现在可以开始总结了。
3.11 摘要
神经网络体系结构对于计算机视觉问题来说是非常有趣和强大的解决方案。它们允许我们在非常大的数据集上进行训练,并有助于正确识别图像。这种能力可以用于跨领域的各种各样的问题。同时,必须注意解决方案的质量在很大程度上取决于训练数据集的质量。记住这句名言,垃圾进垃圾出。
在前几章中,我们研究了卷积、最大池、填充等概念,并使用 CNN 开发了解决方案。这一章标志着定制神经网络架构的开始。这些架构的设计各不相同,即层数、激活函数、步距、内核大小等等。更常见的是,我们测试四种不同架构中的三种来比较精度。
在本章中,我们讨论了 LeNet 体系结构,并重点讨论了 LeNet-5。我们开发了两个从数据加载到网络设计和准确性测试的端到端实施用例。
在下一章,我们将研究另一个流行的架构,叫做 VGGNet。
你现在应该能回答练习中的问题了!
Review Exercises
-
LeNet 的不同版本有什么区别?
-
如何用历元度量精度分布?
-
我们在本章中讨论了两个用例。用不同的超参数值迭代相同的解。为上一章完成的用例创建一个损失函数和准确度分布。
-
取上一章使用的数据集,用 LeNet-5 测试比较结果。
-
从
www.kaggle.com/puneet6060/intel-image-classification/version/2
下载影像场景分类数据集。对此数据集执行德国交通分类数据集中使用的代码。 -
从
http://chaladze.com/l5/
下载林奈 5 数据集。它包含五类——浆果、鸟、狗、花和其他。使用此数据集创建基于 CNN 的解决方案。
进一步阅读
-
在
https://drive.google.com/drive/folders/1-5V1vj88-ANdRQJ5PpcAQkErvG7lqzxs
浏览论文“使用体积表示的卷积神经网络用于 3D 物体识别”。 -
在
https://arxiv.org/abs/1603.08631
用 CNN 浏览老年痴呆症分类的论文。
四、VGGNet 和 AlexNet 网络
一旦我们接受了自己的极限,我们就会超越它们。—阿尔伯特·爱因斯坦
在某一点之后,即使是极其复杂的解决方案也不再改进。然后我们应该改进解决方案的设计。我们回到绘图板,集思广益,提高能力。有了更多可用的选项,我们可以迭代和测试多个解决方案。然后根据手头的业务问题,选择并实施最佳解决方案。我们在深度学习架构中遵循同样的原则。我们致力于网络架构,并对其进行改进,使其更加健壮、准确和高效。神经网络体系结构的选择是基于对各种体系结构进行的测试。
在上一章中,我们从 LeNet 深度学习架构开始。我们浏览了网络架构,并使用它开发了用例。在本章中,我们将讨论 VGG 和 AlexNet 神经网络架构,并开发一个复杂的多类分类功能。我们还将比较这两种架构的性能。与此同时,我们将讨论如何在训练深度学习模型时使用检查点。在生成混淆矩阵时,我们面临一个常见的错误;我们会明白错误的原因以及如何纠正它。
我们将在本章中讨论以下主题:
-
AlexNet 架构
-
VGG16 体系结构
-
VGG16 和 VGG19 的区别
-
使用 AlexNet 的 CIFAR-10 案例研究
-
使用 VGG16 的 CIFAR-10 案例研究
欢迎来到第四章,祝一切顺利!
4.1 技术要求
本章的代码和数据集上传到本书的 GitHub 链接 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter4
。我们将使用朱庇特笔记本。对于这一章,CPU 足以执行代码,但如果需要,您可以使用谷歌合作实验室。如果你不能自己设置 Google Colab,你可以参考这本书的参考资料。
让我们在下一部分继续深入学习架构。
4.2 AlexNet 和 VGG 神经网络
AlexNet 于 2012 年推出,立即成为每个人的最爱,用于图像和对象分类。它还赢得了 ImageNet 大规模视觉识别挑战赛(ILSVRC)。随后,VGG 在 2014 年应运而生,其准确性被证明优于 AlexNet。这并不意味着 AlexNet 不是一个有效的网络,它只是意味着 VGG 有更好的准确性。
现在让我们详细了解这两种架构。我们从 AlexNet 作为第一个架构开始。
4.3 什么是 AlexNet 神经网络?
AlexNet 是由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey E. Hinton 提出的。论文原文可在 https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf
访问。
AlexNet 架构看起来如图 4-1 所示。这是来自上述论文的原始图像。
图 4-1
完整的 AlexNet 架构(图片取自 https://papers.nips.cc/paper/4824-imagenet- classification-with-deep-convolutional-neural-networks.pdf
)
我们现在可以更详细地探索各个层。表 4-1 给出了网络所有层的描述。
表 4-1
网络的每一层和相应的输入参数、信道大小、步幅和激活函数
| -  |如我们所见,第一层是大小为 227x227x3 的输入图像。它通过 96 个特征映射的第一卷积层,步幅为 4,滤波器大小为 11×11,激活函数为 ReLU。输出图像的尺寸为 55x55x96。
接下来是最大池层,过滤器大小为 3×3,跨距为 2,输出图像尺寸为 27×27×96。您可以以这种方式继续分析每一层。必须完全理解每一层及其各自的功能。
AlexNet 拥有 6000 万个参数和 65 万个神经元。我们可以观察到,总共有八层。前五层用于执行卷积运算。最后三层是完全连接的一层。在卷积层中,少数具有以下层作为最大池层。在 AlexNet 中使用 ReLU 非线性,与 tanh 和 sigmoid 激活相比,它显示了改进的训练性能和更快的网络训练。发明人使用数据增加和丢弃层来对抗网络中的过拟合。
现在让我们理解 VGG 架构,然后我们将使用 AlexNet 和 VGG 开发用例。
4.4 什么是 VGG 神经网络?
VGGNet 是由牛津大学的卡伦·西蒙扬和安德鲁·齐泽曼提出的 CNN 架构。VGG 是视觉几何组。您可以在 https://arxiv.org/pdf/1409.1556.pdf
访问原文。
它于 2014 年推出,在 ImageNet 大规模视觉识别挑战赛(ILSVRC)中表现出色。它是最受欢迎的深度学习架构之一,因为它简单(我们将在下面的章节中研究)。通常,人们批评网络的规模,因为它需要更多的计算能力和更多的时间。但是该网络是一个非常健壮的解决方案,并且被称为计算机视觉解决方案的标准解决方案之一。
VGG 神经网络模型有两种形式: VGG16 和 VGG19 。让我们详细研究一下 VGG16,然后我们将考察 VGG19 与 VGG16 有何不同。
4.5 VGG16 体系结构
VGG 是一个容易理解的网络,原因如下:
-
它在整个网络中仅使用 3×3 卷积和 2×2 池。
-
卷积层使用非常小的内核大小(3x3)。
-
有 1x1 卷积来线性变换输入。
-
跨距为 1 个像素,有助于保持空间分辨率。
-
ReLU 用于所有隐藏层。
-
有三个完全连接的层,前两层有 4096 个通道,最后一层有 1000 个通道。最后,我们有一个 softmax 层。
在图 4-2 中,我们可以看到各自的配置以及每一层的描述。
图 4-2
VGG 简单网络
请注意,它在整个网络中仅使用 3x3 conv 层和 2x2 池层。左边的图取自前面引用的原始论文。随着层的增加,配置的深度从左(A)到右(E)增加。conv 层参数表示为“conv(感受野大小)–(通道数量)”为了简洁起见,没有显示 ReLU 激活功能。
Note
不使用 LRN(局部反应标准化),因为尽管训练时间增加,但准确性没有明显提高。
VGG16 是一个相当受欢迎的网络。由于其简单性,它可以作为衡量许多复杂图像分类问题的性能的基准。VGG19 比 VGG16 稍微复杂一些。接下来将研究两者之间的差异。
4.6 vgg 16 和 VGG19 的区别
表 4-2 列出了 VGG16 和 VGG19 之间的主要区别。一般 VGG16 用的最多,也比较普及。这是因为在一般的商业世界中,我们不会对一个问题进行八到十类以上的分类。此外,没有获得真正有代表性和平衡的数据。因此,在实践中,VGG16 是最常用的。
表 4-2
VGG16 和 VGG19 的主要区别
| -  |我们现在将使用 AlexNet 和 VGG16 在 CIFAR-10 数据集上开发用例。CIFAR-10 是一个开源数据集。
4.7 使用 AlexNet 和 VGG 开发解决方案
我们将使用 CIFAR 数据集通过 AlexNet 和 VGGNet 创建解决方案。可以在 www.cs.toronto.edu/~kriz/cifar.html.访问 CIFAR 数据集
根据数据集来源:
CIFAR-10 数据集由 10 类 60000 张 32x32 彩色图像组成,每类 6000 张。有 50000 个训练图像和 10000 个测试图像。数据集分为五个训练批次和一个测试批次,每个批次有 10000 幅图像。测试批次包含从每个类别中随机选择的 1000 个图像。训练批次以随机顺序包含剩余的图像,但是一些训练批次可能包含来自一个类别的比来自另一个类别的更多的图像。在它们之间,训练批次正好包含来自每个类的 5000 个图像。这些类是完全互斥的。汽车和卡车之间没有重叠。“汽车”包括轿车、SUV 等。而“卡车”只包括大卡车。两者都不包括皮卡车。
CIFAR-10 数据集如图 4-3 所示。
图 4-3
CIFAR-10 数据集中的类以及每个类的一些示例。CIFAR-10 数据集是用于测试神经网络功效的流行数据集之一
CIFAR-100 数据集与 CIFAR-10 非常相似,只是它有 100 个类,每个类包含 600 幅图像。每个类有 500 个训练图像和 100 个测试图像。CIFAR-100 中的 100 个类被分成 20 个超类。每个图像都有一个“精细”标签(它所属的类)和一个“粗糙”标签(它所属的超类)。
图 4-4 提供了 CIFAR-100 中的等级列表。每个超类都有子类。例如,苹果、蘑菇、橙子、梨等等是子类,它们的超类是水果和蔬菜。
图 4-4
CIFAR-100 数据集中所有超类和类的列表
是时候使用 AlexNet 和 VGG16 开发用例了。
4.8 使用 AlexNet 在 CIFAR-10 上工作
让我们使用 AlexNet 在 CIFAR-10 上开发一个分类解决方案。
-
在这里导入所有必要的库,并加载 CIFAR-10 数据集。
import keras from keras.datasets import cifar10 from keras import backend as K from keras.layers import Input, Conv2D, GlobalAveragePooling2D, Dense, BatchNormalization, Activation, MaxPooling2D from keras.models import Model from keras.layers import concatenate,Dropout,Flatten
-
这里,我们导入 ModelCheckpoint,它将用于创建检查点,以保存基于验证准确性的最佳模型。配置完设置后,我们将详细研究检查点。
from keras import optimizers,regularizers from keras.preprocessing.image import ImageDataGenerator from keras.initializers import he_normal from keras.callbacks import LearningRateScheduler, TensorBoard, ModelCheckpoint
-
接下来,我们用 cifar.load_data()步骤加载 CIFAR-10 数据。(x_train,y_train),(x_test,y_test) = cifar10.load_data()
-
Now, preprocess the images by getting the mean and standard deviation and then standardizing them.
mean = np.mean(x_train,axis=(0,1,2,3)) std = np.std(x_train, axis=(0, 1, 2, 3)) x_train = (x_train-mean)/(std+1e-7) x_test = (x_test-mean)/(std+1e-7)
注意实验在有和没有步骤 4 的情况下执行代码,以检查已处理和未处理图像之间的性能差异。
-
现在,让我们创建我们的训练和测试目标变量数据。这一步类似于我们在前一章中开发的解决方案。
y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes)
Let us have a look at the dataset.
fig = plt.figure(figsize=(18, 8)) columns = 5 rows = 5 for i in range(1, columns*rows + 1): fig.add_subplot(rows, columns, i) plt.imshow(X_train[i], interpolation="lanczos")
Figure 4-5 shows some of the images from our dataset. Look at the different classes of images we have. Also, examine the resolution and aspect ratio of the images.
图 4-5
此处显示了数据集中的一些示例图像
-
现在让我们创建 AlexNet 架构。这里,我们将尝试一种不同的方法。我们将定义一个函数来创建网络。在创建 AlexNet 时,我们从网络中定义了参数的卷积层开始。第一层的内核大小为 11×11,96 个通道,跨距为 4×4。网络紧随其后。
def alexnet(img_input,classes=10): xnet = Conv2D(96,(11,11),strides=(4,4),padding='same',activation='relu',kernel_initializer='uniform')(img_input) xnet = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same',data_format=DATA_ FORMAT)(xnet) xnet = Conv2D(256,(5,5),strides=(1,1),padding='same', activation="relu",kernel_initializer='uniform')(xnet) xnet = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same',data_format=DATA_ FORMAT)(xnet) xnet = Conv2D(384,(3,3),strides=(1,1),padding='same', activation="relu",kernel_initializer='uniform')(xnet) xnet = Conv2D(384,(3,3),strides=(1,1),padding='same', activation="relu",kernel_initializer='uniform')(xnet) xnet = Conv2D(256,(3,3),strides=(1,1),padding='same', activation="relu",kernel_initializer='uniform')(xnet) xnet = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same',data_format=DATA_ FORMAT)(xnet) xnet = Flatten()(xnet) xnet = Dense(4096,activation='relu')(xnet) xnet = Dropout(0.25)(xnet) xnet = Dense(4096,activation='relu')(xnet) xnet = Dropout(0.25)(xnet) out_model = Dense(classes, activation="softmax")(xnet) return out_model
前面函数的输出将是网络。
-
现在让我们输入 32x32x3 形状的图像。然后,使用 AlexNet 函数得到想要的模型。
img_input=Input(shape=(32,32,3)) output = alexnet(img_input) model=Model(img_input,output)
-
然后,我们生成模型的摘要。
model.summary()
这是总结。我们有所有的层、各自的输出形状和参数数量(图 4-6 )。
图 4-6
AlexNet 模型综述。它有 2100 万个参数需要训练
模型摘要表明我们有 2100 万个参数来训练这个 AlexNet 模型。
- 我们现在需要编译模型,优化器是用来编译基于 Keras 的模型的参数之一。我们在这里使用随机梯度下降(SGD ),学习率为 0.01,动量为 0.8。随意用不同的学习率和动量值迭代。SGD 包括对动量、学习率衰减和内斯特罗夫动量的支持。你必须意识到什么是学习率,我们在前面的章节中已经讨论过了。动量是在相关方向上加速 SGD 并抑制振荡的参数。最后一个参数内斯特罗夫表示是否应用内斯特罗夫动量。
sgd = optimizers.SGD(lr=.01, momentum=0.8, nesterov=True) model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
Note
我们在这里使用了 SGD。对 Adam、RMSProp、Adagrad 和 Adadelta 进行实验,比较训练时间和各自的表现。
-
接下来,我们设置检查点。我们这样做是为了只有当验证准确性提高时,最后保存的模型才会被替换。
想知道什么是检查点吗?
While we develop our model, the validation accuracy decreases often in the next epoch, and hence we would like to use the accuracy in the earlier epoch. For example, if in the fifth epoch we get 74% accuracy which decreases to 73% in the sixth epoch, we would like to use the fifth epoch and not the sixth. Using checkpoint, we can save the model only when the accuracy has increased and not with each subsequent epoch. If we do not have a checkpoint, we will get only the final model in the final epoch. And it might not be the best performing model. Hence, it is advised to use checkpoints.
filepath="weights.best.hdf5" checkpoint = ModelCheckpoint(filepath, monitor="val_acc", verbose=1, save_best_only=True, mode="max") callbacks_list = [checkpoint] epochs = 50
-
现在,让我们使用 ImageDataGenerator 进行数据扩充。它通过实时数据扩充生成批量张量图像数据。数据将(分批)循环。
datagen = ImageDataGenerator(horizontal_flip=True, width_shift_range=0.115, height_shift_range=0.115, fill_mode="constant",cval=0.) datagen.fit(x_train)
-
开始训练网络。注意,我们已经将回调设置为 callbacks_list,这是我们为模型检查点描述的。
model.fit_generator(datagen.flow(x_train, y_train,batch_size=batch_size), steps_per_epoch=iterations, epochs=epochs, callbacks=callbacks_list, validation_data=(x_test, y_test))
训练网络后,您将获得以下输出。为了简洁起见,我们只显示了最后的时代。
注意,在 50 个时期之后,我们获得了 74.29%的验证准确度。还要注意,如果精度没有提高,模型不会改变最佳精度 74.80%。
-
现在让我们来衡量一下我们的表现。首先,我们衡量我们的准确性;随之而来的将是损失。代码和图形类似于前一章中生成的代码和图形。
import matplotlib.pyplot as plt f, ax = plt.subplots() ax.plot([None] + model.history.history['acc'], 'o-') ax.plot([None] + model.history.history['val_acc'], 'x-') ax.legend(['Train acc', 'Validation acc'], loc = 0) ax.set_title('Training/Validation acc per Epoch') ax.set_xlabel('Epoch') ax.set_ylabel('acc')
下面是输出(图 4-7 )。
图 4-7
我们正在绘制训练数据集与验证数据集的准确性对比图
- 现在,让我们来衡量损失。
import matplotlib.pyplot as plt
f, ax = plt.subplots()
ax.plot([None] + model.history.history['loss'], 'o-') ax.plot([None] + model.history.history['val_loss'], 'x-')
ax.legend(['Train loss', 'Validation loss'], loc = 0) ax.set_title('Training/Validation loss per Epoch') ax.set_xlabel('Epoch')
ax.set_ylabel('acc')
下面是输出(图 4-8 )。
图 4-8
此处显示了培训和验证损失
-
我们现在将使用该模型进行预测,并使用 predict 函数为我们的预测生成混淆矩阵。
-
当您尝试使用以下函数生成混淆矩阵时,会收到以下错误:
from sklearn.metrics import confusion_matrix import numpy as np confusion_matrix(y_test, np.argmax(predictions,axis=1))
predictions = model.predict(x_test)
我们得到这个错误是因为混淆矩阵要求预测值和图像标签都是一位数,而不是一次性编码的向量。我们的 test_values 必须在这里进行转换,它应该会为我们解决错误。
Note
打印预测值和 y_test[1]值以检查差异。
-
让我们将标签转换为一位数 1,然后我们将生成混淆矩阵。
rounded_labels=np.argmax(y_test, axis=1) rounded_labels[1] cm = confusion_matrix(rounded_labels, np.argmax(predictions,axis=1)) cm
您将得到下面的混淆矩阵作为最后一条语句的输出。作为一个十类问题,它将生成所有相应十类的值。
-
现在定义混淆矩阵函数并生成混淆矩阵:
def plot_confusion_matrix(cm): cm = [row/sum(row) for row in cm] fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(111) cax = ax.matshow(cm, cmap=plt.cm.Oranges) fig.colorbar(cax) plt.title('Confusion Matrix') plt.xlabel('Predicted Class IDs') plt.ylabel('True Class IDs') plt.show() plot_confusion_matrix(cm)
前面代码的输出如图 4-9 所示。
图 4-9
此处显示了问题的混淆矩阵。该网络对一些课程做出了很好的预测。建议通过调整超参数来测试网络
从混淆矩阵可以清楚地看出,网络对某些类别做出了正确的预测。建议您调整超参数,并检查哪些类会因网络变化而受到影响。
在这个用例中,我们使用 AlexNet 在 CIFAR-10 数据集上工作,我们获得了 74.80%的验证准确率。接下来让我们训练我们的 VGG 网络并测量它的性能。
4.9 利用 VGG 研究 CIFAR-10
我们现在将使用 VGG 网络在 CIFAR-10 上开发一个分类解决方案。
由于这里的大多数步骤与上一节中的步骤相似,所以我们不会解释所有的代码片段,但是只要与前面的讨论有差异,我们都会详细说明。
-
导入库。
import keras from keras.datasets import cifar10 from keras.preprocessing.image import ImageDataGenerator from keras.models import Sequential from keras.callbacks import ModelCheckpoint from keras.layers import Dense, Dropout, Activation, Flatten from keras.layers import Conv2D, MaxPooling2D, BatchNormalization from keras import optimizers import numpy as np from keras.layers.core import Lambda from keras import backend as K from keras import regularizers import matplotlib.pyplot as plt import warnings warnings.filterwarnings("ignore")
-
设置你的超参数。
number_classes = 10 wght_decay = 0.00005 x_shape = [32,32,3] batch_size = 64 maxepoches = 30 learning_rate = 0.1 learning_decay = 1e-6 learning_drop = 20
-
加载数据并生成一些图像。
(x_train, y_train), (x_test, y_test) = cifar10.load_data() x_train = x_train.astype('float32') x_test = x_test.astype('float32') fig = plt.figure(figsize=(18, 8)) columns = 5 rows = 5 for i in range(1, columns*rows + 1): fig.add_subplot(rows, columns, i) plt.imshow(x_train[i], interpolation="lanczos")
-
标准化图像数据。
mean = np.mean(x_train,axis=(0,1,2,3)) std = np.std(x_train, axis=(0, 1, 2, 3)) x_train = (x_train-mean)/(std+1e-7) x_test = (x_test-mean)/(std+1e-7) y_train = keras.utils.to_categorical(y_train, number_classes) y_test = keras.utils.to_categorical(y_test, number_classes)
-
立即创建您的 VGG16 模型。
model = Sequential() model.add(Conv2D(64, (3, 3), padding="same", input_shape=x_shape,kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.3)) model.add(Conv2D(64, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(128, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(128, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(256, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(256, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(256, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(512, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(512, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(512, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(512, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(512, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.4)) model.add(Conv2D(512, (3, 3), padding="same",kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.5)) model.add(Flatten()) model.add(Dense(512,kernel_regularizer=regularizers.l2(wght_decay))) model.add(Activation('relu')) model.add(BatchNormalization()) model.add(Dropout(0.5)) model.add(Dense(number_classes)) model.add(Activation('softmax'))
-
生成模型摘要。
-
从图像增强步骤到拟合 VGG 网络,步骤与上一节中的 AlexNet 示例完全相同。
image_augm = ImageDataGenerator( featurewise_center=False, samplewise_center=False, featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, rotation_range=12, width_shift_range=0.2, height_shift_range=0.1, horizontal_flip=True, vertical_flip=False) image_augm.fit(x_train) sgd = optimizers.SGD(lr=learning_rate, decay=learning_decay, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy', optimizer=sgd,metrics=['accuracy']) filepath="weights.best.hdf5" checkpoint = ModelCheckpoint(filepath, monitor="val_acc", verbose=1, save_best_only=True, mode="max") callbacks_list = [checkpoint] trained_model = model.fit_generator(image_augm.flow(x_train, y_train, batch_size=batch_size), steps_per_epoch=x_train.shape[0]//batch_size, epochs=maxepoches, validation_data=(x_test, y_test),callbacks=callbacks_list,verbose=1)
model.summary()
当你训练你的模型时,你将得到这个输出。我们只展示最后的时代。
最佳验证准确率为 82.74%。
- 画出精确度量,最后画出混淆矩阵。代码和 AlexNet 用的一模一样。
这是精度图(图 4-10 )。
图 4-10
VGG 网络的训练和验证准确度图
这里是损失图(图 4-11 )。
图 4-11
VGG 网络的训练和验证损失图
这是混淆矩阵:
下面是混淆矩阵图(图 4-12 )。
图 4-12
为 VGG 图生成的混淆矩阵
请注意,验证损失比训练准确度小。我们通常期望训练精度高于测试精度,但是这里我们看到了相反的情况。其原因如下:
-
当我们使用 Keras 生成深度学习解决方案时,有两种模式,即训练和测试。在测试阶段,辍学或 L1/L2 体重调整被关闭。
-
我们计算每批训练数据的平均损失以获得训练损失,而为了计算测试损失,使用模型并在时期结束时进行;因此,测试损失较低。
-
此外,随着时间的推移,我们通过训练不断地改变模型,因此最初批次中计算的损失大多高于最终批次。
一般来说,你会发现测试精度低于训练精度。如果测试精度远低于训练精度,我们称之为过拟合,我们已经在第一章中讨论过。我们将在本书后面的章节中再次详细探讨这个概念。
我们现在将比较这两种模型的性能。
4.10 比较 AlexNet 和 VGG
如果我们对比 AlexNet 和 VGGNet 的性能,我们会发现
-
VGG 的验证准确率高于 AlexNet(分别为 82.74%和 74.80%)。
-
混淆矩阵显示,与 AlexNet 相比,VGG 预测了更高数量的正确类别。
但这并不意味着我们可以概括 VGG 的表现总是比 AlexNet 更好。根据数据集和手头的业务问题,我们测试这两个网络。
注意,对于我们讨论的例子,AlexNet 被训练了 50 个时期,相比之下,VGGNet 被训练了 30 个时期。试着训练相同的历元数,分析差异。
在决定挑选网络时,我们比较了这两种体系结构。我们比较了所需的训练时间、数据集要求、历元方向的移动以及最终的精度 KPI。然后我们得出结论。
这两种架构在深度学习社区中都经常被引用和推崇。
4.11 使用 CIFAR-100
建议您对 CIFAR-100 数据集使用类似的代码。代码中有一些变化,如下所示。
导入库时,不要导入 cifar10,而是导入 cifar100。
from keras.datasets import cifar100
类似地,加载 CIFAR-100 的数据集。
(x_train, y_train), (x_test, y_test) = cifar100.load_data()
班级人数是 100 人而不是 10 人。凡是提到班级人数的地方,都改成 100。然后拟合模型,分析结果。
至此,我们已经使用数据集完成了 Python 代码的实现。我们现在可以进行本章的总结了。
4.12 摘要
在本章中,我们介绍了 AlexNet 和 VGGNet,并使用 CIFAR 数据集创建了解决方案。
这两个网络通常用于测试任何基于计算机视觉的解决方案。它们易于理解,实施起来也很快。
AlexNet 和 VGG 在论文和文献中被多次引用。它们也广泛应用于许多实际解决方案中。在实际实施过程中,数据集的质量将决定网络的预测能力。因此,如果您使用网络在自定义数据集上实现,则需要对图像的收集进行尽职调查。
需要注意的重要一点是,数据集应该代表真实世界的业务场景。如果数据集没有捕获真实世界的业务场景,解决方案将无法解决业务问题。而且大量的时间和资源都花在了获取数据集上。我们将在本书的最后一章中再次讨论这些概念——在这一章中,我们将详细讨论这些需求。
现在,让我们转向下一个架构,开发更多实际的用例。让我们继续这段旅程吧!
你现在应该能回答练习中的问题了!
Review Exercises
-
AlexNet 和 VGG16 有什么区别?
-
解释检查点的意义。
-
使用上一章中使用的德国交通标志数据集,并对其拟合 VGG16 和 VGG19。比较两个模型的精确度。
-
了解 VGG16 和 AlexNet 的行业实现。
-
从
https://data.mendeley.com/datasets/4drtyfjtfy/1
下载用于影像分类的多类天气数据集。开发 AlexNet 和 VGG 网络并比较其准确性。 -
从
https://data.mendeley.com/datasets/5y9wdsg2zt/2
获取用于分类的混凝土裂缝图像数据集,并开发 VGG16 和 VGG19 解决方案。
进一步阅读
-
AlexNet 原创论文在
https://papers.nips.cc/paper/4824-imagenet- classification-with-deep-convolutional-neural-networks.pdf
。 -
VGG16 原纸在
https://arxiv.org/pdf/1409.1556.pdf
。 -
关于 CIFAR-10 的论文可在
https://paperswithcode.com/sota/image-classification-on-cifar-10
查阅。 -
关于 CIFAR-100 的论文可在
https://paperswithcode.com/sota/image-classification-on-cifar-100
查阅。
五、使用深度学习的对象检测
仅仅因为某样东西没有按照你的计划去做,并不意味着它是无用的。—托马斯·爱迪生
为了解决一个特定的问题,我们会尝试多种解决方案,很多时候经过几次迭代,我们会找到最佳解决方案。机器学习和深度学习没什么区别。在发现阶段,进行改进和不断修改,以提高先前版本算法的性能。在最后一个阶段观察到的弱点、计算的缓慢、分类的不正确——都为更好的解决方案铺平了道路。
在上两章中,我们了解并创建了将图像分类为二进制或多类的解决方案。但是大多数图像中只有一个物体。我们没有识别出图像中物体的位置。我们只是简单地说物体是否出现在图像中。
在这一章中,我们将识别任何图像中的对象。同时,它的位置也将通过在其周围创建一个边界框来确定。这比我们迄今为止开发的图像分类解决方案领先了一步。
有相当多的对象检测架构,如 R-CNN、快速 R-CNN、更快 R-CNN、SSD(单次多盒检测器)和 YOLO(你只看一次)。
我们将在本章中研究这些网络架构,并为其创建 Python 解决方案。
我们将在本章中讨论以下主题:
-
对象检测和用例
-
R-CNN、快速 R-CNN 和更快 R-CNN 架构
-
单发多盒探测器
-
你只看一次(YOLO)
-
算法的 Python 实现
欢迎来到第五章,祝一切顺利!
5.1 技术要求
本章的代码和数据集上传到本书的 GitHub 链接 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter5
。一如既往,本章我们将使用 Python Jupyter 笔记本。对于这一章,可能需要一个 GPU 来执行代码,您可以使用 Google Colaboratory。设置 Google Colab 的说明。与本书最后一章相同。
让我们在下一部分继续深入学习架构。
5.2 物体检测
对象检测是机器学习和深度学习领域中被引用和认可最多的解决方案之一。这是一个相当新颖的解决方案,解决起来非常有趣。对象检测的用例相当多。因此,组织和研究人员正在花费大量的时间和资源来发现这种能力。顾名思义,目标检测是一种在图像或视频中定位目标的计算机视觉技术。该检测也可以在实况流视频中进行。当我们人类看图片时,我们可以快速识别图像中的对象及其各自的位置。我们可以快速分类它是苹果还是汽车还是人。我们也可以从任何角度确定。原因是我们的大脑被训练成能够识别各种物体。即使一个物体的尺寸变得越来越小或越来越大,我们也能够定位和探测它们。
目标是使用机器学习和深度学习来复制这种决策智能。我们将研究对象检测、定位和分类的概念,并开发 Python 代码。
但是在研究对象检测的基础知识之前,我们应该首先研究对象分类、对象定位和对象检测之间的区别。它们是对象检测的构建概念。现在,我们将在下一节研究这三个组成部分的区别。
5.2.1 物体分类、物体定位和物体检测
请看图 5-1 中吸尘器的图片。在前面的章节中,我们开发了图像分类解决方案,将这些图像分类为“真空吸尘器”或“非真空吸尘器”因此,我们可以很容易地将第一张图片标记为真空吸尘器。
另一方面,定位是指在图像中找到对象的位置。因此,当我们进行图像定位时,这意味着该算法具有双重职责:对图像进行分类,并在其周围绘制一个边界框,如第二幅图像所示。在图 5-1 的第一张图中,我们有一个真空吸尘器,在第二张图中,我们已经对它进行了定位。特小号
图 5-1
物体检测意味着物体的识别和定位。在第一张图中,我们可以分类它是否是吸尘器,而在第二张图中,我们在它周围画一个方框,这是图像的定位
xs
为了扩展解决方案,我们可以在同一幅图像中有多个对象,甚至在同一幅图像中有多个不同类别的对象,我们必须识别所有这些对象。并在它们周围绘制边界框。一个例子可以是被训练来检测汽车的解决方案。在繁忙的道路上,会有许多汽车,因此解决方案应该能够检测到每一辆汽车,并在它们周围绘制边界框。
物体检测无疑是一个奇妙的解决方案。我们现在将在下一节讨论主要的对象检测用例。
5.2.2 物体检测的使用案例
深度学习已经扩展了跨领域和跨组织的许多能力。物体检测是一个关键的问题,也是一个非常强大的解决方案,它在我们的商业和个人世界中产生了巨大的影响。对象检测的主要用例有
-
物体检测是自动驾驶技术背后的关键智能。它允许用户检测汽车,行人,背景,摩托车,等等,以提高道路安全。
-
我们可以检测人们手中的物体,该解决方案可用于安全和监控目的。监控系统可以变得更加智能和精确。人群控制系统可以变得更加复杂,反应时间将会缩短。
-
一种解决方案可以用于检测购物篮中的对象,并且它可以被零售商用于自动交易。这将加快整个过程,减少人工干预。
-
物体检测也用于测试机械系统和生产线。我们可以检测产品上可能污染产品质量的物体。
-
在医学界,通过分析身体部位的图像来识别疾病将有助于更快地治疗疾病。
没有预见到这种用途的领域非常少。这是一个被高度研究的领域,每天都有新的进展。全球各地的组织和研究人员正在这一领域掀起巨大的波澜,并创造出突破性的解决方案。
我们已经研究了对象检测的主要用例。现在让我们来看一些物体检测的方法。
5.3 物体检测方法
我们可以使用机器学习和深度学习来执行对象检测。我们将在本书中讨论深度学习方法,但对于好奇的读者,这里有几个解决方案:
-
使用简单属性(如对象的形状、大小和颜色)进行图像分割。
-
我们可以使用聚合通道功能(ACF ),它是通道功能的变体。ACF 不计算不同位置或比例的矩形和。相反,它直接以像素值的形式提取特征。
-
Viola-Jones 算法可以用于人脸检测。建议的论文在本章末尾。
还有其他解决方案,如 RANSAC(随机样本一致性)、基于 Haar 特征的级联分类器、使用 HOG 特征的 SVM 分类器等等,可以用于对象检测。在本书中,我们将重点放在深度学习方法上。
以下深度学习架构通常用于对象检测:
-
R-CNN:有 CNN 特色的地区。它将地区提案与 CNN 结合起来。
-
快速 R-CNN:一种基于快速区域的卷积神经网络。
-
更快的 R-CNN:基于区域提议算法的目标检测网络,以假设目标位置。
-
屏蔽 R-CNN:这个网络通过在每个感兴趣的区域上增加分段屏蔽的预测来扩展更快的 R-CNN。
-
YOLO:你只能看一次建筑。它提出了一个单一的神经网络来预测边界框和分类概率的图像在一个单一的评估。
-
SSD:单次多盒探测器。它提出了一种使用单一深度神经网络来预测图像中的对象的模型。
现在,我们将在下一节中检查深度学习框架。有一些基本概念需要研究,它们构成了目标检测技术的基础。我们也会研究他们。在深度学习框架的讨论之后,我们将创建 Python 解决方案。
5.4 用于对象检测的深度学习框架
我们现在将从基于深度学习的对象检测算法和架构开始。它们由一些组件和概念组成。在深入研究架构之前,让我们先认识一下物体检测的几个重要组成部分。其中最关键的是
-
用于目标检测的滑动窗口方法
-
包围盒方法
-
并集上的交集
-
非最大抑制
-
锚盒概念
在下一节中,我们将从滑动窗口方法开始。
5.4.1 用于对象检测的滑动窗口方法
当我们想要检测对象时,一个非常简单的方法可以是:为什么不将图像划分为区域或特定区域,然后对它们中的每一个进行分类。这种物体检测的方法是滑动窗口。顾名思义,它是一个可以在整个图像中滑动的矩形框。该框具有固定的长度和宽度,可以在整个图像上移动一大步。
请看图 5-2 中真空吸尘器的图像。我们在图像的每个部分使用滑动窗口。红色方框在吸尘器的整个图像上滑动。从左到右,然后垂直,我们可以观察到图像的不同部分正在成为观察点。因为窗口是滑动的,所以它被称为滑动窗口方法。
图 5-2
滑动窗口方法检测并识别目标。请注意滑动框是如何在整个图像中移动的;该过程能够检测到,但实际上是一个耗时的过程,并且计算量也很大
然后,对于这些被裁剪的区域中的每一个,我们可以分类该区域是否包含我们感兴趣的对象。然后我们增加滑动窗口的大小,继续这个过程。
滑动窗口已经被证明是可行的,但是它是一种计算量非常大的技术,并且由于我们要对图像中的所有区域进行分类,所以实现起来会很慢。此外,为了定位对象,我们需要一个小窗口大小和小步幅。但这仍然是一个简单的理解方法。
下一种方法是包围盒方法。
5.5 包围盒方法
我们讨论了滑动窗口方法。它输出不太精确的边界框,因为它依赖于窗口的大小。因此,我们有另一种方法,其中我们将整个图像分成网格(x 乘 x),然后为每个网格定义我们的目标标签。我们可以在图 5-3 中显示一个边框。
图 5-3
边界框可以生成边界框的 x 坐标、y 坐标、高度和宽度以及类别概率分数
边界框可以给我们以下细节:
-
Pc:网格单元中有对象的概率(0:无对象,1:有对象)。
-
Bx:如果 Pc 为 1,则为包围盒的 x 坐标。
-
By:如果 Pc 为 1,则为边界框的 y 坐标。
-
Bh:如果 Pc 是 1,就是包围盒的高度。
-
Bw:如果 Pc 为 1,则为边界框的宽度。
-
C1:它是物体属于第一类的概率。
-
C2:这是物体属于第二类的概率。
Note
类别的数量取决于手头的问题是二元分类还是多标分类。
如果一个对象位于多个网格上,那么包含该对象中点的网格负责检测该对象。
Info
通常建议使用 19x19 的网格。此外,一个对象的中点位于两个独立的网格中的可能性更小。
到目前为止,我们正在研究确定对象的方法;下一个主题是测量并的交集检测的性能。
5.6 联合交集(IoU)
我们已经研究了一些用于物体检测的方法。在随后的章节中,我们也将研究深度学习架构。但是,我们仍然需要确定我们在物体探测中预测的准确性。交集超过并集是一个测试,以确定我们的预测有多接近实际真相。
用等式 5-1 表示,如图 5-4 所示。
图 5-4
并集上的交集被用来衡量检测的性能。分子是公共面积,而分母是两个面积的完全并集。借据的价值越高越好
IoU =重叠区域/合并的整个区域(等式 5-1)
因此,如果交集比并集的值高,就意味着重叠更好。因此,预测更准确、更好。在图 5-5 中的示例中对其进行了描述,以便形象化。
图 5-5
重叠模块不同位置的 IoU 值。如果该值更接近 1.0,则意味着与值 0.15 相比,检测更准确
正如我们在图 5-5 中看到的,对于 0.15 的 IoU,与 0.85 或 0.90 相比,两个方框之间的重叠非常少。这意味着 0.85 IoU 的解决方案优于 0.15 IoU 的解决方案。因此,可以直接比较检测解决方案。
交集/并集允许我们测量和比较各种解决方案的性能。这也让我们更容易区分有用的边界框和不太重要的边界框。并集上的交是一个有着广泛用途的重要概念。使用它,我们可以比较和对比所有可能解决方案的可接受性,并从中选择最佳方案。
我们现在将研究非最大值抑制技术,这对于过滤重要的边界框是有用的。
5.7 非最大抑制
当我们试图检测图像中的一个对象时,我们可以将一个对象放在多个网格中。可以用图 5-6 来表示。显然,概率最高的网格将是该物体的最终预测。
图 5-6
一个对象可以跨越多个网格,并且无论哪一个给出最佳结果,都是最终用于检测目的的所选网格
整个过程按以下步骤完成:
-
得到所有网格各自的概率。
-
设置概率阈值和 IoU 阈值。
-
丢弃低于该阈值的那些。
-
选择概率最大的方框。
-
计算剩余盒子的借据。
-
丢弃低于 IoU 阈值的那些。
使用非最大值抑制,我们删除了大多数低于某一阈值水平的边界框。
Info
一般情况下,该值保持在 0.5。建议您使用不同的值进行迭代来分析差异。
因此,该算法保留了重要的和有意义的噪声,而去除了较大的噪声。我们现在将着手研究锚盒,这是物体检测过程中的另一个重要因素。
5.8 锚箱
我们希望在深度学习中检测对象,我们需要一种快速准确的方法来获得对象的位置和大小。锚盒是一个有助于检测对象的概念。
锚定框用于捕捉我们希望检测的对象的缩放比例和纵横比。它们具有预定义的大小(高度和宽度),并且基于我们想要检测的对象的大小来确定大小。我们在图 5-7 中展示了锚盒。
图 5-7
锚定框用于捕捉缩放比例和纵横比。我们可以平铺锚盒,神经网络将输出一组独特的预测
在对象检测过程中,每个锚框平铺在图像上,神经网络为每个锚框输出一组唯一的预测。输出由每个锚定框的概率得分、IoU、背景和偏移组成。基于所做的预测,可以进一步改进锚盒。
我们可以有多种尺寸的锚盒来检测不同尺寸的物体。使用锚盒,我们可以探测不同尺度的物体。甚至到了可以使用锚定框来检测多个或重叠对象的程度。这无疑是对滑动窗口的巨大改进,因为我们现在可以在一次拍摄中处理整个图像,从而使更快的实时物体检测成为可能。要注意的是,网络不预测边界框。网络预测平铺锚定框的概率得分和改进。
现在,我们已经研究了关键部件;现在我们从深度学习架构开始。
5.9 深度学习架构
深度学习有助于物体检测。我们可以在图像、视频甚至实时视频流中检测感兴趣的对象。在本章的后面,我们将创建一个实时视频流解决方案。
我们之前已经看到滑动窗口方法存在一些问题。对象在图像中可以有不同的位置,并且可以有不同的纵横比或大小。物体可能覆盖整个区域;另一方面,在某些地方,它将只覆盖一小部分。图像中可能存在不止一个对象。物体可以是各种角度或尺寸。或者一个对象可以位于多个网格中。此外,一些用例需要实时预测。这导致具有非常大量的区域,并因此具有巨大的计算能力。这也需要相当长的时间。在这种情况下,传统的图像分析和检测方法没有多大帮助。因此,我们需要基于深度学习的解决方案来解决和开发鲁棒的对象检测解决方案。
基于深度学习的解决方案使我们能够更好地训练,从而获得更好的结果。我们现在正在讨论架构。
让我们从 R-CNN 作为第一个架构开始吧!
5.9.1 基于区域的 CNN (R-CNN)
我们知道拥有大量区域是一项挑战。Ross Girshick 等人提出了 R-CNN 来解决选择大量区域的问题。R-CNN 是基于区域的 CNN 架构。该解决方案建议使用选择性搜索并从图像中仅提取 2000 个区域,而不是对大量区域进行分类。它们被称为“地区提案”
R-CNN 的架构如图 5-8 所示。
图 5-8
R-CNN 的流程。这里,我们从输入图像中提取区域建议,计算 CNN 特征,然后对区域进行分类。图片来源: https://arxiv.org/pdf/1311.2524.pdf
并经研究者许可在此发表
参照图 5-8 所示的过程,现在让我们详细了解整个过程:
-
第一步是输入图像,如图 5-8 中的步骤 1 所示。
-
然后得到我们感兴趣的区域,如图 5-8 中步骤 2 所示。这些是 2000 年提议的区域。使用以下步骤检测它们:
-
我们为图像创建初始分割。
-
然后,我们为图像生成各种候选区域。
-
我们迭代地将相似的区域组合成更大的区域。贪婪搜索方法被用于它。
-
最后,我们使用生成的区域来输出最终的区域建议。
-
-
然后在下一步中,我们根据 CNN 中的实现重塑所有的 2000 个区域。
-
然后,我们通过 CNN 遍历每个区域,以获得每个区域的特征。
-
所提取的特征现在通过支持向量机来对所提议的区域中对象的存在进行分类。
-
然后,我们使用包围盒回归来预测物体的包围盒。这意味着我们正在对图像进行最终预测。如上一步所示,我们正在预测图像是飞机、人还是电视显示器。
R-CNN 使用前面的过程来检测图像中的对象。这无疑是一个创新的架构,它提出了一个感兴趣的区域作为检测对象的有效概念。
但是 R-CNN 也有一些挑战,它们是
-
R-CNN 实现了三种算法(CNN 用于提取特征,SVM 用于对象分类,边界框回归用于获得边界框)。这使得 R-CNN 解决方案的训练速度相当慢。
-
它使用 CNN 为每个图像区域提取特征。区域数是 2000。这意味着,如果我们有 1000 张图像,要提取的特征数量是 1000 乘以 2000,这又会使速度变慢。
-
由于这些原因,对图像进行预测需要 40-50 秒,因此这对大型数据集来说是个问题。
-
还有,选择性搜索算法是固定的,不能做太多改进。
由于 R-CNN 不是很快,并且对于大型数据集来说很难实现,相同的作者提出了快速 R-CNN 来克服这些问题。让我们了解下一节建议的改进!
5.10 快速 R-CNN
在 R-CNN 中,由于我们为一幅图像提取 2000 个区域提议,因此训练或测试图像在计算上是一个挑战。为了解决这个问题,Ross Girshick 等人提出,不是对每个图像执行 CNN 2000 次,而是对一个图像只运行一次,并得到所有感兴趣的区域。
网络的架构如图 5-9 所示。
图 5-9
快速 R-CNN 中的过程。我们获取感兴趣区域,然后通过应用感兴趣区域合并图层来重塑所有输入。然后由 FC 层对它们进行评估,最后由 softmax 进行分类。图片来源: https://arxiv.org/pdf/1504.08083.pdf
并经研究者许可在此发表
除了几处变化之外,该方法与其前身相似:
-
该图像是如图 5-9 所示的输入。
-
图像被传递到卷积网络,卷积网络返回相应的感兴趣区域。
-
在下一步中,我们应用利息池区域图层。它导致按照卷积的输入对所有区域进行整形。因此,它通过应用 ROI 池层使所有感兴趣区域的大小相同。
-
现在,这些区域中的每一个都被传递给完全连接的网络。
-
最后由 softmax 层完成分类。并行地,使用边界框回归器来识别边界框的坐标。
快速 R-CNN 比 R-CNN 有几个优点:
-
快速 R-CNN 不需要每次向 CNN 馈送 2000 个区域提议,因此它比 R-CNN 更快。
-
它对每幅图像只使用一次卷积运算,而不是 R-CNN 中使用的三次(提取特征、分类和生成边界框)。因此不需要存储特征映射,从而节省了磁盘空间。
-
通常,softmax 图层比 SVM 具有更好的精度,并且执行时间更快。
快速的 R-CNN 大大减少了训练时间,并且被证明更加准确。但是由于使用选择性搜索作为获得感兴趣区域的建议方法,性能仍然不够快。因此,对于大型数据集,预测速度不够快。这就是为什么我们有更快的 R-CNN,这是我们接下来要讨论的。
5.11 更快的 R-CNN
为了克服 R-CNN 和快速 R-CNN 的慢,邵青·兰等人提出了更快的 R-CNN。更快的 R-CNN 背后的直觉是取代缓慢且耗时的选择性搜索。更快的 R-CNN 使用区域提议网络或 RPN。论文可在 https://papers.nips.cc/paper/2015/file/14bfa6bb14875e45bba028a21ed38046-Paper.pdf
访问
更快的 R-CNN 的架构如图 5-10 所示。
图 5-10
更快的 R-CNN 是对以前版本的改进。它由两个模块组成——一个是深度卷积网络,另一个是快速 R-CNN 检测器
引用原文 https://papers.nips.cc/paper/2015/file/14bfa6bb14875e45bba028a21ed38046-Paper.pdf:
我们的物体探测系统,叫做更快的 R-CNN,由两个模块组成。第一个模块是提出区域的深度全卷积网络,第二个模块是使用所提出区域的快速 R-CNN 检测器。整个系统是一个单一的、统一的目标检测网络。
让我们深入了解一下架构。更快的 R-CNN 的工作方式如下:
-
我们获取一个输入图像,并使其通过 CNN,如图 5-10 所示。
-
From the feature maps received, we apply Region Proposal Networks (RPNs). The way an RPN works can be understood by referring to Figure 5-11.
图 5-11
区域建议网络用于更快的 R-CNN。图像取自原始文件
The substeps followed are
-
RPN 采用上一步生成的特征地图。
-
RPN 应用滑动窗口并生成 k 个锚盒。我们已经在上一节讨论了锚盒。
-
生成的锚盒具有不同的形状和大小。
-
RPN 还将预测锚点是否是对象。
-
它还会给出边界框回归量来调整锚点。
-
要注意的是,RPN 没有建议对象的类别。
-
我们将获得目标提案和相应的目标得分。
-
-
应用投资回报池,使所有提案的规模相同。
-
最后,我们用 softmax 和线性回归将它们输入到完全连接的层中。
-
我们将接收预测的对象分类和各自的边界框。
快速 R-CNN 能够结合智能并使用深度卷积完全连接层和使用建议区域的快速 R-CNN。整个解决方案是用于对象检测的单一且统一的解决方案。
虽然相对于 R-CNN 和快速 R-CNN,更快的 R-CNN 在性能上确实有所改进,但是该算法仍然不能同时分析图像的所有部分。相反,图像的每个部分都是按顺序分析的。因此,需要在单个图像上进行大量的传递来识别所有的对象。此外,由于许多系统按顺序工作,一个系统的性能取决于前面步骤的性能。
在下一节中,我们将继续讨论最著名的算法之一——YOLO 或你只看一次。
5.12 你只看一次(YOLO)
你只看一次或 YOLO 是实时对象检测的目标。我们之前讨论的算法使用区域来定位图像中的对象。这些算法着眼于图像的一部分,而不是完整的图像,而在 YOLO,一个单一的 CNN 预测边界框和各自的类别概率。YOLO 于 2016 年由约瑟夫·雷德蒙、桑托什·迪夫瓦拉、罗斯·吉斯克和阿里·法尔哈迪提出。实际论文可在 https://arxiv.org/pdf/1506.02640v5.pdf
访问。
引用实际论文中的话,“我们将对象检测重新定义为一个单一的回归问题,直接从图像像素到边界框坐标和类别概率。”
如图 5-12 所示,YOLO 将一幅图像划分为一个网格状的单元(用 S 表示)。每个单元预测边界框(由 B 表示)。然后,YOLO 对每个边界框进行处理,并生成一个关于该框形状好坏的置信度得分。还预测了对象的分类概率。最后,选择具有上述类别概率分数的边界框,并且使用它们来定位该图像内的对象。
图 5-12
YOLO 过程很简单;图片摘自原纸 https://arxiv.org/pdf/1506.02640v5.pdf
5 . 12 . 1 YOLO 的显著特征
(方程式 5-2)
-
YOLO 把输入图像分成一个 SxS 网格。要注意的是,每个网格只负责预测一个对象。如果对象的中心落在网格单元中,则该网格单元负责检测该对象。
-
对于每个网格单元,它预测边界框(B)。每个边界框都有五个属性——x 坐标、y 坐标、宽度、高度和置信度。换句话说,它有(x,y,w,h)和一个分数。这个置信度得分是盒子里有一个物体的置信度。它也反映了边界框的准确性。
-
宽度 w 和高度 h 被标准化为图像的宽度和高度。x 和 y 坐标表示相对于网格单元边界的中心。
-
置信度定义为概率(目标)乘以 IoU。如果没有对象,置信度为零。否则,置信度等于预测框和实际情况之间的 IoU。
-
每个网格单元预测 C 个条件类概率–Pr(class I | Object)。这些概率取决于包含对象的网格单元。我们只预测每个网格单元的一组类别概率,而不考虑盒子 b 的数量。
-
在测试时,我们将条件类概率和单个类预测相乘。它给出了每个盒子的特定类别的置信度得分。它可以用等式 5-2 来表示:
我们现在将研究如何计算 YOLO 的损失函数。在我们能够详细研究整个架构之前,获得损失函数计算函数是很重要的。
YOLO 的损失函数
我们在上一节已经看到,YOLO 为每个单元格预测了多个边界框。我们选择具有最大 IoU 的包围盒。为了计算损耗,YOLO 优化了模型中输出的平方和误差,因为平方和误差易于优化。
损失函数如等式 5-3 所示,包括定位损失、置信度损失和分类损失。我们首先表示完整的损失函数,然后详细描述各项。
(方程式 5-3)
在等式 5-3 中,我们有定位损失、置信度损失和分类损失,其中 1 obj i 表示对象是否出现在单元 i 中,1objij 表示单元 i 中的 j 第个边界框预测器“负责”该预测。
让我们来描述一下前面等式中的各项。在这里,我们有
-
局部化损失是为了测量预测边界框的误差。它测量它们的位置和尺寸误差。在上式中,前两项代表定位损耗。1obj?? I 如果单元格 I 中的第 j 个边界框负责检测物体,则为 1,否则为 0。λ coord 负责边界框坐标损失的权重增加。λ 坐标的默认值为 5。
-
置信度损失是在盒子中检测到物体时的损失。这是所示等式中的第二个损失项。在前一学期,我们有
- 下一项是如果没有检测到物体的置信度损失。在前一学期,我们有
- 最后一项是分类损失。如果对象确实被检测到,那么对于每个单元,它是每个类的类概率的平方误差。
最终损失是所有这些部分的总和。作为任何深度学习解决方案的目标,目标将是最小化该损失值。
现在我们已经理解了 YOLO 和损失函数的属性;我们现在将着手研究 YOLO 的实际建筑。
YOLO 建筑
网络设计如图 5-13 所示,取自 https://arxiv.org/pdf/1506.02640v5.pdf
的实际纸张。
图 5-13
完整的 YOLO 建筑;图片取自 https://arxiv.org/pdf/1506.02640v5.pdf
的原纸
在论文中,作者提到该网络受到了谷歌网的启发。该网络有 24 个卷积层,后面是 2 个全连接层。YOLO 没有使用 GoogLeNet 使用的 Inception 模块,而是使用 1x1 缩减层,然后是 3x3 卷积层。YOLO 可能会发现同一物体的复制品。为此,实施了非最大抑制。这将删除重复的较低置信度得分。
在图 5-14 中,我们有一个 13x13 网格的图形。总共有 169 个网格,其中每个网格预测 5 个边界框。因此,总共有 169*5 = 845 个边界框。当我们应用 30%或更多的阈值时,我们得到 3 个边界框,如图 5-14 所示。
图 5-14
YOLO 过程将该区域划分成 SxS 网格。每个网格预测五个包围盒,基于阈值设置(这里是 30%),我们得到最终的三个包围盒;图像取自原始文件
所以,YOLO 只看了图像一次,但看的方式很巧妙。这是一种非常快速的实时处理算法。引用原文:
-
YOLO 简单得令人耳目一新。
-
YOLO 速度极快。因为我们把检测框架作为一个回归问题,所以我们不需要复杂的流水线。我们只是在测试时对新图像运行我们的神经网络来预测检测。我们的基础网络在 Titan X GPU 上以每秒 45 帧的速度运行,没有批处理,快速版本的运行速度超过 150 fps。这意味着我们可以实时处理流视频,延迟不到 25 毫秒。此外,YOLO 的平均精度是其他实时系统的两倍多。
-
YOLO 在做预测时会对图像进行全局推理。与基于滑动窗口和区域提议的技术不同,YOLO 在训练和测试期间看到整个图像,因此它隐式地编码了关于类及其外观的上下文信息。
-
YOLO 学习物体的概括表示。当在自然图像上训练和在艺术品上测试时,YOLO 远远超过 DPM 和 R-CNN 等顶级检测方法。由于 YOLO 是高度概括的,当应用于新的领域或意想不到的输入时,它不太可能崩溃。
YOLO 也面临一些挑战。它遭受高定位误差。此外,由于每个网格单元只能预测两个盒子,并且只能输出一个类,因此 YOLO 只能预测有限数量的附近物体。它也有召回率低的问题。因此在 YOLOv2 和 YOLOv3 的下一个版本中,这些问题都得到了解决。有兴趣的读者可以在 https://pjreddie.com/darknet/yolo/
官网获取深入的知识。
YOLO 是最广泛使用的对象检测解决方案之一。它的独特之处在于简单快捷。我们将在下一部分研究的下一个深度学习架构是单次多盒检测器或 SSD。
5.13 单次多盒探测器(SSD)
到目前为止,我们已经在上一节讨论了 R-CNN、快速 R-CNN、更快 R-CNN 和 YOLO。为了克服网络在实时对象检测中工作的缓慢性,C. Szegedy 等人在 2016 年 11 月提出了 SSD(单次多盒检测器)网络。论文可在 https://arxiv.org/pdf/1512.02325.pdf
访问。
固态硬盘使用我们在前面章节讨论过的 VGG16 架构,但做了一些修改。通过使用 SSD,只需要一次拍摄就可以检测到一个对象中的多个图像。它因此被称为单次拍摄,因为它利用单次向前传递来进行物体定位和分类。基于区域提案网络(RPN)的解决方案,如 R-CNN、Fast R-CNN,需要两次拍摄,第一次拍摄获取区域提案,第二次拍摄检测每个提案的对象。因此 SSD 被证明比基于 RPN 的方法快得多。Szegedy 等人称之为多盒,探测器这个词的意义显而易见。让我们深入探讨多盒检测机的概念。
参见图 5-15 。我们可以说,在应用和通过一系列卷积之后,我们获得了尺寸为 m×n 和 p 通道的特征层。
图 5-15
SSD 工艺如图。我们有一个与地面真相(燃气轮机)的原始图像。8×8 卷积完成。我们得到不同大小和位置的包围盒;图像取自原始文件
对于每个位置,我们将得到 k 个边界框,它们可能具有不同的大小和纵横比。并且对于这 k 个边界框中的每一个,我们计算 c 类分数和相对于原始默认边界框的四个偏移,以最终接收(c+4)个 kmn 输出。
SSD 实现平滑 L1 范数来计算位置损失。它可能没有 L1 那么精确,但仍然相当精确。
更多关于多框方法的信息可以在 https://arxiv.org/abs/1412.1441
阅读。
完整的网络如图 5-16 所示,作为与 YOLO 的对比。经作者许可,图片取自同一篇论文。
图 5-16
此处显示了 YOLO 和固态硬盘的对比。图片取自 https://arxiv.org/pdf/1512.02325.pdf
的原纸
在 SSD 中,不同层的特征图正在通过 3×3 卷积层来提高精度。如果我们分析前面的结构,我们可以观察到,对于对象检测的第一层(conv4_3),它具有 38×38 的空间维度,这在尺寸上是相当大的缩减,导致预测较小尺寸的对象的准确度较低。对于同一个 conv4_3,我们可以使用前面讨论的公式来计算输出。对于 conv4_3,输出将是 38x38x4x(c+4 ),其中 c 是要预测的类的数量。
SSD 使用两个损失函数来计算损失——置信度损失(L conf )和本地化损失(L loc )。L conf 是进行类预测的损失,而 L loc 是地面真实值和预测框之间的不匹配。前面提到的论文中给出了这两种损耗的数学公式,它们的推导超出了本书的范围。
固态硬盘还遵循其他一些重要流程:
- 通过翻转、裁剪和颜色失真来增加数据以提高准确性。每个训练示例都是随机抽样的,如下所示:
-
利用原始图像。
-
对 IoU 为 0.1、0.3、0.5、0.7 或 0.9 的贴片进行采样。
-
随机抽取一个补丁。
-
采样的面片具有 0.5 和 2 之间的纵横比,并且每个采样的面片的大小是原始大小的[0.1,1]。
-
然后,将每个采样图像调整到固定大小,并水平翻转。照片失真也用于图像增强。
-
SSD 实现非最大值抑制来移除重复的预测。我们已经在本章开始时讨论了非最大抑制。
-
SSD 产生的预测数量比实际的对象数量多。我们负面的比正面的多,造成阶层失衡。引用实际的论文:“我们没有使用所有的负面例子,而是使用每个默认框的最高置信损失对它们进行排序,并选择顶部的例子,这样负面和正面的比例最多为 3:1。我们发现这导致了更快的优化和更稳定的训练。”
基于上述架构,我们可以总结出关于 SSD 的几点:
-
检测小型物体可能是一项挑战。为了解决这个问题,我们可以提高图像分辨率。
-
精度与速度成反比;如果我们希望提高速度,我们可以增加边界框的数量。
-
SSD 比 R-CNN 具有更高的分类误差,但是定位误差较小。
-
它很好地利用较小的卷积滤波器来预测检测的类别和多尺度特征图。这有助于提高准确性。
-
引用原始论文:“SSD 的核心是使用应用于特征地图的小卷积过滤器来预测一组固定的默认边界框的类别分数和框偏移。”
SSD 的精度可以进一步提高。它混淆了具有相似类别的对象。而且是基于 VGG16 构建的,消耗了大量的训练时间。但是 SSD 是一个很棒的解决方案,可以很容易地用于端到端培训。它速度很快,可以实时运行,性能优于更快的 R-CNN。
由此,我们总结出用于对象检测的深度学习架构。我们已经讨论了主要的算法,在接下来的章节中,我们将开发实际的 Python 代码来实现这个解决方案。但在此之前,我们将考察迁移学习的概念。这是一个创新的解决方案,允许我们建立在由专家训练的最先进的算法之上。迁移学习是我们讨论的下一个话题。
5.14 迁移学习
顾名思义,迁移学习就是分享知识或将学习成果转移给他人。在深度学习的世界中,研究人员和组织创新并创建了新的神经网络架构。他们使用多代码强大处理器的最先进功能,并在精心策划和选择的大型数据集上训练算法。
对我们来说,创造这样的智能就像是重新发明轮子。因此,通过迁移学习,我们可以利用那些经过数百万个数据点训练的网络。这使我们能够使用研究人员产生的智能,并在真实世界的数据集上实现相同的功能。这个过程被称为迁移学习。
在迁移学习中,我们使用一个预先训练好的模型来达到我们的目的。预训练模型具有来自原始模型的最终权重。使用预训练模型的基本思想是,网络的初始层检测基本特征,随着我们深入,这些特征开始成形。基本特征提取可以用于任何类型的图像。所以,如果一个模型被训练来区分手机,它就可以用来区分汽车。
我们可以在图 5-17 中展示这个过程。在这里,网络的第一层被冻结,将用于提取边、线等低级要素。最后一层可以根据业务问题进行定制。
图 5-17
迁移学习利用预先训练好的网络。第一层负责提取低级特征并被冻结。最后一层是为手头的问题定制的
迁移学习使得学习比传统的机器学习更快,并且需要更少的训练数据。我们将在第八章中讨论预训练模型的更多细节。
迁移学习是通过利用在不同环境下开发的解决方案来解决现实世界的业务问题。我们将在下一节和本书的后续章节中使用迁移学习。在前面的章节中,我们可以利用迁移学习来使用这些网络。
理论已经讲得够多了,是时候开发我们的目标检测解决方案了。该打代码了!
5.15 Python 实现
我们将使用 YOLO 实现实时对象检测。必须下载预先训练的重量。代码、权重、标签和预期输出可以从本章开头给出的 GitHub 资源库链接下载。
步骤 1:导入所有需要的库。
import cv2
from imutils.video import VideoStream
import os
import numpy as np
步骤 2:从本地路径加载配置。我们正在加载权重、配置和标签。我们也在设置检测的设置。
localPath_labels = "coco.names"
localPath_weights = "yolov3.weights"
localPath_config = "yolov3.cfg"
labels = open(localPath_labels).read().strip().split("\n")
scaling = 0.005
confidence_threshold = 0.5
nms_threshold = 0.005 # Non Maxima Supression Threshold Vlue
model = cv2.dnn.readNetFromDarknet(localPath_config, localPath_weights)
第三步:现在我们从这一步的视频开始。然后,我们通过从对象模型访问来配置未连接的层。
cap = VideoStream(src=0).start()
layers_name = model.getLayerNames()
output_layer = [layers_name[i[0] - 1] for i in model.getUnconnectedOutLayers()]
Note
建议您研究模型的组件并打印出来,以便更好地理解。
步骤 4:现在我们准备执行对象的检测。此步骤是解决方案的核心步骤。它检测对象和边界框,并在框的顶部添加文本。
我们从 while 循环开始。然后我们在读框架。下一步,设置框架的宽度和高度。然后我们将循环遍历每一帧。如果置信度高于我们之前设置的置信度阈值,则对象将被检测到。
然后,我们进行标记,并且在检测到的边界框上的框中显示对象的相应置信度得分。输出如图 5-18 所示。
while True:
frame = cap.read()
(h, w) = frame.shape[:2]
blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
model.setInput(blob)
nnoutputs = model.forward(output_layer)
confidence_scores = []
box_dimensions = []
class_ids = []
for output in nnoutputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > 0.5 :
box = detection[0:4] * np.array([w, h, w, h])
(center_x, center_y, width, height) = box.astype("int")
x = int(center_x - (width / 2))
y = int(center_y - (height / 2))
box_dimensions.append([x, y, int(width), int(height)])
confidence_scores.append(float(confidence))
class_ids.append(class_id)
ind = cv2.dnn.NMSBoxes(box_dimensions, confidence_scores, confidence_threshold, nms_threshold)
for i in ind:
i = i[0]
(x, y, w, h) = (box_dimensions[i][0], box_dimensions[i][1],box_dimensions[i][2], box_dimensions[i][3])
cv2.rectangle(frame,(x, y), (x + w, y + h), (0, 255, 255), 2)
label = "{}: {:.4f}".format(labels[class_ids[i]], confidence_scores[i])
cv2.putText(frame, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,255), 2)
cv2.imshow("Yolo", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
cap.stop()
输出如图 5-18 所示。我们能够以 99.79%的准确率实时检测手机。
图 5-18
这里显示了实时对象检测。我们能够以 99.79%的准确率检测手机
在这个解决方案中,可以识别真实世界的对象。并且围绕该对象以及名称和置信度得分创建边界框。
该解决方案可用于多种使用情形。相同的代码可以为数据集定制,也可以用于检测图像和视频中的对象。
我们现在可以进行本章的总结了。
5.16 摘要
对象检测是一个非常强大的解决方案。它被应用在许多领域和操作中,几乎所有的行业都可以从目标检测中受益。它可以用于光学字符识别、自动驾驶、跟踪物体和人、人群监视、安全机制等等。这种计算机视觉技术正在真正改变实时能力的面貌。
在本章中,我们讨论了对象检测架构——R-CNN、快速 R-CNN、更快 R-CNN、YOLO 和固态硬盘。所有的网络都是基于深度学习的,在设计和架构上都很新颖。然而,有些表现优于其他人。并且通常在速度和准确性之间有一个折衷。所以基于手头的业务问题,我们必须谨慎选择网络。
我们在本章中还讨论了迁移学习。迁移学习是一种新颖的解决方案,它使用已经在数百万幅图像上训练过的预训练网络。迁移学习允许我们通过使用强大的处理器来使用研究人员和作者产生的智能。这是一个工具,使每个人都能够使用这些真正的深度网络,并根据需要定制它们。我们使用迁移学习来使用预先训练的 YOLO 来实时检测对象。我们将在其他章节中继续使用迁移学习方法。
对象检测可能存在于许多实际解决方案中,但输入数据集决定了解决方案的最终精度。因此,如果您使用网络来实现自定义数据集,请在数据收集阶段做好准备。数据将决定和定义你的成功!
现在,在下一章,我们将致力于另一个令人兴奋的话题——人脸检测和识别。让我们继续这段旅程吧!
你现在应该能回答练习中的问题了!
Review Exercises
建议您解决以下问题:
-
锚箱和非 max 压制是什么概念?
-
边界框对物体检测有多重要?
-
R-CNN、快速 R-CNN、更快速 R-CNN 有何不同,有何改进?
-
迁移学习如何提高神经网络解?
-
从
www.kaggle.com/c/open-images-2019-object-detection
下载 Open Images 2019 数据集,并使用它创建一个使用 YOLO 的解决方案。 -
从
https://public.roboflow.com/object-detection/chess-full
下载象棋数据集,并使用它根据本章中使用的网络来定位棋子。 -
从
https://public.roboflow.com/object-detection/raccoon
获取浣熊数据集,并使用它来创建对象检测解决方案。 -
从
https://cocodataset.org/#home
获取 COCO 数据集,并使用它来比较使用不同网络的性能。 -
从
https://public.roboflow.com/object-detection/vehicles-openimages
下载 Vehicles-OpenImages 数据集,并创建目标检测解决方案。
进一步阅读
-
探索论文《基于深度学习的物体检测》:
https://ieeexplore.ieee.org/document/8110383
。 -
探索论文《MobileNets:用于移动视觉应用的高效卷积神经网络》:
https://arxiv.org/pdf/1704.04861v1.pdf
。 -
探索论文《MobileNetV2:反向残差与线性瓶颈》:
https://arxiv.org/pdf/1801.04381v4.pdf
。 -
探索论文《寻找 MobileNetV3》:
https://arxiv.org/pdf/1905.02244v5.pdf
。
六、人脸识别和手势识别
谁能正确地看到人脸:摄影师、镜子还是画家?—毕加索
这一章延续了巴勃罗·毕加索的思想。我们人类对自己的脸和他人的脸、我们的微笑、我们的情绪、我们做出的不同姿势以及我们的不同表情很感兴趣。我们的手机和相机捕捉到了这一切。当我们认出一个朋友时,我们认出了这张脸——它的形状、眼睛、面部特征。有趣的是,即使我们从侧面看同一张脸,我们也能认出它。
令人惊讶的是,我们人类能够察觉到这张脸,即使我们长时间看着它。我们创造了一张脸的属性的心理位置,我们能够很容易地回忆起它。同时,我们用手做出的手势很容易被识别。深度学习能够帮助重建这种能力。人脸识别的使用非常具有创新性——它可以跨安全、监控、自动化和客户体验等领域使用——使用案例很多。这个领域正在进行大量的研究。
本章试图将同样的魔法传授给算法。
在本章中,我们将学习以下主题:
-
人脸识别
-
人脸识别过程
-
深层建筑
-
FaceNet 架构
-
人脸识别的 Python 实现
-
使用 OpenCV 进行手势识别
所以,我们继续讨论吧!
6.1 技术工具包
本章的代码和数据集上传到本书的 GitHub 链接 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter6
。对于这一章,GPU 足以执行代码,你可以使用谷歌合作实验室。我们将使用 Python Jupyter 笔记本。
让我们在下一部分继续深入学习架构。
6.2 人脸识别
人脸识别并不是什么新鲜事。我们生来就有辨别和识别面孔的能力。对我们来说,这是一项微不足道的任务。我们可以在任何背景下认出我们认识的人,不同的灯光,头发的颜色,带帽子或太阳镜,等等。即使一个人变老了或者长了胡子,我们也能认出他们。太神奇了!
现在,我们试图训练深度学习算法来实现同样的壮举。对我们来说如此琐碎和不费力的任务对机器来说并不容易。在图 6-1 中,我们有一张脸,然后我们检测一张脸,然后识别一张脸。
图 6-1
我们最初有一张脸。在第二张图片中,检测到一张脸,最后我们能够识别出一张有特定名字的脸
回想一下,在上一章中,我们学习了对象检测。我们可以将人脸识别视为对象检测的特例。我们不是在发现汽车和猫,而是在识别人。但问题更简单;我们只有要检测的对象类别——“面部”但是人脸检测不是最终状态。我们还得给这张脸起个名字,这可不是小事。而且,脸可以是任何角度;一张脸可以有不同的背景。所以,这不是一件容易的事。此外,我们可能会在照片或视频中发现面孔。深度学习算法可以帮助我们开发这样的能力。基于深度学习的算法可以利用计算能力、高级数学基础和数百万个数据点或人脸来训练更好的人脸识别模型。
在我们深入研究人脸识别的概念和实现之前,我们将探索这项功能的各种用例。
6.2.1 人脸识别的应用
人脸识别是一项非常激动人心的技术,可以跨领域和跨过程应用。一些主要用途是
-
安全管理:人脸识别解决方案适用于在线和离线安全系统。安全服务、警察部门和秘密服务利用基于机器学习的面部识别技术来追踪反社会元素。护照验证可以更快地进行,并且更加可靠。许多国家都有罪犯照片数据库,作为追踪罪犯的起点。这项技术确实节省了大量的时间和精力,并使研究人员能够将精力集中在其他领域。
-
身份验证是使用人脸识别技术的另一个重要领域。身份验证最著名的例子之一就是智能手机。Face ID 用于 iphone 和手机解锁。在线渠道和社交媒体正在使用人脸识别来检查试图访问帐户的人的身份。
-
零售商使用它来了解历史记录不太好的个人何时进入了商店。当商店扒手、罪犯或诈骗犯进入商店时,他们会构成威胁。零售商可以识别它们,并立即采取措施防止任何犯罪。
-
如果企业知道顾客的年龄、性别和面部表情,营销就会变得更加敏锐。可以安装巨型屏幕(事实上已经这样做了)来识别目标观众。
-
当分析消费者-产品交互时,消费者体验得到改善。人们触摸或尝试产品时的表情捕捉到了真实世界的互动。这些数据是产品团队对产品特性进行必要修改的金矿。同时,运营和店内团队可以让整体购物体验更加愉悦和有趣。
-
进入办公室、机场、大楼、仓库和停车场可以自动化,无需人工干预。安全摄像头拍下照片,并与数据库进行比较,以确保真实性。
前面讨论的用例只是人脸识别功能的许多应用中的一部分。因此解决方案大致为人脸认证或人脸验证或人脸识别。许多组织和国家正在创建庞大的员工/个人数据库,并投资进一步提高技能。
我们现在将着手研究面部识别的过程。它可以分为一步一步的过程,我们将在下一节讨论。
6.2.2 人脸识别过程
我们点击手机和相机中的图片。这些照片拍摄于各种场合——结婚、毕业、旅行、度假、会议等等。当我们在社交媒体上上传图片时,它会自动检测人脸,并识别出这个人是谁。一种算法在后台工作,并发挥神奇的作用。该算法不仅能够检测到人脸,还能从背景中的所有其他人脸中为其命名。我们在这一节研究类似的过程。
大致来说,我们可以围绕人脸识别进行这四个步骤,如图 6-2 所示。
图 6-2
人脸检测的流程——从检测到识别。我们检测人脸,进行比对,提取特征,最后识别人脸
-
人脸检测简单来说就是定位一张照片中是否有一张或多张人脸。我们将围绕它创建一个边界框。回想一下在第一章中,我们使用 OpenCV 做了同样的事情。如图 6-1 所示,我们在照片中检测到了人脸的存在。
-
一旦我们检测到面部,我们归一化面部的属性,如大小和几何形状。这样做是为了与我们的面部数据库相匹配。我们还减少了光照、头部运动等的影响。
-
接下来,我们从面部提取特征。一些有区别的特征是眼睛、眉毛、鼻孔、嘴角等等。
-
然后我们执行人脸识别。这意味着我们把这张脸和数据库里现有的匹配起来。我们可能会执行以下两项中的一项:
-
用已知的身份验证给定的人脸。简单来说,我们想知道“这是 X 先生吗?”。这是一对一的关系。
-
或者我们可能想知道“这个人是谁?,“而在这样的情况下我们会有一对多的关系。
-
因此,这个问题看起来像一个监督学习分类问题。
在第一章中,我们使用 OpenCV 创建了一个人脸检测解决方案。在那里,我们简单地识别是否存在人脸。人脸识别就是给那张脸起一个名字。需要注意的是,如果没有具体的人脸检测,人脸识别的尝试将是徒劳的。毕竟,首先我们应该知道一张脸是否存在,然后只有我们才能给这张脸命名。换句话说,首先要进行检测,然后指定一个名称。如果照片中有不止一个人,我们将为照片中检测到的所有人指定名字。
这就是人脸识别的整个过程。我们现在将研究用于实现相同功能的深度学习解决方案。
人脸识别的 6.2.2.1 深度学习模式
深度学习也让人脸识别感受到它的存在。回想一下,人脸识别类似于任何其他图像分类解决方案。但是人脸和特征属性使得人脸识别和检测变得非常特殊。
我们也可以使用标准的卷积神经网络进行人脸识别。网络各层的行为和处理数据的方式与任何其他图像分析问题类似。
有太多的解决方案可用,但最著名的是作为深度学习算法的 DeepFace、VGGFace、DeepID 和 FaceNet。我们将在本章中深入研究 DeepFace 和 FaceNet,并使用它们创建 Python 解决方案。
我们现在将检查 DeepFace 架构。
6.2.3 脸书的 DeepFace 解决方案
DeepFace 是由脸书人工智能研究所(FAIR)的研究人员在 2014 年提出的。实际论文可在 www.cs.toronto.edu/~ranzato/publications/taigman_cvpr14.pdf
访问。
图 6-3 显示了 DeepFace 的实际架构,摘自之前提到的同一篇论文。
图 6-3
这里展示了 DeepFace 架构。该图摘自 https://www.cs.toronto.edu/~ranzato/publications/taigman_cvpr14.pdf
的原始论文
在前面显示的体系结构中,我们可以分析网络的各个层和流程。DeepFace 期望输入图像是 152x152 的 3D 对齐的 RGB 图像。我们现在将详细探讨 3D 对齐的概念。
对准的目的是从输入图像生成正面。完整的过程如图 6-4 所示,取自同一张纸。
在第一步中,我们使用六个基准点来检测人脸。这六个基准点是两只眼睛、鼻尖和嘴唇上的三个点。在图 6-4 中,在步骤(a)中描述。该步骤检测图像中的面部。
图 6-4
DeepFace 中使用的人脸对齐过程。图像取自原始文件。我们应该注意人脸是如何逐步分析的
在第二步中,如步骤(b)所示,我们从原始图像中裁剪并生成 2D 人脸。我们应该注意到,在这一步中,人脸是如何从原始图像中裁剪出来的。
在接下来的步骤中,三角形被添加到轮廓上以避免不连续。我们在 2D 对齐的作物上应用 67 个基准点及其相应的 Delaunay 三角测量。使用 2D 到 3D 生成器生成 3D 模型,并绘制 67 个点。它还允许我们对齐平面外旋转。步骤(e)示出了相对于装配的 2D-3D 相机的可见性,并且在步骤(f)中,我们可以观察由 3D 模型引起的 67 个基准点,这些基准点用于引导分段仿射包裹。
我们现在将简要讨论 Delaunay 三角剖分。对于平面上给定的离散点集“P ”,在三角剖分 DT 中,没有一个点在 Delaunay 三角剖分中任何三角形的外接圆内。因此,它最大化了三角测量中所有三角形的最小角度。我们在图 6-5 中展示了这一现象。
图 6-5
Delaunay 三角法。影像来源:common。维基媒体。页:1。PHP?PHP?curid= 18929097
最后,我们做最后的临街作物。这是实现三维临街化目标的最后一步。
3D 临街化步骤完成后,图像就可以用于网络中的后续步骤。152x152 大小的图像是输入图像,它被馈送到下一层。
下一层是卷积层(C1 ),有 32 个大小为 11x11x3 的过滤器,后面是跨度为 2 的 3x3 最大池层。然后下一层是另一个卷积,16 个滤镜,大小 9x9x16。
图 6-6
这里显示了完整的 DeepFace 架构。图片来自原始论文。我们可以观察到,在正面化完成后,下一步是卷积过程
然后我们有三个局部相连的层。我们将简要讨论局部连接的层,因为它们与完全连接的层有些不同。
局部连接的层与完全连接的层的行为不同。对于完全连接的层,第一层的每个神经元都连接到下一层。对于本地连接的图层,我们在不同的要素地图中有不同类型的过滤器。例如,当我们分类图像是否是人脸时,我们可以只在图像的底部搜索嘴。因此,如果我们知道一个特征应该被限制在一个小空间内,并且没有必要在整个图像中搜索该特征,那么局部连接的层是很方便的。
在 DeepFace 中,我们有局部连接的层,因此我们可以改进模型,因为我们可以基于不同类型的特征地图来区分面部区域。
倒数第二层是一个完全连接的层,用于面表示。最后一层是 softmax 全连接层,用于分类。
参数总数为 1.2 亿。断开被用作一种正则化技术,但仅用于最终完全连接的图层。我们还归一化 0 和 1 之间的特征,并进行 L2 归一化。该网络在训练期间生成相当稀疏的特征映射,主要是因为 ReLU 已经被用作激活函数。
验证是在 LFW(标记为野生人脸)数据集和 SFC 数据集上进行的。LFW 包含了 5700 多位名人的 13000 多张网络图片。SFC 是脸书自己的数据集,有大约 440 万张 4030 人的图像,每个人有 800 到 1200 张面部图像。两个数据集的 ROC 曲线如图 6-7 所示。
图 6-7
取自 https://www.cs.toronto.edu/~ranzato/publications/taigman_cvpr14.pdf
原始论文的 LFW 数据集和 YTF 数据集的 ROC 曲线
DeepFace 是一种新颖的人脸识别模式。它在 LFW 数据集上的准确率超过 99.5%。它能够解决背景中的姿势、表情或光线强度问题。3D 对齐是一种非常独特的方法,可进一步提高精确度。该架构在 LFW 和 YouTube 人脸数据集(YTF)上表现非常好。
我们现在已经讨论完了 DeepFace 架构。我们现在将讨论称为 FaceNet 的下一个架构。
6.2.4 用于人脸识别的 FaceNet
在上一节中,我们学习了 DeepFace。现在我们正在研究第二种架构,叫做 FaceNet。它是由谷歌研究人员弗洛里安·施罗夫、德米特里·卡列尼琴科和詹姆斯·菲尔宾在 2015 年提出的。原论文为“FaceNet:人脸识别和聚类的统一嵌入”,可在 https://arxiv.org/abs/1503.03832
访问。
FaceNet 不推荐一套全新的算法或复杂的数学计算来执行面部识别任务。这个概念相当简单。
所有的人脸图像首先被表示在欧几里得空间中。然后我们通过计算各自的距离来计算人脸之间的相似度。考虑一下,如果我们有一个图像,X 先生的图像 1 ,那么 X 先生的所有图像或面部将更接近于图像 1 而不是 y 先生的图像 2 ,概念如图 6-8 所示。
图 6-8
爱因斯坦的图像将彼此相似,因此它们之间的距离将更小,而甘地的图像将在远处
前面的概念更容易理解。我们现在将详细了解该架构。如图 6-9 所示,我们可以检查完整的架构。图像取自原始文件本身。
图 6-9
FaceNet 架构。图片取自 https://arxiv.org/abs/1503.03832
的原纸
网络从图像的批量输入层开始。然后是深度 CNN 架构。该网络利用类似 ZFNet 或 Inception network 的架构。我们将在本书的下一章讨论盗梦空间网络。
FaceNet 实现 1x1 卷积来减少参数的数量。1x1 将在下一章中再次详细讨论。这些深度学习模型的输出是图像的嵌入。对输出执行 L2 归一化。这些嵌入是非常有用的补充。FaceNet 从面部图像中理解各自的映射,然后创建嵌入。
一旦嵌入成功完成,我们就可以简单地继续进行,并且在新创建的嵌入作为特征向量的帮助下,我们可以使用任何标准的机器学习技术。嵌入的使用是 FaceNet 和其他方法之间的主要区别,因为其他解决方案通常实现面部验证的定制层。
然后,创建的嵌入被馈送以计算损耗。如前所述,图像在欧几里得空间中表示。损失函数旨在使相似图像的两个图像嵌入之间的平方距离较小,而不同图像之间的平方距离较大。换句话说,各个嵌入之间的平方距离将决定人脸之间的相似性。
FaceNet 中实现了一个重要的概念—三重态丢失函数。
三重态损耗如图 6-10 所示;图像取自原始文件本身。
图 6-10
在 FaceNet 中使用的三重损失。图片取自 https://arxiv.org/abs/1503.03832
的原纸
三重态损耗基于我们在 FaceNet 讨论开始时在图 6-8 中讨论的概念。直觉是,我们希望同一个人 X 先生的图像彼此更接近。让我们称那个图像 1 为锚图像。X 先生的所有其他形象称为正面形象。Y 先生的图像被称为负像。
因此,根据三重损失,我们希望锚图像和正图像的嵌入之间的距离小于锚图像和负图像的嵌入之间的距离。
我们就想实现方程 6-1:
(方程 6-1)
在哪里
主播形象是 xI?? a。
正象是 xIp。
负像是 xIn,所以基本上 x i 就是一个像。
⍺是正负对之间的一个边界。这是我们设置的阈值,它表示相应图像对之间的差异。
t 是训练集中所有可能三元组的集合,基数为 n。
数学上,三重态损耗可以表示为等式 6-2。这是我们希望尽量减少的损失。
(方程式 6-2)
在前面的等式中,图像的嵌入由 f(x)表示,使得 x ∈ℝ.它将图像 x 嵌入到 d 维欧氏空间中。f(x i )是以大小为 128 的向量的形式嵌入的图像。
解决方案取决于图像对的选择。可能存在网络能够通过的图像对。换句话说,他们会满足损失的条件。这些图像对可能不会给学习增加太多,也可能导致收敛缓慢。
为了更好的结果和更快的收敛,我们应该选择不符合方程 6-1 中条件的三元组。
数学上,对于锚图像 x i a ,我们想要选择正图像 xIp 使得相似性最大,并且选择负图像 xIn 使得相似性最小。换句话说,我们希望有 arg maxxip| | f(xIa)–f(xIp)|22这意味着给定一个锚图像 xIa 我们希望有一个正图像 xI??
同样,对于给定的锚图像 x i a ,我们希望得到一个负图像 x i n 使得距离最小,表示为 arg minXin| | f(xIa)-f(xIn)|22
现在,做出这个选择并不是一件容易的事情。在训练期间,确保根据之前在小批量中给出的最大和最小函数选择阳性和阴性。使用带有 Adagrad 的 SGD(随机梯度下降)进行训练。已经使用的两个网络如下所示(ZF 网络和 Inception)。ZF 网络中有 1.4 亿个参数,初始阶段有 750 万个参数。
使用前 100 帧,该模型表现非常好,准确率为 95.12%,标准误差为 0.39。
在 LFW 数据集上,引用论文中的话:
我们的模型有两种评估模式:1 .固定中心作物的 LFW 提供。2.在提供的 LFW 缩略图上运行专有的人脸检测器(类似于 Picasa [3])。如果未能对齐面部(两幅图像都会出现这种情况),则使用 LFW 对齐。
当使用(1)中描述的固定中心裁剪时,我们实现了 98.87%±0.15 的分类准确度,并且当使用额外的面部对齐时,我们实现了破纪录的 99.63%±0.09 的平均值标准误差(2)。
FaceNet 是一种新颖的解决方案,因为它直接学习嵌入到欧几里德空间中用于人脸验证。该模型足够健壮,不受姿势、光照、遮挡或人脸年龄的影响。
我们现在将看看使用 Python 实现 FaceNet。
6.2.5 使用 FaceNet 的 Python 实现
本节中的代码是不言自明的。我们使用预训练的面网模型及其权重,并计算欧几里德距离来测量两张脸之间的相似性。我们使用的是 Sefik Ilkin Serengil 从 https://drive.google.com/file/d/1971Xk5RwedbudGgTIrGAL4F7Aifu7id1/view
公开发布的 facenet_weights。模型从 Tensorflow 转换到 Keras。基本型号可在 https://github.com/davidsandberg/facenet
找到。
步骤 1:加载库。
from keras.models import model_from_json
from inception_resnet_v1 import *
import numpy as np
from keras.models import Sequential
from keras.models import load_model
from keras.models import model_from_json
from keras.layers.core import Dense, Activation
from keras.utils import np_utils
from keras.preprocessing.image import load_img, save_img, img_to_array
from keras.applications.imagenet_utils import preprocess_input
import matplotlib.pyplot as plt
from keras.preprocessing import image
步骤 2:现在加载模型。
face_model = InceptionResNetV1()
face_model.load_weights('facenet_weights.h5')
步骤 3:我们现在将定义三个函数——标准化数据集、计算欧几里德距离和预处理数据集。
def normalize(x):
return x / np.sqrt(np.sum(np.multiply(x, x)))
def getEuclideanDistance(source, validate):
euclidean_dist = source - validate
euclidean_dist = np.sum(np.multiply(euclidean_dist, euclidean_dist))
euclidean_dist = np.sqrt(euclidean_dist)
return euclidean_dist
def preprocess_data(image_path):
image = load_img(image_path, target_size=(160, 160))
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
image = preprocess_input(image)
return image
第四步:现在我们将计算两幅图像的相似度。
在这里,我们拍摄了这两张著名板球明星的照片——Sachin Tendulkar。这两张图片取自互联网。
img1_representation = normalize(face_model.predict(preprocess_data('image_1.jpeg'))[0,:])
img2_representation = normalize(face_model.predict(preprocess_data('image_2.jpeg'))[0,:])
euclidean_distance = getEuclideanDistance(img1_representation, img2_representation)
欧几里德距离相似度是 0.70。我们还可以实现余弦相似性来测试两幅图像之间的相似性。
在下一节中,我们将使用 OpenCV 实现一个手势识别解决方案。
6.2.6 手势识别的 Python 解决方案
手势识别是帮助人类与系统对话的最具创新性的解决方案之一。手势识别意味着系统可以捕捉手势或面部手势,并且系统可以采取相应的动作。它由检测、跟踪和识别等关键部件组成。
-
在检测中,提取视觉部分,如手或手指或身体部分。视觉部分应该在摄像机的视野范围内。
-
然后我们追踪视觉部分。它确保逐帧捕捉和分析数据。
-
最后,我们识别一个或一组手势。基于我们已经完成的算法设置、使用的训练数据,系统将能够识别已经做出的手势的类型。
手势识别是一种颇具开创性的解决方案,可用于自动化、医疗设备、增强现实、虚拟现实、游戏等领域。用例很多,目前在这个领域正在进行大量的研究。
在本书中,我们将使用 OpenCV 实现一个手指计数解决方案。解决方案视频可在 www.linkedin.com/posts/vaibhavverdhan_counting-number-of-fingers-activity-6409176532576722944-Ln-R/
访问。
步骤 1:在这里导入所有的库。
# import all the necessary libraries
import cv2
import imutils
import numpy as np
from sklearn.metrics import pairwise
# global variables
bg = None
步骤 2:我们现在将编写一个函数来查找背景上的移动平均值。
#--------------------------------------------------------------
def run_avg(image, accumWeight):
global bg
# initialize the background
if bg is None:
bg = image.copy().astype("float")
return
# compute weighted average, accumulate it and update the background
cv2.accumulateWeighted(image, bg, accumWeight)
第三步:在这一步中,分割功能开始分割图像中的手部区域。
def segment(image, threshold=25):
global bg
# find the absolute difference between background and current frame
diff = cv2.absdiff(bg.astype("uint8"), image)
# threshold the diff image so that we get the foreground
thresholded = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY)[1]
# get the contours in the thresholded image
(_, cnts, _) = cv2.findContours(thresholded.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# return None, if no contours detected
if len(cnts) == 0:
return
else:
# based on contour area, get the maximum contour which is the hand
segmented = max(cnts, key=cv2.contourArea)
return (thresholded, segmented)
第四步:这个代码用于计算手指的数量。
from sklearn.metrics import pairwise
def count(thresholded, segmented):
# find the convex hull of the segmented hand region
chull = cv2.convexHull(segmented)
# find the most extreme points in the convex hull
extreme_top = tuple(chull[chull[:, :, 1].argmin()][0])
extreme_bottom = tuple(chull[chull[:, :, 1].argmax()][0])
extreme_left = tuple(chull[chull[:, :, 0].argmin()][0])
extreme_right = tuple(chull[chull[:, :, 0].argmax()][0])
# find the center of the palm
cX = int((extreme_left[0] + extreme_right[0]) / 2)
cY = int((extreme_top[1] + extreme_bottom[1]) / 2)
# find the maximum euclidean distance between the center of the palm
# and the most extreme points of the convex hull
distance = pairwise.euclidean_distances([(cX, cY)], Y=[extreme_left, extreme_right, extreme_top, extreme_bottom])[0]
maximum_distance = distance[distance.argmax()]
# calculate the radius of the circle with 80% of the max euclidean distance obtained
radius = int(0.8 * maximum_distance)
# find the circumference of the circle
circumference = (2 * np.pi * radius)
# take out the circular region of interest which has
# the palm and the fingers
circular_roi = np.zeros(thresholded.shape[:2], dtype="uint8")
# draw the circular ROI
cv2.circle(circular_roi, (cX, cY), radius, 255, 1)
# take bit-wise AND between thresholded hand using the circular ROI as the mask
# which gives the cuts obtained using mask on the thresholded hand image
circular_roi = cv2.bitwise_and(thresholded, thresholded, mask=circular_roi)
# compute the contours in the circular ROI
(_, cnts, _) = cv2.findContours(circular_roi.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# initalize the finger count
count = 0
# loop through the contours found
for c in cnts:
# compute the bounding box of the contour
(x, y, w, h) = cv2.boundingRect(c)
# increment the count of fingers only if -
# 1\. The contour region is not the wrist (bottom area)
# 2\. The number of points along the contour does not exceed
# 20% of the circumference of the circular ROI
if ((cY + (cY * 0.20)) > (y + h)) and ((circumference * 0.20) > c.shape[0]):
count += 1
return count
第五步:主要功能如下:
#--------------------------------------------------------------
# Main function
#--------------------------------------------------------------
if __name__ == "__main__":
# initialize accumulated weight
accumWeight = 0.5
# get the reference to the webcam
camera = cv2.VideoCapture(0)
# region of interest (ROI) coordinates
top, right, bottom, left = 20, 450, 325, 690
# initialize num of frames
num_frames = 0
# calibration indicator
calibrated = False
# keep looping, until interrupted
while(True):
# get the current frame
(grabbed, frame) = camera.read()
# resize the frame
frame = imutils.resize(frame, width=700)
# flip the frame so that it is not the mirror view
frame = cv2.flip(frame, 1)
# clone the frame
clone = frame.copy()
# get the height and width of the frame
(height, width) = frame.shape[:2]
# get the ROI
roi = frame[top:bottom, right:left]
# convert the roi to grayscale and blur it
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
# to get the background, keep looking till a threshold is reached
# so that our weighted average model gets calibrated
if num_frames < 30:
run_avg(gray, accumWeight)
if num_frames == 1:
print ("Calibration is in progress...")
elif num_frames == 29:
print ("Calibration is successful...")
else:
# segment the hand region
hand = segment(gray)
# check whether hand region is segmented
if hand is not None:
# if yes, unpack the thresholded image and
# segmented region
(thresholded, segmented) = hand
# draw the segmented region and display the frame
cv2.drawContours(clone, [segmented + (right, top)], -1, (0, 0, 255))
# count the number of fingers
fingers = count(thresholded, segmented)
cv2.putText(clone, str(fingers), (70, 45), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
# show the thresholded image
cv2.imshow("Thesholded", thresholded)
# draw the segmented hand
cv2.rectangle(clone, (left, top), (right, bottom), (0,255,0), 2)
# increment the number of frames
num_frames += 1
# display the frame with segmented hand
cv2.imshow("Video Feed", clone)
# observe the keypress by the user
keypress = cv2.waitKey(1) & 0xFF
# if the user pressed "q", then stop looping
if keypress == ord("q"):
break
第六步:现在释放内存。
# free up memory
camera.release()
cv2.destroyAllWindows()
该代码的输出将是一个现场视频-一些截图如下所示。
据此,我们利用深度学习和 OpenCV 创建了人脸识别和手势识别技术。我们现在开始对本章进行总结。
6.3 总结
人脸检测和识别是一个有趣的领域。面部属性相当独特。对人类来说很容易的能力很难教给机器。我们已经在本章中详细讨论了检测人脸的许多用途。它们在许多领域都有应用,并且非常容易理解。深度学习正在帮助我们实现它。路还很长,我们要在这段旅程中提高很多。有了更好的机器和复杂的算法,进步总是有可能的。
同时,捕获的面部数据集必须是干净的、有代表性的和完整的。如果图像中有大量的背景噪声,或者图像模糊或者有任何其他缺陷,那么训练网络将会非常困难。
面部和手势识别还可以有其他扩展。年龄检测、性别检测和情绪检测是一些已经在组织和研究机构中进行和开发的技术。
在这一章中,我们研究了人脸识别方法和深度学习架构。在本章中,我们研究了 DeepFace 和 FaceNet,并使用预先训练好的网络创建了一个 Python 解决方案。我们还使用 OpenCV 创建了一个手势识别解决方案。
下一章涉及计算机视觉的另一个有趣领域——视频分析。在下一章中,我们还将研究 ResNet 和初始网络这两种最新的算法。所以,敬请期待!
你现在可以开始提问了。
Review Exercises
-
人脸识别系统中的各种过程是什么?
-
面部对齐是什么概念?
-
三重态损失是什么概念?
-
手势识别的各种用例有哪些?
-
从
www.kaggle.com/kpvisionlab/tufts-face-database
下载塔夫茨数据集,并通过对其进行处理来开发人脸识别系统。 -
从
https://research.google/tools/datasets/google-facial-expression/
下载谷歌面部表情对比数据集,开发一个分析面部表情的系统。 -
从
http://vis-www.cs.umass.edu/lfw/
下载野生数据集中的标记人脸,使用 FaceNet 和 DeepFace 创建人脸验证解决方案。 -
从
www.kaggle.com/selfishgene/youtube-faces-with-facial-keypoints
下载带有面部关键点的 YouTube 人脸数据集,并使用它来识别无约束视频中的人脸。如果需要,视频分析的概念可以在下一章学习。
进一步阅读
-
在
https://arxiv.org/pdf/1812.00408v3.pdf
浏览论文《大规模多模态人脸反欺骗数据集及基准》。 -
浏览
https://arxiv.org/pdf/1904.09658.pdf
的论文《概率人脸嵌入》。 -
在
https://arxiv.org/pdf/1710.08092v2.pdf
浏览论文《VGGFace2:跨姿势跨年龄人脸识别数据集》。 -
在
https://arxiv.org/pdf/1807.11649v1.pdf
浏览论文《人脸识别的魔鬼就在噪音中》。
七、使用深度学习的视频分析
一分钟的视频抵得上 180 万字。—詹姆斯·麦克奎维博士
视频是一种非常强大的媒介。每分钟大约有 300 个小时的视频被上传到 YouTube。创建的视频数量每天都在增加。随着智能手机的出现和硬件的改进,视频质量得到了提高。越来越多的视频被跨领域、跨地域地创建和存储。我们有电影、广告、短片和个人视频。大多数视频包含人脸、物体和物体的一些运动。视频可以在白天或晚上,在不同的照明条件下拍摄。我们有捕捉路上行人移动的摄像机、用于监控生产线上商品和产品的制造摄像机、用于机场监控的安全摄像机,以及停车场的车牌检测和读取系统,等等。
本章增强了我们从图像到视频开发的功能。已经用于分类图像和检测对象的算法被扩展到视频。毕竟,简单来说,视频就是一系列图像。我们将使用 Python 开发视频解决方案,就像我们在前几章所做的那样。
在本章中,我们还将我们的知识扩展到 ResNet 和 Inception 网络架构。这两个网络都是高级网络,也是许多前沿深度学习解决方案最受追捧的网络。随着 ResNet 和 Inception networks 的加入,我们将涵盖我们希望在本书中研究的完整网络。
在本章中,我们将学习以下主题:
-
ResNet 架构
-
盗梦架构及其版本
-
视频分析及其使用案例
-
Python 实现使用 ResNet,Inception v3 的视频
-
摘要
祝本章一切顺利!
7.1 技术工具包
所需的技术堆栈没有变化,我们继续使用类似的设置。
我们用的是 Python Jupyter 笔记本。本章的代码和数据集已经在 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter7
签入 git repo。
7.2 视频处理
视频对我们来说并不陌生。我们用手机、笔记本电脑、手持相机等录制视频。YouTube 是最大的视频来源之一。广告、电影、体育、社交媒体上传、抖音视频等等每秒钟都在被创造出来。通过分析它们,我们可以发现许多关于行为、互动、时机和事情顺序的洞见。一个非常强大的媒体!
设计视频分析解决方案有多种方法。我们可以将视频视为帧的集合,然后通过将帧视为单独的图像来执行分析。或者我们也可以给它增加一个额外的声音维度。在这本书里,我们只把精力集中在图像上,声音不包括在内。
我们将在下一部分探讨视频分析的各种使用案例。
7.3 视频分析的使用案例
视频是知识和信息的丰富来源。我们可以跨领域和业务职能利用基于深度学习的能力。其中一些列举如下:
-
实时人脸检测可以使用视频分析来完成,使我们能够检测和识别人脸。它有巨大的好处,可以应用于多个领域。我们在上一章已经详细讨论了这个应用。
-
在灾难管理中,视频分析可以发挥重要作用。考虑一下这个。在类似洪水的情况下,通过分析实际区域的视频,救援队可以确定他们应该关注的区域。这将有助于减少采取行动的时间,从而直接挽救更多的生命。
-
同样,对于人群管理,视频分析扮演着重要的角色。我们可以确定人口的集中和在这种情况下的突出危险。各个团队可以使用摄像机分析视频或实时视频流。并且可以采取适当的行动来防止任何事故。
-
通过分析社交媒体视频,营销团队可以改进内容。营销团队甚至可以分析竞争对手的内容,并根据业务需求相应地调整他们的商业计划。
-
对于对象检测和对象跟踪,视频分析可以快速做出视频中是否存在对象的决定。这可以节省人工。例如,如果我们有一个不同汽车的视频集合,我们希望将它们归类为不同的品牌,手动过程将是打开每个视频,然后做出决定,这既耗时又容易出错。使用基于深度学习的视频分类,整个过程可以自动化。
-
视频分析有助于检验和质量保证。可以拍摄整个过程的视频,而不是人工检查机器中的每个部件。然后利用深度学习,可以进行质量检查。
这些不是唯一的用例。有许多跨领域和跨部门的应用。借助基于深度学习的解决方案,视频分析真正对商业世界产生了影响。
在我们继续进行视频分析之前,我们将首先研究深度网络面临的一个挑战—消失梯度问题。然后我们将考察两个非常强大的深度学习架构——ResNet 和 Inception。在下一节中,我们将从消失梯度问题开始。
7.4 消失梯度和爆炸梯度问题
使用反向传播和基于梯度的学习方法来训练神经网络。在训练过程中,我们希望达到重量的最佳值,使损失最小。现在,每个权重在算法的训练过程中不断更新。该更新与每次训练迭代中误差函数相对于当前权重的偏导数成比例。我们已经在第二章中研究了这个概念。在图 7-1 中,我们展示了在 sigmoid 函数中,我们可能会面临消失梯度的问题,而在 ReLU 或泄漏 ReLU 的情况下,我们不会有消失梯度的问题。
图 7-1
消失梯度是我们在深度神经网络中面临的一个挑战。左图显示,对于 sigmoid 激活函数,我们确实面临着一个大问题,即如何对泄漏 ReLU 进行排序
挑战可能是有时这个更新变得太小,因此权重没有得到更新。这导致很少或者实际上没有网络训练。这被称为消失梯度问题。
现在让我们深入了解一下这个问题。我们再次关注图 7-2 中的基本网络架构。
图 7-2
具有输入层、隐藏层和输出层的基本神经架构
我们知道网络中的每个神经元都有一个激活函数和一个偏置项。它接受有限数量的输入权重乘积,向其添加偏差项,然后对其应用激活函数。然后输出被传递到下一个神经元。
我们还知道,在网络中,预期输出和预测值之间的差异是通过计算得出的,这只是一个误差项。我们希望误差项最小化。当我们在各层和神经元之间实现了使误差最小化的权重和偏差的最佳组合时,误差将被最小化。
计算误差时,在误差函数图上应用梯度下降。这种梯度下降是误差函数相对于其中存在的每个独立变量(权重和偏差)的微分。这是反向传播算法的工作——它通过一个称为学习率的常数项来处理这些权重和偏差。这是从最后一层到第一层反向或从右到左完成的。在每次连续迭代中,计算梯度下降并确定变化方向。因此,权重和偏差被更新,直到网络最小化误差,或者,换句话说,直到误差达到如图 7-3 所示的全局最小值。因此,误差梯度是在网络训练期间计算的方向和幅度。它用于在正确的方向和正确的幅度上更新权重。
Article 25 [Innovation Evaluation Mechanism] Professional backbones with outstanding contributions, especially top-notch young and middle-aged talents, can apply for corresponding professional titles without being restricted by academic qualifications and tenure. The rated personnel who have won the national science and technology award, as well as all kinds of professionals who have made outstanding contributions in promoting the economic construction and social development of eco-cities, can leapfrog to declare their titles.
图 7-3
为了最小化损失,我们希望达到函数的全局最小值。有时,我们可能无法将损失最小化,并且会陷入局部最小值
现在出现了一种情况,如果我们有一个非常深的网络,与网络的最终层相比,初始层对最终输出的影响非常小。或者换句话说,初始层经历很少的训练,并且它们的值经历很少的变化。这是因为反向传播使用从最终层到初始层的链式规则来计算梯度。因此,在 n 层网络中,梯度随着 n 的值呈指数下降,因此初始层将训练得非常慢。或者,在最坏的情况下,他们会停下来训练。
可以有多种迹象来检查消失梯度问题:
-
检测消失梯度最简单的方法是通过核权重分布。如果权重趋于零或者非常非常接近零,我们可能会遇到一个渐变消失的问题。
-
与初始层相比,靠近最终层的模型权重将有更多变化。
-
该模型在训练阶段不会改进或者改进非常缓慢。
-
有时候,训练会提前结束。这意味着任何进一步的训练都不会改进模型。
消失梯度问题有几个建议的解决方案:
-
通常,减少网络中的层数可能有助于解决梯度问题。但与此同时,如果层数减少,网络的复杂性就会降低,这也会影响网络的性能。
-
ReLU 激活函数解决了渐变消失的问题。与 tanh 或 sigmoid 激活函数相比,ReLU 较少受到消失梯度的影响。
-
剩余网络或 ResNets 也是这个问题的解决方案之一。他们没有通过保存梯度流来解决问题;相反,他们使用多个小型网络的组合或集合。因此,尽管是深层网络,但与浅层网络相比,ResNets 能够实现较小的损耗。
一方面,我们有一个消失梯度问题,而另一方面,我们有一个爆炸梯度问题。
在深层网络中,误差梯度有时会随着累积而变得非常大。因此,网络中的更新将非常大,这使得网络不稳定。有一些爆炸梯度的迹象可以帮助我们检测爆炸梯度:
-
该模型在训练阶段损失惨重。
-
在算法的训练过程中,我们可能会因为损失或权重而遇到 NaN。
-
该模型通常是不稳定的,或者换句话说,在随后的迭代中对 loss 的更新是巨大的,这指示了不稳定的状态。
-
对于网络中的每一层和神经元,误差梯度总是大于 1。
分解渐变可以使用
-
我们可以减少网络的层数,或者在训练过程中减少批量。
-
可以添加 L1 和 L2 权重正则化,这将作为网络损失函数的惩罚。
-
渐变裁剪是可以使用的方法之一。我们可以在训练过程中限制梯度的大小。我们为误差梯度设置一个阈值,如果误差梯度超过阈值,误差梯度被设置为该极限或削波。
-
如果我们正在研究循环神经网络,我们可以使用 LSTM(长短期记忆)。这个概念超出了本书的范围。
消失梯度和爆炸梯度都是会影响网络性能的麻烦事。它们会使网络不稳定,需要使用前面提到的几种方法进行校正。现在我们清楚了消失渐变,我们将在下一节详细研究 ResNet 架构。
7.5 ResNet 架构
在前几章中,我们已经学习了很多架构。我们已经将它们用于图像分类、物体检测、人脸识别等等。它们是深度神经网络,为我们带来了良好的结果。但是在非常深的网络中,我们会遇到梯度消失的问题。剩余网络或剩余网络通过使用跳过连接来解决这个问题。ResNets 由、何、、任和发明,论文于 2015 年 12 月发表。更多详情请见 https://arxiv.org/pdf/1512.03385.pdf
。
跳跃连接将激活从网络中的一层进行到更深的层,这允许我们训练甚至更深的网络,其可能超过 100 层。现在,我们将在下一节详细讨论 ResNet 和 skip 连接。
7.5.1 ResNet 和 skip 连接
当我们谈论神经网络和它们所表现出的神奇性能时,它立即被归因于网络的深度。假设网络越深,精度越好。初始层将学习基本功能,更深的层将学习更高级的功能。
但是人们发现,通过增加更多的层数,我们正在增加网络的复杂性。事实上,对于更深的网络(如 56 层深),损耗大于少于(20)层的网络。
Note
通常,使用 16 到 30 之间的卷积和全连接层的模型为 CNN 提供了最好的结果。
这种损失可以归因于我们前面讨论的消失梯度问题。为了解决渐变消失的问题,引入了残差块,如图 7-4 所示。剩余块实现跳过连接或身份映射。
图 7-4
跳过连接是剩余网络的核心。请注意上一层的输出是如何传递到下一层的,从而跳过了中间的一层。它允许训练更深的网络,而没有消失梯度的问题
此标识映射本身没有输入参数;相反,它将前一层的输出添加到下一层。换句话说,它在第二次激活之前充当快捷连接。由于这种捷径,有可能在不降低网络性能的情况下训练甚至更深的网络。这是解决方案的核心,也是它取得巨大成功的原因。
我们现在正在图 7-5 中详细检查 ResNet-34 架构。原架构摘自论文链接: https://arxiv.org/pdf/1512.03385.pdf
。
图 7-5
ResNet-34 完整架构–中间是一个没有跳过连接的普通网络,而右边显示的是一个有剩余连接的网络。该架构摘自位于arxiv . org/pdf/1512 . 03385 . pdf
的原始论文
让我们更深入地了解一下网络。观察架构中的四个剩余模块,如图 7-6 所示。
图 7-6
图中示出了四个剩余块。请注意,对于左侧的每个普通网络,我们都有一个使用跳过连接的相应模块。允许跳过连接来训练更深的网络,而不会对网络的准确性产生不利影响。例如,对于第一个数据块,我们有一个 7x7 conv 层和 3x3 conv 层的平面网络。注意使用跳过连接的相应块
我们可以分析,在每个剩余架构中,skip connection 都是从上一层获取输出,并在两个街区之外共享。这是与左边简单架构的核心区别,它提高了 ResNet 的性能。
跳过连接以一种非常有趣的方式扩展了深层网络的能力。发明人在 CIFAR 数据集上测试了具有 100 层和 1000 层的网络。发明人发现,使用残差网络的集合能够在 ImageNet 上实现 3.57%的错误率,并因此在 ILSVRC2015 竞赛中获得第一名。
ResNet 的其他变体也越来越受欢迎,如 ResNetXt、DenseNet 等等。这些变体探索了可以对原始 ResNet 架构进行的更改。例如,ResNetXt 引入了基数作为模型的超参数之一。我们在本章末尾为感兴趣的读者列出了研究论文。
我们现在将了解另一个创新的架构,称为初始网络。
初始网络
当涉及到复杂任务时,深度学习是非常棒的。我们已经观察到,使用堆叠卷积层,我们能够训练深度网络。但是它也有一些挑战:
-
网络变得过于复杂,需要巨大的计算能力。
-
训练网络时会遇到消失和爆炸梯度问题。
-
很多时候,在观察训练和测试准确性时,网络会过度拟合,因此对看不见的数据集没有用。
-
此外,选择最佳内核大小是一个艰难的决定。选择不好的内核大小会导致不合适的结果。
为了解决面临的挑战,研究人员想,为什么我们不能去宽而不是去深。从技术上讲,让多种尺寸的过滤器在同一水平上运行。因此 Szegedy 等人提出了初始模块。完整论文可在此处查阅: https://arxiv.org/pdf/1409.4842v1.pdf
。
图 7-7 代表了在同一篇论文中出现的两个版本的初始模块。
图 7-7
在左边,我们有初始模块的天真版本。在原始版本中,我们有 1x1、3x3 和 5x5 卷积。为了减少计算量,研究人员添加了一个 1x1 的 conv 层来降维。图片取自arxiv . org/pdf/1409 . 4842 v1 . pdf
在第一个版本中,一个天真的版本,完成了三个不同大小的卷积 1x1,3x3 和 5x5。此外,还建议了 3x3 的最大池。所有相应的输出然后被堆叠并馈送到下一个初始模块。
但随着计算成本的增加,研究人员增加了额外的 1x1 卷积层进行降维。这限制了输入通道的数量,并且 1x1 的计算开销比 3x3 或 5x5 低。一个显著的特征是 1x1 卷积在最大池层之后。
使用第二个版本的维度缩减,创建了一个完整的网络,称为 GoogLeNet 。研究人员选择这个名字是为了向开创 LeNet-5 架构的 Yann LeCuns 致敬。
在我们深入研究 GoogLeNet 架构之前,有必要讨论一下 1x1 卷积的唯一性。
7.5.2.1 1x1 卷积
在深度网络中,特征地图的数量随着网络的深度而增加。因此,如果输入图像有三个通道,并且必须应用 5x5 滤镜,则 5x5 滤镜将应用于 5x5x3 的块中。此外,如果输入是来自深度为 64 的另一个卷积层的特征地图块,则将在 5x5x64 块中应用 5x5 过滤器。这变成了一个计算上具有挑战性的任务。1x1 滤波器有助于解决这一难题。
1x1 卷积也被称为网络中的网络。理解和实现起来非常简单。它具有输入中每个通道的单一特征或权重。类似于任何其他过滤器,输出也是一个单一的数字。它可在网络中的任何地方使用,不需要任何填充,并且生成的要素地图的宽度和高度与输入完全相同。
如果 1x1 卷积中的通道数与输入图像中的通道数相同,则输出也必然包含相同数量的 1x1 滤波器。在这里,1x1 充当非线性函数。如图 7-8 所示。
图 7-8
1x1 卷积层用于缩减信道数量。这里,输入中的通道数和 1x1 块中的通道数相同。因此,输出的通道数量与 1x1 滤波器的数量相同
因此,当我们想要减少通道数量或执行任何特征变换时,1x1 卷积非常有用。这导致计算成本的降低。1x1 用于许多深度学习架构,如 ResNet 和 Inception。我们现在将继续与盗梦空间网络的讨论。
谷歌网络架构
我们在上一节讨论了创建 GoogLeNet 的动机。完整的 GoogLeNet 架构如图 7-9 所示。蓝色方块代表卷积,红色代表池化,黄色代表 softmax,绿色代表其他。
图 7-9
完整的 GoogLeNet 架构。这里,蓝色表示卷积,红色模块是池模块,而黄色是 softmax 模块。稍后我们将放大其中一个部分。图片取自arxiv . org/pdf/1409 . 4842 v1 . pdf
网络有几个重要的属性:
-
初始网络由初始模块的级联块组成。
-
有九个线性堆叠的初始模块。
-
在不同的位置有三个 softmax 分支(图 7-9 中的黄色部分)。在这三个分类器中,两个位于网络的中间部分,作为辅助分类器。它们确保中间特征有利于网络学习并给出正则化效果。
-
两个 softmax 计算辅助损耗。净损失是辅助损失和真实损失的加权损失。辅助损失在训练期间是有用的,并且不被考虑用于最终分类。
-
它有 27 层(22 层+ 5 个池层)。
-
网络中有接近 500 万个参数。
我们现在放大网络中的一个裁剪版本,以便更好地检查网络(图 7-10 )。请注意 softmax 分类器(显示在黄色块中)是如何添加的,以解决渐变消失和过度拟合的问题。最终损耗是辅助损耗和网络真实损耗的加权损耗。
图 7-10
《盗梦空间》网络部分的放大版本。请注意 softmax 分类器是如何添加的(显示为黄色)
Inception v1 被证明是一个很好的解决方案,它在 ILSVRC2014 中获得了第一名,并拥有 6.67%的前 5 名错误率。
但是研究人员并没有就此止步。他们通过提出 Inception v2 和 Inception v3 进一步改进了解决方案,我们接下来将讨论这两个版本。
7 . 5 . 4 Inception v2 的改进
《盗梦空间》第二版和第三版在下面的文章中讨论过: https://arxiv.org/pdf/1512.00567v3.pdf
。动机是提高准确性,降低模型的复杂性,从而降低计算成本。
在 Inception v2 中,有以下改进:
图 7-11
将 5×5 卷积因式分解成两个 3×3 的块,提高了计算速度和解决方案的整体精度。图片取自arxiv . org/pdf/1512 . 00567 v3 . pdf
- 5x5 卷积被分解为两个 3x3 卷积。这样做是为了提高计算速度,从而增强性能。如图 7-11 所示。在左边的图中,我们有原始的 Inception 模块,右边的是修改后的 Inception 模块。
图 7-12
请注意 nxn conv 如何表示为 1xn 和 nx1。比如我们设 n=5,那么 5x5 就变成了 1x5 和 5x1。图片取自arxiv . org/pdf/1512 . 00567 v3 . pdf
- 第二项改进是对卷积进行因式分解,从而将 nxn 大小的滤波器改为 1xn 和 nx1 的组合,如图 7-12 所示。比如 5x5 改为先执行 1x5,再执行 5x1。这进一步提高了计算效率。
图 7-13
模型被做得更宽而不是更深。随着深度的增加,维度急剧减少,这是一种信息损失。图片取自arxiv . org/pdf/1512 . 00567 v3 . pdf
- 随着深度的增加,尺寸减小,因此可能会丢失信息。因此,建议将滤波器组做得更宽,而不是更深,如图 7-13 所示。
研究人员引用了:
虽然我们的网络有 42 层,但我们的计算成本只比 GoogLeNet 高 2.5 倍,而且仍然比 VGGNet 高效得多。
向前看,在 Inception v3 中,除了前面的改进之外,重要的增加是标签平滑的使用,这是一种解决过度拟合的正则化技术。数学证明超出了本书的范围。此外,RMSProp 被用作优化器,并且辅助分类器的全连接层被批量归一化。它在四个模型的集合上实现了 3.58%的 top-5 误差,这几乎是原始 GoogLeNet 模型的一半。
在 Inception v4 和 Inception-ResNet 的形式上有了进一步的改进。它的性能优于以前的版本,3x exception-ResNet(v2)和 1xInceptionv4 的组合导致了 3.08%的 top-5 错误。
至此,我们已经完成了关于初始网络的讨论。
当涉及到真正深度的神经网络时,Inception 和 ResNet 都是使用最广泛的网络之一。使用迁移学习,它们可以用来产生奇妙的结果,并被证明是计算机视觉问题的真正福音。
我们现在将继续研究本章开始时提出的视频分析问题。
7.6 视频分析
视频分析从处理视频开始。正如我们可以通过我们的眼睛看到并使用我们的记忆和大脑处理视频内容一样,计算机也可以通过摄像头看到。而要理解那个视频的内容,深度学习正在提供必要的支持。
视频是丰富的信息来源,但同时也同样复杂。在图像分类中,我们获取输入图像,使用 CNN 对其进行处理以提取特征,然后基于特征对图像进行分类。在视频分类的情况下,我们首先从视频中提取帧,然后对帧进行分类。所以,视频处理不是一项任务;相反,它是子任务的集合。OpenCV 是最流行的视频分析库之一。我们将使用基于深度学习的视频分析解决方案。
使用深度学习进行视频分类的步骤是
-
我们首先从视频中获取帧,并将它们分成训练集和验证集。
-
然后,我们根据训练数据训练网络,并优化精确度。
-
我们将在验证数据集上进行验证,以获得最终模型。
-
对于看不到的新视频,我们会先从视频中抓取帧,然后进行同样的分类。
正如我们所见,这些步骤与任何图像分类解决方案都非常相似。额外的步骤是针对新视频——我们首先抓取一帧,然后对其进行分类。
在下一节中,我们将使用 Inception v3 和 ResNet 创建一个视频分类解决方案。
7.7 使用 ResNet 和 Inception v3 的 Python 解决方案
现在,我们将为视频分析创建一个 Python 解决方案。为此,我们将在体育数据集上训练一个网络,并使用它来预测视频文件。
您可以从 https://github.com/jurjsorinliviu/Sports-Type-Classifier
下载数据集。该数据集包含多种运动类型的图像。我们将为板球、曲棍球和象棋建立一个分类器。数据集和代码在GitHub . com/a press/computer-vision-using-deep-learning/tree/main/chapter 7
上传到 GitHub repo。
板球、曲棍球和象棋图像的一些例子如下所示。
步骤 1:加载所有需要的库。
import matplotlib
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.optimizers import SGD
from sklearn.preprocessing import LabelBinarizer
from tensorflow.keras import optimizers
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from tensorflow.keras.layers import AveragePooling2D
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Input
from tensorflow.keras.models import Model
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import cv2
import os
第二步:为我们感兴趣的运动设置标签。
game_labels = set(["cricket", "hockey", "chess"])
步骤 3:设置其他变量的值,如位置、路径等。我们还将启动两个列表——complete _ data 和 complete _ label——它们将在稍后阶段用于保存值。
location = "/Users/vaibhavverdhan/BackupOfOfficeMac/Book/Restart/Apress/Chapter7/Sports-Type-Classifier-master/data"
data_path = list(paths.list_images(location))
complete_data = []
complete_labels = []
步骤 4:现在加载运动数据集,并读取它们对应的标签。输入大小是 299x299,因为我们首先训练一个 Inception v3。对于 ResNet,大小为 224x224。
for data in data_path:
# extract the class label from the filename
class_label = data.split("/")[-2]
if class_label not in game_labels:
#print("Not used class lable",class_label)
continue
#print("Used class lable",class_label)
image = cv2.imread(data)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (299, 299))
complete_data.append(image)
complete_labels.append(class_label)
步骤 5:将标签转换成 numpy 数组。
complete_data = np.array(complete_data)
complete_labels = np.array(complete_labels)
步骤 6:现在标签的一键编码完成了。
label_binarizer = LabelBinarizer()
complete_labels = label_binarizer.fit_transform(complete_labels)
第七步:将数据分为 80%的训练数据和 20%的测试数据。
(x_train, x_test, y_train, y_test) = train_test_split(complete_data, complete_labels,
test_size=0.20, stratify=complete_labels, random_state=5)
步骤 8:我们现在将为训练数据初始化数据扩充对象。
training_augumentation = ImageDataGenerator(
rotation_range=25,
zoom_range=0.12,
width_shift_range=0.4,
height_shift_range=0.4,
shear_range=0.10,
horizontal_flip=True,
fill_mode="nearest")
步骤 9:我们现在正在初始化测试数据扩充对象。接下来,我们定义每个对象的 ImageNet 平均差值。
validation_augumentation = ImageDataGenerator()
mean = np.array([122.6, 115.5, 105.9], dtype="float32")
training_augumentation.mean = mean
validation_augumentation.mean = mean
步骤 10:现在加载初始网络。这个模型将作为基础模型。
inceptionModel = InceptionV3(weights="imagenet", include_top=False,
input_tensor=Input(shape=(299, 299, 3)))
第 11 步:我们现在将制作模型的头部,它将被放置在基础模型的顶部。
outModel = inceptionModel.output
outModel = AveragePooling2D(pool_size=(5, 5))(outModel)
outModel = Flatten(name="flatten")(outModel)
outModel = Dense(512, activation="relu")(outModel)
outModel = Dropout(0.6)(outModel)
outModel = Dense(len(label_binarizer.classes_), activation="softmax")(outModel)
步骤 12:我们得到最终的模型,并使基础模型层成为不可训练的。
final_model = Model(inputs=inceptionModel.input, outputs=outModel)
for layer in inceptionModel.layers:
layer.trainable = False
步骤 13:我们已经在最后几章中详细学习了剩余的步骤,这些步骤是关于设置超参数和拟合模型的。
num_epochs = 5
learning_rate = 0.1
learning_decay = 1e-6
learning_drop = 20
batch_size = 32
sgd = optimizers.SGD(lr=learning_rate, decay=learning_decay, momentum=0.9, nesterov=True)
final_model.compile(loss='categorical_crossentropy', optimizer=sgd,metrics=['accuracy'])
model_fit = final_model.fit(
x=training_augumentation.flow(x_train, y_train, batch_size=batch_size),
steps_per_epoch=len(x_train) // batch_size,
validation_data=validation_augumentation.flow(x_test, y_test),
validation_steps=len(x_test) // batch_size,
epochs=num_epochs)
步骤 14:我们得到训练/测试的准确度和损失。
import matplotlib.pyplot as plt
f, ax = plt.subplots()
ax.plot([None] + model_fit.history['acc'], 'o-')
ax.plot([None] + model_fit.history['val_acc'], 'x-')
ax.legend(['Train acc', 'Validation acc'], loc = 0)
ax.set_title('Training/Validation acc per Epoch')
ax.set_xlabel('Epoch')
ax.set_ylabel('acc')
import matplotlib.pyplot as plt
f, ax = plt.subplots()
ax.plot([None] + model_fit.history['loss'], 'o-')
ax.plot([None] + model_fit.history['val_loss'], 'x-')
ax.legend(['Train loss', 'Validation loss'], loc = 0)
ax.set_title('Training/Validation loss per Epoch')
ax.set_xlabel('Epoch')
ax.set_ylabel('Loss')
predictions = model_fit.model.predict(testX)
from sklearn.metrics import confusion_matrix
import numpy as np
rounded_labels=np.argmax(testY, axis=1)
rounded_labels[1]
cm = confusion_matrix(rounded_labels, np.argmax(predictions,axis=1))
def plot_confusion_matrix(cm):
cm = [row/sum(row) for row in cm]
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111)
cax = ax.matshow(cm, cmap=plt.cm.Oranges)
fig.colorbar(cax)
plt.title('Confusion Matrix')
plt.xlabel('Predicted Class IDs')
plt.ylabel('True Class IDs')
plt.show()
plot_confusion_matrix(cm)
我们可以分析出网络对于预测来说不够好。
步骤 15:我们现在将实现 ResNet。输入大小更改为 224x224,一切保持不变。我们也在改变体育课。
game_labels = set(["cricket", "swimming", "wrestling"])
步骤 16:完整的代码在 GitHub 链接中。我们在这里提供输出。
该算法产生了 85.81%的良好验证准确度。
该模型被保存,然后我们使用它对一个样本图像进行预测,以检查它是否能够预测。
model_fit.model.save("sport_classification_model.h5")
步骤 17:我们已经在前面的章节中介绍了这些步骤。
file = open("sport_classification", "wb")
file.write(pickle.dumps(label_binarizer))
file.close()
modelToBeUsed = load_model("sport_classification_model.h5")
labels = pickle.loads(open("sport_classification", "rb").read())
import numpy as np
from keras.preprocessing import image
an_image =image.load_img('/Users/vaibhavverdhan/BackupOfOfficeMac/Book/Restart/Apress/Chapter7/Sports-Type-Classifier-master/data/cricket/00000000.jpg',target_size =(224,224))# Load the image
# The image is now getting converted to array of numbers
an_image =image.img_to_array(an_image)
#Let us now expand it's dimensions. It will improve the prediction power
an_image =np.expand_dims(an_image, axis =0)
# call the predict method here
verdict = modelToBeUsed.predict(an_image)
i = np.argmax(verdict)
label = labels.classes_[i]
步骤 18:我们现在将使用这个模型从一个运动的视频中预测类别。我们拍了一段板球录像。GitHub 上也有同样的视频。
步骤 19:捕捉对象中的视频。
video = cv2.VideoCapture(path_video)
步骤 20:我们将迭代视频的所有帧。为此,我们将设置一个指标 isVideoGrabbed 为 1。当视频结束时,isVideoGrabbed 将变为零,然后我们可以从循环中断开。
我们在 while 循环中循环。当一个帧被抓取时,它是一个图像,因此被转换成必要的大小,并馈入模型进行预测。
isVideoGrabbed = 1
while isVideoGrabbed:
(isVideoGrabbed, video_frame) = video.read()
if not isVideoGrabbed:
print("done")
break
video_frame = cv2.cvtColor(video_frame, cv2.COLOR_BGR2RGB)
video_frame = cv2.resize(video_frame, (224, 224)).astype("float32")
video_frame -= mean
prediction_game = modelToBeUsed.predict(np.expand_dims(video_frame, axis=0))[0]
i = np.argmax(verdict)
game = labels.classes_[i]
#print(game)
步骤 21:因此,我们可以逐帧生成整个视频的预测。通过这种方式,我们可以使用神经网络来查看视频,并预测视频中正在进行的运动。
Note
预测有时会有一些模糊之处。我们可以通过采用对帧进行的所有预测的模式来改进最终预测。
这就结束了我们使用 ResNet 和 Inception v3 网络的 Python 解决方案。正如我们所观察到的,使用迁移学习,利用这些非常深的神经网络的能力并不是一个很大的挑战。但是创建一个优化的解决方案仍然是一项艰巨的工作。在前面的例子中,我们可以分析 ResNet 和 Inception v3 网络各自精度之间的差异。这取决于数据集和可用图像的数量。
至此,我们完成了 Python 解决方案的实现。我们现在可以进入总结部分。
7.8 摘要
视频是连续的图像帧,也是娱乐的重要来源。随着技术领域的进步,更小更轻的相机,智能手机中相机的集成,以及社交媒体的渗透,大量的视频正在被创建。深度学习架构提供了很大的灵活性来分析它们并产生见解。但是,与图像相比,视频分析的探索仍然较少。视频是声音和图像的结合。这一领域仍有很大的发展空间。深度学习架构正在推动边界的发展。
深度学习架构越来越深入。而且有一个误区,认为网络越深,性能越好。随着深度的增加,复杂程度也会增加。维度减少,这是信息的损失。网络可能会开始超载。因此,当前需要新颖和创新的解决方案。有时,不同的想法会提供更可靠的解决方案。
本章考察了两个重要的网络——ResNet 和 Inception。这两个网络都非常创新,并增强了功能。我们研究了这些网络的结构和创新性质。这些网络因其卓越的性能而被广泛使用。
在本章中,我们还学习了视频分析和视频处理的概念。我们使用 ResNet 和 Inception v3 网络创建了一个 Python 解决方案。通过使用预先训练的权重来使用迁移学习。
在下一章,也是本书的最后一章,我们将讨论开发深度学习解决方案的整个过程。它还分析了我们面临的问题、解决方案以及遵循的最佳实践。相当重要的一个!
你现在可以进入练习部分。
Review Exercises
Q1。跳过连接的目的是什么?它们有什么用处?
Q2。渐变消失的问题是什么,怎么整改?
Q3。Inception v1 和 Inception v3 网络有什么改进?
Q4。使用 VGG 和 AlexNet 来解决我们在本章中解决的运动分类问题,并比较网络之间的性能。
Q5。从 www.tensorflow.org/datasets/catalog/ucf101
获取视频数据集。数据集有 101 个不同的类;使用它来执行分类。
Q6。从 www.ino.ca/en/technologies/video-analytics-dataset/
获取 INO 传感器数据集。它同时具有彩色和热图像。使用 ResNet 开发分类算法。
进一步阅读
- 浏览以下链接中的论文:
-
【密集连接的卷积网络】:
https://ieeexplore.ieee.org/document/8099726
-
“用于大规模图像识别的极深度卷积网络”:
https://arxiv.org/abs/1409.1556
-
《深层剩余网络中的身份映射》:
https://arxiv.org/abs/1603.05027
-
“辍学:防止神经网络过拟合的简单方法”:
https://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf
-
八、端到端模型开发
有时候,正是旅程教会了你很多关于目的地的事情。—德雷克号
深度学习是一个漫长而乏味的旅程。这需要练习和持续的严谨。通往成功的道路需要精心的计划、奉献、不断的练习和耐心。在这个旅程中,你已经迈出了第一步。
我们从计算机视觉的核心概念开始这本书。我们使用 OpenCV 开发了解决方案。然后我们探讨了卷积神经网络的概念——各种层、功能和各自的输出。
在本书中,我们已经讨论了多种网络架构。在这个过程中,我们讨论了它们各自的组件、技术细节、优缺点以及后续的改进。我们还解决了二进制图像分类、多类图像分类、对象检测、人脸检测和识别以及视频分析的用例。在本书的最后一章,我们将探索端到端的模型开发周期,包括部署、最佳实践、常见陷阱和面临的挑战。我们还将深入研究项目的需求,并更详细地考察迁移学习的概念。我们将对各种架构的性能进行比较和基准测试。最后,我们将为您讨论接下来的步骤。
在本书的最后一章,我们将讨论以下主题:
-
深度学习项目需求
-
端到端模型开发流程
-
图像放大
-
常见错误和最佳实践
-
模型部署和维护
欢迎来到第八章也是最后一章,祝一切顺利!
8.1 技术要求
本章的代码和数据集上传到本书的 GitHub 链接 https://github.com/Apress/computer-vision-using-deep-learning/tree/main/Chapter8
。对于这一章,GPU 足以执行代码,你可以使用谷歌合作实验室。我们将使用 Python Jupyter 笔记本。
让我们从深度学习项目中的需求着手。
8.2 深度学习项目要求
像任何其他项目一样,基于深度学习的项目也意味着拥有一个成功项目的所有组件。我们正在描述一个图像项目的组成部分,如图 8-1 所示。
图 8-1
深度学习计算机视觉项目需要以下组件进行开发和执行。它类似于任何其他项目过程
一个成功的深度学习项目的显著组成部分是
-
构思或想法指的是持续的头脑风暴,这是深度学习项目成功所必需的。构思过程确保在项目过程中遇到问题时,有一系列可靠的想法和解决方案。需要商业知识和深度学习/机器学习专业知识的良好组合。除了提到的两个角色之外,还需要其他角色,这是下一点——团队结构。
-
机器学习项目所需的团队需要混合业务主题专家、具有技术敏锐度的专业人员、数据工程团队、软件工程团队、项目经理和 IT 团队。
-
主题专家确保项目在实现商业目标的成功道路上。
-
数据科学团队负责开发算法。
-
项目经理或 scrum masters 管理整个项目。
-
软件工程团队允许正确部署模型和创建解决方案所需的接口。
-
要求数据工程团队创建传输和保存图像的管道;为了有效的数据管理,需要一个强大的数据工程团队。
-
IT 团队确保为整个界面和解决方案建立适当的基础设施。
-
-
一个强大而具体的流程确保没有任何步骤被忽略,我们正在为项目的成功做准备。深度学习项目的过程将在下一节详细讨论。
-
持续改进是反馈和监控的产物。一旦算法被部署到生产系统中,我们应该不断地监控性能,并通过反馈机制来提高性能。监控还可确保在解决方案低于所需准确度阈值的情况下,对解决方案进行检查和改进。我们将在随后的章节中学习更多关于模型维护的内容。
-
深度学习基础设施是该项目的支柱。如果没有好的机器、处理器、GPU、存储等等,训练一个健壮的基于深度学习的解决方案就变得非常困难,有时甚至是不可能的。在我们具有 4GB 或 8GB RAM 和标准处理器的标准笔记本电脑和台式机中,不可能使用神经网络和对 50,000 幅图像的训练来训练计算机视觉模型。因此,目前需要一个健壮的基础设施。像 Azure 或 Google Cloud 或 Amazon Web Services 这样的云服务提供了可用于此目的的虚拟机,但当然是有成本的。我们在后续章节中建议深度学习项目所需的健壮硬件。
注意感谢谷歌,我们可以免费使用谷歌 Colab。但是 Google Colab 不是永久的解决方案,不应该用于敏感数据集。此外,作为一个免费版本,它并不能确保我们可以使用机器的所有功能。参考资料中提供了 Google Colab 设置。
-
数据集是训练算法、测量性能和监控未来进展所需的原始材料。需要有效的数据管理来确保所有的图像都被仔细地捕获和保存,训练数据被有效地使用和存储用于审计目的,并且在其上进行预测的不可见的和新的数据集被存储用于将来参考。需要具体的数据管理流程来确保项目取得成功。我们将在下一节中再次检查这一步。
深度学习项目是不同团队和技能组合的有趣结合。算法是解决方案的核心,但算法也需要彻底的测试和部署。主题专家(SME)确保算法能够公正地处理手头的业务问题。简而言之,一个完美的团队是解决方案在现实世界中有效工作所必需的。
我们现在要详细研究整个项目过程,这是下一部分。
8.3 深度学习项目流程
像任何其他项目一样,基于深度学习的项目也是为了解决业务问题。像任何其他项目一样,它们需要被部署到生产中。在这一部分,我们将讨论深度学习项目过程中的突出步骤。
深度学习项目的第一步是定义业务问题。整个过程如图 8-2 所示。
图 8-2
深度学习项目的各个阶段。它从定义一个健壮的业务问题开始,然后是处理数据、建模和维护
我们将讨论模型构建过程中的每个步骤。在下一节中,我们将首先探索业务问题的定义。
8.4 业务问题定义
业务问题定义是项目的核心需求。通常,业务问题没有被清楚地定义,也没有正确地确定范围。在整个项目持续期间都有范围蔓延、滞后和延迟。有时,观察到预期的输出和实际的结果不符合,这在团队之间产生了摩擦,并且在许多情况下整个项目不得不报废。
定义业务问题时面临的常见挑战有
-
模糊不清的商业问题令人讨厌。例如,“我们必须增加收入。”这个商业问题没有逻辑意义,可以重新架构。
-
有时,业务目标非常雄心勃勃。例如,“我们必须在接下来的 1 周内将成本降低 80%。”在上述时间框架内实现目标可能不可行。这样的问题必须得到改善。
-
据观察,很多时候缺乏与业务问题相关的适当的 KPI(关键性能指标)。例如,“我们必须增加收入”是一个非常宽泛的说法。没有任何可测量的标准,很难衡量解决方案的性能。
因此,定义一个好的业务问题是至关重要的。我们正在定义一个好的业务问题的几个组成部分:
-
简洁明了:一个好的商业问题集中在目标上。例如,“企业希望组织的考勤系统自动化。为此,需要建立一个自动人脸识别系统。”这个问题仍然有更多的组成部分,但是这里明确定义了业务问题。
-
可度量的:业务问题可以使用 KPI 来度量。如果没有这一衡量标准,将很难衡量系统的实际性能以及解决方案中的误差。为了提高系统的性能,必须有一个 KPI,该 KPI 必须被优化以实现最佳结果。例如,在前面的例子中,我们可以将问题扩展为“企业希望将组织的考勤系统自动化。为此,需要创建一个自动人脸识别系统。该系统的预期准确率为 95%,错误接受率小于 0.01%。”
-
可实现的:一个好的商业问题是可实现的,并且实际上是可能的。它应该足够实用,以便概念化和实现。例如,在前面的人脸检测示例中,期望 100%的准确率和 0%的错误接受率并不是一个好主意。然而,有一些领域,如用于癌症检测的医学成像,需要非常高的准确率,但达到 100%准确率的完美分数可能是无法实现的。
-
可维护性:前瞻性的业务问题计划解决方案的未来状态。对结果的监控、算法的维护、刷新周期和后续步骤是更大范围的业务问题的一部分。
拥有一个定义良好的业务问题会导致一个管理良好的开发过程,并最终产生一个富有成效的解决方案。同时,让业务涉众成为讨论和流程的一部分也是非常必要的。他们当然可以通过传授 SME(主题专家)知识来纠正解决方案。
让我们通过计算机视觉领域的两个例子来检查业务问题的定义。
8.4.1 用于监控的人脸检测
假设我们有一家零售店。可以按照下面的讨论来识别业务问题。
“有必要改进商场中监控较少的区域的安全系统。零售商需要持续监控这些区域,并检测商店中的入店行窃或任何反社会行为。目前的监控流程大部分是手动的。同时还要求对顾客的面部进行检测,并与禁止进店人员数据库进行匹配,以便店家采取相应措施。”
在这样的业务用例中,业务目标是
-
持续监控商场中监控较少的区域
-
发现商场内任何不合群的活动
-
发现并识别商场当局不允许进入商场的人员
为了进一步完成问题陈述,应确定与检测准确性和错误接受/拒绝率相关的 KPI。在前面的业务问题中:
-
真正肯定:正确地将商店中的人员分类为允许进入商店的“可接受”人员。
-
正确否定:正确地将商店中的人归类为不允许进入商店的“不可接受”的人。
-
误报:错误地将商店中的人员分类为“可接受的”,而这些人员不应该被允许进入商店。
-
错误否定:错误地将商店中的人归类为“不可接受”的人,而这些人应该被允许进入商店。
因此,在前面的业务案例中,根据 KPI,我们将优化解决方案。
我们现在将讨论使用深度学习的计算机视觉的第二个商业用例。
制造业的 8.4.1.1 缺陷检测
假设我们有一个手机制造部门。它在不同的生产线上生产手机。在制造过程中,可能会有外来颗粒进入移动室,如细绳、头发、塑料片、碎片等外来颗粒。尺寸可以非常小,比如 200 微米、300 微米等等。可以按照下面的讨论来识别业务问题。
制造厂希望检测移动室内任何外来物质的存在。需要检测各种类型的异物,如绳子、头发、塑料片、碎片,这些异物可能具有不同的尺寸并位于不同的位置。一台摄像机被放置在生产线的顶部,它可以点击每一个进来的产品的图像。实时地,基于深度学习的网络将检测外来颗粒的存在,并接受或拒绝产品。”
在这样的业务用例中,业务目标是
-
持续监控实时生产的产品图像
-
检测产品中是否存在任何异物
为了进一步完成问题陈述,应确定与检测准确性和错误接受/拒绝率相关的 KPI。在前面的业务问题中:
-
真阳性:将产品正确分类为不含任何异物的合格产品
-
正确否:正确地将含有异物的产品归类为不良产品
-
假阳性:错误地接受含有异物的产品
-
假阴性:错误剔除不含异物的产品
因此,在前面的业务案例中,根据 KPI,我们将优化解决方案。
一旦业务问题定义最终确定,我们就进入探索性数据分析、模型构建等下一步(图 8-2 )。我们将在图 8-3 中更详细地描述这些步骤。
图 8-3
深度学习建模过程的详细步骤。在每一步,我们处理图像的各种属性,并创建一个健壮的网络。完成这个项目需要大量的团队合作和专业知识
业务问题定义为我们设定了目标。这意味着我们已经准备好处理数据集,然后开始训练。下一步是我们接下来要讨论的数据发现阶段。
8.4.2 源数据或数据发现阶段
一旦我们完成了冻结业务问题、确定业务问题范围、定义 KPI 和性能测量参数的第一步,我们就可以进入数据发现阶段。
在这个阶段,我们主要搜寻数据,或者在计算机视觉的情况下,我们寻找图像。让我们通过在业务问题定义步骤中讨论的相同示例案例来理解。
用于识别的 8.4.2.1 人脸检测
业务问题是“识别显示在摄像机前的人脸,如果它与员工数据库匹配,则将出勤标记为出席。”
在这样的用例中,原始数据将是雇员面部的图像。这些图像必须从不同的角度拍摄,并且尽可能清晰。如果我们要求员工自己点击图片并与团队分享,那么就存在长宽比、尺寸、背景照明、亮度等方面的危险。此外,不同的员工会使用不同的模式点击图片,因此产生的原始数据可能会有很大不同。因此,在这种情况下,建议在相似的环境中拍摄每张图像,以保持一致性。优选地,可以设置照相机,其中每个雇员都可以来点击他们的照片。
但是这种安排并不是训练网络的要求。该网络可以在不同大小和类型的人脸上进行训练。所建议的方法的优点可以是在训练算法时提高精度和计算优势。网络可以在没有这种安排的情况下被训练。
在这个用例中,我们将捕获人脸的原始图像来训练网络。我们已经在第六章研究了各种人脸检测网络。必须保存这些培训图像,以供将来参考和审计跟踪。我们已经在上一节的数据管理流程中详细讨论了培训数据管理流程。我们将在后续部分中讨论优化的训练数据的属性。
我们现在来看下一个案例,生产线上的实时监控。
生产线上的 8.4.2.2 生活环境
业务问题是“在现场环境中分析移动电话制造厂制造的图像,并评估产品中是否存在异物。”
对于这个业务用例,我们将搜索历史数据来训练网络。训练数据将由没有异物的产品的好例子和其中存在异物颗粒的坏例子组成。图像数据集必须完整,并且可以包括许多历史图像。这是一种可能性,我们没有得到大量的坏图像;因此,这些图像可能需要手动创建。有人建议,我们可以有一个摄像头来点击图像,这将提供原始图像。但是这些原始图像必须被人工分为好的和坏的类别,这样它们才能被输入网络进行训练。
图像必须具有足够的代表性,并应包含所有可能的变化。我们将在下一节中讨论更多关于图像质量的内容,并且还将检查优化的训练数据的属性。
我们现在进入下一阶段,即数据接收或数据管理。
8.5 数据接收或数据管理
需要进行数据管理,以确保我们有一个有效的平台来保存我们的图像,并使用它们来训练算法。它还确保保存所有监控数据集以供将来参考。未来的改进需要算法进行预测的新的和看不见的数据集。
合适的数据管理平台具有以下属性:
-
在数据发现阶段,我们已经确定了我们希望使用的所有数据源。在数据管理阶段,我们首先确定我们在最后阶段列出的所有来源。
-
然后整合所有的图像源,以确保我们在同一个平台上拥有所有的工件。数据源可以是离线的,也可以是在线的,可以被管理,有时也可以手动创建。在一段时间内保存并分析来自多个来源的图像。它还包括获取最新的图像。
-
对于人脸检测问题,数据集包括过去几个月捕获的历史人脸数据集。对于生产线缺陷检测系统,它可以是来自多条生产线和多个月的历史数据集。
-
For face detection, it might include the images captured recently, and for manufacturing detection problem, it will include the live images being captured in the live environment.
图 8-4
数据存储库,其中可以集成来自多个来源的所有图像以供将来使用
-
-
所有数据点随后被加载到一个存储库中(图 8-4 )。该存储库将被算法访问以训练网络。存储库可以是内部托管的独立服务器,也可以是使用 Azure、Google Cloud、AWS 等基于云的解决方案。理想情况下,这个存储库应该是可扩展的,并且可以在需要时扩展以保存更多的图像。
-
数据管理需要强大的数据工程技能和 IT 团队的参与。数据工程团队设置了将图像从实时环境传输到存储库的管道,允许将历史图像保存在同一存储库中,并确保系统受到定期监控且保持完整。
一旦数据管理就绪,我们就有了可用于分析的图像。现在,下一步是分析可用的工件,并创建可用于训练算法的最终训练数据集,这是我们将要讨论的下一步。
8.6 数据准备和扩充
一旦我们准备好数据并存储在一个公共平台上,我们就开始对数据进行初步分析。我们查看大小、纵横比等图像参数。与结构化数据集相比,对于图像,大量的 EDA(探索性数据分析)是不可能的。但是,我们仍然对图像质量进行了健全性检查。
对于我们在服务器上保存和加载的图像,我们分析我们必须分类的不同类型的类,相对于每个类可用的例子的数量。
Note
“垃圾进,垃圾出”这句老话是对的。如果训练数据集有偏差,结果将不会是具体的。
为了有一个稳健的模型,我们需要有一个完整的、具体的、没有偏见的代表性数据集。一个好的训练数据集的一些属性是
-
数据集应该涵盖将要使用该解决方案的每个接口的参与情况。例如,在前面讨论的制造缺陷的情况下,如果十条生产线将要使用它们,我们应该有来自所有十条生产线的数据以确保代表性。否则,有可能该解决方案在大多数线上工作良好,但在那些在训练数据集中没有足够表示的线上工作不良。
-
数据集应该有足够的好的和坏的图像的例子。在理想的情况下,我们会有一个平等的代表,但在现实世界中,我们可能得不到足够的例子。因此,当务之急是我们集中精力收集一个强大的和有代表性的数据集。
注意尽管对于好的和坏的训练数据集没有完美的推荐比例,为了避免偏差,每个类在最终的训练数据集中应该至少有 10%的代表性。
-
如果存在基于时间的依赖性的元素,则训练数据集应该具有足够的遵守时间因素的示例。例如,在制造缺陷问题中,我们可能希望将一周中不同班次和不同日期的数据包含到训练数据中。
-
图像的质量对模型的最终准确性起着至关重要的作用。我们确保包含清晰、不模糊或朦胧的图像。很多时候,我们没有一个大的数据集,我们需要进一步扩充图像,这将在下一节中讨论。
-
这一步的输出应该是我们希望用来训练算法的已配置的训练数据集。
图像增强是一个至关重要的步骤,它允许我们确保我们有足够的训练样本来训练算法,这是我们现在正在讨论的。
8.6.1 图片增加
一旦我们收集了图像,很多时候我们没有足够大的数据集来训练算法。它还允许我们向网络添加泛化能力,并防止它过拟合。对于神经网络,我们需要越来越多的数据,图像增强可以通过创建图像版本来帮助人工增加训练数据集。它增强了模型的能力,因为增强图像与原始训练数据集一起提供了不同的变化。
我们可以转换数据,或者换句话说,使用缩放、移位、翻转、旋转、镜像、裁剪和各种其他方法来处理图像。
但是我们也必须小心,因为我们应该只执行我们在现实世界中可以预期的变化。例如,如果我们希望创建一个人脸检测系统,我们不太可能得到 180 度角的人脸,因此我们可以避免这样的旋转。因此,基于手头的业务问题,我们应该选择增强技术。
Keras 深度学习库提供了一种强大的机制来扩充数据集。有一个 ImageDataGenerator 可用于数据扩充。我们现在正在创建解决方案。
我们将使用图 8-5 中的照片进行放大。
图 8-5
我们将对这个真空吸尘器的图像进行放大处理
步骤 1:首先导入所有的库。
from numpy import expand_dims
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.preprocessing.image import ImageDataGenerator
from matplotlib import pyplot
步骤 2:现在加载图像。
sample_image = load_img('Hoover.jpg')
步骤 3:将图像转换为 numpy 数组,并将维数扩展为一个样本。
imageData = img_to_array(sample_image)
samples = expand_dims(imageData, 0)
步骤 4:现在创建图像数据生成器。这里,我们正在处理宽度移动属性。
dataGenerator = ImageDataGenerator(width_shift_range=[-150,150])
步骤 5:准备迭代器,然后生成图。
dataIterator = dataGenerator.flow(samples, batch_size=1)
for i in range(9):
pyplot.subplot(250 + 1 + i)
batch = dataIterator.next()
image = batch[0].astype('uint8')
pyplot.imshow(image)
pyplot.show()
输出是
我们使用不同的图像生成器,得到不同的结果。在下一个增强版本中,我们将会改变高度。
dataGenerator = ImageDataGenerator(height_shift_range=[-0.4])
接下来,我们在不同的级别缩放图像。
dataGenerator = ImageDataGenerator(zoom_range=[0.15,0.9])
图像现在已旋转。
dataGenerator = ImageDataGenerator(rotation_range=60)
使用不同的亮度值来增强图像。
dataGenerator = ImageDataGenerator(brightness_range=[0.15,0.9])
dataGenerator = ImageDataGenerator(horizontal_flip=True)
在前面的例子中,我们使用了同一个图像的不同版本。我们不需要单独创建这些版本并保存在我们的数据库中。保存在数据库中会导致巨大的存储空间投资。因此,当我们训练网络本身时,这些增强可以在网络本身内完成。
总之,图像增强帮助我们扩展训练数据集并增强模型的性能。我们可以移动、翻转、裁剪和缩放图像,以创建原始训练图像的更多版本。
一旦我们完成了训练数据集的配置,下一步将从我们现在讨论的数据建模开始。
8.7 深度学习建模流程
我们现在可以从建模开始。我们将以生产线上的缺陷检测为例,作为样本用例。我们很清楚训练和测试算法的过程。下文描述了主要步骤,如图 8-6 所示。
图 8-6
训练、测试和部署模型是深度学习解决方案的组成部分。它通常需要一个服务器设置来将模型投入生产
正如我们所观察到的,第一步是在我们创建的数据集上训练算法。第一步中的原始数据按 80:20 或 70:30 的比例分为训练和测试。训练数据用于训练算法,而测试数据作为一个看不见的数据集,用于检查模型在看不见的数据集上的性能。大多数时候,我们迭代并创建算法的多个版本。我们根据测试数据的性能挑选最佳模型。
一些思想流派还建议使用 60:20:20 比例的训练、测试和验证分区。这里,训练数据集用于训练算法,测试数据用于检查在看不见的数据集上的性能。我们将使用测试数据来检查性能并选择最佳算法。但是,只有在我们选择了最佳算法后,才会使用验证数据。
我们现在已经准备好开始训练网络了。一般来说,我们从测试两到三个网络开始。例如,我们可以从 VGG16、Inception v3 和 ResNet 开始训练三种算法。我们可以用迁移学习来解决这个问题。我们已经在第五章讨论了迁移学习,并在前面的章节中使用了它。在这一部分,我们将更多地研究迁移学习。
这个基本版本为我们设置了基本的准确度、召回率、精确度和 AUC 参数。然后我们分析算法造成的错误分类。我们可能需要增加训练数据集,并通过调整超参数来尝试改进基础版本。理想情况下,调优模型后,性能应该会提高。我们将在本节中对此进行更多讨论。
我们在训练和测试数据集上比较了网络的准确度、精确度和召回率。然后我们选择最适合我们的网络。网络的选择取决于手头的业务问题。一些业务问题可能需要非常高的召回率,例如,如果我们希望使用身体的图像分析来检测癌症,我们将需要接近完美的召回率。换句话说,我们希望我们的分析能够检测到所有被感染的患者,即使精确度很低。
因此,在这一步中,我们主要关注以下几点:
-
我们创建模型的第一个和基础版本。
-
我们用不同的版本和性能度量进行迭代。
-
我们调整超参数来提高性能。
-
我们挑选最终的解决方案。
一旦选择了网络的最终版本,我们就在看不见的数据集上测试解决方案。通常,这是在真实世界的图像上完成的,以衡量模型的性能。
我们现在将讨论在进行深度学习项目时应该注意的一些要点。我们从迁移学习开始。
8.7.1 迁移学习
回想一下第五章,我们讨论过迁移学习,如图 8-7 所示。迁移学习是我们使用预先训练好的网络来达到目的的过程。研究人员使用非常高的计算能力和数百万张图像来训练复杂的网络。我们可以利用这些网络来解决我们的业务问题。
图 8-7
迁移学习允许我们使用预先训练好的网络,我们可以根据自己的需要定制它们
在迁移学习中,我们提到,我们希望使用预先训练好的网络,针对我们的业务问题对它们进行定制,并训练网络。我们使用基础网络的权重和架构,并根据我们的目的对其进行修改。
有几点值得注意:
图 8-8
我们可以根据数据集大小和图像与预训练网络图像的相似性来定制我们的策略
-
在使用预先训练好的网络时,必须非常小心。网络的选择很重要,因为它对最终精度起着核心作用。例如,如果我们选择了一个在文本数据上训练的网络,它可能不会对图像给出好的结果。
-
我们可以在尝试使用预训练模型时使用较低的学习速率,而通常我们会保持权重和架构不变。
-
初始图层主要用于从图像中提取当前业务问题的特征。一些层用于训练,而其余的层可以冻结。在大多数情况下,我们输入图像并改变最终的层,同时保留网络的权重和架构。
-
图 8-8 根据我们可用的数据集和图像的相似性,为我们提供了挑选和选择策略的方向。
正如我们所观察到的,我们的建模策略随着数据集的变化而变化。我们迭代版本,调整超参数,并得出算法的最佳版本。
8.7.2 常见错误/挑战和提升绩效
在训练深度学习算法时,我们面临的最常见挑战和犯的最常见错误是:
图 8-9
低学习率会使收敛变慢,而高学习率可能不会给出最好的结果
-
数据质量:如果输入数据集有噪声,训练将不会有成效。我们还需要确保图像质量良好,没有模糊、朦胧、剪切、非常暗或非常白。
-
数据量:我们应该确保我们有足够的来自各个类的表示。一般来说,每个班级至少有 1000 张图片,但是研究人员也用更少的图片训练网络。
-
训练数据组成:训练数据的组成是指各个类的分布。在二进制分类的情况下,它指的是好图像与坏图像的比率。每个类在最终的训练数据集中至少有 10%的代表性,这一点至关重要。
-
训练数据集应该具有足够的代表性和完整性,以解决业务问题。换句话说,在输入数据集和我们希望接收的输出之间应该有关系。
-
日志监控:这是一个调试练习,可以在我们卡住的时候使用。我们使用 print 命令并打印输出,这样我们就可以一步一步地调试代码。
-
图像的放大应谨慎使用。图像增强是一种增加数据大小的流行技术,但它对模型有正则化效果。
-
数据集应该经过洗牌并且不应该按照特定的顺序,否则会导致最终模型出现偏差。
-
如果我们使用预训练模型,并且在需要任何预处理的情况下,它必须为训练数据集计算,然后应用于验证数据集。此外,预处理应该与原始算法所要求的相同。
-
培训期间使用的批量会影响性能。如果批量非常大,模型可能无法很好地概括。
-
权重初始化是另一个起重要作用的参数。我们应该尝试不同的初始化,并监控差异。
-
过拟合是一件麻烦事,其中测试精度低,而训练精度高。这意味着该算法在看不见的数据集上表现不好。我们可以使用放弃、批量归一化、L1/L2 正则化等等来解决过度拟合的问题。
-
模型的不足是我们面临的同样的挑战。为了克服欠拟合,我们可以增加数据量,通过训练更深的网络或更多数量的隐藏神经元来增加复杂性,或者可以尝试更复杂的预训练网络。减少正规化也可能有所帮助。
-
培训/测试流程、准确性和损失的可视化使我们能够可视化整个端到端流程。我们可以监控模型的激活、权重和更新。
-
在训练网络时,我们确实会遇到消失梯度的问题。我们已经在第七章中讨论过了。消失梯度问题是当梯度变得接近零时网络的初始层停止学习的现象。有几种方法可以解决这个问题,如 ReLU、交替权重初始化和梯度下降的变化。
另一方面,爆炸梯度是一个问题,其中误差梯度可以在更新期间累积,并且可以导致非常大的梯度。这会导致网络不稳定,网络无法了解任何信息。有时,我们可能会得到 NaN(不是数字)权重值。我们可以通过重新设计网络来解决爆炸梯度问题。训练时小批量也有帮助。最后,检查渐变的大小有助于解决问题。
- 有时候,我们会在结果中遇到 NaN (不是数字)。有几个原因和解决方法:
1. 如果我们被零除,就可能发生 NaN。
2. 如果引用的是零或负数的对数,则可能发生 NaN。
3. 一般可以通过改变学习率来应对 NaN。
4. 如果没有工作,我们打印日志并逐层分析各自的输出。
- 训练时间有时可能会过长。在训练过程中,每一层的权重发生变化,激活也发生变化。因此整体分布也发生变化。这会导致很长的训练时间。如果数据集太大,网络非常复杂,或者硬件不是很强,我们可能需要很长的训练时间。
To tackle training time, we can try the following options:
1. 降低网络的复杂性,但必须谨慎行事,因为这不会对模型的性能产生太大影响。
2. 有时,数据的标准化有助于减少训练时间。
3. 迭代批量大小以选择最佳训练时间的最佳值也有帮助。
4. 总的来说,更快更好的硬件将有助于减少网络的训练时间。
-
学习率影响模型的收敛性。高学习率会导致低精度,但会更快地收敛。另一方面,非常低的学习率会更慢,但解决方案会更好。我们可以观察到,在图 8-9 中。一般我们以 0.1 的系数来改变学习率。
-
有时,我们会禁用一些图层的渐变更新,或者冻结错误的图层。一个更常见的情况是,我们编写了任何自定义层,并且我们进行了错误的计算。
我们现在给出非常简单的步骤来训练深度学习模型:
-
我们首先创建解决方案的基础版本。例如,VGG16 和使用标准损失函数、学习率等等。
-
我们在培训前验证以下几点:
-
输入数据是正确的。
-
如果必须进行预处理,它应该按照原始模型的要求进行匹配。
-
-
在这个基础版本中,我们使用了非常小的样本量,可能只有 50 张图片。它确保代码语法正确,并且模型正在训练。我们可能得到非常低的精度或者模型可能过度拟合,但是这一步通常已经完成。
-
一旦这个基础版本被训练,我们就添加完整的数据集。
-
一旦基础版本被训练好,我们就添加更多的部分,比如数据扩充、正则化等等。我们调整超参数,逐步提高模型的性能。
-
然后我们继续更复杂的模型,如 ResNet 或 Inception v3。
恭喜你!在训练之后,您的模型现在已经准备就绪,可以进行下一步的部署了。
8.8 模型部署和维护
我们现在有了算法的最终版本,我们希望在生产环境中部署它。这仅仅意味着网络将被用于在真实世界和新鲜的看不见的图像上进行预测。
在选择和设计在生产中部署模型的策略之前,我们应该考虑四个重要因素:
-
该模型将用于实时场景还是批处理模式?如果是实时预测,建议使用基于 API 的部署。
-
如果模型是实时的,我们预期的传入数据负载是多少?
-
对于传入的和不可见的数据集需要多少预处理,我们是否期望传入的格式与训练数据集的格式有很大的不同?
-
模型的刷新周期是多少?如果模型需要经常刷新,建议在离线模式下使用模型,以最小化软件界面。
执行部署的主要技术如下。还可以使用其他解决方案:
-
有人可能会说,为什么我们不能用应用程序的语言来编写深度学习算法。例如,JavaScript 可能不支持我们在书中讨论过的这些高级网络。此外,用这种语言执行同样的任务需要花费大量的时间和精力,就像“重新发明轮子”
-
如果核心应用程序是用 Python 编写的,那么部署就变得更容易了。这是一个理想的情况,因为面临的挑战较少。但是仍然需要加载库和包,确保所有的依赖项都已安装,配置也已完成。
-
我们可以配置一个 Web API 并调用它来为我们做预测。Web API 使得跨语言应用程序之间的相互通信变得更加容易。因此,如果应用程序的前端需要来自深度学习模型的结果,它只需要从提供 API 的地方获得 URL 端点。前端应用程序需要以预定义的格式提供输入,模型可以返回结果。我们正在讨论使用 API 和部署深度学习模型的主要方法:
-
REST API 可以用 Flask 或者 Django 创建。Flask 是模型的一个访问点,它允许我们通过 HTTP 请求来利用模型的能力。
-
Docker 正在成为部署基于深度学习的模型的最受欢迎的选择。包括所有依赖项的模型可以在一个地方进行容器化和打包。它允许服务器根据需要自动扩展。Kubernetes 是部署机器学习模型的最著名的方法之一。
-
-
我们还可以使用 Azure、AWS 等服务来部署我们的模型。这些服务不仅让我们能够训练网络,还能在最终部署中提供支持。
-
TensorFlow 服务也是我们可用的选项之一。它是 Google 首选的高性能部署系统。
模型的部署是最关键的一步。这是我们希望深度学习模型停留的最终目的地。除了前面讨论的解决方案,我们还使用了 Spark/Flink、Apache Beam 等等来部署模型。
一旦模型被部署到生产环境中,我们现在就可以在真实世界的未知数据集上监控模型的性能。我们一直在衡量表现。现在,模型已经进入了维护阶段,在这个阶段,我们必须确保模型能够按照标准执行并交付结果。在图 8-10 中,我们描述了模型维护计划。
图 8-10
需要模型维护来确保模型长期表现良好,并且能够满足交付输出的业务需求
有几点对于模型维护至关重要:
-
一旦解决方案生效并用于对未知数据集进行预测。但是该算法的性能是基于用于训练该算法的训练数据集的。在训练阶段,会有一些类是算法看不到的。换句话说,可能有一些类别在训练数据中未被充分代表。该模型在这些类上可能表现不佳。
-
我们必须定期监控性能。我们可能会从真实世界的图像中抽取一小部分样本,并手动对这些图像进行评分。然后,我们可以与模型给出的实际预测进行比较,并检查模型是否表现良好。如果模型的精度/召回率低于阈值,则意味着模型需要刷新。
-
定期刷新模型也很重要。例如,6 个月一次或一年一次,建议在较新的一组映像上重新训练模型。
-
一旦模型的新版本准备就绪,我们就比较它的性能,以确保新的性能有所提高。只有当性能有所提高时,我们才能在生产中重新部署这种新模式。
-
为了使模型刷新成功,我们必须保存现实世界中看不到的图像,以便进行再训练。一个好策略是每天保存一个小的子集,这样就可以捕获跨阶段和跨时间的变化。一旦我们必须重新训练算法,这些图像可以被分级和使用。
在整个过程中,业务涉众必须是讨论中不可分割的一部分。最终,他们拥有解决方案,并且是其中的主要利益相关者。
这就结束了端到端的深度学习模型开发过程。我们现在可以看本章的概要了。
8.9 总结
深度学习是一个不断发展的领域。计算机视觉也是如此。使用深度学习的计算机视觉是最受欢迎的解决方案之一,它正在改变整个格局。它们是更加复杂、创新、具体和可维护的解决方案。他们能够比其他解决方案更大程度地扩展功能。这些应用遍及所有领域——零售、电信、航空、BFSI、制造、公用事业等等。
解决方案的功效取决于许多因素,如业务问题定义、培训数据、硬件可用性、准确性 KPI 等。也许最重要的部分是用于训练模型的训练数据。获取具体、完整、有代表性的训练数据确实是一项繁琐的任务。一旦实现了这一点,就会覆盖很多领域。
在本书中,我们探讨了计算机视觉问题的各种神经网络架构。我们从计算机视觉的基础开始,理解了一些使用 OpenCV 的例子。然后,我们详细研究了卷积神经网络及其组件的基础知识。从第三章开始,我们从 LeNet 开始了我们的旅程,并进一步讨论了许多架构,如 VGG16、AlexNet、R-CNN、Fast R-CNN、Fast R-CNN、YOLO、SSD、DeepFace 和 FaceNet。所有这些架构都与实际的 Python 实现一起进行了研究。我们开发了一些用例,如二进制图像分类、多类图像分类、实时视频捕捉中的对象检测、人脸检测、手势识别、人脸识别、视频分析、图像增强技术等。CNN 和各种各样的架构有助于他们的发展。
神经网络还有其他分支,如递归神经网络、GAN、自动编码器等等。神经网络不仅对计算机视觉领域,而且对自然语言处理、音频分析等领域都产生了影响。有很多新颖的用途,如语音到文本的转换、机器翻译、摘要生成、语音情感分析、声音识别等等。
但这并不是结束。还有很长的路要走。我们建议您继续沿着这条路走下去,学习新概念,探索想法,发现新技术。同时,你要记住,人工智能是一把双刃剑。必须谨慎使用。它应该用于提高穷人和受压迫者的地位,改善医疗设施,并与世界上的饥饿和不公正现象作斗争。它也可以用于破坏和暴力,但作为负责任的人类,我们有责任避免将人工智能用于错误的目的。
就这样,我们结束了这一章和这本书。祝你在未来的旅途中一切顺利!
你现在应该能回答练习中的问题了!这一章的结尾有一些参考资料。它们快速回顾了激活功能、图像处理方法、Keras 的各种层及其用法,以及不同类型的图像格式及其各自的差异。
Review Exercises
建议您解决以下问题:
-
模型部署过程中的各个步骤是什么?
-
我们如何解决模型中的过度拟合问题?
-
给你的脸拍照,用 Python 旋转 10 度。
-
使用本章中的图像增强技术来改进前面章节中的数据集,并比较各自的精确度。
进一步阅读
-
翻阅研究论文《通过划分减少神经网络的训练时间》:
https://arxiv.org/abs/1511.02954
。 -
探索“使用胶囊层提高神经网络的精度”:
https://ieeexplore.ieee.org/document/8928194
。 -
通读论文《通过使用 t-SNE 识别和移除数据集中的离群图像来提高卷积神经网络的准确性》:
www.mdpi.com/2227-7390/8/5/662
。 -
探索“主动偏向:通过强调高方差样本训练更精确的神经网络”:
https://papers.nips.cc/paper/2017/file/2f37d10131f2a483a8dd005b3d14b0d9-Paper.pdf
。 -
阅读论文《CSPNet:可提升 CNN 学习能力的新骨干》:
https://arxiv.org/pdf/1911.11929v1.pdf
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了