原文:Computer Vision Using Deep Learning
协议:CC BY-NC-SA 4.0
一、计算机视觉和深度学习简介
远见是上帝给人类最好的礼物。
从我们出生开始,视觉就允许我们发展有意识的思维。颜色、形状、物体和面孔都是我们世界的组成部分。这种自然的礼物对我们的感官来说非常重要。
计算机视觉是允许机器复制这种能力的能力之一。利用深度学习,我们正在增强我们的指挥能力,并在这一领域取得进展。
这本书将从深度学习的角度审视计算机视觉的概念。我们将研究神经网络的基本构件,通过基于案例研究的方法开发实用的用例,并比较和对比各种解决方案的性能。我们将讨论最佳实践,分享行业中遵循的技巧和见解,让您了解常见的陷阱,并开发设计神经网络的思维过程。
在整本书中,我们引入了一个概念,详细探讨了它,然后围绕它开发了一个 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
- 1
- 2
- 3
-
加载图像文件。
image = cv2.imread('Color.png')
- 1
- 2
图 1-1
用于颜色检测的原始图像。显示的图像有四种不同的颜色,OpenCV 解决方案将分别检测它们
-
现在让我们将原始图像转换成 HSV(色调饱和度值)格式。它使我们能够从饱和和伪照明中分离出来。允许我们这样做。
hsv_convert = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
- 1
- 2
-
在此定义颜色的上限和下限。我们正在检测蓝色。从
numpy
库中,我们给出了蓝色各自的范围。lower_range = np.array([110,50,50]) upper_range = np.array([130,255,255])
- 1
- 2
-
现在,让我们检测蓝色,并将其从图像的其余部分分离出来。
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
- 3
- 4
该代码的输出将如图 1-2 所示。
图 1-2
颜色检测系统的输出。我们想要检测蓝色,蓝色被检测并与图像的其余部分分离
可以看到,蓝色以白色突出显示,而图像的其余部分为黑色。通过在步骤 5 中更改范围,您可以检测自己选择的不同颜色。
完成颜色后,是时候检测图像中的形状了;我们开始吧!
1.3 使用 OpenCV 的形状检测
就像我们在上一节中检测蓝色一样,我们将检测图像中的三角形、正方形、矩形和圆形。形状检测允许您分离图像中的部分并检查图案。颜色和形状检测使解决方案变得非常具体。可用性存在于安全监控、生产线、汽车中心等等。
对于形状检测,我们得到每个形状的轮廓,检查元素的数量,然后进行相应的分类。例如,如果这个数字是三,它就是一个三角形。在本解决方案中,您还将观察如何对图像进行灰度处理并检测轮廓。
按照以下步骤检测形状:
-
首先导入库。
import numpy as np import cv2
- 1
- 2
- 3
-
加载如图 1-3 所示的原始图像。
shape_image = cv2.imread('shape.png')
- 1
- 2
图 1-3
用于检测圆形、三角形和矩形三种形状的原始输入图像
-
接下来将图像转换为灰度。因为 RGB 是三维的,而灰度是二维的,所以进行灰度缩放是为了简单,并且转换为灰度简化了解决方案。这也使得代码高效。
gray_image = cv2.cvtColor(shape_image, cv2.COLOR_BGR2GRAY) ret,thresh = cv2.threshold(gray_image,127,255,1)
- 1
- 2
-
找到图像中的轮廓。
-
使用
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()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
-
The output of the preceding code is shown in Figure 1-4.
图 1-4
颜色检测系统的输出。圆形以黄色显示,正方形以红色显示,三角形以绿色显示
contours,h = cv2.findContours(thresh,1,2)
- 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
- 1
- 2
- 3
-
加载分类器 xml 文件。的。xml 文件由 OpenCV 设计,由叠加在正面图像上的负面人脸的训练级联生成,因此可以检测人脸特征。
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
- 1
- 2
- 3
-
接下来,加载图像(图 1-5 )。
img = cv2.imread('Vaibhav.jpg')
- 1
- 2
图 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()
- 1
- 2
- 3
- 4
- 5
-
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
- 2
人脸检测允许我们在图像和视频中找到人脸。这是人脸识别的第一步。它广泛用于安全应用、考勤监控等。我们将在后续章节中使用深度学习开发人脸检测和识别。
我们已经学习了图像处理的一些概念。是时候检查和学习深度学习的概念了。这些是你踏上旅程的基石。
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§ + (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/
访问。 -
浏览以下文件:
-
www.usenix.org/system/files/conference/osdi16/osdi16-abadi.pdf
-
https://arxiv.org/pdf/1404.7828.pdf
-
二、计算机视觉深度学习的具体细节
头脑不是一个需要被填满的容器,而是一团需要被点燃的火。—普鲁塔克
我们人类被赋予了非凡的思维能力。这些力量允许我们区分和辨别,发展新技能,学习新艺术,做出理性的决定。我们的视觉能力没有极限。不管姿势和背景如何,我们都能识别人脸。我们可以区分汽车、狗、桌子、电话等物体,而不管它们的品牌和类型。我们可以识别颜色和形状,并清楚而容易地区分它们。这种力量是周期性和系统性发展的。在我们年轻的时候,我们不断地学习物体的属性,发展我们的知识。这些信息被安全地保存在我们的记忆中。随着时间的推移,这种知识和学习提高。这是一个如此惊人的过程,反复训练我们的眼睛和头脑。经常有人认为,深度学习起源于一种模仿这些非凡能力的机制。在计算机视觉中,深度学习正在帮助我们发现可以用来帮助组织将计算机视觉用于生产目的的能力。深度学习已经发展了很多,仍然有很大的进一步发展空间。
在第一章中,我们从深度学习的基础开始。在第二章中,我们将在这些基础上更深入地理解神经网络的各个层次,并使用 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
- 1
- 2
- 将两个常数相乘:
a = tf.constant([1,1,1,1])
b = tf.constant([2,2,2,2])
- 1
- 2
- 3
- 打印最终结果:
product_results = tf.multiply(a, b)
- 1
- 2
print(product_results)
- 1
- 2
如果你能得到结果,恭喜你一切就绪!
现在让我们详细研究一个卷积神经网络。之后,您将准备好创建您的第一个图像分类模型。
很刺激,对吧?
2.3.1 什么是卷积神经网络?
当我们人类看到一幅图像或一张脸时,我们能够立即识别它。这是我们拥有的基本技能之一。这个识别过程是大量小过程的融合,是我们视觉系统各种重要组成部分之间的协调。
卷积神经网络或 CNN 能够使用深度学习来复制这种惊人的能力。
考虑一下这个。我们必须创建一个解决方案来区分猫和狗。使它们不同的属性可以是耳朵、胡须、鼻子等等。细胞神经网络有助于提取对图像有意义的图像属性。或者换句话说,CNN 将提取区别猫和狗的特征。CNN 在图像分类、目标检测、目标跟踪、图像字幕、人脸识别等方面非常强大。
让我们深入了解 CNN 的概念。我们将首先研究卷积。
2.3.2 什么是卷积?
卷积过程的主要目的是提取对图像分类、对象检测等重要的特征。这些特征将是边缘、曲线、色差、线条等等。一旦该过程被很好地训练,它将在图像中的重要点学习这些属性。然后它可以在图像的任何部分检测到它。
假设你有一张 32×32 的图片。这意味着如果它是彩色图像(记住 RGB),它可以表示为 32x32x3 像素。现在让我们在这张图片上移动(或回旋)一个 5×5 的区域,覆盖整个图片。这个过程叫做卷积。从左上角开始,这个区域覆盖了整个图像。你可以参考图 2-2 来看看一个 32×32 的图像是如何被一个 5×5 的滤镜旋绕的。
图 2-2
卷积过程:输入层在左边,输出在右边。32×32 的图像被 5×5 大小的滤镜进行了卷积
在整个图像上通过的 5×5 区域被称为滤波器,它有时被称为内核或特征检测器。图 2-2 中突出显示的区域被称为过滤器的感受野。因此,我们可以说,过滤器只是一个矩阵,其值称为权重。这些权重在模型训练过程中被训练和更新。该过滤器在图像的每个部分上移动。
我们可以通过图 2-3 所示完整过程的例子来理解卷积过程。原图 5×5 大小,滤镜 3×3 大小。过滤器在整个图像上移动,并继续产生输出。
图 2-3
卷积是按元素进行乘积和加法的过程。在第一幅图像中,输出为 3,在第二幅图像中,过滤器向右移动了一个位置,输出为 2
在图 2-3 中,3×3 滤波器对整个图像进行卷积。过滤器检查它想要检测的特征是否存在。该滤波器执行卷积过程,它是两个度量之间的元素级乘积和总和。如果存在某个特征,滤波器和图像部分的卷积输出将产生一个较高的数值。如果该功能不存在,输出将会很低。因此,该输出值表示过滤器对图像中存在特定特征的置信度。
我们在整个图像上移动这个过滤器,产生一个输出矩阵,称为特征图或激活图。该特征图将具有整个图像上的滤波器的卷积。
假设输入图像的维数是(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 的过滤器。因此,输出变成大小为(3×3)的图像。我们应该注意到,滤波器的通道数应该与输入图像的通道数完全相同。在图 2-4 中,为 3。它允许度量之间的逐元素乘法。
图 2-4
滤镜的通道数与输入图像的通道数相同
还有一个我们应该注意的因素。过滤器可以以不同的间隔在输入图像上滑动。它被称为步幅值,并且它建议滤波器在每一步应该移动多少。其过程如图 2-5 所示。在左边的第一幅图中,我们有一个单一的步幅,而在右边,显示了两个步幅的运动。
图 2-5
Stride 建议过滤器在每一步应该移动多少。该图显示了步幅对卷积的影响。在第一张图中,我们的步幅为 1,而在第二张图中,我们的步幅为 2
你会同意,在卷积过程中,我们会很快丢失周边的像素。正如我们在图 2-3 中看到的,一个 5×5 的图像变成了一个 3×3 的图像,这种损失会随着层数的增加而增加。为了解决这个问题,我们有了填充的概念。在填充中,我们给正在处理的图像添加一些像素。例如,我们可以用零填充图像,如图 2-6 所示。填充的使用导致卷积神经网络的更好的分析和更好的结果。
图 2-6
零填充已添加到输入图像。卷积由于这个过程减少了像素的数量,填充使我们能够解决这个问题
现在我们已经理解了 CNN 的主要组成部分。让我们结合这些概念,创建一个小流程。如果我们有一个大小为(nxn)的图像,我们应用大小为“f”的过滤器,步长为“s”,填充为“s”,那么该过程的输出将是
((n+2p–f)/s+1),(n+2p–f)/s+1))(等式 2-1)
我们可以理解如图 2-7 所示的过程。我们有一个大小为 37×37 的输入图像和一个大小为 3×3 的滤波器,滤波器数量为 10,跨距为 1,填充为 0。根据等式 2-1,输出将为 35x35x10。
图 2-7
一种卷积过程,其中我们有一个大小为 3×3 的滤波器,步长为 1,填充为 0,滤波器数量为 10
卷积帮助我们提取图像的重要属性。更靠近原点(输入影像)的网络图层学习低级特征,而最终图层学习高级特征。在网络的初始层中,边缘、曲线等特征被提取出来,而更深的层将从这些低级特征(如面、对象等)中了解最终的形状。
但是这个计算看起来很复杂,不是吗?随着网络的深入,这种复杂性将会增加。那么我们该如何应对呢?池层是答案。我们来了解一下。
2.3.3 什么是池层?
我们研究的 CNN 层产生了输入的特征图。但是随着网络变得更深,这种计算变得复杂。这是由于随着每一层和每一个神经元,网络中的维数增加。因此网络的整体复杂性增加。
还有一个挑战:任何图像增强都会改变特征图。例如,旋转将改变图像中特征的位置,因此相应的特征图也将改变。
Note
通常,您会面临原始数据不可用的情况。图像增强是为您创建新图像的推荐方法之一,这些新图像可用作训练数据。
特征图中的这种变化可以通过下采样来解决。在缩减像素采样中,输入图像的分辨率会降低,而合并图层可以帮助我们做到这一点。
在卷积层之后添加一个汇集层。每个特征地图被单独操作,我们得到一组新的汇集的特征地图。此操作过滤器的大小小于要素地图的大小。
通常在卷积层之后应用汇集层。具有 3×3 像素和跨距的池化图层将要素地图的大小缩小了 2 倍,这意味着每个维度减半。例如,如果我们将池图层应用于 8×8 (64 像素)的要素地图,输出将是 4×4 (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
- 1
- 2
- 3
- 4
-
初始化一个模型,这里的
catDogImageclassifier
变量:catDogImageclassifier = Sequential()
- 1
- 2
-
现在,我们将添加层到我们的网络。
Conv2D
将增加一个二维卷积层,它将有 32 个过滤器。3,3 代表过滤器的大小(3 行,3 列)。以下输入图像形状为 64 * 64 * 3–高宽RGB。每个数字代表像素强度(0–255)。catDogImageclassifier.add(Conv2D(32,(3,3),input_shape=(64,64,3)))
- 1
- 2
-
最后一个图层的输出将是一个要素地图。训练数据将对其进行处理,并获得一些特征图。
-
让我们添加激活功能。我们在这个例子中使用
ReLU
(整流线性单位)。在前一层输出的特征图中,激活函数将所有负像素替换为零。注从 ReLU 的定义中回忆;它是 max(0,x)。ReLU 允许正值,而将负值替换为 0。一般来说,ReLU 只用于隐藏层。
-
现在我们添加最大池层,因为我们不希望我们的网络在计算上过于复杂。
catDogImageclassifier.add(MaxPooling2D(pool_size =(2,2)))
- 1
- 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)))
- 1
- 2
-
现在,让我们展平数据集,该数据集会将汇集的要素地图矩阵转换为一列。
catDogImageclassifier.add(Flatten())
- 1
- 2
-
Add the dense function now followed by the ReLU activation:
catDogImageclassifier.add(Dense(64)) catDogImageclassifier.add(Activation('relu'))
- 1
- 2
你认为我们为什么需要非线性函数,比如 tanh,ReLU 等等?如果只使用线性函数,输出也将是线性的。因此,我们在隐藏层中使用非线性函数。
-
过度合身是一件讨厌的事。接下来,我们将添加 Dropout 层来克服过度拟合:
catDogImageclassifier.add(Dropout(0.5))
- 1
- 2
-
再添加一个完全连接的层,以获得 n 维类的输出(输出将是一个矢量)。
catDogImageclassifier.add(Dense(1))
- 1
- 2
-
添加 Sigmoid 函数以转换为概率:
catDogImageclassifier.add(Activation('sigmoid'))
- 1
- 2
-
让我们打印一份网络摘要。
catDogImageclassifier.summary()
- 1
- 2
catDogImageclassifier.add(Activation('relu'))
- 1
- 2
我们在下图中看到了整个网络:
我们可以看到网络中的参数数量为 36,961。建议您尝试不同的网络结构,并评估其影响。
-
让我们现在编译网络。我们使用使用梯度下降的优化器
rmsprop
,然后我们添加损失或成本函数。catDogImageclassifier.compile(optimizer ='rmsprop', loss ='binary_crossentropy', metrics =['accuracy'])
- 1
- 2
- 3
-
现在我们在这里做数据增强(缩放,比例等。).这也有助于解决过度拟合的问题。我们使用
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)
- 1
- 2
- 3
- 4
-
加载训练数据:
training_set = train_datagen.flow_from_directory('/Users/DogsCats/train',target_size=(64,6 4),batch_size= 32,class_mode='binary')
- 1
- 2
-
加载测试数据:
test_set = test_datagen.flow_from_directory('/Users/DogsCats/test', target_size = (64,64), batch_size = 32, class_mode ='binary')
- 1
- 2
- 3
- 4
-
让我们现在开始训练。
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)
- 1
- 2
- 3
- 4
每个时期的步数是 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')
- 1
- 2
- 3
加载保存的模型
-
检查模型如何预测一个看不见的图像。
catDogImageclassifier.save('catdog_cnn_model.h5')
- 1
- 2
这是我们用来做预测的图像(图 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))
- 1
- 2
- 3
- 4
- 5
图像现在被转换成一组数字:
an_image =image.img_to_array(an_image)
- 1
- 2
现在让我们扩大图像的维度。它将提高模型的预测能力。它用于扩展数组的形状,并插入一个新的轴,该轴出现在扩展后的数组形状的轴的位置上。
an_image =np.expand_dims(an_image, axis =0)
- 1
- 2
现在是时候调用 predict 方法了。我们将概率阈值设置为 0.5。建议您测试多个阈值,并检查相应的准确性。
verdict = catDogImageclassifier.predict(an_image) if verdict[0][0] >= 0.5:
prediction = 'dog'
else:
prediction = 'cat'
- 1
- 2
- 3
- 4
- 5
-
让我们打印我们的最终预测。
print(prediction)
- 1
- 2
该模型预测图像是一只“狗”
在这个例子中,我们使用 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 架构很容易理解。让我们看看它的层的尺寸。
-
第一层:28×28 输入图像
-
第二层:四个 24×24 卷积层(5×5 大小)
-
第三层:平均池层(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 略有改进。多了一个完全连接的图层和更多的要素地图。
-
第一层:32×32 输入图像
-
第二层:四个 24×24 卷积层(5×5 大小)
-
第三层:平均池层(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 的第一层是 32×32 的输入图像层。它是一个灰度图像,通过一个卷积块,该卷积块有六个大小为 5×5 的滤波器。结果尺寸为 28x28x1,32x32x1。这里,1 代表通道;它是 1,因为它是灰度图像。如果是 RGB,应该是三个通道。
-
第二层:汇集层也称为子采样层,其过滤器大小为 2×2,跨距为 2。图像尺寸缩小到 14x14x6。
-
第三层:这也是一个卷积层,具有 16 个特征图,大小为 5×5,步幅为 1。注意,在该层中,16 个特征地图中只有 10 个连接到前一层的 6 个特征地图。它给了我们几个明显的优势:
-
计算成本较低。这是因为连接数量从 240,000 减少到 151,600。
-
该层的训练参数总数是 1516,而不是 2400。
-
它还打破了体系结构的对称性,因此网络中的学习更好。
-
-
第四层:它是一个池层,过滤器大小为 2×2,跨距为 2,输出为 5x5x16。
-
第五层:它是一个全连接的卷积层,有 120 个特征图,每个图的大小为 1×1。120 个单元中的每一个都连接到前一层中的 400 个节点。
-
第六层:是全连通层,有 84 个单元。
-
最后,输出层是一个 softmax 层,每个数字对应十个可能的值。
LeNet-5 架构的总结如表 3-1 所示。
表 3-1
整个 LeNet 架构的总结
| – ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fgitee.com%2FOpenDocCN%2Fvkdoc-cv-zh%2Fraw%2Fmaster%2Fdocs%2Fcv-dl%2Fimg%2F496201_1_En_3_Figa_HTML.jpg&pos_id=img-nnUh0K0u-1724460700172) |
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
接下来,我们导入数据集,然后从 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
-
定义超参数。这一步类似于上一章中我们开发 MNIST 和狗/猫分类的那一步。
image_rows, image_cols = 28, 28 batch_size = 256 num_classes = 10 epochs = 10
- 1
- 2
- 3
- 4
-
现在加载数据集。MNIST 是默认情况下添加到库中的数据集。
(x_train, y_train), (x_test, y_test) = mnist.load_data()
- 1
- 2
-
将图像数据转换为浮点型,然后将其规范化。
x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255
- 1
- 2
- 3
-
让我们打印训练和测试数据集的形状。
print('x_train shape:', x_train.shape) print(x_train.shape[0], 'train samples') print(x_test.shape[0], 'test samples')
- 1
- 2
- 3
-
在下一个代码块中,我们将变量转换为一键编码。我们使用 Keras 的 to _ categorical 方法。
y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes)
- 1
- 2
- 3
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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
是时候创建我们的模型了!
-
首先添加一个顺序层,然后是卷积层和最大池层。
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)))
- 1
- 2
- 3
- 4
-
我们现在将添加多层卷积层、最大池层和数据扁平化层。
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"))
- 1
- 2
- 3
-
我们添加一个密集层,然后是 softmax 层。Softmax 用于分类模型。之后,我们编译我们的模型。
model.add(Dense(num_classes)) model.add(Activation("softmax")) model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy'])
- 1
- 2
- 3
该模型已准备好接受训练和调整。
-
我们可以看到每个历元对精确度和损失的影响。我们鼓励您尝试不同变化的超参数,如 epoch 和 batch size。取决于超参数,网络将需要时间来处理。
theLeNetModel = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test))
- 1
- 2
- 3
- 4
这里,我们可以分析损耗和精度如何随每个历元而变化。
十个历元后,验证准确率为 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')
- 1
- 2
- 3
- 4
- 5
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')
- 1
- 2
- 3
- 4
- 5
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
导入 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
-
然后导入 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'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
-
数据集以 pickle 文件的形式提供,并保存为 train.p 和 test.p 文件。数据集可以在
www.kaggle.com/ meowmeowmeowmeowmeow/gtsrb-german-traffic-sign
从 Kaggle 下载。training_file = "train.p" testing_file = "test.p"
- 1
- 2
- 3
-
打开文件并保存训练和测试变量中的数据。
with open(training_file, mode="rb") as f: train = pickle.load(f) with open(testing_file, mode="rb") as f: test = pickle.load(f)
- 1
- 2
- 3
-
将数据集分为测试和训练。我们在这里采用了 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']
- 1
- 2
- 3
- 4
- 5
-
让我们来看看这里的一些样本图像文件。我们使用了随机函数来选择随机图像;因此,如果您的输出与我们的不同,请不要担心。
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])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这里是如图 3-7 所示的输出。
图 3-7
此处显示了德国交通标志分类数据集的一些示例
-
接下来,让我们选择我们的超参数。不同类别的数量为 43。我们已经开始使用十个纪元,但是我们鼓励您使用不同的纪元值来检查性能。批量大小也是如此。
image_rows, image_cols = 32, 32 batch_size = 256 num_classes = 43 epochs = 10
- 1
- 2
- 3
- 4
- 5
-
现在,我们将执行一些探索性的数据分析。这样做是为了查看我们的图像数据集看起来如何,以及直方图中各种类的频率分布是什么。
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()
- 1
- 2
- 3
- 4
输出如图 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
现在,绘制直方图;对于训练数据集和测试数据集,颜色分别设置为红色和绿色。
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()
- 1
- 2
- 3
- 4
这里是如图 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')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
因此,我们有 35209 个训练数据点和 12630 个测试数据点。下一步,将类别向量转换为二进制类别矩阵。这与我们开发 MNIST 分类的上一个示例中的步骤类似。
y_train = keras.utils.to_categorical(y_train, num_classes) y_test = keras.utils.to_categorical(y_test, num_classes)
- 1
- 2
下面的代码块与前面开发的 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)
- 1
- 2
- 3
- 4
- 5
让我们现在开始创建神经网络架构。这些步骤类似于前面的用例。
-
添加一个顺序层,后跟一个卷积层。
model = Sequential() model.add(Conv2D(16,(3,3),input_shape=(32,32,3)))
- 1
- 2
-
添加池层,然后添加卷积层,依此类推。
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"))
- 1
- 2
- 3
- 4
- 5
-
让我们打印模型摘要。
model.summary()
- 1
- 2
这是输出。
-
模型已准备好进行编译;让我们训练它。
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))
- 1
- 2
- 3
- 4
- 5
- 6
这是如图 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')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
这是结果图,如图 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')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
由此产生的图可以在图 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))
- 1
- 2
- 3
-
现在让我们创建一个名为 cm 的变量,它只不过是混淆矩阵。
predictions = theLeNetModel.model.predict(x_test)
- 1
- 2
请随意打印并分析结果。
-
现在让我们开始混淆矩阵的可视化。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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
cm = confusion_matrix(y_test, np.argmax(predictions,axis=1))
- 1
- 2
输出如下所示。由于维数的原因,混淆矩阵在图 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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
这里是显示混淆矩阵的图(图 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 浏览老年痴呆症分类的论文。