受限玻尔兹曼机只改一件事:砍掉同层之间的连接,只留可见与隐之间的连。 这一刀把结构变成二分图,代价是表达力下降,收益是两样东西立刻变得可算。 第一样是条件独立:已知一层,另一层的概率一次矩阵乘就全出来。 实测和穷举能量定义的差距是 2.22e-16,不是近似,是等价改写。 第二样是自由能:隐层被解析地求和掉,误差 8.88e-16。 于是 2 的 164 次方个联合状态,被压成「只跟可见层有关」的一次计算。
上一节最后留了两个麻烦:同层连接让条件概率算不出,配分函数 Z 算不出。 这一节砍掉同层连接,第一个麻烦当场消失; 同时白捡一个自由能,把第二个麻烦从「绝无可能」压成「能算但太慢」。 这一节没有新算法,全是结构上的取舍。 但正是这一刀,让下一节的对比散度算法有可能跑起来——否则采一步的代价就已经算不动。
一、砍掉的是什么
上一节那个五单元网络有 10 对连接:可见层内部 3 对、隐层内部 1 对、跨层 6 对。 受限玻尔兹曼机只保留跨层的 6 对,同层那 4 对全部去掉。
| 连接类型 | 玻尔兹曼机 | 受限玻尔兹曼机 |
|---|---|---|
| 可见层内部 | 有 | 没有 |
| 隐层内部 | 有 | 没有 |
| 可见与隐之间 | 有 | 有 |
| 结构 | 一般图 | 二分图 |
| 参数个数 | 含层内项 | 只有 W、b、c |
| 「受限」这两个字说的就是这个限制。 | ||
| 名字听起来像退让,实际上是整章最值钱的一刀——下面两段都在讲它赚回了什么。 | ||
| 能量函数也跟着瘦身,层内那两项直接消失: |
E(v, h) = -b.v - c.h - v.W.h
二、赚回来的第一样:条件独立
要让「已知可见层时,各隐单元相互独立」成立,前提正是同层之间没有连线。 道理不难:两个隐单元如果没有直接连线,它们只能通过可见层互相影响; 一旦可见层被固定,这条影响通路就断了。
p(h 给 v) = sigmoid(c + v @ W) # 一次矩阵乘,全部隐单元
p(v 给 h) = sigmoid(b + W @ h) # 反过来也一样
于是「采一层」变成一次矩阵乘法加一次掷骰子,不再是「一个一个轮流试」。 实测给定一个 6 位可见状态,一次乘法得到三个隐单元的概率是 0.1493、0.9640、0.8083; 拿能量定义穷举 8 个隐状态硬算再求平均,得到的是同样三个数,最大差 2.22e-16。 这不是近似,是等价改写。 上一节的网络做不到这一点:已知可见层时,隐层两个单元还互相牵制,只能轮流采样逼近, 而轮流采样要跑很多步。
三、赚回来的第二样:自由能
把隐层所有可能的取值都试一遍再求和,能得到一个「只关于可见层」的量,叫自由能:
exp(-F(v)) = 隐层全部状态 exp(-E(v, h)) 之和
F(v) = -b.v - 求和 log(1 + exp(c + v.W))
公式化之后不用再枚举隐层:只要隐单元个数那么多次 exp 运算就够了。 实测对全部 64 个可见状态逐一比对「穷举隐层求和对」与「自由能公式」,最大误差 8.88e-16。 这一步省下的是指数级的算力。 6 个可见加 3 个隐要枚举 512 个联合状态;换成自由能只需要 64 个可见状态,每个再算 3 次 exp。 放到真实规模上:64 个像素加 100 个隐,联合状态是 2 的 164 次方,绝无可能; 用自由能要算的是 2 的 64 次方个可见状态,还是算不完,但从「绝无可能」变成了「能算但太慢」。 这个差距很重要:它决定了后面的采样算法有没有意义。
四、它还是不够用
砍完连接之后,RBM 依然算不出配分函数 Z,因为 Z 要把 2 的可见单元个数次方个自由能加起来。 所以「某个可见状态的绝对概率」还是算不出来。 但它能算的东西已经足够训练用了:给定一层采另一层、算自由能、算重构误差。 下一节的对比散度正是只用到这三样,从头到尾没碰过 Z。 还有一处实践中的偏差要提前说清。 严格按定义,隐层的取值应该是采样出来的 0 或 1; 但把多层 RBM 摞起来时,下一层的输入如果只是 0 与 1,信息损失太大。 实践中通行的做法是直接拿「取 1 的概率」当下一层的输入,取值在 0 到 1 之间连续变化。 这不符合原始定义,但效果明显更好——第 4.5 节的实测会用到这个做法。
五、动手 Demo:两次验算:条件独立对不对、自由能省了多少
这个 Demo 用两个精确验算来证明上面两段话。 验算能用穷举做,是因为网络小:6 个可见、3 个隐,一共 512 个状态。 两处都对上了,才敢说「等价改写」和「解析求和」不是空话。
def ph_given_v(v):
u"""给定可见层,一次矩阵乘算出全部隐单元取 1 的概率。"""
return sig(c + v @ W)
def pv_given_h(h):
return sig(b + W @ h)
def free_energy(v):
u"""隐层被解析地求和掉,只剩可见层。"""
return float(-b @ v - np.log1p(np.exp(c + v @ W)).sum())
# 验证一:条件独立与能量定义等价
p = ph_given_v(v0)
num = np.exp(-np.array([energy(v0, h) for h in ALLH]))
marg = (num / num.sum()) @ ALLH
print(np.abs(p - marg).max()) # 2.22e-16
# 验证二:自由能等于把隐层全部状态加起来
err = 0.0
for v in ALLV:
brute = float(np.exp(-np.array([energy(v, h) for h in ALLH])).sum())
err = max(err, abs(-np.log(brute) - free_energy(v)))
print(err) # 8.88e-16
实跑输出:
--------------------------------------------------------------------
受限玻尔兹曼机:砍掉层内连接,换来条件独立与自由能
--------------------------------------------------------------------
[1] 从玻尔兹曼机到受限玻尔兹曼机:只砍一件事,砍掉层内连接
上一节那个五单元网络,同层之间也连着(可见层 3 对、隐层 1 对、跨层 6 对,共 10 对)
RBM 只保留跨层的连:可见层 3 个、隐层 2 个,连接数正好 3 乘 2 等于 6
砍完之后结构变成一张「二分图」:连线只跨两层,同层之间一条都没有
这一刀看着像让步,其实是整章最值钱的一刀。下面三段都在讲它赚回了什么
[2] 赚回来的第一样:条件独立,可以一次算完
要让隐单元 h 的取值在「已知可见层 v」时相互独立,前提正是同层之间没有连线
于是 P(h 给 v) 直接等于各个隐单元概率连乘,可以一次算出来,不用一个一个试
取一个可见状态 v = 101101
一次乘法得到三个隐单元取 1 的概率:0.1493 0.9640 0.8083
拿能量定义穷举 2 的 3 次方个隐状态硬算,再求各单元的平均取值:0.1493 0.9640 0.8083
两种算法最大差 2.22e-16——条件独立不是近似,是等价改写
(上一节的网络做不到:已知可见层时隐层两个单元还互相牵制,只能轮流采样逼近)
[3] 赚回的第二样:自由能,把隐层整个求和掉
把隐层所有可能的取值都试一遍再求和,得到「只关于 v 的能量」,也就是自由能:
exp(负自由能) = 隐层全部状态 exp(负能量) 之和
公式化之后不用再枚举隐层:F(v) 等于 负 b 乘 v 减 log(1 加 exp(c 加 v 乘 W)) 之和
对全部 64 个可见状态逐一比对,最大误差 8.88e-16
[4] 省下来的是指数级的算力
可见层 6 个、隐层 3 个时,直接枚举联合状态要 6 加 3 等于 9 位,
也就是 512 个状态;用自由能只需要 64 个可见状态各算一次,每次 3 次 exp
放大到真正的图像任务:可见层 64 个像素、隐层 100 个,联合状态是 2 的 164 次方;
有了自由能,要算的只是 2 的 64 次方个可见状态——还是算不完,但已经从「绝无可能」
变成「能算但太慢」,这一步的差距决定了后面的采样算法有没有意义
[5] 采样:已知一层就采另一层,来回各一次叫一步重构
用 8 乘 8 数字图当可见层(64 个像素),隐层 8 个单元
隐层取 1 的概率 0.53 0.47 0.59 0.54 0.47 0.57 0.55 0.38
采到的隐层模式 10101100
再由隐层反推可见层,和原图逐像素一致的比例 0.5312
为什么这么低?因为权重是随机初始化、一次都没训练过的。
随机权重下 RBM 只会把图搅成一团噪声——下一节要解决的正是「怎么把权重练出来」
[6] 这一个「砍」字换来的三件事
一、条件独立:已知一层,另一层的概率一次矩阵乘法算完,可以并行采样
二、自由能:隐层被解析求和掉,算可见层概率时不再枚举 2 的隐层个数次方
三、对比散度才有可能:后面的训练算法靠「反复采几步」近似梯度,
没有前两条,采一步的代价就已经算不动了
第一处验算的结果是 2.22e-16,这个数字就是双精度浮点的机器精度量级。 换句话说,两种算法在浮点误差范围内完全一致——不是「差不多」,是「同一个东西」。 第二处验算更关键。 它把「穷举隐层求和再取负对数」和「自由能公式」逐状态比对,最大误差 8.88e-16。 这证明自由能不是近似值,是把隐层求和这一步用解析式做掉了。 最后一段还留了个诚实的小尾巴。 用随机初始化、一次都没训练的权重去做一次重构,逐像素一致率只有 0.5312——和瞎猜差不多。 这不是 RBM 不行,是权重还没练。 下一节要解决的正是「怎么把权重练出来」,届时同样的重构任务会做到 0.95 以上。 读这张表时留意一件事:能算的东西变了,但「算不全」没有变。 Z 依然算不出来,绝对概率依然没有。 RBM 的价值不在于「什么都能算」,而在于「训练需要的那些量恰好都能算」。
30 秒实验
- 把
NV, NH = 6, 3改成6, 4,再跑一遍验算。 误差依旧是 10 的负 16 次方量级,但自由能要算的 exp 次数从 3 变成 4。 这就是「线性增长」对「指数增长」的差别。 - 把
ph_given_v里的v @ W改成逐单元循环算,结果一样但慢得多。 条件独立的意义就是能一次算完,写成循环等于把这份好处扔了。 - 把第 5 段的权重从随机初始化换成第 4.4 节训练好的,再看重构一致率。 它会从 0.53 跳到 0.95 左右——同一套结构,差别只在权重。
常见误区
- 「受限」是能力被削弱,所以不如玻尔兹曼机。 表达力确实下降了,但可算性大幅上升。 一个算不动的强模型不如一个能算的弱模型——玻尔兹曼机就是卡在「算不动」上,几十年没进实用。
- 「条件独立意味着隐单元之间毫无关系。」 不是。它们的边缘分布仍然相关,只是「已知可见层之后」条件独立。 这就像两个学生都受同一门课的成绩影响:不告诉你成绩时两人的表现相关,告诉你成绩之后, 剩下的差异就互不相干了。
- 「自由能就是把隐层平均掉。」 不是平均,是对数求和,形式上是「soft-min」而不是均值。 如果写成平均,低温时就丢掉了「只走最低那条路」的性质,算出来的东西和真实概率对不上。 实测的 8.88e-16 误差是靠 log-sum-exp 拿到的。
自测三问(点开对答案)
1. **砍掉同层连接,为什么条件概率就能算了?** —— **因为同层没有直接连线,两个同层单元只能通过另一层互相影响。** **另一层一固定,这条通路就断了,剩下的只有各自的偏置和跨层权重,互不相干。** **于是联合条件概率直接等于各自条件概率的乘积。** 2. **自由能省了多少计算?举个具体的数。** —— **6 个可见加 3 个隐:直接枚举要 512 个联合状态,** **用自由能只要 64 个可见状态各算 3 次 exp。** **放大到 64 个像素加 100 个隐:联合状态 2 的 164 次方,自由能要算的是 2 的 64 次方。** **指数没消失,但从「隐层个数」那一层指数上撤下来了。** 3. **为什么实践里用「概率」而不是「采样值」当下一层的输入?** —— **因为采样值只有 0 和 1,信息丢得太多;概率是 0 到 1 的连续值,保留了不确定性。** **这不严格符合 RBM 定义,但实测效果好得多——第 4.5 节的两层堆叠就是这么做的。**结构砍完了,接下来是「怎么练」。 RBM 的对数似然梯度是「数据上的平均」减「模型上的平均」, 后者要在模型分布里采样——而现在一次采样只要一次矩阵乘,比上一节快得多。 下一节的对比散度,就是在这个基础上再省一招:不等链收敛,从数据出发走几步就够。
- ☐ (本节完,下一节 4.4 对比散度算法)