照片由 mendhak 拍摄,版权全部。html
卷积和卷积层是卷积神经网络中使用的主要构建模块。前端
卷积是将输入简单经过滤波器进行激活。重复对输入使用同一个滤波器获得的激活后的图称为特征图/特征映射(feature map),表示输入(好比一张图像)中检测到的特征的位置和强度。android
卷积神经网络在特定的预测建模问题(如图像分类)的约束下,能创新的针对训练数据集并行自动学习大量滤波器。结果是能够在输入图像的任何位置检测到高度特定的特征。ios
在本教程中,你将了解卷积在卷积神经网络中是如何工做的。git
完成本教程后,你将知道:github
让咱们开始吧。后端
本教程分为四个部分,分别为:bash
卷积神经网络,简称 CNN,是一种专门用于处理二维图像数据的神经网络模型,尽管其也可用于一维和三维数据。网络
卷积神经网络的核心是卷积层,这也是卷积神经网络命名的由来。该层执行的操做称为“卷积”。app
在卷积神经网络的语境中,卷积是涵盖了一组权重与输入相乘的线性操做,很像传统的神经网络。因为该技术是为二维输入设计的,这个乘法会在输入数据阵列和二维权重阵列之间进行,称为滤波器或核。
滤波器小于输入数据,在滤波器大小的输入区块和滤波器之间应用的乘法被称做点积。点积是滤波器大小的输入区块和滤波器之间的对应元素相乘,而后求和产生的单一值。由于它产生单一值,因此该操做一般被称为“标积”。
咱们故意使用小于输入的滤波器,由于它容许相同的滤波器(权重集)在输入的不一样点处屡次乘以输入阵列。具体而言,滤波器从左到右、从上到下,系统地应用于输入数据的每一个重叠部分或滤波器大小的区块。
在图像上系统地应用相同的滤波器是一个强大的想法。若是滤波器设计为检测输入中的特定类型的特征,那么在整个输入图像上系统地应用该滤波器能有机会在图像中的任何位置发现该特征。这种能力一般被称为平移不变性,好比说,关注该特征是否存在,而不是在哪里存在。
若是咱们更关心某个特征是否存在而不是存在的确切位置,那么本地平移不变性会是很是有用的属性。例如,当断定图像是否包含面部时,咱们不须要知道眼睛的像素级准确位置,咱们只须要知道面部左侧和右侧分别有一只眼睛。
—— 第 342 页,深度学习,2016。
将滤波器与输入阵列相乘一次,可获得单一值。因为滤波器屡次应用于输入阵列,所以结果是一个表示输入滤波后的二维阵列输出值,这个结果被称为“特征映射”。
特征映射建立后,咱们能够经过非线性函数(例如 ReLU )传递特征映射中的每一个值,就像咱们对全链接层的输出所作的那样。
对二维输入建立特征映射的滤波器示例
若是你来自数字信号处理领域或相关的数学领域,你可能会将矩阵的卷积运算理解为不一样的东西。尤为应用输入前先翻转滤波器(核)。理论上,在卷积神经网络中说的卷积其实是“互相关”。然而在深度学习中,它被称为“卷积”操做。
不少机器学习库实现的互相关被称为卷积。
—— 第 333 页,深度学习,2016。
总之,咱们有一个输入,例如一个像素值图像,咱们还有一个滤波器,它也是一组权重,滤波器系统地应用于输入数据从而建立出特征映射。
对卷积神经网络来讲,用卷积处理图像数据的想法并不新颖或独特,它是计算机视觉中的经常使用技术。
历史上,滤波器是由计算机视觉专家手工设计的,而后将其应用于图像以产生特征映射或滤波后的输出,这在某种程度上使图像分析更容易。
例如,下面是一个手工 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 提供了一系列卷积层。
经过看一些人为数据和手工滤波器的样例,咱们能够更好地理解卷积运算。
在本节中,咱们将同时研究一维卷积层和二维卷积层的例子,二者都具体化了卷积操做,也提供了使用 Keras 层的示范。
咱们能够定义一个具备八个元素的一维输入,正中间两个凸起元素值为 1.0,其他元素值为 0.0。
[0, 0, 0, 1, 1, 0, 0, 0]
复制代码
对于一维卷积层,Keras 的输入必须是三维的。
第一维指每一个输入样本,在本例中咱们只有一个样本。第二个维度指每一个样本的长度,在本例中长度是 8。第三维指每一个样本中的通道数,在本例中咱们只有一个通道。
所以,输入阵列的 shape 为 [1, 8, 1]。
# define input data
data = asarray([0, 0, 0, 1, 1, 0, 0, 0])
data = data.reshape(1, 8, 1)
复制代码
咱们将定义一个模型,其输入样本的 shape 为 [8, 1]。
该模型将具备一个滤波器,shape 为 3,或者说三个元素宽。Keras 将滤波器的 shape 称为 kernel_size。
# create model
model = Sequential()
model.add(Conv1D(1, 3, input_shape=(8, 1)))
复制代码
默认状况下,卷积层中的滤波器使用随机权重进行初始化。在这我的为例子中,咱们将手动设定单个滤波器的权重。咱们将定义一个可以检测凸起的滤波器,这是一个由低输入值包围的高输入值,正如咱们在输入示例中定义的那样。
咱们将三元素滤波器定义以下:
[0, 1, 0]
复制代码
卷积层还具备误差输入值,该值也须要咱们设置一个为 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。
回想一下,点积是对应元素相乘的总和,在这它是 (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。
[0, 1, 0] . [0, 0, 1] = 0
复制代码
咱们是系统的,因此再一次,滤波器沿着输入的另外一个元素移动,并应用于索引 二、3 和 4 处的输入。此次在特征映射中输出值是 1。咱们检测到该特征并相应的激活。
[0, 1, 0] . [0, 1, 1] = 1
复制代码
重复该过程,直到咱们计算出整个特征映射。
[0, 0, 1, 1, 0, 0]
复制代码
请注意,特征映射有六个元素,而咱们的输入有八个元素。这是滤波器应用于输入序列的手工结果。还有其它方法能够将滤波器应用于输入序列可获得不一样 shape 的特征映射,例如填充,但咱们不会在本文中讨论这些方法。
你能够想象,经过不一样的输入,咱们能够检测到具备不一样强度的特征,且在滤波器中具备不一样的权重,那么咱们将检测到输入序列中的不一样特征。
咱们能够将上一节的凸起检测样例扩展为二维图像的垂直线检测器。
一样的,咱们能够约束输入,在这里为一个具备单个通道(如灰度)的正方形 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。
所以,输入必须具备四维 shape [样本,列,行,通道],在本例中是 [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 样例所作的那样。
滤波器将是二维的,一个 shape 3×3 的正方形。该层将指望输入样本具备 shape [列,行,通道],在本例中为 [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)
复制代码
特征映射的输出 shape 将是四维的,[批,行,列,滤波器]。咱们将执行单个批处理,而且咱们有一个滤波器(一个滤波器和一个输入通道),所以输出 shape 为 [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 元素的图像区块。理论上,图像区块是三维的,具备单个通道,滤波器具备相同的尺寸。在 NumPy 中咱们不能使用 dot() 函数实现它,咱们必须使用 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 的输入图像小,由于滤波器应用于输入图像的限制。
若是你但愿更深刻,本节将提供此主题的更多资源。
在本教程中,你了解到卷积在卷积神经网络中是如何工做的。
具体来讲,你学到了:
你有什么问题? 在下面的评论中提出您的问题,我会尽力回答。
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。