📑 本页目录(点开跳转)
12 · CNN:处理图像
⏱ 30 分钟 | ⭐ 核心
🎯 一句话
CNN 的全部智慧是两条归纳偏置:「局部相关」(相邻像素才有关系)和 「平移不变」(猫在左上角还是右下角都是猫)。把这两条硬编码进网络结构,参数量降几个数量级。
💥 一、为什么图像不能直接用 MLP
一张 224×224 的彩色图 = 224×224×3 = 150,528 个数
接一个 1000 维的全连接层 → 1.5 亿个参数(就这一层!)
❌ 参数爆炸
❌ 完全没利用「相邻像素相关」这个先验
❌ 图片平移一个像素,所有权重都要重新学
🔲 二、卷积:一个小窗口滑过整张图
输入图像 卷积核 3×3 输出特征图
每个位置算一次
两个关键机制:
| 机制 | 含义 | 好处 |
|---|---|---|
| 局部连接 | 每个输出只看输入的一个小窗口 | 参数从 1.5 亿降到 9 个 |
| 权重共享 ⭐ | 同一个卷积核滑遍全图 | 平移不变性 + 参数再降 |
💡 人话:一个卷积核就是一个「特征探测器」(比如"检测竖直边缘"), 它滑遍整张图,在哪都用同一套标准。 这就是「平移不变」。
🧮 算一次参数量,感受一下差距
任务:224×224×3 的图 → 64 个特征图
MLP 做法:150,528 × 64 = 9,633,792 个参数
卷积做法:3×3 × 3(输入通道) × 64 = 1,728 个参数 ⭐
→ 少了【5575 倍】
→ 而且卷积的每个参数被复用了 224×224 = 5 万次
🔑 这就是"归纳偏置很值钱"的最直观证据。 🔗 数学原理第 9 章 PAC 给了它的定量版: 限制假设空间能把样本需求降几个数量级。 CNN 不是"更聪明的 MLP",而是一个被"图像的结构"约束过的 MLP。
🎨 多通道是怎么回事(最容易卡住的一点)
输入:H × W × C_in (比如 224×224×3,RGB 三通道)
卷积核:k × k × C_in ← ⭐ 卷积核【也是三维的】,深度必须等于输入通道数
一个核 → 一张 H'×W'×1 的特征图
要 C_out 个特征图?→ 用 C_out 个这样的核
⭐ 所以参数量 = k × k × C_in × C_out (+ C_out 个 bias)
💡 一句话记住:卷积核在空间上滑动,但在通道上是"一次吃掉全部"的。
⭐ 这句话能不能落到代码里,是「懂了」和「以为懂了」的分界: 🔗 附录 C 第 7 题用 15 行 numpy 把
conv2d写出来了(和F.conv2d误差 0.0)。 那一行np.tensordot(patch, w, axes=([1,2,3],[1,2,3]))就是「通道一次吃掉全部」的字面翻译。 顺便还有两个这一节没说的:输出尺寸公式 $\lfloor(H+2p-k)/s\rfloor+1$(记不住就用padding = k//2配stride=1,尺寸不变), 以及深度学习里的「卷积」严格说是互相关——数学上的卷积要先把核翻转 180°,框架都没翻。主动说出这条是加分项。
🎛️ 三、四个必须懂的超参
import torch.nn as nn
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)
| 参数 | 含义 | 经验 |
|---|---|---|
kernel_size |
窗口大小 | 3×3 是现代默认(两个 3×3 ≈ 一个 5×5 但参数更少、非线性更多) |
stride |
每次滑几格 | 1(保持尺寸)或 2(尺寸减半) |
padding |
边缘补零 | padding=1 配 kernel=3 可保持尺寸不变 ⭐ |
out_channels |
用多少个不同的卷积核 | 越深越多:64→128→256→512 |
输出尺寸公式:
$$H_{out} = \left\lfloor \frac{H_{in} + 2p - k}{s} \right\rfloor + 1$$
💡 记不住就用
padding = kernel_size // 2,stride=1时尺寸不变。
🏗️ 四、一个典型 CNN 的结构
🔑 层级抽象:浅层学边缘和颜色,中层学纹理和部件,深层学物体概念。 这是"深比宽好"最直观的证据(第 7 章)——每一层都在上一层的基础上组合。
池化 Pooling:取窗口内最大值(MaxPool)或平均值(AvgPool)
- 作用:降尺寸、扩大感受野、提供一点平移容忍
- 现代趋势:有时用 stride=2 的卷积替代池化(可学习的降采样比固定规则更灵活)
👁️ 感受野:一个必须建立的直觉
感受野 = 一个输出神经元"能看到"输入的多大区域
1 层 3×3 卷积 → 感受野 3×3
2 层 3×3 卷积 → 感受野 5×5
3 层 3×3 卷积 → 感受野 7×7 (每加一层 +2)
加一次 stride=2 池化 → 之后每层的感受野【翻倍增长】⭐
⭐ 所以池化不只是"省算力",它是【快速扩大感受野】的主要手段
💡 为什么两个 3×3 优于一个 5×5:
| 一个 5×5 | 两个 3×3 | |
|---|---|---|
| 感受野 | 5×5 | 5×5(一样) |
| 参数量 | 25·C² | 18·C²(少 28%) ⭐ |
| 非线性 | 1 次 ReLU | 2 次 ReLU(表达力更强) ⭐ |
🔑 这就是 VGG 的核心发现,也是"3×3 成为现代默认"的原因。
⚠️ 感受野不够的症状:模型能识别小物体但对大物体/整体结构判断差。 解法:加深、加池化、或用空洞卷积(dilated conv)。
🕳️ 三个变体(知道存在即可)
| 变体 | 做什么 | 用在哪 |
|---|---|---|
| 1×1 卷积 | 不看空间,只在通道间做线性组合 | 降/升通道数(省算力)、跨通道信息融合 ⭐ |
| 深度可分离卷积 | 空间卷积和通道卷积拆开做 | MobileNet,参数量降 8~9 倍 |
| 空洞卷积 | 卷积核中间"留空" | 不降分辨率也能扩感受野(分割任务) |
💡 1×1 卷积值得单独理解:它看起来什么都没做,但 它是所有"瓶颈结构"(bottleneck)的核心—— 先用 1×1 把 256 通道压到 64,做完 3×3 再用 1×1 升回 256, 算力降到 1/4 而表达力几乎不变。ResNet-50 及以上全靠它。
📜 五、架构演进(记思想,不必记细节)
| 架构 | 年份 | 关键贡献 |
|---|---|---|
| LeNet | 1998 | 第一个成功的 CNN |
| AlexNet | 2012 | ReLU + Dropout + GPU,深度学习引爆点 |
| VGG | 2014 | 全部用 3×3,证明「深度」的价值 |
| ResNet ⭐ | 2015 | 残差连接,让上百层可训练 |
| MobileNet | 2017 | 深度可分离卷积,移动端 |
| EfficientNet | 2019 | 复合缩放(深度/宽度/分辨率一起调) |
| ConvNeXt | 2022 | 借鉴 Transformer 设计的现代 CNN |
⭐ 残差连接:最重要的那一个
[Conv-BN-ReLU] 照常算,另外拉一条什么都不做的捷径,把 x 原样送到加号那里。⭐ 关键就在那个 +:反向传播时梯度可以顺捷径原样穿回去,不必挤过中间每一层 —— 这就是上百层能训起来的原因。为什么它能让网络变深:
- 梯度高速公路:反向传播时梯度可以直接通过 + 走捷径,不用穿过所有层 → 缓解梯度消失(第 8 章)
- 学残差比学映射容易:网络只需学"要改变多少",最差就是学成 0(恒等映射)
🔗 你在全景导论第 2 章看到 Transformer 每层都有残差连接——同一个思想,源头在 ResNet。
💡 六、实践:别从零训练
❌ 从随机初始化训练自己的 CNN
→ 需要大量数据和算力,效果还差
✅ 迁移学习(99% 的场景该这么做)⭐
拿在 ImageNet 上预训练好的模型 → 换掉最后一层 → 在你的数据上微调
import torchvision.models as models
import torch.nn as nn
model = models.resnet18(weights="IMAGENET1K_V1") # 预训练权重
model.fc = nn.Linear(model.fc.in_features, num_classes) # 换分类头
# 数据少时:冻结主干,只训分类头
for p in model.parameters(): p.requires_grad = False
for p in model.fc.parameters(): p.requires_grad = True
策略选择:
| 数据量 | 做法 |
|---|---|
| < 1000 张 | 冻结主干,只训分类头 |
| 1000–1 万 | 微调最后几层,小 lr |
| > 1 万 | 全部微调,lr 比从零训小 10 倍 |
🔗 Kaggle CV 板块(09–12 章)全是这套流程的实战细节。
⚠️ 七、CNN 的假设什么时候会害你
平移不变的代价:CNN【故意】丢掉了"物体在哪"这个信息
✅ 适合:图像分类(猫在哪都是猫)
❌ 不适合:位置本身携带关键信息的任务
· 医学影像:"病灶在左肺还是右肺"
· 文档理解:"这个数字在表格的哪一列"
· 界面识别:"按钮在屏幕上部还是下部"
解法:把位置作为额外通道喂进去(CoordConv),或改用带位置编码的 Transformer。
🔗 这正是数学原理第 11 章说的 「算法的专长是守恒的」——CNN 在图像分类上的优势, 换个任务就变成了负担。归纳偏置永远是双刃剑。
🔗 和站内其他章的关系
| 相关的地方 | 这里的对应 |
|---|---|
| 第 7 章"深比宽好" | 层级抽象是最直观的证据 |
| 第 8 章梯度消失 | 残差连接是解法 |
| 数学原理第 9 章 PAC | 归纳偏置降低样本需求的定量版 |
| 数学原理第 11 章 NFL | 为什么平移不变有时是负担 |
| Kaggle 第 9–12 章 CV 竞赛 | 本章的实战版(检测/分割/框融合) |
| 全景导论第 10 章的 ViT | ViT 用 Transformer 替代了 CNN,但 patch 切分借鉴了卷积思想 |
| 全景导论第 2 章的残差连接 | 源头就是本章的 ResNet |
| 推荐算法第 13 章的图像 Embedding | 就是 CNN 倒数第二层的输出 |
✅ 检查点
- CNN 硬编码了哪两条归纳偏置?靠什么机制实现?
- 算一下:224×224×3 → 64 个特征图,MLP 和卷积的参数量差多少倍?
- 卷积核在通道维度上是怎么工作的?参数量公式是什么?
padding怎么设能保持尺寸不变?- 感受野怎么增长?为什么两个 3×3 优于一个 5×5?
- 1×1 卷积在做什么?为什么它是"瓶颈结构"的核心?
- 残差连接为什么能让网络变深?(两个理由)
- CNN 的平移不变在什么任务上会变成负担?
- 数据只有 500 张图,该怎么做?
👀 答案
- 局部相关(相邻像素才有关系)和平移不变(物体在哪个位置都一样)。靠局部连接和权重共享实现。
- MLP:150,528 × 64 ≈ 963 万;卷积:3×3×3×64 = 1,728。差约 5575 倍,而且每个卷积参数还被复用了 5 万次。
- 卷积核也是三维的,深度必须等于输入通道数,在通道上"一次吃掉全部",只在空间上滑动。参数量 = k × k × C_in × C_out(+ C_out 个 bias)。
padding = kernel_size // 2,配合stride=1。- 每加一层 3×3 感受野 +2;加一次 stride=2 池化后,之后每层的感受野翻倍增长。两个 3×3 与一个 5×5 感受野相同,但参数少 28%、多一次 ReLU(表达力更强)——这是 VGG 的核心发现。
- 不看空间,只在通道间做线性组合——用来降/升通道数和跨通道融合。瓶颈结构:先 1×1 把 256 压到 64,做 3×3,再 1×1 升回去,算力降到 1/4 而表达力几乎不变。
- ①梯度高速公路:反传时梯度可直接走捷径,不必穿过所有层,缓解梯度消失;②学残差比学完整映射容易,最差学成 0(恒等映射)就行。
- 位置本身携带关键信息的任务:医学影像(病灶在左肺还是右肺)、文档理解(数字在哪一列)、界面识别。解法:CoordConv 或带位置编码的 Transformer。
- 迁移学习:用 ImageNet 预训练模型,冻结主干只训分类头,配合强数据增强。绝不从零训练(第 17 章的实验:100 张图时从零只有 25%,冻结主干能到 65%)。
🛑 可以停在这里
⚡ 走神救援
CNN两条归纳偏置:局部相关+平移不变,靠局部连接+权重共享实现——224×224×3→64通道时,MLP要963万参数,卷积只要1728个(差5575倍),这是"归纳偏置很值钱"最直观的证据。卷积核是三维的,通道上一次吃掉全部、只在空间滑动,参数量=k×k×C_in×C_out。超参:3×3是默认、stride、padding=k//2保持尺寸。结构=Conv+BN+ReLU+池化+GlobalAvgPool,层级抽象(边缘→纹理→物体)。感受野:每层3×3 +2,池化后翻倍增长;两个3×3 = 一个5×5的感受野但参数少28%、多一次ReLU(VGG的核心发现)。1×1卷积=只在通道间线性组合,是瓶颈结构的核心(256→64→3×3→256,算力降到1/4)。演进关键是ResNet残差连接:梯度高速公路+学残差比学映射容易,Transformer也用它。⚠️ 平移不变的代价:故意丢掉了"在哪"——在"病灶在左肺还是右肺"这类任务上是负担(NFL:专长守恒)。实践永远用迁移学习,数据少就冻主干。
下一节 👉 13-RNN与序列建模.md