LSTM 与 GRU:门控机制解决长依赖
LSTM 与 GRU:门控机制如何解决长时依赖
在自然语言处理、时间序列预测等序列建模任务中,我们常常需要捕捉时间步之间相隔较远的依赖关系。例如,生成一段文字时,可能需要“回忆”前文很远处提到的人物或事件。经典的循环神经网络(RNN)在面对这种“长时依赖”时显得力不从心,而 长短期记忆网络(LSTM) 和 门控循环单元(GRU) 正是为了解决这一痛点而设计的。本教程将带你直观理解它们的工作原理、结构差异,并通过代码快速上手。
为什么简单 RNN 会“遗忘”遥远的信息?
简单 RNN 的核心是一个反复使用的隐藏状态 $h_t$:
$$ h_t = \tanh(W_{xh} x_t + W_{hh} h_{t-1} + b_h) $$
在反向传播时,梯度需要通过时间步连乘多个权重矩阵 $W_{hh}$。如果 $W_{hh}$ 的特征值大于 1,梯度会指数级爆炸;如果小于 1,则会指数级衰减到零。梯度消失是导致长时依赖难以学习的主因——遥远的输入对当前状态的影响微乎其微,网络“记不住”太久以前的事。
LSTM 和 GRU 通过巧妙的 门控机制 主动控制信息的流动,让网络可以选择性地保留或遗忘信息,从而为梯度提供一条相对稳定的“高速公路”。
LSTM:三重门控的记忆单元
LSTM 在普通 RNN 的基础上引入了一个细胞状态(cell state) $C_t$,它像一条传送带贯穿全部时间步,信息可以几乎没有衰减地在上面流动。三个门结构负责向细胞状态添加或移除信息。
LSTM 的核心组件
- 细胞状态 $C_t$:长期记忆的载体,线性变换为主,梯度容易传递。
- 隐藏状态 $h_t$:短期记忆,作为当前时间步的输出,并参与下一时间步的计算。
- 遗忘门 $f_t$:决定要从细胞状态中丢弃哪些旧信息。
- 输入门 $i_t$ 与 候选值 $\tilde{C}_t$:决定要添加哪些新信息到细胞状态。
- 输出门 $o_t$:决定将细胞状态的哪些部分输出为隐藏状态。
逐步拆解 LSTM 的运算
-
遗忘门——选择性“忘记” 通过拼接当前输入 $x_t$ 和上一隐藏状态 $h_{t-1}$,经过 sigmoid 函数输出一个 0~1 之间的向量,与上一个细胞状态 $C_{t-1}$ 逐元素相乘。接近 0 表示“完全忘记”,接近 1 表示“完全保留”。
$$ f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + b_f) $$
-
输入门——选择性“记忆” 由两部分协作:
- 输入门 $i_t$ 决定哪些值需要更新。
- 候选细胞状态 $\tilde{C}_t$ 生成新的候选信息(使用 $\tanh$ 激活,输出值在 -1~1 之间)。
$$ i_t = \sigma(W_i \cdot [h_{t-1}, x_t] + b_i) $$ $$ \tilde{C}t = \tanh(W_C \cdot [h{t-1}, x_t] + b_C) $$
-
更新细胞状态 旧状态乘以遗忘门丢弃一部分信息,再加上输入门控制的新信息:
$$ C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t $$
这一步是 LSTM 的关键:遗忘门和输入门的线性组合让细胞状态的更新变得平滑,梯度在反向传播时可以直接通过 $C_t$ 流向 $C_{t-1}$,极大缓解了梯度消失。
-
输出门——选择性“展示” 输出门控制当前细胞状态的哪些部分会被输出为隐藏状态 $h_t$。
$$ o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + b_o) $$ $$ h_t = o_t \odot \tanh(C_t) $$
$h_t$ 被用于当前时间步的预测,以及传入下一时间步。
小结:LSTM 通过细胞状态作为长期记忆,用三个门精确控制信息的遗忘、写入和读取,从而能够有效地捕捉数百步以外的依赖关系。
GRU:更精简的门控循环单元
GRU 可以看作是 LSTM 的一种高效变体,它将遗忘门和输入门合并成一个更新门,并将细胞状态与隐藏状态合并。因此结构更简单,参数更少,训练通常更快,在很多任务上能达到与 LSTM 相当的性能。
GRU 的两个门
-
更新门 $z_t$ 决定多少过去的信息需要保留,多少新的信息需要写入。类似于 LSTM 中遗忘门和输入门的组合作用。
$$ z_t = \sigma(W_z \cdot [h_{t-1}, x_t] + b_z) $$
-
重置门 $r_t$ 决定如何将新的输入与过去的记忆结合。当 $r_t$ 接近 0 时,模型会“忘记”之前的状态,只关注当前输入,就像是在阅读一个新句子的开头。
$$ r_t = \sigma(W_r \cdot [h_{t-1}, x_t] + b_r) $$
GRU 的状态更新
-
候选隐藏状态 $\tilde{h}_t$ 将经过重置门筛选后的 $h_{t-1}$ 与当前输入 $x_t$ 结合,生成新信息的候选值。
$$ \tilde{h}t = \tanh(W_h \cdot [r_t \odot h{t-1}, x_t] + b_h) $$
-
最终隐藏状态 $h_t$ 更新门 $z_t$ 在线性插值中决定:是保留更多旧状态 $h_{t-1}$,还是加入更多新候选状态 $\tilde{h}_t$。
$$ h_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t $$
这与 LSTM 的 $C_t$ 更新形式类似,但 GRU 没有独立的细胞状态,所有记忆都放在同一个隐藏状态中。当 $z_t$ 接近 1 时,旧信息几乎完全被替换;当 $z_t$ 接近 0 时,旧信息几乎完整地传递下去。
GRU 与 LSTM 的直观对比
| 特性 | LSTM | GRU |
|---|---|---|
| 记忆结构 | 细胞状态 + 隐藏状态 | 只有隐藏状态 |
| 门数量 | 3 个(遗忘、输入、输出) | 2 个(更新、重置) |
| 控制遗忘与输入的机制 | 单独的门分别控制 | 更新门统一控制1-z_t和z_t |
| 参数数量 | 较多(4 组权重矩阵) | 较少(3 组权重矩阵) |
| 训练速度 | 相对较慢 | 更快 |
| 实际表现 | 功能强大,更灵活 | 在许多中等规模任务上与 LSTM 持平 |
一般情况下,如果数据量很大、任务复杂,优先考虑 LSTM;如果希望模型更简洁、训练更快,GRU 是很好的起点。两者都可以有效地缓解长时依赖问题。
代码示例:用 Keras 构建 LSTM 和 GRU
以下示例展示如何用 TensorFlow/Keras 快速搭建一个简单的序列分类模型。我们以 IMDB 影评情感分类为例(序列长度 500,词嵌入维度 32)。
import tensorflow as tf
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing import sequence
# 参数设置
max_features = 10000 # 词汇表大小
maxlen = 500 # 截断/填充长度
embedding_dim = 32
# 加载数据
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)
# 构建 LSTM 模型
model_lstm = tf.keras.Sequential([
tf.keras.layers.Embedding(max_features, embedding_dim, input_length=maxlen),
tf.keras.layers.LSTM(64, dropout=0.2, recurrent_dropout=0.2),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model_lstm.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
model_lstm.summary()
# 训练(仅示意,可适当减少 epoch 快速验证)
history_lstm = model_lstm.fit(x_train, y_train,
batch_size=128,
epochs=3,
validation_split=0.2)
若要将模型换成 GRU,只需将 LSTM 层替换为 GRU 层即可,其他代码完全相同:
model_gru = tf.keras.Sequential([
tf.keras.layers.Embedding(max_features, embedding_dim, input_length=maxlen),
tf.keras.layers.GRU(64, dropout=0.2, recurrent_dropout=0.2),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model_gru.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
model_gru.fit(x_train, y_train,
batch_size=128,
epochs=3,
validation_split=0.2)
小贴士:在实际项目中,你还可以堆叠多层 LSTM/GRU,或添加 Bidirectional 包装器捕获上下文信息。
总结与关键要点
- 长时依赖问题 源于梯度消失,简单 RNN 难以学习间隔较远的依赖关系。
- LSTM 使用细胞状态和三个门(遗忘门、输入门、输出门)精确控制信息的增删,为梯度提供顺畅通路。
- GRU 将遗忘门与输入门融合为更新门,并合并细胞状态与隐藏状态,结构更精简,在许多场景下效果相当。
- 两者的核心思想都是通过 0~1 之间的门控信号线性调节信息流,从而选择性地保留历史信息,解决长期记忆难题。
- 实践中可根据任务规模、训练资源在 LSTM 和 GRU 之间灵活选择。
掌握了 LSTM 和 GRU 后,你就可以自信地应对各种序列建模挑战了。下一步,不妨尝试将它们应用于你手头的文本、音频或时间序列数据,感受门控机制的实际威力。