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

教材这一节说了三个瓶颈:要大量数据、要大量算力、结果说不清。 三个都能量。实测:宽度从 8 加到 128,参数涨 28 倍,准确率最后一档只多 0.0012。 每类 5 张时三档宽度的极差只有 0.0713,样本一多才拉到 0.2887。 容量和数据是一对,缺哪一半都白搭。 黑箱这件事也量了:把第一层 32 个单元的权重摊回 8 乘 8,没有一个长成干净的方向检测器。 因为没人逼它——卷积核长成笔画,是局部连接和权重共享逼出来的。

📝 本节和前面几节的关系

上一节讲「已经做成了什么」,这一节讲「还差什么」。 教材作者写这一节的时候是 2015 年,他列的三条,十年后一条都没消失。 这一节的做法跟前七章不一样:不去验证他的结论,而是把三条瓶颈各量一遍,看能测出什么、 测不出什么。

一、第一个瓶颈:数据

教材的说法是「深度学习需要大量的训练数据」。 这句话没错,但它太笼统,笼统到没法指导任何决定。 多少算大量?跟什么比才算大量? 有个更好用的口径:每参数样本数。 训练样本总数除以参数总数,得到「每个参数分到几个样本」。

隐层宽 参数量 每参数样本数
8 606 0.3300
32 2778 0.0720
128 17226 0.0116
最宽那一档,每个参数分到 0.0116 个样本——连一个都不到。
这就是「数据不够」的准确含义:不是样本绝对少,是参数太多。
这个口径能解释很多看起来矛盾的现象。
比如为什么同一个网络在小数据集上表现平平,换个大点的数据集就好了——网络没变,那个比值变好了。
也解释了为什么迁移学习管用:拿一个在大数据集上训好的网络,只调最后几层。
参数还是那么多,但要学的参数少了,比值就上去了。

二、第二个瓶颈:算力

教材说「计算量大」,这也是对的,但同样笼统。 真正该问的是:多花一倍算力,能换回多少准确率? 实测的做法是把隐层宽度从 8 一路加到 128,每一档都翻倍多一点,看参数、耗时、准确率怎么走:

相邻两档 参数 耗时 准确率
8 → 16 2.1 倍 1.3 倍 +0.2137
16 → 32 2.2 倍 1.5 倍 +0.0200
32 → 64 2.4 倍 2.0 倍 +0.0138
64 → 128 2.6 倍 1.7 倍 +0.0012
头一档涨两成一,最后一档涨一个千分点,差了一百七十倍。
代价那边却很稳:每一档参数都翻两倍多,耗时涨一半到一倍。
这就是教材那句「计算量大」的具体形状——不是贵,是越加越不值。
而这个曲线的形状是有道理的:模型一开始欠拟合,加容量当然涨得快;
到了某个点,横轴上的限制换成了数据,再加参数就是纯浪费。

三、第三个瓶颈:黑箱

这一条最有意思,因为它是唯一一条可以动手看的。 做法很朴素:W1 是 64 乘 32,每一列是一个隐单元的入权,正好 64 个数,可以摊回 8 乘 8 的形状。 于是「这个单元在看什么」,就变成了一张图。 摊出来之后,第一反应是:不好懂。 有的地方整行同号,有的地方一格一个样。说它像某段笔画也行,说它是随机噪声也行。 为了说清楚这不是挑样本的问题,给 32 个单元都算了一个指标:竖方向和横方向的能量比。 如果第一层真长出了笔画检测器,这些数应该密密麻麻挤在两头。 实测:最小 0.36,中位 0.49,最大 0.61。三十二个里三十一个「两种都有」,一个偏横,零个偏竖。 一个干净的都没有。 为什么会这样?因为没人逼它。 卷积核之所以长得像笔画,是被两条约束逼出来的:只在局部看,同一组权重全图滑。 这两条约束本身就是「图像长什么样」的先验。先验对了,学出来的东西人也认得。 全连接层没有这两条约束。它只要把十类分开就算交差,用 64 个数怎么组合是它的自由。 它挑了一个人看不懂、但完全合法的解。 所以「黑箱」不只是「算不出来」,更是「没有共同语言」。

四、那教材给的三个方向呢

教材在展望部分提了几个方向:无监督学习、强化学习、以及想办法少要点标注数据。 这几个方向对不对,这份笔记没法验证——手上只有一台八核笔记本和 numpy, 没有数据也没有算力去跑那类实验。 能验证的只有一件事:他指出的那三个瓶颈,在 8 乘 8 的小数字上就已经露头了。 真实的系统只是把这三件事放大到了花钱的量级。

瓶颈 在小数字上测出来了吗
黑箱 测出来了,画出来也没长成笔画检测器
算力昂贵 测出来了,宽度翻倍只换千分之一的准确率
数据饥饿 测出来了,样本少时多出的容量兑现不了
未来会怎样 测不出来,这不是实验能回答的问题
最后一行很重要。
一份诚实的笔记,得说清楚哪些结论是量出来的、哪些是抄来的、哪些是猜的。

五、动手 Demo:三个瓶颈,一个个量过去

这个 Demo 分三段,对应三个瓶颈。 第一段把隐层权重摊回 8 乘 8 打印成字符画,第二段扫宽度,第三段扫样本量。 字符画那一段是本 Demo 最像「实验」的部分——它不给你结论,它给你一张图,让你自己看。

# 白箱化第一步:把第一层的每一列摊回 8x8
def art(w):
    img = w.reshape(8, 8)
    m = np.abs(img).max()
    return [[_ch(v / m) for v in row] for row in img]

# _ch:大于 0.55 打 #,大于 0.18 打 +,接近零打 . ,负的打 - 和 o

# 32 个单元,各算一次「竖能量」和「横能量」
he = ((img[:, 1:] - img[:, :-1]) ** 2).sum()   # 沿列方向的变化
ve = ((img[1:, :] - img[:-1, :]) ** 2).sum()   # 沿行方向的变化

# 结果:竖/横能量比最小 0.36,中位 0.49,最大 0.61
# 全挤在中间——32 个单元里没有一个长成干净的方向检测器

实跑输出:

--------------------------------------------------------------------
三个瓶颈:黑箱、算力、数据,一个个量过去
--------------------------------------------------------------------
[1] 第一个瓶颈:黑箱。把第一层的权重拆开来看
  训一个 64→32→16→10 的网络,每类 20 张,两千轮。
  W1 是 64 乘 32——每一列是一个隐单元的入权,正好也是 64 个数,
  可以摊回 8 乘 8 的形状。于是「这个单元在看什么」就变成了一张图。

  训完:2778 个参数,训练 1.0000,测试 0.9025。

  32 个单元里挑几个出来看。# 是正权重(这儿亮就投赞成票),
  o 是负权重(这儿亮就投反对票),点是接近零。

    单元 22  横条纹  竖/横能量比 0.36
            --..---.
            -.#---o-
            ++oo..o.
            +-.#..o-
            +#+---..
            -.+#+##.
            -+..+.+.
            ..-.---o

    单元 7   混合  竖/横能量比 0.61
            ++.-.o..
            ...+ooo.
            .-..ooo-
            ...+ooo.
            .-++---.
            ...+#++.
            ....+++-
            ..-..+++

    单元 0   混合  竖/横能量比 0.39
            ....-..-
            -----...
            -.--....
            ----.-o.
            .---o--+
            +++o..--
            .-+.oo--
            --.....+

  这几张图说实话不好懂。有的地方整行同号,有的地方一格一个样,
  说它像某段笔画也行,说它是随机噪声也行。这不是我挑得不典型——
  把 32 个单元全按这个口径过一遍:

    竖/横能量比:最小 0.36  中位 0.49  最大 0.61

    竖条纹为主       0 个  占 0%
    横条纹为主       1 个  占 3%
    两种都有        31 个  占 97%

  要是第一层真长出了笔画检测器,这些数应该密密麻麻挤在两头——
  要么专数竖,要么专数横。实测全挤在中间,一个干净的都没有。
  这套网络压根没长出第 3 章那张手工核表里的东西。

  为什么会这样?因为没人逼它。
  卷积核之所以长得像笔画,是被两条约束逼出来的:只在局部看,同一组权重全图滑。
  这两条约束本身就是「图像长什么样」的先验,先验对了,学出来的东西人也认得。
  全连接层没有这两条约束,它只要把十类分开就算交差,
  用 64 个数怎么组合是它的自由——它挑了一个人看不懂但完全合法的解。
  所以「黑箱」不只是「算不出来」,更是「没有共同语言」。

  更麻烦的是,这已经是最好画的一层。第 2 层、第 3 层的入权摊不回一张图——
  它们的输入是上一层那 32 个抽象数,不是像素,画出来也没有人认得。
  想看「第 2 层在干什么」,只能反过来做实验:改输入、看输出变多少。
  那就不是解释,是试探了。

[2] 第二个瓶颈:算力换准确率,越来越不划算
  固定每类 20 张,只把隐层宽度从 8 加到 128,看参数量、耗时、准确率怎么走。

    隐层宽    参数量    训练耗时    训练准确率  测试准确率  每参数样本数
    8         606       0.442 秒    0.9650      0.6687      0.3300
    16        1266      0.580 秒    1.0000      0.8825      0.1580
    32        2778      0.841 秒    1.0000      0.9025      0.0720
    64        6570      1.650 秒    1.0000      0.9163      0.0304
    128       17226     2.866 秒    1.0000      0.9175      0.0116

  光看「比最低那档好多少」会显得很划算,得看每一档之间差多少:

    8→16:参数 2.1 倍,耗时 1.3 倍,准确率 +0.2137
    16→32:参数 2.2 倍,耗时 1.5 倍,准确率 +0.0200
    32→64:参数 2.4 倍,耗时 2.0 倍,准确率 +0.0138
    64→128:参数 2.6 倍,耗时 1.7 倍,准确率 +0.0012

  头一档宽度翻倍,准确率涨 0.2137;最后一档宽度也翻倍,只涨 0.0012,
  差 171 倍。参数照涨、耗时照涨,换来的是小数点后第三位的动静。
  这就是课本 8.2 节那句「深度学习很贵」的原始形态:
  加容量的边际收益一路递减,代价却一直在线性往上爬。

[3] 第三个瓶颈:容量上去了,数据得跟上
  刚才那张表最后一列是「每参数样本数」。200 个训练样本摊到一万七千个参数上,
  每个参数分不到 0.02 个样本——这就是过拟合的温床。把样本量提上去再看:

    每类张数    宽度 8      宽度 32     宽度 128    三者极差
    5           0.5325      0.6038      0.5988      0.0713
    10          0.7288      0.7875      0.7725      0.0587
    20          0.6687      0.9025      0.9175      0.2488
    40          0.7013      0.9263      0.9625      0.2612
    80          0.6700      0.9325      0.9587      0.2887

  看「三者极差」这一列:每类 5 张时只有 0.0713,三个宽度挤成一团;
  每类 80 张时拉到 0.2887。样本少的时候,多出来的参数根本兑现不了。

  反过来更值得注意的是宽度 8 那一列:从每类 5 张到 80 张,
  它一直在 0.5325 到 0.7288 之间打转,喂多少数据都顶不上去。
  一个卡在「有数据没容量」,一个卡在「有容量没数据」。
  容量和数据是一对,缺哪一半都白搭——这比「深度学习要吃大数据」那句口号具体。

[4] 这三件事,哪件测出来了、哪件测不出来

    黑箱        测出来了,画出来也没长成笔画检测器
    算力昂贵    测出来了,宽度翻倍能换千分之一的准确率
    数据饥饿    测出来了,样本少时多出的容量兑现不了
    未来会怎样  测不出来,这不是实验能回答的问题

  课本 8.2 节讲的是作者的判断:无监督学习、强化学习、更省数据的办法。
  判断对不对,这份笔记没法验证——手上只有一台八核笔记本和 numpy。
  能验证的只有一件事:他指出的那三个瓶颈,在 8 乘 8 的小数字上就已经露头了。
  真正的系统只是把这三件事放大到花钱的量级。

字符画用的符号:# 是正权重最大的一档,+ 次之,. 接近零,- 和 o 是负权重。 正权重意思是「这儿的像素亮了,就投赞成票」。 打印出来的六张图,说实话都不好认。 这不是挑得不典型——32 个单元的竖横能量比,最小 0.36、中位 0.49、最大 0.61,全挤在中间。 容量扫描那一段也要注意读法。 如果只跟最低那一档比,宽度 128 比宽度 8 好 0.2488,看着很划算。 必须看相邻两档之间,才能看出收益递减:最后一档只多 0.0012。 样本量那一段有个容易漏掉的细节:宽度 8 那一列, 从每类 5 张到 80 张几乎没动(0.5325 到 0.7288)。 容量不够的时候,喂再多数据也上不去。 一个卡在「有数据没容量」,一个卡在「有容量没数据」。 训练耗时那一列是这台机器的实数,换台机器会变。 但「参数涨两倍多、耗时涨一半」这个比例关系,是稳定的。

30 秒实验

  1. 把隐层再加宽到 256,看参数、耗时、准确率怎么走。 按那个趋势,准确率大概还会多一个千分点,而耗时又要多一半。 这是最容易亲手验证「收益递减」的实验。
  2. 把字符画改成只看前 8 个单元,跟 32 个全看对比。 你会发现「挑几个出来看」这件事本身就有偏——挑出来的恰好是最像样的那几个,会被误当成全体。
  3. 在样本量表里加一列「每参数样本数」。 每一行都算一遍,你会看到那张表其实是在两个变量之间做权衡。

常见误区

  1. 「加参数总归没坏处,反正过拟合可以靠正则化压。」 实测里最后一档加了 10656 个参数,准确率只多 0.0012。 参数不是免费的:显存、训练时间、部署体积都跟着涨。 拿不准的时候,小网络先跑通比大网络调参快得多。
  2. 「黑箱的意思是网络拒绝解释。」 不是拒绝,是它内部没有「像素」这个坐标系。 第一层还能摊回图,第二层的输入已经是 32 个抽象数,摊出来没人认得。 想解释第二层,只能改输入看输出变多少——那不是解释,是试探。
  3. 「无监督学习能解决数据问题。」 这话听着对,但目前能做到的是「少要标注」而不是「不要数据」。 第 4、5 章那些预训练方法,在标签充足的今天已经很少单独用了。
自测三问(点开对答案) 1. **为什么全连接的第一层没长出笔画检测器,卷积网络的第一层却长出来了?** —— **因为卷积被两条约束逼着:只在局部看,同一组权重全图滑。** **这两条约束本身就是图像规律的先验,逼出来的解人也认得。** **全连接没有这两条约束,它只要分对类就行,长成什么样都合法。** 2. **「每参数样本数」这个口径能解释什么?** —— **它能解释为什么同样一个网络,小数据集上表现平平、大数据集上就好了——网络没变,** **是那个比值变好了。** **也能解释迁移学习为什么管用:参数还在,但要学的那部分少了。** 3. **容量扫描为什么必须看相邻两档,不能看「比最低档好多少」?** —— **因为「比最低档好多少」是累积收益,会把前面的红利算到最后一次上。** **看相邻两档才能看出边际收益:头一档涨两成一,最后一档涨一个千分点。**

下一节是全书最后一节。 它要把八章的东西拼成一条流水线真跑一遍,再给每一章留一句话。

相关笔记