深度学习和 CNN 计算机视觉应用实践指南(一)
原文:Practical Computer Vision Applications Using Deep Learning with CNNs
一、计算机视觉中的识别
大多数计算机科学研究试图建造一个像人类一样的机器人,能够完全像人类一样工作。甚至情感属性对于这样的机器人来说也不是不可能的。使用传感器,机器人可以感觉到周围环境的温度。利用面部表情,有可能知道一个人是悲伤还是快乐。即使是看似不可能的事情,最终也只会变得具有挑战性。
目前,一个非常具有挑战性的应用是对象识别。识别可以基于不同类型的数据,例如音频、图像和文本。但是图像识别是一种非常有效的方法,因为它有丰富的信息可以帮助我们完成任务。因此,它被认为是计算机视觉中最受欢迎的应用。
世界上存在大量的物体,区分它们是一项复杂的任务。除了细微的细节之外,不同的物体可能具有相似的视觉外观。此外,同一物体基于其周围环境而呈现出不同的外观。例如,根据光线、视角、扭曲和遮挡,同一对象在图像中会以不同的方式出现。根据原始图像,像素可能不是图像识别的好选择。这是因为每个像素的微小变化都会导致图像的重大变化,因此系统无法正确识别对象。目标是找到一组独特的属性或特征,只要对象的结构出现在图像中的某个地方,这些属性或特征即使随着像素位置或值的改变也不会改变。从图像中手动提取特征是图像识别中的一大挑战。这就是为什么自动特征提取方法正在成为一种选择。
因为在当前任何环境中识别任何对象都是复杂的,所以另一种方法是限制环境或目标对象。例如,我们可以不识别所有类型的动物,而只锁定其中的一组。与其在室内和室外工作,我们可以将环境限制在室内图像。我们可能只处理一些视图,而不是识别不同视图中的对象。一般来说,创建一个狭义的人工智能应用,虽然具有挑战性,但比一般的人工智能应用更容易,困难也更少。
本章讨论如何构建一个识别应用来对水果图像进行分类。它首先介绍一些对不同类型的应用普遍有用的特性,然后找出这些特性中最好的一个用于我们的目标应用。到本章结束时,我们将发现为什么手动提取特征是具有挑战性的,以及为什么使用卷积神经网络(CNN)的自动特征挖掘是首选的。
图像识别流水线
与大多数传统的识别应用类似,图像识别可能会遵循一些预定义的步骤,从接受输入到返回所需的结果。这些步骤的总结如图 1-1 所示。
图 1-1
通用识别流水线
有时输入图像的当前形式不适合处理。例如,如果我们要构建一个面部识别应用,该应用在复杂的环境中捕获图像以识别其中的人,那么在开始识别目标对象之前,最好先去除背景。在这种情况下,背景去除是一种预处理。通常,实际工作之前的任何步骤都称为预处理。预处理是使成功识别的概率最大化的步骤。
准备好输入后,我们开始实际工作,从特征提取或挖掘开始。这是大多数识别应用中的关键步骤。目标是找到一组能够准确描述每个输入的代表性特征。这样的一组特征应该最大化将每个输入映射到其正确输出的概率,并且最小化将每个输入分配给错误标签的概率。因此,应该对要使用的功能类型进行分析。
应用和所使用的功能集是相关的。基于应用选择特征。通过了解应用的性质,所需的功能类型将很容易被发现。对于人脸检测这样的应用,需要提取哪些特征?人脸具有各种颜色的皮肤,因此我们可以确定皮肤颜色是要使用的特征。知道该应用是在灰度户外图像、低照明和移动环境中检测人脸有助于选择适当的特征。如果要求您构建一个识别橙子和香蕉的应用,您可以从橙子和香蕉具有不同颜色的事实中受益,从而决定仅使用颜色特征就足够了。例如,它们不足以识别不同类型的皮肤癌。必须做更多的工作来找到最合适的特性集。下一节标题为特征提取讨论了一些在图像识别应用中有帮助的特征。
在创建了包含可能在识别应用中有用的特征的特征向量之后,我们进入添加进一步增强的其他步骤,即特征选择和减少。特征选择和减少的主要目标可以被定义为从一组特征中获得最佳特征子集,其通过减少不相关、相关和噪声特征的数量来增强学习算法的性能或准确性。标题为特征选择&减少的章节讨论了通过去除这些特征来减少特征向量长度的方法。
特征抽出
将图像以其原始形式作为输入应用于训练模型是不常见的。为什么提取特征是一种更好的方法,有不同的原因。一个原因是图像,即使是小图像,也有大量的像素,每个像素都作为模型的输入。对于大小为 100×100 像素的灰度图像,有 100×100 = 10,000 个输入变量要应用于模型。对于包含 100 个样本的小型数据集,整个数据集总共有 100×10,000 = 1,000,000 个输入。如果图像是红绿蓝(RGB),总数乘以 3。这除了计算量大之外,还需要大的存储器。
在训练之前优选特征提取的另一个原因是,输入图像具有不同类型的具有不同属性的对象,并且我们只想针对单个对象。例如,图 1-2(a) 显示了“狗与猫 Kaggle”比赛中一只狗的图像。我们的目标是发现狗,我们不关心树林或草地。如果将完整的图像用作模型的输入,木材和草地将会影响结果。最好只使用专属于狗的功能。从图 1-2(b) 可以明显看出,狗的颜色不同于图像中的其他颜色。
图 1-2
使用特征时,锁定图像中的特定对象更容易
通常,问题的成功建模依赖于最佳特征的选择。数据科学家应该为正在解决的问题选择最具代表性的功能集。有不同类型的特征用于描述图像。这些特征可以以不同的方式分类。一种方法是检查它们是从图像中的特定区域全局还是局部提取的。局部特征是诸如边缘和关键点的那些特征。全局特征是诸如颜色直方图和像素计数之类的特征。全局意味着该特征描述了整个图像。说颜色直方图在左侧区域居中意味着整个图像是暗的。该描述不仅仅针对图像的特定区域。局部特征集中在图像中的特定部分,例如边缘。
后续小节将讨论以下特性:
-
颜色直方图
-
边缘
- 猪
-
纹理
-
[军]GroundLaunchedCruiseMissile
-
梯度共生矩阵
-
垂直线间的距离
-
颜色直方图
颜色直方图表示颜色在图像中的分布。它通常用于灰色图像,但也有修改用于彩色图像。为简单起见,让我们计算图 1-3 中 5×5 2 位图像的颜色直方图。该图像只有 4 个灰度级。图像是使用 NumPy 随机生成的。
图 1-3
尺寸为 5×5 的两位灰度图像
通过计算每个灰度级的频率,直方图如图 1-4 所示。基于直方图,很明显高频箱位于右侧,因此图像是明亮的,因为其大多数像素是高的。
图 1-4
2 位 5×5 灰度图像的直方图
清单 1-1 给出了 Python 代码,除了计算和显示直方图之外,还用来随机生成前面的小图像。
import matplotlib.pyplot
import numpy
rand_img = numpy.random.uniform(low=0, high=3, size=(5,5))
rand_img = numpy.uint8(rand_img)
hist = numpy.histogram(rand_img, bins=4)
matplotlib.pyplot.bar(left=[0,1,2,3], height=hist[0], align="center", width=0.3)
matplotlib.pyplot.xticks([0,1,2,3], fontsize=20)
matplotlib.pyplot.yticks(numpy.arange(0, 15, 2), fontsize=20)
Listing 1-1Histogram for a Tiny Randomly Generated Image
numpy.random.uniform()除了接受图像像素赋值范围的上限和下限之外,还接受要返回的数组大小。下限为 0,上限为 3,因为我们希望创建一个 2 位映像。numpy.uint8()用于将浮点值转换为整数。然后,使用numpy.histogram()计算直方图,它接受图像和箱数,并返回每个级别的频率。最后,matplotlib.pyplot.bar()用于返回一个条形图,在 x 轴上显示每个级别,在 y 轴上显示其频率。matplotlib.pyplot.xticks()和matplotlib.pyplot.yticks()用于改变 x 轴和 y 轴的范围以及显示字体大小。
真实世界图像的直方图
让我们在将现实世界的图像转换成黑白图像后,计算如图 1-2(a) 所示的直方图。灰度图像和直方图如图 1-5 所示。似乎直方图大多集中在左侧部分,这意味着图像普遍较暗。因为狗的身体是白色的,所以部分直方图位于直方图分布的最右边。
图 1-5
灰度图像直方图
清单 1-2 给出了读取彩色图像、将其转换为灰度、计算其直方图并最终将直方图绘制成条形图的 Python 代码。
import matplotlib.pyplot
import numpy
import skimage.io
im = skimage.io.imread("69.jpg", as_grey=True)
im = numpy.uint8(im*255)
hist = numpy.histogram(im, bins=256)
matplotlib.pyplot.bar(left=numpy.arange(256), height= hist[0], align="center", width=0.1)
Listing 1-2Histogram for a Real-World Image
使用skimage.io.imread()功能,使用as_grey属性读取图像并将其转换为灰度。当设置为True时,图像以灰度返回。返回的图像数据类型为float64。为了将它转换成范围从 0 到 255 的无符号整数,使用了numpy.uint8()。图像在转换前首先乘以 255,因为numpy.uint8()不会改变输入的比例。它只是确保数字是由 8 位表示的整数。例如,将等于 0.7 的数字应用于该函数,结果为 0。我们希望将 0.4 从 0–1 范围重新调整到 0–255 范围,然后将其转换为 uint8。如果不将输入乘以 255,所有的值将只是 0 或 1。注意,直方图仓的数量被设置为 256,而不是上例中的 4,因为图像被表示为 8 位。
HSV 颜色空间
颜色直方图是指将图像像素表示在其中一个颜色空间中,然后统计这些颜色空间中存在的级别的频率。以前,图像是在 RGB 颜色空间中表示的,每个通道的颜色范围从 0 到 255。但是这不是唯一存在的颜色空间。
我们将涉及的另一个颜色空间是 HSV(色调-饱和度-值)。这个颜色空间的优点是颜色和照明信息的分离。色调通道保存颜色信息,其他通道(饱和度和值)指定颜色的亮度。以颜色而不是照明为目标并创建照明不变的特征是有用的。我们不会在本书中涉及 HSV 色彩空间,但是阅读更多关于如何使用 HSV 生成颜色的内容是很好的。值得一提的是,色调通道表示一个圆,其值在 0 到 360 之间,其中 0 度表示红色,120 度表示绿色,240 度表示蓝色,360 度返回红色。所以,它以红色开始和结束。
对于图 1-2(a) 中的图像,色调通道及其直方图如图 1-6 所示。当色调通道被表示为如图 1-6(a) 所示的灰度图像时,红色将被赋予高值(白色),如狗项圈所示。因为蓝色被赋予 240 的高色调值,所以它在灰度图像中更亮。色调值为 140 的绿色比 360 更接近 0;因此,它的颜色很深。请注意,狗的身体在 RGB 颜色空间中是白色的,在 HSV 中看起来是黑色的。原因是 HSV 不负责强度,而只负责颜色。在价值通道中会是白色的。
图 1-6
用 HSV 表示的彩色图像的色调通道及其直方图
根据清单 1-3 ,RGB 图像被转换到 HSV 颜色空间并显示其色调通道直方图。
import matplotlib.pyplot
import numpy
import skimage.io
import skimage.color
im = skimage.io.imread("69.jpg", as_grey=False)
im_HSV = skimage.color.rgb2hsv(im)
Hue = im_HSV[:, :, 0]
hist = numpy.histogram(Hue, bins=360)
matplotlib.pyplot.bar(left=numpy.arange(360), height=hist[0], align="center", width=0.1)
Listing 1-3Displaying the Image Histogram Using Matplotlib
因为色调通道是 HSV 颜色空间中的第一个通道,所以它被赋予索引 0 以获得返回。
对于不同的图像,特征应该是唯一的。如果不同的图像具有相同的特征,结果将是不准确的。颜色直方图有这样的缺点,因为它对于不同的图像可能是相同的。原因是颜色直方图只计算颜色的频率,不管它们在图像中的排列如何。图 1-7(a) 调换了图 1-3 中的图像。从图 1-7(b) 可以看出,尽管像素位置不同,但是图像在转置前后的直方图是相同的。
图 1-7
改变像素位置不会改变颜色直方图
人们可能认为这不是问题,因为一个好的特征描述符应该保持持久,即使图像发生了变化,如旋转和缩放。颜色直方图不能满足这个属性,因为即使图像完全不同,它也返回相同的直方图。为了解决这个问题,应该考虑像素强度和位置,以返回更有代表性的特征。这种特征的例子是纹理特征,例如 GLCM。
[军]GroundLaunchedCruiseMissile
一种流行的统计纹理分析方法依赖于从像素对之间的空间关系中提取的二阶统计量。这些特征中最流行的是从共现矩阵(CM)中提取的特征。其中一个 CMs 是灰度共生矩阵(GLCM)。根据其名称,它接受灰度图像作为输入,并返回 GLCM 矩阵作为输出。
GLCM 可以描述为二维直方图,它根据每对灰度级之间的距离来计算它们之间的共现次数。GLCM 与一阶直方图的不同之处在于,GLCM 不仅取决于强度,还取决于像素的空间关系。对于每两个像素,一个称为参考,另一个称为邻居。当两个强度级别之间的距离为 D 且角度为θ时,GLCM 会找出两个强度级别同时出现的次数。 GLCM (1,3),D = 1,θ = 0 是指强度值为 1 的参考像素与其强度为 3 的邻居相隔距离 D = 1,角度θ= 0?? 时共现的次数。当θ = 0 时,这意味着它们在同一水平线上。θ指定方向,D 指定该方向的距离。请注意,引用位于邻居的左侧。
计算 GLCM 的步骤如下:
-
如果输入图像是灰度或二进制的,直接使用它。如果是彩色图像,将其转换为灰度图像,或者在适当的情况下只使用其中一个通道。
-
找出图像中强度等级的总数。如果数字为 L,则从 0 到 L-1 对这些级别进行编号。
-
创建一个 LxL 矩阵,其中行和列的编号都是从 0 到 L 1。
-
选择合适的 GLCM 参数(D,θ)。
-
找出每两对强度等级之间的同现。
d 值
研究表明 D 的最佳值在 1 到 10 之间。较大的值将产生无法捕捉详细纹理信息的灰度共生矩阵。因此,对于 D=1,2,4,8,结果是准确的,D=1,2 是最好的。通常,一个像素可能与其附近的像素更相关。减小距离比增大距离会产生更好的结果。
θ值
对于 3×3 矩阵,中心像素有 8 个相邻像素。在这样的中心像素和所有其他 8 个像素之间,θ有 8 个可能的值,如图 1-8 所示。
图 1-8
中心像素与其八个相邻像素之间的θ值
因为选择θ设置为 0°和 180°得到的共现对是相等的(即 GLCM (1,3),θ = 0 = GLCM (3,1),θ= 180°,只需要一个角度就足够了。通常,相隔 180°的角度会返回相同的结果。这适用于角度(45,225),(135,315)和(90,270)。
当 D=1 且θ=0 时,让我们开始计算图 1-3 中的前一矩阵的 GLCM,在下面的矩阵中再次重复。因为该图像具有四个强度级别,所以当参考强度为 0 时,可用的对是(0,0)、(0,1)、(0,2)和(0,3)。当参考强度为 1 时,则配对为(1,0)、(1,1)、(1,2)和(1,3)。这种情况持续到 2 和 3。
| three | Two | Two | Zero | three | | one | three | Zero | Two | Two | | Two | Two | Two | Two | three | | three | three | three | Two | three | | Zero | Two | three | Two | Two |计算 GLCM (0,0),D = 1,θ = 0 ,数值将为 0。这是因为没有强度为 0 的像素与另一个强度为 0 的像素水平相距 1 个像素。对于对(0,1)、(1,0)、(1,1)、(1,2)、(2,1)和(3,1),结果也是 0。
对于 GLCM (0,2),D = 1,θ = 0 ,结果是 2,因为有三次强度 3 位于水平方向上距离强度 0 1 个像素的位置(即θ = 0 )。对于 GLCM (3,3),D = 1,θ = 0 ,结果也是 2。对于 GLCM (0,3),D = 1,θ = 0 ,结果为 1,因为强度 3 只出现一次,距离强度 0 为 1,角度为 0。这位于原始矩阵的右上方。
完整的 GLCM 如图 1-9 所示。该矩阵的大小为 4×4,因为它具有从 0 到 3 编号的 4 个强度等级。添加行和列标签是为了更容易知道哪个强度级别与另一个强度级别同时出现。
图 1-9
图 1-3 中距离为 1,角度为 0 的矩阵的 GLCM
清单 1-4 中给出了用于返回前面的 GLCM 的 Python 代码。
import numpy
import skimage.feature
arr = numpy.array([[3, 2, 2, 0, 3],
[1, 3, 0, 2, 2],
[2, 2, 2, 2, 3],
[3, 3, 3, 2, 3],
[0, 2, 3, 2, 2]])
co_mat = skimage.feature.greycomatrix(image=arr, distances=[1], angles=[0], levels=4)
Listing 1-4GLCM Matrix Calculation
skimage.feature.greycomatrix()用于计算 GLCM。它接受输入图像、距离、计算矩阵的角度,最后是使用的级数。级别的数量很重要,因为默认值是 256。
请注意,每对唯一的角度和距离都有一个矩阵。只使用了一个角度和距离,因此返回了一个 GLCM 矩阵。返回输出的形状有四个数字,如下所示:
co_mat.shape = (4, 4, 1, 1)
前两个数字分别代表行数和列数。第三个数字代表使用的距离数。最后一个是角度数。如果要计算更多距离和角度的矩阵,则在skimage.feature.greycomatrix()中指定。下一行使用两个距离和三个角度计算 GLCM。
co_mat = skimage.feature.greycomatrix(image=arr, distances=[1, 4], angles=[0, 45, 90], levels=4)
返回的矩阵的形状是
co_mat.shape = (4, 4, 2, 3)
因为有两个距离和三个角度,所以返回的 GLCMs 总数为 2×3 = 6。要在距离 1 和角度 0 处返回 GLCM,步进如下:
co_mat[:, :, 0, 0]
这将返回完整的 4×4 GLCM,但仅针对第一个距离(1)和第一个角度(0),根据它们在skimage.feature.greycomatrix()函数中的顺序。为了返回对应于距离 4 和角度 90?? 的 GLCM,索引将如下:
co_mat[:, :, 1, 2]
GLCM 归一化
先前计算的 GLCMs 对于了解每个强度等级彼此共出现多少次是有用的。我们可以从这些信息中受益,以预测每两个强度级别之间的同现概率。GLCM 可以被转换成概率矩阵,因此我们可以知道当被距离 D 和角度θ分开时,两个强度等级 l 1 和l2 中的每一个之间的同现概率。这是通过将矩阵中的每个元素除以矩阵元素的总和来实现的。由此产生的矩阵被称为规范化或概率矩阵。根据图 1-9 ,所有元素的总和为 20。将每个元素除以后,归一化矩阵如图 1-10 所示。
图 1-10
距离为 1、角度为 0 的归一化 GLCM 矩阵
归一化 GLCM 的一个好处是输出矩阵中的所有元素都在从 0.0 到 1.0 的相同范围内。此外,结果与图像大小无关。例如,根据尺寸为 5×5 的图 1-9 ,对(2,2)的最高频率为 6。如果新图像更大(例如 100×100),则最高频率将不是 6,而是更大的值,例如 2000。我们不能比较 6 乘 2,000,因为这样的数字与图像大小有关。通过归一化矩阵,GLCM 的元素与图像大小无关,因此我们可以正确地比较它们。在图 1-10 中,对(2,2)给出的概率为 0.3,这与来自任何大小的任何图像的同现概率相当。
用 Python 规范化 GLCM 非常简单。基于名为normed的布尔参数,如果设置为True,结果将被归一化。默认设置为False。归一化矩阵是根据下面这条线计算的:
co_mat_normed = skimage.feature.greycomatrix(image=arr, distances=[1], angles=[0], levels=4, normed=True)
GLCM 的大小为 4×4,因为我们使用的是只有 4 个级别的 2 位图像。对于图 1-2(a) 中的 8 位灰度图像,有 256 级,因此矩阵大小为 256×256。归一化的灰度共生矩阵如图 1-11 所示。两个地区的概率很大。第一个在左上角(低强度),因为背景是深色的。另一个区域在狗身体的右下方(高强度),因为它的颜色是白色的。
图 1-11
灰度图像的 GLCM 矩阵,256 级,距离为 6,角度为 0
随着级别数量的增加,矩阵的大小也会增加。图 1-11 中的 GLCM 有 256×256 = 65536 个元素。在特征向量中使用矩阵中的所有元素将大大增加其长度。我们可以通过从矩阵中提取一些特征来减少这个数字,包括相异度、相关性、同质性、能量、对比度和 ASM(角二阶矩)。清单 1-5 给出了提取这些特征所需的 Python 代码。
import skimage.io, skimage.feature
import numpy
img = skimage.io.imread('im.jpg', as_grey=True);
img = numpy.uint8(img*255)
glcm = skimage.feature.greycomatrix(img, distances=[6], angles=[0], levels=256, normed=True)
dissimilarity = skimage.feature.greycoprops(P=glcm, prop="dissimilarity")
correlation = skimage.feature.greycoprops(P=glcm, prop="correlation")
homogeneity = skimage.feature.greycoprops(P=glcm, prop="homogeneity")
energy = skimage.feature.greycoprops(P=glcm, prop="energy")
contrast = skimage.feature.greycoprops(P=glcm, prop="contrast")
ASM = skimage.feature.greycoprops(P=glcm, prop="ASM")
glcm_props = [dissimilarity, correlation, homogeneity, energy, contrast, ASM]
print('Dissimilarity',dissimilarity,'\nCorrelation',correlation,'\nHomogeneity',homogeneity,'\nEnergy',energy,'\nContrast',contrast,'\nASM',ASM)
Listing 1-5Extracting GLCM Features
GLCM 的一个缺点是依赖于灰度值。光照的微小变化都会影响最终的灰度共生矩阵。一种解决方案是使用梯度而不是强度来构建 CM。这种矩阵被称为基于灰度梯度的共生矩阵(GLGCM)。通过使用梯度,GLGCM 对于光照变化是不变的。
GLCM 和 GLGCM 都是图像变换的变体。也就是说,如果相同的灰度图像受到诸如旋转的变换的影响,描述符将产生不同的特征。一个好的特征描述符应该不受这些影响。
猪
灰度共生矩阵用于描述图像纹理,但不能描述图像强度的突变(即边缘)。有时纹理并不适合在问题中使用,我们必须寻找另一个特征。一类特征描述符用于描述图像边缘。这些特征描述了边缘的不同方面,例如边缘方向或方位、边缘位置以及边缘强度或大小。
本小节讨论一个称为梯度方向直方图(HOG)的描述符,它描述边缘方向。有时,目标对象具有唯一的移动方向,因此 HOG 是一个合适的特征。HOG 创建了一个表示边缘方向频率的直方图。让我们看看 HOG 是如何工作的。
图像渐变
图像中每对相邻像素之间的强度都有变化。为了测量这种变化,计算每个像素的梯度向量,以测量从该像素到其相邻像素的强度如何变化。该向量的大小是两个像素之间的亮度差。向量也反映了 X 方向和 Y 方向的变化方向。对于图 1-12 中的灰度图像,让我们计算第三行第四列的像素 21 在 X 和 Y 方向上的强度变化。
图 1-12
灰度图像计算其梯度
图 1-13 中显示了用于查找 X 和 Y 方向上的梯度幅度的掩模。让我们开始计算梯度。
图 1-13
用于计算水平和垂直梯度的遮罩
通过将水平蒙版在目标像素上居中,我们可以计算 X 方向的梯度。在这种情况下,相邻像素是 83 和 98。通过减去这些值,或者从右边的像素中减去左边的像素,或者从左边的像素中减去右边的像素,但在整个图像中保持一致:该像素的变化量为 9883 = 15。这种情况下使用的角度为 0°。
为了获得 Y 方向上该像素的变化量,垂直遮罩以目标像素为中心。然后,减去该像素的顶部和左侧像素,得出 6353 = 10。这种情况下使用的角度是 90 度。
在计算 X 和 Y 方向上的变化之后,接下来是根据等式 1-1 计算最终梯度幅度,以及根据等式 1-2 计算梯度方向。
(方程式 1-1)
(方程式 1-2)
梯度幅度等于
梯度方向
关于梯度方向,可以说该像素的变化方向在 0°处,因为 0°处的幅度高于 90°处的矢量。然而,其他人可能会说,像素不会在 0°或 90°处变化,而是在两个角度之间变化。这种角度是通过考虑 X 和 Y 方向来计算的。向量的方向是。结果,像素变化方向为 56.31。
计算完所有图像的角度后,下一步是为这些角度创建一个直方图。为了使直方图更小,并不使用所有的角度,而只是一组预定义的角度。最常用的角度是水平(0°)、垂直(90°)和对角(45°和 135°)。每个角度的贡献值等于根据等式 1-3 计算的梯度幅度。例如,如果当前像素对 Z 轴有贡献,它会将值 18.03 添加到其中。
有助于直方图仓
我们之前计算的角度是 56.31。这不是以前选择的角度之一。解决方案是将该角度分配给最近的直方图仓。56.31 位于仓 45 和 90 之间。因为 56.31 比 90 更接近 45,所以它将被分配到 bin 45。更好的方法是将该像素对这两个角度(45°和 90°)的贡献分开。
45 度角和 90 度角之间的距离是 45 度。56.31°角和 45°角之间的距离正好是∣56.31 45°∣= 11.31°。这意味着 56.31 度角与 45 度角相差一个等于的百分比。换句话说,56.31 是接近 45 的 75%。同样,56.31°角和 95°角之间的距离正好是∣56.31 90°∣= 33.69°。这意味着 56.31°的角度与 90°相差一个等于
的百分比。根据等式 1-3 计算角度添加到仓中的值。
(方程式 1-3)
其中像素 角度 为当前像素的方向,像素gradient magnitude为当前像素的渐变幅度, bin 角度 为直方图 bin 值, bin 间距 为每两个 bin 之间的间距量。
因此,56.31°的角度增加了 45°的 75%的梯度幅度,等于。它只将 25%的渐变幅度增加到 45 到 90,等于
。
更实际的直方图包含从 0°开始到 180°结束的九个角度。每对角度之间的差将是 180/9=20。因此,所用的角度为 0、20、40、60、80、100、120、140、160 和 180。箱不是这些角度,而是每个范围的中心。对于 0–20 范围,使用的 bin 是 10。对于 20–40,仓位为 30,依此类推。最终的直方图柱为 10、30、50、70、90、110、130、150 和 170。如果角度为 25 °,它会添加到其所在的面元中。也就是说,它添加到库 10(增加 0.25)和库 30(增加 0.75)。
通过对位于第二行第二列的像素 68 重复上述步骤,应用水平掩码的结果是 9750 = 47,这是 X 方向的梯度变化。应用垂直遮罩后,结果为 4323 = 20。根据等式 1-2,变化方向计算如下:
同样,合成角度不等于任何直方图区间。因此,该角度的贡献在它所落入的 15°和 45°的区间上被分开。它把 0.27 加到 45,又把 0.73 加到 45。
对于位于强度值为 88 的第四行第二列的像素,X 方向上的变化为 0。应用等式 1-2,结果将除以 0。为了避免被零除,在分母上加一个很小的值,如 0.0000001。
拱形台阶
至此,我们已经学会了如何计算任何像素的梯度大小和方向。但是在计算这些值之前和之后还有一些工作要做。HOG 步骤总结如下:
-
将输入图像分割成纵横比为 1:2 的面片。例如,补丁大小可能是 64×128、100×200 等等。
-
将补丁分成块(例如,四个块)。
-
将每个区块划分成单元格。块内的单元大小是不固定的。
- 例如,如果块大小为 16×16,我们决定将其分成四个单元,则每个单元的大小为 8×8。还要注意,块可能彼此重叠,并且一个单元可能在多个块中可用。
-
对于每个块中的每个单元,计算所有像素的梯度大小和方向。
-
基于图 1-13 中的掩模计算梯度。
-
梯度大小和方向分别根据等式 1-1 和 1-2 计算。
-
-
根据梯度的大小和方向,为每个像元构建直方图。如果用于构成直方图的角度数是 9,则每个单元返回一个 9×1 的特征向量。直方图是根据我们之前的讨论计算出来的。
-
连接同一块中所有单元的所有直方图,并只返回整个块的单个直方图。如果每个单元直方图由九个二进制表示,并且每个块具有四个单元,则级联直方图长度为 4×9=36。这个 36×1 的矢量是每个块的结果。
-
该矢量被归一化以使其对光照变化具有鲁棒性。
-
连接图像补片中所有块的归一化向量,以返回最终的特征向量。
图 1-14 显示了图 1-5(a) 中图像的一个补丁,尺寸为 64×128。
图 1-14
计算其 HOG 的图像补丁
在创建直方图之前,根据垂直和水平遮罩计算垂直和水平梯度。梯度如图 1-15 所示。
图 1-15
64×128 图像面片的垂直和水平渐变
清单 1-6 中给出了用于计算这种梯度的 Python 代码。
import skimage.io, skimage.color
import numpy
import matplotlib
def calculate_gradient(img, template):
ts = template.size #Number of elements in the template (3).
#New padded array to hold the resultant gradient image.
new_img = numpy.zeros((img.shape[0]+ts-1,
img.shape[1]+ts-1))
new_img[numpy.uint16((ts-1)/2.0):img.shape[0]+numpy.uint16((ts-1)/2.0),
numpy.uint16((ts-1)/2.0):img.shape[1]+numpy.uint16((ts-1)/2.0)] = img
result = numpy.zeros((new_img.shape))
for r in numpy.uint16(numpy.arange((ts-1)/2.0, img.shape[0]+(ts-1)/2.0)):
for c in numpy.uint16(numpy.arange((ts-1)/2.0,
img.shape[1]+(ts-1)/2.0)):
curr_region = new_img[r-numpy.uint16((ts-1)/2.0):r+numpy.uint16((ts-1)/2.0)+1,
c-numpy.uint16((ts-1)/2.0):c+numpy.uint16((ts-1)/2.0)+1]
curr_result = curr_region * template
score = numpy.sum(curr_result)
result[r, c] = score
#Result of the same size as the original image after removing the padding.
result_img = result[numpy.uint16((ts-1)/2):result.shape[0]-numpy.uint16((ts-1)/2),numpy.uint16((ts-1)/2):result.shape[1]-numpy.uint16((ts-1)/2)]
return result_img
Listing 1-7
Gradient Magnitude
Listing 1-6Calculating Gradients
基于接受灰度图像和遮罩的calculate_gradient(img, template)函数,基于遮罩对图像进行过滤,然后将其返回。通过用不同的掩码(垂直和水平)调用它两次,返回垂直和水平渐变。
然后,根据清单 1-7 中的gradient_magnitude()函数,使用垂直和水平梯度计算梯度幅度。
def gradient_magnitude(horizontal_gradient, vertical_gradient):
horizontal_gradient_square = numpy.power(horizontal_gradient, 2)
vertical_gradient_square = numpy.power(vertical_gradient, 2)
sum_squares = horizontal_gradient_square + vertical_gradient_square
grad_magnitude = numpy.sqrt(sum_squares)
return grad_magnitude
Listing 1-8
Gradient Direction
该函数只是将等式 1-1 应用于先前计算的垂直和水平梯度。补片图像的梯度大小如图 1-16 所示。
图 1-16
基于先前为 64×128 图像补片计算的垂直和水平渐变的渐变幅度
使用清单 1-8 中的函数gradient_direction(),计算梯度方向。
def gradient_direction(horizontal_gradient, vertical_gradient):
grad_direction = numpy.arctan(vertical_gradient/(horizontal_gradient+0.00000001))
grad_direction = numpy.rad2deg(grad_direction)
# Some angles are outside the 0-180 range. Next line makes all results fall within the 0-180 range.
grad_direction = grad_direction % 180
return grad_direction
Listing 1-9Cell Histogram
注意添加到分母上的小值(0.00000001)。这避免了被零除。忽略这一点,一些输出值将是 NaN(不是数字)。
图 1-17 显示了分割成 16×8 个单元后的图像块。每个单元具有 8×8 个像素,每个块具有 4 个单元(即,每个块具有 16×16 个像素)。
图 1-17
图像分为 16×8 个单元格
基于之前计算的梯度大小和方向,我们可以返回图像块中第一个 8×8 单元(左上角单元)的结果,如图 1-18 所示。
图 1-18
左上角 8×8 单元格的渐变幅度和方向
直方图将基于我们之前讨论的简单示例来创建。有 9 个直方图仓,覆盖从 0°到 180°的角度范围。仅使用有限数量的箱来表示这样的范围使得每个箱覆盖不止一个角度。仅使用 9 个箱,那么每个箱将覆盖 20 个角度。第一个箱覆盖从 0(包括 0)到 20(不包括 0)的角度。第二个从 20(含)到 40(不含),直到覆盖 160(含)到 180(含)角度的最后一个面元。每个范围的仓将被赋予一个等于每个范围中心的数字。也就是说,第一个箱被赋予 10,第二个箱被赋予 20,依此类推,直到最后一个箱被赋予 170。我们可以说,从第 20 步开始,仓从 10 到 170。对于图 1-18(a) 中的每个角度,找到其所在的两个直方图仓。从左上角的值为 44.41 的元素开始,它位于库 30 和 50 之间。根据等式 1-3,该值对这两个仓都有贡献。箱 30 的贡献值计算如下:
关于箱 50,贡献值计算如下:
对当前单元中的所有 8×8 像素继续该过程。左上角单元格的直方图如图 1-19(a) 所示。假设每个块包含 2×2 个单元,图 1-17 中用亮色标记的左上块中剩余三个单元的 9-bin 直方图也显示在图 1-19 中。通过计算给定块的所有直方图,其特征向量是这四个 9-bin 直方图的串联。特征向量的长度为 9×4 = 36。
图 1-19
当前图像块左上块内四个单元的九格直方图
计算完第一个块的特征向量后,选择下一个有四个单元格的块,在图 1-20 中用亮色标记。
图 1-20
图像补片中的第二个块以亮色突出显示
同样,如图 1-21 所示,计算该块内四个单元中每一个单元的九格直方图,并将它们的结果连接起来,返回 36×1 特征向量。
图 1-21
在当前图像块的图 1-20 中用亮色标记的第二个块内的四个单元的九格直方图
使用清单 1-9 中的HOG_cell_histogram()函数计算每个单元格的直方图。该函数接受给定单元格的方向和大小,并返回其直方图。
def HOG_cell_histogram(cell_direction, cell_magnitude):
HOG_cell_hist = numpy.zeros(shape=(hist_bins.size))
cell_size = cell_direction.shape[0]
for row_idx in range(cell_size):
for col_idx in range(cell_size):
curr_direction = cell_direction[row_idx, col_idx]
curr_magnitude = cell_magnitude[row_idx, col_idx]
diff = numpy.abs(curr_direction - hist_bins)
if curr_direction < hist_bins[0]:
first_bin_idx = 0
second_bin_idx = hist_bins.size-1
elif curr_direction > hist_bins[-1]:
first_bin_idx = hist_bins.size-1
second_bin_idx = 0
else:
first_bin_idx = numpy.where(diff == numpy.min(diff))[0][0]
temp = hist_bins[[(first_bin_idx-1)%hist_bins.size, (first_bin_idx+1)%hist_bins.size]]
temp2 = numpy.abs(curr_direction - temp)
res = numpy.where(temp2 == numpy.min(temp2))[0][0]
if res == 0 and first_bin_idx != 0:
second_bin_idx = first_bin_idx-1
else:
second_bin_idx = first_bin_idx+1
first_bin_value = hist_bins[first_bin_idx]
second_bin_value = hist_bins[second_bin_idx]
HOG_cell_hist[first_bin_idx] = HOG_cell_hist[first_bin_idx] + (numpy.abs(curr_direction - first_bin_value)/(180.0/hist_bins.size)) * curr_magnitude
HOG_cell_hist[second_bin_idx] = HOG_cell_hist[second_bin_idx] + (numpy.abs(curr_direction - second_bin_value)/(180.0/hist_bins.size)) * curr_magnitude
return HOG_cell_hist
Listing 1-10Complete Implementation for Calculating Histogram for the Top-Left Cell
清单 1-10 给出了用于读取图像补丁的完整代码,并返回第一个块中左上角单元格的直方图。请注意,该代码适用于灰度图像。如果输入图像是灰度图像,它将只有两个维度。如果输入图像是彩色的,那么它将具有表示通道的第三维。在这种情况下,只使用一个灰度通道。使用 ndim 属性返回 NumPy 数组的维数。
import skimage.io, skimage.color
import numpy
import matplotlib.pyplot
def calculate_gradient(img, template):
ts = template.size #Number of elements in the template (3).
#New padded array to hold the resultant gradient image.
new_img = numpy.zeros((img.shape[0]+ts-1,
img.shape[1]+ts-1))
new_img[numpy.uint16((ts-1)/2.0):img.shape[0]+numpy.uint16((ts-1)/2.0),
numpy.uint16((ts-1)/2.0):img.shape[1]+numpy.uint16((ts-1)/2.0)] = img
result = numpy.zeros((new_img.shape))
for r in numpy.uint16(numpy.arange((ts-1)/2.0, img.shape[0]+(ts-1)/2.0)):
for c in numpy.uint16(numpy.arange((ts-1)/2.0,
img.shape[1]+(ts-1)/2.0)):
curr_region = new_img[r-numpy.uint16((ts-1)/2.0):r+numpy.uint16((ts-1)/2.0)+1,
c-numpy.uint16((ts-1)/2.0):c+numpy.uint16((ts-1)/2.0)+1]
curr_result = curr_region * template
score = numpy.sum(curr_result)
result[r, c] = score
#Result of the same size as the original image after removing the padding.
result_img = result[numpy.uint16((ts-1)/2.0):result.shape[0]-numpy.uint16((ts-1)/2.0), numpy.uint16((ts-1)/2.0):result.shape[1]-numpy.uint16((ts-1)/2.0)]
return result_img
def gradient_magnitude(horizontal_gradient, vertical_gradient):
horizontal_gradient_square = numpy.power(horizontal_gradient, 2)
vertical_gradient_square = numpy.power(vertical_gradient, 2)
sum_squares = horizontal_gradient_square + vertical_gradient_square
grad_magnitude = numpy.sqrt(sum_squares)
return grad_magnitude
def gradient_direction(horizontal_gradient, vertical_gradient):
grad_direction = numpy.arctan(vertical_gradient/(horizontal_gradient+0.00000001))
grad_direction = numpy.rad2deg(grad_direction)
grad_direction = grad_direction%180
return grad_direction
def HOG_cell_histogram(cell_direction, cell_magnitude):
HOG_cell_hist = numpy.zeros(shape=(hist_bins.size))
cell_size = cell_direction.shape[0]
for row_idx in range(cell_size):
for col_idx in range(cell_size):
curr_direction = cell_direction[row_idx, col_idx]
curr_magnitude = cell_magnitude[row_idx, col_idx]
diff = numpy.abs(curr_direction - hist_bins)
if curr_direction < hist_bins[0]:
first_bin_idx = 0
second_bin_idx = hist_bins.size-1
elif curr_direction > hist_bins[-1]:
first_bin_idx = hist_bins.size-1
second_bin_idx = 0
else:
first_bin_idx = numpy.where(diff == numpy.min(diff))[0][0]
temp = hist_bins[[(first_bin_idx-1)%hist_bins.size, (first_bin_idx+1)%hist_bins.size]]
temp2 = numpy.abs(curr_direction - temp)
res = numpy.where(temp2 == numpy.min(temp2))[0][0]
if res == 0 and first_bin_idx != 0:
second_bin_idx = first_bin_idx-1
else:
second_bin_idx = first_bin_idx+1
first_bin_value = hist_bins[first_bin_idx]
second_bin_value = hist_bins[second_bin_idx]
HOG_cell_hist[first_bin_idx] = HOG_cell_hist[first_bin_idx] + (numpy.abs(curr_direction - first_bin_value)/(180.0/hist_bins.size)) * curr_magnitude
HOG_cell_hist[second_bin_idx] = HOG_cell_hist[second_bin_idx] + (numpy.abs(curr_direction - second_bin_value)/(180.0/hist_bins.size)) * curr_magnitude
return HOG_cell_hist
img = skimage.io.imread("im_patch.jpg")
if img.ndim >2:
img = img[:, :, 0]
horizontal_mask = numpy.array([-1, 0, 1])
vertical_mask = numpy.array([[-1],
[0],
[1]])
horizontal_gradient = calculate_gradient(img, horizontal_mask)
vertical_gradient = calculate_gradient(img, vertical_mask)
grad_magnitude = gradient_magnitude(horizontal_gradient, vertical_gradient)
grad_direction = gradient_direction(horizontal_gradient, vertical_gradient)
grad_direction = grad_direction % 180
hist_bins = numpy.array([10,30,50,70,90,110,130,150,170])
cell_direction = grad_direction[:8, :8]
cell_magnitude = grad_magnitude[:8, :8]
HOG_cell_hist = HOG_cell_histogram(cell_direction, cell_magnitude)
matplotlib.pyplot.bar(left=numpy.arange(9), height=HOG_cell_hist, align="center", width=0.8)
matplotlib.pyplot.show()
在计算了块的特征向量之后,下一步是归一化该向量。特征归一化的动机是特征向量依赖于图像强度水平,并且最好使其对光照变化具有鲁棒性。归一化通过将向量中的每个元素除以根据等式 1-4 计算的向量长度来进行。
(方程式 1-4)
其中XI代表矢量元素编号 i 。归一化向量是第一块的结果。该过程继续,直到返回所有块的所有 36×1 特征向量。然后,将这些向量连接起来,用于正在处理的整个图像补片。
根据前面的讨论,HOG 在计算之前需要指定以下参数:
-
方向的数量。
-
每个单元格的像素数。
-
每个块的单元数。
HOG 已经在 Python 的skimage.feature模块中实现,可以根据skimage.feature.hog()函数轻松使用。前面三个参数有默认值,可以根据您的目标进行更改。如果normalized参数设置为True,则返回标准化的 HOG。
skimage.feature.hog(image, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(3, 3), visualise=False, transform_sqrt=False, feature_vector=True, normalise=None)
垂直线间的距离
LBP 代表局部二元模式,这是另一种二阶纹理描述符。提取 LBP 特征的步骤如下:
-
将图像分成块(例如,16×16 块)。
-
对于每个块,一个 3×3 的窗口位于每个像素的中心。
根据等式 1-5,将所选中心像素 P 中心 与其周围 8 个邻居 P 邻居 中的每一个进行比较。从八次比较中,将有八个二进制数字。
(方程式 1-5)
-
8 位二进制代码被转换成整数。整数范围从 0 到 2 8 = 255。
-
用计算出的整数替换 P 中央 的值。
-
在计算同一块内所有像素的新值后,计算直方图。
-
在计算了所有块的直方图后,它们被连接起来。
假设我们当前正在处理的块在图 1-12 中,我们可以基于它开始计算基本的 LBP。
通过处理第三行和第四列的像素,将中心像素与八个相邻像素中的每一个进行比较。图 1-22 显示了比较的结果。
图 1-22
将中心像素与其八个相邻像素进行比较的结果
接下来是返回二进制代码。您可以从 3×3 矩阵中的任何位置开始,但必须在整个图像中保持一致。例如,从左上位置开始顺时针移动,代码为 11011101。你可以顺时针或逆时针移动,但要保持一致。
此后,通过将每个二进制数乘以对应于其在二进制码中的位置的权重相加,将二进制码转换成十进制码。结果是 128 + 64 + 16 + 8 + 4 + 1 = 221。
在计算块中每个像素的二进制代码并返回其十进制代码后,就创建了直方图。对所有图像块重复该过程,并且像在 HOG 的情况下一样,连接来自所有块的直方图。
这是 LBP 的基本实现,但是这种特征描述符具有多种变化,使得它对光照变化、缩放和旋转具有鲁棒性。
使用接受三个参数的skimage.feature.local_binary_pattern()函数可以很容易地在 Python 中实现它:
-
输入图像。
-
圆中相邻点的数目(P)。该参数有助于实现旋转不变性。
-
圆的半径(R)。这种参数有助于实现比例不变性。
这里是一个 LBP 应用于图 1-2(a) 中的灰度图像的例子。
import skimage.feature
import skimage.io
import matplotlib.pyplot
im = skimage.io.imread("69.jpg", as_grey=True)
lbp = skimage.feature.local_binary_pattern(image=im, P=9, R=3)
matplotlib.pyplot.imshow(lbp, cmap="gray")
matplotlib.pyplot.xticks([])
matplotlib.pyplot.yticks([])
输出图像如图 1-23 所示。
图 1-23
在 P-9 和 R=3 的灰度图像上应用 LBP 的输出
特征选择和减少
假设在与某个领域的专家讨论之后,您推断出特性 X、Y 和 Z 是合适的。这些功能只是最初选择的,其中一些功能可能没有帮助。您可以根据一些实验来决定每个特性是好是坏。在基于这三个特征训练模型之后,识别率较低,并且必须在特征向量中改变某些东西。为了知道原因,您通过为每个单独的特征训练模型来进行一些实验。您发现特征 Z 和目标之间的相关性很低,因此决定不使用该特征。从特征向量中完全消除某些特征并保留其他特征称为特征选择。选择技术将特征分为好的或坏的。坏的特征被完全消除,而好的特征被专门使用。
事实上,每个特性中的一些元素可能不适合所分析的应用类型。假设在特征向量中使用特征 X,并且该特征具有一组 10 个元素。这些元素中的一个或多个可能对任务没有帮助。例如,有些功能是多余的。也就是说,一些特征可能彼此最佳相关,因此仅一个特征就足以描述数据,并且不需要使用工作相同的多个特征。由于相关的特征,可能不存在唯一的最优特征子集。这是因为可能有不止一个完全相关的特征,因此一个可以替换另一个并创建新的特征子集。
另一类是无关的特性。一些特征与所需的预测无关,它们被视为噪声。这样的特征不会增强而是降低结果。因此,最好检测这些特征并删除它们,这样它们就不会影响学习过程。只删除元素的一个子集而保留其他的叫做特征约简。
通过尽可能去除坏特征来减少特征向量长度的另一个动机是,特征向量长度越长,在训练和测试模型时消耗的计算时间就越多。
要将要素分类为相关或不相关,需要一些度量来显示每个要素与输出类的相关性或每个要素预测所需输出的程度。特征相关性是特征区分不同类别的能力。在选择度量之后,它们将被用于通过消除坏的特征来创建好的特征子集。特征消除方法分为监督和非监督方法。有监督的方法包括过滤器和包装器,无监督的方法包括嵌入式。
过滤器
过滤方法增加了额外的预处理步骤,以应用可变排序技术,基于为每个特征计算的不同标准来对特征进行排序,从而测量特征的相关性。这些标准包括标准偏差(STD)、能量、熵、相关性和互信息(MI)。基于阈值,选择排名高的特征来训练模型。与其他选择方法相比,过滤方法非常快速且不耗时。它们的计算也很简单,可扩展,能够避免过拟合,并且独立于学习模型。
对于训练不同的模型,选择只进行一次,然后训练模型可以使用所选择的特征。但是,与其他特征选择方法相比,过滤方法有许多严重影响其性能的严重缺点。过滤/分级方法没有对特征依赖性建模。它独立于同一子集中的其他特征/变量来选择每个变量/特征。当根据所选择的标准被高度排序时,特征被选择。用于排序的标准没有考虑多个特征之间的关系。忽略特征依赖性会破坏整个所选子集,因为当与其他特征组合时,不能保证那些本身对提高学习速率很重要的特征也是如此。在某些情况下,有用的变量在组合在一起时仍然有用,但情况并非总是如此。
如果两个特征 f 1 和 f 2 的有用性分别是 x 1 和 x 2 ,这并不意味着它们组合在一起时有用性就会是x1+x2。此外,忽略特性依赖会导致冗余和相关的特性。这是因为可能有两个或两个以上的特征完全满足标准,但它们中的每一个都是做相同任务的所有其他特征的完美反映。所以不要求使用同一事物的倍数;一个就够了。相关性是冗余的另一种形式,在这种形式中,特征可能不相同,但是相互依赖,并且总是工作相同(可以表示为两条平行线)。完全相关的变量确实是多余的,因为它们没有增加额外的信息。冗余和相关特征的使用导致大的特征向量,因此不能实现特征选择减少特征向量长度的好处。过滤方法不与学习模型交互,因为它们不依赖于学习算法性能,这是由于将特征选择与性能解耦。相反,这些只是考虑特征和类标签之间的单个标准,而不是指示特征与学习算法工作得如何的信息。一个好的特征选择器应该考虑学习算法和训练数据集如何交互。最后,计算用于将特征分类为选中或未选中的阈值并不是一件容易的事情。所有这些原因导致了克服这些问题的其他特征选择方法的使用。
包装材料
包装器方法是解决过滤方法的一些问题的第二种方法。包装器方法试图通过创建使性能最大化的所选特征的子集来与学习模型交互。包装器方法创建所有可能的特征子集,以找到最佳子集。包装器方法之所以这么叫,是因为它围绕着学习算法。它使用归纳或学习算法作为黑盒,通过用所选子集训练算法来测量所选特征子集的工作情况,然后使用最大化其性能的算法。当谈到包装器方法时,应该很好地涵盖多个要点,包括选择特征子集长度、创建特征子集空间、搜索特征子集空间、评估学习算法的性能、停止搜索标准以及确定使用哪个学习算法。任何特征缩减/选择算法的目标都是从长度为 N 的原始完整特征向量中创建长度为 L 个特征的所有可能的特征组合,以最大化性能,其中< N 。对于长度=30 的正常特征向量,有大量的组合来创建长度为 L = 10 的子集。为此,应用搜索策略来搜索最佳子集,并使用评价函数惩罚不良子集。在这种情况下,目标函数是模型性能。这样,问题就从学习问题转化为搜索问题。穷举搜索不适用于这种问题,因为它访问和训练具有所有子集的学习模型,这在计算方面是密集的。因此,进化算法(EAs)被用来避免探索所有的子集,这些算法可以分为两类:顺序选择算法(确定性的)和启发式搜索算法(随机的)。
确定性搜索算法被进一步分为实际上非常相似的两类,即前向选择和后向选择。在前向选择中,该算法从表示空集特征的根开始,然后逐个特征地添加,同时为每个变化训练学习模型。在后向选择中,搜索的根是完整的特征集,然后算法逐个删除特征,同时为每个变化训练学习模型。这种算法的例子包括顺序特征选择(SFS)、顺序向后选择(SBS)、顺序向前浮动选择(SFFS)、自适应 SFFS (ASFFS)和波束搜索。为了防止搜索穷尽,添加了停止标准以防止探索所有可能的组合。该标准可以是前向选择的最大特征向量长度或后向选择的最小长度。它也可以是一个最大性能,因此在达到选定的性能后,搜索停止。
第二类搜索算法,随机算法,是使用启发式评估函数的通知搜索,生成一个启发式值,告诉每个子集有多接近最大性能。这种搜索算法的例子包括遗传算法(GA)、粒子群优化(PSO)、模拟退火(SA)和随机爬山。GA 将在第五章中详细讨论。
一个必须回答的问题是 L (特征数)的最优值是多少?包装器方法有不同的方法来回答这个问题。一种方法是选择固定数量的特征来创建特征向量,并且通过使用组合,有可能获得从 N 个特征中仅选择 L 个特征的所有可能性。但不幸的是,为 L 选择的固定值可能不是最佳值,并且不能保证 L 选择的特征是为学习模型提供最佳性能的特征。因此,另一种方法是使被选择的特征的数量可变并且动态地变化。这是通过尝试不同数量的特征并选择最佳数量的特征来最大化性能,如同在顺序选择算法中一样。使特征的数量动态变化的缺点是通过创建不同长度的不同特征组合并用它们训练模型来增加越来越多的计算时间。为了减少这个时间,可以添加一个标准,使得在达到目标性能之后或者在所选特征的数量达到最大长度之后,学习更早地停止。
将包装器方法与过滤器方法相比较,有许多优点。包装器方法与学习模型交互,因为它使用学习算法性能作为选择最佳特征子集的度量。它还对功能依赖关系进行建模,因为功能不是单独或相互独立选择的,并监控将功能组合在一起对性能的影响。最后,它对冗余和相关特征具有鲁棒性。但是各种包装方法都有许多缺点。它们非常耗时,因为每个模型都需要多次训练。有些模型非常耗时,一次训练可能需要几个小时。因此,包装方法不是这种模型的选项。此外,包装器方法受到过度拟合的影响,因为选择依赖于学习模型,因此不能在不同的模型上概括选择的特征。
植入的
过滤器和包装器这两种特性选择方法各有优缺点。嵌入式方法试图结合过滤器和包装器方法的优点。这种方法并不耗时,因为它们避免了在过滤器和包装器方法中看到的学习算法的再训练。它们通过与学习模型交互来最大化性能,就像在包装器方法中一样。仅当该特征与输出类标签 m 相关时,才选择该特征。之所以称之为嵌入式,是因为它通过在训练步骤中嵌入特征选择来工作。
嵌入式方法的类别如下:修剪、内置和正则化(惩罚)。修剪方法首先用完整的特征集训练学习模型,然后计算每个特征的相关系数。这些系数用于根据所使用的模型对特征的重要性进行排序。系数的高值反映了强相关性。嵌入式特征选择的内置方法计算每个特征的信息增益,就像决策树学习(ID3)一样。
在机器学习(ML)中,一些模型被训练得非常好,可以对训练数据中的任何样本做出正确的预测,但不幸的是,不能对训练样本之外的其他样本做出正确的预测。这个问题叫做过拟合。正则化是一种用来避免这个问题的技术。正则化是调整或选择最佳的模型复杂度以适应训练数据,同时能够预测看不见的样本。如果不进行正则化,模型可能会非常简单且拟合不足(无法对训练样本和测试样本都做出正确的预测),或者非常复杂且过度拟合(对训练样本的预测正确,但对测试样本的预测错误)。欠拟合和过拟合都使得模型太弱,不能推广到任何样本。因此,正则化是一种概括模型以预测任何样本的方法,无论是训练还是测试。
正规化
为了找到最佳模型,ML 中的常用方法是定义一个描述模型与数据拟合程度的损失或成本函数。目标是找到最小化这个损失函数的模型。通常,任何学习模型中的目标函数只有一个标准,即最大化性能。正则化方法为目标函数增加了另一个标准,以控制复杂程度,如等式 1-6 所示。
L = min 误差 ( Y 预测 , Y 校正 ) + λ罚(WI)(方程式 1-6)
其中 Y 预测 为预测类标签, Y 正确 为正确类标签,错误(。)计算预测误差, W i 是特征元素 X i 的权重, λ 是控制模型复杂度的正则化参数。该参数用于控制目标函数和惩罚之间的权衡。根据等式 1-7 定义罚值。
(方程式 1-7)
通过改变 λ 值,模型复杂度发生变化。这是通过将一些特征的权重设置为接近或等于零来惩罚它们。系数的大小是决定模型复杂性的一个重要因素。通过选择具有高权重幅度的特征来间接实现特征选择。权重越高,预测正确类别的特征越相关。这就是为什么正规化的方法被称为惩罚。
正则化参数 λ 的目标是最小化损失 L 并使其保持最小。对于接近∞λ的非常大的值,系数必须小并且接近零,以使总值尽可能小。这使得大部分系数为零,从而消除它们。对于一个值为 0 < ʎ < ∞,会有一些等于零的系数被去掉,但等于零的并不多。 λ 的最佳值是多少? λ 没有固定值,可以使用交叉验证(CV)有效地计算其值。
结合过滤器和包装器方法的优点使得嵌入式方法成为特征选择的最新研究趋势。它与学习模型交互,因为它像包装方法一样使用训练模型性能作为度量,不像过滤方法那样耗时,因为它不需要重新训练模型,并且还对特征依赖性进行建模以避免冗余和相关的特征。在训练时选择特征在数据使用方面是有效的,因为不需要将数据分成训练集和验证集。然而,尽管通过嵌入式方法选择的特征对于用于选择特征的学习模型来说表现良好,但是所选择的特征可能依赖于这样的模型,并且不会像使用过滤器方法产生的那样在不同的模型之间工作良好。
二、人工神经网络
机器学习(ML)问题可以分为三类:有监督的、无监督的和强化的。在监督学习中,人类专家在受限环境中进行一些实验,并注意到它们的结果。监督学习算法探索从实验中收集的数据,以将输入映射到输出。例如,一个受限的环境可能有一个机器人想要从一个小房间的一边走到另一边。房间里有一些障碍物可能会使机器人摔倒。主管提供如何到达墙壁而不摔倒的指导。这是通过以例子的形式给机器人知识来帮助它学习如何通过障碍来实现的。机器人利用这些知识来增加通过障碍而不摔倒的概率。在这种情况下,机器人的知识完全依赖于人类。
在强化学习中,人类给机器人一个衡量标准来评估它的表现。机器人必须最大化这个度量来达到它的目标。它不知道何时向右移动。基于度量,机器人将尝试移动不同的位置并计算度量。如果机器人掉在一个给定的位置,那么下次它必须避开它。这样,机器人就会找到使它到达目标而不摔倒的路。
与监督和半监督学习相比,无监督学习既不会给出实验结果,也不会给出度量。根本没有人类来引导它。这很有挑战性。
人工神经网络(简称 ANN)是一种能解决所有这些问题的算法。本书只讨论使用 ANN 的监督学习。ANN 是一个受生物启发的 ML 模型,模仿人类大脑的运作。这是谈论深度学习(DL)时要涵盖的最重要的话题之一。理解只有几层和几个神经元的简单人工神经网络的操作,就更容易理解复杂模型是如何工作的。
在这一章中,将介绍学习 CNN 如何工作的先决条件。它从探索初级水平的人工神经网络开始。从知道它是线性模型的集合开始,你会发现它根本不是一个奇怪的概念;事实上,你已经知道了。本章讨论了一些与人工神经网络相关的概念,如学习率、反向传播和过拟合。本章将帮助你理解为什么我们需要人工神经网络中的学习率,以及它对训练是否有用。对单层感知器使用一个非常简单的 Python 代码,学习率值将被改变以捕捉它的想法,并注意改变学习率如何影响结果。它还讨论了反向传播算法如何用于更新人工神经网络的权重。本章还解释了过度拟合,这是对未知样本预测不佳的原因之一。一种基于回归的正则化技术通过简单的步骤来说明如何避免过拟合。人工神经网络有一个特殊的图表,使解释其结果更容易。本章绘制了数学表示及其图形,并探讨了令初学者感到困难的一点,即如何确定最佳的神经元数量和隐藏层。最后,给出了一个用人工神经网络进行 Python 分类的例子。
人工神经网络简介
监督学习问题分为两大类:分类和回归。回归输出是连续的数字,而分类输出是分类标签。每种类型的问题都可以使用线性或非线性模型。分类问题也可以分为二元或多类分类问题。所有这些类型的问题都可以使用神经网络来解决。也就是说,可以使神经网络产生连续或离散的输出。它可以处理二元或多类问题,并模拟线性和非线性函数。ANN 是一种通用函数逼近器(即 ANN 可以模拟任何线性和非线性函数的运算)。ANN 是一个参数模型,它有一组从问题中学习到的参数,如权重和偏差。它还有许多可以由工程师调整的超参数,例如学习速率和隐藏层数。
人工神经网络实际上由线性模型组成,这些模型被组合在一起以解决复杂的问题。下一小节讨论人工神经网络的基本构件实际上是一个线性模型。
线性模型是人工神经网络的基础
对于初学者来说,最简单的模型类型是线性模型。当然,每个人都知道线性模型,这使得接下来的解释更容易。我们可以从一个简单的回归问题开始,我们希望为表 2-1 中所示的样本创建一个线性模型。拟合此类数据的最佳线性模型是什么?让我们看看。
表 2-1
简单回归问题
|输入(X)
|
输出(Y)
| | --- | --- | | Two | six |
“线性模型”是指将每个输入映射到其相应输出的线。我们将从最简单的线性模型开始,如方程 2-1。该模型将输入和输出均衡在一起,方程中没有任何其他参数。
之后,我们创建了第一个模型。有人可能会问,构建任何模型的培训部分在哪里?答案是这个模型是非参数模型。“非参数”意味着模型没有可以从数据中学习的参数。因此,做这项工作不需要培训。在本章的后面,将添加一些参数。
Y = X (等式 2-1)
在常规的 ML 流水线中,在建立一个模型之后,我们必须测试它。在传统问题中,会有更多的样本,数据会被分成训练集和测试集。在训练模型之后,基于训练数据开始测试。如果它在训练数据上做得很好,那么我们可以在看不见的测试数据上逐步测试它。这是因为,如果一个模型不能很好地处理它所训练的数据,那么对于看不见的数据,情况可能会更糟。总之,我们的例子使我们摆脱了这样的工作,因为它只有一个样本,不需要培训。但是没有训练阶段并不意味着没有测试阶段。让我们基于这样一个样本来测试我们的模型。
测试阶段检查模型预测未知样本而非训练样本输出的准确性。基于 X=2 的示例,当应用于模型时,它也将返回 2。这是因为输入总是等于输出。除了预测和期望输出的位置,线性模型如图 2-1 所示。
图 2-1
非参数线性模型的预测和期望输出
我们可以简单地将期望输出和预测输出之间的差值作为等式 2-2。差值为 26 = 4。
误差 = 预测—期望(等式 2-2)
误差的存在意味着我们必须改变模型中的某些东西以减少误差。回头看看方程 2-1 中的模型,我们看到没有我们可以改变的参数。这个等式只有我们无法改变的输入和输出。因此,我们可以给这个等式添加一个参数**,这有助于输入和输出之间的映射。方程 2-3 显示了修正的模型方程。**
**Y = aX (方程式 2-3)
假设 a 的初始值为 1.5 。该方程在 2-3ʹ.方程中给出这样的线性模型如图 2-2 所示。
Y = 1.5 X (方程式 2-3’)
图 2-2
参数线性模型
请注意,添加参数后,模型现在是参数化模型。这是因为至少有一个参数需要从数据中学习。现在,在建立了新的模型之后,我们可以预测样本的输出。预测产量为 Y 预测 = 1.5(2) = 3。然后我们可以测量误差。根据等式 2-2,误差为 36 = 3。与先前的误差相比,与没有参数的先前模型相比,具有参数 a = 1.5 的新模型似乎增强了结果。但是预测仍然有误差,我们需要减少。
我们可以想象方程 2-1 中的第一个模型实际上是用方程 2-3 来表示的,但是参数总是设置为 1 。比较 a = 1 时产生的误差和*a =1.5时的误差,也就是—4时的误差,似乎在a =*1.5为 3 时误差减小了。人们可能想知道值 3 怎么会小于4。答案是,误差中的负号只是说预测输出低于期望输出。差异量是误差的绝对值。也就是说,4的误差意味着期望输出与预测输出之间存在 4 的差值,并且期望输出低于预测输出,因为误差为负。注意,改变方程 2-2 中预测输出和期望输出的位置将改变误差的符号。现在让我们回到我们的问题上来。
******当 a = 1.5 时,结果比 a = 1.0 更好,这意味着增加该参数的值将减少误差。因此,我们知道变化的方向。我们试试用 a = 2.0。预测输出会是 Y 预测 = 2.0(2) = 4。这种情况下的误差将等于 46 = 2。误差比以前减少了很多。
根据前面的结果,我们可以推导出参数和误差之间的关系。使用 a = 1,误差为 4。将参数( a = 1.5)增加 0.5,误差减少 1.0 至 3。参数( a = 2.0)再加 0.5,误差减少 1.0,为 2。因此,将参数增加 0.5 会将误差减少 1.0 倍。因此,我们可以给参数加 1.0 来完全消除它,参数将是 a = 3.0。这种情况下的预测输出为 Y 预测 = 3.0(2) = 6。误差将为 66 = 0。误差现在为 0,当 a = 3.0 时,我们达到了最佳结果。
让我们对表 2-1 中的示例进行更改,除了使用新的示例之外,还将输出的输出从 6 更改为 6.5。基于等式 2-3,其中 a = 3.0,第一个样本的预测输出为 Y 预测 = (3.0)2 = 6.0,第二个样本的预测输出为Y**=(3.0)3 = 9.0。因此,总误差等于(6.0 6.5)+(9.0 9.5)= 1.0(表 2-2 )。如何减少这类错误?
*表 2-2
双样本回归问题
|输入(X)
|
输出(Y)
| | --- | --- | | Two | Six point five | | three | Nine point five |
我们遵循的程序是改变参数的值,直到将误差减小到 0。表 2-3 显示了两个参数值的总误差。似乎 3.0 和大于等于 3.0 的值都不能消除误差。对应于 a = 2.5、 a = 3.0、 a = 3.5 的模型以及期望输出如图 2-3 所示。
图 2-3
多参数线性模型。虚线对应于 a=2.5 的模型,星号线对应于 a=3.5,实线对应于 a=3.0。
表 2-3
双样本回归问题
|参数
|
输出(Y)
|
预测
|
错误
|
总误差
| | --- | --- | --- | --- | --- | | Three point five | Six point five | Seven | 7.0−6.5=1.0 | Two | | Nine point five | Ten point five | 10.5−9.5=1.0 | | Two point five | Six point five | Five | 5.0−6.5=−1.5 | −4.0 | | Nine point five | Seven point five | 7.5−9.5=−2.5 |
事实是,在我们的例子中,没有使误差等于 0 的参数值。我们想要一个乘以 2 得到 6.5,乘以 3 得到 9.5 的值。不可能找到这样的值。满足第一个样本的参数值为 a = 3.25,而对于第二个样本,参数值为 a = 3.17。因此,在模型的当前形式上,达到 0 的误差是不可能的。由于这个原因,偏差在解决这种情况中起着重要的作用。
我们可以像等式 2-4 一样,在等式 2-3 中添加一个偏差 b 。这种偏见能够解决我们的问题。
Y = aX + b (等式 2-4)
但是问题的复杂性现在增加了。我们试图找到两个参数(a,b)的值。基于之前的结果,当 a = 3.0 时,两个样本的预测输出分别为 6.0 和 9.0。预测输出比正确输出小 0.5。因此, b = 0.5 的值就是我们要找的。因此, a = 3.0 和 b = 0.5 将给出 0 的误差。这就是偏见如此重要的原因。
偏差允许我们在 y 轴上自由移动线性模型,同时增加拟合数据的可能性,而不仅仅是在 x 轴上移动它。请注意,它在我们的示例中非常有用,因为参数较少。当模型参数较多时,偏差可以忽略。
扩展表 2-2 中的示例,有一个新的输入 Z 添加到问题中,新数据在表 2-4 中。因为有两个输入和一个输出,等式 2-4 中的先前模型将不起作用,我们必须添加新的输入及其相关参数。等式 2-5 代表了新的模型。
表 2-4
双输入单输出回归问题
|输入(X)
|
输入(Z)
|
输出(Y)
| | --- | --- | --- | | Two | One point one | Six point five | | three | Zero point eight | Nine point five |
Y=aX+cZ+b(等式 2-5)
现在,除了偏差 b 之外,我们还必须找到两个参数 a 和 c 的最佳值。之前使用的相同程序将应用于这个问题,以找到这些变量的最佳值。
通过创建简单的线性模型,我们已经成功地了解了人工神经网络的组成部分是如何工作的。人工神经网络由多个这样的线性模型组成,这些模型连接在一起以适应一个问题。以下部分将解释如何通过将线性模型连接在一起来设计网络。下一小节讨论如何为之前创建的模型绘制 ANN。
图形人工神经网络
人工神经网络是通过将多个线性模型连接在一起而构建的。随着每个模型中所需参数数量的增加,网络的完整方程变得过于复杂。因此,很难将问题表示为方程,但更简单的方法是将网络可视化为图形。网络图更容易理解和设计。在这里,我们将学习如何构建网络图,从线性模型开始。
ANN 是生物神经网络的人工表示。我们可以这样开始:人工神经网络的基本构件是人工神经元。在本章前面,我们也说过人工神经网络的基础是线性模型。因此,我们可以推断,神经元实际上是一个线性模型。与线性模型一样,神经元接受输入,进行一些处理,如乘法和加法,最后返回输出。图 2-4 显示了等式 2-4 中的线性模型和人工神经元之间的映射。注意,在线性模型中存在的所有变量也存在于 ANN 图中。这种人工神经网络被称为单层感知器。
图 2-4
从单输入线性模型到人工神经网络图的映射
我们可以从图形的核心开始,也就是带有文字“Math”的圆圈。这个圆圈代表神经网络的神经元。神经元是一个计算单元,它进行的计算类型是将每个输入乘以其相应的参数,将所有结果相加,然后返回表示乘积和(SOP)的输出。由于这个原因,输入 X 被连接到那个神经元。
因为每个参数必须与其输入相关联以计算 SOP,所以用于输入 X 的参数写在将其连接到神经元的箭头上方。使每个参数靠近其输入有助于找到与每个输入相关联的参数。这是针对输入及其参数的。基于当前的例子,这个想法可能不清楚,因为只有一个输入,但是稍后会更清楚。让我们转向偏见。
**在神经元之后使用新的块来将偏置 b 添加到 SOP。SOP 加上bT7 后,产生输出 Y 。到目前为止,一切都很好,但我们仍然可以使图形更简单。
在之前的讨论中,我们将偏差与输入区别对待。每个参数都乘以其输入,但偏差没有要乘以的输入。我们可以假设偏置有一个输入始终等于 + 1 。这大大简化了过程,因为我们可以消除神经元后添加的偏置模块,如图 2-5 所示。神经元将每个参数乘以其相关的输入,并同样对待偏差。它将被视为输入为 + 1 的参数。为了使偏差不同于常规参数,可以垂直添加偏差,同时在图表中水平添加其他参数。
图 2-5
从具有一个输入的线性模型映射到具有偏差的 ANN 图,通过将偏差与+1 的输入相关联,将偏差视为常规参数
根据前面的例子,我们知道如何从神经网络的角度绘制线性方程。现在,我们可以使用方程 2-5,其中有两个输入。唯一的变化是将新的输入 、Z 及其相关参数 、c 添加到图形中,类似于我们对输入 X 及其参数 a 所做的操作。新图如图 2-6 所示。对于每个新的输入,该过程重复进行。
图 2-6
从具有两个输入的线性模型到 ANN 图的映射
简而言之,人工神经网络中的神经元接受一组输入,将每个输入乘以相关参数,将乘法结果相加,最后返回输出。在人工神经网络中,神经元排列成三种类型的层:输入层、隐藏层和输出层。这种安排在生物神经网络中并不存在,但它有助于我们组织网络。图 2-7 显示了具有这三层的一般全连接(FC)人工神经网络的架构。该网络按照三层来组织。网络只有一个输入和输出层,但它可以有多个隐藏层。注意,每一层内的神经元都是根据它来命名的。也就是说,输入层内的神经元称为输入神经元,而隐藏神经元是隐藏层内的神经元。
图 2-7
通用 FC 人工网络架构
为简单起见,所有输入被赋予符号 X ,所有输出被赋予符号 O ,带有定义输入或输出的索引的下标。网络有 n 个 输入,其中X1是第一个输入,X5是第五个输入,依此类推,直到Xn它还有 m 个输入,其中 O 个 1 个 是第一个输入, O 个 5 个 是第五个输入,依此类推直到 O 个 m 个
****隐藏层中的神经元被赋予具有两个指数的符号,以反映其层指数以及在其层中的位置。例如,第一个隐藏层有 k 个神经元,其中是第一个隐藏层的第一个隐藏神经元,
是第二个隐藏层的第五个隐藏神经元,以此类推,直到
**,**是第 r th 隐藏层的第 p th 个隐藏神经元。
在每两层之间,有许多参数等于两层内神经元数量的乘积。例如,如果输入层有 n 个神经元,第一个隐层有 k 个神经元,那么连接它们所需的参数个数等于 n × k **,**其中参数是指输入层中第 n 个神经元和第 k 个神经元之间的参数这个参数也可以称为权重,因为每个参数反映了其相关输入的重要性。参数值越大,其相关输入就越重要。
到目前为止,预计对 ANN 有一个基本的了解,但还需要了解更多。接下来的几节涵盖了一些关于人工神经网络的重要概念,这些概念对于人工神经网络的成功构建至关重要。
调整训练人工神经网络的学习率
新手学习 ann 的一个障碍是学习速度。我曾多次被问到学习速度对人工神经网络训练的影响。我们为什么要用学习率?学习率的最佳值是多少?在这一节中,我将用一个例子来说明学习率对于训练一个人工神经网络是多么有用,从而使事情变得简单。让我们从解释使用的例子开始。
过滤器示例
一个非常简单的例子可以让我们摆脱复杂性,专注于我们的目标,即学习率。这个例子用等式 2-6 表示。
(方程式 2-6)
如果输入等于或小于 250,那么输出将与输入相同。如果输入大于 250,那么它将被剪裁,输出将是 250。它的工作原理就像一个过滤器,只让 250 以下的输入通过,而将其他输入截止到 250。其图形如图 2-8 所示。
图 2-8
过滤器示例的激活功能
六个样本的数据如表 2-5 所示。
表 2-5
用于训练网络的数据,以过滤输入,了解学习率如何影响训练过程
|输入(X)
|
输出(Y)
| | --- | --- | | Sixty | Sixty | | Forty | Forty | | four hundred | Two hundred and fifty | | Three hundred | Two hundred and fifty | | -50 | -50 | | -10 | -10 |
人工神经网络架构
所用人工神经网络的架构如图 2-9 所示。只有输入层和输出层。输入层只有一个神经元用于我们的单一输入。输出层只有一个神经元来产生输出。输出层神经元负责将输入映射到正确的输出。还有一个偏置施加到输出层神经元,值为 b ,输入为 +1 。还有一个用于输入的权重 W 。
图 2-9
与过滤器示例一起使用的 ANN 架构
激活功能
基于前面讨论的网络,我们只能近似线性函数,如图 2-1 。但是我们的问题使用了一个非线性函数,如图 2-8 所示。我们如何使用人工神经网络来表示这种类型的网络?本例中的解决方案是使用一个函数,如 ANN 中的激活函数。
人工神经网络可以逼近线性和非线性函数。人工神经网络将非线性纳入其计算的方式是通过激活函数。激活函数在 ANN 图中的位置是在 SOP 计算之后。在这种情况下,神经元的输出将是激活函数输出,而不仅仅是 SOP。这就是为什么在等式 2-6 中网络输出被设置为等于激活函数输出。
Python 实现
清单 2-1 中给出了实现整个网络的 Python 代码。在讨论了它的每个部分并使其尽可能简单之后,我们将集中讨论改变学习率如何影响网络训练。
1 import numpy
2
3 def activation_function(inpt):
4 if(inpt > 250):
5 return 250 # clip the result to 250
6 else:
7 return inpt # just return the input
8
9 def prediction_error(desired, expected):
10 return numpy.abs(numpy.mean(desired-expected)) # absolute error
11
12 def update_weights(weights, predicted, idx):
13 weights = weights + 0.00001*(desired_output[idx] - predicted)*inputs[idx] # updating weights
14 return weights # new updated weights
15
16 weights = numpy.array([0.05, .1]) #bias & weight of input
17 inputs = numpy.array([60, 40, 100, 300, -50, 310]) # training inputs
18 desired_output = numpy.array([60, 40, 150, 250, -50, 250]) # training outputs
19
20 def training_loop(inpt, weights):
21 error = 1
22 idx = 0 # start by the first training sample
23 iteration = 0 #loop iteration variable
24 while(iteration < 2000 or error >= 0.01): #while(error >= 0.1):
25 predicted = activation_function(weights[0]*1+weights[1]*inputs[idx])
26 error = prediction_error(desired_output[idx], predicted)
27 weights = update_weights(weights, predicted, idx)
28 idx = idx + 1 # go to the next sample
29 idx = idx % inputs.shape[0] # restricts the index to the range of our samples
30 iteration = iteration + 1 # next iteration
31 return error, weights
32
33 error, new_weights = training_loop(inputs, weights)
34 print('--------------Final Results----------------')
35 print('Learned Weights : ', new_weights)
36 new_inputs = numpy.array([10, 240, 550, -160])
37 new_outputs = numpy.array([10, 240, 250, -160])
38 for i in range(new_inputs.shape[0]):
39 print('Sample ', i+1, '. Expected = ', new_outputs[i], ' , Predicted = ', activation_function(new_weights[0]*1+new_weights[1]*new_inputs[i]))
Listing 2-1Adjusting Learning Rate for Successful ANN Training
第 17 行和第 18 行负责创建两个数组(inputs 和 desired_output ),用于保存我们示例中的训练输入和输出数据。第 16 行创建了一个网络参数数组,它们是输入参数和偏差。它们被随机初始化为 0.05 的偏置和 0.1 的输入。第 3 行到第 7 行使用 activation_function(inpt)方法实现激活函数本身。它接受作为输入的单个参数,并返回作为网络预测输出的单个值。
因为预测可能会有误差,所以我们需要测量一下,才能知道我们离正确的预测有多远。因此,在第 9 行和第 10 行中实现了一个名为 prediction_error(desired,expected)的方法,它接受两个输入:期望的和预测的输出。该方法只是计算每个期望输出和预测输出之间的绝对差值。任何误差的最佳值肯定是 0。这是最佳值。
如果出现预测误差怎么办?在这种情况下,我们必须对网络进行更改。但是到底要改变什么呢?必须改变的是网络参数。为了更新网络参数,在第 13 和 14 行中定义了一个名为 update_weights(weights,predicted,idx)的方法。它接受三个输入:旧权重、预测输出和具有错误预测的输入的索引。等式 2-7 用于更新权重。
(方程式 2-7)
在哪里
-
η–学习率
-
d–期望输出
-
Y–预测输出
-
X–输入
-
W(n)–当前重量
-
W(n+1)——更新权重
该等式使用当前步骤 n 的权重来生成下一步骤的权重( n + 1 )。这个等式有助于我们理解学习速度是如何影响学习过程的。
最后,我们需要将所有这些连接在一起,使网络能够学习。这是使用从第 20 行到第 31 行定义的 training_loop(inpt,weights)方法完成的。它进入一个训练循环。该循环用于以最小的可能预测误差将输入映射到它们的输出。该循环执行三项操作:
-
产量预测。
-
错误计算。
-
更新权重。
既然我们已经对这个例子和它的 Python 代码有了一个概念,现在让我们来看看学习率对于获得最佳结果是如何有用的。
学习率
在前面讨论的清单 2-1 的例子中,第 13 行有权重更新等式,其中使用了学习率。让我们从等式中去掉学习率。具体如下:
weights = weights + (desired_output[idx] - predicted)*inputs[idx]
我们来看看去掉学习率的效果。在训练循环的第一次迭代中,网络的偏差和权重的初始值分别为 0.05 和 0.1。输入是 60,期望输出是 60。第 25 行的预期输出,即激活函数的结果,将是 activation _ function(0.05(+1)+0.1(60))。预测产量为 be 6.05。在第 26 行,通过得到期望输出和预测输出之间的差来计算预测误差。误差为 ABS(60 6.05)= 53.95。然后在第 27 行,权重将根据前面的等式进行更新。新的权重是[0.05,0.1] + (53.95)*60 = [0.05,0.1] + 3237 = [3237.05,3237.1]。似乎新的权重与以前的权重相差太大。每个重量增加了 3,237,这太大了。但是让我们继续做下一个预测。
在下一次迭代中,网络将拥有这些数据(b=3237.05,W=3237.1,输入=40,期望输出= 40)。预期输出将是 activation _ function((3237.05+3237.1(40))= 250。预测误差将为 ABS(40250)= 210。误差非常大。这个误差比之前的 53.95 要大。因此,我们必须再次更新权重。根据上式,新权重为[3237.05,3237.1]+(210)* 40 =[3237.05,3237.1]+8400 =[5162.95,5162.9]。表 2-6 总结了前三次迭代的结果。
表 2-6
训练滤波器网络的前三次迭代的结果
|预报
|
错误
|
更新值
|
新重量
| | --- | --- | --- | --- | | Six point zero five | Fifty-three point nine five | 3237.0 | [3237.05, 3237.1] | | Two hundred and fifty | Two hundred and ten | —8400 | [–5162.95, –5162.9 ] | | −521452.95 | Five hundred and twenty-one thousand five hundred and fifty-two point nine five | 52155295.0 | [52150132.04999999, 52150132.09999999] | | −2555356472.95 | Two billion five hundred and fifty-five million three hundred and fifty-six thousand four hundred and twenty-two point nine five | —127767821147.0 | [–1.27715671 e+11,–1.27715671 e+11] |
随着我们进行更多的迭代,结果会变得更糟。权重的大小变化很快,有时甚至改变符号。它们从非常大的正值变为非常大的负值。我们怎样才能阻止这些巨大而突然的重量变化呢?如何缩小权重更新的值?
如果我们从表 2-6 中查看重量变化的值,该值似乎非常大。这意味着网络高速改变其权重。我们只需要让它慢下来。如果我们能够降低这个值,那么一切都会好的。但是怎么做呢?回到代码,看起来更新等式是生成如此大的值的原因,特别是这一部分:
(desired_output[idx] - predicted)*inputs[idx]
我们可以通过将其乘以一个小值(如 0.1)来缩放该部分。因此,在第一次迭代中,不是生成 3237.0 作为更新值,而是减少到 323.7。我们甚至可以将这个值降低到 0.001。使用 0.001,更新值仅为 3.327。
我们现在可以抓住它了。这个值就是学习率。为学习率选择小的值使得权重更新的速率更小,并且避免突然的变化。值越大,变化越快,这会产生不好的结果。
但是对于学习率来说什么是最好的 值 ?
对于学习率来说,没有一个特定的值可以说是最佳值。学习率是一个超参数。超参数的值由实验确定。我们尝试不同的值,并使用给出最佳结果的值。
测试网络
对于我们的问题,使用值. 00001 就可以了。用那个学习率训练完网络,就可以做个测试了。表 2-7 显示了四个新测试样本的预测结果。使用学习率后,现在的结果似乎好了很多。
表 2-7
测试样本预测结果
|投入
|
输出量的希望值
|
预测产量
| | --- | --- | --- | | Ten | Ten | Ten point eight seven | | Two hundred and forty | Two hundred and forty | Two hundred and thirty-nine point one three | | Five hundred and fifty | Two hundred and fifty | Two hundred and fifty | | –160 | –160 | –157.85 |
现在我们能够理解学习速度决定了我们前进的步伐。步长越大,变化越突然。我们可能接近最佳解,只需要稍微改变我们的参数来达到它,但是忽略或使用学习率的坏值会使我们远离解。
使用反向传播的权重优化
在上一节中,我们使用学习率来更新人工神经网络的权重。在本节中,我们将使用反向传播算法来完成这项工作,并推导出它如何优于仅使用学习率。用两个例子对算法进行了数值说明。
本节不会直接深入反向传播算法的细节,而是从训练一个非常简单的网络开始。这是因为反向传播算法意味着在训练后应用于网络。因此,我们应该在应用它之前训练网络,以获得反向传播算法的好处以及如何使用它。读者应该对人工神经网络的工作原理、偏导数和多元链式法则有一个基本的了解。
无隐层神经网络的反向传播
从一个简单的例子开始,图 2-10 显示了它的网络结构,我们将用它来解释反向传播算法是如何工作的。它只有两个输入,符号化为X1和X2。输出层只有一个神经元,没有隐藏层。每个输入都有相应的权重其中W1和W2是权重为**1和X2输出层神经元有一个偏置,值为 b ,固定输入值为 + 1 。**
**
图 2-10
训练和应用反向传播的网络结构
输出层神经元使用由等式 2-8 定义的 sigmoid 激活函数:
(方程式 2-8)
其中 s 为每个输入与其对应权重之间的 SOP。 s 是激活函数的输入,在本例中,如等式 2-9 所定义。
s=【w】
****表 2-8 显示了用作训练数据的单个输入及其相应的期望输出。这个例子的基本目标不是训练网络,而是理解如何使用反向传播来更新权重。现在,为了集中于反向传播,我们将分析单个数据记录。
表 2-8
第一反向传播示例的训练数据
|X?? 1
|
X2
|
期望输出
| | --- | --- | --- | | 0.1 | 0.3 | 0.03 |
假设权重和偏差的初始值如表 2-9 所示。
表 2-9
网络的初始参数
|W1
|
W2
|
b
| | --- | --- | --- | | 0.5 | 0.2 | 1.83 |
为简单起见,所有输入、权重和偏差的值将被添加到网络图中,如图 2-11 所示。
图 2-11
添加了输入和参数的第一反向传播示例的网络
现在,让我们训练网络,看看是否会根据当前的权重和偏差返回所需的输出。激活函数的输入将是每个输入与其权重之间的 SOP。然后,偏差将被添加到总数中,如下所示:
激活函数的输出将通过将先前计算的 SOP 应用于所用函数(sigmoid)来计算,如下所示:
激活函数的输出反映了当前输入的预测输出。很明显,期望输出和期望输出之间存在差异。但是这种差异的来源是什么呢?应该如何改变预测的输出以更接近期望的结果?这些问题后面会回答。但至少,让我们看到我们的神经网络基于一个误差函数的误差。
误差函数表明预测输出与期望输出的接近程度。误差的最佳值为零,这意味着根本没有误差,并且期望的和预测的结果是相同的。误差函数之一是平方误差函数,如等式 2-10 所示。
(方程式 2-10)
注意,加在方程上的是为了以后简化导数。我们可以按如下方式测量网络误差:
结果保证了大误差的存在( ~0.357 )。这是错误所告诉我们的。它只是给了我们一个指示,告诉我们预测的结果离期望的结果有多远。既然我们知道如何测量误差,我们需要找到一种方法来最小化它。我们唯一能玩的参数是重量。我们可以尝试不同的权重,然后测试我们的网络。
权重更新方程
权重可以根据等式 2-7(用于上一节)来改变,其中
-
n :训练步骤(0,1,2,…)。
-
W(n):当前训练步的重量。
-
【w】(=【b】【n】-我...。,【w】【m】)]****
***** η :网络学习率。
* ***d***(***n***):期望输出。
* ***Y***(***n***):预测产量。
* ***X***(***n***):网络做出错误预测的当前输入。****
****对于我们的网络,这些参数具有以下值:
-
n : 0
-
W(n):【1.83,0.5,0.2】
-
η :超参数。比如我们可以选择 0.01。
-
d(n):【0.03】。
-
Y(n):【0.874352143】。
-
X(n):[+1,0.1,0.3]。第一个值(+1)是偏差。
我们可以基于前面的等式更新我们的神经网络权重:
新的重量在表 2-10 中给出。
表 2-10
第一反向传播示例的网络的更新权重
|W 1 新
|
W 2 新
|
b 新
| | --- | --- | --- | | 0.197466943 | 0.499155648 | 1.821556479 |
基于新的权重,我们将重新计算预测输出,并继续更新权重和计算预测输出,直到达到手头问题的可接受误差值。
这里,我们成功地更新了权重,而没有使用反向传播算法。我们还需要那个算法吗?是的。接下来将解释原因。
为什么反向传播算法很重要?
对于最佳情况,假设权重更新方程产生最佳权重;现在还不清楚这个函数实际上做了什么。它就像一个黑匣子,因为我们不了解它的内部运作。我们只知道,万一出现分类错误,我们应该应用这个等式。然后,该函数将生成新的权重,用于接下来的训练步骤。但是为什么新的权重更擅长预测呢?每个权重对预测误差有什么影响?增加或减少一个或多个权重如何影响预测误差?
需要更好地理解如何计算最佳权重。为此,我们应该使用反向传播算法。它帮助我们理解每个权重如何影响 NN 总误差,并告诉我们如何将误差最小化到非常接近零的值。
向前传球和向后传球
在训练一个神经网络时,有前向和后向两个通道,如图 2-12 。第一遍永远是正向传递,输入应用到输入层,向输出层移动,计算输入和权重之间的 SOP,应用激活函数生成输出,最后计算预测误差,就知道当前网络的精度有多高。
图 2-12
神经网络训练的前向和后向途径
但是如果有预测误差呢?我们应该修改网络以减少错误。这是在反向传递中完成的。在前向传递中,我们从输入开始,直到计算预测误差。但是在反向传递中,我们从错误开始,直到到达输入。这一步的目标是了解每个权重如何影响总误差。知道权重和误差之间的关系允许我们修改网络权重以减小误差。比如在后向传递中,我们可以得到有用的信息,比如将W1的当前值增加 1.0,预测误差就会增加 0.07。这有助于我们理解如何选择***【W】***1的新值,以使误差最小化(W1不应增加)。
偏导数
向后传递中使用的一个重要操作是计算导数。在开始计算反向传递中的导数之前,我们可以从一个简单的例子开始,让事情变得简单一些。
对于一个多元函数,如Y=X2Z+H,给定变量 X 的变化对输出 Y 有什么影响?这个问题用偏导数来回答。它是这样写的:
注意,除了 X 之外的一切都被视为常数。这就是为什么 H 在计算偏导数后被替换为 0。这里, ∂X 表示变量 X 的微小变化, ∂Y 表示 Y 的微小变化。 Y 的变化是 X 变化的结果。通过对 X 做一个很小的改动,对 Y 有什么影响?微小的变化可以是增加或减少一个微小的值,例如 0.01。通过代入不同的 X 值,我们可以发现 Y 相对于 X 如何变化。
将遵循相同的程序,以便了解 NN 预测误差如何相对于网络权重的(wrt)变化而变化。所以,我们的目标是计算和
,因为我们只有两个权重:W1和W2。我们来计算一下。
预测误差权重的变化
看这个等式,Y = X2Z+H,计算偏导数似乎很简单,因为有一个等式同时关联 Y 和 X 。但是在预测误差和权重之间没有直接的等式。这就是为什么我们要用多元链式法则来求 Y wrt X 的偏导数。
重量链的预测误差
让我们试着找出预测误差与权重之间的联系。预测误差是根据等式 2-10 计算的。
但是这个方程没有任何权重。没问题:我们可以按照前一个方程的每个输入进行计算,直到我们得到权重。期望的输出是一个常数,因此不可能通过它达到权重。预测输出基于 sigmoid 函数计算,如等式 2-8 所示。
同样,计算预测产量的等式没有任何权重。但是仍然有变量 s (SOP),根据等式 2-11,其计算已经依赖于权重。
s=【w】
****图 2-13 显示了计算重量时应遵循的计算链。
图 2-13
从预测误差开始计算权重的计算链
因此,要知道预测误差如何随权重的变化而变化,我们应该做一些中间运算,包括找出预测误差如何随预测输出的变化而变化。然后,我们需要找到预测产量和 SOP 之间的关系。最后,我们将通过改变权重来发现 SOP 是如何变化的。有如下四个中间偏导数:
、
、
和
这个链将最终告诉预测误差如何随着每个权重的变化而变化,这是我们的目标,通过将所有单独的偏导数相乘,如下所示:
重要说明
目前,还没有将预测误差与网络权重直接联系起来的等式,但是我们可以创建一个将它们联系起来的等式,并对其直接应用偏导数。这是等式 2-12。
(方程式 2-12)
因为这个方程看起来很复杂,为了简单起见,我们可以使用多元链式法则。
计算链偏导数
让我们计算之前创建的链的每个部分的偏导数。
误差预测输出偏导数:
通过值替换,
预测产量- SOP 偏导数:
请记住,商法则可用于计算 sigmoid 函数的导数,如下所示:
通过值替换,
**SOP-**W1偏导数:
通过值替换,
**SOP-**W2偏导数:
通过值替换,
在计算出每个单独的导数后,我们可以将它们相乘,从而得到预测误差和每个权重之间的关系。
**预测误差-**W1偏导数:
**预测误差-**W2偏导数:
最后,有两个值反映预测误差如何相对于权重变化(对于 W 1 为 0.009276093,对于 W 2 为 0.027828278)。但这意味着什么呢?结果需要解释。
解释反向传播的结果
从下面得到的最后两个导数中的每一个都有两个有用的结论:
-
导数符号
-
导数大小
如果导数为正,这意味着增加权重会增加误差,同样,减少权重会减少误差。如果导数是负的,那么增加权重将减少误差,相应地,减少权重将增加误差。
但是误差会增加或减少多少呢?DM 可以告诉我们。对于正导数,增加 p 的权重会增加DM∫p的误差。对于负导数,增加权重 p 将减少误差DM∫p。
因为求导的结果是正的,这意味着如果W1增加 1 那么总误差将增加 0.009276093。同样,因为
导数的结果是正的,这意味着如果**2增加 1,那么总误差将增加 0.027828278。**
**#### 更新权重
在成功计算出误差相对于每个单独权重的导数之后,我们可以更新权重以增强预测。每个权重将根据其导数进行更新,如下所示:
对于第二重量,
注意,导数是减去的,而不是加到重量上,因为它是正的。
然后,继续预测和更新权重的过程,直到产生具有可接受误差的期望输出。
隐层神经网络的反向传播
为了使思路更加清晰,我们可以在添加一个具有两个神经元的隐藏层之后,在下面的 NN 上应用反向传播算法。新网络如图 2-14 所示。
图 2-14
第二反向传播示例的网络架构
先前使用的相同输入、输出、激活函数和学习率也将应用于本例。以下是网络的完整权重:
|W1
|
W2
|
W3
|
W4
|
W5
|
W6
|
b1
|
b2
|
b3
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 0.5 | 0.1 | 0.62 | 0.2 | ***—***0.2 | 0.3 | 0.4 | ***—***0.1 | 1.83 |
图 2-15 显示了添加了所有输入和权重的先前网络。
图 2-15
在添加输入和参数值之后的第二反向传播示例的网络架构
首先,我们应该通过正向传递来获得预测的输出。如果预测有错误,那么我们应该根据反向传播算法通过反向传递来更新权重。让我们计算隐层中第一个神经元的输入(1):
**
对隐层中第二个神经元的输入(2):
**
隐藏层的第一个神经元的输出:
以及隐藏层的第二神经元的输出:
下一步是计算输出神经元的输入:
输出神经元的输出:
因此,基于当前权重,我们的神经网络的预期输出是 0.865。然后,我们可以根据以下等式计算预测误差:
误差似乎非常大,因此我们应该使用反向传播算法来更新网络权重。
偏导数
我们的目标是得到总误差 E 如何改变 wrt 的六个权重(W1:W6):
、
、
、
、
、
让我们从计算隐藏输出层权重的输出的偏导数开始(W5和W6)。
EWT55偏导数:
从W5开始,我们将遵循这样的链条:
我们可以首先计算每个单独的部分,然后将它们组合起来得到所需的导数。
对于一阶导数:
通过替换这些变量的值,
对于二阶导数:
对于最后一个导数:
计算完所有三个所需的导数后,我们可以计算目标导数,如下所示:
E***—W***6偏导数:
为了计算,我们将使用以下链:
将重复相同的计算,仅改变最后一个导数。它可以计算如下:
最后,可以计算导数:
这是给 W 5 和W6 的。我们来计算一下对W1 到W4 的导数 wrt。
E*—WT51偏导数:*
从 W 1 开始,我们将遵循这个链条:
我们将按照前面的程序,计算每个单独的导数,最后将它们全部组合起来。前面已经计算了前两个导数,结果如下:
对于下一个导数:
对于:
对于:
最后,可以计算目标导数:
E***—W***2偏导数:
类似于计算的方法,我们可以计算
。唯一的变化将是在最后一个衍生物
。
然后:
后两个权重( W 3 和 W 4 )的计算方法与 W 1 和 W 2 类似。
E*—WT53偏导数:*
从 W 3 开始,我们应该遵循这个链条:
需要计算的缺失导数是。
对于:
对于:
最后,我们可以计算所需的导数,如下所示:
EWT54偏导数:
我们现在可以类似地计算:
我们应该计算缺失的导数:
然后计算:
更新权重
至此,我们已经成功地根据网络中的每个权重计算出了总误差的导数。下一步是根据导数更新权重并重新训练网络。更新后的权重将按如下方式计算:
过度拟合
您是否曾经创建过一个 ML 模型,它对于训练样本来说是完美的,但是对于看不见的样本却给出了非常糟糕的预测?你想过为什么会这样吗?原因可能是过度拟合。有过拟合问题的模型对训练样本的预测很好,但对验证数据的预测很差。这是因为该模型使其自身适应训练数据中的每条信息,直到收集到只能在训练数据中找到的一些属性。让我们试着理解这个问题。
ML 的重点是用训练数据训练算法,以便创建能够对看不见的数据(测试数据)做出正确预测的模型。例如,为了创建分类器,人类专家将从收集训练 ML 算法所需的数据开始。人类负责寻找最佳类型的特征,这些特征能够区分不同的类别,以便代表每个类别。这些特征将用于训练 ML 算法。假设我们要建立一个 ML 模型,将图 2-16 中的图像分类为包含或不包含猫。
图 2-16
训练模型的猫的图像
我们要回答的第一个问题是“使用什么功能最好?”这是 ML 中的一个关键问题,因为使用的特征越好,训练的 ML 模型做出的预测就越好,反之亦然。让我们试着将这些图像形象化,并提取一些代表猫的特征。一些代表性的特征可能是存在两个深色的瞳孔和两个对角线方向的耳朵。让我们假设我们已经以某种方式从前面的训练图像中提取了特征,并且已经创建了训练的 ML 模型。这个模型可以处理各种各样的猫图像,因为所使用的特征存在于大多数猫中。我们可以使用一些看不见的数据来测试模型,如图 2-17 所示。假设测试数据的分类准确率为 x% 。
图 2-17
猫的测试图像
人们可能希望提高分类精度。首先要考虑的是使用比以前更多的功能。这是因为使用的区别特征越多,准确度就越高。通过再次检查训练数据,我们可以发现更多的特征,例如整体图像颜色,因为所有训练猫样本都是白色的,而训练数据中的虹膜颜色是黄色的。特征向量将具有这四个特征:
-
深色瞳孔
-
对角耳朵
-
白色毛皮
-
黄色鸢尾花
它们将用于重新训练 ML 模型。
创建训练好的模型后,下一步是测试它。使用新的特征向量后的预期结果是分类精度将下降到小于 x% 。但是为什么呢?准确性下降的原因是使用了一些已经存在于训练数据中但并非普遍存在于所有 cat 图像中的特征。这些特征并不是所有猫图像的共性。在检测数据中,有些猫的皮毛是黑色或黄色的,而不是训练中使用的白色皮毛。
在我们的例子中,所使用的特征对于训练样本来说是强有力的,但是对于测试样本来说是非常差的,这可以被描述为过度拟合。该模型用一些特征来训练,这些特征是训练数据所独有的,但不存在于测试数据中。
前面讨论的目的是通过使用一个高层次的例子来简化过度拟合的概念。要了解细节,最好用一个更简单的例子。这就是为什么接下来的讨论将基于回归示例。
基于回归示例理解正则化
假设我们想要创建一个回归模型来拟合图 2-18 中所示的数据。我们可以使用多项式回归。
图 2-18
拟合回归模型的数据
我们可以从一个一次多项式方程的线性模型开始,如方程 2-13 所示。
y1= f1(x)=θ1x+θ0(等式 2-13)
其中θ0和θ1是模型参数, x 是唯一使用的特征。
前一型号的图如图 2-19 所示。
图 2-19
使用一级模型拟合数据的初始模型
根据损失函数,如等式 2-14 中的损失函数,我们可以得出结论,该模型不适合数据。
(方程式 2-14)
其中 fI(xI是样本 i 的期望输出,d i 是同一样本的期望输出。
模型过于简单,有很多预测都不准确。由于这个原因,我们应该创建一个更复杂的模型,它可以很好地拟合数据,我们可以将方程的次数从一次增加到二次,如方程 2-15 所示。
y2= f1(x)=θ2x2+θ1x+θ0(等式 2-15)
通过使用相同的特征 x 的 2 次方(x 2 ),我们创建了一个新的特征,我们将不仅捕获数据的线性属性,还捕获一些非线性属性。新模型的图形如图 2-20 所示。
图 2-20
使用更多特征来创建二次模型
该图显示,二次多项式比一次多项式更适合数据。但是二次方程也不太适合一些数据样本。这就是为什么我们可以用方程 2-16 建立一个更复杂的三次模型。该图如图 2-21 所示。
图 2-21
三次模型
y3= f3(x)=θ3x3+2x2
可以注意到,在添加了捕获三次数据属性的新特征之后,模型更好地拟合了数据。为了比以前更好地拟合数据,我们可以将方程的次数增加到四次,如方程 2-17 所示。该图如图 2-22 所示。
y4= f4(x)=θ4x4+3x3
图 2-22
四阶模型
似乎多项式方程的次数越高,就越符合数据。但是有一些重要的问题需要回答。如果通过添加新特征来增加多项式方程的次数可以增强结果,为什么不应该使用非常高的次数,例如 100 次次次?对于一个问题,用什么度最好?
模型容量/复杂性
术语“模型容量/复杂性”指的是模型可以处理的变化程度。容量越高,模型能够应对的变化就越多。第一款 y 1 据说比 y 4 容量小。在我们的例子中,容量随着多项式次数的增加而增加。
当然,多项式方程的次数越高,它就越适合数据。但是请记住,增加多项式次数会增加模型的复杂性。使用容量高于所需容量的模型可能会导致过度拟合。该模型变得非常复杂,并且非常适合训练数据,但是不幸的是对于看不见的数据来说非常弱。ML 的目标是创建一个模型,该模型不仅对训练数据而且对看不见的数据样本都是健壮的。
四度(y 4 的模型很复杂。是的,它很好地适应了可见的数据,但是对于不可见的数据却不是这样。对于这种情况,y 4 中新使用的特征,即x4,捕获了比所需更多的细节。因为这个新特性使得模型过于复杂,我们应该去掉它。
在这个例子中,我们实际上知道要删除哪些特性。所以,我们可以去掉它们,回到之前的三次模型(θ4x4+θ3x3+θ2x2+θ1x+θ0)。但是在实际工作中,我们不知道要删除哪些特征。此外,假设新特性不太糟糕,我们不想完全删除它,只想惩罚它。我们做什么呢
回头看损失函数,唯一的目标是最小化/惩罚预测误差。我们可以设定一个新的目标,尽可能地最小化/惩罚新特性x?? 4 的影响。在修改损失函数来惩罚 x 3 之后,新的在等式 2-18 中。
(方程式 2-18)
我们现在的目标是最小化损失函数。我们现在只对最小化这一项θ4x4感兴趣。显然,为了最小化θ4x4,我们应该最小化θ4,因为它是我们唯一可以改变的自由参数。如果我们想完全去掉这个特征,以防它是一个非常糟糕的特征,我们可以把它的值设置为零,如等式 2-19 所示。
(方程式 2-19)
去掉它,我们回到三次多项式方程(y 3 )。y 3 并不像 y 4 那样完美地拟合可见数据,但一般来说,它会比 y 4 对不可见数据给出更好的性能。
但是如果 x 4 是一个相对较好的特征,我们只是想惩罚它而不是完全删除它,我们可以将它设置为一个接近但不为零的值(比如 0.1),如等式 2-20 所示。通过这样做,我们限制了 x 4 的影响。因此,新模型不会像以前那样复杂。
(方程式 2-20)
回到 y 2 ,好像比 y 3 简单。它可以很好地处理可见和不可见的数据样本。所以,我们应该删除 y 3 中使用的新功能,也就是 x 3 ,或者如果它做得相对好就惩罚它。我们可以修改损失函数来做到这一点,如方程 2-21 所示。
(方程式 2-21)
L1 正则化
注意,我们实际上知道 y 2 是拟合数据的最佳模型,因为数据图对我们来说是可用的。这是一个非常简单的任务,我们可以手动解决。但是,如果我们无法获得这些信息,随着样本数量和数据复杂性的增加,我们将无法轻易得出这样的结论。必须有一些自动的东西来告诉我们哪种程度适合数据,并告诉我们要惩罚哪些特征来获得对看不见的数据的最佳预测。这就是正规化。
正则化有助于我们选择适合数据的模型复杂度。自动惩罚使模型过于复杂的特征是很有用的。请记住,如果特征不差,正则化是有用的,它将帮助我们在相对意义上获得良好的预测;我们只需要惩罚他们,而不是完全消除他们。正则化会惩罚所有使用的要素,而不是选定的子集。之前,我们只惩罚了两个特征,x 4 和 x 3 ,而不是所有的特征。但正规化就不是这样了。
使用正则化,一个新的项被添加到损失函数中以惩罚特征,因此损失函数将如等式 2-22 所示。
(方程式 2-22)
将λ移出总和后,也可以写成等式 2-23。
(方程式 2-23)
新增加的术语用于惩罚特征,以控制模型的复杂程度。在加入正则项之前,我们之前的目标是尽可能减小预测误差。现在我们的目标是最小化误差,但要小心不要让模型太复杂,避免过度拟合。
有一个称为 lambda (λ)的正则化参数,用于控制如何惩罚要素。它是一个没有固定值的超参数。它的值根据手头的任务是可变的。随着其值的增加,将有更高的惩罚功能。因此,模型变得更简单。当它的值降低时,特征的惩罚将降低,因此模型复杂性增加。值为零表示完全不移除特征。
当λ为零时,那么θj的值根本不会被罚,如下式所示。这是因为将λ设置为零意味着去除正则化项,只留下误差项。因此,我们的目标将返回到将误差最小化到接近于零。当以误差最小化为目标时,模型可能会过拟合。
但是当惩罚参数λ的值很高时(比如 10 9 ,那么为了使损失保持在最小值,参数θj必须有很高的惩罚。因此,参数θj将为零。因此,模型(y 4 )的θI将被修剪,如下所示。
请注意,正则项从 1 开始其索引 j 不为零。实际上,我们用正则项来惩罚特征(xIT5)。因为θ0没有关联特征,所以没有理由惩罚它。这种情况下,模型为 y4=θ0,图形如图 2-23 所示。
图 2-23
惩罚所有特征后平行于 x 轴的模型
设计人工神经网络
人工神经网络的初学者可能会问一些问题,包括以下问题:使用多少个隐藏层是正确的?每个隐藏层有多少个隐藏神经元?使用隐藏层/神经元的目的是什么?增加隐藏层/神经元的数量是否总能得到更好的结果?我很高兴地说,我们可以回答这些问题。明确地说,如果要解决的问题很复杂,回答这样的问题可能会太复杂。在本节结束时,你至少可以知道如何回答这些问题,并能够通过简单的例子来测试自己。我们开始吧。
ANN 的灵感来自于生物神经网络。为简单起见,在计算机科学中,它被表示为一组层。这些层分为三类:输入、隐藏和输出。
知道输入和输出层的数量及其神经元的数量是最容易的部分。每个网络都有单一的输入和输出层。输入层中神经元的数量等于正在处理的数据中输入变量的数量。输出层中神经元的数量等于与每个输入相关联的输出的数量。但是挑战在于知道隐藏层及其神经元的数量。
以下是学习分类问题中隐藏层和每个隐藏层中神经元数量的一些指导原则:
-
根据这些数据,画出一个预期的决策边界来分隔这些类。
-
将决策边界表示为一组线。请注意,这些线的组合必须服从决策边界。
-
选定线的数量表示第一个隐藏层中隐藏神经元的数量。
-
为了连接由前一层创建的线,添加了一个新的隐藏层。请注意,每次需要在前一个隐藏层中的线条之间创建连接时,都会添加一个新的隐藏层。
-
每个新隐藏层中隐藏神经元的数量等于要建立的连接的数量。
为了让事情更清楚,让我们将前面的准则应用到几个例子中。
示例 1:无隐藏层的人工神经网络
先说一个简单的两类分类问题的例子,如图 2-24 所示。每个样本有两个输入和一个表示类标签的输出。这与异或问题非常相似。
图 2-24
两类分类问题
要回答的第一个问题是是否需要隐藏层。确定这一点要遵循的规则如下:
- 在人工神经网络中,当且仅当数据必须非线性分离时,才需要隐藏层。
查看图 2-25 ,似乎类别必须非线性分离。单行不行。因此,我们必须使用隐藏层,以获得最佳决策边界。在这种情况下,我们可能仍然不使用隐藏层,但这会影响分类精度。所以,最好使用隐藏层。
知道我们需要隐藏层之后,我们需要回答两个重要的问题。这些问题如下:
-
所需的隐藏层数是多少?
-
每个隐藏层中隐藏神经元的数量是多少?
按照前面的过程,第一步是绘制一个划分两个类的决策边界。正确拆分数据的可能决策边界不止一个,如图 2-25 所示。我们将用于进一步讨论的是图 2-25(a) 。
图 2-25
非线性分类问题不能用一条线来解决
按照指导方针,下一步是用一组线来表达决策边界。
使用一组线来表示决策边界的想法来自于这样一个事实,即任何人工神经网络都是使用单层感知器作为构建块来构建的。单层感知器是一个线性分类器,它使用根据等式 2-24 创建的线来分离类别。
和=***【w】*x
其中xI是输入的****,wI是其权重, b 是偏差,而因为每个添加的隐藏神经元都会增加权重的数量,所以建议使用完成任务的最小数量的隐藏神经元。使用比所需更多的隐藏神经元将增加更多的复杂性。****
**回到我们的例子,说 ANN 是使用多个感知器网络构建的,等同于说网络是使用多条线构建的。
在这个例子中,判定边界由一组线代替。这些线从边界曲线改变方向的点开始。此时,放置了两条线,每条线的方向都不同。
因为边界曲线只有一个点改变方向,如图 2-26 中灰色圆圈所示,那么就只需要两条线。换句话说,有两个单层感知器网络。每个感知器产生一条线。
图 2-26
对问题进行分类需要两行
知道只需要两条线来表示决策边界告诉我们,第一个隐藏层将有两个隐藏神经元。
到目前为止,我们有一个带有两个隐藏神经元的隐藏层。每个隐藏的神经元可以被视为一个线性分类器,用一条线来表示,如图 2-26 所示。将有两个输出,一个来自每个分类器(即,隐藏神经元)。但是我们要构建一个单一的分类器,用一个输出表示类标签,而不是两个分类器。结果,两个隐藏神经元的输出将被合并成单个输出。换句话说,这两条线将由另一个神经元连接。结果如图 2-27 所示。
图 2-27
用一个隐藏的神经元把两条线连接起来
幸运的是,我们不需要添加另一个具有单个神经元的隐藏层来完成这项工作。输出层神经元将完成这项任务。这个神经元将合并先前生成的两条线,以便网络只有一个输出。
学习了隐含层及其神经元的数目后,网络架构现在就完成了,如图 2-28 所示。
图 2-28
分类问题的网络结构,其中曲线是通过连接两条线创建的,每条线都是使用隐藏层神经元创建的
示例 2:具有单一隐藏层的人工神经网络
另一个分类示例如图 2-29 所示。它类似于前面的例子,其中有两个类,每个样本有两个输入和一个输出。区别在于决策边界。本例中的边界比上例中的边界更复杂。
图 2-29
寻找最佳网络架构的更复杂的分类问题
根据指导方针,第一步是划定决策边界。我们讨论中使用的决策边界如图 2-30(a) 所示。
下一步是将决策边界分割成一组线;每条线将被模拟为人工神经网络中的感知器。在画线之前,边界改变方向的点要做好标记,如图 2-30(b) 所示。
图 2-30
对第二个示例进行分类的决策边界
问题是需要多少行。每个顶点和底点将有两条线与之相关联,总共四条线。中间点的两条线将与其他点共享。要创建的线如图 2-31 所示。
图 2-31
创建第二个示例的决策边界所需的行
因为第一隐藏层将具有与行数相等的隐藏层神经元,所以第一隐藏层将具有四个神经元。换句话说,有四个分类器,每个都由一个单层感知器创建。目前,网络将产生四个输出,每个分类器一个。下一步是将这些分类器连接在一起,以使网络只生成一个输出。换句话说,这些线将通过其他隐藏层连接在一起,以生成一条曲线。
由模型设计者来选择网络的布局。一种可行的网络架构是建立具有两个隐藏神经元的第二隐藏层。第一个隐藏神经元将连接前两条线,最后一个隐藏神经元将连接后两条线。第二次隐藏层的结果如图 2-32 所示。
图 2-32
连线以创建单个决策边界
到目前为止,已经有两条独立的曲线。因此,网络有两个输出。下一步是将这些曲线连接在一起,以便整个网络只有一个输出。在这种情况下,输出层神经元可以用于进行最终连接,而不是添加新的隐藏层。最终结果如图 2-33 所示。
图 2-33
使用输出层连接隐藏层的输出
网络设计现已完成,完整的网络架构如图 2-34 所示。
图 2-34
对第二个例子进行分类的网络架构*****************************************