NLP学习笔记08:循环神经网络(RNN)------从基础 RNN 到 LSTM 与 GRU
作者:Ye Shun
日期:2026-04-17
一、前言
循环神经网络(Recurrent Neural Network,RNN)是一类专门处理序列数据的神经网络。所谓序列数据,指的是数据之间存在先后顺序、上下文依赖或时间关系的数据,例如:
- 文本序列
- 语音信号
- 时间序列
- DNA 序列
与传统的前馈神经网络不同,RNN 具有一种"记忆"能力。它在处理当前输入时,不只看当前时刻的数据,还会参考之前时刻的隐藏状态(Hidden State),从而建模序列中的上下文依赖关系。
如果把普通神经网络理解为"只看当前这一帧"的模型,那么 RNN 更像是"边读边记"的模型。比如人在读句子时,理解当前词的含义,往往依赖前面已经读过的内容。RNN 模仿的正是这种过程。
这篇笔记将围绕以下几个问题展开:
- RNN 的核心思想是什么
- 标准 RNN 是如何工作的
- 为什么会出现梯度消失和长期依赖问题
- LSTM 和 GRU 是如何改进 RNN 的
- 双向 RNN 在什么场景下更有优势
二、RNN 的核心思想
RNN 的关键在于"循环连接(Recurrent Connection)"。它在每一个时间步都会把上一步的隐藏状态传递到下一步,因此当前输出不仅依赖当前输入,也依赖之前的上下文。
1. 传统神经网络与 RNN 的区别
传统前馈神经网络
前馈神经网络通常假设每个输入样本彼此独立。例如图像分类中,模型处理某张图片时,不需要依赖上一张图片。
RNN
RNN 不同,它假设数据之间存在顺序依赖。每一步计算时,都会把上一步的隐藏状态带到当前步骤,形成一种"记忆链条"。
因此,RNN 在每个时间步的输入可以理解为:
- 当前输入
x_t - 上一步隐藏状态
h_{t-1}
当前状态又会生成新的隐藏状态 h_t,供后续时间步继续使用。
2. 一个直观类比
可以把 RNN 理解成一个在阅读时会不断做笔记的人。
- 读到当前词时,他不仅看这个词本身
- 还会结合前面记下的内容一起理解
例如:
他打开了......
读到"打开了"时,人脑会自动利用前文推测下一个词可能是"门""书""电脑"等。这种对上下文的依赖,就是 RNN 想建模的东西。
三、RNN 的工作机制
标准 RNN 在每个时间步 t 的计算通常包括三步:
- 接收当前输入
x_t - 接收前一时刻隐藏状态
h_{t-1} - 计算当前隐藏状态
h_t,并进一步得到输出y_t
其常见形式为:
text
h_t = f(W_hh * h_{t-1} + W_xh * x_t + b_h)
y_t = g(W_hy * h_t + b_y)
其中:
x_t:当前输入h_{t-1}:上一时刻隐藏状态h_t:当前隐藏状态y_t:当前输出W_xh、W_hh、W_hy:权重矩阵f、g:激活函数,如tanh、sigmoid、softmax
1. 一个简单的 RNN 单元示例
下面是一个简化版 RNN 单元实现:
python
import numpy as np
class SimpleRNN:
def __init__(self, input_size, hidden_size):
self.Wx = np.random.randn(hidden_size, input_size)
self.Wh = np.random.randn(hidden_size, hidden_size)
self.b = np.zeros((hidden_size, 1))
def forward(self, x, h_prev):
h_next = np.tanh(np.dot(self.Wx, x) + np.dot(self.Wh, h_prev) + self.b)
return h_next
这个例子已经体现了 RNN 的核心:当前隐藏状态由"当前输入 + 过去状态"共同决定。
2. 参数共享
RNN 的一个重要特点是参数共享。也就是说,同一组权重会在所有时间步重复使用。
这有两个好处:
- 可以处理任意长度的序列
- 参数量不会随着序列长度增加而线性膨胀
也正因为这样,RNN 很自然地适合处理文本、语音等变长输入。
四、RNN 的优缺点
1. 优点
RNN 的核心优势在于它天然适合序列建模:
- 能处理变长序列
- 能利用历史上下文
- 参数共享,模型结构统一
- 理论上可以记住较长的历史信息
在早期 NLP、语音识别和时间序列建模中,RNN 曾经非常重要。
2. 缺点
不过,标准 RNN 也有明显局限:
- 难以捕捉长期依赖
- 存在梯度消失和梯度爆炸问题
- 时间步之间强依赖,难以并行计算
- 序列较长时训练效率较低
其中最关键的问题,就是长期依赖和梯度传播问题。
五、RNN 的长期依赖问题
虽然标准 RNN 理论上可以记住任意长度的信息,但在实际训练中,它往往只能较好利用较近的上下文,而难以记住很早之前的信息。
1. 梯度消失
在反向传播时,梯度需要跨越很多时间步不断连乘。如果权重和激活函数的组合使得梯度逐步变小,那么越往前的时间步,梯度就越接近 0,模型就很难学到长期依赖。
2. 梯度爆炸
与之相反,如果梯度在反向传播过程中不断放大,就会出现梯度爆炸,训练不稳定甚至发散。
3. 为什么这很重要
例如在句子:
我昨天在书店买的那本关于深度学习的书非常有意思。
如果模型要理解"书非常有意思"中的"书"究竟指的是什么,就需要跨较长距离记住前文信息。标准 RNN 在这种场景下往往表现不佳。
这正是 LSTM 和 GRU 被提出的背景。
六、长短期记忆网络(LSTM)
LSTM(Long Short-Term Memory)是 RNN 的一个重要改进版本,专门设计来缓解长期依赖问题。
它最核心的思想是:不要让隐藏状态在每一步都被完全重写,而是引入更稳定的记忆单元,并用门控机制控制信息流动。
1. LSTM 的核心结构
LSTM 中最关键的组成部分包括:
| 组件 | 作用 |
|---|---|
| 遗忘门 | 决定保留多少旧记忆 |
| 输入门 | 决定接收多少新信息 |
| 输出门 | 决定输出多少内部状态 |
| 记忆单元 | 存储长期状态 |
2. 为什么 LSTM 更强
标准 RNN 每一步都在直接更新隐藏状态,而 LSTM 引入了专门的记忆单元 c_t,让信息可以沿着相对更稳定的路径传播。
这样做的好处是:
- 可以有选择地记住长期信息
- 可以有选择地遗忘无关内容
- 梯度传播路径更稳定
3. LSTM 单元的简化实现
python
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class LSTMCell:
def __init__(self, input_size, hidden_size):
self.hidden_size = hidden_size
self.W = np.random.randn(4 * hidden_size, input_size + hidden_size)
self.b = np.zeros((4 * hidden_size, 1))
def forward(self, x, h_prev, c_prev):
combined = np.vstack((h_prev, x))
gates = np.dot(self.W, combined) + self.b
hs = self.hidden_size
f_gate = sigmoid(gates[:hs])
i_gate = sigmoid(gates[hs:2 * hs])
o_gate = sigmoid(gates[2 * hs:3 * hs])
c_candidate = np.tanh(gates[3 * hs:])
c_next = f_gate * c_prev + i_gate * c_candidate
h_next = o_gate * np.tanh(c_next)
return h_next, c_next
这个实现虽然简化,但已经体现了 LSTM 的核心思想:通过门控机制控制记忆。
七、门控循环单元(GRU)
GRU(Gated Recurrent Unit)可以看作 LSTM 的简化版本。它在保留门控思想的同时,减少了参数数量,使模型更紧凑。
1. GRU 的核心结构
GRU 主要包含:
| 组件 | 作用 |
|---|---|
| 更新门 | 决定保留多少旧信息 |
| 重置门 | 决定如何组合旧信息与当前输入 |
| 候选隐藏状态 | 生成新的候选状态 |
GRU 没有像 LSTM 那样显式区分隐藏状态和记忆单元,而是把结构做得更简洁。
2. GRU 单元示例
python
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class GRUCell:
def __init__(self, input_size, hidden_size):
self.hidden_size = hidden_size
self.Wz = np.random.randn(hidden_size, input_size + hidden_size)
self.Wr = np.random.randn(hidden_size, input_size + hidden_size)
self.Wh = np.random.randn(hidden_size, input_size + hidden_size)
self.bz = np.zeros((hidden_size, 1))
self.br = np.zeros((hidden_size, 1))
self.bh = np.zeros((hidden_size, 1))
def forward(self, x, h_prev):
combined = np.vstack((h_prev, x))
z = sigmoid(np.dot(self.Wz, combined) + self.bz)
r = sigmoid(np.dot(self.Wr, combined) + self.br)
candidate_input = np.vstack((r * h_prev, x))
h_candidate = np.tanh(np.dot(self.Wh, candidate_input) + self.bh)
h_next = (1 - z) * h_prev + z * h_candidate
return h_next
3. GRU vs LSTM
两者可以做一个直观比较:
| 特性 | GRU | LSTM |
|---|---|---|
| 参数数量 | 较少 | 较多 |
| 训练速度 | 通常更快 | 通常更慢 |
| 显式记忆单元 | 无 | 有 |
| 门控数量 | 2 个 | 3 个 |
| 适用场景 | 小数据或轻量任务常见 | 长依赖和复杂任务更常见 |
一般来说:
- 如果更看重模型简洁和训练速度,可以优先试 GRU
- 如果更看重长期记忆能力,可以优先试 LSTM
八、双向 RNN(Bi-RNN)
标准 RNN 只能利用"过去到现在"的上下文,但很多任务中,理解当前词时不仅依赖前文,也依赖后文。
例如在命名实体识别中,一个词是不是组织机构,有时需要结合前后词共同判断。
1. 双向 RNN 的思想
双向 RNN 包含两个方向的循环层:
- 前向层:从左到右处理序列
- 反向层:从右到左处理序列
最后把两个方向的输出拼接或融合起来,得到更完整的上下文表示。
2. 为什么双向 RNN 有用
它的优势在于:
- 同时利用过去和未来信息
- 对词性标注、命名实体识别、语音识别等任务更有效
3. 双向 LSTM 示例
现代应用中,双向结构通常配合 LSTM 或 GRU 使用。例如:
python
from tensorflow.keras.layers import Bidirectional, LSTM
model.add(Bidirectional(LSTM(64)))
这表示构建一个双向 LSTM 层,让模型同时读取正向和反向上下文。
九、RNN、LSTM、GRU 的应用场景
虽然近年来 Transformer 已经成为主流,但 RNN 系列模型仍然非常值得理解,因为它们在序列建模史上占据核心位置,也仍然适用于一些场景。
1. 自然语言处理
典型应用包括:
- 语言建模
- 文本分类
- 词性标注
- 命名实体识别
- 机器翻译(早期 Seq2Seq)
2. 语音识别
语音信号天然是时间序列,RNN 和 LSTM 在早期语音识别系统中非常常见。
3. 时间序列预测
例如:
- 股票价格预测
- 电力负载预测
- 传感器数据预测
4. 生物信息学
例如 DNA、RNA、蛋白质序列分析等,都会涉及序列建模。
十、RNN 的局限与 Transformer 的兴起
虽然 RNN 在序列建模中非常经典,但它有两个结构性弱点:
1. 难以并行
因为每个时间步都依赖前一个时间步,所以训练和推理往往是串行的。
2. 长距离依赖仍然难
即便 LSTM 和 GRU 已经缓解了问题,但当序列特别长时,依然不如后来的自注意力模型更灵活。
也正因为这样,Transformer 后来逐渐取代 RNN 成为 NLP 中更主流的架构。不过,理解 RNN 仍然非常重要,因为:
- 它是现代序列模型的重要起点
- 它帮助我们理解"记忆"和"时序依赖"到底是什么
- LSTM / GRU 的门控思想对后续很多模型设计都有启发
十一、学习建议
如果你刚开始学神经网络中的序列模型,我建议按下面顺序来:
- 先理解标准 RNN 的状态传递机制
- 再理解为什么会有梯度消失和长期依赖问题
- 接着学习 LSTM 的遗忘门、输入门、输出门
- 再学习 GRU 如何做结构简化
- 最后再去理解双向结构和 Transformer
这样会更容易把"序列建模"的演化逻辑串起来。
十二、总结
循环神经网络(RNN)是处理序列数据的重要模型,它通过循环连接把历史信息带入当前计算,从而具备建模上下文的能力。
从方法演进来看:
- 标准 RNN 奠定了序列建模基础
- LSTM 通过门控机制缓解长期依赖问题
- GRU 在保留性能的同时简化了结构
- 双向 RNN 则进一步利用了前后文信息
虽然 Transformer 在很多 NLP 场景中已经成为主流,但 RNN、LSTM 和 GRU 依然是理解深度学习序列建模不可绕过的基础。