图像处理
大约 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)。
- 教学点:MNIST 是灰度图,只有 1 个通道。如果是彩色图 (RGB),这里应该是 3。这对应之前实训中的
6(out_channels):输出通道数。- 教学点:意味着网络学习了 6 种不同的特征滤波器。有的可能检测横线,有的检测竖线。
kernel_size=5:卷积核大小为 5x5。- 教学点:这是一个滑动窗口,每次看 5x5 的区域。
padding=2:填充 2 圈像素(通常填 0)。- 教学点:为什么是 2? 为了保持输入输出尺寸一致。
- 计算公式:
- 代入:。输入 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)))
conv1(x):- 输入:
(N, 1, 28, 28) - 操作:6 个 5x5 卷积核,padding=2。
- 输出:
(N, 6, 28, 28) - 解释:通道数从 1 变 6,尺寸不变。
- 输入:
relu(...):- 形状不变,但将负值变为 0。
pool(...):- 输入:
(N, 6, 28, 28) - 操作:2x2 最大池化。
- 输出:
(N, 6, 14, 14) - 解释:长宽减半 (28→14)。
- 输入:
3.2 第二块:卷积 + 激活 + 池化
x = self.pool(self.relu(self.conv2(x)))
conv2(x):- 输入:
(N, 6, 14, 14) - 操作:16 个 5x5 卷积核,padding=0。
- 计算:。
- 输出:
(N, 16, 10, 10) - 解释:通道数从 6 变 16,尺寸缩小 (14→10)。
- 输入:
relu(...):- 形状不变。
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 维张量) - 操作:将后三维压成一维。
- 计算:。
- 输出:
(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 × 28 | 6 × 28 × 28 | 6 个滤波器,Padding 保持尺寸 |
| Pool1 | 池化 | 6 × 28 × 28 | 6 × 14 × 14 | 尺寸减半 |
| Conv2 | 卷积 | 6 × 14 × 14 | 16 × 10 × 10 | 16 个滤波器,无 Padding 尺寸缩小 |
| Pool2 | 池化 | 16 × 10 × 10 | 16 × 5 × 5 | 尺寸减半 |
| Flat | 展平 | 16 × 5 × 5 | 400 | 3 维变 1 维 |
| FC1 | 全连接 | 400 | 120 | 特征压缩 |
| FC2 | 全连接 | 120 | 84 | 特征压缩 |
| FC3 | 输出层 | 84 | 10 | 对应 10 个数字类别 |
5. 教学思考题 (用于课堂互动)
在讲解完这段代码后,可以抛出以下问题检查学生理解程度:
- 如果把
padding=2去掉,代码会报错吗?- 答案:不会报错,但
fc1的输入维度会变。 - 推导:Conv1 输出变 24x24 -> Pool1 变 12x12 -> Conv2 输出变 8x8 -> Pool2 变 4x4。
- 后果:
fc1需要改为16 * 4 * 4 = 256,否则维度不匹配报错。
- 答案:不会报错,但
- 为什么
conv2的输入通道是 6?- 答案:因为
conv1的输出通道是 6。上一层的输出必须是下一层的输入,就像水管接口一样。
- 答案:因为
- 如果想识别彩色图片 (如 CIFAR-10),哪里需要改?
- 答案:
conv1的输入通道从1改为3。
- 答案:
view(-1, ...)中的-1是什么意思?- 答案:自动推断。这里指 Batch_Size (N)。无论一次输入多少张图片,它都能自动适应。
6. 与之前实训的联系
conv1的权重 OpenCV 的 Sobel 算子。- 在之前的实训中,我们手工定义了 Sobel 矩阵来提取边缘。
- 在 CNN 中,
conv1.weight是随机初始化的,通过训练自动变成了类似 Sobel 的矩阵。
pool层cv2.resize缩小图片。- 之前我们用
resize加速处理。 - CNN 用池化层在保留特征的前提下缩小尺寸。
- 之前我们用
fc层 逻辑回归分类器。- 将提取好的特征进行最终的打分决策。
深度学习不是魔法,而是由一个个可计算的数学层(卷积、池化、全连接)堆叠而成的自动化特征工程管道。
