【深度学习】CNN(卷积神经网络)从零开始教程
写在前面
这篇文档不假设你有任何 CNN 基础。我们会从"人眼怎么看图片"出发,一步一步引出 CNN 的每个核心概念,让你读完后能说清楚:卷积在干什么、为什么要池化、BatchNorm 有什么用、Dropout 怎么防过拟合、整个网络是怎么把一张图变成一个分类结果的。
第一章 从人眼说起——为什么需要 CNN
1.1 人是怎么认出一只猫的
你看到一张猫的照片,大脑不会一个像素一个像素地扫描。你的视觉系统是这样工作的:
- 先看局部:这里有个尖尖的轮廓(耳朵)、这里有一团柔和的纹理(毛发)、这里有两个圆形高光(眼睛)
- 再组合:尖耳朵 + 圆眼睛 + 胡须 = 猫脸
- 最后判断:这是一只猫
这个过程的关键词是:局部特征 → 组合 → 判断。
1.2 MLP 为什么不擅长看图
MLP(全连接网络)处理图片的方式是:先把整张图拍成一条长长的数字串,然后一股脑全部连接。
问题在于:
- 它不知道哪些像素是邻居。左上角的像素和右下角的像素,在拍平后的向量里可能离得很远,也可能离得很近,完全取决于拍平顺序
- 它没有"局部"的概念。不会先看耳朵再看眼睛,而是试图从 3072 个数字里直接找规律
- 参数爆炸。一张 32×32×3 的图拍平就有 3072 个输入,如果下一层有 1024 个神经元,光这一层就有 3072×1024 ≈ 300 万个参数
1.3 CNN 的核心思想
CNN 的设计哲学直接模仿了人眼的工作方式:
- 用小窗口扫描(卷积)→ 提取局部特征
- 逐层抽象(多层卷积 + 池化)→ 从边缘 → 纹理 → 部件 → 整体
- 最后做判断(全连接层)→ 输出分类结果
下面我们逐一拆解。
1.4 一句话总结
CNN 本质上就是一条流水线:卷积看局部 → 池化缩尺寸 → 逐层叠加从低级到高级 → 全连接做决策。整个过程是监督学习——每张图都有标签,模型对着标签学。它需要的全部组件可以用一张表概括:
| 组件 | 做什么 | 在流水线里的位置 |
|---|---|---|
| 卷积(Conv) | 用小窗口提取局部特征 | 前面,反复出现 |
| ReLU | 把负数归零,引入非线性 | 每次卷积之后 |
| BatchNorm | 校准数据分布,稳定训练 | 卷积和 ReLU 之间 |
| 池化(MaxPool) | 缩小尺寸,保留关键信息 | 每个阶段末尾 |
| Dropout | 随机关闭神经元,防过拟合 | 池化之后或全连接层之间 |
| 全连接层(Linear) | 综合所有特征,输出分类得分 | 最后 |
| 交叉熵损失 | 衡量预测和标签的差距 | 训练时计算 |
| 优化器(SGD/Adam) | 根据梯度更新参数 | 每步训练 |
后面的章节会逐个讲清楚每个组件的原理。
第二章 卷积(Convolution)——CNN 的灵魂
2.1 什么是卷积核
想象你有一个 3×3 的小方格,里面填了 9 个数字。这就是一个卷积核(也叫滤波器/filter)。
卷积核示例(边缘检测):
┌────┬────┬────┐
│ -1 │ -1 │ -1 │
├────┼────┼────┤
│ -1 │ 8 │ -1 │
├────┼────┼────┤
│ -1 │ -1 │ -1 │
└────┴────┴────┘
2.2 卷积运算的过程
把这个 3×3 的小窗口放在图片的左上角,窗口覆盖了图片的 3×3 区域。然后:
- 把窗口里的 9 个像素值和卷积核里的 9 个数字对应位置相乘
- 把 9 个乘积加起来,得到一个数
- 把这个数写到输出图的对应位置
然后窗口往右移一格,重复上述过程。移到头了就换下一行。这样扫完整张图,就得到了一张新的"特征图"。
输入图片(5×5) 卷积核(3×3) 输出特征图(3×3)
┌───┬───┬───┬───┬───┐ ┌───┬───┬───┐
│ 1 │ 2 │ 3 │ 0 │ 1 │ ┌───┬───┬───┐ │ │ │ │
├───┼───┼───┼───┼───┤ │ 1 │ 0 │-1 │ ├───┼───┼───┤
│ 0 │ 1 │ 2 │ 3 │ 0 │ × │ 1 │ 0 │-1 │ → │ │ │ │
├───┼───┼───┼───┼───┤ │ 1 │ 0 │-1 │ ├───┼───┼───┤
│ 1 │ 0 │ 1 │ 2 │ 1 │ └───┴───┴───┘ │ │ │ │
├───┼───┼───┼───┼───┤ └───┴───┴───┘
│ 2 │ 1 │ 0 │ 1 │ 0 │
├───┼───┼───┼───┼───┤
│ 0 │ 1 │ 2 │ 1 │ 1 │
└───┴───┴───┴───┴───┘
2.3 卷积能检测什么
不同的卷积核能检测不同的模式:
| 卷积核类型 | 能检测到的内容 | 直觉理解 |
|---|---|---|
| 横向边缘核 | 水平方向的边界 | 像用手指横着划过图片,能感受到上下颜色变化 |
| 纵向边缘核 | 垂直方向的边界 | 像用手指竖着划 |
| 模糊核 | 平滑区域 | 像隔着磨砂玻璃看 |
关键点:在 CNN 里,卷积核的数值不是人工设计的,而是训练出来的。网络会自动学习"什么样的卷积核对分类最有帮助"。
2.4 通道(Channel)
彩色图片有 3 个通道(R、G、B)。卷积核也必须是 3 个通道的——实际上是一个 3×3×3 的小方块。
但我们通常不只用一个卷积核,而是同时用很多个。比如 Conv2d(3, 64, kernel_size=3) 的意思是:
- 输入有 3 个通道
- 同时使用 64 个不同的卷积核
- 每个卷积核扫一遍图片,产生一张特征图
- 64 个卷积核就产生 64 张特征图,堆在一起形成 64 个通道的输出
你可以理解为:64 个"观察员"从 64 种不同的角度去看同一张图片,有的专门看边缘,有的专门看颜色变化,有的专门看纹理。
2.5 padding(填充)
卷积核在图片上滑动时,边缘位置是"覆盖不全"的。什么意思?看这张图:
5×5 图片中,每个像素被 3×3 卷积核覆盖的次数:
┌───┬───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 2 │ 1 │ ← 角落只被扫到 1 次
├───┼───┼───┼───┼───┤
│ 2 │ 4 │ 6 │ 4 │ 2 │
├───┼───┼───┼───┼───┤
│ 3 │ 6 │ 9 │ 6 │ 3 │ ← 中心被扫到 9 次
├───┼───┼───┼───┼───┤
│ 2 │ 4 │ 6 │ 4 │ 2 │
├───┼───┼───┼───┼───┤
│ 1 │ 2 │ 3 │ 2 │ 1 │
└───┴───┴───┴───┴───┘
原因很简单:卷积核必须完整地落在图片里面才能计算。角落像素只有一种摆法——核贴着角放;而中心像素周围空间充足,核可以在 9 个位置覆盖到它。边缘像素参与计算的次数少,信息提取不充分,而且输出尺寸也会缩小(5×5 输入经 3×3 核卷积后只剩 3×3)。
通用公式:输出尺寸 = 输入尺寸 − 卷积核尺寸 + 1 = 5 − 3 + 1 = 3
padding 怎么解决这个问题
padding=1 的意思是:在图片四周补一圈 0(零填充),让输入从 5×5 变成 7×7,这样卷积后输出尺寸就和原来一样大了。
原始输入(5×5): 补零后(7×7):
■ ■ ■ ■ ■ 0 0 0 0 0 0 0
■ ■ ■ ■ ■ 0 ■ ■ ■ ■ ■ 0
■ ■ ■ ■ ■ → 0 ■ ■ ■ ■ ■ 0
■ ■ ■ ■ ■ 0 ■ ■ ■ ■ ■ 0
■ ■ ■ ■ ■ 0 ■ ■ ■ ■ ■ 0
0 ■ ■ ■ ■ ■ 0
0 0 0 0 0 0 0
补零后用 3×3 核卷积:输出 = 7 − 3 + 1 = 5×5 ✓ 尺寸不变!
加了 padding 后的通用公式:
输出尺寸=输入尺寸+2×padding−卷积核尺寸+1\text{输出尺寸} = \text{输入尺寸} + 2 \times \text{padding} - \text{卷积核尺寸} + 1输出尺寸=输入尺寸+2×padding−卷积核尺寸+1
对于 3×3 的核,padding=1 刚好让输出 = 输入。对于 5×5 的核,需要 padding=2。规律就是 padding = (核尺寸 − 1) / 2。
补的 0 不会"污染"特征吗?实际上影响很小——边缘的 0 只参与最外圈的卷积计算,而且网络会自动学会忽略这些零值。
这样就不会因为卷积操作而不断缩小图片了。
2.6 stride(步长)
默认情况下卷积核每次移动一格(stride=1)。如果设 stride=2,就每次跳两格,输出尺寸会减半。本项目里卷积层都用 stride=1,尺寸缩减交给池化层来做。
第三章 激活函数 ReLU——给网络注入"非线性"
3.1 为什么需要激活函数
假设没有激活函数,网络里全是线性运算(矩阵乘法 + 加法)。不管你叠多少层:
输出=W3⋅(W2⋅(W1⋅x))=(W3⋅W2⋅W1)⋅x=W′⋅x\text{输出} = W_3 \cdot (W_2 \cdot (W_1 \cdot x)) = (W_3 \cdot W_2 \cdot W_1) \cdot x = W' \cdot x输出=W3⋅(W2⋅(W1⋅x))=(W3⋅W2⋅W1)⋅x=W′⋅x
多层线性变换等于一层线性变换。这意味着网络再深也只能画直线,无法学会复杂的弯弯曲曲的分类边界。
3.2 ReLU 是什么
ReLU(Rectified Linear Unit)是最简单也是最常用的激活函数:
ReLU(x)=max(0,x)\text{ReLU}(x) = \max(0, x)ReLU(x)=max(0,x)
- 输入是正数 → 原样输出
- 输入是负数 → 变成 0
输入: [-2, 3, -0.5, 7, -1, 0]
ReLU: [ 0, 3, 0, 7, 0, 0]
3.3 为什么要把负数归 0
这是理解 ReLU 最关键的一个问题。答案有三层:
第一,负数代表"没匹配到的信号"。 卷积核在扫描图片时,如果某个区域不匹配它要检测的模式(比如一个边缘检测核扫到了一片平坦区域),输出就是负数。负数的含义是"这里没有我要找的东西"。ReLU 把它归 0,就是在说:没找到就是没找到,不保留这个无关信号。
第二,制造稀疏性。 归 0 之后,网络中大量神经元的输出变成了 0——只有少部分被"激活"。这类似于人脑的工作方式:看到一张猫的图片,只有负责识别"尖耳朵"“胡须”"毛发纹理"的神经元会兴奋,其余保持沉默。这种稀疏激活让网络更高效、更不容易过拟合。
第三,这是引入非线性的最简单方式。 正数区间是直线,零点处有一个"拐角",这就打破了线性。比 sigmoid 之类的函数计算快得多,梯度也不会消失。
3.4 为什么 ReLU 这么受欢迎
| 优点 | 解释 |
|---|---|
| 计算快 | 只是比较大小,比 sigmoid、tanh 等函数快得多 |
| 梯度不会消失 | 正数区域梯度恒为 1,不会越乘越小(深层网络的大问题) |
| 稀疏激活 | 负数变 0 后,网络中只有部分神经元被激活,类似于大脑的工作方式 |
一句话总结:ReLU 就是给网络装了一个"开关"——每个神经元要么全力输出(正数),要么彻底关闭(负数变 0),让网络有了区分复杂模式的能力。
第四章 池化(Pooling)——聪明地缩小图片
4.1 为什么要池化
经过卷积后,特征图可能还是很大(比如 32×32,64 个通道)。如果直接堆更多卷积层,计算量会非常大。
池化的作用:
- 缩小尺寸:减少计算量和参数量
- 提取显著特征:只保留最重要的信息
- 增加平移不变性:即使目标在图片里稍微移动了位置,池化后的结果也差不多
4.2 最大池化(MaxPool)
本项目用的是 MaxPool2d(kernel_size=2),意思是:
- 把特征图划分成 2×2 的小格子
- 每个格子里取最大值
- 输出尺寸变为原来的一半
输入(4×4): 输出(2×2):
┌────┬────┬────┬────┐ ┌────┬────┐
│ 1 │ 3 │ 2 │ 4 │ │ 3 │ 4 │ (取左上 2×2 的最大值 3,右上的最大值 4)
├────┼────┼────┼────┤ ├────┼────┤
│ 5 │ 2 │ 1 │ 0 │ → │ 5 │ 3 │ (取左下 2×2 的最大值 5,右下的最大值 3)
├────┼────┼────┼────┤ └────┴────┘
│ 0 │ 1 │ 3 │ 2 │
├────┼────┼────┼────┤
│ 4 │ 2 │ 1 │ 3 │
└────┴────┴────┴────┘
4.3 为什么取最大值而不是平均值
最大池化保留的是最强烈的响应。比如某个卷积核是边缘检测器,那么 2×2 区域里最大的那个值就代表"这个区域里最明显的边缘强度"。微弱的信号(小值)可能只是噪声,丢掉也无妨。
当然也有平均池化(AvgPool),取的是平均值,保留的是区域的"整体感觉"。两种各有适用场景,但在分类任务里最大池化更常用。
4.4 池化后尺寸怎么变
本项目中图片尺寸的变化过程:
输入: 32×32
↓ 第一次 MaxPool(2)
16×16
↓ 第二次 MaxPool(2)
8×8
↓ 第三次 MaxPool(2)
4×4
三次池化,尺寸缩了 8 倍(32 → 4),但通道数从 3 涨到了 256。空间信息换成了语义信息——图变小了,但每个位置包含的"含义"更丰富了。
"3 个通道"和"256 个通道"是完全不同的概念
最初的 3 个通道就是 RGB 三个颜色层——红、绿、蓝,这是人类定义的,每个通道只记录一种颜色的亮度。
后面涨到 64、128、256 个通道,含义完全不同——每个通道是一个卷积核扫描出来的"特征图"。比如第一层 Conv2d(3, 64) 有 64 个卷积核,训练后每个核学会检测一种模式:
第 1 个核 → 检测水平边缘 → 产出第 1 张特征图
第 2 个核 → 检测垂直边缘 → 产出第 2 张特征图
第 3 个核 → 检测红色色块 → 产出第 3 张特征图
...
第 64 个核 → 检测对角线纹理 → 产出第 64 张特征图
64 张特征图堆在一起 = 64 个通道
到了第二层 Conv2d(64, 128),128 个核在这 64 张特征图上做组合——比如"水平边缘 + 垂直边缘 = 角落",又产出 128 张更高级的特征图。
为什么越深通道越多? 浅层检测的是边缘、颜色这类简单模式,几十种就够了。但深层要表达"猫耳朵"“车轮”"花瓣"这些复杂概念,种类远多于简单边缘,所以需要更多通道来编码。
一句话:3 个通道 = 3 种颜色(给人看的),256 个通道 = 256 种学到的特征(给网络用的)。
第五章 批归一化(Batch Normalization)——网络的"校准器"
5.1 问题背景:内部协变量偏移
训练神经网络时,每一层的输入分布会随着前面层参数的更新而不断变化。今天第 3 层收到的数据分布是这样的,明天参数更新后又变了。这种现象叫内部协变量偏移(Internal Covariate Shift)。
后果:后面的层需要不断适应前面层输出分布的变化,训练变得又慢又不稳定。
5.2 BatchNorm 做了什么
对于一个 batch 的数据,BatchNorm 在每个通道上做以下操作:
- 计算均值和方差:统计当前 batch 在该通道上所有像素值的均值 μ 和方差 σ²
- 标准化:把每个值减去均值、除以标准差,拉到均值 0、方差 1
x^=x−μσ2+ϵ\hat{x} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}x^=σ2+ϵx−μ
- 缩放和平移:再乘以一个可学习的参数 γ,加上一个可学习的参数 β
y=γ⋅x^+βy = \gamma \cdot \hat{x} + \betay=γ⋅x^+β
第 3 步很重要——如果网络觉得"这个通道不需要均值 0、方差 1",它可以通过学习 γ 和 β 把分布调回去。也就是说,BatchNorm 给了网络选择权:可以标准化,也可以不标准化。
5.3 BatchNorm 的好处
| 好处 | 解释 |
|---|---|
| 训练更快 | 每层输入分布稳定了,梯度传播更顺畅,可以用更大的学习率 |
| 不那么依赖初始化 | 即使初始权重不太好,BatchNorm 也能把数据"矫正"过来 |
| 轻微的正则化效果 | 因为每个 batch 的统计量有随机性,相当于给训练加了一点噪声 |
5.4 在本项目里怎么用的
Conv2d(3, 64) → BatchNorm2d(64) → ReLU
固定套路:卷积 → BatchNorm → 激活。BatchNorm 放在激活函数之前,先把卷积输出的分布"校准"好,再送给 ReLU 决定开关。
第六章 Dropout——随机"翘课"防过拟合
6.1 什么是过拟合
训练集上表现很好,但新数据上表现很差。就像一个学生背完了所有历年真题的答案,但遇到新题就不会做了。
6.2 Dropout 的原理
训练时,每一步随机选择一部分神经元暂时关掉(输出置 0),剩下的神经元正常工作。
正常网络: Dropout(0.3) 后:
○ ─ ○ ─ ○ ○ ─ ✕ ─ ○ (✕ 表示被关掉的神经元)
○ ─ ○ ─ ○ ○ ─ ○ ─ ✕
○ ─ ○ ─ ○ ✕ ─ ○ ─ ○
○ ─ ○ ─ ○ ○ ─ ○ ─ ○
每一步被关掉的神经元都是随机的,所以每一步训练的其实是一个"残缺版"的网络。
6.3 为什么 Dropout 能防过拟合
直觉解释有两个角度:
角度一:防止共适应
如果某几个神经元总是"抱团"一起工作,它们可能合作记住了训练数据的某些特殊模式(噪声),而不是学到真正有用的规律。Dropout 随机拆散它们,迫使每个神经元独立地学有用的特征。
角度二:集成学习
每次 Dropout 关掉不同的神经元,相当于训练了一个不同的小网络。最终的网络可以看作是指数级多个小网络的集成,而集成学习天然能降低过拟合。
6.4 训练 vs 推理
- 训练时:随机关掉神经元(比如 30%)
- 推理时(测试/部署):所有神经元都打开,但每个神经元的输出乘以 (1 - dropout_rate),保证输出的期望值和训练时一致
PyTorch 的 nn.Dropout 已经自动处理了这个切换,你只需要在评估时调用 model.eval() 就行。
6.5 Dropout vs Dropout2d
| 类型 | 关掉什么 | 适用场景 |
|---|---|---|
| Dropout | 随机关掉单个神经元 | 全连接层 |
| Dropout2d | 随机关掉整个通道 | 卷积层 |
为什么卷积层要关掉整个通道?因为同一个通道内的像素是高度相关的(它们是同一个卷积核产生的),只关掉单个像素几乎没效果,需要把整个通道一起关掉才有意义。
第七章 全连接层(Fully Connected Layer)——最终的"投票器"
7.1 全连接层的角色
经过多层卷积和池化后,图片已经被压缩成了一个高维特征向量(比如 256×4×4 = 4096 维)。这个向量包含了图片的"语义摘要"。
全连接层的任务就是:根据这个摘要,给 100 个类别打分。
7.2 怎么理解"全连接"
特征向量(4096 维) → 隐藏层(512 维) → 输出层(100 维)
每个输入连接 每个输入连接 第 i 个数 = 属于第 i 类的得分
每个输出 每个输出
"全连接"就是:每一个输入节点都和每一个输出节点有连接。4096 → 512 这一层就有 4096×512 ≈ 200 万个参数。
7.3 为什么不直接从特征图接到 100 类
理论上可以,但中间加一个隐藏层(4096 → 512 → 100)给了网络一个"缓冲":先把 4096 维的特征压缩到 512 维,去掉冗余信息,再做最终分类。实践中这样效果更好。
第八章 把积木拼起来——完整的 CNN 架构
8.1 本项目 CNN 的完整数据流
让我们跟踪一张 32×32×3 的图片,看它怎么一步一步变成 100 个分数:
输入: [batch, 3, 32, 32]
═══════ 第一阶段:检测低级特征(边缘、颜色块)═══════
Conv2d(3→64, 3×3, pad=1) → [batch, 64, 32, 32] 64 个卷积核同时扫描
BatchNorm2d(64) → [batch, 64, 32, 32] 校准分布
ReLU → [batch, 64, 32, 32] 激活
Conv2d(64→64, 3×3, pad=1) → [batch, 64, 32, 32] 进一步提取
ReLU → [batch, 64, 32, 32] 激活
MaxPool2d(2) → [batch, 64, 16, 16] ★ 尺寸减半
Dropout2d(0.3) → [batch, 64, 16, 16] 随机关通道
═══════ 第二阶段:检测中级特征(纹理、局部形状)═══════
Conv2d(64→128, 3×3, pad=1) → [batch, 128, 16, 16] 通道数翻倍
BatchNorm2d(128) → [batch, 128, 16, 16] 校准
ReLU → [batch, 128, 16, 16] 激活
Conv2d(128→128, 3×3, pad=1) → [batch, 128, 16, 16] 进一步提取
ReLU → [batch, 128, 16, 16] 激活
MaxPool2d(2) → [batch, 128, 8, 8] ★ 尺寸再减半
Dropout2d(0.3) → [batch, 128, 8, 8] 随机关通道
═══════ 第三阶段:检测高级特征(部件、语义)═══════
Conv2d(128→256, 3×3, pad=1) → [batch, 256, 8, 8] 通道数再翻倍
BatchNorm2d(256) → [batch, 256, 8, 8] 校准
ReLU → [batch, 256, 8, 8] 激活
MaxPool2d(2) → [batch, 256, 4, 4] ★ 尺寸再减半
═══════ 分类阶段 ═══════
Flatten → [batch, 4096] 拍平
Linear(4096→512) → [batch, 512] 压缩
ReLU → [batch, 512] 激活
Dropout(0.3) → [batch, 512] 防过拟合
Linear(512→100) → [batch, 100] ★ 输出 100 个类别的得分
最终: 取 100 个得分中最大的那个对应的类别,作为预测结果
8.2 特征是怎么逐层抽象的
第一阶段(浅层) 第二阶段(中层) 第三阶段(深层)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 边缘 │ │ 纹理 │ │ 部件 │
│ 颜色变化 │ → │ 角落 │ → │ 物体轮廓 │
│ 亮度梯度 │ │ 简单形状 │ │ 语义特征 │
└─────────┘ └─────────┘ └─────────┘
看"点" 看"线和面" 看"东西"
这就像一个画家素描的过程:先画轮廓线(边缘),再画纹理和阴影(中级特征),最后你就能看出画的是什么了(高级语义)。
8.3 参数量对比
| 层 | 参数量 | 说明 |
|---|---|---|
| Conv2d(3→64, 3×3) | 3×64×3×3 + 64 = 1,792 | 很少!权重共享的功劳 |
| Conv2d(64→64, 3×3) | 64×64×3×3 + 64 = 36,928 | 还是不多 |
| Linear(4096→512) | 4096×512 + 512 = 2,097,664 | 一下子 200 万! |
卷积层参数少是因为权重共享——一个 3×3 的卷积核在整张图上滑动时,用的是同一组参数。而全连接层每个连接都是独立的参数。
第九章 训练的全过程——从白纸到学会分类
9.0 CNN 是监督学习吗
本项目里的 CNN 是典型的监督学习——每张训练图片都带有标签(“猫”“汽车”“苹果”……),模型预测一个类别,和标签比较算损失,再调参数。有标准答案、对着答案学,就是监督学习。
不过 CNN 本身只是一种网络架构,不绑定学习方式。它也能用在别的范式里:
| 学习范式 | 有没有标签 | CNN 怎么用 | 例子 |
|---|---|---|---|
| 监督学习 | 全有 | 直接训练分类/检测 | 本项目(CIFAR-100) |
| 自监督学习 | 全无 | 自己给自己造任务 | SimCLR |
| 半监督学习 | 少量有 + 大量无 | 结合有标签和无标签数据 | FixMatch |
一句话:CNN 是工具,监督学习是使用方式。
9.1 前向传播
数据从输入层一路算到输出层,得到 100 个得分(logits)。
9.2 计算损失
用交叉熵损失函数衡量"预测和真实答案差多远"。如果给正确类别打了最高分,损失就小;否则损失就大。
9.3 反向传播
从损失出发,从后往前计算每个参数对损失的"贡献度"(梯度)。这个过程用的是微积分的链式法则。
你可以理解为:最终成绩不好(损失大),需要追责——从最后一层开始,问"是你的参数太大了还是太小了?该往哪个方向调?",一层一层往前问,直到第一层。
9.4 参数更新
优化器根据梯度更新参数:
θ新=θ旧−学习率×梯度\theta_{\text{新}} = \theta_{\text{旧}} - \text{学习率} \times \text{梯度}θ新=θ旧−学习率×梯度
9.5 重复
以上四步反复执行。每遍历完整个训练集一次,称为一个 epoch。一般训练几十到几百个 epoch 后,网络就能学会分类了。
┌──────────────────────────────────────────┐
│ │
│ 输入图片 → 前向传播 → 得到预测 │
│ ↓ │
│ 计算损失 │
│ ↓ │
│ 反向传播 │
│ ↓ │
│ 更新参数 │
│ ↓ │
│ 下一个 batch ───────→│
│ │
└──────────────────────────────────────────┘
第十章 常见疑问解答
Q1:卷积核的初始值是怎么确定的?
随机初始化。PyTorch 默认用 Kaiming 初始化(也叫 He 初始化),专门为 ReLU 激活函数设计,能让每层输出的方差保持在合理范围内,避免一开始就梯度爆炸或消失。
Q2:为什么通道数越来越多(3 → 64 → 128 → 256)?
因为图片的空间尺寸在不断缩小(32 → 16 → 8 → 4),每个位置能"看到"的区域越来越大,需要表达的信息也越来越抽象。增加通道数就是给网络更多的"维度"来编码这些复杂信息。
可以类比:一段话翻译成英文可能只需要 10 个词,但翻译成摩尔斯电码就需要几百个字符。越底层的编码方式(像素值)越简单直接,越高层的编码(语义特征)越需要更多维度来表达。
Q3:如果不做池化会怎样?
- 计算量会非常大(特征图一直是 32×32)
- 每个卷积核只能看到 3×3 的局部区域,即使叠很多层,"视野"增长也很慢
- 池化后,同样的 3×3 卷积核在下一层实际上"看到"了原图更大的区域
Q4:BatchNorm 在推理时怎么工作?
训练时用的是当前 batch 的均值和方差。推理时用的是训练过程中累积的全局均值和方差(移动平均)。这样推理时不依赖 batch 大小,单张图片也能正常工作。
Q5:为什么用 ReLU 而不用 sigmoid?
sigmoid 的输出范围是 (0, 1),在两端梯度几乎为 0(饱和区),多层叠加后梯度会指数级衰减(梯度消失问题)。ReLU 在正数区域梯度恒为 1,不存在这个问题。而且 ReLU 计算就是一个 max 操作,比 sigmoid 的指数运算快得多。
Q6:CNN 的"感受野"是什么意思?
感受野是指输出特征图上的一个像素,对应输入图片上多大的区域。
- 第一个卷积层(3×3):感受野 = 3×3
- 经过一次池化后再卷积:感受野变大了,因为池化缩小了特征图,后续的 3×3 核实际上覆盖了原图更大的区域
- 层数越深,感受野越大,网络能"看到"的上下文越多
这也是为什么深层网络能识别"整体"而不仅仅是"局部"。
第十一章 从本项目的 CNN 到更强大的网络
本项目的 CNN 是一个较为基础的架构。如果你想了解它和业界主流网络的关系:
| 网络 | 年份 | 核心创新 | 和本项目的关系 |
|---|---|---|---|
| LeNet-5 | 1998 | 最早的 CNN,用于手写数字识别 | 本项目的 CNN 可以看作 LeNet 的加强版 |
| AlexNet | 2012 | 更深更宽,首次用 ReLU 和 Dropout | 本项目用了同样的 ReLU + Dropout 策略 |
| VGGNet | 2014 | 全部用 3×3 小卷积核,靠深度取胜 | 本项目的"多层 3×3 卷积"思路和 VGG 一致 |
| ResNet | 2015 | 残差连接,解决了超深网络的退化问题 | 本项目没有用残差连接,是下一步可以尝试的方向 |
| EfficientNet | 2019 | 同时优化深度、宽度和分辨率 | 更高级的架构设计 |
本项目的 CNN 麻雀虽小五脏俱全,包含了理解现代 CNN 所需要的所有基础组件。掌握了这些之后,再去看 ResNet、DenseNet 等更复杂的网络,就只是在这些基础上做加法了。
总结:CNN 核心概念一页纸
┌─────────────────────────────────────────────────────────────┐
│ CNN 核心概念速查 │
├─────────────┬───────────────────────────────────────────────┤
│ 卷积 │ 用小窗口扫描图片,提取局部特征 │
│ 通道 │ 多个卷积核 = 多个观察角度 │
│ padding │ 在图片周围补零,保持尺寸不变 │
│ stride │ 卷积核每次移动的步长 │
│ ReLU │ 负数变 0,引入非线性 │
│ MaxPool │ 取局部最大值,缩小尺寸,保留显著特征 │
│ BatchNorm │ 标准化每层输出,加速训练,稳定梯度 │
│ Dropout │ 随机关掉神经元/通道,防止过拟合 │
│ Dropout2d │ 关掉整个通道(专门给卷积层用的 Dropout) │
│ Flatten │ 把多维特征图拍成一维向量 │
│ 全连接层 │ 根据特征向量给每个类别打分 │
│ 交叉熵损失 │ 衡量预测和真实标签之间的差距 │
│ 反向传播 │ 从损失出发,计算每个参数该怎么调 │
│ 感受野 │ 输出的一个点对应输入的多大区域 │
│ 权重共享 │ 同一个卷积核在整张图上复用,大幅减少参数 │
└─────────────┴───────────────────────────────────────────────┘
更多推荐


所有评论(0)