从全连接层到卷积层:计算机视觉的数学基础

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

发布于 2025-08-05 更新于 2025-08-052,835 字 7 分钟
从全连接层到卷积层:计算机视觉的数学基础的文章封面
展开文章目录
  1. 1. 图像处理的挑战
  2. 1.1 传统方法的局限性
  3. 1.2 卷积神经网络的诞生
  4. 1.3 CNN的设计灵感
  5. 2. 计算机视觉的基本原理
  6. 2.1 不变性原理
  7. 2.2 局部性原理
  8. 3. 卷积层的数学推导
  9. 3.1 从MLP到卷积层的演进
  10. 3.2 应用平移不变性
  11. 3.3 应用局部性原理
  12. 3.4 卷积运算的本质
  13. 4. CNN相对于MLP的优势
  14. 4.1 效率对比
  15. 4.2 计算复杂度分析
  16. 5. 多通道卷积的数学表示
  17. 5.1 多通道输入输出
  18. 5.2 完整的卷积公式
  19. 5.3 多通道卷积的直观理解
  20. 6. 卷积层的实际应用
  21. 6.1 特征提取的层次性
  22. 6.2 感受野的概念
  23. 参考资料

1. 图像处理的挑战

1.1 传统方法的局限性

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

1.2 卷积神经网络的诞生

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

CNN应用领域

计算机视觉领域:

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

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

扩展应用领域:

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

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

1.3 CNN的设计灵感


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

2.1 不变性原理

人类视觉的不变性

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

不变性因素

几何变换不变性:

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

环境因素不变性:

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

计算机视觉中的不变性

2.2 局部性原理

人类视觉的局部性

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

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

计算机视觉中的局部性


3. 卷积层的数学推导

3.1 从MLP到卷积层的演进

第一步:保持空间结构

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

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

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

[H]i,j=kl[W]i,j,k,l[X]k,l+[U]i,j[\mathbf{H}]_{i,j} = \sum_k \sum_l [\mathsf{W}]_{i,j,k,l} [\mathbf{X}]_{k,l} + [\mathbf{U}]_{i,j}

参数说明

参数解释1

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

第二步:引入相对位置

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

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

[H]i,j=ab[V]i,j,a,b[X]i+a,j+b+[U]i,j[\mathbf{H}]_{i,j} = \sum_a \sum_b [\mathsf{V}]_{i,j,a,b} [\mathbf{X}]_{i+a,j+b} + [\mathbf{U}]_{i,j}

3.2 应用平移不变性

平移不变性的数学表达

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

[H]i,j=ab[V]a,b[X]i+a,j+b+u[\mathbf{H}]_{i,j} = \sum_a \sum_b [\mathsf{V}]_{a,b} [\mathbf{X}]_{i+a,j+b} + u

其中,uu应为一个常数。

3.3 应用局部性原理

局部连接的实现

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

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

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

[H]i,j=a=ΔΔb=ΔΔ[V]a,b[X]i+a,j+b+u[\mathbf{H}]_{i,j} = \sum_{a=-\Delta}^{\Delta} \sum_{b=-\Delta}^{\Delta} [\mathbf{V}]_{a,b} [\mathbf{X}]_{i+a,j+b} + u

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

特殊情况分析

3.4 卷积运算的本质


4. CNN相对于MLP的优势

4.1 效率对比

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

CNN优势

参数数量优势:

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

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

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

先验假设优势:

  • 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 多通道输入输出

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

符号定义

符号定义

空间维度索引:

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

通道维度索引:

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

5.2 完整的卷积公式

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

[H]i,j,d=a=ΔΔb=ΔΔc[V]a,b,c,d[X]i+a,j+b,c[\mathsf{H}]_{i,j,d} = \sum_{a=-\Delta}^{\Delta} \sum_{b=-\Delta}^{\Delta} \sum_{c} [\mathsf{V}]_{a,b,c,d} [\mathsf{X}]_{i+a,j+b,c}

参数详细说明

参数解释2

输出特征:

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

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

卷积核权重:

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

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

输入像素:

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

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

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

讨论与回应

欢迎补充细节、指出问题,或分享与这篇文章有关的经验。

昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。

正在准备留言区…

演示赞赏界面

谢谢你愿意支持长期写作。

第一阶段不会发起支付或收集任何信息。接入真实赞赏渠道后,这里会展示清楚的金额、渠道和完成状态。

输入关键词开始搜索 · 按 Esc 关闭
打开完整搜索页 →