玻尔兹曼机在 Hopfield 的基础上加了两样东西:隐层,和随机性。 于是它不再「记住几张图」,而是用一个概率分布描述「哪些状态常见、哪些罕见」。 概率由能量决定:能量越低的状态出现概率越大,公式是 exp(负能量) 除以配分函数 Z。 实测 32 个状态里最低的那个占 19.83%,最高的只占 0.0269%。 温度是旋钮:T 等于 0.05 时最低态占 100%,熵几乎为 0; T 等于 100 时 32 个状态几乎等概率,熵 3.4657 逼近上限。
上一节的网络只能回答「这个状态稳不稳」,这一节要回答「这个状态多常见」。 从「稳定」到「概率」的关键一步是引入温度:既然每个状态都可能出现, 那就得有个数说清「多可能」。 这一节也是后面所有内容的地基。 受限玻尔兹曼机的自由能、对比散度的采样、深度信念网络的预训练, 全都是在处理「这个概率怎么算、怎么采」的问题。
一、多了什么
相比 Hopfield 网络,玻尔兹曼机多了两处改变,每一处都改变了它的用途。 第一,加了隐层:一部分单元看得见(可见层),一部分看不见(隐层)。 第二,更新变成随机的:不再「局部场为正就取正」,而是「以某个概率取正」。
| 对比项 | Hopfield | 玻尔兹曼机 |
|---|---|---|
| 单元取值 | 正 1 与负 1 | 1 与 0 |
| 隐层 | 没有 | 有 |
| 更新方式 | 确定:按局部场取符号 | 随机:按 sigmoid 概率采样 |
| 描述的对象 | 几个具体的记忆点 | 整个状态空间上的分布 |
| 连接 | 全连接、对称 | 全连接、对称(同层也连) |
| 隐层带来的是「表示能力」:可见层看到的是像素, | ||
| 隐层可以去表示像素背后的因素(比如「这里有一根竖笔画」)。 | ||
| 随机性带来的是「可描述性」:只有确定性的网络,讨论「概率」没有意义。 |
二、能量定概率
玻尔兹曼机的能量函数比 Hopfield 多几项,因为同层之间也连。
E(v, h) = -b.v - c.h - v.W.h - 0.5*v.U.v - 0.5*h.P.h
b, c 可见层与隐层的偏置
W 可见 与 隐 之间的连接
U, P 可见层内部、隐层内部的连接
概率的写法只有一行:
P(状态) = exp(-E(状态)) / Z
Z = 所有状态 exp(-E) 之和
Z 叫配分函数,作用是让所有概率加起来等于 1。 实测 32 个状态算出 Z 等于 136.7214,概率总和 1.000000。 这个公式和信息论里的玻尔兹曼分布是同一个东西,名字也是从这里来的。 它的含义很直白:能量每低一点,概率就按指数放大一点——低能量的状态被指数级地偏爱。 最低的那个状态是 01111,能量负 3.3,概率 0.1983,占了将近两成。 最高的状态能量负 0.1,概率只有 0.000269——差了七百多倍。 注意这不是「谁出现次数多」,而是能量决定的固定分布,只要权重定了,分布就定了。
三、温度:给能量除一个数
把能量除以温度 T,再套进同一个公式,就得到了带温度的分布。
P(状态) = exp(-E(状态) / T) / Z(T)
T 趋于 0:除以一个很小的数,能量差被放大,概率全压到最低的那个状态上。 T 很大:除以一个大数,能量差被抹平,所有状态几乎等概率。
| 温度 T | 最低态概率 | 概率过 1% 的状态数 | 熵(奈特) |
|---|---|---|---|
| 0.05 | 1.0000 | 1 / 32 | 0.0005 |
| 0.20 | 0.8735 | 4 / 32 | 0.5328 |
| 0.50 | 0.4422 | 8 / 32 | 1.8511 |
| 1.00 | 0.1983 | 19 / 32 | 2.8220 |
| 2.00 | 0.0933 | 32 / 32 | 3.2913 |
| 10.00 | 0.0400 | 32 / 32 | 3.4592 |
| 100.00 | 0.0320 | 32 / 32 | 3.4657 |
| 熵那一列是这张表的重点:它衡量「不确定性有多大」。 | |||
| 32 个状态的熵上限是 log 32 等于 3.4657,也就是完全等概率时的值。 | |||
| T 等于 100 时熵是 3.4657,说明网络已经彻底在乱猜; | |||
| T 等于 0.05 时熵是 0.0005,说明它咬死了那一个状态。 | |||
| 中间那档是折中:既能往低处偏,又保留跳出局部坑的随机性。 | |||
| 它到底算不算「更好」,要看你要什么——找最低态和按分布采样是两件不同的事。 |
四、吉布斯采样:一次只动一个单元
概率公式给了,但怎么从里面取样本?答案是吉布斯采样。 做法:别的单元都当已知,只算第 i 个单元取 1 的条件概率,然后掷一次骰子。
p(第 i 个单元取 1) = sigmoid(局部场 / T)
局部场 = 第 i 个单元取 1 比取 0 省下的能量
一次只动一个单元,其余保持不动,这叫单点吉布斯。 跑得足够久,访问各状态的频率就会逼近理论分布——实测跑 4 万步、扔掉前一半, 32 个状态的最大偏差只有 0.0074。 为什么必须一次只动一个? 因为「保持其余不动」是让条件概率有意义的前提:条件概率里的「条件」就是其余单元。 同时翻所有单元,等于在求一个根本不存在的联合条件概率。 能量景观只有一个深坑时,低温就已经足够:实测全程低温 0.05 走 12000 步, 30 次里 30 次都落在全局最低。 但全程高温 10 只有 1 次,全程中温 1 只有 7 次——它在状态之间不停乱跳,停不下来。 模拟退火是折中方案:先高温把该逛的地方逛遍,再逐步降温锁死。 它的本事要等能量景观里有好几个坑、还一个比一个深时才显出来。 眼前这个五单元小网络只有一个坑,所以低温就已经够用,退火看不出领先。
五、动手 Demo:五个单元的小网络:算出分布、扫温度、跑吉布斯采样
网络很小:3 个可见、2 个隐,一共 5 个单元、2 的 5 次方等于 32 个状态, 小到可以把每一个状态的能量都算出来。 参数是手工设的,刻意让两个可见单元 v0 与 v1 的层内权重为负 1(喜欢一起亮), v1 与 v2 为正 1(互相排斥)——这样能量景观里就不止一个坑。
NV, NH = 3, 2 # 3 个可见、2 个隐
B = np.array([0.3, -0.2, 0.5]) # 可见层偏置
C = np.array([0.1, -0.4]) # 隐层偏置
W = np.array([[0.8, -0.6],
[-0.5, 0.9],
[0.7, 0.7]]) # 可见 乘 隐
U = np.array([[0.0, -1.0, 0.0], # 可见层内部(同层也连)
[-1.0, 0.0, 1.0],
[0.0, 1.0, 0.0]])
P = np.array([[0.0, 0.5],
[0.5, 0.0]]) # 隐层内部
ALL = np.array(list(itertools.product([0.0, 1.0], repeat=5))) # 32 个状态
def energy(s):
v, h = s[:NV], s[NV:]
return float(-B @ v - C @ h - v @ W @ h - 0.5 * v @ U @ v - 0.5 * h @ P @ h)
E = np.array([energy(s) for s in ALL])
raw = np.exp(-E)
Z = float(raw.sum()) # 配分函数 136.7214
p1 = raw / Z # 玻尔兹曼分布,加起来 1.000000
def boltz(T):
e = -E / T # 给能量除以温度
e -= e.max()
p = np.exp(e)
return p / p.sum()
def gibbs(s, steps=2000, T=1.0, seed=0):
rg = np.random.default_rng(seed)
for t in range(steps):
i = rg.integers(0, NV + NH) # 随机挑一个单元
p1_ = 1.0 / (1.0 + np.exp(-local_field(s, i, T)))
s[i] = 1.0 if rg.random() < p1_ else 0.0
return s
实跑输出:
--------------------------------------------------------------------
玻尔兹曼机:能量定概率,温度定随机性
--------------------------------------------------------------------
[1] 一个五单元的小网络:3 个可见、2 个隐
状态记成 5 个 0 或 1;五个单元两两都可能相连(同一层内也连),这就是「玻尔兹曼机」
能量写成四块:可见偏置、隐层偏置、可见乘隐的连接、层内连接
能量函数 E = 负 b 乘 v 负 c 乘 h 负 v 乘 W 乘 h 负 半个 v 乘 U 乘 v 负 半个 h 乘 P 乘 h
这里刻意让两个可见单元 v0 与 v1 的层内权重为 负 1(喜欢一起亮),
v1 与 v2 为 正 1(互相排斥)——这样能量景观里就有不止一个坑
5 个单元一共 2 的 5 次方等于 32 个状态,可以直接全部穷举
[2] 玻尔兹曼分布:能量越低,出现的概率越大
配分函数 Z 是把全部 32 个状态的 exp(负能量) 加起来,这里 Z = 136.7214
概率就是 exp(负能量) 除以 Z,加起来 1.000000(必然等于 1)
排名 状态 v0v1v2 h0h1 能量 概率
1 01111 -3.3000 0.1983
2 11111 -2.8000 0.1203
3 10111 -2.6000 0.0985
4 01101 -2.5000 0.0891
5 10110 -2.4000 0.0806
最低能量的状态占了 19.83%,最高能量的状态只占 2.69e-01%
注意概率不是「谁出现次数多」,而是能量决定的固定分布——这是它和 Hopfield 最大的区别
[3] 温度 T:给能量除一个数,概率分布就变形了
温度 T 最低态概率 状态数(概率过 1%) 熵(奈特)
0.05 1.0000 1 / 32 0.0005
0.20 0.8735 4 / 32 0.5328
0.50 0.4422 8 / 32 1.8511
1.00 0.1983 19 / 32 2.8220
2.00 0.0933 32 / 32 3.2913
10.00 0.0400 32 / 32 3.4592
100.00 0.0320 32 / 32 3.4657
温度趋于 0,概率全压到最低态上,熵趋于 0,网络变成一台死记硬背的机器;
温度很高,32 个状态几乎等概率,熵逼近上限 3.4657,等于在乱猜。
中间那档是个折中:既能往低处偏,又保留跳出局部坑的随机性。
它到底算不算「更好」,要看你要什么——找最低态和按分布采样是两件不同的事。
[4] 吉布斯采样:一次只动一个单元,按条件概率决定它翻不翻
别的单元都当已知,第 i 个单元取 1 的概率就是 sigmoid(局部场 除 温度)
局部场等于「它取 1 比取 0 省下多少能量」,正数说明取 1 划算
这样一次一步地改,跑得足够久,访问各状态的频率就会逼近第 2 段算出的理论概率
从初态 11001 出发,T 等于 1,采样 40000 步,扔掉前一半
状态 理论概率 采样频率 差距
01111 0.1983 0.1940 -0.0044
11111 0.1203 0.1262 +0.0059
10111 0.0985 0.0948 -0.0037
01101 0.0891 0.0818 -0.0074
全部 32 个状态的最大偏差 0.0074,吻合得相当好
[5] 温度旋钮的作用:乱逛与下钻
温度策略 结束时正落在全局最低态(共 30 次)
全程高温 10 1 / 30
全程中温 1 7 / 30
全程低温 0.05 30 / 30
从 10 降到 0.05 30 / 30
高温那一行最惨:它在各个状态之间不停乱跳,压根停不下来,最后停在哪儿全看运气。
低温那一行反而最好,因为这个网络只有一个明显的深坑,只要往下滑就滑对了。
模拟退火(最后一行)先高温把该逛的地方逛遍,再逐步降温锁死——
它的本事要等能量景观里有好几个坑、还一个比一个深时才显出来。
眼前这个五单元的小网络只有一个坑,低温就已经够用,所以退火看不出领先。
这也解释了为什么玻尔兹曼机的训练那么慢:每算一次梯度都要在链上跑很多步。
[6] 后一节要砍掉的东西
这个小网络有 10 个连接:可见层内部 3 对、隐层内部 1 对、跨层 6 对
层内连接让它很强,也让「已知可见层时隐层各单元互相影响」——条件概率算不出来
下一节把层内连接全部砍掉,只留可见与隐之间的连接,这就是受限玻尔兹曼机
第二段在验证一件事:概率加起来必须等于 1。 这不是巧合,是除以 Z 的目的——Z 就是为了归一化才存在的。 第四段最有说服力。 从初态 11001 出发,用单点吉布斯跑 4 万步、扔掉前一半, 采样频率和理论概率的最大偏差只有 0.0074。 最高几个状态:理论 0.1983 对采样 0.1940、0.1203 对 0.1262、0.0985 对 0.0948。 这说明「按条件概率一步步改」确实能忠实地重现那个分布——不是近似,是收敛到它。 第五段的结果有点反直觉:低温比退火还好用。 全程低温 0.05 是 30 次全中,退火也是 30 次全中,两者打平; 而中温 1 只有 7 次、高温 10 只有 1 次。 原因很简单:这个网络的能量景观只有一个深坑,随便从哪儿出发,往下滑都滑到同一个地方。 退火的价值要靠「好几个坑」才体现得出来,这里没有那个条件。 所以别把这张表读成「退火没用」,要读成「坑少时低温就够,坑多时才需要退火」。 最后一段是这一节的落点。 这个网络有 10 对连接,其中 4 对是同层内部的。 正是这 4 对连接让「已知可见层时隐层各单元还互相影响」——条件概率算不出来,只能一个个轮流采。 下一节要把这 4 对全砍掉。
30 秒实验
- 把
U里的负 1 改成 0(取消 v0 与 v1 的相互吸引),再看温度表。 能量景观变平,最低态的概率优势会缩小,同一温度下的熵会变大。 - 把
boltz(T)里的 T 换成0.01,看最低态概率变成多少。 它会变成 1.0000 的近似值——这就是「零温极限」,网络退化成一个只会背书的机器。 - 把吉布斯采样的步数从 40000 改成 2000,再对比采样频率与理论概率。 偏差会明显变大,这说明「跑得足够久」不是客套话,是收敛的前提。
常见误区
- 「能量越低说明模型越好。」 能量是状态的属性,不是模型的评分。 训练玻尔兹曼机是让「数据状态的自由能」变低,不是让某个状态的能量变低。 而且不同权重下的能量尺度不能互相比较,只能在同一组参数内部比高低。
- 「配分函数 Z 可以顺便算出来。」 不能。Z 要把所有状态的能量加起来,状态数是 2 的单元个数次方。 5 个单元还能穷举,64 个可见加 100 个隐就是 2 的 164 次方——宇宙里没有那么多算力。 整个对比散度算法,就是为了绕开这个 Z 才被发明出来的。
- 「高温能到处逛,所以高温更好。」 高温确实逛得广,但它停不下来。 实测全程高温 10 走 12000 步,30 次里只有 1 次恰好停在全局最低态——它采到的是整个分布, 不是最低点。 要最低点就得降温,要分布就得保持温度,两件事不能同时满足。
自测三问(点开对答案)
1. **温度趋于 0 和趋于无穷,分布分别变成什么?** —— **T 趋于 0:概率全压到能量最低的那个状态上,熵趋于 0。** **T 趋于无穷:所有状态等概率,每个占 1 除以状态数,熵趋向 log 状态数。** **实测 T 等于 0.05 时最低态占 1.0000、熵 0.0005;T 等于 100 时占 0.0320、熵 3.4657。** 2. **为什么吉布斯采样一次只能改一个单元?** —— **因为条件概率的「条件」是其余单元固定不变。** **同时改多个单元,就等于在求一个没有定义的联合条件概率,采出来的样本不再服从目标分布。** **这也是它慢的原因:一步只能动一个,跑几万步才能采到一个像样的样本。** 3. **配分函数算不出来,为什么还能用这个模型?** —— **因为很多用途只需要「能量」或「条件概率」,不需要 Z。** **比如给定可见层采隐层、算自由能、做梯度更新,这些都能绕开 Z。** **只有在需要「某个可见状态的绝对概率」时才非算 Z 不可,而那个用法在实践中很少。**玻尔兹曼机有两个麻烦:一是同层连接让条件概率算不出,二是 Z 算不出。 下一节砍掉同层连接,第一个麻烦直接消失; 顺带还白捡一个「自由能」,把第二个麻烦压缩到「只跟可见层有关」。
- ☐ (本节完,下一节 4.3 受限玻尔兹曼机)