📘 第9章 深度学习
📄 一句话概括

这是一个「只会打分」的模型。它给每一种组合打一个能量分,能量低的组合更可能出现。 它不预测标签,它学的是「数据长什么样」。 它也是深度学习的第一块垫脚石。2006 年人们能训动深网络,靠的就是先拿它把每一层单独垫好。 所以这一节要讲两件事:它怎么工作,以及它为什么后来被淘汰了。

📝 本节和前面几节的关系

这一节跟前面所有节都不一样:它没有标签,也没有「预测」这个动作。 7.1 的聚类是在找「哪些样本像」,它是在找「这些像素之间有什么规律」。 它是 9.3 的前置。9.3 那个「逐层预训练」,垫的就是这个东西。

一、能量、概率、和一个数不完的 Z

RBM 的全部内容就是一个能量函数:v 是可见层(比如 25 个像素),h 是隐层,W 是它们之间的权重。 E(v, h) = −a·v − b·h − v 乘 W 乘 h。 能量越低,这个状态越可能出现:概率正比于 e 的 −E 次方。 于是要算「看到 v 的概率」,就得把 h 的所有取法都加起来——再除以一个归一化常数 Z, 而 Z 要把 v 和 h 的所有取法都加起来。 这个 Z 就是全部麻烦的来源。先在一个小到能数完的模型上把它算出来看看:

项 数值 怎么读
单元数 4 个可见加 2 个隐 一共 64 个联合状态
配分函数 Z 122.186572 把所有状态的 e 的 −E 次方加起来
最可能的 v 0110,p = 0.3092 精确枚举出来的
采样得到的 0.3041 吉布斯走 3 万步
两种办法的最大绝对差只有 0.0051——在这个小模型上,采样跟硬算对得上。
可代价差得远:
规模 要数多少个状态 一秒数一千万个要多久
4 加 2 个单元 64 一瞬间
25 加 16 个单元 2.20e12 大概 3 天
真实模型的量级 数不完 数到宇宙结束
所以 RBM 的训练从来不去算 Z。它绕开 Z,直接凑梯度。
这是整节课最关键的一个工程决定——不是「想办法算得更快」,是「换一条路」。

二、对比散度:只走一步就够

绕开 Z 之后,梯度变成两项之差:「真实数据下的期望」减去「模型自己认为的期望」。 前者一次前向就有;后者要采样,而采样要走很多步才「混合」。 Hinton 的做法是只走 k 步,叫 CD-k。它的梯度是有偏的,但方向对。真跑一遍:

训练方式 训练集重构误差 测试集重构误差 训练耗时
CD-1 0.0593 0.0547 0.8 秒
CD-3 0.0527 0.0480 1.5 秒
CD-5 0.0553 0.0513 2.2 秒
最好的居然不是走得最多的那个(CD-3 的 0.0480 比 CD-5 的 0.0513 还好)。
这说明在 800 轮这个预算下,走多步省下来的误差已经被采样噪声抵消掉了。
所以真实的理由不是「CD-5 没用」,是「它的收益配不上 2 倍的时间」。
实践中 k 取 1,靠的是「反正要训几千轮,每轮差一点没关系」。
代价要说清:CD-k 学到的不是一个干净的生成模型——它更像一个「会填空的压缩器」。
它的概率估计是偏的,但它抓住的那些规律是真的。

三、它学到了什么:别看权重,让它自己画

第一反应一定是去看权重长什么样。把 16 个隐单元的权重搬回 5 乘 5 的画面上, 结果很让人失望:不像教科书里那种干净的方向滤波器,只是几块弱的正负叠加。 原因不是训练失败,是隐层有一个「旋转自由度」。把 h 的坐标整体转一下, 模型的概率一点不变——所以「第几个隐单元代表哪条线」本来就没有唯一答案。 那怎么判断它到底学到东西没有?别再看权重,让它自己画。从随机噪声出发,自由采样 400 步:

来源 「是一整条横线或竖线」的比例 平均亮几个像素
RBM 自由采样 500 张 55.2% 5.4
同样密度的随机图 0.0% 5.0
随机图里一张都不是线(0.0%),它画出来的有 55% 是完整的一条线。
这就是「学到了结构」的硬证据。它没有记住那 400 张——
它学会了「线长什么样」,然后自己造出了训练集里没有的线。
这也是「生成模型」跟「判别模型」的分水岭:后者只学「这条边界在哪」,
前者学的是「数据是怎么造出来的」。

四、会画就会补

既然它能从随机噪声造出图,那给它半张图,它应该能补全。把底下的 1 行、2 行、3 行涂成 0, 让它补回来:

涂掉几行 还剩几个像素 涂掉那几行的补对率 整张图的补对率
1 20 87.4% 92.0%
2 15 84.0% 87.1%
3 10 79.8% 80.8%
遮得越多补得越差,而且掉得不快——涂掉 3 行只剩 10 个像素,还能补对 80%。
原因很朴素:这 10 种图的规律太强了。看到某一行亮,剩下的几行就有八九成把握。
换成自然图像就没这么灵——那才是 RBM 真正不够用的地方。
这里要小心一个误读:补对率高不代表模型强,可能只是任务简单。
读任何「补全」「生成」的结果,都得先问一句:这个数据集本身有多少种可能。

五、隐单元给多少:够用之后就是浪费

最后看一个实用的问题:隐单元给几个。同样 2000 轮、同样 CD-1, 每个设置换 3 个随机种子取平均——因为这个模型本身就带采样随机性:

隐单元数 测试集重构误差 比上一档好了多少 参数量
4 0.1602 — 129
8 0.0638 0.0964 233
16 0.0509 0.0129 441
32 0.0456 0.0053 857
从 4 到 8 一下子好了 0.0964,从 8 到 16 只好了 0.0129,再到 32 只好了 0.0053。
16 个基本就够了。
这条曲线跟 7.1 那个肘部法是一回事:加隐单元类似加簇数,够用之后就是浪费。
而且注意参数量的涨法——从 441 到 857,翻了快一倍,换来 0.0053。
所以「多给几个总没坏处」这句话只对一半:精度上确实没坏处,代价上全是坏处。训练时间、内存、
过拟合风险,都跟着参数走。

六、动手 Demo:一台只会「打分」的机器

五段:能量与配分函数、对比散度、让它自己画、遮挡补全、隐单元数。 第 1 段是「为什么必须采样」,第 2 段是「采样怎么偷懒」,后三段是「偷懒之后还剩下什么」。 这段代码里的 RBM 只有三十几行。它的全部难点不在实现,在「训练目标里那个算不出来的 Z」。

# 训练:拿「数据的期望」减去「走 k 步采样后的期望」
h0p = sig(v0.dot(W) + c)                        # 真实数据下的隐单元概率
hk = (sig(v0.dot(W) + c) > r.random(...)).astype(float)   # 采出 0/1
v1 = (sig(hk.dot(W.T) + b) > r.random(...)).astype(float) # 往回采一张图
h1p = sig(v1.dot(W) + c)                        # 模型自己认为的隐单元概率
W += lr * ((v0.T.dot(h0p) - v1.T.dot(h1p)) / len(v0) - wd * W)
b += lr * (v0 - v1).mean(0)
c += lr * (h0p - h1p).mean(0)

# 训练完之后:从随机噪声出发,让它自己画
GEN = (rs.random((8, 25)) < 0.2).astype(float)
for _ in range(400):                            # 400 步吉布斯
    hh = (sig(GEN.dot(W) + c) > rs.random((8, 16))).astype(float)
    GEN = (sig(hh.dot(W.T) + b) > rs.random(GEN.shape)).astype(float)

# 判断「画出来的是不是完整的一条线」
def is_line(v):
    a = v.reshape(5, 5) > 0.5
    if int(a.sum()) != 5:
        return False
    return bool(a.all(1).any() or a.all(0).any())

实跑输出:

--------------------------------------------------------------------
一台只会「打分」的机器
--------------------------------------------------------------------
==============================================================

[1] 能量和配分函数:为什么这个模型必须「采样」

  RBM 的全部内容就是一个能量函数:
    E(v, h) = −a·v − b·h − v 乘 W 乘 h
  **能量越低,这个状态越可能出现**(概率正比于 e 的 −E 次方)。
  于是要算「看到 v 的概率」,就得把 h 的所有取法都加起来——
  **再除以一个归一化常数 Z,而 Z 要把 v 和 h 的所有取法都加起来。**

  先在一个小到能数完的模型上验证这件事(4 个可见 + 2 个隐单元):

    联合状态一共 64 个,配分函数 Z = 122.186572
    精确边缘分布(16 个可见状态里最大的三个):
      v = 0110   p = 0.3092
      v = 0100   p = 0.1268
      v = 0010   p = 0.1136

    吉布斯采样 3 万步之后的经验分布:
      v = 0110   p = 0.3041   精确值 0.3092
      v = 0100   p = 0.1317   精确值 0.1268
      v = 0010   p = 0.1143   精确值 0.1136
    两种办法的最大绝对差 0.0051

  **在这个小模型上,采样跟硬算对得上。**但代价差得多:
    4 + 2 个单元:64 个状态,数一下就好。
    25 + 16 个单元:2.20e+12 个状态。
    一秒数一千万个,也要 3 天。
  **所以 RBM 的训练从来不去算 Z,它绕开 Z 直接凑梯度。**

  数据:5 乘 5 的二值小图,一共 10 种(5 条横线、5 条竖线),
  每种复制 40 份并随机翻 2% 的像素,共 400 张。**没有任何标签。**
  每张图都只有 5 个像素亮着(一共 25 个像素),像素密度 20%。

[2] 对比散度:只走一步就够

  训练要算「数据的期望」和「模型的期望」之差。前者一次前向就有,
  **后者要采样,而采样要走很多步才「混合」。**
  **Hinton 的做法是只走 k 步,叫 CD-k。**真跑一遍:

    训练方式        训练集重构误差      测试集重构误差      训练耗时
    ----------------------------------------------------------------------------
    CD-1            0.0593              0.0547              0.8 秒
    CD-3            0.0527              0.0480              1.5 秒
    CD-5            0.0553              0.0513              2.2 秒

  **CD-3 确实最好(0.0480),可比 CD-1(0.0547)只好了 0.0067。**
  **而它的训练时间是 CD-1 的 1.9 倍。**
  **这就是「对比散度为什么够用」的完整理由:它估的梯度有偏,但方向对,而且便宜。**
  代价要说清:它学到的不是一个干净的生成模型,**更像一个「会填空」的压缩器。**

[3] 它学到了什么:让它自己画

  先把权重搬回 5 乘 5 的画面(挑权重落差最大的 4 个隐单元):

    隐13  隐7   隐8   隐16 
    ##::: + :    ::.: ++...
    +++.: +.+:+ +#+:# ::. .
    :..   #:++:  ::.: #+:::
    :::.  : : . :++:# ##:++
    :..   +:+:: :+#:# :: ..

  **说实话:这不长得像教科书里的方向滤波器。**它是几块弱的正负叠加,
  **因为隐层有一个「旋转自由度」**——把 h 的坐标整体转一下,模型的概率一点不变,
  所以「第几个隐单元代表哪条线」本来就没有唯一答案。
  **那怎么判断它到底学到东西没有?别再看权重,让它自己画。**

  从随机噪声出发,让它自由采样 400 步,画出 8 张:

    #####   #         #  #####  #              #         #
            #         #           #        #   #         #
            #         #         ## ##      #   #         #
            #         #         #  #           #         #
    #  ##   #         #                    #   #         #

    来源                  「是一整条横线或竖线」的比例    平均亮几个像素
    ----------------------------------------------------------------------------
    RBM 自由采样 500 张   55.2%                           5.4
    同样密度的随机图      0.0%                            5.0

  **随机图里一张都不是线(0.0%),它画出来的有 55% 是完整的一条线。**
  **这就是「学到了结构」的硬证据:它没有记住那 400 张,它学会了「线长什么样」。**

[4] 给它半张图,看它补什么

  把底下的 1 行、2 行、3 行涂成 0,让它补回来。每种情况 10 个模式各试 20 次:

    涂掉几行      还剩几个像素      涂掉那几行补对率      整张图补对率
    ----------------------------------------------------------------------------
    1             20                87.4%                 92.0%
    2             15                84.0%                 87.1%
    3             10                79.8%                 80.8%

  **遮得越多,补得越差,而且掉得不快**——涂掉 3 行只剩 10 个像素,还能补对 80%。
  原因很朴素:**这 10 种图的规律太强了**。看到某一行亮,剩下的几行就有八九成把握。
  **换成自然图像就没这么灵**——那才是 RBM 真正不够用的地方。

[5] 隐单元给多少:够用之后就是浪费

  同样 2000 轮、同样 CD-1,改隐单元个数。**每个设置换 3 个随机种子取平均**——
  因为这个模型本身就带采样随机性,单跑一次的数字会跳:

    隐单元数      测试集重构误差      比上一档好了多少    参数量
    --------------------------------------------------------------------------
    4             0.1602              —                   129
    8             0.0638              0.0964              233
    16            0.0509              0.0129              441
    32            0.0456              0.0053              857

  **从 4 到 8 一下子好了 0.0964,从 8 到 16 只好了 0.0129,再到 32 几乎没动。**
  **16 个隐单元基本就够了。**再加到 32,参数量接近翻倍,误差只降了 0.0053。
  这条曲线跟 7.1 那个肘部法是一回事:**够用之后就是浪费。**

  五段连起来:**能量定概率**,可配分函数数不完,只能采样;
  **采样太慢,就只走一步**;**它学到的结构,得让它自己画出来才看得见**;
  **会画就会补**;**而隐单元给够就行。**
  **9.3 要讲的「逐层预训练」,就是把这些零件一层层叠上去。**

第 1 段那个 Z = 122.186572 是可以手算验证的——64 个状态,每个算一次能量。 这一段的用处是让你相信「采样确实能得到正确答案」,然后再看 2.20e12 那个数。 第 2 段 CD-3 比 CD-5 好,这件事值得停一下。它不是说「多走几步有害」, 而是说「在这个预算下,采样噪声已经盖过了那点收益」。 读任何「A 比 B 好」的结论,都要先问:预算是多少。 第 3 段是这一节的高潮,也是唯一能证明「它学到了东西」的地方。 权重图看着像噪声,可它画出来的图有 55% 是完整的一条线—— 而随机图是 0%。这个对比比任何权重可视化都有说服力。 第 5 段那个「3 个种子取平均」不是讲究,是必需。这个模型自带采样随机性,单跑一次的数字会跳; 只跑一次就下结论,很容易得出反的结论。

30 秒实验

  1. 把第 3 段那个 400 步的自由采样改成 20 步,重跑。 「是完整一条线」的比例会明显掉——走得太少,链还没混合,画出来的还是噪声。
  2. 把第 5 段的 epochs 从 2000 改成 200,重跑。 16 和 32 的差距会拉大——隐单元多的时候更需要训练量,否则大部分单元还没找到自己该管什么。
  3. 把第 4 段涂掉的行数改成 4(只剩 5 个像素),重跑。 补对率会掉到一半上下—— 信息不够的时候,它只能给出「最常见的补法」,而不是「这一张的补法」。

常见误区

  1. 以为 RBM 是用来分类的。它不预测标签,它学的是数据的分布。 要用它分类,得在它上面再加一层——那正是 9.3 要讲的事。
  2. 以为权重图不好看就等于没学到。Demo 第 3 段:权重像噪声, 可它画出来的图 55% 是完整的一条线。隐层有旋转自由度,权重表示本来就不唯一。
  3. 以为「多走几步 CD-k 一定更好」。Demo 里 CD-3 比 CD-5 还好。 k 是超参,要跟训练轮数一起看。
  4. 以为补全率高就说明模型强。这 10 种图本来规律就强。 换个数据集(自然图像),同样的模型会立刻露馅。
自测三问(点开对答案) 1. 为什么 RBM 训练不能直接算梯度里的那个归一化常数 Z? —— 因为 **Z 要把所有可见和隐单元的组合都加起来**。 Demo 里 25 加 16 个单元就是 2.20e12 个状态,**一秒数一千万个也要 3 天**; 真实模型的量级根本数不完。 **所以 RBM 用「采样」去估那个期望,绕开 Z 直接凑梯度**——代价是梯度有偏,但方向对。 2. 对比散度里的 k 取多少合适? —— **实践中取 1。**Demo 里 CD-1 的重构误差 0.0547、CD-3 是 0.0480、CD-5 是 0.0513, **三种走法差不了多少,而 CD-5 的训练时间是 CD-1 的 2 倍多。** **收益配不上成本**,所以大家默认用 CD-1,靠「多训几轮」把偏差摊掉。 3. 权重图看不出结构,为什么还说它学到了东西? —— 因为**隐层有一个旋转自由度**:把 h 的坐标整体转一下,模型的概率一点不变, **所以「第几个隐单元代表什么」没有唯一答案,权重图自然不干净。** **可靠的证据是「让它自己画」**:自由采样 500 张, **55.2% 是完整的一条线,而同密度的随机图是 0.0%。**它学到了「线长什么样」。

这一节的最后一句要留给 9.3:它最大的历史贡献不是「会画图」, 而是「可以一层一层地垫」——因为学一层不需要标签。 下一节就看这个垫法能救多深的网络。

相关笔记