📕 第6章 深度学习的应用
📄 一句话概括

GRU 只有两道门:重置门 r 管「上一步的记忆要不要先清掉再看」, 更新门 z 管「上一步和这一步各占多少」。它连 cell 通道都省了,直接用 h 自己当传送带。 实测同一套超参:RNN 98 参、GRU 258、LSTM 338。GRU 是 RNN 的 263%,是 LSTM 的 76%。 同一个延迟复述任务 800 轮:RNN 一直 0.6909、GRU 第 600 轮才掉到 0.0021、 LSTM 第 200 轮就到 0.0092。都能学会,但快慢差很多。

📝 本节和前面几节的关系

6.2 的 LSTM 有四道门。 GRU 把「遗忘门」和「输入门」合成一个更新门——因为它们本来就在做同一件事:决定新旧各占多少。 6.4 开始用这些结构做实际任务,GRU 和 LSTM 在那里都会被用到。 第 5 章算过卷积的参数账,这一节算循环网络的账,方法一样:数矩阵的尺寸。

一、两道门怎么分工

更新门 z 的用法是:h 等于(1 减 z)乘上一步的 h,加 z 乘候选。z 接近 0 就是全留旧的, 接近 1 就是全换新的。 重置门 r 用在候选里:候选的隐状态那一项乘的是 r 乘上一步的 h。r 接近 0 等于「先把旧记忆清空, 只看这一步的输入」。 注意 z 和 LSTM 的 f 用法是反的。LSTM 的 f 是「留多少旧的」,GRU 的 z 是「要多少新的」。 读别人的代码时,这个符号最容易搞反。 为什么能省掉一条 cell 通道? 因为(1 减 z)和 z 加起来正好是 1,这一步天然就是一个凸组合。旧状态和新候选,按比例混合就行, 不用再单独存一份。

二、参数账

模型 门数 参数量 相对 RNN
RNN 0 98 100%
GRU 2 258 263%
LSTM 3 338 345%

算法很直接: RNN 一个 8×8 的 Wh;GRU 是 8×24,三道同宽的块(z、r、候选);LSTM 是 8×32,四道门各一块。 所以 GRU 的隐层权重是 LSTM 的四分之三。加上输入和输出那两块两者一样,总体就落在 76%。 24 对 32,是这两节最该记住的一个数字比。它决定了 GRU 在手机上跑得动、 LSTM 在服务器上更稳这类经验说法背后的账。

三、同一个任务,三种表现

模型 参数 第 200 轮 第 600 轮 第 800 轮 测试
RNN 98 0.6909 0.6909 0.6909 0.5000
GRU 258 0.6909 0.6893 0.0021 1.0000
LSTM 338 0.0092 0.0008 0.0005 1.0000

RNN 那一行三列损失完全一样。它不是「收敛慢」,是根本没动。 GRU 在第 600 轮之前也几乎没动(0.6909 到 0.6893,掉了一点点), 然后突然掉下去。这类任务上它有一个开窍点。 LSTM 是一路平滑下来的:0.0092 到 0.0008 到 0.0005。 同样能学会,轨迹差这么多,说明门控结构影响的是优化的难易程度,不是表达能力的上限。 这一点很要紧: 看到「A 模型比 B 模型强」,先分清是「B 学不会」还是「B 学得慢」。后者加训练轮数就能追平。

四、怎么选

没有定论。GRU 参数少、训练快;LSTM 表达力强一点、在长序列上更稳。 实践里通常两个都试。参数只差 24%,试一遍的成本很低,比猜划算。 还有一层:GRU 少一条 cell 通道,反向时少一条路径。这让它更难「卡住」,也让它更难「记住很久」。 同一件事的两面。 如果序列很长、依赖跨得很远,LSTM 那条独立的 c 通道还是更占便宜。如果序列短、要求实时, GRU 的参数和计算量更讨喜。

五、再往后

这两个结构统治了 2014 到 2017 年的序列建模。 机器翻译、语音识别、文本生成,当时的标准配置就是多层 LSTM。 2017 年之后 Transformer 用注意力替代了循环(6.6),但门控的思路留下来了——GLU 变体、 各种「按比例混合两路信息」的设计,骨子里还是这一套。 所以这一节不只是学一个老模型。「用一个 0 到 1 的开关去混合两路信息」这个手法, 在今天的网络里到处都是。

六、动手 Demo:三道门并成两道

把 RNN、GRU、LSTM 放在同一个任务、同一套超参下比:先比参数量,再比 800 轮里的损失轨迹。 三个模型都跑同样的 300 条训练样本、100 条测试样本。

import numpy as np
import _rnn43 as R

rg = np.random.default_rng(7)
Xtr, Ytr, ytr = R.recall_data(300, 8, rg)     # 延迟复述任务
Xte, Yte, yte = R.recall_data(100, 8, rg)

for cls, nm in ((R.RNN, "RNN"), (R.GRU, "GRU"), (R.LSTM, "LSTM")):
    m = cls(1, 8, 2, np.random.default_rng(3))
    hist, a1, a2 = R.train(m, Xtr, Ytr, ytr, Xte, Yte, yte,
                           iters=800, lr=0.5, every=100)
    print(nm, m.nparam(), hist[1][1], hist[5][1], hist[-1][1], a2)

# GRU 的前向:两道门,一次算出 z、r 和候选
zc = X[:, t, :] @ m.Wx + h @ m.Wh + m.b
z = sig(zc[:, :H])                            # 更新门
r = sig(zc[:, H:2 * H])                       # 重置门
hn = np.tanh(X[:, t, :] @ m.Wx[:, 2 * H:]
             + (r * h) @ m.Wh[:, 2 * H:] + m.b[2 * H:])
h = (1.0 - z) * h + z * hn

实跑输出:

--------------------------------------------------------------------
GRU:把三道门并成两道
--------------------------------------------------------------------

  GRU 只留两道门:重置门 r 决定「上一步的记忆要不要先清掉再看」,
  更新门 z 决定「上一步和这一步各占多少」。

  少了一道门,参数量就少一截。同一套超参(输入 1 维、隐层 8、输出 2 类):

  模型      门数    参数量      相对 RNN
  ----------------------------------------
  RNN       0       98          100%
  GRU       2       258         263%
  LSTM      3       338         345%

  参数量是这么来的:**RNN 一个 8×8 的 Wh;GRU 是 8×24(三道同宽的块:z、r、候选);**
  **LSTM 是 8×32(四道门各一块)。**
  所以 GRU 的隐层权重是 LSTM 的四分之三,加上输入和输出,总体是 76%。

  同一个「记住第 1 位」的任务,三个模型各跑 800 轮:

  模型    参数    第 200 轮   第 600 轮   第 800 轮   测试
  ------------------------------------------------------------
  RNN     98      0.6909      0.6909      0.6909      0.5000
  GRU     258     0.6909      0.6893      0.0021      1.0000
  LSTM    338     0.0092      0.0008      0.0005      1.0000

  **RNN 那一行三列损失完全一样,一直在 0.6909 上不动**——它压根没开始学。
  GRU 和 LSTM 都到了 1.0000,但过程不一样:
  **LSTM 第 200 轮就下来了,GRU 拖到第 600 轮才突然开窍。**
  这类任务上两种门控结构谁更好,没有定论。
  **GRU 参数少、训练快;LSTM 表达力强一点、在长序列上更稳。**实践里通常两个都试。

参数表三行:98、258、338。 GRU 是 LSTM 的 76%,不是 75%——因为输入和输出那两块三者一样,稀释了比例。 这个 76% 可以手算:隐层那三块 24 对 32,都是 0.75; 但 Wo 和 bo 这 18 个参数两边完全一样,它们把比例往 1 拉了一点,所以是 0.76 而不是 0.75。 训练曲线那一张表是这一节的重点。RNN 三列一个数,说明它一步没走。 GRU 第 200 轮 0.6909、第 600 轮 0.6893。 前 600 轮它也只掉了 0.0016,和 RNN 几乎一样——然后第 800 轮突然到 0.0021。 这种「长时间不动、然后突然掉下去」的形状,在门控网络里很常见。原因是门一开始都在 0.5 附近, 梯度虽然能传回去,但信号很弱;一旦某个门的偏置被推到有利的一侧,整条路一下就通了。 LSTM 没有这个开窍点,它是一路平滑下来的。多一条 cell 通道,等于多一条不经过门的捷径。 所以「谁更好」这个问题要分场景问: 要快要省,GRU;要稳要长,LSTM。实测这一张表只能说明「这个任务上 LSTM 收敛更快」, 说明不了谁的上限更高。

30 秒实验

  1. 把隐层从 8 加到 16,三者的参数量比例会变吗?先算 Wh 的尺寸再验。
  2. 如果把重置门 r 整个置 0,候选里还剩哪些项?
  3. 为什么 GRU 的 z 和 LSTM 的 f 用法是反的?两者能互相换算吗?

常见误区

  1. 以为 GRU 是「LSTM 的简化版所以更差」。参数少 24%,很多任务上效果一样,训练还更快。
  2. 以为 0.6909 是「训练不动」的通用值。它是二分类瞎猜的交叉熵 ln2。 换成 10 类,这个值会变成 ln10。
  3. 以为门控结构解决了「记不住很久」。它把衰减率变成可学的,但仍然是衰减。想记更久, 还是得靠注意力那种「直接连过去」的结构。
自测三问(点开对答案) 1. GRU 的两道门各管什么? —— 重置门决定上一步的记忆要不要先清掉再看这一步的输入;更新门决定新旧状态各占多少比例。 2. 同样隐层 8、输入 1、输出 2,三个模型的参数量是多少? —— RNN 98、GRU 258、LSTM 338。差别全在隐层权重:8×8、8×24、8×32。 3. 为什么 GRU 和 LSTM 在这个任务上的收敛轨迹差这么多? —— 门控结构影响的是优化难易程度,不是表达能力上限。 GRU 要等一个开窍点,LSTM 有独立的 cell 通道,是一路平滑下降的。

到这里三种循环单元都见过了。6.4 把它们用在第一个真正的任务上:一个字符一个字符地预测下一个。

相关笔记