2025-08-23 李沐深度学习19——长短期记忆网络LSTM
文章目录
- 1 GRU
- 1.1 核心组件:两个“门”
- 1.2 GRU 运作流程
- 1.3 GRU vs. RNN
- 2 LSTM
- 2.1 三个“门”和一个“记忆单元”
- 2.2 运作流程
- 2.3 LSTM vs. GRU
- 3 深度循环神经网络
- 3.1 构建 Deep RNN
- 3.2 公式表示
- 4 双向神经循环网络
- 4.1 网络结构
- 4.2 应用场景和局限性
1 GRU
我们之前学过的 RNN 有一个很大的问题:处理长序列时会“失忆”。比如一长串句子,RNN 读到最后,可能早就忘了开头讲了什么了。这是因为 RNN 把所有的信息都塞进一个隐藏状态里,随着序列越来越长,重要的信息很容易被淹没。
但在现实生活中,并不是所有信息都同等重要。
- 句子: 很多时候,只有几个关键词或者关键句最重要。
- 股票: 多数时候股价只是小幅震荡,但偶尔的暴跌或暴涨才是关键信息。
- 视频: 视频里大部分帧都很相似,但场景切换的那几帧往往是重要的时刻。
所以,我们需要一种机制,让神经网络能够选择性地记忆和遗忘。这就是 GRU 的核心思想——“门控”(gated)。
GRU 引入了额外的控制单元,就像是给网络装上了“门”,可以决定哪些信息要进去,哪些信息要留在外面。这就像你整理房间,把重要的东西收纳好,把不重要的东西扔掉。

1.1 核心组件:两个“门”
GRU 引入了两个关键的门来控制信息的流动,它们就像是网络里的两个“守门员”。
- 更新门(Update Gate):用 Z 表示。
- 作用: 决定当前时刻的输入信息和前一时刻的隐藏状态,有多少需要被更新到新的隐藏状态中。
- 通俗理解: 这是一个“要不要接受新信息”的门。它的值在 0 到 1 之间,如果接近 1,表示它认为当前输入的信息很重要,会尽可能多地更新隐藏状态;如果接近 0,则表示它更倾向于保留旧信息,不怎么更新。
- 重置门(Reset Gate):用 R 表示。
- 作用: 决定前一时刻的隐藏状态,有多少需要被遗忘(或者说“重置”)。
- 通俗理解: 这是一个“要不要遗忘过去”的门。它的值在 0 到 1 之间,如果接近 0,表示它要“忘记”过去的隐藏状态;如果接近 1,则表示它要完全保留过去的信息。
1.2 GRU 运作流程
下面我们结合公式和图示,一步步来看 GRU 内部到底发生了什么。
1. 门的计算
首先,我们用前一时刻的隐藏状态 Ht−1H_{t-1}Ht−1 和当前输入 XtX_{t}Xt 来计算这两个门 RtR_{t}Rt 和 ZtZ_{t}Zt。
Rt=σ(XtWxr+Ht−1Whr+br)Zt=σ(XtWxz+Ht−1Whz+bz)R_{t}=\sigma(X_{t}W_{xr}+H_{t-1}W_{hr}+b_{r})\\Z_{t}=\sigma(X_{t}W_{xz}+H_{t-1}W_{hz}+b_{z}) Rt=σ(XtWxr+Ht−1Whr+br)Zt=σ(XtWxz+Ht−1Whz+bz)
- 通俗理解: 这两个公式看起来复杂,但本质上就是两个全连接层。它们把输入 XtX_{t}Xt 和前一时刻的状态 Ht−1H_{t-1}Ht−1 拼起来,然后通过一个 Sigmoid 激活函数(σ\sigmaσ),将输出压缩到 0 到 1 的范围内。
- 注意: 这里的 WWW 和 bbb 都是可学习的权重和偏置。每个门都有自己独立的一套参数,所以模型可以根据数据自己学习如何控制信息。

2. 候选隐藏状态的计算
接下来,我们计算一个“候选隐藏状态”,用 H~t\tilde{H}_{t}H~t 表示。这个状态还不是最终的隐藏状态,你可以把它想象成一个“草稿”,用来评估当前输入 XtX_{t}Xt 能带来什么新的信息。
H~t=tanh(XtWxh+(Rt⊙Ht−1)Whh+bh)\tilde{H}_t=\tanh(X_tW_{xh}+(R_t\odot H_{t-1})W_{hh}+b_h) H~t=tanh(XtWxh+(Rt⊙Ht−1)Whh+bh)
- 通俗理解: 这个公式和 RNN 的隐藏状态计算很像,但多了一个 Rt⊙Ht−1R_t\odot H_{t-1}Rt⊙Ht−1 的操作。
- 这里的 ⊙ 表示按元素相乘(element-wise multiplication)。RtR_tRt 的值在 0 到 1 之间。
- Rt 的作用: 如果 RtR_tRt 的某个元素接近 0,那么它与 Ht−1H_{t-1}Ht−1 对应元素相乘后,结果会接近 0。这意味着我们**“重置”**(或者说忽略)了前一时刻的这部分信息。如果 RtR_tRt 的元素接近 1,那么 Ht−1H_{t-1}Ht−1 的信息就会被完整地保留下来。这个机制让模型可以根据需要,选择性地“忘记”过去的信息。

3. 最终隐藏状态的计算
现在,我们用更新门 ZtZ_tZt 来决定如何融合旧状态 Ht−1H_{t-1}Ht−1 和新的候选状态 H~t\tilde{H}_{t}H~t,得到最终的隐藏状态 HtH_tHt。
Ht=Zt⊙Ht−1+(1−Zt)⊙H~tH_t=Z_t\odot H_{t-1}+(1-Z_t)\odot\tilde{H}_t Ht=Zt⊙Ht−1+(1−Zt)⊙H~t
- 通俗理解: 这个公式是 GRU 最巧妙的地方。
- Zt⊙Ht−1Z_t\odot H_{t-1}Zt⊙Ht−1: 这是保留旧信息的部分。如果 ZtZ_tZt 的元素接近 1,表示要保留更多的旧状态信息。
- (1−Zt)⊙H~t(1-Z_t)\odot\tilde{H}_t(1−Zt)⊙H~t: 这是融入新信息的部分。如果 ZtZ_tZt 的元素接近 1,那么 (1−Zt)(1−Z_t)(1−Zt) 就接近 0,表示要少用新信息;反之,如果 ZtZ_tZt 接近 0,那么 (1−Zt)(1−Z_t)(1−Zt) 就接近 1,表示要多用新信息。
- 总结: ZtZ_tZt 就像一个“开关”:
- 当 ZtZ_tZt 接近 1 时:Ht≈1⊙Ht−1+0⊙H~t=Ht−1H_t\approx1\odot H_{t-1}+0\odot\tilde{H}_t=H_{t-1}Ht≈1⊙Ht−1+0⊙H~t=Ht−1。这意味着我们几乎不更新状态,直接把前一时刻的状态拿过来,忽略当前输入 XtX_tXt 的影响。
- 当 ZtZ_tZt 接近 0 时:Ht≈0⊙Ht−1+1⊙H~t=H~tH_t\approx0\odot H_{t-1}+1\odot\tilde{H}_t=\tilde{H}_tHt≈0⊙Ht−1+1⊙H~t=H~t。这意味着我们完全用新的候选状态来更新隐藏状态,相当于只关注当前输入 XtX_tXt 带来的信息。

1.3 GRU vs. RNN
现在你可能明白了,GRU 的本质就是在 RNN 的基础上,多了两个可学习的“门”来控制信息的流向。
- 参数数量: GRU 的参数比 RNN 多,因为它多了两个门的权重矩阵。具体来说,GRU 的参数量大约是 RNN 的三倍。
- 功能: GRU 通过门控机制,能够更有效地处理长期依赖问题,避免了 RNN 中容易出现的梯度消失或爆炸问题。
总结
- GRU 是一种特殊的循环神经网络,它通过门控机制来解决传统 RNN 难以处理长序列的问题。
- 它主要有两个“门”:重置门(Reset Gate, R)和更新门(Update Gate, Z)。
- 重置门决定了前一时刻的隐藏状态有多少需要被遗忘。
- 更新门决定了当前输入和过去隐藏状态有多少需要被更新到新的隐藏状态中。
- 这两个门都是可学习的,它们会根据数据自动调整,从而让模型能够更好地选择性地记忆和遗忘信息。

2 LSTM
LSTM 虽然诞生于上世纪 90 年代,但至今仍被广泛使用。它比 GRU 稍微复杂一些,引入了更多的“门”和另一个重要的状态——记忆单元,但核心思想是相通的:选择性地记忆和遗忘。
2.1 三个“门”和一个“记忆单元”
相比于 GRU 的两个门(更新门和重置门),LSTM 有三个门,并且引入了一个特殊的“记忆单元”。
- 遗忘门(Forgot Gate):用 F 表示。
- 作用: 决定要遗忘前一时刻的记忆单元中的哪些信息。
- 通俗理解: 这是一个“扔东西”的门。它会告诉网络,前一时刻的哪些旧信息已经不重要了,可以被“忘记”。
- 输入门(Input Gate):用 I 表示。
- 作用: 决定要保留当前输入的哪些信息,并将它们添加到记忆单元中。
- 通俗理解: 这是一个“收纳新东西”的门。它会告诉网络,当前输入 XtX_tXt 中,哪些是重要的,需要被“记住”。
- 输出门(Output Gate):用 O 表示。
- 作用: 决定要用记忆单元的哪些信息来输出当前的隐藏状态。
- 通俗理解: 这是一个“拿东西出来用”的门。它决定了最终的隐藏状态 HtH_tHt 要从记忆单元 CtC_tCt 中提取多少信息。
除了这三个门,LSTM 还多了一个核心状态:
- 记忆单元(Cell State):用 C 表示。
- 作用: 这条是贯穿整个网络的“主干道”,用来保存长期信息。它通过门的控制,可以很平滑地传递信息,而不会像 RNN 那样在每一步都被剧烈地改变。
2.2 运作流程
下面我们一步步来拆解 LSTM 的内部计算过程。
1. 门的计算
首先,我们用前一时刻的隐藏状态 Ht−1H_{t−1}Ht−1 和当前输入 XtX_tXt 来计算三个门 FtF_tFt、ItI_tIt 和 OtO_tOt。
Ft=σ(XtWxf+Ht−1Whf+bf)It=σ(XtWxi+Ht−1Whi+bi)Ot=σ(XtWxo+Ht−1Who+bo)F_t = \sigma(X_t W_{xf} + H_{t-1} W_{hf} + b_f)\\ I_t = \sigma(X_t W_{xi} + H_{t-1} W_{hi} + b_i)\\ O_t = \sigma(X_t W_{xo} + H_{t-1} W_{ho} + b_o) Ft=σ(XtWxf+Ht−1Whf+bf)It=σ(XtWxi+Ht−1Whi+bi)Ot=σ(XtWxo+Ht−1Who+bo)
- 通俗理解: 和 GRU 一样,这三个公式都是全连接层加上 Sigmoid 激活函数,输出的值都在 0 到 1 之间。每个门都有一套独立的参数 WWW 和 bbb。

2. 候选记忆单元的计算
接下来,我们计算一个“候选记忆单元”,用 C~t\tilde{C}_tC~t 表示。这个单元用来评估当前输入 XtX_tXt 带来的新信息。
C~t=tanh(XtWxc+Ht−1Whc+bc)\tilde{C}_t=\tanh(X_tW_{xc}+H_{t-1}W_{hc}+b_c) C~t=tanh(XtWxc+Ht−1Whc+bc)
- 通俗理解: 这个计算和 RNN 的隐藏状态计算很像,都是一个全连接层加上 tanh 激活函数。它代表了当前时刻,有哪些新的信息值得被“记忆”。

3. 最终记忆单元的更新
这是 LSTM 的核心步骤。我们用遗忘门 FtF_tFt 和输入门 ItI_tIt 来更新记忆单元 Ct−1C_{t−1}Ct−1,得到新的记忆单元 CtC_tCt。
Ct=Ft⊙Ct−1+It⊙C~tC_t=F_t\odot C_{t-1}+I_t\odot\tilde{C}_t Ct=Ft⊙Ct−1+It⊙C~t
- 通俗理解: 这就像是给记忆单元进行“大扫除”和“添置新物”。
- Ft⊙Ct−1F_t\odot C_{t-1}Ft⊙Ct−1: 这是遗忘旧信息的部分。如果 FtF_tFt 的元素接近 0,那么旧的记忆单元 Ct−1C_{t-1}Ct−1 的相应信息就会被“遗忘”。
- It⊙C~tI_t\odot\tilde{C}_tIt⊙C~t: 这是添置新信息的部分。如果 ItI_tIt 的元素接近 1,那么新的候选记忆单元 C~t\tilde{C}_tC~t 的相应信息就会被“记住”。
- 与 GRU 的区别: LSTM 的遗忘门 FtF_tFt 和输入门 ItI_tIt 是相互独立的。这意味着,你可以决定“遗忘”旧信息的同时,也可以决定“不添加”新信息;或者说,你也可以既保留旧信息,又添加新信息。而 GRU 里的更新门 ZtZ_tZt 和 (1−Zt)(1−Z_t)(1−Zt) 是联动的,你保留旧信息多,新信息就必然添加得少。

4. 最终隐藏状态的计算
最后,我们用输出门 OtO_tOt 来决定如何从记忆单元 CtC_tCt 中提取信息,生成最终的隐藏状态 HtH_tHt。
Ht=Ot⊙tanh(Ct)H_t=O_t\odot\tanh(C_t) Ht=Ot⊙tanh(Ct)
- 通俗理解:
- tanh(Ct)\tanh(C_t)tanh(Ct): 这里的 tanh 激活函数是一个很重要的步骤。因为记忆单元 CtC_tCt 经过多次加法,它的值可能会变得很大。tanh 函数的作用就是把 CtC_tCt 的值压缩回 -1 到 1 之间,防止数值过大。
- Ot⊙tanh(Ct)O_t\odot\tanh(C_t)Ot⊙tanh(Ct): 输出门 OtO_tOt 就像一个“过滤器”,决定了有多少信息能从记忆单元中被“过滤”出来,成为最终的隐藏状态。如果 OtO_tOt 的元素接近 0,那么隐藏状态 HtH_tHt 也会接近 0,相当于**“重置”**了当前时刻的输出。

2.3 LSTM vs. GRU
特性 | LSTM | GRU |
---|---|---|
状态 | 有两个状态:隐藏状态 HtH_tHt 和记忆单元 CtC_tCt。 | 只有一个隐藏状态 HtH_tHt。 |
门 | 有三个门:遗忘门、输入门、输出门。 | 有两个门:重置门、更新门。 |
复杂度 | 结构更复杂,参数更多,计算量更大。 | 结构相对简单,参数更少,计算更快。 |
效果 | 都能有效解决长序列依赖问题,实际应用中两者效果通常很接近。 | GRU 因为结构更简洁,在某些场景下可能训练更快。 |
3 深度循环神经网络
在深度学习中,让模型“深”起来是一种非常常见的做法。
- 增强非线性能力: 每一层隐藏层都增加了一次非线性变换。浅层网络可能只能学习简单的模式,而多层网络能通过层层非线性转换,捕捉更复杂、更抽象的特征。
- 避免过拟合: 如果你把一个隐藏层做得特别“宽”(神经元很多),它可能会过度拟合训练数据。而多层网络则是将能力分散到多个“薄”层中,每一层只学习一点点非线性,累积起来就能实现复杂的功能。
所以,深度循环神经网络(Deep RNN)就是让循环神经网络变“深”,其实和多层感知机(MLP)是类似的,就是多加几个隐藏层。

3.1 构建 Deep RNN
想象一下,一个浅层 RNN 就像一个单层楼的工厂:输入原材料,经过一层处理,就得到最终产品。
而深度 RNN 就像一个多层楼的工厂:
- 第一层: 接收最原始的输入(比如句子里的第一个词),进行第一次处理,然后把结果传递给楼上。
- 第二层: 接收楼下的结果,进行第二次处理,再传递给下一层。
- …
- 顶层: 接收下面所有层处理过的结果,进行最后一次处理,然后得到最终的输出。
在每个时间步,这个多层结构都会运行一遍。
用图示来理解:
- 垂直方向: 代表不同的层。从最底下的输入层,一层一层往上,直到最顶层的输出层。
- 水平方向: 代表不同的时间步。从左到右,网络依次处理序列中的每一个元素。

3.2 公式表示
假设我们有一个深度为 LLL 的循环神经网络。
-
第 1 层: 只接收外部输入 XtX_tXt 和前一时刻本层的隐藏状态 Ht−1(1)H_{t-1}^{(1)}Ht−1(1)。
Ht(1)=F(Xt,Ht−1(1))H_t^{(1)}=F(X_t,H_{t-1}^{(1)}) Ht(1)=F(Xt,Ht−1(1))
这里的 FFF 就是你选择的循环神经网络单元(可以是 RNN、GRU 或 LSTM)。 -
第 j 层(j>1): 不再接收外部输入,而是接收下面一层的隐藏状态 Ht(j−1)H_t^{(j-1)}Ht(j−1) 作为输入,同时接收前一时刻本层的隐藏状态 Ht(j)H_t^{(j)}Ht(j)。
Ht(j)=F(Ht(j−1),Ht−1(j))H_t^{(j)}=F(H_t^{(j-1)},H_{t-1}^{(j)}) Ht(j)=F(Ht(j−1),Ht−1(j))
可以发现,除了第一层,后面的每一层都把下面一层的输出作为自己的输入。 -
最终输出: 我们通常只用最顶层(第 LLL 层)的隐藏状态来计算最终的输出。
Ot=线性层(Ht(L))O_t=\text{线性层}(H_t^{(L)}) Ot=线性层(Ht(L))

4 双向神经循环网络
无论是 RNN、GRU 还是 LSTM,它们都有一个共同点:只能从前向后看。就像你读书一样,只能一页一页往后读。但在很多任务中,我们希望模型能够同时看到过去和未来的信息。
举个例子,假设你要做一个完形填空:
- 句子: “I am very hungry. I can eat a _.”
- 要填的词: 你可能觉得填 “sandwich”、“pizza” 都可以。
- 如果增加上下文: “I am so hungry, I could eat a _ horse.”
现在,你是不是一下就知道了要填 “horse”?
这个例子说明,要做出最准确的判断,有时候光看前面的信息是不够的,未来的信息也同样重要。双向循环神经网络就是为了解决这个问题而设计的。
它的核心思想是:一个隐藏层里其实有两个独立的 RNN(或 GRU/LSTM),一个从前往后读序列,另一个从后往前读。

4.1 网络结构
想象一下,双向 RNN 就像一个双向列车,有两个车头:
- 前向传播层(Forward Layer): 这是一个正常的 RNN,它从序列的第一个元素开始,依次向后处理,并计算一个隐藏状态。
- 后向传播层(Backward Layer): 这是另一个独立的 RNN,但它从序列的最后一个元素开始,依次向前处理,并计算另一个隐藏状态。

运作流程:
- 处理输入: 在每个时间步 ttt,输入 XtX_tXt 会同时进入两个独立的 RNN 层。
- 前向计算: 前向层根据 XtX_tXt 和前一时刻的前向隐藏状态 Ht−1→H_{t-1}^{\rightarrow}Ht−1→ 计算出当前的前向隐藏状态 Ht→H_{t}^{\rightarrow}Ht→。
- 后向计算: 后向层根据 XtX_tXt 和后一时刻的后向隐藏状态 Ht+1←H_{t+1}^{\leftarrow}Ht+1← 计算出当前的后向隐藏状态 Ht←H_{t}^{\leftarrow}Ht←。
- 合并状态: 最后,我们将这两个方向的隐藏状态 拼接(concatenate) 起来,形成一个完整的隐藏状态 HtH_tHt。这个新的状态就同时包含了过去和未来的信息。
- 输出: 基于这个包含了双向信息的隐藏状态 HtH_tHt,我们可以计算最终的输出。

在代码实现上双向 RNN 的思路:
- 复制序列: 将输入序列复制一份。
- 正常处理: 将原始序列输入到第一个 RNN 层,得到前向的隐藏状态序列。
- 反向处理: 将另一个序列反转后,输入到第二个 RNN 层,得到后向的隐藏状态序列。
- 反转输出: 将后向层的隐藏状态序列再次反转回来,使其与前向序列在时间步上对齐。
- 拼接: 将两个对齐的隐藏状态序列在维度上进行拼接。
这样,在任何一个时间步,你得到的隐藏状态都包含了从序列开始到当前点的信息,以及从序列末尾到当前点的信息。
4.2 应用场景和局限性
优点:
- 特征提取: 双向 RNN 最大的优势是对整个序列进行特征提取。它能为序列中的每个元素生成一个“上下文丰富”的表示,这个表示包含了全局信息。这对于理解句子的语义、进行机器翻译或情感分析等任务非常有用。
- 完形填空: 就像我们开头的例子一样,它非常适合处理需要上下文信息来做判断的任务。
局限性:
- 不适合预测未来: 双向 RNN 不能用于实时地预测下一个词或下一个事件。因为在推理时,你根本不知道未来的信息。为了计算当前时刻的后向隐藏状态,你需要未来的输入,但在预测任务中,未来的输入是未知的。
- 需要整个序列: 双向 RNN 需要拿到整个序列后才能开始计算。这意味着它不适合处理流式数据(stream data)或需要实时响应的应用。
