📗 第8章 深度学习的现在和未来
📄 一句话概括

八章讲的是八件事,跑起来是一条线上的八道工序。 实测把这条线接起来: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 秒实验

  1. 把流水线里的 Dropout 关掉(保留率设成 1.0),从头的准确率对比一次。 第 6 章 6.4 测过这个,这里的区别是它被放进了完整流水线里。
  2. 把卷积那一段换成全连接,其他工序不动,再跑一次剖面。 卷积那 79% 的时间会消失,但参数会涨上去——这就是那个权衡的形状。
  3. 把每一段的计时改成累计到 5000 轮,看比例会不会变。 如果比例变了,说明某一段有固定开销被摊薄了,那是另一个信息。

常见误区

  1. 「整本书就读了个手写数字识别。」 8 乘 8 的数字图是这套东西能跑起来的最小载体,不是它的适用范围。 换数据、换尺寸,八道工序一道都不会少,只是每一道都变大。
  2. 「参数越多模型越强。」 这一节那张剖面表说的是相反的事:参数占 97% 的那两层,干活只占 9%。 算力花在哪儿,跟参数堆在哪儿,常常不是一回事。
  3. 「跑通了就等于学会了。」 跑通只说明代码没错。第 6 章那五个手段逐项累加的结果是:叠到第四个反而掉了八个半点。 知道什么时候不该加,比知道怎么加更难。
自测三问(点开对答案) 1. **为什么卷积层参数只占 3%,时间却占了近八成?** —— **因为它要把图铺成很多小块逐个乘核,池化还要拆开取最大,全是搬数据。** **全连接只是两个小矩阵相乘,一次算完。** **参数数量和计算量是两件事。** 2. **那个「差十五倍」的增强实验,教训是什么?** —— **量出来的时间要先分清是算法慢还是解释器慢。** **同一批数字、同一件事,逐张循环和批量 roll 差十五倍,差别只在 Python 转了几圈。** **测错了这一层,后面所有结论都是理直气壮地错。** 3. **这本书读完,最该记住的三样东西是什么?** —— **是八道工序的顺序、每一段的输入输出形状、以及它们之间怎么接。** **不是具体数字、不是框架 API、也不是「谁赢谁输」的结论。** **后面那三样都带着前提,换环境就变。**

全书到此结束。 八章、五十一篇、五十一个 Demo,从三条螺旋一直走到这条流水线。 接下来该做的不是再读一遍,是找一份自己的数据,把这条线接一遍。

相关笔记