跳到主要内容

数据截至 (上游 commit c187ef3271d5)

图像就是一摞数 — 卷积网络怎么把「谁挨着谁」保住

这一章讲三件事。第一件:一张照片在机器眼里到底是什么。 答案很朴素: 一摞排好队的数,每个数是一个点的亮度。

第二件,也是这一章的主干:第 02 章那种「每个输入各连一根线」的网络,处理图像会出大问题 —— 它把「哪个像素挨着哪个像素」这件最要紧的信息整个丢掉了。换上卷积就是为了修这一处。

第三件:一套配套的工程手法 —— 数据不够时怎么凭空造出变体、怎么让深层网络训得稳、 怎么防它把训练图背下来。

在全书链条里的位置: 第 03 章之后,训练循环再也没改过。 在原书第 2 到第 10 章那一段里 —— 也就是我们的第 02–09 章加第 12 章 —— 变的永远只有三个零件:数据摆成什么形状、网络里放什么层、输出层配哪把尺。 这一章换的是前两个 —— 表格 (样本数, 特征数) 换成图像 (样本数, 通道, 高, 宽), 全连接层换成卷积和池化。 (范围之外的几章各有各的断口: 第 10、11 章那一类模型训不动、整章没有训练循环, 第 13 章干脆把这段循环整个抽走。)

1. 顶层全景:一张吉娃娃照片走完全程

这一章的主走查是一个很不体面的问题:这张图是松饼,还是吉娃娃? 书用了一整节来铺垫这个问题有多难 —— 烤成金黄、点着两颗黑巧克力豆的松饼, 和一只趴着的小吉娃娃,在低分辨率下真的很像1

计算机视觉(让机器从像素里读出意义的那一行,也叫机器视觉、图像理解2) 按「答得多细」分成三级,这一章只做最粗的第一级:

级别输出什么在哪一章
图像分类给整张图贴一个标签本章
目标检测框出每个东西在哪第 05 章
语义分割给每一个像素判一个类第 05 章

书里用作者自己那只趴在沙发上的狗 Kiki 演示这三级3

主走查那张图从进来到出结果,链条是这样的:

一张彩色照片

├─① 缩成 32×32、转成灰度 ──────→ 一摞 32×32 个数,只有一层

├─② 训练时随机翻转、转 ±10° ───→ 同一张图变出好多张(只在训练时做)

├─③ 6 个小方块各滑过整张图 ────→ 6 张 30×30 的「哪里有这种花纹」图

├─④ 每 2×2 只留最大的那个 ─────→ 6 张 15×15,尺寸减半

├─⑤ 再来一轮 ③④ ──────────────→ 16 张 6×6

├─⑥ 全部拉直成一长条 ──────────→ 576 个数

└─⑦ 两层全连接 ───────────────→ 最后吐 1 个数

图说:③④⑤ 是这一章的新东西;⑥⑦ 是第 02 章那种老式网络,它被留在了最后 —— 先让卷积把「图里有什么花纹」提炼出来,再交给老式网络下判断。

2. 图像在机器眼里是什么:一摞 0 到 255 的数

结论先行:图片没有任何特殊之处,它就是数。

屏幕上一张图由一格一格的像素拼成,每个像素带一个颜色值。 常见的记法是把颜色拆成红、绿、蓝三种基色,按不同比例加起来配出其他所有颜色4

在代码里,一张图被摆成第 01 章那种张量,通常写成三个数:高 × 宽 × 通道。 高和宽就是纵向、横向各有多少个像素;通道是这一章最要按住的一个词 —— 它指的是「同一张图上摞了几层数」,每一层记同一件事的一个侧面5

摞几层,取决于图是什么样的6:

图的种类通道数每个数是什么张量形状
灰度图1亮度,0 是纯黑、255 是纯白(1, 高, 宽)
彩色图(RGB)3红、绿、蓝各自的强度(3, 高, 宽)
带透明度(RGBA)4多一层「这一点有多透明」(4, 高, 宽)

书里那张示例图被处理成了 (1, 100, 100) —— 一层、100 行、100 列, 一共一万个数7我们眼里的明暗,在计算机那里就是这一万个数字,没有别的。

记住通道这个词,后面它会变个身份回来。 第 5 节你会看到: 网络内部那些「摞起来的层」也叫通道,只不过那时候每一层记的不再是颜色, 而是「这张图的这个位置有没有某种花纹」。

3. 为什么不能像表格那样喂进去

先看现象。 第 02 章的做法是:一行数据有 30 个特征,就给输入层 30 个口子, 每个口子连到下一层的每个神经元。照搬到图上,一张 32×32 的灰度图有 1024 个数, 那就给 1024 个口子 —— 代码跑得起来,效果却很差。

原因只有一句话:那样一来,「谁挨着谁」就全丢了。

把 32×32 的方阵拉成一条 1024 长的队,第 1 个数和第 33 个数在原图上是上下相邻的两点, 拉直之后它们隔了 32 个位置;而第 1 个和第 2 个原本左右相邻,拉直后仍然挨着。 同样是「相邻」,拉直后的距离完全不同,而全连接层看不出这个区别 —— 对它来说 1024 个输入是 1024 个互不相干的数,顺序怎么排都一样8

可图像里几乎所有有意义的东西都是「一小片相邻像素凑出来的」:一条边、一块纹理、一只耳朵。 丢掉邻接关系,等于把图像里最要紧的信息在第一步就扔了。

书里对这一点的措辞是:传统的前馈(数据从输入一路向前流到输出、中途不回头)网络 一个像素一个像素地单独看, 而卷积网络能认出局部的图案,并且靠不同的网络层学出不同层级的图案9

4. 卷积:一个小方块滑过整张图,而且滑到哪儿都用同一套数

这一节是全章的地基。一句话说完:卷积就是拿一个小方块盖住图的一小片, 算出一个数,再挪一格接着算。分三步走,一步都不能跳。

第一步:换一种看法 —— 别一次看整张图,一次只看一小片。 拿一个 3×3 的小方块,盖在图的左上角,只看被它盖住的那 9 个像素。 方块里也有 9 个数,把对应位置一乘、九个乘积一加,得到一个数。 然后方块往右挪一格,再算一个数;走完一行换下一行10

书给了一个具体例子,我们把它算给你看。输入是一张 5×5 的图, 方块里的 9 个数是一种常见的边缘检测配置11:

输入图(5×5) 小方块(3×3)
0 1 0 1 1 0 1 0
0 0 1 1 0 1 -4 1
1 1 0 1 0 0 1 0
0 1 0 0 1
0 0 1 1 0

把方块盖在左上角,盖住的是输入图左上那 3×3 块:

盖住的: 0 1 0 方块: 0 1 0
0 0 1 1 -4 1
1 1 0 0 1 0

对应位置相乘再全加起来:
0×0 + 1×1 + 0×0 + 0×1 + 0×(−4) + 1×1 + 1×0 + 1×1 + 0×0 = 3

图说:这就是一次卷积计算,结果是一个数:3。方块往右挪一格再算一次得 −3, 再挪一格得 −1;换到第二行第一格得 −2 …… 5×5 的图被 3×3 的方块滑完, 得到一张 3×3 的新图12:

3 −3 −1
−2 3 −3
−3 2 3

这张新图叫特征图 —— 每个数回答同一个问题:「这个位置像不像方块要找的那种花纹?」 数越大越像13

第二步,也是书里没讲、但不补就说不通的一级台阶:

同一个方块,滑到哪个位置用的都是同一套 9 个数。

这一级看着不起眼,它一次买回三样东西 ——

其一,参数塌缩。 上面那个例子里,要调的数只有方块里那 9 个; 换成第 02 章那种全连接的做法,25 个输入各要一份权重,而且每个输出神经元都要一整套。 图越大,这个差距越夸张。

其二,换个位置照样认得。 因为左上角和右下角用的是同一套数, 所以只要在左上角学会了「这里有一条竖边」,同一条竖边挪到右下角照样被算出高分。

其三,邻接关系被保住了。 每个输出只由原图上相邻的 9 个像素算出来, 「挨着」这件事从头到尾没被打散。

这三样正是第 3 节那个毛病的解药。(这一级台阶是我们补的 —— 原书只描述了滑窗怎么算,没说「为什么这么做有效」。)

第三步:方块里那 9 个数不是人定的,是训出来的。 书里说得很清楚:在图像处理软件里这些数是手工设计的,而在卷积网络里, 模型在训练中自己调整这些数,直到它们最擅长解决当前这个任务14。 换句话说,第 01 章那个「挪权重」的循环,挪的就是这 9 个数。

一层通常不止一个方块。放 6 个,就得到 6 张特征图 —— 一个可能专挑竖边, 另一个专挑横边15这 6 张特征图摞在一起,就是第 2 节说的那个「换了身份的通道」。

这种以卷积层为核心的网络,行话叫卷积神经网络,缩写就叫 CNN; 它是这一章、也是整个计算机视觉领域最常用的一种结构16

5. 池化与层级特征:一边缩小,一边升级

卷积之后还有一道很便宜的工序,它就是把上一步那张图缩小。 做法:拿一个 2×2 的小窗口在特征图上挪, 每挪一次只保留窗口里最大的那个数(这叫最大池化;也有取平均的做法)17

拿上一节那张 3×3 的特征图走一遍。左上角那个 2×2 窗口盖住的是:

3 −3 四个数里最大的是 3
−2 3

四个窗口各取一次最大值,3×3 缩成 2×2。尺寸小了,后面每一层要算的量就少了; 而且一个花纹稍微挪一两个像素,窗口里的最大值往往不变 —— 书给的两条理由正是这两条:降维(把后面每一层要处理的数变少)以加速训练, 以及对位置的轻微移动不那么敏感18

判断(我们的,不是书里的): 书里讲池化那张图的左上角写的是 4, 得出的池化结果是 [[4,3],[3,3]];但上一节那张特征图的左上角是 3我们按书自己给的输入图和方块把这一格重算了一遍(见第 4 节的算式),结果是 3。 所以对得上的是特征图那张,池化图的 4 是排版时改错的一个数; 按 3 重算,池化结果应该是 [[3,3],[3,3]]如果错,会错在: 如果那张 5×5 输入图或那个 3×3 方块在原书里还有别的数值、 而转码时丢了字,我们的算式就建立在错的输入上。判据是:另外八格我们也核过, 两张图完全一致,只有左上角这一格对不上 —— 一个数错的可能性远大于八个数巧合。

把「卷积 + 池化」重复几轮,就得到这一章最重要的一个效果:层级特征。 书里的说法是:浅层学到的是简单的边缘和纹理,深层才学到更复杂的形状, 比如腿、耳朵这类身体部件;到最后一层,网络给出各个类别的可能性19

原图 → [边缘、纹理] → [眼睛、耳朵、腿] → [这是一只狗]
浅层 深层 输出

图说:一边把尺寸越缩越小,一边把「看懂的东西」越提越大块。

6. 数据增强:数据不够时,凭空造出更多样本

先看现象: 图像数据集贵在标注,更贵在拍摄。想让模型见多识广, 最直接的办法是再去收集几千张照片 —— 而这往往既费钱又费时间20

数据增强换了个思路:不去拍新的,把手上这张改一改,当成新的。 把图转一转、镜像翻一翻、调调亮度和对比度,就得到同一个对象的一批变体21

为什么这么做有效: 书给的解释是,模型见过同一个东西的更多变化之后, 会去学那些本质的特征,而对「位置偏了一点」「转了个角度」这类无关变化变得不敏感, 从而避开第 02 章那个把训练数据背下来的毛病22

边界很硬,而且书是用代码而不是用文字表达的: 训练那一套变换有六步, 验证和测试那一套只有四步 —— 随机翻转和随机旋转这两步只在训练时做23

训练用验证 / 测试用
缩到 32×32
转灰度
随机水平翻转
随机旋转(最多 10°)
转成张量
按均值 0.5、标准差 0.5 缩放

补充(不在书里,来自通用知识):为什么验证和测试不能加随机变换 —— 一是同一份数据每跑一次成绩都会变,那个数就没法比; 二是你想量的是「模型在真实图片上多准」,不是「在被随机拧过的图片上多准」。

7. 两件防身器:BatchNorm 与 Dropout

第 03 章为了让你读懂代码,已经各给过它们一句话。这一节把「为什么有效」和「边界」补上。 书把这两件东西放在一个新的例子里讲,那也是它们唯一真正上场的地方, 所以我们跟着换一次场景 —— 详见本节末的第二条走查。

7.1 BatchNorm:别让每一层脚下的地毯被人抽走

它解决什么问题。 训练时权重每一批都在变,于是下一层收到的数,量级也一直在变。 书打的比方很传神:像有人在你脚下不停地抽动地毯,你还得站稳24。 这会让训练不稳、优化器难以收敛;压低学习率能缓解,但训练也跟着变慢。

书给这个现象起了个专名,拆开看就好懂:变量就是「一个会变的量」, 这里指的是每一层收到的那批数;它的量级一路飘移,叫协变量偏移; 发生在网络内部,所以全称是内部协变量偏移

它怎么做。 三步:算出当前这一批数据在这一层的均值和方差 (方差量的是这批数散得有多开,第 02 章那把「标准差」的尺就是它开一次平方)→ 按它们把这一层的输出 拉回标准量级 → 再做一次缩放和平移,而这两个缩放平移的系数本身也是训出来的参数25。 PyTorch 里按数据形状分成两种:表格数据用 BatchNorm1d,图像用 BatchNorm2d

为什么有效 —— 这里必须岔开一句,因为书给的解释已经被推翻了。

判断(我们的,不是书里的): 书把 BatchNorm 有效的原因说成是「消除了内部协变量偏移」 —— 就是上面那条「地毯被抽走」。那是 2015 年提出这一层时的原始说法,后来被推翻了。 2018 年的两项工作指出,减少内部协变量偏移并不是它起作用的机制; 真正的作用是让损失函数的优化地形变得更平滑,从而允许更大的学习率、加速收敛26。 这不是这本书写错了,是它没跟上 —— 而修正比这本书早了八年。 如果错,会错在: 如果后来又有工作把「优化地形更平滑」这个解释推翻、 重新支持了原始说法,那我们这里就把一个中间结论当成了定论。 判据是看这一行有没有出现新的、被广泛接受的第三种解释。

实用上这不影响你怎么用它: 加了它训练更稳、能用更大的学习率 —— 这一条两边都同意。

7.2 Dropout:让一部分神经元随机休假

它解决什么问题。 第 02 章那个「把训练数据背下来」的毛病。

它怎么做。 训练时,按一个你自己定的概率 p,随机让一部分神经元这一轮彻底不干活 —— 既不参与前向计算,也不参与反向传播27

为什么有效。 书的比方是一个项目团队:每天随机抽几个人放假,剩下的人得把活全干了; 这样你就能看出哪几个人是真的关键28。落到网络上:因为谁都可能缺席, 网络没法把宝压在某几个固定的神经元上,只好把本事分散到更多神经元里。

一个容易被问到的细节: 推理时所有神经元都在,那输出岂不是比训练时大一截? 框架的做法是训练时就先把留下来的神经元的输出除以 (1−p) 补回来, 推理时就什么都不用调了 —— 这个做法叫倒置 dropout29

7.3 这两件东西在哪条走查上:手势六分类

主走查那个松饼网络里根本没有这两层。 书是在另一个例子里引入它们的, 所以我们也在这里另起一条短走查 —— 这一处和主走查用的不是同一份数据,请当心。

数据是 21600 张手势照片,按伸出几根手指分成 6 类(0 到 5 根)30。 网络是两个卷积块,每块都是「卷积 → BatchNorm2d → ReLU → 池化 → Dropout2d(0.25)」, 后面接全连接层,中间再挂一个 Dropout(0.5)31:

32×32 灰度图
→ 卷积(1→32 通道) → BatchNorm2d(32) → ReLU → 池化 → Dropout2d(0.25) 尺寸 32→16
→ 卷积(32→64 通道) → BatchNorm2d(64) → ReLU → 池化 → Dropout2d(0.25) 尺寸 16→8
→ 拉直成 64×8×8 = 4096 个数
→ 全连接到 256 → ReLU → Dropout(0.5)
→ 全连接到 6 ← 6 类,所以输出层放 6 个数(第 03 章那条规矩)

图说:BatchNorm 紧跟在卷积之后(先把量级稳住再过激活), Dropout 放在每一块的末尾。损失函数用 CrossEntropyLoss, 所以输出层不加任何激活 —— 又是第 03 章那条规矩32

训 5 轮,测试准确率 99.94%;而这份数据每类图片数完全一样, 所以「永远猜同一类」的基线恰好是 1/6 = 16.67%33

把这个 16.67% 和主走查那一次的基线摆在一起看,才能读懂基线这件事: 基线是多少,完全由数据里各类各占多少决定,不是一个固定的数。 (主走查那次的基线到底是多少,第 9 节末的判断块会重算 —— 书报的那个数不对。)

8. 另一条路:把图切成小块,当成一串词来读

卷积不是唯一的做法。 还有一条路叫视觉 Transformer(ViT), 它把那套本来为处理文字发明的结构搬到了图像上34

先说它面对的麻烦:那套结构是为序列(有先后次序的一串东西,比如一句话里排着队的词) 设计的 —— 一段文字有天然的先后顺序, 而一张图是二维的像素方阵,没有天然的「先谁后谁」35

它的做法分三步。第一步,把整张图切成一个个不重叠、大小相同的小方块, 每个小方块有个名字,叫图像块36

第二步,把每个小方块里的像素拉成一长串数,再把这串数转换成一串更紧凑的数 —— 这一步叫嵌入,做法和把词转换成一串数几乎一样,区别只在这里表达的是画面特征 而不是词义37。(嵌入这件事,第 06 章会专门讲一次,第 11 章再讲一次。)

第三步,给每个小方块补上「它原本在图的哪个位置」。 这一步叫加位置嵌入 —— 卷积靠滑窗天然知道空间关系, 而这套结构对输入的排列顺序一无所知,所以位置必须另外喂给它38

三步做完,这串带位置的数就进入那套标准结构,后面和处理文字完全一样39

这一节到此为止 —— 那套结构内部怎么转,书推给了它的第 9 章,我们放在第 11 章。 到那一章你会看到:书号称「深潜」,其实停在了半路,最关键的一步全书一个字没讲。

9. 主走查:1184 张测试图,从 32×32 走到 79.9%

这一节把前面所有零件装上。下面每个数都来自书。

第 ① 步,备数据。 数据集按「一个类一个文件夹」的方式摆好:训练目录下有 chihuahuamuffin 两个子目录,图片各自放进去。这么摆有个好处: 框架有一个现成的类能一行代码把它读成数据集,类别名就从目录名来40。 读进来之后按 8:2 切出验证集,最后是 训练 3786 张 / 验证 947 张 / 测试 1184 张41

第 ② 步,预处理。 每张图缩到 32×32、转成灰度 —— 书说这两步都是为了省算力 (就是省计算机要算的次数,它最后换算成时间和电费)42。 所以进网络的形状是 (1, 32, 32):一个通道、32 行、32 列。 训练那一路再加上第 6 节那两步随机变换。

第 ③ 步,第一层卷积。 6 个 3×3 的方块,一个通道进、6 个通道出。 32×32 被 3×3 滑完剩 30×30。下面这些形状里,第一个数是批大小——第 02 章讲过的那件事, 一次喂进去多少张;后面三个才是通道数、高、宽。所以形状变成 [批大小, 6, 30, 30]43这里的 6 就是第 4 节末说的那件事:6 个方块 = 6 张特征图 = 6 个通道。

第 ④ 步,ReLU 加池化。 2×2 最大池化把尺寸减半:[批大小, 6, 15, 15]

第 ⑤ 步,第二层卷积加池化。 16 个方块,6 个通道进、16 个通道出: [批大小, 16, 13, 13];再池化一次:[批大小, 16, 6, 6]注意通道数在涨(6 → 16)而尺寸在缩(30 → 6) —— 这就是第 5 节那句 「一边缩小,一边升级」在维度上的样子。

第 ⑥ 步,拉直。 16 × 6 × 6 = 576 个数,拍平成一条:[批大小, 576]。 到这一步「谁挨着谁」才被丢掉 —— 但此时丢的已经不是像素,而是提炼过的花纹强度, 代价小得多(这句是我们的解读,书没说)。

第 ⑦ 步,两层全连接。 576 → 64 → 1。两类,所以输出层只放 1 个数; 损失用 BCEWithLogitsLoss,输出层不加任何激活 —— 第 03 章那条规矩44

第 ⑧ 步,训练。 每批 256 张、30 轮、学习率 0.001。 训练损失一路降,但验证损失从大约第 20 轮起就不再改善,甚至略有回升 —— 按第 02 章那张图的读法,它从这里开始背答案了45

所以训练循环里挂了一句:只在验证损失创新低时才把权重存一次, 最后拿存下来的那一份去测46。(这件事第 13 章会变成一行现成的配置。)

第 ⑨ 步,测 1184 张。 准确率 79.90%47

第 ⑩ 步,和基线比。 书报的基线是 50.17%48

第 ⑪ 步,看得更细一点。 书这次没有只报一个数,还打印了一份分类报告49:

类别精确率召回率F1这一类有多少张
00.830.790.81640
10.770.810.79544
整体准确率0.801184

读法(前两列第 03 章讲过):类 0 那一行的 0.83 是说 —— 模型每次判「这是类 0」, 有 83% 真的是;0.79 是说 —— 所有真的属于类 0 的图里,它找出来了 79%。 第三列 F1 是把这两个数合成一个:两个都高它才高,有一个低它就被拉下来50。 最后一列是这一类在测试集里有多少张51

报告最后还有两行汇总:一行是每个类算一遍再取平均、不管每类有多少张 (在类别悬殊的数据上,它才看得见少数类的死活); 另一行是按每类的张数加权平均52。这两行在这份数据上差别不大, 因为 640 和 544 本来就接近。

判断(我们的,不是书里的):那个 50.17% 根本不是基线 —— 它量的是另一件事, 而且书给的理由也和它自己印的数对不上。

先说理由那一层。 书写的是「两类图片数一样多,所以瞎猜应该是 50%」; 可它自己在紧接着的那份分类报告里就印了出来(就是上面那张表): 类 0 有 640 张、类 1 有 544 张,并不一样多

再说更重的那一层:那两行代码算的压根不是基线。 「永远猜多数类」这个对照要成立,得拿真实标签当答案去对。 而书那两行,把真实标签当成了输入,把模型自己的预测当成了答案48

于是 50.17% 的真实含义是:模型自己吐出来的那一堆预测里,最多的那一类占了多少。 它从头到尾没看过一眼正确答案,所以它既不是「瞎猜能拿多少」,也不是任何别的对照; 它只说明模型判两类的次数差不多一样多。这就是为什么印出来是 50.17% 而不是 54.1%。

拿真实标签当答案重算一遍,才是第 03 章那条规矩要的基线:640 ÷ 1184 = 54.1%。 所以真实的领先幅度是 79.9% − 54.1% ≈ 26 个百分点, 而不是照书那个 50.17% 算出来的 30 个。

如果错,会错在: 如果 640 和 544 这两个数指的不是测试集而是别的什么集合, 后半段那条算术就落空。判据在书里:那两个数印在分类报告的最后一列, 而那份报告算的正是这 1184 张测试图。 前半段那条不依赖算术 —— 两行代码原样印在书上,参数摆的是什么一看便知。

判断(我们的,不是书里的):这一步还藏着两个 bug,而且其中一个会一路带到线上。

第一个:阈值又卡在了原始分数上。 书在这里写「此时预测还是概率(0 到 1 之间的值)」, 然后拿 0.5 卡阈值53。可这个网络用的是 BCEWithLogitsLoss,输出层没有任何激活 —— 吐出来的是第 03 章说的 logits,取值负无穷到正无穷。这是第 03 章那处错误的原样重犯。

第二个:类别名对错了位置。 书自己打印过类别顺序,是 ['chihuahua', 'muffin'],并且明说吉娃娃排在第 0 位、松饼排在第 1 位54。 训练用的就是这套编号配 BCEWithLogitsLoss,所以模型输出的那个数越大, 越是在说「这是松饼」。可书的判定写的是「大于阈值就是吉娃娃」55 —— 反了。

在这一章它没露馅,因为书把真实标签也用同一套反过来的对应关系转了一遍, 一正一反抵消,准确率和四格都不受影响。 但第 15 章把这段代码原样搬进了线上服务,那里只有预测、没有真实标签, 抵消不掉 —— 于是拿一张松饼图去问它,它回答「吉娃娃」。 那一章会把这条线收网。

如果错,会错在: 如果 ImageFolder 给出的类别顺序不是书打印的那样, 或者训练时用的标签不是这套编号,这条判断就不成立。 判据很直接:书在第 741 行印出了那个列表,并在下一段用文字确认了谁排第 0 位; 而训练标签就是 ImageFolder 给的那一个。

10. 书里的立场与证据

书里给了证据的:

  • 卷积的算法本身 —— 给了 5×5 输入、3×3 方块、3×3 输出的完整数值例子,可以自己验算 (我们验算了,见第 5 节那个判断块);
  • 层级特征 —— 书给的是示意图和描述,不是实验证据,但这是这一行公认的观察;
  • 两个成绩 —— 79.90% 和 99.94% 都是真实跑出来的,而且都配了基线对照(其中一个算错了)。

作者的经验判断(书里没给证据):

  • 「验证损失从大约 20 轮起不再改善,可以认为开始过拟合」 —— 是看曲线得出的判断, 书自己的措辞是「我们可以假设」;
  • 超参数(32×32、灰度、6 和 16 个方块、批 256、30 轮)—— 书只说 32×32 和灰度是为了省算力, 其余没解释;
  • 「一般推荐用不带激活的输出层配带 logits 的损失」 —— 经验法则,和第 03 章同一句话。

已经被推翻的: BatchNorm 的动机解释,见第 7.1 节的判断块。

11. 边界与局限

这本书对的地方先说清: 这一章把「图像为什么不能当表格喂」讲得很直白, 卷积和池化都配了可以自己验算的数值例子,维度链在代码注释里一步一步标出来 —— 这在同类书里并不常见,读者不必猜中间形状。

但有几处照抄会踩坑:

  • 阈值卡在原始分数上、类别名对错位置 —— 见第 9 节末的判断块。 这两处都给了行号,你可以自己去看;
  • 那个「和瞎猜比」的对照量错了东西 —— 它拿模型自己的预测当答案,所以那个 50.17% 不是基线;同样见第 9 节末的判断块;
  • pretrained=True 这个参数已经过时。 这一章后半(我们的第 05 章)要用别人已经训好的权重, 那件事叫预训练(拿别人在大数据集上训好的模型接着用,第 12 章整章讲它); 书调用它时写的是 pretrained=True,而 torchvision 从 0.13 起已经改用 weights=。 照抄能跑,但会收到一条弃用(这个写法以后会被删掉)警告56;
  • 手势那一节的脚本路径写成了上一节二分类的路径,两处一模一样,照着找会找错文件57

这一章没覆盖的:

  • 多标签分类不给代码,书明说骨干层一样,自己把输出激活换成 sigmoid、损失换成带 logits 的 BCE 就行;
  • ViT 只讲了三步入口,内部一个字没讲,推给了原书第 9 章(我们的第 11 章);
  • 卷积的两个常用旋钮 —— 步幅(方块一次挪几格)和填充(边上补几圈零)—— 只在代码里出现过, 正文没解释。 手势那个例子里 padding=1 就是靠它让 32×32 卷完还是 32×32 的, 书没提这一点;
  • 为什么是「6 个方块、16 个方块」而不是别的数,书没讨论;
  • 公式全是图片。 卷积、池化的算式在原文里一个字都没有,上面第 4、5 节那两处算式 是我们按书给的数值例子反推着写出来的。

12. 可带走的

全章那条走查,一行写完: 一张照片 → 缩成 32×32 灰度、张量 (1,32,32) → 训练时随机翻转旋转 → 6 个 3×3 方块滑一遍得 [BS,6,30,30] → 池化 [BS,6,15,15] → 16 个方块 [BS,16,13,13] → 池化 [BS,16,6,6] → 拉直 576 → 64 → 1 → BCEWithLogitsLoss 训 30 轮、第 20 轮起开始背答案、只存验证最好的那一份 → 1184 张测试图 79.9%,而正确的基线是 54.1%。

  1. 图 = 一摞 0 到 255 的数;通道就是「摞了几层」:灰度 1 层、彩色 3 层、带透明度 4 层;
  2. 拉直送进全连接层会把「谁挨着谁」丢掉 —— 这是图像不能当表格喂的全部原因;
  3. 卷积 = 一个小方块滑过整张图,每停一处算出一个数,拼成一张特征图;
  4. 同一个方块滑到哪儿都用同一套数 —— 这一条同时买回参数少、位置无关、邻接保住;
  5. 方块里的数是训出来的,不是人设计的;放 6 个方块就出 6 个通道;
  6. 池化 = 每 2×2 只留最大的,尺寸减半,顺带对轻微位移不敏感;
  7. 一边缩尺寸一边涨通道:浅层认边缘纹理,深层认耳朵轮子;
  8. 数据增强只在训练时做,验证测试绝不做 —— 否则成绩每跑一次都不一样;
  9. BatchNorm 稳住每层输入的量级;它的原始解释(内部协变量偏移)已经被推翻, 真正的作用是让优化地形更平滑;
  10. Dropout 让一部分神经元随机休假,逼网络把本事分散开;框架用「训练时除以 (1−p)」补偿;
  11. 分类报告一次给四个数:精确率、召回率、F1、这一类有多少张;类别悬殊时看不加权的那一行;
  12. 基线必须拿真实标签当答案算 —— 这一章那个 50.2% 拿的是模型自己的预测当答案, 量的是「模型判两类的次数有多平均」,不是基线;按真实标签重算是 640 ÷ 1184 = 54.1%;
  13. ViT 走另一条路:切块 → 嵌入 → 补位置,而位置必须另外喂给它。

13. 原文地图

主题原书章原文位置
计算机视觉的三级任务Chapter 4text/06-ch04-chapter-4.txt:9(搜「machine vision or image understanding」) · text/06-ch04-chapter-4.txt:19(搜「applies a stamp to」) · text/06-ch04-chapter-4.txt:24(搜「referred to as object detection」)
图像=像素与 RGBChapter 4text/06-ch04-chapter-4.txt:43(搜「combination of pixels」) · text/06-ch04-chapter-4.txt:47(搜「multidimensional tensors」)
三种通道数与取值范围Chapter 4text/06-ch04-chapter-4.txt:63(搜「only one color channel」) · text/06-ch04-chapter-4.txt:65(搜「completely black」) · text/06-ch04-chapter-4.txt:67(搜「three channels」) · text/06-ch04-chapter-4.txt:73(搜「stores information about transparency」)
示例张量 (1,100,100)Chapter 4text/06-ch04-chapter-4.txt:56(搜「(1, 100, 100)」)
CNN 与前馈网络的差别、层级特征Chapter 4text/06-ch04-chapter-4.txt:90(搜「classic feedforward networks」) · text/06-ch04-chapter-4.txt:93(搜「simple edges and textures」)
卷积的算法与数值例Chapter 4text/06-ch04-chapter-4.txt:112(搜「3 × 3 matrix」) · text/06-ch04-chapter-4.txt:117(搜「element-by-element multiplication」) · text/06-ch04-chapter-4.txt:121(搜「0 1 0 1 1」)
特征图、多个方块、方块的数是学出来的Chapter 4text/06-ch04-chapter-4.txt:136(搜「also known as a feature map」) · text/06-ch04-chapter-4.txt:140(搜「vertical edge」) · text/06-ch04-chapter-4.txt:159(搜「don’t set the weights」)
池化的做法与两条理由Chapter 4text/06-ch04-chapter-4.txt:162(搜「Another frequently used concept is pooling」) · text/06-ch04-chapter-4.txt:178(搜「reduce the dimensions in subsequent layers」)
勘误 池化图左上角那个数Chapter 4text/06-ch04-chapter-4.txt:165(搜「the maximum value (in this case, 4)」)
ViT 三步Chapter 4text/06-ch04-chapter-4.txt:188(搜「designed to process sequences」) · text/06-ch04-chapter-4.txt:194(搜「smaller patches」) · text/06-ch04-chapter-4.txt:200(搜「embedding vectors for the patches」) · text/06-ch04-chapter-4.txt:206(搜「Adding positional embeddings」)
松饼 vs 吉娃娃数据集Chapter 4text/06-ch04-chapter-4.txt:251(搜「distinguish muffins from Chihuahuas」)
数据增强的定义、目的、两套变换Chapter 4text/06-ch04-chapter-4.txt:293(搜「artificial enlargement」) · text/06-ch04-chapter-4.txt:297(搜「performance and robustness」) · text/06-ch04-chapter-4.txt:404(搜「augmentation for training」) · text/06-ch04-chapter-4.txt:429(搜「much simpler」)
目录结构与 ImageFolder、三份数据大小Chapter 4text/06-ch04-chapter-4.txt:479(搜「as many subfolders as there are classes」) · text/06-ch04-chapter-4.txt:538(搜「Train dataset size: 3786」)
网络结构与维度链Chapter 4text/06-ch04-chapter-4.txt:563(搜「dimensions (1, 32, 32)」) · text/06-ch04-chapter-4.txt:586(搜「[BS, 6, 30, 30]」) · text/06-ch04-chapter-4.txt:592(搜「[BS, 1666]」)
超参数、损失函数、只存最好的权重Chapter 4text/06-ch04-chapter-4.txt:448(搜「BATCH_SIZE = 256」) · text/06-ch04-chapter-4.txt:605(搜「binary cross entropy」) · text/06-ch04-chapter-4.txt:672(搜「store best model」)
20 轮后过拟合Chapter 4text/06-ch04-chapter-4.txt:709(搜「tends more towards overfitting」)
类别顺序、阈值、成绩、基线Chapter 4text/06-ch04-chapter-4.txt:735(搜「.classes property」) · text/06-ch04-chapter-4.txt:743(搜「still probabilities」) · text/06-ch04-chapter-4.txt:777(搜「Model Accuracy: 79.90%」) · text/06-ch04-chapter-4.txt:791(搜「Dummy Classifier Accuracy: 50.17%」)
分类报告与四个指标Chapter 4text/06-ch04-chapter-4.txt:801(搜「0 0.83 0.79 0.81 640」) · text/06-ch04-chapter-4.txt:810(搜「what is meant by」) · text/06-ch04-chapter-4.txt:818(搜「harmonic mean」) · text/06-ch04-chapter-4.txt:822(搜「macro-average」)
BatchNorm 信息框Chapter 4text/06-ch04-chapter-4.txt:1036(搜「more stable, faster」) · text/06-ch04-chapter-4.txt:1041(搜「moving the carpet」) · text/06-ch04-chapter-4.txt:1049(搜「means and variances of the current batch」)
Dropout 信息框Chapter 4text/06-ch04-chapter-4.txt:1061(搜「randomly “deactivated” during training」) · text/06-ch04-chapter-4.txt:1064(搜「large project team」) · text/06-ch04-chapter-4.txt:1070(搜「known as inverted」)
手势数据集、网络、成绩、基线Chapter 4text/06-ch04-chapter-4.txt:851(搜「21,600 images of hands」) · text/06-ch04-chapter-4.txt:1078(搜「two convolutional blocks」) · text/06-ch04-chapter-4.txt:1257(搜「0.9994444444444445」) · text/06-ch04-chapter-4.txt:1269(搜「perfectly balanced」)
勘误 那个「基线」量错了东西Chapter 4text/06-ch04-chapter-4.txt:786(搜「dummy_clf.fit(y_test_true, y_test_pred_class)」) · text/06-ch04-chapter-4.txt:780(搜「the accuracy should be 50%」)
勘误 手势那一节的脚本路径Chapter 4text/06-ch04-chapter-4.txt:859(搜「binary_img_classification.py」)

Footnotes

  1. 出处:「Chapter 4 Computer Vision」第 251-258 段(text/06-ch04-chapter-4.txt:251,搜「distinguish muffins from Chihuahuas」)。原文的措辞是「一个被严重低估的问题」,并在图 4.9 里摆了一组对比图,然后问读者:比你想的难吧?

  2. 出处:「Chapter 4」第 9-12 段(text/06-ch04-chapter-4.txt:9,搜「machine vision or image understanding」)。原文:这一行也叫机器视觉或图像理解,讲的是用算法有意义地处理像素这类视觉输入。

  3. 出处:「Chapter 4」第 19-27 段(text/06-ch04-chapter-4.txt:19,搜「applies a stamp to」)。原文:最简单的一类是图像分类,给整张图盖一个戳(专业叫法是 label);第二类在物体周围画一个框,叫目标检测(text/06-ch04-chapter-4.txt:24,搜「referred to as object detection」);最复杂的一类是给每个像素分类,叫语义分割。演示图用的是作者的狗 Kiki 趴在沙发上那张。

  4. 出处:「Chapter 4」第 43-46 段(text/06-ch04-chapter-4.txt:43,搜「combination of pixels」)。原文:计算机把图像看成像素的组合,每个像素由一个颜色值刻画;颜色通常用 RGB 模型编码,把红绿蓝三原色按不同方式相加得到其他颜色。

  5. 出处:「Chapter 4」第 47-49 段(text/06-ch04-chapter-4.txt:47,搜「multidimensional tensors」)。原文:在计算机视觉里,图像被当作多维张量形式的数值数据 —— 这是计算机能处理视觉信息的唯一形式;最常见的描述方式是三个维度 (H, W, C),分别是纵向像素数、横向像素数、以及承载每个像素颜色信息的通道数。

  6. 出处:「Chapter 4」第 59-74 段的信息框(text/06-ch04-chapter-4.txt:63,搜「only one color channel」)。原文三种情形:灰度图 C=1,每个像素是亮度,典型取值 0(纯黑)到 255(纯白);RGB 图 C=3;带 alpha 通道的图 C=4,多出来那一层存透明度信息。

  7. 出处:「Chapter 4」第 55-57 段(text/06-ch04-chapter-4.txt:56,搜「(1, 100, 100)」)。原文:我们理解为颜色(这里是亮度)的东西,对计算机来说就是数值。

  8. 补充(不在书里,来自通用知识):把二维方阵拉成一维之后,原本上下相邻的两个像素在一维序列里相隔一整行的长度,而全连接层对输入的排列顺序不敏感 —— 打乱输入顺序再固定住,它照样能训。书只说了前馈网络「一个像素一个像素地单独看」这个结论,没有把「拉直丢掉了什么」这一步展开。

  9. 出处:「Chapter 4」第 89-92 段(text/06-ch04-chapter-4.txt:90,搜「classic feedforward networks」)。原文:卷积网络与经典前馈网络不同,后者单独考虑每个像素,而卷积网络能识别局部图案,并通过不同的网络层学到不同层级的结构。

  10. 出处:「Chapter 4」第 116-119 段(text/06-ch04-chapter-4.txt:117,搜「element-by-element multiplication」)。原文:网络把这个方块系统地在整张输入图上移动,每停在一个位置,就在方块的值和它盖住的那块像素之间做逐元素相乘,再把乘积加起来得到一个值。

  11. 出处:「Chapter 4」第 108-113 段(text/06-ch04-chapter-4.txt:112,搜「3 × 3 matrix」)与第 121-131 段的三张矩阵(text/06-ch04-chapter-4.txt:121,搜「0 1 0 1 1」)。原文把这个 3×3 的方块标成「边缘检测器」。原书里卷积运算的公式是图片,提取出来是空行 —— 上面那条算式是我们按图里的数值把这一格重算出来的。

  12. 出处:「Chapter 4」第 121-131 段(text/06-ch04-chapter-4.txt:127,搜「0 1 0 0 1」)。特征图三行的数值:3 −3 −1 / −2 3 −3 / −3 2 3。我们逐格验算过前四格,与图一致。

  13. 出处:「Chapter 4」第 136-137 段(text/06-ch04-chapter-4.txt:136,搜「also known as a feature map」)。原文:这个过程的结果是一个矩阵,也叫特征图;它显示你要找的那个特征在图像的什么位置出现、以及有多明显。

  14. 出处:「Chapter 4」第 159-161 段(text/06-ch04-chapter-4.txt:159,搜「don’t set the weights」)。原文:我们不像在图像处理软件里那样手工设定卷积方块的权重,而是让模型在训练中调整这些权重,使它最好地识别出解决当前任务所需的特征;卷积层之后通常再过一个 ReLU 这类激活函数,给特征图引入非线性。

  15. 出处:「Chapter 4」第 138-140 段(text/06-ch04-chapter-4.txt:140,搜「vertical edge」)。原文:一个卷积层通常不只用一个方块,而是用好几个,每个被训练来识别一种不同的特征 —— 比如一个突出竖直边缘,另一个突出水平边缘。

  16. 出处:「Chapter 4」第 29-30 段(text/06-ch04-chapter-4.txt:30,搜「convolutional neural networks (CNNs)」)与第 80-82 段(text/06-ch04-chapter-4.txt:81,搜「extremely powerful while not being very」)。原文说本书后面的模型主要基于卷积网络,因为它们被证明极其有效,同时又不算复杂。

  17. 出处:「Chapter 4」第 162-166 段(text/06-ch04-chapter-4.txt:162,搜「Another frequently used concept is pooling」)。原文:通常拿一个 2×2 的矩阵在特征图上「推」过去,对每个当前选中的区域取最大值(最大池化)或平均值(平均池化)作为输出。

  18. 出处:「Chapter 4」第 178-179 段(text/06-ch04-chapter-4.txt:178,搜「reduce the dimensions in subsequent layers」)。原文:主要用池化来降低后续层的维度、加快训练;池化层还帮助模型对特征位置的移动变得不那么敏感。

  19. 出处:「Chapter 4」第 93-95 段(text/06-ch04-chapter-4.txt:93,搜「simple edges and textures」)。原文:图 4.3 展示一个被训练来分类动物的卷积网络,它在浅层学到简单的边缘和纹理,在深层学到更复杂的形状(例子里是腿、耳朵这类身体部件);网络末端是要预测的类别,给出每一类的概率,例子里以很高的概率判为狗。

  20. 出处:「Chapter 4」第 289-296 段(text/06-ch04-chapter-4.txt:293,搜「artificial enlargement」)。原文:与其去拍新照片(往往非常昂贵且耗时),不如生成一批属性略有调整的新版本,比如旋转、扭曲、镜像。

  21. 出处:「Chapter 4」第 302-303 段(text/06-ch04-chapter-4.txt:302,搜「random upside-down flipping」)。原文列举的手法:旋转、随机上下翻转、转灰度;第 964-967 段(text/06-ch04-chapter-4.txt:966,搜「randomly adjusts the brightness」)还列了随机调整亮度、对比度、色调、饱和度,以及随机的旋转与平移。

  22. 出处:「Chapter 4」第 297-301 段(text/06-ch04-chapter-4.txt:297,搜「performance and robustness」)。原文:目标是提升模型的表现和稳健性;用同一张图的更多变体来训练,模型学会识别一个物体的本质特征,并对位置、旋转这类无关变化变得不敏感,从而避免过拟合。

  23. 出处:「Chapter 4」第 404-425 段(text/06-ch04-chapter-4.txt:404,搜「augmentation for training」)与第 429-440 段(text/06-ch04-chapter-4.txt:429,搜「much simpler」)。原文明说训练用的变换比验证与测试用的更复杂;验证测试那一套只保证尺寸和通道数对得上。前两步(缩到 32×32、转灰度)的理由原文写得很直白:为了把计算量压低(text/06-ch04-chapter-4.txt:405,搜「keep the computing effort low」)。

  24. 出处:「Chapter 4」第 1035-1045 段的信息框(text/06-ch04-chapter-4.txt:1041,搜「moving the carpet」)。原文:训练中权重不断调整,数据又按批处理,如果各批之间差异很大,每一层的权重就得不断适应一个变化中的输入分布;这个问题的专业名称叫 internal covariate shift,它会让训练不稳定、让优化器出现收敛问题,降低学习率能对抗它但会拖慢训练。

  25. 出处:「Chapter 4」第 1046-1055 段(text/06-ch04-chapter-4.txt:1049,搜「means and variances of the current batch」)。原文三步:算出当前批次的均值与方差 → 归一化各层的激活 → 做缩放与平移,而缩放与平移的系数本身是这一层可学习的参数;PyTorch 按数据是一维还是二维提供 nn.BatchNorm1dnn.BatchNorm2d

  26. 补充(不在书里,依据我们的 ai-book-reference 书架):书给的「消除内部协变量偏移」是 2015 年提出这一层时的原始动机,后续研究已经修正了这个解释。依据: book=nndl-2e §18-init-preprocess-normalization 事实=该章写明批归一化的主要作用不在于减少内部协变量偏移,而在于让损失函数的优化地形更平滑、从而允许更大学习率并加速收敛,并注明依据是 Bjorck 等与 Santurkar 等 2018 年的两项工作。

  27. 出处:「Chapter 4」第 1059-1063 段的信息框(text/06-ch04-chapter-4.txt:1061,搜「randomly “deactivated” during training」)。原文:dropout 层用来降低网络的过拟合;训练时随机「停用」某些神经元及其连接,停用概率 p 是开发者自己指定的超参数;被停用的神经元既不参与前向传播,也不参与反向传播。

  28. 出处:「Chapter 4」第 1064-1067 段(text/06-ch04-chapter-4.txt:1064,搜「large project team」)。原文的比方:想象你在一个大项目团队里,每天随机决定哪些成员休假,剩下的人必须把全部工作做完 —— 这样你就能判断哪些关键人物对项目特别重要。补充(不在书里,依据我们的 ai-book-reference 书架):这一层出自 Srivastava 等 2014 年的工作。 依据: book=little-book-of-deep-learning §07-layers-for-trainability 事实=该章讲 dropout 训练时按概率 p 独立清零每个激活、用「逼每个激活独自承重、防搭伙背题」解释它为什么有效,并注明 dropout 引 Srivastava et al., 2014。

  29. 出处:「Chapter 4」第 1068-1075 段(text/06-ch04-chapter-4.txt:1070,搜「known as inverted」)。原文:框架(包括 PyTorch)常用相反的做法 —— 训练时把仍然活跃的神经元的输出除以 (1 − p),被停用的神经元输出为 0;推理时所有神经元都活跃,不再需要任何缩放,因为缩放已经在训练时做过了。

  30. 出处:「Chapter 4」第 850-854 段(text/06-ch04-chapter-4.txt:851,搜「21,600 images of hands」)。原文:数据集是 Kaggle 上的 Fingers,一共 21600 张手部图片,按伸出的手指数分类,从 0 根到 5 根共 6 类。数据切成训练 18000 / 验证 1800 / 测试 1800(text/06-ch04-chapter-4.txt:1014,搜「Training set size: 18000」)。

  31. 出处:「Chapter 4」第 1077-1080 段(text/06-ch04-chapter-4.txt:1078,搜「two convolutional blocks」)与第 1096-1144 段的代码。原文:两个卷积块,每块以一个卷积层起头,后面跟批归一化、最大池化和 dropout;两块之后是全连接层,最后是输出层。代码里两块的通道数是 1→32 和 32→64,两次池化把 32 缩到 16 再缩到 8,所以拉直后是 64×8×8。

  32. 出处:「Chapter 4」第 1148-1160 段(text/06-ch04-chapter-4.txt:1150,搜「without further activation」)。原文两个选项:用 nn.LogSoftmax()nn.NLLLoss(),或者直接用未激活的原始输出(logits)配 nn.CrossEntropyLoss();后者是一般推荐做法,数值上更稳定也更好实现。

  33. 出处:「Chapter 4」第 1255-1271 段(text/06-ch04-chapter-4.txt:1257,搜「0.9994444444444445」)与第 1269 段(text/06-ch04-chapter-4.txt:1269,搜「perfectly balanced」)。原文:由于数据完全平衡、每一类图片数相同,靠猜的正确率是 16.67%。训练轮数见第 897-901 段(text/06-ch04-chapter-4.txt:899,搜「EPOCHS = 5」)。

  34. 出处:「Chapter 4」第 184-187 段(text/06-ch04-chapter-4.txt:185,搜「transfer the idea of transformer architecture」)。原文:视觉 Transformer 把 transformer 架构的想法搬到计算机视觉领域;transformer 本身怎么工作放在第 9 章讲,这里只讲 ViT 特有的地方。

  35. 出处:「Chapter 4」第 188-192 段(text/06-ch04-chapter-4.txt:188,搜「designed to process sequences」)。原文:transformer 是为处理序列(通常是文本)设计的,而图像是二维的像素矩阵,没有明显的序列;视频算是一种序列,但这里不考虑。

  36. 出处:「Chapter 4」第 194-197 段(text/06-ch04-chapter-4.txt:194,搜「smaller patches」)。原文:ViT 先把给定图像拆成一批固定数量、互不重叠、大小相同的小图,相当于把整张图切成许多同样大小的小方块。

  37. 出处:「Chapter 4」第 200-205 段(text/06-ch04-chapter-4.txt:200,搜「embedding vectors for the patches」)。原文:ViT 把每个子图合成一个长向量,再对这个像素值向量做嵌入,做法与自然语言处理里的词嵌入或句嵌入非常相似;区别是那边表达的是词句的语义,这里反映的是图像特征。

  38. 出处:「Chapter 4」第 206-213 段(text/06-ch04-chapter-4.txt:206,搜「Adding positional embeddings」)。原文:在卷积网络里空间信息由卷积运算处理,而 transformer 对输入序列的空间排列一无所知,所以视觉 transformer 要额外加上位置嵌入,告诉每个小方块它原来在图的什么位置 —— 比如它是不是在图像左上角。

  39. 出处:「Chapter 4」第 219-222 段(text/06-ch04-chapter-4.txt:220,搜「backbone of language models」)。原文:三步之后,带着位置嵌入的向量进入 transformer 的编码器,后面就是标准的 transformer 网络;由于这类结构是语言模型的骨干,细节放在第 9 章展开。原文还说本书不从零实现这套结构,而是在后面微调一个现成的。

  40. 出处:「Chapter 4」第 477-496 段(text/06-ch04-chapter-4.txt:479,搜「as many subfolders as there are classes」)。原文:理想情况下数据已经按训练/验证/测试分好,每一份下面再按类别开子目录;满足这个结构,一行 ImageFolder 就能建出数据集。

  41. 出处:「Chapter 4」第 497-511 段(text/06-ch04-chapter-4.txt:505,搜「VALIDATION_SPLIT = 0.2」)与第 534-540 段(text/06-ch04-chapter-4.txt:538,搜「Train dataset size: 3786」)。原文的三个数:3786 / 947 / 1184。

  42. 出处:「Chapter 4」第 404-410 段(text/06-ch04-chapter-4.txt:405,搜「keep the computing effort low」)。原文第 1 步和第 2 步都写了理由:为了把计算量压低,把图缩到 32×32、并从三个颜色通道减到只有一个的灰度。

  43. 出处:「Chapter 4」第 573-596 段的模型代码(text/06-ch04-chapter-4.txt:586,搜「[BS, 6, 30, 30]」)。代码注释里逐行标了维度:[BS,1,32,32][BS,6,30,30][BS,6,15,15][BS,16,13,13][BS,16,6,6][BS,16*6*6]。BS 是批大小。

  44. 出处:「Chapter 4」第 604-620 段(text/06-ch04-chapter-4.txt:605,搜「binary cross entropy」)与第 608-614 段的信息框(text/06-ch04-chapter-4.txt:611,搜「numerically stable」)。原文:一般推荐用 nn.BCEWithLogitsLoss,它数值上更稳定,而且不需要在输出层加 sigmoid,因为 sigmoid 已经集成在损失函数里;若改用 nn.BCELoss,则必须在输出层加 nn.Sigmoid

  45. 出处:「Chapter 4」第 703-710 段(text/06-ch04-chapter-4.txt:709,搜「tends more towards overfitting」)。原文:训练损失会继续下降,但更要紧的是模型在验证数据上的表现;从大约 20 轮起验证几乎不再改善,可以认为模型开始把训练数据背下来。超参数见第 444-450 段(text/06-ch04-chapter-4.txt:448,搜「BATCH_SIZE = 256」)。

  46. 出处:「Chapter 4」第 672-675 段(text/06-ch04-chapter-4.txt:672,搜「store best model」)与第 711-714 段(text/06-ch04-chapter-4.txt:713,搜「load best model」)。原文:每一轮算完验证损失,如果比历史最好还低,就把权重存成 best_model.pth;测试前再把这一份加载回来。

  47. 出处:「Chapter 4」第 772-777 段(text/06-ch04-chapter-4.txt:777,搜「Model Accuracy: 79.90%」)。

  48. 出处:「Chapter 4」第 779-793 段(text/06-ch04-chapter-4.txt:791,搜「Dummy Classifier Accuracy: 50.17%」)、第 779-780 段(text/06-ch04-chapter-4.txt:780,搜「the accuracy should be 50%」)与第 786-787 段(text/06-ch04-chapter-4.txt:786,搜「dummy_clf.fit(y_test_true, y_test_pred_class)」)。原文的理由:两类图片数一样多时,瞎猜的正确率应当是 50%;原文对这个对照器的描述是「它看测试数据里各类别出现的频次,永远预测最常见的那一类」。而那两行代码写的是 dummy_clf.fit(y_test_true, y_test_pred_class)dummy_clf.score(y_test_true, y_test_pred_class) —— 第一个参数(输入)放的是真实标签,第二个参数(答案)放的是模型自己的预测。 这个理由和它自己打印的分类报告对不上,那两行代码量的也不是基线,两条都见本节末的判断块。 2

  49. 出处:「Chapter 4」第 795-806 段(text/06-ch04-chapter-4.txt:801,搜「0 0.83 0.79 0.81 640」)。表里的两行数值直接照录原文输出。

  50. 出处:「Chapter 4」第 812-818 段(text/06-ch04-chapter-4.txt:818,搜「harmonic mean」)。原文:精确率和召回率可以汇总成 F1 分数,它是两者的调和平均;F1 会被经常用到,因为它在两个指标之间给出一个好的平衡。

  51. 出处:「Chapter 4」第 819-821 段(text/06-ch04-chapter-4.txt:819,搜「The support refers to」)。原文:support 指的是数据基础 —— 类 0 的 support 是 640,意思是数据集里有 640 个属于这一类的样本。

  52. 出处:「Chapter 4」第 822-827 段(text/06-ch04-chapter-4.txt:822,搜「macro-average」)。原文:宏平均是三项指标在所有类别上的不加权平均,当每个类别对整体质量同等重要、不论样本多少时用它;因此宏平均能清楚反映模型在少数类上的表现,在类别不平衡时是重要的稳健性指标。另有加权平均,按每类样本数加权。

  53. 出处:「Chapter 4」第 743-750 段(text/06-ch04-chapter-4.txt:743,搜「still probabilities」)。原文:此时预测仍然是概率(0 到 1 之间的值);二分类的常规做法是把低于阈值的归到类 0、高于阈值的归到类 1;没有更多信息时,为简单起见把阈值设为 0.5。这段代码里的模型用 BCEWithLogitsLoss 训练、输出层无激活,所以它拿到的不是概率。

  54. 出处:「Chapter 4」第 735-741 段(text/06-ch04-chapter-4.txt:735,搜「.classes property」)。原文:用 .classes 属性可以查出原始类别,发现「Chihuahua」排在列表最前面因而索引为 0,而「Muffin」的索引是 1;打印结果是 ['chihuahua', 'muffin']

  55. 出处:「Chapter 4」第 748-751 段(text/06-ch04-chapter-4.txt:749,搜「’chihuahua’ if float(i[0]) > threshold」)。原文的两行代码把「大于阈值」映射成 chihuahua、把真实标签 1 也映射成 chihuahua,两处用的是同一套反过来的对应,所以在这一章互相抵消。第 15 章的线上服务只保留了预测那一侧(text/15-ch13-chapter-13.txt:232,搜「chihuahua」)。

  56. 出处:「Chapter 4」第 2285-2288 段(text/06-ch04-chapter-4.txt:2288,搜「models.vgg19(pretrained=True)」)。原文加载那个现成网络时传的就是 pretrained 这个参数。pretrained= 这个参数从 torchvision 0.13(2022)起已经改成 weights=,照抄能跑但会收到弃用警告 —— 这是我们补的,不在书里(来自通用知识)。第 12 章脚注 19 处是同一件事。

  57. 出处:「Chapter 4」第 858-859 段(text/06-ch04-chapter-4.txt:859,搜「binary_img_classification.py」)。原文给手势多分类标的脚本路径,和上一节二分类那个(text/06-ch04-chapter-4.txt:355,搜「binary_img_classification.py」)完全相同。