八章讲的是八件事,跑起来是一条线上的八道工序。 实测把这条线接起来:800 轮、15.12 秒、测试准确率 0.9575。 耗时剖面里最反直觉的一条:参数只占 3% 的卷积加池化,吃掉了 79.4% 的时间; 参数占 97% 的全连接两层,只占 9.1%。 前向三段 6.772 秒、反向两段 6.606 秒,几乎一样贵。 所以训一轮的成本大约是一次前向的两倍。 省参数不等于省计算。参数和计算量是两件事——这也是真实网络离不开 GPU 的原因。
这是全书最后一篇,也是整个 41 课 51 篇的最后一篇。 前七章每一章都留了一个 Demo,最后一章留三个:一个讲表示,一个讲瓶颈, 一个把整条流水线接起来。
一、八章,八个位置
| 章 | 题目 | 在流水线上的位置 |
|---|---|---|
| 1 | 绪论 | 造数据:图像从哪来、标签怎么给 |
| 2 | 神经网络 | 前向、反向、更新——整条线的主循环 |
| 3 | 卷积网络 | 把像素变成特征图,再压成向量 |
| 4 | 受限玻尔兹曼机 | 不看标签先摸清数据长什么样 |
| 5 | 自编码器 | 同上,换一种更省事的实现 |
| 6 | 提高泛化 | 数据、预处理、激活、Dropout |
| 7 | 深度学习工具 | 上面这些怎么写成能跑的代码 |
| 8 | 现在和未来 | 回头看看这套东西的边界在哪 |
| 这张表建议留着。 | ||
| 以后遇到任何一个深度学习的系统,先问它在第几行——这个问题比「它用了什么模型」有用得多。 | ||
| 注意第 4、5 章的位置:它们标的是「不看标签」。 | ||
| 这两章在今天的工程里已经很少单独用了,但它们占的那个位置还在——今天叫自监督预训练, | ||
| 干的是同一件事。 |
二、一条流水线的耗时剖面
把上面八道工序接起来真跑 800 轮,每一段单独计时:
| 工序 | 占比 | 每轮 |
|---|---|---|
| 数据增强 | 8.8% | 1.66 毫秒 |
| 标准化 | 2.5% | 0.48 毫秒 |
| 卷积前向 | 21.9% | 4.14 毫秒 |
| 池化 | 16.4% | 3.10 毫秒 |
| 全连接前向+损失 | 6.5% | 1.22 毫秒 |
| 全连接反向 | 2.7% | 0.50 毫秒 |
| 卷积反向 | 41.0% | 7.75 毫秒 |
| 参数更新 | 0.2% | 0.03 毫秒 |
| 这张表里藏着三个结论。 | ||
| 第一,最贵的活不在参数最多的地方。 | ||
| 卷积 8 个核 80 个参数、池化 0 个参数,两段合计 79.4% 的时间; | ||
| 全连接 2666 个参数,两段只有 9.1%。 | ||
| 原因很朴素:卷积要把 8 乘 8 的图摊成 36 个 3 乘 3 小块逐个乘核,池化要拆成 2 乘 2 再取最大, | ||
| 全是搬数据;全连接只是两个小矩阵相乘,一次就完了。 | ||
| 第 3 章讲过卷积省参数(2410 对 810)。这里补上另一半:省参数不等于省计算。 | ||
| 参数和计算量是两件事——真实网络之所以离不开 GPU,就是因为算得多的那一层, | ||
| 恰好是参数少的那一层。 | ||
| 第二,前向和反向差不多贵(6.772 对 6.606 秒)。 | ||
| 听起来反向活更多——要算转置乘、要给池化重建梯度、要乘 ReLU 的开关。 | ||
| 可前向那边光是 im2col 搬数据就抵掉了。 | ||
| 第三,数据增强加标准化占 11%。 | ||
| 这是批量写法的价钱:同一件事逐张循环写,一项就能翻十五倍,直接吃掉整个训练过程的一半。 |
三、那「十五倍」是怎么来的
它值得单独说一段,因为这类错误每个人都会犯,而且犯的时候毫无察觉。 增强要做的事很简单:每张图随机抖一格。最直白的写法是 for 循环,一张一张来。 200 张图、抖三份,就是 600 次单张的 np.roll。 换个写法:把 600 个位移按九种情况分组,每组把整批一次性 roll 过去。 一共九次 numpy 调用,算的是同一批数字,一回事都没少干。
| 写法 | 一次增强 |
|---|---|
| 逐张循环 | 22.76 毫秒 |
| 按位移分组 | 1.54 毫秒 |
| 差 14.8 倍。差别全在「Python 转了几圈」。 | |
| 这条经验在整套笔记里反复出现:量出来的时间,先问一句「是算法慢还是解释器慢」。 | |
| 测错了这一层,后面所有结论都会是错的,而且是理直气壮地错。 |
四、三个「别记住」的东西
这本书读完,有三样东西最容易被记住,而它们恰好是最不该记的。 第一是具体数字。 「0.9575」「15.12 秒」「2410 个参数」——这些都是这台机器、这份数据、这个随机种子上的数。 换一个就变。 值得记的是关系和量级,不是末位。 第二是框架的 API。 第 7 章那七个工具,五个已经停止维护。你今天背下来的接口,两年后大概就不在了。 第三是「谁赢谁输」的结论。 「白化没用」「Dropout 训练准确率不掉」「编译只快一成」——这些结论都对,但都带着前提。 笔记里每一处这样的地方,都写了前提是什么。 记住结论忘了前提,下次就会用错。
五、接着往下走
八章读完了,能做的事其实还很少:只能处理 8 乘 8 的图,只能用 numpy,只能在笔记本上跑。 但这个「少」是有形状的——你知道缺的是哪一块。
| 想做的事 | 缺的那一块 | 去哪补 |
|---|---|---|
| 处理真实图片 | 大尺寸卷积的工程写法 | 换框架,别再手写 im2col |
| 跑得动大模型 | GPU 和显存管理 | 从一个小网络开始,先跑通 |
| 少要标注 | 自监督、迁移学习 | 第 4、5 章的位置还在,换了名字 |
| 能部署 | 量化、剪枝、算子融合 | 第 7 章 7.7 那个融合实验的放大版 |
| 四个方向对应四件不同的事,别混在一起焦虑。 | ||
| 这份笔记能保证的只有一件事:你不会因为「不知道网络内部在干什么」而写出错的代码。 |
六、动手 Demo:把八章拼成一条流水线,量一遍各段耗时
这个 Demo 是全书最后一个,也是最像「总装」的一个。 它不教新东西,只把前七章的零件接起来,标上号。 中间还插了一段对照:同一个数据增强,两种写法差十五倍。 那一段是故意的——它本身就是一条该记住的教训。
t0 = time.perf_counter()
for _ in range(800):
Xa, ya = augment(Xtr, ytr, 4) # 第 6 章:数据增强
Xn = (Xa - mu) / sd # 第 6 章:标准化
P, s = im2col(Xn) # 第 3 章:把图铺成小块
Ac = np.maximum(P @ K + bk, 0.0) # 第 3 章:卷积加 ReLU
Ap = Ac.reshape(n, 3, 2, 3, 2, 8).max((2, 4)) # 第 3 章:2x2 池化
H = np.tanh(Ap.reshape(n, -1) @ W1 + b1) # 第 2 章:全连接加激活
Hd = H * (rg.random(H.shape) < 0.8) / 0.8 # 第 6 章:Dropout
Z = Hd @ W2 + b2 # 第 2 章:全连接
P = softmax(Z) # 第 3 章:softmax
loss = -np.log(P[range(n), ya] + 1e-12).mean() # 第 2 章:交叉熵
# ... 反向传播与参数更新,第 2 章 ...
# 八道工序合计 15.12 秒,800 轮后测试准确率 0.9575
实跑输出:
--------------------------------------------------------------------
结课:把八章拼成一条流水线,量一遍各段耗时
--------------------------------------------------------------------
[1] 八章,其实是一条流水线
这本书讲了八个题目,读的时候像八件事。真跑起来它们是一条线上的八道工序:
章 题目 在流水线上的位置
第 1 章 绪论 造数据:图像从哪来、标签怎么给
第 2 章 神经网络 前向、反向、更新——整条线的主循环
第 3 章 卷积网络 把像素变成特征图,再压成向量
第 4 章 受限玻尔兹曼机 不看标签先摸清数据长什么样
第 5 章 自编码器 同上,换一种更省事的实现
第 6 章 提高泛化 数据、预处理、激活、Dropout
第 7 章 深度学习工具 上面这些怎么写成能跑的代码
第 8 章 现在和未来 回头看看这套东西的边界在哪
下面把这条线接起来真跑一遍,看看每一段占多少时间。
[2] 接起来:一条 8 乘 8 的图走到「是几」
第 1 章 造数据 训练 200 张、测试 800 张,每张 8 乘 8
第 6 章 数据增强 每张再抖三份,变成 800 张
第 6 章 标准化 按训练集的均值方差逐像素拉平
工序 输出形状 参数量
输入 200×8×8 —
3×3 卷积 ×8 加 ReLU 200×6×6×8 80
2×2 最大池化 200×3×3×8 —
摊平 200×72 —
全连接 72→32 加 tanh 200×32 2336
Dropout 保留 0.8 200×32 —
全连接 32→10 200×10 330
softmax 加交叉熵 200×10 —
总共 2746 个参数。第 3 章那台全连接要 2778 个,这里少一些,
因为卷积那一层只用 80 个参数就生成了 288 个特征。
[3] 插一段:同一个增强,两种写法差多少
增强要做的事很简单——每张图随机上下左右抖一格。
最直白的写法是 for 循环,一张一张来。先量一量它到底多贵:
逐张循环 22.26 毫秒一次(200 张变 800 张)
按位移分组 1.51 毫秒一次
差 14.7 倍
600 张图、九种位移,逐张写就是 600 次 roll 加 600 次判断;
按位移分九组,每组把整批一次性 roll 过去,只要九次。
两次算的是同一批数字,一回事都没少干——差别全在「Python 转了几圈」。
这条经验在整套笔记里反复出现:测出来的时间,先问一句「是算法慢还是解释器慢」。
[4] 800 轮的耗时剖面
800 轮里每一段各花多少时间,逐段计时累加:
工序 累计耗时 占比 每轮
数据增强 1.336 秒 8.9% 1.67 毫秒
标准化 0.380 秒 2.5% 0.48 毫秒
卷积前向 3.306 秒 21.9% 4.13 毫秒
池化 2.485 秒 16.5% 3.11 毫秒
全连接前向+损失 0.982 秒 6.5% 1.23 毫秒
全连接反向 0.407 秒 2.7% 0.51 毫秒
卷积反向 6.162 秒 40.8% 7.70 毫秒
参数更新 0.025 秒 0.2% 0.03 毫秒
合计 15.087 秒 100.0%
[5] 这个剖面说了三件事
第一,最贵的活不在参数最多的地方。
卷积加池化:80 个参数,占了 79.2% 的时间。
全连接两层:2666 个参数,占 9.2%。
参数占九成七的那两层,干活只占不到一成。
原因不神秘:卷积要把 8 乘 8 的图摊成 36 个 3 乘 3 小块逐个乘核,
池化要拆成 2 乘 2 再取最大,全是搬数据;
全连接只是两个小矩阵相乘,一次就完了。
第 3 章讲过卷积省参数(2410 对 810),这里补上另一半:
省参数不等于省计算。参数和计算量是两件事——
真实网络之所以离不开 GPU,就是因为算得多的那一层恰好是参数少的那层。
第二,前向和反向差不多贵:前向三段 6.774 秒,反向两段 6.568 秒。
所以训一轮的成本大约是一次前向的两倍,跟第 2 章那个印象对得上。
有意思的是这个「差不多」——反向要算转置乘、要给池化重建梯度、要乘 ReLU 的开关,
听起来活更多,可前向那边光是 im2col 搬数据就抵掉了。
第三,数据增强加标准化 1.716 秒,占 11%。
这是批量写法的价钱。同一件事照前面那个逐张循环写,一项就能翻 14.7 倍,
直接吃掉整个训练过程的一半。
第 6 章把它们讲成「技巧」,技巧背后是实打实的工程开销。
最后一行数:800 轮、损失 0.2205、测试准确率 0.9575。
从造数据到算准确率,这条线就是这本书的全部内容。
[6] 八章各自留下的一句话
第 1 章 数据和标签决定了天花板,模型只决定离天花板多近
第 2 章 反向传播就是链式法则,难的不是数学是工程
第 3 章 局部连接加权重共享,用 80 个参数换 288 个特征
第 4 章 能量模型能解释「网络在记什么」
第 5 章 自编码器的中间那层,就是数据的浓缩版
第 6 章 泛化靠手段堆,但手段不是越多越好
第 7 章 框架换了一茬又一茬,范式一直在那儿
第 8 章 三个瓶颈——黑箱、算力、数据——一个都没解决
这本书 2015 年出日文版,讲的还是 Theano 和 Caffe。
十年过去,框架全换了,上面这八句话一句没过时。
这就是读它的理由:学的是这条流水线,不是某个库的 API。
流水线的形状要留意:输入是 200×8×8,卷积后 200×6×6×8,池化后 200×3×3×8,摊平成 200×72, 最后 200×10。 每一步都在变小,直到最后一步变成十个类别。 这是所有分类网络的共同形状。 参数量 2746,比第 3 章那台全连接的 2778 少一点。 少得不多,但要看结构:卷积那一层只用了 80 个参数,就生成了 288 个特征。 参数效率的差别在这里,不在总数上。 计时那一列是本机数字,换机器会变;但比例关系稳定:卷积反向最贵,参数更新最便宜。 字符画和数字之外,这个 Demo 还有一句话值得读:「从造数据到算准确率, 这条线就是这本书的全部内容」。 八章读下来,能被这条线串住的,就是真读进去了。
30 秒实验
- 把流水线里的 Dropout 关掉(保留率设成 1.0),从头的准确率对比一次。 第 6 章 6.4 测过这个,这里的区别是它被放进了完整流水线里。
- 把卷积那一段换成全连接,其他工序不动,再跑一次剖面。 卷积那 79% 的时间会消失,但参数会涨上去——这就是那个权衡的形状。
- 把每一段的计时改成累计到 5000 轮,看比例会不会变。 如果比例变了,说明某一段有固定开销被摊薄了,那是另一个信息。
常见误区
- 「整本书就读了个手写数字识别。」 8 乘 8 的数字图是这套东西能跑起来的最小载体,不是它的适用范围。 换数据、换尺寸,八道工序一道都不会少,只是每一道都变大。
- 「参数越多模型越强。」 这一节那张剖面表说的是相反的事:参数占 97% 的那两层,干活只占 9%。 算力花在哪儿,跟参数堆在哪儿,常常不是一回事。
- 「跑通了就等于学会了。」 跑通只说明代码没错。第 6 章那五个手段逐项累加的结果是:叠到第四个反而掉了八个半点。 知道什么时候不该加,比知道怎么加更难。
自测三问(点开对答案)
1. **为什么卷积层参数只占 3%,时间却占了近八成?** —— **因为它要把图铺成很多小块逐个乘核,池化还要拆开取最大,全是搬数据。** **全连接只是两个小矩阵相乘,一次算完。** **参数数量和计算量是两件事。** 2. **那个「差十五倍」的增强实验,教训是什么?** —— **量出来的时间要先分清是算法慢还是解释器慢。** **同一批数字、同一件事,逐张循环和批量 roll 差十五倍,差别只在 Python 转了几圈。** **测错了这一层,后面所有结论都是理直气壮地错。** 3. **这本书读完,最该记住的三样东西是什么?** —— **是八道工序的顺序、每一段的输入输出形状、以及它们之间怎么接。** **不是具体数字、不是框架 API、也不是「谁赢谁输」的结论。** **后面那三样都带着前提,换环境就变。**全书到此结束。 八章、五十一篇、五十一个 Demo,从三条螺旋一直走到这条流水线。 接下来该做的不是再读一遍,是找一份自己的数据,把这条线接一遍。
- ☐ (全书完)