本文基于d2l项目内容整理,介绍从全连接层到卷积层的演进过程,深入探讨卷积神经网络的数学原理和设计思想。

1. 图像处理的挑战

1.1 传统方法的局限性

可用矩阵表示二维图像,根据色彩模式的不同,每一个像素由单值或多值组成。若采取先前所用的技术,直接将图像像素简单地展平,将打乱特征像素的空间顺序。

传统全连接层的问题:

直接将图像展平输入全连接层会丢失重要的空间信息,这对于图像理解来说是致命的。显然,最佳策略应保留像素间的位置关系。

1.2 卷积神经网络的诞生

卷积神经网络 (Convolutional Neural Network, CNN) 为图像而生,用卷积层捕捉局部特征,能处理多种结构化数据且所用的参数更少。

计算机视觉领域:

  • 图像识别
  • 目标检测
  • 语义分割
  • 图像生成

CNN是计算机视觉领域处于主导地位的网络架构。

扩展应用领域:

  • 序列数据:音频、文本和时间序列分析
  • 图结构数据:社交网络分析
  • 推荐系统:用户行为建模
  • 自然语言处理:文本分类和情感分析

CNN也逐渐在这些领域备受欢迎。

1.3 CNN的设计灵感

CNN的设计灵感来源:

  1. 生物学启发:CNN模拟视觉皮层神经元,通过局部感受野和共享权重的方式实现局部感知

  2. 数学理论:群论 (Group Theory) 视角有助于解释卷积操作的平移不变性

  3. 工程优化:网络深度、卷积核大小、池化层设计等研究,极大地推动了CNN的性能提升


2. 计算机视觉的基本原理

2.1 不变性原理

人类视觉的不变性

对于人类视觉而言,从图像中识别特定物体时,目标物体是否能被识别,只取决于物体的局部特征的上下文信息,而与以下因素无关:

几何变换不变性:

  • 位置:物体在图像中的位置
  • 缩放:物体的大小变化
  • 旋转:物体的方向改变
  • 形变:物体的轻微变形

环境因素不变性:

  • 光照:光线条件的变化
  • 遮挡:物体被部分遮挡
  • 背景:不同的背景环境
  • 噪声:图像中的噪声干扰

计算机视觉中的不变性

CNN的不变性设计:

计算机视觉网络架构也应支持这种不变性,只要物体保留了大部分局部特征,基于前几层的局部感受野和权重共享机制,算法都能产生一致性的响应输出。

本文主要讨论位置不变性(平移不变性)。

2.2 局部性原理

人类视觉的局部性

人类阅读一张图片的方式往往是:

  1. 先观察图片中几个关键的位置
  2. 分析局部区域的特征
  3. 最后综合考虑在整张图像中的含义

计算机视觉中的局部性

CNN的局部性设计:

计算机视觉网络架构也应如此:

  • 网络的前几层只探索局部区域
  • 不过度关注较远区域间的关系
  • 最后通过聚合多个局部特征,完成对完整图像的预测

3. 卷积层的数学推导

3.1 从MLP到卷积层的演进

第一步:保持空间结构

从MLP开始。若将二维图像$\mathbf{X}$作为MLP的输入,同时使隐藏表示$\mathbf{H}$具有与$\mathbf{X}$相同的形状,保证图像中的每个像素$[\mathsf{X}]{i,j}$与$[\mathsf{H}]{i,j}$一一对应。

为了考虑像素间的空间结构与相对位置关系,用每个像素点的位置$(k,l)$加权隐藏表示中各神经元的位置$(i,j)$,得到新的四阶权重张量$[\mathsf{W}]_{i,j,k,l}$。

隐藏层$(i,j)$处的神经元激活值的数学表示如下:

参数说明

  • $[\mathbf{X}]_{k,l}$:输入图像$(k,l)$处的像素值
  • $[\mathbf{H}]_{i,j}$:隐藏层$(i,j)$处的神经元激活值
  • $[\mathbf{U}]_{i,j}$:隐藏层$(i,j)$处的偏置项
  • $[\mathsf{W}]_{i,j,k,l}$:连接输入位置$(k,l)$到隐藏层位置$(i,j)$的权重
  • $\sum_k \sum_l$:对图像的所有像素位置$(k,l)$进行加权求和

第二步:引入相对位置

为了将像素位置$(k,l)$用相对于$(i,j)$的偏移量表示,令$k = i + a$、$l = j + b$。于是,$[\mathsf{W}]{i,j,k,l}$被重新表示为$[\mathsf{V}]{i,j,a,b}$。

隐藏层$(i,j)$处的神经元激活值可继续用新的形式表示为:

3.2 应用平移不变性

平移不变性的数学表达

平移不变性是计算机视觉中的基本原理之一。即,位置参数$i$、$j$与隐藏层$(i,j)$处的神经元激活值无关:

其中,$u$应为一个常数。

平移不变性的含义:

这意味着无论特征出现在图像的哪个位置,卷积核都会产生相同的响应。这正是权重共享的数学基础。

3.3 应用局部性原理

局部连接的实现

基于局部性基本原理,神经元只需要关注输入图像的局部区域,而不是整个图像。为此,引入一个距离参数$\Delta$。

当偏移量超过$\Delta$,即$|a| > \Delta$或$|b| > \Delta$时,超过部分的像素对当前神经元的计算无影响,$[\mathbf{V}]_{a,b} = 0$。

这样,隐藏层$(i,j)$处的神经元激活值可继续表示为:

其中,$\sum{…=-\Delta}^{\Delta}$表示权重$[\mathbf{V}]{a,b}$与覆盖的窗口大小$[-\Delta, \Delta]$间像素的加权求和。

特殊情况分析

退化情况:

当$\Delta = 0$时,卷积核尺寸为$1 \times 1$,此时CNN将退化成全连接层的形式。这说明全连接层是卷积层的一个特例。

3.4 卷积运算的本质

卷积运算的核心思想:

局部加权求和的思想是卷积运算的本质。以上设计的MLP变体被称为卷积神经网络 (CNN),这种形式的隐藏层表示被称为卷积层 (convolutional layer),其中的权重$[\mathsf{V}]_{a,b}$即为卷积核 (convolution kernel) 或滤波器 (filter)。


4. CNN相对于MLP的优势

4.1 效率对比

当卷积核的尺寸相对于整幅图像较小时,CNN往往比MLP更快、更高效:

参数数量优势:

  • MLP:每个神经元与输入的特征以独立权重连接,参数数量随输入尺寸线性增长
  • CNN:卷积核与输入矩阵只发生局部连接且共享权重,参数数量只取决于卷积核的尺寸

举例:对于$224 \times 224$的RGB图像

  • MLP第一层需要$224 \times 224 \times 3 = 150,528$个权重参数
  • CNN使用$3 \times 3$卷积核只需要$3 \times 3 \times 3 = 27$个权重参数

先验假设优势:

  • MLP:设计中不包含显式的先验假设,依赖多样的数据完成训练
  • CNN:设计了位置不变性和局部性的先验假设,减少了模型对数据的依赖

这使得CNN能够更快地收敛,并且在数据量较少的情况下也能取得良好的性能。

4.2 计算复杂度分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import torch
import torch.nn as nn
import time

def compare_mlp_cnn():
"""比较MLP和CNN的参数量和计算时间"""

# 输入图像尺寸
batch_size, channels, height, width = 32, 3, 224, 224
input_tensor = torch.randn(batch_size, channels, height, width)

# MLP模型(展平输入)
mlp = nn.Sequential(
nn.Flatten(),
nn.Linear(channels * height * width, 1000),
nn.ReLU(),
nn.Linear(1000, 10)
)

# CNN模型
cnn = nn.Sequential(
nn.Conv2d(channels, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(64, 10)
)

# 计算参数量
mlp_params = sum(p.numel() for p in mlp.parameters())
cnn_params = sum(p.numel() for p in cnn.parameters())

print(f"MLP参数量: {mlp_params:,}")
print(f"CNN参数量: {cnn_params:,}")
print(f"参数量比例: {mlp_params / cnn_params:.2f}:1")

# 计算前向传播时间
with torch.no_grad():
# MLP
start_time = time.time()
_ = mlp(input_tensor)
mlp_time = time.time() - start_time

# CNN
start_time = time.time()
_ = cnn(input_tensor)
cnn_time = time.time() - start_time

print(f"MLP前向传播时间: {mlp_time:.4f}s")
print(f"CNN前向传播时间: {cnn_time:.4f}s")

# 运行比较
compare_mlp_cnn()

5. 多通道卷积的数学表示

5.1 多通道输入输出

为了兼容色彩模式中的不同颜色通道,我们需要扩展卷积的数学表示。

符号定义

空间维度索引:

  • $i$和$j$:二维矩阵各个像素的位置,或隐藏表示中各个神经元的位置(卷积后的矩阵值索引)
  • $a$和$b$:卷积核滑动时在上下左右方向的偏移量

通道维度索引:

  • $c$:输入的通道索引,即多通道输入像素的索引
  • $d$:输出的通道索引,即卷积层输出的不同特征图索引

5.2 完整的卷积公式

卷积层从输入图像到隐藏层特征的映射如下:

参数详细说明

输出特征:

$[\mathsf{H}]_{i,j,d}$:输出的隐藏表示(或特征图)在$(i,j,d)$位置的神经元激活值

  • $(i,j)$:空间位置
  • $d$:第$d$个输出通道(特征图)

卷积核权重:

$[\mathsf{V}]_{a,b,c,d}$:卷积核在$(a,b)$位置、第$c$个输入通道、第$d$个输出通道的权重

  • $(a,b)$:卷积核内的相对位置
  • $c$:输入通道索引
  • $d$:输出通道索引

输入像素:

$[\mathsf{X}]_{i+a,j+b,c}$:输入图像在$(i+a,j+b)$处、第$c$个通道的像素值

  • $(i+a,j+b)$:输入图像的绝对位置
  • $c$:输入通道索引

5.3 多通道卷积的直观理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import torch
import torch.nn as nn

def demonstrate_multichannel_conv():
"""演示多通道卷积的计算过程"""

# 创建一个简单的多通道输入
# 形状: (batch_size, input_channels, height, width)
input_tensor = torch.randn(1, 3, 5, 5) # RGB图像

# 创建卷积层:3个输入通道,2个输出通道,3x3卷积核
conv_layer = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=3, padding=1)

# 前向传播
output = conv_layer(input_tensor)

print(f"输入形状: {input_tensor.shape}")
print(f"输出形状: {output.shape}")
print(f"卷积核权重形状: {conv_layer.weight.shape}")
print(f"偏置形状: {conv_layer.bias.shape}")

# 手动验证卷积计算(简化版本)
with torch.no_grad():
# 获取卷积核权重和偏置
weight = conv_layer.weight # 形状: (out_channels, in_channels, kernel_h, kernel_w)
bias = conv_layer.bias # 形状: (out_channels,)

print(f"\n卷积核权重详细形状解释:")
print(f"- 输出通道数: {weight.shape[0]}")
print(f"- 输入通道数: {weight.shape[1]}")
print(f"- 卷积核高度: {weight.shape[2]}")
print(f"- 卷积核宽度: {weight.shape[3]}")

# 运行演示
demonstrate_multichannel_conv()

6. 卷积层的实际应用

6.1 特征提取的层次性

CNN的层次特征提取:

  1. 浅层:检测边缘、角点等低级特征
  2. 中层:组合低级特征形成纹理、形状等中级特征
  3. 深层:组合中级特征形成语义级别的高级特征

这种层次性特征提取是CNN强大表达能力的关键。

6.2 感受野的概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def calculate_receptive_field():
"""计算卷积网络的感受野"""

# 简单的CNN结构
layers = [
{'type': 'conv', 'kernel_size': 3, 'stride': 1, 'padding': 1},
{'type': 'conv', 'kernel_size': 3, 'stride': 1, 'padding': 1},
{'type': 'pool', 'kernel_size': 2, 'stride': 2, 'padding': 0},
{'type': 'conv', 'kernel_size': 3, 'stride': 1, 'padding': 1},
]

# 计算感受野
receptive_field = 1
jump = 1

print("层级\t类型\t感受野大小")
print("-" * 30)
print(f"输入\t-\t{receptive_field}")

for i, layer in enumerate(layers):
if layer['type'] == 'conv':
receptive_field += (layer['kernel_size'] - 1) * jump
elif layer['type'] == 'pool':
receptive_field += (layer['kernel_size'] - 1) * jump
jump *= layer['stride']

print(f"第{i+1}层\t{layer['type']}\t{receptive_field}")

# 运行计算
calculate_receptive_field()

总结

从全连接层到卷积层的演进体现了深度学习中重要的设计思想:

  1. 数学推导:通过严格的数学推导,从MLP逐步演进到CNN
  2. 设计原理:平移不变性和局部性是CNN设计的核心原理
  3. 效率优势:CNN通过权重共享和局部连接大大减少了参数量
  4. 实际应用:多通道卷积为处理彩色图像提供了数学基础

理解这个演进过程有助于我们更好地设计和优化卷积神经网络,为后续学习更复杂的CNN架构打下坚实的理论基础。