这是一个「只会打分」的模型。它给每一种组合打一个能量分,能量低的组合更可能出现。 它不预测标签,它学的是「数据长什么样」。 它也是深度学习的第一块垫脚石。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 秒实验
- 把第 3 段那个
400步的自由采样改成 20 步,重跑。 「是完整一条线」的比例会明显掉——走得太少,链还没混合,画出来的还是噪声。 - 把第 5 段的
epochs从 2000 改成 200,重跑。 16 和 32 的差距会拉大——隐单元多的时候更需要训练量,否则大部分单元还没找到自己该管什么。 - 把第 4 段涂掉的行数改成 4(只剩 5 个像素),重跑。 补对率会掉到一半上下—— 信息不够的时候,它只能给出「最常见的补法」,而不是「这一张的补法」。
常见误区
- 以为 RBM 是用来分类的。它不预测标签,它学的是数据的分布。 要用它分类,得在它上面再加一层——那正是 9.3 要讲的事。
- 以为权重图不好看就等于没学到。Demo 第 3 段:权重像噪声, 可它画出来的图 55% 是完整的一条线。隐层有旋转自由度,权重表示本来就不唯一。
- 以为「多走几步 CD-k 一定更好」。Demo 里 CD-3 比 CD-5 还好。 k 是超参,要跟训练轮数一起看。
- 以为补全率高就说明模型强。这 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:它最大的历史贡献不是「会画图」, 而是「可以一层一层地垫」——因为学一层不需要标签。 下一节就看这个垫法能救多深的网络。
- ☐ (本节完,下一节 9.3 深度神经网络)