深度学习神经网络中卷积层是如何工作的?
卷积层是卷积神经网络中使用的主要构件。
卷积是将滤波器简单地应用于导致激活的输入。对输入重复应用相同的过滤器会产生称为特征图的激活图,其指示输入(例如图像)中检测到的特征的位置和强度。
卷积神经网络的创新之处在于能够在特定预测建模问题(如图像分类)的约束下,自动并行学习针对训练数据集的大量滤波器。其结果是在输入图像的任何位置都可以检测到高度特定的特征。
在本教程中,你将了解卷积神经网络中的卷积是如何工作的。
完成本教程后,你将了解:
- 卷积神经网络将滤波器应用于输入,以创建总结输入中检测到的特征的存在的特征映射。
- 滤波器可以是手工制作的,例如线检测器,但卷积神经网络的创新是在特定预测问题的背景下在训练期间学习滤波器。
- 如何在卷积神经网络中计算一维卷积层和二维卷积层的特征映射。
我们开始吧。
教程概述
本教程分为四个部分,它们是:
- 卷积神经网络中的卷积。
- 计算机视觉中的卷积。
- 学习滤波器的功率。
- 卷积层的实例。<
/ul>
卷积神经网络中的卷积
卷积神经网络,简称CNN,是为处理二维图像数据而设计的一种特殊类型的神经网络模型,尽管它们可以用于一维和三维数据。
卷积神经网络的中心是使网络得名的卷积层。这一层执行一种称为“卷积”的操作。
在卷积神经网络的上下文中,卷积是涉及一组权重与输入相乘的线性运算,与传统神经网络非常相似。假设该技术是为二维输入设计的,则在输入数据数组和称为过滤器或核的二维权重数组之间执行乘法。
滤波器小于输入数据,并且在输入的滤波器大小的块和滤波器之间应用的乘法类型是点积。点积是输入的滤波器大小的块和滤波器之间的逐元素乘法,然后求和,始终得到单个值。因为它产生单个值,所以该运算通常被称为“标量积”。
有意使用小于输入的过滤器,因为它允许在输入上的不同点将相同的过滤器(权重集)与输入数组多次相乘。具体地说,从左到右、从上到下,将过滤器系统地应用于输入数据的每个重叠部分或过滤器大小的块。
在图像上系统地应用相同的滤光片是一个强大的想法。如果过滤器被设计为检测输入中的特定类型的特征,则在整个输入图像上系统地应用该过滤器允许过滤器有机会在图像中的任何位置发现该特征。这种能力通常被称为平移不变性,例如,通常关注的是特征是否存在而不是它出现在哪里。
如果我们更关心某些特征是否存在而不是它的确切位置,则局部平移的不变性可能是一个非常有用的属性。例如,在判断图像是否包含人脸时,我们不需要以像素完美的精度知道眼睛的位置,只需要知道人脸的左侧有一只眼睛,右侧有一只眼睛。
将滤波器与输入数组相乘一次的输出是单个值。由于过滤器多次应用于输入数组,因此结果是表示输入过滤的输出值的二维数组。因此,此操作的二维输出数组称为“特征映射”。
一旦创建了要素地图,我们就可以通过非线性(如ReLU)传递要素地图中的每个值,就像我们对完全连接的图层的输出所做的那样。
如果你来自数字信号处理领域或相关的数学领域,你可能会把矩阵上的卷积运算理解为不同的东西。具体地说,过滤器(内核)在应用于输入之前被翻转。从技术上讲,卷积神经网络的使用中所描述的卷积实际上是一种“互相关”。然而,在深度学习中,它被称为“卷积”运算。
许多机器学习库实现互相关,但称之为卷积。
总而言之,我们有一个输入,比如像素值的图像,我们有一个过滤器,它是一组权重,过滤器被系统地应用于输入数据以创建特征地图。
计算机视觉中的卷积
将卷积运算应用于图像数据的想法对于卷积神经网络来说并不新鲜或独特,它是计算机视觉中常用的技术。
在历史上,滤镜是由计算机视觉专家手工设计的,然后将这些滤镜应用到图像上,以产生特征地图或应用滤镜的输出,然后在某种程度上使图像的分析变得更容易。
例如,下面是手工制作的3×3元素过滤器,用于检测垂直线:
0.0, 1.0, 0.0 0.0, 1.0, 0.0 0.0, 1.0, 0.0
将此过滤器应用于图像将生成仅包含垂直线的要素地图。这是一个垂直线探测器。
你可以从过滤器中的权重值中看到这一点;中心垂直线上的任何像素值都将被正激活,而两边的任何像素值都将被负激活。系统地跨图像中的像素值拖动此滤镜只能突出显示垂直线像素。
还可以创建水平线检测器并将其应用于图像,例如:
0.0, 0.0, 0.0 1.0, 1.0, 1.0 0.0, 0.0, 0.0
组合来自两个过滤器的结果,例如组合两个特征地图,将导致图像中的所有线被高亮显示。
可以设计一套由数十个甚至数百个其他小过滤器组成的套件来检测图像中的其他特征。
在神经网络中使用卷积运算的创新之处在于,滤波器的值是在网络训练过程中要学习的权重。
网络将学习从输入中提取哪些类型的特征。具体地说,在随机梯度下降的训练下,网络被迫学习从图像中提取特征,以最小化网络正在训练以解决的特定任务的损失,例如提取对于将图像分类为狗或猫最有用的特征。
在这种情况下,你可以看到这是一个强大的想法。
学习滤波器的功率
学习特定于机器学习任务的单个过滤器是一种强大的技术。
然而,卷积神经网络在实践中取得了更大的成就。
多个过滤器
卷积神经网络不学习单个滤波器;事实上,对于给定的输入,它们并行地学习多个特征。
例如,对于给定输入,卷积层通常并行学习32到512个滤波器。
这给模型32或甚至512提供了从输入提取特征的不同方式,或者给出了“学习看”和在训练之后的许多不同的方式来“看”输入数据的许多不同的方式。
这种多样性允许专门化,例如,不仅是行,而且是在你的特定培训数据中看到的特定行。
多渠道
彩色图像有多个通道,通常每个颜色通道对应一个通道,如红色、绿色和蓝色。
从数据的角度来看,这意味着作为输入提供给模型的单个图像实际上是三个图像。
滤波器必须始终具有与输入相同的通道数,通常称为“深度”。如果输入图像具有3个通道(例如深度3),则应用于该图像的滤镜也必须具有3个通道(例如深度3)。在这种情况下,对于行、列和深度,3×3过滤器实际上应该是3x3x3或[3,3,3]。无论输入深度和过滤器深度如何,都会使用点积运算将过滤器应用于输入,从而产生单个值。
这意味着如果卷积层具有32个滤波器,则这32个滤波器不仅对于二维图像输入是二维的,而且是三维的,对于三个通道中的每一个都具有特定的滤波器权重。然而,每个过滤器都会产生一个单一的功能地图。这意味着对于所创建的32个特征地图,应用具有32个滤波器的卷积层的输出深度为32。
多层
卷积层不仅可以应用于输入数据,例如原始像素值,还可以应用于其他层的输出。
卷积层的堆叠允许输入的分层分解。
考虑到直接对原始像素值进行操作的过滤器将学习提取低级特征,例如线条。
对第一线层的输出进行操作的过滤器可以提取作为较低级别特征的组合的特征,诸如包括用于表示形状的多条线的特征。
这个过程一直持续到非常深的层提取人脸、动物、房屋等等。
这正是我们在实践中看到的。随着网络深度的增加,特征提取到更高、更高的层次。
卷积层的实例
KERAS深度学习库提供了一套卷积层。
通过查看一些使用人工数据和手工制作的滤波器的工作示例,我们可以更好地理解卷积运算。
在本节中,我们将同时查看一维卷积层和二维卷积层示例,以使卷积运算具体化,并提供使用Kera层的示例。
一维卷积层的实例
我们可以定义一个具有8个元素的一维输入,所有元素的值都为0.0,中间有一个值为1.0的两个元素凹凸。
[0, 0, 0, 1, 1, 0, 0, 0]
对于一维卷积层,Keras 的输入必须是三维的。
第一个维度指的是每个输入样本;在本例中,我们只有一个样本。第二个维度指的是每个样本的长度;在本例中,长度为8。第三个维度是指每个样本中的通道数;在本例中,我们只有一个通道。
因此,输入数组的形状将为 [1, 8, 1]。
# define input data data = asarray([0, 0, 0, 1, 1, 0, 0, 0]) data = data.reshape(1, 8, 1)
我们将定义一个模型,该模型期望输入样本具有形状[8,1]。
该模型将有一个形状为3或三个元素宽的过滤器。Keras将过滤器的形状称为kernel_size。
# create model model = Sequential() model.add(Conv1D(1, 3, input_shape=(8, 1)))
默认情况下,卷积层中的过滤器使用随机权重进行初始化。在这个精心设计的示例中,我们将手动指定单个过滤器的权重。我们将定义一个能够检测凸起的过滤器,即被低输入值包围的高输入值,正如我们在输入示例中定义的那样。
我们将定义的三元素过滤器如下所示:
[0, 1, 0]
卷积层也有一个偏置输入值,它也需要一个我们将设置为零的权重。
因此,我们可以强制一维卷积层的权重使用手工制作的滤波器,如下所示:
# define a vertical line detector weights = [asarray([[[0]],[[1]],[[0]]]), asarray([0.0])] # store the weights in the model model.set_weights(weights)
必须按照行、列和通道在三维结构中指定权重。该过滤器具有单行、三列和一个通道。
我们可以检索权重并确认它们设置正确。
# confirm they were stored print(model.get_weights())
最后,我们可以将单个过滤器应用于我们的输入数据。
我们可以通过调用模型上的predict()函数来实现这一点。这将直接返回功能映射:即在输入序列中系统地应用过滤器的输出。
# apply filter to input data yhat = model.predict(data) print(yhat)
将所有这些结合在一起,下面列出了完整的示例。
# example of calculation 1d convolutions from numpy import asarray from keras.models import Sequential from keras.layers import Conv1D # define input data data = asarray([0, 0, 0, 1, 1, 0, 0, 0]) data = data.reshape(1, 8, 1) # create model model = Sequential() model.add(Conv1D(1, 3, input_shape=(8, 1))) # define a vertical line detector weights = [asarray([[[0]],[[1]],[[0]]]), asarray([0.0])] # store the weights in the model model.set_weights(weights) # confirm they were stored print(model.get_weights()) # apply filter to input data yhat = model.predict(data) print(yhat)
运行该示例首先打印网络的权重;这是对模型中手工创建的过滤器是否如我们预期的那样设置的确认。
接下来,将过滤器应用于输入模式,并计算并显示特征地图。我们可以从特征地图的值中看到,凸起被正确检测到。
[array([[[0.]], [[1.]], [[0.]]], dtype=float32), array([0.], dtype=float32)] [[[0.] [0.] [1.] [1.] [0.] [0.]]]
让我们仔细看看这里发生了什么。
回想一下,输入是一个八元素向量,值为: [0, 0, 0, 1, 1, 0, 0, 0]。
首先,通过计算点积(“.”),将三元素过滤器 [0, 1, 0] 应用于输入 [0, 0, 0]的前三个输入。运算符),这导致特征映射中的单个输出值为零。
回想一下,点积是逐个元素乘法的和,或者这里是 (0 x 0) + (1 x 0) + (0 x 0) = 0。在NumPy中,这可以手动实现为
from numpy import asarray print(asarray([0, 1, 0]).dot(asarray([0, 0, 0])))
在我们的手动示例中,这如下所示:
[0, 1, 0] . [0, 0, 0] = 0
然后沿着输入序列的一个元素移动过滤器,并且重复该过程;具体地,将相同的过滤器应用于索引1、2和3处的输入序列,这也导致特征映射中的零输出。
[0, 1, 0] . [0, 0, 1] = 0
我们是系统化的,因此再次沿输入的另一个元素移动过滤器,并将其应用于索引2、3和4处的输入。这一次,输出是功能地图中的值1。我们检测到该功能并适当激活。
[0, 1, 0] . [0, 1, 1] = 1
重复该过程,直到我们计算出整个特征地图。
[0, 0, 1, 1, 0, 0]
请注意,功能地图有6个元素,而我们的输入有8个元素。这是过滤器如何应用于输入序列的伪像。还有其他方法可以对输入序列应用过滤器来更改生成的特性地图的形状,例如填充,但我们不会在本文中讨论这些方法。
你可以想象,对于不同的输入,我们可能会检测到或多或少强度的特征,并且在滤波器中具有不同的权重,我们将在输入序列中检测到不同的特征。
二维卷积层示例
我们可以将上一节中的凹凸检测示例扩展到二维图像中的垂直线检测器。
同样,我们可以将输入限制为正方形8×8像素的输入图像,在这种情况下,该图像具有单通道(例如灰度),中间有一条垂直线。
[0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0] [0, 0, 0, 1, 1, 0, 0, 0]
Conv2D图层的输入必须是四维的。
第一个维度定义样本;在本例中,只有一个样本。第二个维度定义行数;在本例中为8行。第三个维度定义列数,在本例中也是8列,最后是通道数,在本例中是1。
因此,在这种情况下,输入必须具有四维形状[样本, 行, 列, 通道]或[1, 8, 8, 1] 。
# define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1)
我们将使用单个过滤器定义Conv2D,就像我们在上一节中使用Conv1D示例所做的那样。
过滤器将是形状为3×3的二维正方形。该层将期望输入样本的形状为[列,行,通道]或 [8,8,1]。
# create model model = Sequential() model.add(Conv2D(1, (3,3), input_shape=(8, 8, 1)))
我们将定义一个垂直线检测器过滤器来检测输入数据中的单个垂直线。
过滤器如下所示:
0, 1, 0 0, 1, 0 0, 1, 0
我们可以按如下方式实施:
# define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights) # confirm they were stored print(model.get_weights())
最后,我们将对输入图像应用过滤器,这将产生我们期望显示输入图像中垂直线检测的特征图。
# apply filter to input data yhat = model.predict(data)
要素地图输出的形状将是四维的,形状为[批次, 行, 列, 过滤器]。我们将执行单个批处理,并且我们只有一个滤波器(一个滤波器和一个输入通道),因此输出形状为 [1, ?, ?, 1]。我们可以美化打印单个功能地图的内容,如下所示:
for r in range(yhat.shape[1]): # print each column in the row print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
将所有这些结合在一起,下面列出了完整的示例。
# example of calculation 2d convolutions from numpy import asarray from keras.models import Sequential from keras.layers import Conv2D # define input data data = [[0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0]] data = asarray(data) data = data.reshape(1, 8, 8, 1) # create model model = Sequential() model.add(Conv2D(1, (3,3), input_shape=(8, 8, 1))) # define a vertical line detector detector = [[[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]], [[[0]],[[1]],[[0]]]] weights = [asarray(detector), asarray([0.0])] # store the weights in the model model.set_weights(weights) # confirm they were stored print(model.get_weights()) # apply filter to input data yhat = model.predict(data) for r in range(yhat.shape[1]): # print each column in the row print([yhat[0,r,c,0] for c in range(yhat.shape[2])])
运行该示例首先确认在图层权重中正确定义了手工创建的过滤器。
接下来,打印计算出的要素地图。从数字的尺度我们可以看出,滤波器确实检测到了特征地图中间具有很强激活度的单个垂直线。
[array([[[[0.]], [[1.]], [[0.]]], [[[0.]], [[1.]], [[0.]]], [[[0.]], [[1.]], [[0.]]]], dtype=float32), array([0.], dtype=float32)] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0] [0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
让我们仔细看看是怎么计算出来的。
首先,将滤波器应用于图像的左上角,即3×3元素的图像块。从技术上讲,图像贴片是三维的,只有一个通道,而滤光片具有相同的尺寸。我们不能使用dot()函数在NumPy中实现这一点,而必须使用tensordot()函数,以便可以对所有维度进行适当求和,例如:
from numpy import asarray from numpy import tensordot m1 = asarray([[0, 1, 0], [0, 1, 0], [0, 1, 0]]) m2 = asarray([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) print(tensordot(m1, m2))
该计算导致单个输出值为0.0,例如,未检测到该特征。这为我们提供了功能地图左上角的第一个元素。
手动设置如下:
0, 1, 0 0, 0, 0 0, 1, 0 . 0, 0, 0 = 0 0, 1, 0 0, 0, 0
筛选器沿左侧一列移动,并重复该过程。同样,未检测到该功能。
0, 1, 0 0, 0, 1 0, 1, 0 . 0, 0, 1 = 0 0, 1, 0 0, 0, 1
再向左移动一次到下一列,将首次检测到该功能,从而导致强大的激活。
0, 1, 0 0, 1, 1 0, 1, 0 . 0, 1, 1 = 3 0, 1, 0 0, 1, 1
重复此过程,直到滤波器的边缘靠在输入图像的边缘或最后一列为止。这提供了功能地图第一整行中的最后一个元素。
[0.0, 0.0, 3.0, 3.0, 0.0, 0.0]
然后,过滤器向下移动一行并返回到第一列,该过程从左到右相关,以提供特征地图的第二行。并打开,直到滤镜的底部位于输入图像的底部或最后一行。
同样,与上一节一样,我们可以看到特征映射是一个6×6的矩阵,比8×8的输入图像小,这是因为过滤器如何应用于输入图像的限制。
进一步阅读
如果你想深入了解,本节提供了更多关于该主题的资源。
文章
书籍
- 第9章:卷积网络,深度学习,2016。
- 第5章:计算机视觉的深度学习,Python的深度学习,2017。
API接口
摘要
在本教程中,你了解了卷积神经网络中卷积的工作原理。
具体地说,你了解到:
- 卷积神经网络将滤波器应用于输入,以创建总结输入中检测到的特征的存在的特征映射。
- 滤波器可以是手工制作的,例如线检测器,但卷积神经网络的创新是在特定预测问题的背景下在训练期间学习滤波器。
- 如何在卷积神经网络中计算一维卷积层和二维卷积层的特征映射。