在了解了 RNN 的基本架构及其固有的缺陷后,本节将探讨两种经典的 RNN 改进方案——长短期记忆网络 (LSTM) 与门控循环单元 (GRU),并剖析它们是如何通过精巧的结构设计来克服长距离依赖这一挑战的。
常规 RNN 的问题是它内部状态的更新方式是“粗暴”的。每一步的新信息都会与旧信息(隐藏状态)无差别地混合,并通过权重矩阵
与 RNN 只有一个隐藏状态
(1)细胞状态(Cell State,
(2)隐藏状态(Hidden State,
LSTM 通过门控单元,来控制细胞状态
LSTM 中的“门”是一种让信息选择性通过的结构,设计灵感来源于数字电路中的逻辑门。它的实现非常简单,就是一个以 Sigmoid 为激活函数的全连接层。这个全连接层的输入通常是当前时间步的输入
从另一个角度看,门控机制也是为了解决权重冲突问题。其中输入门保护细胞状态不受无关输入的干扰,输出门则保护其他单元不受当前细胞状态中无关记忆的干扰。LSTM 内部署了三个这样的门,来精确控制信息的流动。
一个 LSTM 单元在 tanh 层,计算出新的细胞状态
为了方便后续公式的表述,首先将当前输入
这种拼接操作是一种常见的计算优化。将两个向量拼接后再进行一次矩阵乘法,与对两个向量分别进行矩阵乘法然后相加,其结果是等价的。例如,
$W \cdot [h_{t-1}, x_t]$ 等价于$W_h \cdot h_{t-1} + W_x \cdot x_t$ ,其中$W$ 被相应地拆分为$W_h$ 和$W_x$ 两部分。这样做可以利用深度学习框架中优化过的大矩阵乘法操作,提升计算效率。
在深入公式之前,可以将上图中的计算模块与公式对应起来,以便理解信息流,图中各符号的含义如下:
- 每一个
σ(Sigmoid) 符号都对应一个门的计算,即遗忘门、输入门和输出门。 -
tanh符号有两个,分别负责生成候选记忆($\tilde{c}_t$),以及对细胞状态$c_t$ 进行处理,将其值压缩到 [-1, 1] 区间以计算最终的隐藏状态$h_t$ 。 -
⊙(圆圈)符号代表按元素乘法,这是门控机制发挥作用的关键。 -
+(圆圈)符号代表按元素加法,用于更新细胞状态。
LSTM 的第一步是决定我们从细胞状态中丢弃什么信息。这个决定由被称为“遗忘门”的 Sigmoid 层完成。它会审视
1997 年提出的原始 LSTM 结构中并没有遗忘门,这一机制是由 Gers 等人在 2000 年引入的 2。
下一步是决定我们在细胞状态中存储什么新信息。这由两部分共同完成:
(1)输入门:首先,一个 Sigmoid 层(即图中的“输入门”)决定了更新哪些值。
(2)候选记忆:然后,一个 tanh 层(即图中的“候选值”模块)创建一个新的候选记忆向量
现在,可以更新细胞状态了,我们需把旧状态
最后,我们需要决定输出什么。输出将基于我们的细胞状态,但会是一个过滤后的版本。输出的生成分为两步:
(1)输出门:一个 Sigmoid 层(“输出门”)决定了细胞状态的哪些部分将被输出。
(2)计算隐藏状态:将刚刚更新的细胞状态 tanh 函数(将其值规范化到 -1 和 1 之间),并将其与输出门
这个
现在,回到最初的问题。LSTM 是如何通过这套复杂的门控机制来缓解梯度消失问题的?关键在于细胞状态
其中,细胞状态的更新公式为 $c_t = (f_t \odot c_{t-1}) + (i_t \odot \tilde{c}t)$。可以看到,$c_t$ 对 $c{t-1}$ 的偏导数直接就是遗忘门
这个关系非常关键。它表明,从
由此,梯度的“高速公路”得以建立,从序列末端到开端的梯度传递主要取决于一系列遗忘门
所以,我们不说 LSTM 解决了梯度消失问题,而是极大地缓解了它。因为即使
为了更深刻地理解 LSTM 内部复杂的信息流动,可以像实现 RNN 一样,基于公式,用 NumPy 手写一个 LSTM 的前向传播过程。这里我们同样实现一个不含偏置项的简化版 LSTM,计算公式如下:
-
遗忘门:
$f_t = \sigma(U_f x_t + W_f h_{t-1})$ -
输入门:
$i_t = \sigma(U_i x_t + W_i h_{t-1})$ - 候选记忆: $\tilde{c}t = \tanh(U_c x_t + W_c h{t-1})$
-
细胞状态更新:
$c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t$ -
输出门:
$o_t = \sigma(U_o x_t + W_o h_{t-1})$ -
隐藏状态更新:
$h_t = o_t \odot \tanh(c_t)$
为了将上述公式转化为可执行的代码,我们可以遵循以下步骤:
(1)初始化: 我们第一步需要初始化两个零向量 h_prev 和 c_prev,分别作为处理序列开始前的“短期记忆”和“长期记忆”。
(2)逐帧处理: 接着,使用 for 循环遍历序列中的每一个时间步,对每个时间步的输入进行处理。
(3)核心计算: 在循环内部,严格遵循 LSTM 的四个步骤进行计算。我们首先要计算遗忘门 f_t,决定从旧的细胞状态 c_prev 中忘记多少信息;然后计算输入门 i_t 和候选记忆 c_tilde_t,准备要写入的新信息;随后通过公式 c_t = f_t * c_prev + i_t * c_tilde_t,结合遗忘和记忆操作,得到新的细胞状态 c_t;最后计算输出门 o_t,并结合 tanh(c_t) 生成新的隐藏状态 h_t。
(4)状态更新: 在每一步计算结束后,执行 h_prev, c_prev = h_t, c_t,将当前计算出的状态传递给下一个时间步,完成“循环”过程。
具体的代码实现如下:
def manual_lstm_numpy(x_np, weights):
U_f, W_f, U_i, W_i, U_c, W_c, U_o, W_o = weights
B_local, T_local, _ = x_np.shape
h_prev = np.zeros((B_local, H), dtype=np.float32)
c_prev = np.zeros((B_local, H), dtype=np.float32)
steps = []
# 按时间步循环
for t in range(T_local):
x_t = x_np[:, t, :]
# 1. 遗忘门
f_t = sigmoid(x_t @ U_f + h_prev @ W_f)
# 2. 输入门与候选记忆
i_t = sigmoid(x_t @ U_i + h_prev @ W_i)
c_tilde_t = np.tanh(x_t @ U_c + h_prev @ W_c)
# 3. 更新细胞状态
c_t = f_t * c_prev + i_t * c_tilde_t
# 4. 输出门与隐藏状态
o_t = sigmoid(x_t @ U_o + h_prev @ W_o)
h_t = o_t * np.tanh(c_t)
steps.append(h_t)
h_prev, c_prev = h_t, c_t
outputs = np.stack(steps, axis=1)
return outputs, h_prev, c_prev通过这个实现,我们可以直观地看到 LSTM 是如何通过门控机制,在每个时间步对信息流进行控制的。
在实际的深度学习框架(如 PyTorch、TensorFlow)中,为了提高计算效率,LSTM 的实现通常会进行一项优化。回顾 LSTM 的计算过程,遗忘门、输入门、候选记忆和输出门都对拼接向量
-
$f_t$ :$W_f \cdot [h_{t-1}, x_t] + b_f$ -
$i_t$ :$W_i \cdot [h_{t-1}, x_t] + b_i$ - $\tilde{c}t$ : $W_c \cdot [h{t-1}, x_t] + b_c$
-
$o_t$ :$W_o \cdot [h_{t-1}, x_t] + b_o$
这相当于对同一个输入进行了四次独立的线性层计算。为了优化,框架会将这四个权重矩阵和偏置项在内部进行拼接,将与输入
GRU 由 Cho 等人在 2014 年提出 3。它最初是在 RNN Encoder-Decoder 框架下被提出的,是为了解决统计机器翻译中的短语表示问题。实验表明,这种结构能够很好地捕捉短语的语义和句法结构,并且相比 LSTM 更易于训练。
GRU 对 LSTM 做了两大核心改动,一个是合并细胞状态与隐藏状态,也就是不再区分细胞状态
一个 GRU 单元如图 3-3 在
其计算过程可以分解为以下四步:
(1)重置门($r_t$)
决定如何将新输入
(2)更新门($z_t$)
控制前一时刻的状态信息
(3)候选记忆($\tilde{h}_t$)
计算当前时间步的候选隐藏状态。这个计算受到了重置门
(4)最终隐藏状态($h_t$)
结合更新门
标准的 LSTM 结构本身已经非常强大,但在其发展过程中,研究者们也提出了一些有趣的变体,用于简化计算或增强性能。其中两种著名的变体是“窥孔连接”和“耦合的输入/遗忘门”。
在标准 LSTM 中,三个门(遗忘、输入、输出)的决策依据仅来自当前输入
实验表明,带有窥孔连接的 LSTM 在处理需要精确计时和计数的任务(如学习生成具有特定时间间隔的脉冲序列)时,性能显著优于标准 LSTM。公式上的变化体现是在计算每个门时,额外加入一个与细胞状态相关的项:
$f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + V_f \odot c_{t-1} + b_f)$ $i_t = \sigma(W_i \cdot [h_{t-1}, x_t] + V_i \odot c_{t-1} + b_i)$ $o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + V_o \odot c_t + b_o)$
其中
该变体的思想是遗忘旧信息和写入新信息是紧密耦合的两个决策。应该遗忘多少旧信息,恰恰是因为准备写入等量的新信息。基于此,它将输入门和遗忘门合并为一个决策。不再单独计算输入门
这种方式不仅使得模型逻辑更直观,还减少了模型的参数量。Greff 等人在大规模实验研究中验证了这一点,发现 CIFG 可以在不降低模型性能的前提下有效减少计算开销 5。除了验证 CIFG 的有效性外,该研究还对 LSTM 的架构进行了详尽的探索,得出了一些对工程实践极具价值的结论。例如,核心组件中的遗忘门和输出激活函数是 LSTM 中最关键的组件,移除它们会显著降低性能;各超参数独立性较高,这意味着可以单独调整学习率等参数,而无需进行复杂的组合搜索;在在线随机梯度下降训练中动量作用有限,无论是好是坏,动量对 LSTM 的性能影响都微乎其微。
要做哦,别偷懒 😇
- 在前面的练习中,我们构建了一个基于全连接层的文本分类模型。现在,尝试将其改造为使用 LSTM 网络结构,以更好地捕捉文本中的序列信息。(可以参考基于 LSTM 的文本分类)
Footnotes
-
Hochreiter, S., & Schmidhuber, J. (1997). Long short-term memory. Neural Computation, 9(8), 1735-1780. ↩
-
Gers, F. A., Schmidhuber, J., & Cummins, F. (2000). Learning to forget: Continual prediction with LSTM. Neural Computation, 12(10), 2451-2471. ↩
-
Cho, K., Van Merriënboer, B., Gulcehre, C., Bahdanau, D., Bougares, F., Schwenk, H., & Bengio, Y. (2014). Learning phrase representations using RNN encoder-decoder for statistical machine translation. Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP), 1724-1734. ↩
-
Gers, F. A., Schmidhuber, J. (2000). Recurrent nets that time and count. Proceedings of the IEEE-INNS-ENNS International Joint Conference on Neural Networks (IJCNN), 3, 189-194. ↩
-
Greff, K., Srivastava, R. K., Koutník, J., Steunebrink, B. R., & Schmidhuber, J. (2017). LSTM: A search space odyssey. IEEE Transactions on Neural Networks and Learning Systems, 28(10), 2222-2232. ↩