跳至主要內容

图像处理

周子力大约 6 分钟教学文档Python基础

卷积神经网络简介

卷积神经网络代码

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # 第一层卷积:输入 1 通道 (灰度), 输出 6 通道,核大小 5x5
        self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2)
        self.pool = nn.MaxPool2d(2, 2)  # 2x2 池化
        # 第二层卷积:输入 6 通道,输出 16 通道,核大小 5x5
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5)
        # 全连接层
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)  # 输出 10 个数字类别
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.pool(self.relu(self.conv1(x)))  # 卷积 -> 激活 -> 池化
        x = self.pool(self.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)  # 展平
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)
        return x




📘 SimpleCNN 代码深度解析

1. 类结构概览

class SimpleCNN(nn.Module):
  • 继承 nn.Module:这是 PyTorch 中所有神经网络模型的基类。继承它意味着这个类拥有了管理参数、GPU 加速、反向传播等“超能力”。
  • 两个核心方法
    • __init__“建房”。定义网络有哪些层(砖块、水泥、窗户)。
    • forward“流水”。定义数据如何流经这些层(从大门进,经过客厅,最后从窗户出)。

2. __init__ 方法:搭建网络架构

这部分定义了网络的静态结构

2.1 第一层卷积 self.conv1

self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2)
  • nn.Conv2d:二维卷积层,处理图像(高 x 宽)。
  • 1 (in_channels):输入通道数。
    • 教学点:MNIST 是灰度图,只有 1 个通道。如果是彩色图 (RGB),这里应该是 3。这对应之前实训中的 cv2.cvtColor(..., cv2.COLOR_BGR2GRAY)
  • 6 (out_channels):输出通道数。
    • 教学点:意味着网络学习了 6 种不同的特征滤波器。有的可能检测横线,有的检测竖线。
  • kernel_size=5:卷积核大小为 5x5。
    • 教学点:这是一个滑动窗口,每次看 5x5 的区域。
  • padding=2:填充 2 圈像素(通常填 0)。
    • 教学点为什么是 2? 为了保持输入输出尺寸一致。
    • 计算公式:Output=InputKernel+2×PaddingStride+1Output = \frac{Input - Kernel + 2 \times Padding}{Stride} + 1
    • 代入:(285+2×2)/1+1=28(28 - 5 + 2 \times 2) / 1 + 1 = 28。输入 28x28,输出还是 28x28,保留了边界信息。

2.2 池化层 self.pool

self.pool = nn.MaxPool2d(2, 2)
  • MaxPool2d:最大池化。
  • 2, 2:核大小 2x2,步长 2。
  • 作用:将图片长宽各缩小一半。
    • 教学点:这是一种降采样。保留最显著的特征(最大值),忽略细节,减少计算量,并提供平移不变性。

2.3 第二层卷积 self.conv2

self.conv2 = nn.Conv2d(6, 16, kernel_size=5)
  • 6 (in_channels):必须与上一层 conv1 的输出通道数一致。
  • 16 (out_channels):特征数量增加到 16 个。
    • 教学点:随着网络加深,我们提取的特征更复杂(从边缘变成了纹理或部件)。
  • padding 默认 0:这里没有填充。
    • 教学点:图片尺寸会缩小。

2.4 全连接层 self.fc1, fc2, fc3

self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
  • nn.Linear:传统的全连接神经网络层。
  • 16 * 5 * 5:输入特征数。
    • 关键问题这个数字怎么来的? (见下文维度计算部分)。
  • 10:输出类别数。
    • 教学点:MNIST 有 0-9 共 10 个数字,所以输出层必须有 10 个神经元,分别代表属于每个数字的概率。

2.5 激活函数 self.relu

self.relu = nn.ReLU()
  • 作用:引入非线性
  • 教学点:如果没有它,多层网络就等价于单层线性网络,无法拟合复杂曲线。ReLU 让网络能够学习“是或否”的开关特性。

3. forward 方法:数据流动与维度变换

这部分是动态计算图。我们需要追踪 Tensor 的形状 (Shape) 变化,这是理解 CNN 最关键的部分。

假设输入一张图片 x,形状为 (Batch_Size, 1, 28, 28)(注:Batch_Size 表示一次处理多少张图,这里假设为 N)

3.1 第一块:卷积 + 激活 + 池化

x = self.pool(self.relu(self.conv1(x)))
  1. conv1(x):
    • 输入:(N, 1, 28, 28)
    • 操作:6 个 5x5 卷积核,padding=2。
    • 输出:(N, 6, 28, 28)
    • 解释:通道数从 1 变 6,尺寸不变。
  2. relu(...):
    • 形状不变,但将负值变为 0。
  3. pool(...):
    • 输入:(N, 6, 28, 28)
    • 操作:2x2 最大池化。
    • 输出:(N, 6, 14, 14)
    • 解释:长宽减半 (28→14)。

3.2 第二块:卷积 + 激活 + 池化

x = self.pool(self.relu(self.conv2(x)))
  1. conv2(x):
    • 输入:(N, 6, 14, 14)
    • 操作:16 个 5x5 卷积核,padding=0。
    • 计算:(145)/1+1=10(14 - 5) / 1 + 1 = 10
    • 输出:(N, 16, 10, 10)
    • 解释:通道数从 6 变 16,尺寸缩小 (14→10)。
  2. relu(...):
    • 形状不变。
  3. pool(...):
    • 输入:(N, 16, 10, 10)
    • 操作:2x2 最大池化。
    • 输出:(N, 16, 5, 5)
    • 解释:长宽再次减半 (10→5)。

3.3 展平 (Flatten)

x = x.view(-1, 16 * 5 * 5)
  • 输入(N, 16, 5, 5) (4 维张量)
  • 操作:将后三维压成一维。
  • 计算16×5×5=40016 \times 5 \times 5 = 400
  • 输出(N, 400) (2 维张量)
  • 教学点:全连接层只能处理一维向量。这一步将“空间特征图”变成了“特征向量”。这里必须与 fc1 的输入维度 16*5*5 严格对应,否则会报错。

3.4 全连接分类

x = self.relu(self.fc1(x))  # (N, 400) -> (N, 120)
x = self.relu(self.fc2(x))  # (N, 120) -> (N, 84)
x = self.fc3(x)             # (N, 84)  -> (N, 10)
  • 最终输出 (N, 10),代表这张图属于 0-9 每个类别的得分(Logits)。

4. 维度变化可视化总结表

建议将此表展示给学生,这是调试 CNN 维度错误的“救命稻草”。

层级操作输入形状 (CHW)输出形状 (CHW)参数说明
Input原始图片1 × 28 × 28-灰度图
Conv1卷积1 × 28 × 286 × 28 × 286 个滤波器,Padding 保持尺寸
Pool1池化6 × 28 × 286 × 14 × 14尺寸减半
Conv2卷积6 × 14 × 1416 × 10 × 1016 个滤波器,无 Padding 尺寸缩小
Pool2池化16 × 10 × 1016 × 5 × 5尺寸减半
Flat展平16 × 5 × 54003 维变 1 维
FC1全连接400120特征压缩
FC2全连接12084特征压缩
FC3输出层8410对应 10 个数字类别

5. 教学思考题 (用于课堂互动)

在讲解完这段代码后,可以抛出以下问题检查学生理解程度:

  1. 如果把 padding=2 去掉,代码会报错吗?
    • 答案:不会报错,但 fc1 的输入维度会变。
    • 推导:Conv1 输出变 24x24 -> Pool1 变 12x12 -> Conv2 输出变 8x8 -> Pool2 变 4x4。
    • 后果fc1 需要改为 16 * 4 * 4 = 256,否则维度不匹配报错。
  2. 为什么 conv2 的输入通道是 6?
    • 答案:因为 conv1 的输出通道是 6。上一层的输出必须是下一层的输入,就像水管接口一样。
  3. 如果想识别彩色图片 (如 CIFAR-10),哪里需要改?
    • 答案conv1 的输入通道从 1 改为 3
  4. view(-1, ...) 中的 -1 是什么意思?
    • 答案:自动推断。这里指 Batch_Size (N)。无论一次输入多少张图片,它都能自动适应。

6. 与之前实训的联系

  • conv1 的权重 \approx OpenCV 的 Sobel 算子
    • 在之前的实训中,我们手工定义了 Sobel 矩阵来提取边缘。
    • 在 CNN 中,conv1.weight 是随机初始化的,通过训练自动变成了类似 Sobel 的矩阵。
  • pool \approx cv2.resize 缩小图片
    • 之前我们用 resize 加速处理。
    • CNN 用池化层在保留特征的前提下缩小尺寸。
  • fc \approx 逻辑回归分类器
    • 将提取好的特征进行最终的打分决策。

深度学习不是魔法,而是由一个个可计算的数学层(卷积、池化、全连接)堆叠而成的自动化特征工程管道。

上次编辑于:
贡献者: zilizhou