学习目标与重点
💡 目标:掌握 RNN 核心原理、经典变体结构,以及文本序列任务的实战开发流程。 💡 重点:理解 RNN 的循环计算机制,学会使用 TensorFlow/Keras 搭建基础 RNN 与 LSTM 模型,完成文本分类任务。
核心原理
为什么需要 RNN
传统的前馈神经网络(如 CNN、全连接网络)输入和输出相互独立,无法处理序列数据的上下文关联。自然语言、语音、时间序列等数据的核心特点是当前时刻信息与之前时刻紧密相关。循环神经网络通过引入隐藏状态存储历史信息,有效捕捉这种依赖关系。
RNN 的循环计算机制
RNN 的核心是循环核,本质是带有自连接的神经元。它在每个时间步接收输入数据和上一时刻的隐藏状态,计算当前输出和新隐藏状态。
计算过程分为三步:
- 初始化隐藏状态 $h_0$,通常设为全零向量。
- 对每个时间步 $t$,计算当前隐藏状态:$h_t = \tanh(W_{xh}x_t + W_{hh}h_{t-1} + b_h)$。
- 根据隐藏状态计算输出:$y_t = W_{hy}h_t + b_y$。
⚠️ 注意:基础 RNN 存在梯度消失或爆炸问题,难以捕捉长序列依赖,实际应用中更多使用其变体。
import tensorflow as tf
from tensorflow.keras.layers import SimpleRNN
# 定义基础 RNN 层
# units: 隐藏状态维度,return_sequences: 是否返回所有时间步输出
rnn_layer = SimpleRNN(units=64, return_sequences=True, input_shape=(10, 20))
# 模拟输入:批次大小 32,序列长度 10,特征维度 20
input_seq = tf.random.normal(shape=(32, 10, 20))
# 执行 RNN 计算
output_seq = rnn_layer(input_seq)
print("RNN 输出形状:", output_seq.shape)
# 输出形状 (32, 10, 64)
梯度问题与改进方向
基础 RNN 在反向传播中梯度随时间步指数级衰减或膨胀,导致无法学习长距离依赖。研究者提出了长短期记忆网络(LSTM) 和 门控循环单元(GRU),通过门控机制控制信息遗忘和更新,缓解梯度消失。
经典变体——LSTM
LSTM 由 Hochreiter & Schmidhuber 于 1997 年提出,是最常用的 RNN 变体。它通过输入门、遗忘门和输出门协同作用,实现对历史信息的选择性记忆。
门控机制解析
- 遗忘门:决定丢弃哪些历史信息(sigmoid 输出 0~1)。
- 输入门:决定加入哪些新信息(筛选 + tanh 生成候选)。
- 输出门:决定输出哪些信息作为隐藏状态。
- 细胞状态:核心记忆单元,存储长序列历史。
代码实现
from tensorflow.keras.layers import LSTM
# 定义 LSTM 层
lstm_layer = LSTM(units=128, return_sequences=False, return_state=True, input_shape=(10, 20))
# 执行计算
output, final_hidden_state, final_cell_state = lstm_layer(input_seq)
print("LSTM 输出形状:", output.shape) # (32, 128)
print("最终隐藏状态形状:", final_hidden_state.shape) # (32, 128)
print("最终细胞状态形状:", final_cell_state.shape) # (32, 128)
实战:基于 LSTM 的文本分类
任务介绍与数据集准备
本次任务是情感分类,使用 IMDB 电影评论数据集(50000 条标注为'正面'或'负面')。目标是搭建 LSTM 模型自动判断情感倾向。
- 加载 IMDB 数据集,限制词汇表大小为 10000,序列长度统一为 200。
- 将文本转换为整数索引,截断超长序列,补零填充短序列。
- 划分训练集和测试集(各 25000 条)。
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences
# 1. 加载数据集
vocab_size = 10000
max_seq_len = 200
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=vocab_size)
# 2. 序列填充与截断
x_train = pad_sequences(x_train, maxlen=max_seq_len, padding="post", truncating="post")
x_test = pad_sequences(x_test, maxlen=max_seq_len, padding="post", truncating="post")
print("训练集形状:", x_train.shape) # (25000, 200)
print("测试集形状:", x_test.shape) # (25000, 200)
搭建模型
模型结构分为三层:嵌入层、LSTM 层、全连接分类层。嵌入层解决文本稀疏问题,LSTM 捕捉上下文依赖,全连接层输出分类结果。
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Dense
embedding_dim = 128
model = Sequential([
# 嵌入层
Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=max_seq_len),
# LSTM 层
LSTM(units=128, dropout=0.2, recurrent_dropout=0.2),
# 全连接分类层
Dense(units=1, activation="sigmoid")
])
model.summary()
编译与训练
- 编译模型:Adam 优化器,二分类交叉熵损失,评估指标准确率。
- 训练模型:批次大小 64,轮数 5,10% 验证集。
- 保存历史用于绘图。
# 1. 编译
model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
# 2. 训练
batch_size = 64
epochs = 5
history = model.fit(
x_train, y_train,
batch_size=batch_size,
epochs=epochs,
validation_split=0.1
)
# 3. 评估
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f"测试集准确率:{test_acc:.4f}")
优化技巧
- 预训练词向量:使用 Word2Vec/GloVe 替换随机初始化,提升特征表示。
- 双向 LSTM:同时捕捉正向和反向上下文依赖。
- 早停法:验证集损失不再下降时停止,防止过拟合。
双向 LSTM 示例:
from tensorflow.keras.layers import Bidirectional
Bidirectional(LSTM(units=128, dropout=0.2, recurrent_dropout=0.2))
早停法示例:
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(monitor="val_loss", patience=2, restore_best_weights=True)
model.fit(x_train, y_train, callbacks=[early_stopping])
GRU 简介
GRU 是 LSTM 的简化版,合并了遗忘门和输入门为更新门,取消细胞状态,直接使用隐藏状态。参数更少,训练更快,效果相当。
from tensorflow.keras.layers import GRU
gru_layer = GRU(units=128, return_sequences=True, input_shape=(10, 20))
gru_output = gru_layer(input_seq)
print("GRU 输出形状:", gru_output.shape)
小结
循环神经网络通过隐藏状态存储历史信息,有效处理序列数据的上下文依赖。LSTM 引入门控机制解决梯度消失,是长序列任务的核心。在文本分类中,LSTM 结合嵌入层效果良好,双向结构和早停法等技巧能进一步优化性能。


