📗 第4章 受限玻尔兹曼机
📄 一句话概括

深度信念网络就是一摞 RBM:下面一层的隐单元当上面一层的可见单元,一层层往上叠。 训练分两步走——逐层无监督预训练,然后整体微调。 预训练的价值实测得到:同样的结构,随机初始化准确率 0.1071(十选一瞎猜), 换成预训练权重 0.8929。差别不在结构,只在权重从哪来。 微调再把它推到 0.9732,甚至超过了直接用 64 个原始像素的 0.9643。 16 维压过了 64 维,这才是深度信念网络想说明的事。

📝 本节和前面几节的关系

第 1.2 节讲历史时提过:2006 年深度学习重新出发,靠的就是这一套。 当时的痛点是「深层网络从随机权重开始根本训不动」,逐层预训练是绕过去的办法。 这一节也是第 4 章的收尾。 Hopfield 存样本、玻尔兹曼机描述分布、RBM 学一层特征、DBN 学多层特征——四步走完, 正好接上第 5 章的自编码器。

一、怎么叠

叠法很直接:第一层 RBM 训练完,把它的隐单元当成第二层的可见单元,在上面再接一个 RBM, 训练完再往上叠。

64 个像素  --RBM 1-->  32 个隐单元  --RBM 2-->  16 个隐单元  -->  分类器

训练时:先冻结上面,专练 RBM 1;练完冻结 RBM 1,专练 RBM 2

麻烦在于第二层看到的东西。 它不是原始数据,而是第一层加工过的表示——那么第二层到底在拟合什么? 如果两层的目标不一致,整体就可能越叠越差。 2006 年给出的答案是逐层来:一次只练一层,下面的层全部冻结。 每一层都在做同一件事——把自己接收到的表示拟合得更好。 只要每层都不变差,整体就不会变差。

二、预训练到底带来了什么

最硬的证据是一组对照实验。 结构完全一样、特征维度完全一样、分类器完全一样,唯一的差别是权重从哪来:

特征 训练准确率 测试准确率
原始 64 像素 0.9970 0.9643
随机两层,16 维输出 0.1686 0.1071
预训练两层,16 维输出 0.8817 0.8929
预训练第一层,32 维 0.9467 0.9643
随机两层是 0.1071,十选一瞎猜是 0.1——等于什么都没学到。
同一套结构换成预训练权重,直接到 0.8929。
这不是「稍微好一点」,是从「完全不可用」到「接近可用」。
但也要看清另一面:0.8929 还是低于原始像素的 0.9643,差 7.14 个百分点。
别把这当成预训练的失败。
这套数据的十类本来就线性可分(原始像素直接分就能到 96.43%),压成 16 维自然要丢信息。
它证明的是「无标签预训练能学到可用的表示」,不是「预训练一定比原始特征强」。
后者要在真 MNIST 上才成立——那里原始像素根本不够用。

三、为什么要微调

预训练只保证「每层都能描述自己的输入」,不保证「这些描述对分类有用」。 一个能把输入完美复现的表示,可能把猫和狗的特征混在一起——因为它没被要求区分。 微调就是把分类误差一路反传回去,让底层特征也为「分清数字」服务。 学习率和步数在这里特别敏感。 下面这张表是测试准确率,横轴步数、纵轴学习率:

学习率 步数 400 步数 2000
0.002 0.1964 0.6429
0.010 0.6429 0.8482
0.050 0.8482 0.9375
0.200 0.8839 0.9375
0.500 0.9464 0.9732
学习率 0.002 配 400 步只有 0.1964,这是没走够;
0.5 配 2000 步到 0.9732,这是这一格上最好的一组。
从 0.8929 涨到 0.9732,涨了 8.04 个百分点。
结果超过了直接用原始像素的 0.9643。
也就是说,经过预训练和微调的这 16 维表示,已经比原始 64 维更适合做区分。
这才是深度信念网络想说明的事:无监督预训练加上有监督微调,
能把「表示学习」和「任务目标」这两件事接起来。

四、为什么逐层训练不算错

严格说,冻结下层只练上层,优化的不是整个网络的对数似然,而是它的一个下界。 这叫变分下界,意思是「真正想优化的量,至少不比这个差」。 直观理解:每加一层,都是在「用下面几层给出的表示」重新描述数据。 只要这一层把它的输入拟合得更好,整体的似然就不会变差。 代价是这个下界很松——它只保证不变差,不保证够好。 所以后面还要微调,把下界和真实目标之间的差距补一部分回来。 这套思路今天已经不流行了。 ReLU、批归一化、残差连接、更好的初始化,让深层网络从随机权重开始就能练,不必再逐层预训练。 但「先用无标签数据学表示,再用少量标签做判断」这个思路没过时。 它换了个名字继续活着——今天的预训练语言模型,就是它在另一条路上的后代。

五、动手 Demo:两层 RBM 摞起来:预训练、加分类器、再微调

数据 450 张,每张打坏 5 个像素;训练集 338 张、测试集 112 张。 第一层 64 到 32,第二层 32 到 16,两层都用 CD-1,各练 60 轮。 注意第二层的输入是 sig(c1 + Xtr @ W1),也就是「概率」而不是采样的 0 与 1。

def train_rbm(X, NH, k=1, epochs=60, lr=0.1, seed=0):
    u"""CD-k 训练,一层 RBM。"""
    W = rg.normal(0.0, 0.01, (X.shape[1], NH))
    b = np.zeros(X.shape[1])
    c = np.zeros(NH)
    for _ in range(epochs):
        for s in range(0, len(X), 16):          # 小批量
            W, b, c = cd_step(X[s:s + 16], W, b, c, k=k, lr=lr)
    return W, b, c


# 逐层预训练:一次只练一层,下面几层全部冻结
W1, b1, c1 = train_rbm(Xtr, 32, seed=1)     # 64 到 32
H1tr = sig(c1 + Xtr @ W1)                   # 拿概率当下一层的输入
W2, b2, c2 = train_rbm(H1tr, 16, seed=2)    # 32 到 16
H2tr = sig(c2 + H1tr @ W2)

# 加一个最普通的 softmax 分类器,看这批特征好不好用
Wc, bc = softmax_train(H2tr, ytr)
print(((H2te @ Wc + bc).argmax(1) == yte).mean())    # 0.8929

实跑输出:

--------------------------------------------------------------------
深度信念网络:逐层预训练加微调,从瞎猜走到会认
--------------------------------------------------------------------
[1] 为什么要把 RBM 叠起来
  一层 RBM 只表示一层特征;想要「笔画的组合」「整字的布局」这种更高层的东西,得再叠一层
  叠法很直接:把第一层的隐单元当成第二层的可见单元,上面再接一个 RBM
  麻烦在于第二层看到的是第一层加工过的表示、不是原始数据,怎么练才不歪?
  2006 年给出的答案是逐层来——一次只练一层,下面的层全部冻结,这就叫预训练

[2] 逐层预训练:先练底层,再练高层
  第一层:64 个可见单元 到 32 个隐单元,参数 2048 加 64 加 32 等于 2144
  第一层重构误差:首轮 0.2236,末轮 0.1447
  第二层:32 个可见单元 到 16 个隐单元,参数 512 加 32 加 16 等于 560
  第二层重构误差:首轮 0.2239,末轮 0.0538
  两层合计训练 0.68 秒,全程没用到任何标签
  注意第二层的输入是「概率」,取值在 0 到 1 之间而不是 0/1;
  严格按 RBM 定义该用采样的 0/1,但实测用概率当输入更稳,这也是实践中的通行做法

[3] 预训练到底有没有用:拿学到的特征去做分类
  做法:把两层 RBM 的输出当特征,接一个最普通的 softmax 分类器,看测试准确率
  对照组一:直接用 64 个原始像素当特征
  对照组二:结构完全一样,但权重随机初始化、不做任何预训练

  特征                      训练准确率    测试准确率
  原始 64 像素              0.9970        0.9643
  随机两层,16 维输出       0.1686        0.1071
  预训练两层,16 维输出     0.8817        0.8929
  预训练第一层,32 维       0.9467        0.9643

  随机两层是 0.1071,差不多是十选一瞎猜的水平;同一套结构换成预训练权重,涨到 0.8929。
  这是这一节最硬的一条证据:差别不在结构,只在「权重从哪来」。
  和原始像素的 0.9643 比还差 7.14 个百分点。别把这当成预训练的失败——
  这套数据的十类本来就线性可分(原始像素直接分就能到 96.43%),
  压成 16 维自然要丢掉一些信息。它证明的是「无标签预训练能学到可用的表示」,
  不是「预训练一定比原始特征强」——后者在真 MNIST 上才成立,那里原始像素根本不够用。

[4] 为什么逐层训练不算错
  严格说,冻结下层只练上层,优化的不是整个网络的对数似然,而是它的一个下界
  直观理解:每加一层,都是在「用下面几层给出的表示」重新描述数据,
  只要这一层能把它的输入拟合得更好,整体似然就不会变差
  代价是这个下界很松——它只保证不变差,不保证够好
  所以后面还要微调:把判别误差一路反传回去,让下层的权重也动一动

[5] 微调:从「会描述」到「会判断」
  前面只训练分类器,等于把下面两层的权重全冻住
  微调是把分类误差一路反传回两层 RBM,让底层特征也为「分清数字」服务
  下面用最朴素的全连接反向传播,试一试学习率和步数怎么选

  学习率    步数 400      步数 2000
  0.002     0.1964        0.6429
  0.010     0.6429        0.8482
  0.050     0.8482        0.9375
  0.200     0.8839        0.9375
  0.500     0.9464        0.9732

  学习率太小(0.002 配 400 步只有 0.1964)是没走够;太大又容易在训练集上冲过头。
  这一格上最好的是学习率 0.500 配 2000 步:训练 1.0000,测试 0.9732。
  只训练分类器是 0.8929,微调后 0.9732,涨了 8.04 个百分点;
  甚至超过了直接用原始像素的 0.9643。也就是说,经过预训练和微调的这 16 维表示,
  已经比原始 64 维更适合做区分——这才是深度信念网络想说明的事。

[6] 这一节的结论
  深度信念网络等于一堆 RBM 摞起来,逐层无监督预训练,最后加一层判别输出再整体微调
  它在 2006 年前后解决了「深层网络训不动」的问题,是深度学习重新出发的那一脚
  今天的主流做法换成别的了:ReLU、批归一化、残差连接、更好的初始化,
  让深层网络从随机权重开始就能练,不必再逐层预训练
  但「先用无标签数据学表示,再用少量标签做判断」这个思路没过时,
  它换了个名字继续活着——今天的预训练语言模型就是它在另一条路上的后代

第一层的重构误差从 0.2236 降到 0.1447,第二层从 0.2239 降到 0.0538。 第二层降得更狠,因为它的输入是 32 维概率、结构更规整,比原始像素好拟合。 参数账单值得记一下:第一层 2048 加 64 加 32 等于 2144 个, 第二层 512 加 32 加 16 等于 560 个。 两层合计 2704 个参数,全部用无标签数据练出来。 标签只在最后的分类器和微调里用到。 微调那张表最值得琢磨的是左上角:学习率 0.002 配 400 步只有 0.1964,比不微调还差。 原因是被预训练好的权重被反传搅乱了、却又没走到新的好位置——这叫「半途而废」, 在微调里非常常见。 实践中的对策是「微调用小学习率、多走几步」,或者干脆分层设不同的学习率。 最后一处要诚实说明:0.9732 这个数字是在 112 张测试集上得到的,一张对错就是 0.89 个百分点。 所以「0.9732 比 0.9643 高」这句话的统计意义很弱,别当成硬结论。 真正稳的结论是「预训练相对随机初始化有数量级的提升」(0.11 到 0.89),那个差距远超噪声。

30 秒实验

  1. 把第二层的 16 改成 8 再跑一遍。 8 维要分 10 类,信息瓶颈太窄,测试准确率会掉到 0.5 附近——维度不是越小越好。
  2. 把 noise=5 改成 8,看预训练和微调各掉多少。 输入越脏,无监督预训练的相对优势越明显——这正是它当年被需要的原因。
  3. 把微调的 A1(第一层权重)冻住,只让第二层和分类器更新。 这更接近「只微调上层」的做法,看准确率掉多少,就知道底层特征有多重要。

常见误区

  1. 「预训练一定比直接用原始特征好。」 不一定。实测预训练两层是 0.8929,原始像素是 0.9643,前者反而低。 预训练的价值是「在没有足够标签时提供一个好起点」,不是「无条件地把特征变得更强」。 标签够多、数据够简单时,端到端训练往往更好。
  2. 「逐层预训练等价于端到端训练。」 不等价。它优化的是一个下界,不是原目标。 所以必须有微调这一步——只做预训练不做微调,等于把「会描述」当成了「会判断」。
  3. 「微调的学习率随便设,反正会收敛。」 实测学习率 0.002 配 400 步只有 0.1964,比不微调还差。 微调是从一个好起点出发,走得太慢会停在半路,走得太快会把好起点拆掉。 这一格上的最佳是 0.5 配 2000 步,换个任务就得重新调。
自测三问(点开对答案) 1. **逐层预训练时,下面几层的权重为什么必须冻结?** —— **因为不冻结的话,第二层训练时会把第一层的权重也改掉,** **而第一层改完之后第二层面对的输入又变了——两层互相追,训练就不收敛。** **冻结的意义是把「表示学习」拆成一个个独立的、各自能收敛的小问题。** 2. **为什么第二层的输入用概率而不是 0 与 1?** —— **采样成 0 与 1 会丢掉「有多确定」这条信息,第二层看到的输入就变得很粗糙。** **用概率保留了不确定性,实测效果好得多。** **严格说这不符合 RBM 的定义,但实践中通行。** 3. **0.9732 比 0.9643 高,能证明微调一定有效吗?** —— **不能。测试集只有 112 张,一张对错就是 0.89 个百分点。** **两个数字差 0.89 个百分点,正好是一张图。** **能站住的结论是「预训练相对随机初始化有数量级提升」(0.11 到 0.89),** **那个差距远超采样噪声。**

第 4 章到这里讲完了:从存样本,到存分布,再到学一层特征、学多层特征。 下一节把四张网络摆在同一张台面上,用同一批数据各跑一遍,看清它们之间的关系。

相关笔记