2.12 能量模型与联想记忆 2.12.0 能量模型(家族公共部分)
一句话
给每个状态定义一个「能量」,能量越低越可能出现;学习就是把训练数据所在位置的能量压低、把其他位置的能量抬高。
这就是统计物理里的玻尔兹曼分布。T 是温度:T 高时各状态概率接近,T 低时概率集中到能量最低的状态。Z 叫配分函数,要对所有可能的状态求和(连续变量时是积分,积分有限才是合法的概率密度)。状态数随维度指数增长,大规模的一般模型很难精确算出 Z;小模型可以直接枚举,RBM 等可以用退火重要性采样(AIS)估计,但有额外的计算成本和估计误差。这个家族的难点和各种技巧几乎都围绕 Z 展开。
学习规则的共同形状 :取 T = 1(温度可以并进能量里;不为 1 时下式整体再乘 1/T),对数似然对参数的梯度是两项之差:
第一项(正相)压低训练数据的能量,第二项(负相)抬高模型自己生成的样本的能量。负相要算模型分布下的期望。高维时只能近似,最常用的是 MCMC(Gibbs 采样、Langevin 动力学),也可以用重要性采样等方法;这个期望算起来很贵,是这类模型训练慢的主要原因。
在现代模型里的延续 :现代 Hopfield 网络的更新等价于注意力(2.12.1);连续、可微时,能量的负梯度除以温度就是 score,∇ₓ log P(x) = −∇ₓE(x)/T:Z 和 x 无关,求梯度时消掉了,score 方法正是这样绕开配分函数的。用去噪的方式学 score 再一步步采样,就是扩散模型(2.2.1)。2024 年诺贝尔物理学奖授予 Hopfield 和 Hinton,表彰的正是 Hopfield 网络和玻尔兹曼机这一支工作。
1 2 3 4 5 6 7 8 9 10 11 import itertoolsimport torchW = torch.tensor([[0. , 1. , -1. ], [1. , 0. , 0.5 ], [-1. , 0.5 , 0. ]]) states = torch.tensor(list (itertools.product([-1. , 1. ], repeat=3 ))) E = -0.5 * ((states @ W) * states).sum (1 ) for T in [5.0 , 1.0 , 0.2 ]: p = torch.softmax(-E / T, 0 ) best = states[p.argmax()].int ().tolist() print (f"温度 {T} :最可能的状态 {best} ,概率 {p.max ():.2 f} ,分布的熵 {-(p * p.log()).sum ():.2 f} " )
2.12.1 Hopfield 网络 〔图:HN〕
一句话
全连接、权重对称的二值神经元网络;用 Hebb 规则把若干模式「写进」权重,之后给一个残缺或带噪的模式,如果它落在某个存储模式的吸引域里,网络会自己演化回这个模式:按内容寻址的记忆。也可能停在伪记忆上,不保证到达距离最近的存储模式。
图中 HN 是所有单元两两相连的圆,每个单元画成「回馈输入单元」:同一组单元既是输入也是输出。Hopfield 1982 年提出。
能量与更新 :状态 s_i ∈ {−1, +1},权重对称且 w_ii = 0:
局部场 Σ_j w_ij s_j 恰好为 0 时要定一个规则:保持原状态,或者固定取 +1(下面代码的做法),都不会让能量上升。一次更新一个神经元(异步更新)时,能量只降不升,网络一定会停在某个能量的局部极小点。
Hebb 学习 (一次算完,不需要迭代训练):
Hebb 权重的用意是让每个存储的模式 ξ 成为能量地形上的一个谷底(稳定点)。能不能做到,取决于模式的数量和它们之间的相关性:模式之间的串扰太大时,有些写进去的模式本身就不再稳定。
容量与伪记忆 :经典的理论结果是 N 个神经元大约能存 0.14N 个随机模式(允许回忆结果有少量错位);要求一位不差地回忆时,容量更低,超过之后模式互相干扰、回忆迅速失败(下面代码里能看到这个断崖)。网络还会出现「伪记忆」,比如几个模式的混合、模式的反相。
双向联想记忆(BAM,Kosko 1988) :两层神经元 X、Y,存成对的模式 (x, y),W = Σ x yᵀ。给 x 能回忆出 y(y = sign(Wᵀx)),给 y 也能回忆出 x(x = sign(W y)),来回迭代直到稳定。局部场正好为 0 时 sign 要约定一个值,下面代码和 Hopfield 一样取 +1。
现代 Hopfield 网络(Ramsauer 2020,「Hopfield Networks is All You Need」) :状态变成连续值,能量函数换成 log-sum-exp 形式,在论文给的条件下(模式彼此分得足够开、β 足够大),存储容量随维度指数增长;查询离某个存储模式足够近时,一步更新就能以很小的误差取回它(模式彼此相近时,更新会收敛到几个模式的平均附近,即亚稳态);这一步更新的公式和 Transformer 的注意力完全相同:
X 的列是存储的模式(Key 和 Value),ξ 是查询(Query)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import mathimport torchtorch.manual_seed(0 ) N, P = 100 , 5 patterns = torch.randint(0 , 2 , (P, N)).float () * 2 - 1 W = patterns.T @ patterns / N W.fill_diagonal_(0 ) energy = lambda s: (-0.5 * s @ W @ s).item() def recall (s, sweeps=5 ): s = s.clone() for _ in range (sweeps): for i in torch.randperm(N): s[i] = 1.0 if W[i] @ s >= 0 else -1.0 return s probe = patterns[0 ].clone() probe[torch.randperm(N)[:25 ]] *= -1 out = recall(probe) print (f"经典 Hopfield:与原模式一致 {(out == patterns[0 ]).float ().mean():.2 f} ,能量 {energy(probe):.1 f} → {energy(out):.1 f} " )for P_ in [5 , 10 , 14 , 20 , 30 ]: ok = 0 for trial in range (40 ): pats = torch.randint(0 , 2 , (P_, N)).float () * 2 - 1 W = pats.T @ pats / N; W.fill_diagonal_(0 ) noisy = pats[0 ].clone(); noisy[torch.randperm(N)[:10 ]] *= -1 ok += torch.equal(recall(noisy), pats[0 ]) print (f" 存 {P_:2d} 个模式({P_ / N:.2 f} N):完整回忆成功 {ok / 40 :.2 f} " ) print (f" 对照理论:要一位不差地回忆出某个模式,容量约 N/(2 ln N) = {N / (2 * math.log(N)):.1 f} 个(渐近公式,只作量级参考)" )X = patterns.T xi_new = X @ torch.softmax(0.5 * X.T @ probe, dim=0 ) print (f"现代 Hopfield 一步取回:与原模式一致 {(torch.sign(xi_new) == patterns[0 ]).float ().mean():.2 f} " )xs = torch.randint(0 , 2 , (3 , 40 )).float () * 2 - 1 ys = torch.randint(0 , 2 , (3 , 20 )).float () * 2 - 1 Wb = xs.T @ ys x = xs[1 ].clone(); x[torch.randperm(40 )[:6 ]] *= -1 for _ in range (5 ): y = torch.where(Wb.T @ x >= 0 , 1.0 , -1.0 ) x = torch.where(Wb @ y >= 0 , 1.0 , -1.0 ) print ("BAM 回忆出的 y 正确:" , torch.equal(y, ys[1 ]))
这张图用字母图案单独画(正文代码用的是随机模式)。上排只存 T、X、O 三个图案,翻转 30% 像素的 T 一轮更新后就回到了 T。下排多存一个和 T、O 很像的 L,同样的输入落进了一个伪记忆:几个存储图案混在一起的状态。
常见说法与实测
常见说法 :N 个神经元的 Hopfield 网络大约能存 0.138N 个模式。
实测 :N = 100、每次翻转 10% 的像素再回忆:存 5 个(0.05N)全部完整回忆成功,10 个(0.10N)95%,14 个(0.14N)只有 65%,20 个 33%,30 个 0。存的若是彼此相似的图案,即使只存几个也会出错:另外试了 20 次,存 T、L、X、O 四个字母、翻转 10% 像素时,L 只有 75% 能完整回到 L;去掉 L、只存 T、X、O,翻转 30% 也是 20 次全部成功。
怎么理解 :0.138N 是随机模式、N 很大时的理论结果(Amit 等 1985),允许回忆结果有约 1.6% 的位出错。要求一位不差地回忆,McEliece 等 1987 给的是渐近结果:多数模式能精确回忆时容量约 N/(2 ln N),要求每一个模式都精确回忆时约 N/(4 ln N)。N = 100 代进去约 11 个,只是量级参考;有限 N 下的结果还和「成功」怎么定义、翻转多少像素有关,实测的趋势和它大致吻合。两个结论都假设各模式彼此随机、不相关;相似的图案之间串扰大得多,伪记忆更容易出现。
典型例子
联想记忆与模式补全:从残缺、带噪的图像恢复存储的原图
组合优化:Hopfield 与 Tank(1985)把旅行商问题写成能量函数,用网络的演化求近似解
现代 Hopfield 层:用于免疫组库分类(DeepRC)等需要在大量元素中检索的任务;理解注意力的一个视角
优势 :按内容寻址,给一部分线索就能恢复完整记忆;Hebb 规则一步写入,不需要反向传播;对称权重 + 异步更新保证收敛
局限 :经典版容量只有约 0.14N;会收敛到伪记忆;主要用来存储、检索和补全模式,学不到能迁移到其他任务的表征
适合的数据 :二值或 ±1 模式(黑白图、二进制编码)的存储与补全;能写成二次能量函数的组合优化;现代版适合在集合或序列里做检索
2.12.2 马尔可夫链 〔图:MC〕
一句话
一组状态和它们之间的转移概率;下一个状态只取决于当前状态,与更早的历史无关。
图里把 MC 画成所有「概率隐藏单元」两两相连的圆,作者也说明它严格说不算神经网络,放进来是因为玻尔兹曼机、Hopfield 网络和它长得像,而且采样这些模型要用到马尔可夫链(MCMC)。
平稳分布 :满足 π = πP 的分布。有限状态、不可约、非周期的链(可数无限状态时还要求正常返),从任何初始分布出发,长时间之后都会收敛到唯一的平稳分布。π 是 Pᵀ 特征值为 1 的特征向量。
和其他模型的关系 :隐马尔可夫模型(HMM)在马尔可夫链上加了「状态不可见、只能看到观测」;马尔可夫决策过程(MDP)加了动作和奖励,是强化学习的基础;MCMC 构造一条平稳分布恰好是目标分布的马尔可夫链来采样;n-gram 语言模型是「下一个词只看前 n−1 个词」的马尔可夫链,大语言模型可以看成把这个条件推到几十万个 token、并用神经网络表示转移概率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import torchP = torch.tensor([[0.7 , 0.2 , 0.1 ], [0.3 , 0.4 , 0.3 ], [0.2 , 0.3 , 0.5 ]]) torch.manual_seed(0 ) s, path = 0 , [] for _ in range (20 ): s = torch.multinomial(P[s], 1 ).item() path.append("晴云雨" [s]) print ("模拟 20 天:" , "" .join(path))evals, evecs = torch.linalg.eig(P.T) pi = evecs[:, evals.real.argmax()].real print ("平稳分布(长期晴/云/雨比例):" , (pi / pi.sum ()).numpy().round (3 ))print ("从「晴」出发走 50 步后的分布:" , (torch.tensor([1. , 0. , 0. ]) @ torch.linalg.matrix_power(P, 50 )).numpy().round (3 ))text = "the cat sat on the mat. the dog sat on the log. the cat saw the dog. " chars = sorted (set (text)); idx = {c: i for i, c in enumerate (chars)} counts = torch.ones(len (chars), len (chars)) for a, b in zip (text, text[1 :]): counts[idx[a], idx[b]] += 1 T = counts / counts.sum (1 , keepdim=True ) c, out = "t" , "t" for _ in range (50 ): c = chars[torch.multinomial(T[idx[c]], 1 ).item()] out += c print ("二元语法生成:" , out)
典型例子
PageRank:网页排名就是「随机冲浪者」马尔可夫链的平稳分布
n-gram 语言模型、输入法联想
语音识别:深度学习之前的主流是 HMM + 高斯混合模型
MCMC:贝叶斯推断、玻尔兹曼机的采样
强化学习的 MDP、排队论、金融里的信用评级转移
优势 :极简、可解释;平稳分布、首达时间等性质可以解析计算;采样便宜
局限 :只记得当前状态,长程依赖要把历史编进状态,状态数指数增长;没有学到的特征表示
适合的数据 :离散状态序列:天气、用户行为序列、文本、网页链接图;作为 HMM、MDP 等更大模型的骨架
2.12.3 RBM 受限玻尔兹曼机 〔图:RBM〕
一句话
只有「可见层—隐藏层」之间有连接、层内没有连接的玻尔兹曼机;这一限制让每层可以整层并行采样,再配上对比散度(CD)训练,就从不可用变得可用。
图中 RBM 左边一列是可见单元(回馈输入单元),右边一列是隐藏单元(概率隐藏单元),只有两列之间有连线。Smolensky 1986 年以 Harmonium 之名提出,Hinton 2002 年提出 CD 训练后流行起来。
条件独立 :给定可见层,隐藏单元之间相互独立(反过来也一样):
所以可以「整层采样隐藏层 → 整层采样可见层」交替进行(块 Gibbs 采样)。
对比散度 CD-k :负相本该从模型分布里采样(要 Gibbs 链跑到平衡),CD 改成从训练样本出发只跑 k 步(常用 k = 1):
这是一个有偏的近似,但实践中效果足够好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import torchdef bars_and_stripes (n ): """4×4 二值图:要么若干整行亮(横条),要么若干整列亮(竖条)""" bits = torch.randint(0 , 2 , (n, 4 )).float () rows = bits[:, :, None ].expand(n, 4 , 4 ) vertical = torch.rand(n) < 0.5 return torch.where(vertical[:, None , None ], rows.transpose(1 , 2 ), rows).reshape(n, 16 ) class RBM : def __init__ (self, n_vis, n_hid ): self.W = 0.01 * torch.randn(n_vis, n_hid) self.a, self.b = torch.zeros(n_vis), torch.zeros(n_hid) def p_h (self, v ): return torch.sigmoid(v @ self.W + self.b) def p_v (self, h ): return torch.sigmoid(h @ self.W.T + self.a) def cd_step (self, v0, lr=0.05 , k=1 ): ph0 = self.p_h(v0) h = torch.bernoulli(ph0) for _ in range (k): v = torch.bernoulli(self.p_v(h)) ph = self.p_h(v) h = torch.bernoulli(ph) n = v0.size(0 ) self.W += lr * (v0.T @ ph0 - v.T @ ph) / n self.a += lr * (v0 - v).mean(0 ) self.b += lr * (ph0 - ph).mean(0 ) torch.manual_seed(0 ) rbm = RBM(16 , 64 ) for step in range (10000 ): rbm.cd_step(bars_and_stripes(64 )) v_true = bars_and_stripes(500 ) mask = torch.zeros_like(v_true) mask[torch.arange(500 )[:, None ], torch.rand(500 , 16 ).argsort(1 )[:, :4 ]] = 1 v = torch.where(mask.bool (), torch.bernoulli(torch.full_like(v_true, 0.5 )), v_true) for _ in range (50 ): v_new = torch.bernoulli(rbm.p_v(torch.bernoulli(rbm.p_h(v)))) v = torch.where(mask.bool (), v_new, v_true) print (f"被遮住像素的补全准确率 {(v[mask.bool ()] == v_true[mask.bool ()]).float ().mean():.3 f} (瞎猜 0.5)" )v = torch.bernoulli(torch.full((500 , 16 ), 0.5 )) for _ in range (1000 ): v = torch.bernoulli(rbm.p_v(torch.bernoulli(rbm.p_h(v)))) v = (rbm.p_v(torch.bernoulli(rbm.p_h(v))) > 0.5 ).float () g = v.view(-1 , 4 , 4 ) legal = ((g == g[:, :, :1 ]).all (2 ).all (1 ) | (g == g[:, :1 , :]).all (1 ).all (1 )).float ().mean() print (f"500 个样本(最后一步按概率 > 0.5 取整后)里,合法的横条/竖条图案占 {legal:.2 f} ;前 6 个样本:" )for r in range (4 ): print (" " .join("" .join("█" if p else "·" for p in img.view(4 , 4 )[r].int ().tolist()) for img in v[:6 ]))
典型例子
Netflix 大奖赛(2007–2009):Salakhutdinov、Mnih、Hinton 用 RBM 做协同过滤,是获胜方案融合的模型之一
深度信念网络的逐层预训练(2.12.5)
量子多体物理:Carleo 和 Troyer(2017)用 RBM 表示量子态的波函数
早期的特征学习:MNIST 上学出笔画状的特征
优势 :二分结构让条件分布可以因式分解,整层并行采样;CD 训练比一般玻尔兹曼机快几个数量级;无监督地学特征;可以堆叠成深层模型
局限 :似然难以精确计算(Z 要靠 AIS 等方法估计),训练进度难监控;CD 有偏;大部分任务上已被 VAE、扩散模型、自监督方法取代
适合的数据 :二值或可二值化的数据:用户-物品评分矩阵、黑白图像、自旋构型
2.12.4 玻尔兹曼机 〔图:BM〕
一句话
随机版的 Hopfield 网络:单元以概率开关,网络整体服从玻尔兹曼分布;加入隐藏单元后可以学数据的高阶相关,但训练要靠漫长的 MCMC,几乎不可用。
图中 BM 是全连接的圆,一部分是可见单元(回馈输入单元),一部分是隐藏单元(概率隐藏单元)。Hinton 和 Sejnowski 1983–1985 年提出。
(这里 s 取 ±1;取 0/1 时条件概率里没有因子 2。)
学习规则 :
两项都是「两个单元同时激活的程度」的平均:s 取 0/1 时就是两者同时为 1 的概率,取 ±1 时是两者同号的程度。一个在数据上统计,一个在模型自由运行时统计。规则本身很简洁、只用局部信息,难处在第二项:模型统计量要用 Gibbs 采样跑到平衡才能估准,而且有隐藏单元时,第一项也要先把隐藏单元采样到平衡。网络稍大就慢得不可接受。RBM(2.12.3)去掉了层内连接,给定一层时另一层可以整层并行采样,正相也能精确算出来,训练才变得可行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import itertoolsimport torchtorch.manual_seed(0 ) n = 6 base = torch.randint(0 , 2 , (2000 , 3 )).float ().repeat_interleave(2 , dim=1 ) flip = torch.rand_like(base) < 0.05 data = torch.where(flip, 1 - base, base) * 2 - 1 W, b = torch.zeros(n, n), torch.zeros(n) all_states = torch.tensor(list (itertools.product([-1. , 1. ], repeat=n))) def model_stats (W, b ): E = -0.5 * ((all_states @ W) * all_states).sum (1 ) - all_states @ b p = torch.softmax(-E, dim=0 ) return (all_states.T * p) @ all_states, p @ all_states data_corr, data_mean = data.T @ data / len (data), data.mean(0 ) for step in range (500 ): model_corr, model_mean = model_stats(W, b) W += 0.1 * (data_corr - model_corr) W.fill_diagonal_(0 ) b += 0.1 * (data_mean - model_mean) print ("学到的权重(成对的单元之间是大的正数):\n" , W.numpy().round (1 ))s = torch.ones(n) samples = [] for t in range (20000 ): i = t % n s[i] = 1.0 if torch.rand(()) < torch.sigmoid(2 * (W[i] @ s + b[i])) else -1.0 samples.append(s.clone()) S = torch.stack(samples[2000 :]) print (f"采样里 s0·s1 的平均 {(S[:, 0 ] * S[:, 1 ]).mean():.3 f} ,数据里 {data_corr[0 , 1 ]:.3 f} " )print (f"采样里 s0·s2 的平均 {(S[:, 0 ] * S[:, 2 ]).mean():.3 f} ,数据里 {data_corr[0 , 2 ]:.3 f} " )
典型例子
历史意义远大于实际应用:第一批能学习隐藏表示的神经网络之一,是 RBM、DBN 和整个能量模型路线的源头
约束满足与组合优化:配合模拟退火求解
量子退火机(D-Wave)曾被用来对玻尔兹曼机做采样
优势 :有完整的概率解释,显式定义了联合分布;隐藏单元能学数据的高阶相关;学习规则局部、简洁
局限 :配分函数难以精确计算,训练要长时间 MCMC,规模稍大就不可用;实际用途已被 RBM 及之后的模型取代
适合的数据 :小规模二值数据的分布建模、组合优化;今天主要用来理解能量模型的思想
2.12.5 DBN 深度信念网络 〔图:DBN〕
一句话
把多个 RBM 一层层叠起来,逐层无监督预训练,再把权重拷进一个前馈网络用标签微调;2006 年这个办法让深层全连接网络重新训得动,是深度学习复兴的代表性工作。
图中 DBN 左边是输入(回馈输入单元),中间是交替的概率隐藏层和隐藏层,右边是输入输出匹配单元。Hinton、Osindero、Teh 2006 年提出。
作为生成模型的结构 :严格的 DBN 是一个混合模型,最顶上两层之间是无向连接,构成一个 RBM;下面各层之间是自顶向下的有向连接(sigmoid 信念网络)。生成样本时先在顶层 RBM 里做 Gibbs 采样,再一层层往下采到可见层。它和所有层之间都是无向连接的深度玻尔兹曼机(DBM)不同。下面第 3 步拷进去的前馈网络只是借用了 DBN 学到的权重,本身不是 DBN。
训练流程
用数据训练第一个 RBM
把第一个 RBM 隐藏层的激活概率当成「数据」,训练第二个 RBM;以此类推,逐层贪心地往上叠
把各层 RBM 的权重拷进一个同结构的前馈网络,顶上加分类头,用带标签的数据整体微调
历史位置 :2006 年这篇论文证明了「深层网络是可以训练的」,带起了深度学习这个名字和后来的复兴。2010–2012 年 ReLU、更好的初始化、Dropout、GPU 和大数据出现后,直接端到端训练的效果更好,逐层预训练就不再必要。但「先在大量无标签数据上预训练,再在少量有标签数据上微调」这个范式,后来在 BERT、GPT、MAE 上以新的形式回归。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import torchimport torch.nn as nndef bars_and_stripes (n ): """4×4 二值图;标签:0 = 横条,1 = 竖条""" bits = torch.randint(0 , 2 , (n, 4 )).float () rows = bits[:, :, None ].expand(n, 4 , 4 ) vertical = torch.rand(n) < 0.5 imgs = torch.where(vertical[:, None , None ], rows.transpose(1 , 2 ), rows).reshape(n, 16 ) return imgs, vertical.long() class RBM : def __init__ (self, n_vis, n_hid ): self.W, self.a, self.b = 0.01 * torch.randn(n_vis, n_hid), torch.zeros(n_vis), torch.zeros(n_hid) def p_h (self, v ): return torch.sigmoid(v @ self.W + self.b) def p_v (self, h ): return torch.sigmoid(h @ self.W.T + self.a) def cd_step (self, v0, lr=0.05 ): ph0 = self.p_h(v0); h = torch.bernoulli(ph0) v = torch.bernoulli(self.p_v(h)); ph = self.p_h(v) self.W += lr * (v0.T @ ph0 - v.T @ ph) / len (v0) self.a += lr * (v0 - v).mean(0 ); self.b += lr * (ph0 - ph).mean(0 ) torch.manual_seed(0 ) sizes = [16 , 64 , 32 ] unlabeled, _ = bars_and_stripes(5000 ) rbms, x = [], unlabeled for n_vis, n_hid in zip (sizes[:-1 ], sizes[1 :]): rbm = RBM(n_vis, n_hid) for epoch in range (30 ): for batch in x.split(64 ): rbm.cd_step(batch) rbms.append(rbm) x = rbm.p_h(x) def build (pretrained ): torch.manual_seed(1 ) layers = [] for i, (n_in, n_out) in enumerate (zip (sizes[:-1 ], sizes[1 :])): lin = nn.Linear(n_in, n_out) if pretrained: lin.weight.data, lin.bias.data = rbms[i].W.T.clone(), rbms[i].b.clone() layers += [lin, nn.Sigmoid()] return nn.Sequential(*layers, nn.Linear(sizes[-1 ], 2 )) accs = {False : [], True : []} for rep in range (5 ): torch.manual_seed(100 + rep) x_lab, y_lab = bars_and_stripes(20 ) x_te, y_te = bars_and_stripes(2000 ) for pretrained in [False , True ]: model = build(pretrained) opt = torch.optim.Adam(model.parameters(), lr=1e-3 ) for _ in range (300 ): loss = nn.functional.cross_entropy(model(x_lab), y_lab) opt.zero_grad(); loss.backward(); opt.step() accs[pretrained].append((model(x_te).argmax(1 ) == y_te).float ().mean().item()) for pretrained, name in [(False , "随机初始化直接训练" ), (True , "DBN 预训练 + 微调" )]: print (f"{name} :5 组的平均测试准确率 {sum (accs[pretrained]) / 5 :.3 f} ,各组 {[round (a, 2 ) for a in accs[pretrained]]} " )
常见说法与实测
常见说法 :先用大量无标签数据逐层预训练,再用少量标签微调,比直接从随机初始化训练好。
实测 :最初一版设置(隐藏层 32、16,微调学习率 1e-2,只抽一组 20 个标签)得到相反的结论:随机初始化 0.86,预训练 0.71。同样的设置换 5 组标签取平均,变成 0.68 对 0.71。按现在的设置(隐藏层 64、32,学习率 1e-3),5 组里预训练全部更好,平均 0.61 → 0.72。
怎么理解 :只有 20 个标签时,抽到哪 20 个样本对结果影响很大,单次比较可能得出相反的结论。预训练的收益在平均意义上存在,但幅度不大。2010 年以后有了更好的初始化和归一化,这点收益已经不值得两阶段训练的麻烦。
典型例子
2006 年:不用任何领域知识,DBN 在 MNIST 上达到当时很好的错误率
2009–2012 年语音识别:Mohamed、Dahl、Hinton 等用 DBN 预训练的深层网络做声学模型,大幅降低了错误率,微软、谷歌随后在产品中采用,这是深度学习复兴后第一个大规模工业应用(更早的卷积网络在 1990 年代已经用于支票识别)
今天主要作为「无监督预训练 → 有监督微调」范式的历史原型
优势 :历史上解决了「深层网络训不动」;能利用大量无标签数据;本身也是生成模型
局限 :流程繁琐(两阶段、超参多);有了 ReLU、BatchNorm、残差之后,直接端到端训练更好,逐层预训练不再必要
适合的数据 :无标签多、标签少的二值或 [0, 1] 数据;今天主要用于理解预训练思想的来历