从全连接层到卷积层:计算机视觉的数学基础
本文讲述从全连接层到卷积层的演进过程,掌握卷积神经网络的数学原理和设计思想

展开文章目录
1. 图像处理的挑战
1.1 传统方法的局限性
可用矩阵表示二维图像,根据色彩模式的不同,每一个像素由单值或多值组成。若采取先前所用的技术,直接将图像像素简单地展平,将打乱特征像素的空间顺序。
1.2 卷积神经网络的诞生
卷积神经网络 (Convolutional Neural Network, CNN) 为图像而生,用卷积层捕捉局部特征,能处理多种结构化数据且所用的参数更少。
CNN应用领域
计算机视觉领域:
- 图像识别
- 目标检测
- 语义分割
- 图像生成
CNN是计算机视觉领域处于主导地位的网络架构。
扩展应用领域:
- 序列数据:音频、文本和时间序列分析
- 图结构数据:社交网络分析
- 推荐系统:用户行为建模
- 自然语言处理:文本分类和情感分析
CNN也逐渐在这些领域备受欢迎。
1.3 CNN的设计灵感
2. 计算机视觉的基本原理
2.1 不变性原理
人类视觉的不变性
对于人类视觉而言,从图像中识别特定物体时,目标物体是否能被识别,只取决于物体的局部特征的上下文信息,而与以下因素无关:
不变性因素
几何变换不变性:
- 位置:物体在图像中的位置
- 缩放:物体的大小变化
- 旋转:物体的方向改变
- 形变:物体的轻微变形
环境因素不变性:
- 光照:光线条件的变化
- 遮挡:物体被部分遮挡
- 背景:不同的背景环境
- 噪声:图像中的噪声干扰
计算机视觉中的不变性
2.2 局部性原理
人类视觉的局部性
人类阅读一张图片的方式往往是:
- 先观察图片中几个关键的位置
- 分析局部区域的特征
- 最后综合考虑在整张图像中的含义
计算机视觉中的局部性
3. 卷积层的数学推导
3.1 从MLP到卷积层的演进
第一步:保持空间结构
从MLP开始。若将二维图像作为MLP的输入,同时使隐藏表示具有与相同的形状,保证图像中的每个像素与一一对应。
为了考虑像素间的空间结构与相对位置关系,用每个像素点的位置加权隐藏表示中各神经元的位置,得到新的四阶权重张量。
隐藏层处的神经元激活值的数学表示如下:
参数说明
参数解释1
- :输入图像处的像素值
- :隐藏层处的神经元激活值
- :隐藏层处的偏置项
- :连接输入位置到隐藏层位置的权重
- :对图像的所有像素位置进行加权求和
第二步:引入相对位置
为了将像素位置用相对于的偏移量表示,令、。于是,被重新表示为。
隐藏层处的神经元激活值可继续用新的形式表示为:
3.2 应用平移不变性
平移不变性的数学表达
平移不变性是计算机视觉中的基本原理之一。即,位置参数、与隐藏层处的神经元激活值无关:
其中,应为一个常数。
3.3 应用局部性原理
局部连接的实现
基于局部性基本原理,神经元只需要关注输入图像的局部区域,而不是整个图像。为此,引入一个距离参数。
当偏移量超过,即或时,超过部分的像素对当前神经元的计算无影响,。
这样,隐藏层处的神经元激活值可继续表示为:
其中,表示权重与覆盖的窗口大小间像素的加权求和。
特殊情况分析
3.4 卷积运算的本质
4. CNN相对于MLP的优势
4.1 效率对比
当卷积核的尺寸相对于整幅图像较小时,CNN往往比MLP更快、更高效:
CNN优势
参数数量优势:
- MLP:每个神经元与输入的特征以独立权重连接,参数数量随输入尺寸线性增长
- CNN:卷积核与输入矩阵只发生局部连接且共享权重,参数数量只取决于卷积核的尺寸
举例:对于的RGB图像
- MLP第一层需要个权重参数
- CNN使用卷积核只需要个权重参数
先验假设优势:
- MLP:设计中不包含显式的先验假设,依赖多样的数据完成训练
- CNN:设计了位置不变性和局部性的先验假设,减少了模型对数据的依赖
这使得CNN能够更快地收敛,并且在数据量较少的情况下也能取得良好的性能。
4.2 计算复杂度分析
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 多通道输入输出
为了兼容色彩模式中的不同颜色通道,我们需要扩展卷积的数学表示。
符号定义
符号定义
空间维度索引:
- 和:二维矩阵各个像素的位置,或隐藏表示中各个神经元的位置(卷积后的矩阵值索引)
- 和:卷积核滑动时在上下左右方向的偏移量
通道维度索引:
- :输入的通道索引,即多通道输入像素的索引
- :输出的通道索引,即卷积层输出的不同特征图索引
5.2 完整的卷积公式
卷积层从输入图像到隐藏层特征的映射如下:
参数详细说明
参数解释2
输出特征:
:输出的隐藏表示(或特征图)在位置的神经元激活值
- :空间位置
- :第个输出通道(特征图)
卷积核权重:
:卷积核在位置、第个输入通道、第个输出通道的权重
- :卷积核内的相对位置
- :输入通道索引
- :输出通道索引
输入像素:
:输入图像在处、第个通道的像素值
- :输入图像的绝对位置
- :输入通道索引
5.3 多通道卷积的直观理解
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 特征提取的层次性
6.2 感受野的概念
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()
参考资料
如果这篇记录对你有帮助,可以留下一句回应。

DISCUSSION
讨论与回应
欢迎补充细节、指出问题,或分享与这篇文章有关的经验。
昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。