Convolutional Neural Networks¶
约 12305 个字 20 行代码 预计阅读时间 62 分钟
概念¶
卷积神经网络(CNN,或称 ConvNet)是专门为处理图像和视频数据而设计的神经网络,它们通过引入卷积操作来捕捉输入数据中的空间和局部模式信息。与传统的全连接神经网络相比,CNN 在处理视觉数据时具有更强的表现力和效率。
结构概述¶
在上一章中,神经网络的输入是一个向量,然后在一系列的隐藏层中对它做变换。每个隐层都是由若干的神经元组成,每个神经元都与前一层中的所有神经元连接。但是在一个隐层中,神经元相互独立不进行任何连接。最后的全连接层被称为 "输出层",在分类问题中,它输出的值被看做是不同类别的评分值。
常规神经网络对于大尺寸图像效果不尽人。在 CIFAR-10 中,图像的尺寸是 \(32\times32\times3\)(宽高均为 32 像素,3 个颜色通道),因此,对应的的常规神经网络的第一个隐层中,每一个单独的全连接神经元就有 \(32\times32\times3 = 3072\) 个权重。这个数量看起来还可以接受,但是很显然这个全连接的结构不适用于更大尺寸的图像。举例说来,一个尺寸为 \(200\times200\times3\) 的图像,会让神经元包含\(200\times200\times3 = 120000\)个权重值。如此庞大的参数数量不仅导致计算资源的浪费
,还容易导致模型的过拟合
。
CNN 通过利用输入数据的空间结构来减少参数数量,并提高模型的学习能力。与常规神经网络不同,卷积神经网络的各层中的神经元是 3 维排列的:宽度、高度 和 深度(这里的 深度 指的是该层特征图的数量,而不是整个网络的深度,整个网络的深度指的是网络的层数)。
举个例子,CIFAR-10 中的图像是作为卷积神经网络的输入,该数据体的维度是 \(32\times32\times3\)(宽度,高度和深度)。每一层的神经元只与前一层的小区域(称为感受野)连接,而不是采取全连接方式。这样,卷积层的每个神经元只负责检测局部特征(例如边缘、角点等),然后这些局部特征逐层组合,形成对整个图像的全局理解。对于用来分类 CIFAR-10 中的图像的卷积网络,其最后的输出层的维度是 \(1\times1\times10\),因为在卷积神经网络结构的最后部分将会把全尺寸的图像压缩为包含分类评分的一个向量,向量是在深度方向排列的。
左边是一个 3 层的常规神经网络,每一层的神经元与前一层的所有神经元全连接。右边是一个卷积神经网络,图例中网络将它的神经元都排列成 3 个维度(宽、高和深度)。卷积神经网络的每一层都将 3D 的输入数据变化为神经元 3D 的激活数据并输出。在这个例子中,红色的输入层装的是图像,所以它的宽度和高度就是图像的宽度和高度,它的深度是 3(代表了红、绿、蓝 3 种颜色通道)。
核心特性和不同的 layer¶
一个简单的卷积神经网络是由各种层按照顺序排列组成,网络中的每个层使用一个可以微分的函数将激活数据从一个层传递到另一个层。卷积神经网络主要由三种类型的层构成:卷积层,池化(Pooling)层 和 全连接层(全连接层和常规神经网络中的一样)。通过将这些层叠加起来,就可以构建一个完整的卷积神经网络。
一个用于 CIFAR-10 图像数据分类的卷积神经网络的结构可以是 [输入层-卷积层-ReLU 层-池化层-全连接层]。
-
输入 [\(32\times32\times3\)] 存有图像的原始像素值,本例中图像宽高均为 32,有 3 个颜色通道。
-
卷积层使用多个卷积核(滤波器)在输入图像上滑动,每个卷积核计算局部区域与自身权重的内积。内积值表示该区域与卷积核的匹配程度。(这个内积作为元素组成下一张特征图,其含义为与模版匹配的程度),同时每个卷积核都有一个偏差项。卷积层会计算所有神经元的输出。如果我们使用 12 个滤波器(也叫作核),输出尺寸为
[32x32x12]
,12 个特征图,每个特征图表示不同的局部特征(如边缘、纹理等)。
卷积操作(Convolution Operation)
在卷积神经网络中,卷积层取代了传统神经网络中的全连接层。卷积层使用
卷积核
(或称为滤波器)在输入图像上滑动,通过与图像局部区域进行点积运算来提取特征。卷积操作的关键在于它保留了图像的空间结构,利用了图像像素之间的空间关系。
- 局部连接性:卷积层中的每个神经元只与上一层的
局部区域
连接,而不是与所有神经元连接,这显著减少了参数数量
。- 权重共享:
同一个卷积核在图像的不同位置共享权重
,这进一步减少了模型的参数,并使模型能够在不同图像位置上检测相同的特征。卷积层的架构设计
- 深度(Depth):卷积核的数量决定了输出特征图的深度。
- 步幅(Stride):卷积核在图像上移动的步长,步幅越大,输出特征图越小。
- 填充(Padding):为了保持输出特征图的尺寸,可以在输入图像边缘添加额外的像素(通常为零)。
- ReLU 层将会逐个元素地进行激活函数操作,比如使用以 0 为阈值的\(f(x) = max(0,x)\)作为激活函数。该层对数据尺寸没有改变,还是 [32x32x12]。
激活函数(Activation Function)
卷积层的输出通常通过非线性激活函数处理,例如 ReLU(Rectified Linear Unit),以引入非线性,使模型能够表示更复杂的特征。
- 池化层在在空间维度(宽度和高度)上进行降采样(downsampling)操作,通常是最大池化(Max Pooling),将局部区域(如 2x2 窗口)内的最大值作为输出,减少空间维度。输出尺寸:
[16x16x12]
,宽度和高度均减半,深度不变。
池化层(Pooling Layer)
卷积神经网络中通常包含池化层,主要用于
下采样(downsampling)
,以 减少特征图的尺寸,降低计算复杂度,并增加模型的空间不变性。最常见的池化操作是最大池化(Max Pooling),它在局部区域内取最大值作为输出。
- 全连接层将会计算分类评分,特征图
[16x16x12]
,需要展平成一个向量。将展平的特征向量与全连接层的权重矩阵相乘,并加上偏置,输出分类得分。数据尺寸变为 [\(1\times1\times10\)],其中 10 个数字对应的就是 CIFAR-10 中 10 个类别的分类评分值。
全连接层(Fully Connected Layer)
在卷积神经网络的最后,通常会包含一个或多个全连接层。这些层将卷积和池化层提取的特征映射到输出类别。
全连接层将卷积网络的输出展平为一维向量
,并通过 Softmax 或 SVM 等损失函数进行分类。总结:
层次结构:通过卷积层提取图像特征,ReLU 层引入非线性,池化层降采样并减少特征图尺寸,最后全连接层生成分类评分。
参数和超参数:卷积层和全连接层含有可训练的权重参数,而 ReLU 层和池化层没有参数。超参数包括滤波器大小、数量、步幅、填充方式以及池化窗口大小。
特征提取和分类:CNN 逐层将图像从原始像素值转化为高层次特征,最终用于图像分类。
优点
- 空间不变性:卷积操作保留了输入的空间信息,使得模型能够识别出在不同位置的相同特征。
- 参数效率:通过局部连接和权重共享,CNN 大幅减少了模型参数,使得训练更加高效。
- 自动特征提取:CNN 可以自动学习和提取多层次的特征,从低级边缘和纹理到高级的对象和形状。
卷积层¶
卷积操作¶
- 单次卷积操作示意图
在这张示意图中,卷积操作展示了如何利用一个卷积核扫描输入图像的每个局部区域并生成对应的输出值。具体步骤如下:
输入:左侧为输入图像的一部分,表示一个局部的像素区域。
卷积核:卷积核在图像上滑动,在每个位置计算图像局部区域与卷积核之间的内积。这种内积计算是线性操作,即将输入区域的每个像素值与卷积核对应的权重相乘后求和。
输出:内积的结果形成一个输出值,表示该局部区域与卷积核特征的匹配程度。这些输出值组成特征图(Feature Map),如图右侧所示。
可以这样理解,一个卷积核就表示一种特征,输出的结果就是卷积核在此处进行匹配操作的得分或者相似度,得分越高,
说明此处局部图像就与卷积核要寻找的特征越相似
,然后卷积核的参数是可以改变的,或者说是可学习的,也就是卷积核要寻找的特征是会变化的,卷积核会去寻找最有用的特征,或者说最有助于降低分类误差的特征)
- 多维卷积核表示
这里有六个卷积核,每个卷积核的大小为 [3x5x5]
,其中 3
表示通道数(对应 RGB),5x5
表示空间尺寸。
多个卷积核堆叠在一起,形成一个四维张量。对于卷积层来说,输入图像的每个局部区域与每个卷积核进行运算后,会生成一个特征图。
这里有 6 个卷积核,因此将生成 6 张特征图,表示不同的卷积核在图像上提取到的不同特征。
- 卷积操作后生成特征图
如果我们进行多次卷积操作,那么一张 3*32*32 的图片,经过大小为 6*3*5*5 的卷积核卷积,就得到六张特征图,大小为 6*28*28
但是,我们并不能直接这样操作,因为实际上卷积是线性操作,多个卷积仍然是线性网络,所以我们需要加上激活函数
,这样才可以更好的学习
- 激活函数示意图
此图展示了在卷积操作后添加激活函数的步骤:
- 卷积的线性特性:卷积本质上是一个线性操作,这意味着即使进行多次卷积,网络仍然是线性的,不具备复杂的表示能力。
- 激活函数:为了引入非线性特性,需要在卷积操作后加上非线性激活函数。常用的激活函数包括 ReLU(
max(0, x)
),它会将负值置为 0,保留正值不变。通过激活函数,网络可以学习更复杂的特征。
概述和直观介绍:假设我们有一张 32x32 像素的 RGB 图像作为输入,第一层卷积层使用一个 5x5x3 的滤波器。这个滤波器的作用就像一个“模板”,它会滑动在图像的不同区域上,检测每个区域中是否存在特定的视觉模式(例如一条边缘)。如果滤波器“匹配”到图像中的特征,它的输出(激活值)就会高,反之则低。通过卷积操作生成的激活图展示了滤波器对整个图像的反应。
假如我们有多个滤波器,每个滤波器都能检测不同的特征。例如,一个滤波器可能专门检测水平边缘,另一个则检测垂直边缘,最终生成多个不同的激活图,在深度方向上层叠起来就生成了输出数据。
卷积神经网络中的神经元连接和参数共享
- 局部连接与感受野
在卷积层中,神经元并不是像全连接层那样与前一层的所有神经元相连。相反,它们仅与输入数据的一个局部区域连接。这种局部连接的区域称为神经元的 感受野(receptive field),它对应卷积核的空间尺寸。例如,如果卷积核的大小是 5x5
,那么感受野的空间尺寸就是 5x5
。
空间上的局部连接:神经元只与输入图像的一个小块相连,而非整个图像。这种局部连接的设计基于图像的局部相关性,允许网络集中关注图像的局部特征,如边缘、角点等。
深度上的全连接:虽然在空间上是局部连接,但在深度维度上,神经元与输入数据的所有通道都连接。这意味着输入图像的每个颜色通道(如 RGB 图像的 3 个通道)都参与计算。
- 参数共享
卷积层中的参数共享机制意味着同一个滤波器(卷积核)在输入数据的不同位置重复使用
。具体来说,卷积层的每个神经元虽然只观察输入数据的一个小区域,但这个小区域对应的参数(即滤波器的权重)在空间上是共享的。
共享参数的效果:这种参数共享方式使得卷积层可以有效地检测相同的特征在不同的空间位置。举例来说,一个卷积核如果能够在图像的一部分检测到边缘,它也能在其他部分检测到相同的边缘
。这种共享机制减少了模型参数的数量,提升了网络的泛化能力。
举例说明
例 1:假设输入数据是一个 32x32x3
的 RGB 图像(如 CIFAR-10 图像),卷积层使用 5x5x3
的滤波器。在这种情况下,卷积层中的每个神经元都只与 5x5
空间区域和 3
个颜色通道连接,因此每个神经元有 5x5x3 = 75
个权重,再加上一个偏置参数。
例 2:如果输入数据的尺寸是 16x16x20
,并且感受野的尺寸是 3x3
,那么卷积层中的每个神经元将与输入数据的一个 3x3x20
区域相连,这意味着每个神经元有 3x3x20 = 180
个权重。
左边:左侧的示意图展示了卷积层中的神经元如何连接到输入数据的一个局部区域。红色区域表示输入数据,蓝色表示卷积层中的神经元。可以看到,卷积层中的神经元只与输入数据的一个空间区域(感受野)连接,而在深度方向上,则与所有输入通道(如颜色通道)连接。
右边:神经网络章节中介绍的神经元保持不变,它们还是计算权重和输入的内积,然后进行激活函数运算,只是它们的连接被限制在一个局部空间。
超参数分析:深度、步长和零填充¶
卷积神经网络(CNN)的输出特征图的空间排列受多个超参数的影响,包括深度、步长和零填充。这些超参数不仅决定了输出特征图的尺寸,还影响了特征图的分辨率和信息保留程度。
- 深度(Depth):
- 深度对应于输出特征图的通道数,等同于使用的滤波器的数量。每个滤波器在输入数据中寻找不同的特征,例如边缘、颜色斑点等。因此,输出特征图的深度是由滤波器的数量决定的。每个深度列中的神经元共享相同的感受野,但在不同通道上激活。
- 步长(Stride):
- 步长决定了卷积核在输入图像上滑动的距离。
较小的步长(如 1)意味着卷积核逐像素移动,这会产生更大的输出特征图
。较大的步长(如 2)则意味着每次移动多个像素,这将导致输出特征图尺寸减小。 - 步长影响输出特征图的分辨率:
步长越大,输出特征图越小,分辨率也越低。大步长通常用于减小特征图的空间尺寸。
- 零填充(Zero-padding):
- 零填充是在输入图像的边缘填充额外的零像素,以控制输出特征图的空间尺寸。零填充的目的是避免在卷积过程中丢失边缘信息,或者保持输出特征图与输入图像在空间尺寸上的一致性。
- 通过适当的零填充,
可以确保输出特征图的尺寸与输入图像相同
,或者根据需要调整为特定大小。
输出特征图空间尺寸计算
\[ 输出尺寸 = \dfrac{W - F + 2P}{S} + 1 \]
W 是输入图像的尺寸(假设高度和宽度相等)。
F 是卷积核的尺寸。
P 是零填充的数量。
S 是步长。
例 1:假设输入图像尺寸为 \(W = 7\),卷积核尺寸 \(F = 3\),步长 \(S = 1\),零填充 \(P = 0\)。那么输出特征图的尺寸为:
\(\text{输出尺寸} = \dfrac{7 - 3 + 2(0)}{1} + 1 = 5\)
例 2:如果使用相同的输入图像和卷积核,但步长\(S = 2\),则输出特征图的尺寸为:
\(\text{输出尺寸} = \dfrac{7 - 3 + 2(0)}{2} + 1 = 3\)
空间排列的图示。在本例中只有一个空间维度(x 轴),神经元的感受野尺寸 F = 3,输入尺寸 W = 5,零填充 P = 1
。左边:神经元使用的步长 S = 1,所以输出尺寸是(5-3+2)/1+1 = 5。右边:神经元的步长 S = 2,则输出尺寸是(5-3+2)/2+1 = 3。注意当步长 S = 3 时是无法使用的,因为它无法整齐地穿过数据体。从等式上来说,因为(5-3+2)= 4 是不能被 3 整除的。
本例中,神经元的权重是 [1,0,-1],显示在图的右上角,偏差值为 0。这些权重是被所有黄色的神经元共享的
使用零填充¶
零填充(Zero-padding)在卷积神经网络(CNN)中的作用主要体现在两个方面:保持输出特征图的尺寸与输入一致,以及避免边缘信息丢失。通过合理使用零填充,可以有效地解决卷积操作中遇到的一些尺寸限制问题,尤其是当步长和滤波器尺寸的组合不适用于输入图像时。
- 保持空间维度不变
在上面的例子中,当感受野为 3 且步长为 1 时,通过使用零填充 \(P = 1\),可以确保输出特征图的空间维度与输入相同。这种情况下,如果不使用零填充,输出特征图的空间维度将减少至 3
。这种保持输入与输出空间维度一致的策略在深层卷积网络中很常见,因为它使网络能够逐层提取特征,同时保持图像的空间分辨率。
- 步长的限制与有效设置
对于某些步长和滤波器尺寸组合,如果不使用零填充,输出特征图的尺寸计算可能无法得到整数结果。例如,输入尺寸为 10,滤波器尺寸为 3,步长为 2,不使用零填充时,计算结果为 \(\dfrac{10-3+0}{2}+1 = 4.5\),不是整数,这就是说神经元不能整齐对称地滑过输入数据体,这显然是无效的设置。为避免此类问题,通常需要调整零填充或步长,使得计算结果为整数。
参数共享¶
- 参数共享的原理
在标准的全连接神经网络中,每个神经元都有其独立的权重和偏差,与前一层的所有神经元相连。然而,在卷积神经网络中,基于这样一个假设:如果某个特征在输入数据的某个位置有用,那么在其他位置也很可能有用
。这个假设被称为平移不变性
,意味着特征在不同位置上的表示可以是相同的。因此,通过将同一组权重(即滤波器)应用于输入的不同区域,网络可以共享这些权重,从而减少需要学习的参数数量。
- 参数共享的实际应用
以文中提到的卷积层为例,假设输入数据体的尺寸为 [55x55x96],每个卷积核的尺寸为 [11x11x3],且网络使用了 96 个不同的滤波器。如果不进行参数共享,每个神经元将独立学习其对应的参数,这将导致大量参数。例如,在一个 [55x55] 的深度切片中,共有 55x55 = 3025 个神经元,每个神经元需要学习 11x11x3 = 363 个参数。这意味着在没有参数共享的情况下,卷积层将拥有上亿个参数($290400\times364 = 105,705,600 $个),难以实际应用。
通过参数共享,同一滤波器被应用于每个空间位置,从而使每个深度切片中的所有神经元共享同一组权重和偏差。这样,卷积层只需学习每个深度切片中的 34,944 (\(96\times11\times11\times3 = 34,848\))个参数(包括偏差),大大减少了参数的数量。这不仅降低了模型的计算复杂度,还减轻了过拟合的风险。
注意,如果在一个深度切片中的所有权重都使用同一个权重向量,那么卷积层的前向传播在每个深度切片中可以看做是在计算神经元权重和输入数据体的 卷积(这就是 "卷积层" 名字由来)。这也是为什么总是将这些权重集合称为 滤波器(filter)(或 卷积核(kernel)),因为它们和输入进行了卷积。
Numpy 例子:为了让讨论更加的具体,我们用代码来展示上述思路。假设输入数据体是 numpy 数组 X。那么:
- 一个位于 (x, y) 的深度列(或纤维)将会是 \(X [x, y,:]\)。
-
在深度为 d 处的深度切片,或激活图应该是 \(X [:,:, d]\)。
-
输入和参数说明
假设我们有一个三维输入数据 X
,其尺寸为 (11, 11, 4)
,即输入数据的高度为 11,宽度为 11,深度为 4。我们还假设有两个卷积滤波器(或权重矩阵),W0
和 W1
,它们的尺寸为 (5, 5, 4)
,表示每个滤波器在高度和宽度方向覆盖 5x5 区域,深度与输入数据相同(即 4)。此外,每个滤波器还有一个偏差 b0
和 b1
。
- 卷积输出的计算
根据输入数据 X
的尺寸 (11, 11, 4)
、滤波器尺寸 (5, 5, 4)
、步长 S = 2
,以及零填充 P = 0
,输出数据 V
的空间尺寸为:
$$ \text{Output size} = \left( \frac{11 - 5}{2} + 1 \right) \times \left( \frac{11 - 5}{2} + 1 \right) = 4 \times 4 $$
因此,输出数据体 V
的尺寸为 (4, 4, 2)
,其中深度为 2 表示有两个激活图。
-
逐步理解卷积操作
-
第一层的第一个位置
(0,0,0)
:公式:
V[0, 0, 0] = np.sum(X[:5, :5, :] * W0) + b0
解释:取输入数据
X
的前 5 行和前 5 列,以及所有深度的切片,与滤波器W0
对应元素相乘,然后求和,加上偏差b0
。 -
第一层的第二个位置
(1,0,0)
:公式:
V[1, 0, 0] = np.sum(X[2:7, :5, :] * W0) + b0
解释:由于步长为 2,因此在 x 方向上移动 2 个像素,对新的区域
[2:7, :5, :]
进行相同操作。 -
第二层的第一个位置(0,0,1)
V[0, 0, 1] = np.sum(X[:5, :5, :] * W1) + b1
- 输入的窗口
X[:5, :5, :]
代表从输入X
中选取[0:5]
行、[0:5]
列的区域及其全部深度。 - 与滤波器
W1
执行逐元素相乘,然后求和,并加上偏差b1
,得到输出激活图V
在位置[0, 0]
的值。
- 输入的窗口
-
V[2, 3, 1] = np.sum(X[4:9, 6:11, :] * W1) + b1
- 同时在行和列方向上各滑动 2 个位置,输入窗口为
[4:9]
行和[6:11]
列。 - 计算得到
V
在位置[2, 3]
的值。
- 同时在行和列方向上各滑动 2 个位置,输入窗口为
-
Numpy 实现
import numpy as np
# 初始化输入数据 X 和滤波器 W0, W1
X = np.random.rand(11, 11, 4) # 输入数据
W0 = np.random.rand(5, 5, 4) # 第一个滤波器
W1 = np.random.rand(5, 5, 4) # 第二个滤波器
b0 = np.random.rand() # 第一个滤波器的偏差
b1 = np.random.rand() # 第二个滤波器的偏差
# 初始化输出数据 V
V = np.zeros((4, 4, 2)) # 输出数据,(4, 4, 2) 尺寸
# 计算卷积层的输出 V
for i in range(4):
for j in range(4):
V[i, j, 0] = np.sum(X[i*2:i*2+5, j*2:j*2+5, :] * W0) + b0 # 第一个激活图
V[i, j, 1] = np.sum(X[i*2:i*2+5, j*2:j*2+5, :] * W1) + b1 # 第二个激活图
# 激活函数(ReLU)
V = np.maximum(0, V) # ReLU 激活
卷积层的性质总结
在卷积神经网络(CNN)中,卷积层具有以下几个关键性质和超参数配置:
-
输入数据体的尺寸:
- 记为 \(W_1 \times H_1 \times D_1\) :
- \(W_1\) 是输入数据的宽度。
- \(H_1\) 是输入数据的高度。
- \(D_1\) 是输入数据的深度(通常是通道数,例如 RGB 图像的深度为 3)。
- 记为 \(W_1 \times H_1 \times D_1\) :
-
四个超参数:
- 滤波器的数量 K :
- K 表示卷积层使用的滤波器(或卷积核)的数量,决定输出数据体的深度。
- 滤波器的空间尺寸 F :
- F 表示滤波器的宽度和高度,通常是正方形,例如 F = 3 代表 \(3 \times 3\) 的滤波器。
- 步长 S :
- S 表示滤波器在输入数据体上滑动的步幅。较小的步长会产生更大的输出数据体。
- 零填充数量 P :
- P 表示在输入数据体的边缘进行零填充的数量,以控制输出数据的空间尺寸。
- 滤波器的数量 K :
-
输出数据体的尺寸:
- 记为 \(W_2 \times H_2 \times D_2\) :
- 输出宽度 \(W_2 = \frac{W_1 - F + 2P}{S} + 1\)
- 输出高度 \(H_2 = \frac{H_1 - F + 2P}{S} + 1\)
- 输出深度 \(D_2 = K\) (由滤波器的数量决定)
- 记为 \(W_2 \times H_2 \times D_2\) :
-
参数共享:
- 每个滤波器包含 \(F \times F \times D_1\) 个权重和一个偏差。因此卷积层一共具有 \(F \times F \times D_1 \times K\) 个权重和 K 个偏置。
-
输出数据的生成:
- 在输出数据体中,第 d 个深度切片(即 \(W_2 \times H_2\) 的二维矩阵)是通过将第 d 个滤波器在输入数据上进行有效卷积运算并加上对应的偏差得到的。
用矩阵乘法实现:卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。卷积层的常用实现方式就是利用这一点,将卷积层的前向传播变成一个巨大的矩阵乘法:
-
输入数据展开(im2col 操作):
-
假设输入图像尺寸为
[227x227x3]
,滤波器尺寸为[11x11x3]
,步长为4
。 -
im2col 操作将输入中的每个感受野拉伸为列向量。例如,尺寸为
[11x11x3]
的感受野会被拉伸为一个长度为11x11x3 = 363
的列向量。 -
对整个输入图像进行该操作,得到一个矩阵
X_col
,其中每列是一个感受野的展开。因为步长为4
,输出宽高为(227-11)/4+1 = 55
,所以X_col
的尺寸为[363x3025]
,即有55x55=3025
列。 -
滤波器展开:
-
假设有
96
个尺寸为[11x11x3]
的滤波器。 -
滤波器被展开成行向量,组成矩阵
W_row
,尺寸为[96x363]
。 -
矩阵乘法:
-
卷积运算变为矩阵乘法
np.dot(W_row, X_col)
,输出矩阵尺寸为[96x3025]
,即每个滤波器在每个感受野位置的点积结果。 -
结果重整:
- 将矩阵乘法的结果重新整形为合理的输出尺寸
[55x55x96]
。
缺点:im2col 操作可能导致内存消耗增加,因为输入数据的某些值会被重复存储在不同列中。
池化层¶
通常,在连续的卷积层之间会周期性地插入一个池化层。它的作用是逐渐降低数据体的空间尺寸,这样的话就能减少网络中参数的数量,使得计算资源耗费变少,也能有效控制过拟合。
池化层的操作本质上是对输入特征图的每个深度切片进行独立的降采样。最常用的是最大池化(Max Pooling),它在每个 2x2 区域内选择最大值作为输出。深度保持不变。池化层的一些公式:
- 池化层的输出尺寸 \(H_{out} \times W_{out}\) 可以通过以下公式计算:
其中:
- \(H_{in}\) 和 \(W_{in}\) 分别是输入特征图的高度和宽度。
- \(F\) 是池化窗口的尺寸(通常是 2x2 的大小,表示 F=2)。
- \(P\) 是填充(padding)的大小,一般情况下为 0。
-
\(S\) 是池化的步长(stride),通常为 2。
-
在池化层中很少使用零填充
-
对于每个深度切片中的 \(2 \times 2\) 区域,池化操作如下:
-
\(x_{i,j}\) 是输入特征图在位置 \((i, j)\) 的像素值。
-
池化层的一个关键作用是减少后续层的参数数量。假设输入特征图的大小为 \(H_{in} \times W_{in} \times D\)(高度、高度和深度),池化后输出的大小为 \(H_{out} \times W_{out} \times D\),那么参数数量减少的比率可以通过以下公式估算:
池化的不同形式与选择
池化操作不仅限于最大池化。还有平均池化(Average Pooling)和 L2 范式池化(L2-norm Pooling)。然而,实践表明最大池化通常效果更好,因为它能够提取特征图中更具辨识度的信息。
-
重叠池化(Overlapping Pooling):这种池化方式使用较大的滤波器尺寸和较小的步长(如
F=3, S=2
),保留更多的特征信息,但计算复杂度更高。 -
常用池化形式:更常见的配置是
F=2, S=2
,以平衡计算效率与信息保留。
池化层在输入数据体的每个深度切片上,独立地对其进行空间上的降采样。左边:本例中,输入数据体尺寸$ [224\times224\times64] \(被降采样到了\) [112\times112\times64]$,采取的滤波器尺寸是 2,步长为 2,而深度不变。右边:最常用的降采样操作是取最大值,也就是最大池化,这里步长为 2,每个取最大值操作是从 4 个数字中选取(即 2x2 的方块区域中)。
反向传播: 在反向传播时,池化层只将梯度传递给池化区域内的最大值元素
。为了实现这一点,在前向传播时,网络会记录下最大值元素的索引(即“道岔”)
,从而在反向传播中高效地回传梯度。
不使用池化层:尽管池化层是卷积神经网络中的常见组件,但近年来有研究尝试完全去除池化层,采用更大的卷积步长来达到同样的降采样效果。例如在 All Convolutional Net 中,通过仅使用卷积操作来减少特征图的空间尺寸。对于生成模型如 变化自编码器(VAEs:variational autoencoders
) 和 GANs:generative adversarial networks
,不使用池化层也有其独特的优势。
归一化层¶
归一化方式的作用,其目的是使网络更容易训练,加速模型的收敛,并提高模型的稳定性和性能
。(或者说可以使得输出具有零均值和单位方差),常见的方式称为批量归一化
,即 Batch Normalization
批量归一化的核心思想是在每个批次中对网络中的中间激活值进行归一化。具体来说,它通过对每个小批量(batch)的激活值进行均值和方差的标准化,使其具有零均值和单位方差。公式如下:
其中:
- \(x\) 是输入的激活值。
- \(\mu_B\) 和 \(\sigma_B^2\) 分别是当前批次激活值的均值和方差。
- \(\epsilon\) 是一个小的正数,用于避免除零错误。
使网络更容易训练,加速模型的收敛,并提高模型的稳定性和性能
内部协变量偏移(Internal Covariate Shift
): 内部协变量偏移指的是,由于上一层参数的变化导致当前层输入分布的变化,进而使得模型的训练更加困难。批量归一化通过标准化输入数据,减少了这种偏移,使得网络每一层的输入保持稳定,从而提高了优化效率。- 通过引入批量归一化,网络的每一层能够处理稳定且归一化的输入,
减少了训练中的梯度消失和梯度爆炸问题
。正如图示所示,批量归一化加速了网络的收敛,使得训练更为高效。
全连接层¶
在全连接层中,神经元对于前一层中的所有激活数据是全部连接的,这个常规神经网络中一样。它们的激活可以先用矩阵乘法,再加上偏差。更多细节请查看神经网络章节。
把全连接层转化成卷积层¶
全连接层和卷积层之间唯一的不同就是卷积层中的神经元只与输入数据中的一个局部区域连接,并且在卷积列中的神经元共享参数。但其本质都是通过权重与输入计算点积。因此,可以在理论上和实践中将二者相互转化。
- 卷积层转化为全连接层
卷积层可以被转化为等效的全连接层。这种转化的权重矩阵通常是巨大的、稀疏的,并且具有块结构。其中的大部分块中元素相等,反映了卷积层的局部连接和参数共享特性。除了某些特定块(这是因为有局部连接),其余部分都是零。
- 全连接层转化为卷积层
相比之下,将全连接层转化为卷积层在实际应用中更加有用,尤其在处理不同输入尺寸的情况下。一个典型的全连接层可以被视为一个卷积层,其中滤波器的尺寸与输入数据的空间尺寸相同
,这样卷积操作仅产生一个单一的输出。
我们以 AlexNet 为例,假设网络的输入是一张尺寸为
224x224x3
的图像,经过多层卷积和池化操作后,最终输出的激活数据体的尺寸为7x7x512
(通过使用 5 个池化层来对输入数据进行空间上的降采样,每次尺寸下降一半,所以最终空间尺寸为 \(224/2/2/2/2/2 = 7\))。接下来是三层全连接层:
第一个全连接层:4096 个神经元,接收 7x7x512
的输入。
第二个全连接层:4096 个神经元,接收第一个全连接层的输出。
第三个全连接层:1000 个神经元,输出类别概率。
-
将第一个全连接层转化为卷积层
-
原始全连接层:输入尺寸为
7x7x512
,输出为4096
个神经元。 -
转化后的卷积层:卷积核大小设置为
F=7
,步长为1
,不使用填充(padding)。卷积核的深度与输入的深度一致,即512
。由于卷积核覆盖了整个输入平面(即
7x7
的区域),卷积操作只会产生一个输出,因此输出的空间尺寸为1x1
,深度为4096
,即1x1x4096
。 -
将第二个全连接层转化为卷积层
-
原始全连接层:输入为第一个全连接层的
4096
个神经元,输出也是4096
个神经元。 -
转化后的卷积层:卷积核大小设置为
F=1
,步长为1
,输入深度为4096
,输出深度为4096
。由于卷积核的大小为
1x1
,输出的空间尺寸仍为1x1
,深度为4096
,即1x1x4096
。 -
将第三个全连接层转化为卷积层
-
原始全连接层:输入为第二个全连接层的
4096
个神经元,输出为1000
个神经元(用于分类)。 -
转化后的卷积层:卷积核大小设置为
F=1
,步长为1
,输入深度为4096
,输出深度为1000
。输出的空间尺寸仍为
1x1
,深度为1000
,即1x1x1000
。
为什么要将全连接层转化为卷积层
假设一个卷积神经网络的输入为
224x224x3
的图像,经过多层卷积和池化操作后,输出尺寸为7x7x512
。如果使用全连接层进行分类,通常需要将这个7x7x512
的激活数据展平成一维向量,然后进行分类。但这种展平操作损失了空间信息,同时计算效率低下。通过将全连接层转化为卷积层,可以避免展平操作,并且允许网络处理不同尺寸的输入图像。这种方法尤其适用于以下情况:希望在更大尺寸的图像上滑动较小的窗口(例如
224x224
)进行分类,并在一次前向传播中获得多个位置的分类结果。
具体例子分析
假设现在的输入图像尺寸为
384x384x3
,我们希望使用224x224
尺寸的滑动窗口以步长32
在这张大图上滑动,并获得各个窗口的分类结果。基于之前提到的转换方法:
- 转换前:传统方法需要对每个
224x224
的窗口分别进行前向传播计算,共需 36 次独立的计算(对应6x6
个位置)。转换后:将全连接层转化为卷积层后,可以直接对整个
384x384
的图像进行一次前向传播计算。如果 224x224 的输入图片经过卷积层和池化层之后得到了 [7x7x512] 的数组,那么,384x384 的大图片直接经过同样的卷积层和池化层之后会得到 [12x12x512] 的数组(因为途径 5 个池化层,尺寸变为 384/2/2/2/2/2 = 12)。通过卷积层替代全连接层后,可以得到6x6x1000
(\(\dfrac{12-7}{1} + 1 = 6\)) 的输出,表示每个224x224
窗口的分类得分。
这种转化的最大优势在于计算效率。原本需要进行 36 次独立计算的操作,转化后只需一次前向传播即可完成。通过共享计算资源,避免了重复计算,大幅度提高了计算效率。
卷积神经网络的结构¶
卷积神经网络通常是由三种层构成:卷积层
,池化层
(除非特别说明,一般就是最大值池化
)和全连接层
(简称 FC
)。ReLU 激活函数也应该算是是一层,它逐元素地进行激活函数操作。在本节中将讨论在卷积神经网络中这些层通常是如何组合在一起的。
层的排列规律¶
卷积神经网络最常见的形式就是将一些卷积层和 ReLU 层放在一起,其后紧跟池化层,然后重复如此直到图像在空间上被缩小到一个足够小的尺寸
,在某个地方过渡成成全连接层也较为常见。最后的全连接层得到输出,比如分类评分等。换句话说,最常见的卷积神经网络结构如下:
INPUT -> [[CONV -> RELU] _ N -> POOL?] _ M -> [FC -> RELU] * K -> FC
-
N 是卷积层和 ReLU 层组合的重复次数。
-
POOL? 表示是否使用池化层。
-
M 是卷积-池化组合的重复次数。
-
K 是全连接层的重复次数。
常见的 CNN 结构实例
- INPUT -> FC, 最简单的结构,实现一个线性分类器,此处 N = M = K = 0。
- INPUT -> CONV -> RELU -> FC
- INPUT -> [CONV -> RELU -> POOL] *2 -> FC -> RELU -> FC。此处在每个池化层之间有一个卷积层。
- INPUT -> [CONV -> RELU -> CONV -> RELU -> POOL] 3 -> [FC -> RELU] 2 -> FC。此处每个池化层前有两个卷积层,这个思路适用于更大更深的网络,因为在执行具有破坏性的池化操作前,多重的卷积层可以从输入数据中学习到更多的复杂特征。
在卷积神经网络中,使用多个小滤波器(如 3x3)的卷积层组合比使用单个大滤波器(如 7x7)的卷积层有明显的优势。这种做法不仅提高了特征提取的能力,还能减少模型的参数数量。
- 感受野的扩展
通过堆叠 3 个 3x3 的卷积层,逐层扩展感受野:
- 第一个卷积层:每个神经元的感受野为 3x3。
- 第二个卷积层:每个神经元的感受野扩大为 5x5(相对于输入数据体)。
- 第三个卷积层:每个神经元的感受野进一步扩大到 7x7。
与此相比,直接使用一个 7x7 的卷积层,其感受野虽然也是 7x7,但无法充分利用多层卷积带来的非线性特征提取能
- 特征提取能力
多个小滤波器卷积层的组合由于在每一层后面都加入了非线性激活函数
(如 ReLU),使得网络能够学习到更复杂、更深层的特征
。单个大滤波器卷积层无法提供这种层次化的特征表达能力。
- 参数数量减少
在卷积神经网络中,使用多个小滤波器(如 3x3)的卷积层组合相比于使用单个大滤波器(如 7x7)的卷积层具有明显的参数量减少优势。以下是对这种参数减少的详细分析。
参数计算方式
对于一个卷积层,假设输入特征图的通道数为 \(C_{\text{in}}\) ,输出特征图的通道数为 \(C_{\text{out}}\) ,滤波器的尺寸为 \(F \times F\) :
- 每个卷积核的参数数量为 \(F \times F \times C_{\text{in}}\) 。
- 卷积层的总参数数量为 $C{\text{out}} \times F \times F \times C} $。
大滤波器 vs. 小滤波器
假设我们要设计一个卷积神经网络,其输入特征图的通道数为 $C_{\text{in}} $,输出特征图的通道数为 \(C_{\text{out}}\) ,分别考虑使用单个 \(7\times7\) 的大滤波器和三个 \(3\times3\) 的小滤波器。
-
单个 7x7 滤波器的参数量
-
滤波器大小:7x7
-
参数数量: $ C{\text{out}} \times 7 \times 7 \times C} = C{\text{out}} \times 49 \times C}$
-
-
三个 3x3 滤波器的参数量
如果使用三个 3x3 的卷积层,每个卷积层将从上一个卷积层的输出通道作为输入通道:
- 第一层:输入通道数为 \(C_{\text{in}}\) ,输出通道数为 \(C_{\text{out}}\)
参数数量: \(C_{\text{out}} \times 3 \times 3 \times C_{\text{in}} = C_{\text{out}} \times 9 \times C_{\text{in}}\)
- 第二层:输入通道数为 $ C*{\text{out}}$ ,输出通道数为 \(C*{\text{out}}\)
参数数量: \(C_{\text{out}} \times 3 \times 3 \times C_{\text{out}} = C_{\text{out}}^2 \times 9\)
- 第三层:输入通道数为 \(C_{\text{out}}\) ,输出通道数为 \(C_{\text{out}}\)
参数数量: \(C_{\text{out}} \times 3 \times 3 \times C_{\text{out}} = C_{\text{out}}^2 \times 9\)
三个 3x3 卷积层的总参数数量为: [ \(9 \times C_{\text{in}} \times C_{\text{out}} + 9 \times C_{\text{out}}^2 + 9 \times C_{\text{out}}^2 = 9 \times C_{\text{in}} \times C_{\text{out}} + 18 \times C_{\text{out}}^2\) ]
层的尺寸设置规律¶
卷积神经网络(CNN)中的层的尺寸设置是设计网络架构时的关键因素,直接影响模型的表现和计算效率。以下是对这些设置规律的详细分析:
- 输入层的尺寸
输入层的尺寸通常设置为 32、64、96、224 等,这些尺寸的共同特点是能被 2 多次整除
。原因在于,在 CNN 中,经过多次池化操作
后,图像的空间尺寸逐渐减小,因此初始尺寸能被 2 整除可以使网络结构更容易设计,并保持对称性。例如,典型的 ImageNet 数据集使用 224x224 的输入尺寸,这样的尺寸可以通过多次 2x2 池化操作对图像进行下采样,并最终在全连接层之前获得合理的小尺寸特征图。
- 卷积层的滤波器尺寸和步长:
小尺寸滤波器与零填充
- 滤波器尺寸:卷积层通常使用 3x3 或最多 5x5 的小滤波器。较小的滤波器在提取局部特征时具有较高的空间分辨率,并且可以通过堆叠多层卷积来增加网络的感受野,而不显著增加参数量。
- 步长与零填充:
步长通常设置为 1
,这样可以保证卷积操作不会跳过任何像素,从而保留更多的空间信息。为了在卷积后保持输入和输出的空间尺寸相同
,通常会在输入数据周围进行零填充(padding)。当滤波器尺寸为 \(F\) 时,使用 \(P = \dfrac{F-1}{2}\) 的零填充,可以保证输出的空间尺寸不变(保留边缘信息
)。
- 池化层的设置
池化层通常用来进行空间维度的降采样,从而减少数据的空间尺寸和计算量。常见的设置是使用 2x2 的最大池化(Max Pooling),步长为 2,这样每次池化操作都会将输入的宽度和高度减半
。这种设置可以有效减少输入数据的尺寸,并保留重要的特征信息。
更大的池化滤波器(如 3x3)和步长也偶尔使用,但通常更大的池化操作会导致过多的信息损失,不利于网络性能
。因此,在实践中,更大的池化滤波器很少使用。
在设计 CNN 时,内存使用和计算开销往往是必须考虑的因素,尤其是在网络的初始层。由于初始层的输入通常为高分辨率图像,其特征图的空间维度较大,因此会占用大量内存
。为了应对这一挑战,可以在初始层使用较大的步长或更大的滤波器尺寸,从而减少特征图的尺寸和内存占用。
- ZFNet:在第一层使用 7x7 的滤波器,步长为 2,减少了空间尺寸和内存消耗。
- AlexNet:使用 11x11 的滤波器,步长为 4,以进一步降低初始层的计算量。
计算上的考量¶
在构建和训练卷积神经网络(CNN)时,内存瓶颈是一个关键的计算考虑因素,尤其在使用现代 GPU 时更为显著。要优化内存使用,需要理解和管理以下几个主要的内存消耗来源:
-
中间数据体尺寸(激活数据和梯度)
-
激活数据的存储:在前向传播过程中,每一层的激活数据(feature map)需要存储在内存中,因为这些数据在反向传播阶段需要用来计算梯度。通常,靠近输入层的卷积层会生成更大的激活数据体,这些数据的尺寸决定了内存使用的主要部分。在训练过程中,由于反向传播的需要,不能轻易丢弃这些数据。
-
测试优化:在测试时,网络的激活数据不再需要进行反向传播,因此可以在每层计算完后,丢弃不再需要的前层激活数据,从而显著降低内存占用。
-
来自参数尺寸
-
参数及其梯度:每个卷积层和全连接层都包含参数(权重和偏置),这些参数在训练时需要存储其梯度信息。通常来说,存储参数时需要额外的内存来保存这些梯度,以及在使用如动量(momentum)、Adagrad、RMSProp 等优化算法时的历史计算缓存。一般情况下,这些需求会使内存使用达到原始参数量的 3 倍甚至更多。
-
优化技巧:通过减少网络的参数数量,例如使用较小的卷积核、更少的通道数、或移除全连接层(如 GoogLeNet 所做的),可以减少内存需求。
-
其他内存占用
- 批量数据:训练时,整个批次的数据都要同时加载到内存中进行处理。随着批量大小(batch size)的增加,内存需求会显著增加。