阶段 2 · 看懂图像

拿一个小印章
盖满整张图片

上一章《超参怎么选》给了「该加数据还是加模型」的判据;可图片这条路,光加模型会撞上参数爆炸, 猫挪一格还得重新认一遍。CNN 的办法是只刻一块 3×3 的小印章,拿它盖满整张图。

1

挪一挪,它就认不出了

MLP 看图的办法,是给每个像素单独拉一根线。 假设它已经学会在左上角认出一个「十字」:那一块的几根线权重大,其余都是 0。 现在把十字往右挪,看它还认不认得。

互动 · 同一个十字,挪一挪位置

0 格

十字还是那个十字,可全连接神经元的权重只长在虚线框里。东西一离开那个框,它就看不见了; 想在每个位置都认得,就得在每个位置各学一遍。

另一个问题是参数:一张 224×224 的彩图是 15 万个数字(长 × 宽 × 红绿蓝 3 个颜色),第一层只放 256 个神经元, 就要 3,850 万个参数(第 3 节可以拖着看)。右边那根不动的条,就是这一章的答案:一块 3×3 的小印章。

2

一块 3×3 小窗口,滑过整张图

上一节说的小印章,正式名字叫卷积核(filter / kernel):一块 3×3 的小数字板。把它盖在图像的某个 3×3 区域上, 对应位置相乘再求和,得到一个数——输出的一个像素就是这么来的。 然后窗口往右挪一格,再来一次……滑完整张图,就得到一张特征图。

核心大图 · 卷积到底在算什么

卷积核(垂直边缘检测)
当前窗口逐项相乘

输入是 8×8 的灰度图(0=黑,255=白),3×3 的核滑过以后输出是 6×6 —— 每滑一次就少一圈。 为了看清楚,输出的灰度取的是加权和的绝对值(否则负值会全黑,只能看到一半的边缘)。

👆 把这句话写成公式

把鼠标移到公式里的 I、K、S、i、j、m、n 上,会显示它怎么念、在上面那张图上是哪一块;Σ 的说明在下面那张表里。鼠标不方便就直接读表。

符号怎么念一句话在图上是哪一块
I输入图像 一张二维数字表,每格一个像素演示里左边那张 8×8
K卷积核(filter) 一块很小的数字板,值是学出来的,没人设计它标着「卷积核」的那块 3×3,彩色的是非零项
S特征图(feature map) 滑完一遍的输出,存的是「这里的边缘有多强」右边那张 6×6,鼠标移上去窗口会跳过去
i, j输出上的位置 「第 i 行第 j 列的输出点」,窗口每次只算一个6×6 里光标指的那一格
m, n核内部的位置 「核里第 m 行第 n 列的数」,双重求和就是跑完这九个格3×3 核里正在被读到的那个格子
Σ西格玛,把所有都加起来 3×3 一共九项,一个不漏地加起来演示里那行「逐项相乘」的九个结果相加那一步
✱卷积运算 把窗口盖上去、逐项相乘再求和这一个动作演示里中间那一步「相乘求和 →」
数学课上的卷积要先把核翻个面,这里怎么没翻?

严格说,这里算的叫互相关(不翻核)。深度学习里的核是学出来的,翻不翻只是把学到的 9 个数倒个序,所以大家直接叫它卷积,PyTorch 的 Conv2d 算的也是这个。

🎯 类比 想象你拿着一块 3×3 的镂空模板在照片上盖印,模板上写着"左边减、右边加"。 盖在平坦的地方得到 0(什么都没有);盖在明暗交界处,得到的数很大 —— 于是"边缘"就被这个模板挑出来了。

💡 关键点

卷积核里的数字是学出来的,没人手工设计!我们只规定"用 3×3 的窗口去滑",里面那 9 个数字由反向传播自己长出来——和 MLP 调权重是同一套流程:算梯度,把每个数往下走一步。训练完会发现,浅层的核往往长得像边缘检测器——因为边缘对图像确实最有用。

3

三件法宝,省下多少参数

第 1 节那两个问题(参数太多、挪了就不认得),CNN 用三招一起解决。

1局部连接每个神经元只看 3×3 的小范围,不跟全图的像素都拉线
2权值共享同一个 3×3 核在整张图上重复使用,参数与图像大小无关
3平移等变猫在左上还是右下,同一个核都会响应(响应跟着一起挪)——这叫等变,是权值共享带来的性质;「在哪都一样」是不变,要再加池化才接近

法宝一 + 二:参数量差距能有多大?

拖一下滑块,看看同一张图用两种方式处理,参数量差了多少倍:

互动 · MLP 全连接 vs CNN 卷积

224
MLP 第一层(输出 256 维)
0
CNN 一层 3×3×64 核
1,792
差距
—

MLP 那个数字跟图像面积成正比(边长的平方),CNN 那个数字永远是 1792。 换成分辨率更高的图,MLP 直接崩,CNN 纹丝不动。
两个数怎么来的:MLP 是「边长 × 边长 × 3 个颜色」个输入,每个都连到 256 个神经元,再加 256 个偏置; CNN 是 64 块 3×3×3 的核,每块 27 个数加 1 个偏置,(27 + 1) × 64 = 1,792。

法宝三:平移等变,亲眼看一下

点下面这个按钮,把图片整体右移两格,然后观察右边的特征图发生了什么:

互动 · 图像挪位置,特征图跟着挪

现在两边是对齐的。点按钮试试。

💡 直觉

特征图上的数字一模一样,只是整体平移了:不管猫在图片的哪个角落,同一组数字都会出现,只是出现的位置跟着挪。 这叫等变——输出跟着输入一起挪。它还不等于「不变」(不管在哪答案都一样):卷积本身只做到「跟着挪」, 要靠后面把位置收起来——第 5 节的 2×2 池化只能在同一个窗口内容忍一格左右的挪动, 现代 CNN 在末尾再把整张特征图取一次平均(全局平均池化),位置才彻底不影响结果。

4

换个卷积核,看到的东西就变了

同一张输入图,卷积核里的数字变一变,输出的特征图就完全是另一回事。 点下面的按钮,看同一张 8×8 图被不同的"眼睛"看成了什么。

互动 · 卷积核画廊

垂直边缘
当前窗口
卷积核里面的数字(3×3)它"看见"的东西
垂直边缘−1 0 1 / −2 0 2 / −1 0 1竖直方向的明暗分界线
水平边缘−1 −2 −1 / 0 0 0 / 1 2 1水平方向的明暗分界线
模糊9 个格子全是 1/9把周围平均一下 → 噪点消失,细节变糊
锐化中间 5,四邻 −1把"自己和邻居的差异"放大 → 边缘更硬
浮雕−2 −1 0 / −1 1 1 / 0 1 2给出方向性的立体感
原样复制中间 1,其余 0什么都不做(这是卷积的"单位元":乘进去什么都不改变的那个数)
⚠️ 别误解

上面这些核是人类手工设计的经典例子,用来帮你理解"卷积能干什么"。 真实网络里的卷积核是训练出来的,长得跟这些不完全一样,但浅层的核往往确实很像边缘/纹理检测器。 想看自己训出来的核长什么样:把第一层的每个 3×3 核当成一张小图打印出来就行——上面画廊里显示的那个 3×3 方阵就是这么摆的,表里每一行也是一个核按行写开。

5

池化:缩小,但留下最重要的

特征图太大了,计算吃不消。池化的做法很暴力:每 2×2 的一小块里,只留最大的那个数。 面积直接砍到 1/4,信息留下最"亮"的部分。

互动 · 2×2 池化

6×6 输入 → 2×2 窗口、步长 2(窗口每次挪 2 格)→ 3×3 输出。参数 0 个,完全没有要学的东西。

💡 池化为什么有用

假设这 2×2 里最大的那个数是"这里有一条边"。不管这条边在窗口里偏左一像素还是偏右一像素, 最大值都一样。所以池化提供了一点"容忍误差"的能力——在同一个池化窗口之内,轻微位移、轻微变形都不影响结果。

那被丢掉的 3/4 信息呢?确实丢了。池化赌的是「最强的响应出现在哪一块附近」比精确位置更重要——后面接全连接做判断时,需要的正是这个。

6

层层叠起来:从"边缘"到"猫"

一层卷积只能看 3×3。但把卷积 + 池化反复叠,感受野会越来越大, 网络看到的东西也越来越"高级"。

第 1~2 层

看到的:边缘、色块

第 3~4 层

看到的:角、弧线、纹理部件

第 5 层以后

看到的:眼睛、耳朵、整张猫脸

🎯 类比 像拼乐高:边缘是小砖块 → 角、弧线是小组件 → 眼睛、耳朵是模块 → 猫脸是成品。 层数越深,能表达的"零件"越复杂。"深度学习"里的"深"就在这。

互动 · 立方体里的小立方体:一个核怎么吃掉所有通道

7

把整个流程串起来

前面几节都在讲零件。这一节把它们接起来串成一条路:卷积 → 池化 → 展平 → MLP。 下面第一层卷积的输出和输入一样大(都 32×32):四周各补了一圈 0(填充),所以尺寸不变;第 M 节细讲。

互动 · 完整前向过程逐步播放

💡 规律

空间尺寸一路变小(224 → 112 → 56 → 28 → 14 → 7),通道数一路变多(3 → 64 → 128 → 256 → 512)。 通道就是每个位置上的那一串数:彩图有红绿蓝 3 个通道,卷积层里每个核产出 1 个通道。 翻译成人话:图片越来越小,但每个位置携带的"语义信息"越来越丰富。 最后那 7×7×512 已经不再像图片,而是一组"关于整张图的高层描述"。

CNN 和 MLP 的关系:一张图看懂

左边是这一章讲的卷积部分,右边是 《MLP 多层感知机》那一章的全连接部分(数字取自 VGG-16)。 它们不是两种模型,而是同一个模型的两段。 VGG-16 是 2014 年一个经典的 16 层卷积网络,最后在 ImageNet(约 120 万张图、1000 类的图像数据集)上分 1000 类——和上面演示的 4 类是两个不同的例子。

⚠️ 这条分界线在哪

分界点就是那个 Flatten(展平)。
展平之前:数据还是三维的(高 × 宽 × 通道),卷积核在上面滑,保留空间结构, 所以能"看出哪里有什么"。
展平之后:变成一根长长的向量。每个数还固定对应原来的某个位置, 但「谁和谁挨着」这层关系没了:把所有像素按同一个固定顺序打乱,MLP 学起来一样好,CNN 会明显变差。
所以最后那几层全连接只负责拿特征下判断;这也是 MLP 单独处理图片效果差的原因:它从第一层起就没用上「谁和谁挨着」。

🎯 类比 CNN 像一个流水线上的质检团队:一层一层地把"原料"加工成"半成品", 每一站都在丢掉位置细节、提炼语义。
到了 Flatten 这一步,相当于把一块立体雕塑碾成了一张清单—— "有 512 个数值,分别是……"。
MLP 就是拿到这张清单的决策者:它不需要知道雕塑长什么样, 只看这 512 个数就能拍板"这是猫"。

M

数学 · 一块小窗口到底能盖几次

第 2 节看着小窗口一格一格滑过去。这一节只回答一个问题: 它一共能盖几个位置?答案就是下面这条式子。N 是输入边长,k 是核大小,p 是补的圈数,s 是步长——拖四个滑块,输出格子跟着变。

互动 · 输入多大、窗口多大、跳几步,决定输出多大

🎬 自己验一遍

把「步长」从 1 调到 2:绿点立刻只剩 1/4——跳着走,能盖的位置就少了,输出变小。 再把「填充」先拖到 0(输出缩一圈),再拖回 1(又长回来)。搭网络时算每一层输出多大,用的就是这一行。

8

卷积这个操作,还有哪些变体

前面几节讲的都是标准卷积。它有一个通病:感受野和计算量是绑死的, 想看得更远就得堆更多层,想更省就得把图缩小。 这一节把人们绕开这个绑定的五种办法排开(第一行「标准卷积」是基线)。

变体它改了什么一行写法什么时候用
标准卷积 基线:3×3 窗口,逐位相乘再相加 Conv2d(cin, cout, 3) 默认。绝大多数位置都用它
1×1 卷积 窗口缩到一个像素,只看通道方向 Conv2d(cin, cout, 1) 改通道数、跨通道混合。GoogLeNet 用它先把通道压下来,再做 3×3(GoogLeNet 是 2014 年的经典网络,后面《经典 CNN 家族》里讲)
空洞卷积 把窗口「撑开」:采样点之间留空,不动分辨率就扩大感受野 Conv2d(..., dilation=2) 语义分割。一次盖 5×5 的范围,却只花 3×3 的参数量
深度可分离卷积 拆成「逐通道各卷各的」+「1×1 混合」两步 Conv2d(c, c, 3, groups=c)
+ Conv2d(c, cout, 1)
移动端。计算量和参数量省 8~9 倍,精度只掉一点
转置卷积 把卷积反过来:小图变大图 ConvTranspose2d(...) 上采样。分割的 decoder、生成模型都靠它
可变形卷积 那 9 个采样点的位置也变成可学的 学一个 offset 再采样 目标形状多变时(文字、行人)。代价是实现复杂

这张表只讲「卷积这个操作怎么变」。那些整机的经典网络 (AlexNet / VGG / ResNet)是另一件事,在 《经典 CNN 家族》那一章。 后面两行是特定任务的工具。
写法里的 cin / cout 是输入 / 输出通道数,groups 是把通道分成几组各卷各的,offset 是采样点额外学出来的位置偏移。

1×1 卷积听起来什么都没干,为什么还要它?

因为它干的不是「看邻居」,是「把通道重新组合一遍」。 3×3 卷积在每个位置把 9 个空间邻居乘起来,1×1 把同一位置的所有通道乘起来。 想改通道数又不想动空间尺寸,就只有它。

9

卷积什么时候不够用

卷积之所以省参数,是因为它假设了两件事:一次只看一小块(局部性), 并且让同一块核走遍全图(权值共享)。权值共享带来一个性质:东西挪到哪,响应就跟着挪到哪(平移等变)。 这两条在图像上几乎总是对的,所以它赢了二十年。但也正是它们,划出了它的边界。

2026 年的一次实测

2026 年一篇视网膜疾病筛查的基准,把 12 个架构分成 4 族比了一遍: 简单的二分类上大家都不错,难的多标签任务上 attention 系和混合架构更强。 这只是一个医学影像任务,但方向和这几年的公开榜单一致:纯 CNN 没有被淘汰,只是不再是默认答案。

所以,它不成立的地方在哪

它不成立的地方什么时候真的会痛怎么办
全局长程依赖要靠堆层数硬看 图像里相隔很远的两个部分要发生关系 换成带注意力(attention)的骨干(骨干=网络里负责提取特征的主体部分)。CNN 要几十层才够的距离,attention 一层就到
它假设了平移等变 位置本身携带信息:医学影像、文档版面、有坐标意义的场景 显式加位置信息(坐标通道、位置编码),别指望卷积自己留着
标准的 2D 卷积只吃规则网格
像素排成规整方格的那种数据
文本序列、图结构(→ 《图神经网络》)、点云、任意变长的输入,都不是二维网格 文本要用一维卷积(窗口只沿一个方向滑),图和点云要专门设计的变体(图卷积、点卷积),或换 attention。Transformer(全靠注意力搭起来的模型,后面章节讲)能在文本、图像、语音上通用,这是一个重要原因
它的归纳偏置在数据多时会变成束缚
归纳偏置=结构里事先认定的规律,比如「附近的像素更相关」
数据量已经大到不需要「先验帮忙」了 数据少时卷积的先验(就是上面说的归纳偏置)是帮忙的,数据多时它限制上限。这也是 ViT(把图片切成小块、用注意力处理的模型,后面章节讲)要在大数据上才赢的原因
它在端侧不是最省的 要在手机上实时跑 换成第 8 节的深度可分离卷积,或者整机换成 MobileNet 系(为手机设计的轻量网络)

怎么判断手上的任务会不会踩到这些边界?问一句:把图里的东西挪个位置,答案该不该变——该变(比如「这块阴影在左肺还是右肺」),就别指望卷积自己保住位置。

那我现在做图像任务,该用 CNN 还是 ViT?

看两件事:数据量(小数据就用 CNN,它的先验能帮你省样本) 和有没有现成的预训练权重(有就用 ViT / hybrid,它们在大数据上更强)。 两边都占的话,混合架构最稳。

10

小结

这一章站在 ② 没有免费午餐 这条线上:CNN 先对图像下了两个判断(挨得近的更相关、同一块核走遍全图), 再拿「只看局部 + 权值共享」换来省参数;权值共享还带来一个性质——东西挪到哪,响应跟着挪。

它对应哪条线 ② 没有免费午餐——CNN 的每一处设计都是对图像世界的一种假设
一句话 MLP 给每个像素单独拉线,「谁挨着谁」「挪了位置还认不认得」全靠数据自己学; CNN 把「只看局部 + 同一块核走遍全图」直接焊进结构。
它牺牲了什么 牺牲了通用性。正因为把「只看局部」和「同一块核走遍全图」焊进了结构,它在图像上极其高效; 但二维卷积只吃规则网格,换成序列要用一维卷积、RNN(按顺序一个个读的网络,后面章节讲)或注意力。暗线 E 说的就是这个。
🎬 自己验一遍

回到第 2 节那张卷积演示。把鼠标在右边 6×6 输出图上从左划到右—— 窗口一路跟过去,每一格都是同一块 3×3 核算出来的。

那个瞬间你看到的就是「假设被焊进结构」:同一套 9 个数字,不管滑到哪儿都在找同一种东西。 再回第 1 节把十字挪三格:全连接那根条掉到 0,小印章那根一动不动。

这条假设的三个组成部分

假设代码里的体现什么时候它会失效
局部性
附近像素更相关
3×3 的窗口只看相邻九个格子 图像里相隔很远的两块需要互相参照时(比如"这张脸和那片天空配不配")
平移等变性
猫挪了位置,响应跟着挪
同一块核滑遍全图,权重共享 位置本身就是信息时("这块阴影在左肺还是右肺"):卷积只是让响应跟着挪,后面的池化加全局平均会把位置收掉,所以最后只知道「有」、不知道「在哪」
层次性
边缘→纹理→部件→物体
一层层堆叠,每层感受野变大 只需要一层就能判断的任务里,深堆叠纯属浪费

它在六条暗线里站在哪

暗线这一章的回答
A 信息流动 输入是 (高, 宽, 通道) 的三维张量,卷积一直保留空间结构,直到 Flatten 把它压平——那一步不可逆。
B 什么被牺牲了 牺牲了「任意两个位置可以直接相连」的自由;换来的是参数不再随图片尺寸增长——一个 3×3 核只有 9 个数,第 3 节那层 64 个核、3 个输入通道合起来是 (27 + 1) × 64 = 1792 个(那个滑块量的就是它)。
C 参数账本 一块 3×3 核就 9 个参数,图多大都不变;换成同样大小的全连接层,8×8 输入就要 64 × 36 = 2304 个。
D 跑在什么上 算力受限的那一类。同一块核要在 224×224 的图上盖 5 万多个位置,每个权重被重复使用几万次——取一次、用很多次,这正是 GPU 最喜欢的计算形状,也是卷积比全连接跑得动的原因。一次算一批(batch)比一张一张算更能喂满硬件,这是 batch 在硬件上的好处。更细的账在 《硬件与算力账本》里。
E 它假设了什么 见上面那张表:CNN 厉害,是因为它猜对了两个关于图像的假设(局部性、权值共享→平移等变)。这是这一章最该被记住的事。
F 违背了哪个直觉 "看得越多越准"是错的:一层 3×3 看到的信息少得可怜,堆几十层后最上面的神经元却能看到整张图——感受野不是一层给的,是叠出来的(下一章 《感受野与下采样》)。

🎯 前后钩子 它接住了前面的什么:上一章 《超参怎么选》说损失高就加模型; 可图片上直接加全连接会参数爆炸——CNN 的局部连接与权值共享正是为它准备的。

它给下一章留了什么:卷积核只有 3×3,看不到大东西。 那"整张图"是怎么被看到的?《感受野与下采样》。

11

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式