考虑到篇幅问题和实际用途进行精简,只选取个人觉得值得一说的部分
本章节代码未放全,但实测可跑,详见官网

参考
CNN笔记:通俗理解卷积神经网络
Convolutional Neural Networks (CNNs / ConvNets)
卷积神经网络 (CNN) 基本原理和公式
零基础入门深度学习(4) - 卷积神经网络
深度学习:卷积神经网络中的卷积核
最容易理解的对卷积(convolution)的解释
【DL笔记6】从此明白了卷积神经网络(CNN)
在 CNN 中,为什么要逐渐增加特征图的通道数?
机器学习算法之——卷积神经网络(CNN)原理讲解

扩展推荐
你必须要知道CNN模型:ResNet 残差学习
ResNet——CNN经典网络模型详解


卷积神经网络(convolutional neural network,CNN)

将空间不变性(spatial invariance)的这一概念系统化,从而基于这个模型使用较少的参数来学习有用的表示。

  • 平移不变性(translation invariance):不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。
  • 局部性(locality):神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,这就是“局部性”原则。最终,可以聚合这些局部特征,以在整个图像级别进行预测。

平移不变性(translation invariance)

现在引用上述的第一个原则:平移不变性。
这意味着检测对象在输入 $\mathbf{X}$ 中的平移,应该仅导致隐藏表示 $\mathbf{H}$ 中的平移。
也就是说,**$\mathbf{V}$** 和 $\mathbf{U}$ 实际上不依赖于 $X(i,j)$ 的位置,即

$$
V_{i,j,a,b} = V_{a,b}.
$$

并且 $\mathbf{U}$ 是一个常数,比如 $u$。
因此,我们可以简化 $\mathbf{H}$ 定义为:

$$
[H]{i,j} = u + \sum_a \sum_b [V]{a,b} [X]_{i+a, j+b}.
$$

这就是卷积(convolution)。
我们使用系数 $[V]{a,b}$ 对位置 $(i,j)$ 附近的像素 $[X]{i+a,j+b}$ 进行加权得到 $[H]_{i,j}$。

注意,$[V]{a,b}$ 的系数比 $[V]{i,j,a,b}$ 少很多,因为前者不再依赖于图像中的位置。这是显著的进步!

局部性(locality)

引用上述的第二个原则:局部性。
如上所述,为了收集用来训练参数 $[H]_{i,j}$ 的相关信息,我们不应偏离到距 $(i,j)$ 很远的地方。这意味着在

$$
|a| > \Delta \quad \text{或} \quad |b| > \Delta
$$

的范围之外,我们可以设置

$$
[V]_{a,b} = 0.
$$

因此,我们可以将 $[H]_{i,j}$ 重写为:

$$
[H]{i,j} = u + \sum{a=-\Delta}^{\Delta} \sum_{b=-\Delta}^{\Delta} [V]{a,b} [X]{i+a, j+b}.
$$

简而言之,是一个卷积层(convolutional layer),而卷积神经网络是包含卷积层的一类特殊神经网络。在深度学习研究社区中,**$V$** 被称为卷积核(convolution kernel)或者滤波器(filter),亦或简单称为该卷积层的权重,通常是可学习的参数。

当图像处理的局部区域很小时,卷积神经网络与多层感知机的训练差异可能巨大:以前多层感知机可能需要数十亿个参数来表示网络中的一层,而现在卷积神经网络通常只需几百个参数,而且不需要改变输入或隐藏表示的维数。

参数大幅减少的代价是,我们的特征现在是平移不变的,并且每个隐藏活性值确定时,每层只包含局部信息。以上所有权重学习都依赖归纳偏置。当偏置与现实相符时,我们能得到样本有效的模型,并能很好泛化;若偏置与现实不符(比如图像不满足平移不变性),模型可能难以拟合训练数据。

卷积

在进一步讨论前,先简要回顾为什么该操作称为卷积。
数学中,两个函数(比如 $f, g: \mathbb{R}^d \to \mathbb{R}$)的卷积定义为:

$$
(f * g)(\mathbf{x}) = \int f(\mathbf{z}) , g(\mathbf{x} - \mathbf{z}) , d\mathbf{z}.
$$

也就是说,卷积是在把一个函数“翻转”并平移 $\mathbf{x}$ 时,测量 $f$ 和 $g$ 之间的重叠。

离散情况下,积分变成求和。例如,对于索引为 $\mathbb{Z}$ 的平方可和无限维向量,我们有:

$$
(f * g)(i) = \sum_a f(a) , g(i - a).
$$

二维张量的卷积定义为:

$$
(f * g)(i,j) = \sum_a \sum_b f(a,b) , g(i - a, j - b).
$$

这看起来类似于

$$
Y_{i,j} = \sum_{a,b} W_{a,b} , X_{i+a, j+b},
$$

但有个主要区别:这里用的是索引差值 $(i - a, j - b)$,而不是 $(i + a, j + b)$。

这种区别是表面的。
我们在中的定义更准确地描述了互相关(cross-correlation)。

通道

这种方法有一个问题:我们忽略了图像一般包含三个通道(红、绿、蓝)。
实际上,图像是一个三维张量,比如尺寸为 $1024 \times 1024 \times 3$,其中前两个轴是空间位置,第三个轴是颜色通道。

因此,将张量 $X$ 索引为:

$$
X[i,j,k].
$$

对应地,卷积权重张量扩展为四维:

$$
V[a,b,c,d].
$$

此外,输入图像是三维的,隐藏表示 $H$ 也最好用三维张量表示:对每个空间位置采用一组隐藏表示(多个通道)。可以把隐藏表示想象为多个二维张量堆叠而成的通道(channel),也称为特征映射(feature maps)。

直观上,底层通道可能专注于边缘检测,其他通道检测纹理等。

多通道卷积层可表示为:

$$
H[i,j,d] = \sum_{a=-\Delta}^\Delta \sum_{b=-\Delta}^\Delta \sum_c V[a,b,c,d] \cdot X[i+a,j+b,c].
$$

其中隐藏表示 $H$ 中的索引 $d$ 是输出通道,输出继续作为三维张量输入下一卷积层。

定义了多通道卷积层,$V$ 是该层权重。

互相关运算

严格来说,卷积层是个错误的叫法,因为它所表达的运算其实是互相关运算(cross-correlation),而不是卷积运算。

感受野(Receptive Field)

参考
彻底搞懂感受野的含义与计算

神经网络中神经元“看到的”输入区域,在卷积神经网络中,feature map上某个元素的计算受输入图像上某个区域的影响,这个区域即该元素的感受野。感受野是个相对概念,某层feature map上的元素看到前面不同层上的区域范围是不同的,通常在不特殊指定的情况下,感受野指的是看到输入图像上的区域。

卷积核大小(Kernel size)

常用大小为 3×3、5×5,有时也用 1×1(用于通道变换和升维降维)。较小卷积核组合层数可以实现更大感受野,且参数更少。

汇聚层与卷积层的区别与作用总结

降维(减少空间尺寸),从而减少后续计算量和参数规模,提高模型对平移和微小变形的鲁棒性,助于防止过拟合,增强模型的泛化能力。

通过局部汇聚(最大池化或平均池化)提取局部显著信息,虽然会丢失部分细节,但保留最重要的特征。

卷积层(Convolutional Layer)与汇聚层的对比

方面 卷积层 汇聚层
参数 可训练的卷积核参数 无参数,固定的聚合操作
主要功能 特征提取和变换 降维与特征汇聚
降维方式 通过步长(stride)和填充(padding)调整输出尺寸 通过池化窗口大小和步长缩小空间尺寸
信息保留 保留更多细节和复杂特征 牺牲部分细节,保留局部显著特征
计算复杂度 相对较高 较低
对模型鲁棒性的影响 依赖卷积核设计和训练 提高对平移、小变形的鲁棒性

总结

从信息保留角度看,卷积层保留更多特征细节,且具有学习能力。
汇聚层主要用于降低空间维度和计算成本,并增强模型对输入变换的鲁棒性。两者在卷积神经网络中常常配合使用,互为补充。

图像卷积

卷积神经网络(convolutional neural network)是含有卷积层(convolutional layer)的神经网络。
基于图像的空间局部相关性假设——邻近像素更相关,局部特征更有意义。

conv 是 convolution(卷积) 的缩写,卷积是一个滑动窗口求加权和的过程。

互相关运算

卷积层得名于卷积(convolution)运算,但我们通常在卷积层中使用更加直观的互相关(cross-correlation)运算。在二维卷积层中,一个二维输入数组和一个二维核(kernel)数组通过互相关运算输出一个二维数组。
在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。

1
2
3
4
5
6
7
8
9
10
11
12
import torch 
from torch import nn
# X:输入特征图,形状 (H, W)。
# K:卷积核,形状 (h, w)
# Y:结果图,存放每个滑动窗口计算结果的二维张量
def corr2d(X, K):
h, w = K.shape # 取核的高、宽
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1)) # 固定形状,存放输出结果,赋0只是惯例无特殊含义
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum() # 取出与核同形状的窗口,与 K 做逐元素相乘后求和
return Y

卷积层

卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。卷积层的模型参数包括了卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。

1
2
3
4
5
6
7
8
9
10
# 继承 nn.Module,Conv2D 是一个神经网络模块
class Conv2D(nn.Module):
def __init__(self, kernel_size): # __init__构造函数接收 kernel_size(卷积核形状)
super(Conv2D, self).__init__() # 调用父类的初始化函数,确保 nn.Module 内部机制正常运行
self.weight = nn.Parameter(torch.randn(kernel_size)) # 卷积核的参数,是二维张量不是向量
self.bias = nn.Parameter(torch.randn(1)) # 偏置项

def forward(self, x): # 前向传播
return corr2d(x, self.weight) + self.bias

学习卷积核

卷积核也称为滤波器(Filter)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 构造二维卷积层,(输出通道,输入通道,卷积核形状,是否使用偏置)
# ps:PyTorch 的 Conv2d 参数是 [out_channels, in_channels, height, width],上面是自定义的
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)

# 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2

for i in range(10):
Y_hat = conv2d(X) # 前向传播
l = (Y_hat - Y) ** 2 # 保留逐元素的平方误差
conv2d.zero_grad() # 清空梯度
l.sum().backward() # 得到卷积核 conv2d.weight 的梯度
# 迭代卷积核
conv2d.weight.data[:] -= lr * conv2d.weight.grad # 更新卷积核参数
# .data[:] 表示对原张量的值做原地更新(不会创建新张量),不会被 autograd 记录,下面注释效果等价
# with torch.no_grad():
# conv2d.weight -= lr * conv2d.weight.grad
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')

特征映射和感受野

卷积层有时被称为特征映射(feature map),因为它可以被视为一个输入映射到下一层的空间维度的转换器。 在卷积神经网络中,对于某一层的任意元素
,其感受野(receptive field)是指在前向传播期间可能影响
计算的所有元素(来自所有先前层)。

填充(padding)和步幅(stride)

填充

填充(padding):在输入图像的边界填充元素(通常填充元素是0)

填充0并不能还原边缘的信息,而是让边缘能被卷积核覆盖到,不是恢复边缘,而是通过训练去拟合边缘区域应该如何处理。
卷积不降维控制特征图的空间维度(H × W)变化策略能保持特征图大小不变(对于残差连接、对齐输出特征很重要),逐层控制感受野增长速度(稳定局部特征提取速度),预防过快地降维(避免剧烈的信息损失)
填充0虽然不直接增加感受野,但通过保持尺寸不变与多层卷积堆叠,能让更深层的卷积核感受到更大范围的输入区域。

CNN强调从细节到语义的渐进式建构,而不是突变式压缩。逐步丢弃是必须的,快速丢弃是有害的。

  1. 低层特征是高层语义的基础,图像语义的形成是逐层抽象的过程,如果低层细节在前几层就被池化或卷积快速抹去,那么深层根本没机会学习“从边缘->形状->物体”的演化路径。
  2. 很多任务需要细节,图像分割、图像修复、关键点检测等任务对局部精度要求极高。
  3. 梯度传播与训练稳定性,太快压缩特征图尺寸,会导致梯度传回路径过窄,信息瓶颈造成特征通道表达受限,导致训练困难。

卷积神经网络中卷积核的高度和宽度通常为奇数,例如1、3、5或7。 选择奇数的好处是,保持空间维度的同时,我们可以在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列。

此外,使用奇数的核大小和填充大小也提供了书写上的便利。对于任何二维张量X,当满足: 1. 卷积核的大小是奇数; 2. 所有边的填充行数和列数相同; 3. 输出与输入具有相同高度和宽度 则可以得出:输出Y[i, j]是通过以输入X[i, j]为中心,与卷积核进行互相关计算得到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch
from torch import nn
# 对输入 X 调整维度以适配 nn.Conv2d 的输入格式,并返回去掉批量和通道维度的卷积结果
def comp_conv2d(conv2d, X):
# nn.Conv2d 的输入必须是 4D 张量(batch_size, channels, height, width),其他可选项不计
# X 是一个二维张量 (H, W),扩充到四维,batch_size = 1(1 张图)。channels = 1(单通道灰度图)
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# 省略前两个维度:批量大小和通道,只是为了符合 nn.Conv2d 的输入输出格式添加的
return Y.reshape(Y.shape[2:])

# 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
X = torch.rand(size=(8, 8))
print(comp_conv2d(conv2d, X).shape) # 输出张量形状

当卷积核的高度和宽度不同时,可以填充不同的高度和宽度,使输出和输入具有相同的高度和宽度。

1
2
3
# kernel_size高5宽3,padding表示上下两边各填充2行零左右两边各填充1行零,padding可选,nn.Conv2d本质传入还是4D
# kernel_size只有一个数会自动扩充成方阵
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))

步幅(Stride)

在卷积操作中,stride 是卷积核在输入特征图上滑动时每一步移动的像素数

有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。将每次滑动元素的数量称为步幅(stride)。

1
2
# 卷积层
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))

多输入多输出通道

添加通道时,我们的输入和隐藏的表示都变成了三维张量。

多输入通道

当输入包含多个通道时,需要构造一个与输入数据具有相同输入通道数的卷积核,以便与输入数据进行互相关运算。

1
2
3
4
5
6
7
8
9
10
11
import torch
def corr2d(X, K):
h, w = K.shape
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i+h, j:j+w] * K).sum()
return Y
def corr2d_multi_in(X, K):
# 先遍历"X"和"K"的第0个维度(通道维度),再把它们加在一起
return sum(corr2d(x, k) for x, k in zip(X, K))

多输出通道

随着神经网络层数的加深,我们常会增加输出通道的维数,通过减少空间分辨率以获得更大的通道深度。直观地说,我们可以将每个通道看作对不同特征的响应。

1
2
3
4
5
6
def corr2d_multi_in_out(X, K):
# X: 输入特征图,形状 (C_in, H_in, W_in)
# K: 单个输出通道的卷积核,形状(C_out, C_in, kH, kW)
# 把二维图按通道维度排列在一起,形成一个三维张量 (C, H, W)
# torch.stack 的第二个参数 dim=0 表示沿着第0维堆叠
return torch.stack([corr2d_multi_in(X, k) for k in K], 0)

1X1卷积层

看起来似乎没有多大意义。毕竟,卷积的本质是有效提取相邻像素间的相关特征,而1X1卷积显然没有此作用。尽管如此,仍然十分流行,经常包含在复杂深层网络的设计中。

总之就是展平为线性回归算了,不用考虑那么多

1
2
3
4
5
6
7
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape # 输入特征图的通道数、高度、宽度
c_o = K.shape[0] # 输出通道数
X = X.reshape((c_i, h * w)) # 将空间维展平成一维:每列是一个空间位置的通道向量
K = K.reshape((c_o, c_i)) # 将1x1卷积核权重展平为二维矩阵,行是输出通道,列是输入通道
Y = torch.matmul(K, X) # 矩阵乘法,等价于对每个空间位置做线性变换
return Y.reshape((c_o, h, w)) # 恢复空间维,输出多通道特征图

汇聚层

通常处理图像时,希望逐渐降低隐藏表示的空间分辨率、聚集信息,这样随着我们在神经网络中层叠的上升,每个神经元对其敏感的感受野(输入)就越大。
汇聚(pooling)层,它具有双重目的:降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。

最大汇聚层和平均汇聚层

池化是 CNN 中的一种空间汇聚操作。池化又叫下采样(Dwon sampling), 与之相对的是上采样(Up sampling). 卷积得到的特征图一般需要一个池化层以降低数据量

与卷积层类似,汇聚层运算符由一个固定形状的窗口组成,该窗口根据其步幅大小在输入的所有区域上滑动,为固定形状窗口(有时称为汇聚窗口)遍历的每个位置计算一个输出。 然而,不同于卷积层中的输入与卷积核之间的互相关计算,汇聚层不包含参数。 相反,池运算是确定性的,我们通常计算汇聚窗口中所有元素的最大值或平均值。这些操作分别称为最大汇聚层(maximum pooling)和平均汇聚层(average pooling)。

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
from torch import nn

def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size # 池化
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max': # 池化模式
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y

填充和步幅

汇聚层也可以改变输出形状。

1
2
X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4))
pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))

多个通道

在处理多通道输入数据时,汇聚层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着汇聚层的输出通道数与输入通道数相同。

1
X = torch.cat((X, X + 1), 1)  # 拼接

卷积神经网络(LeNet)

LeNet

LeNet(LeNet-5)由两个部分组成:

卷积编码器:由两个卷积层组成;

全连接层密集块:由三个全连接层组成。

为了将卷积块的输出传递给稠密块,我们必须在小批量中展平每个样本。换言之,我们将这个四维输入转换成全连接层所期望的二维输入。这里的二维表示的第一个维度索引小批量中的样本,第二个维度给出每个样本的平面向量表示。

在卷积神经网络中,常在若干卷积层后接一个池化层(总体趋势是逐渐降低分辨率,但方式不限于池化),以降低特征图的空间分辨率,从而减少计算量,并增强网络对图像小幅平移、缩放等变换的鲁棒性(不变性)。

权值共享是卷积神经网络(CNN)的一大核心特点。具体来说,每个卷积核(滤波器)在输入特征图上滑动,使用相同的一组权重进行卷积操作。因此,卷积层的参数数量只等于卷积核中权重的总数加上一个偏置项。相比于传统的全连接神经网络(如 BP 网络),这种权值共享机制大幅减少了需要训练的参数数量,从而降低了模型复杂度并有助于防止过拟合。

权值共享让卷积核参数在空间上”全局固定”,即用同一组局部滤波器权重在整个图上滑动,保证网络能高效且一致地捕捉图像中的局部特征。