共计 2659 个字符,预计需要花费 7 分钟才能阅读完成。
技术背景
循环神经网络(RNN)是处理序列数据的核心架构之一,在自然语言处理、时间序列预测等领域具有不可替代的作用。B 站机器学习第二版第十章将 RNN 作为深度学习基础的重要组成部分,着重讲解其在序列建模中的独特优势。与传统前馈神经网络相比,RNN 通过引入隐藏状态(hidden state)的记忆机制,能够有效捕捉数据中的时序依赖关系。

核心原理
基础结构
RNN 的核心思想是通过循环连接实现信息的持久化。其数学表示为:
h_t = σ(W_hh h_{t-1} + W_xh x_t + b_h)
y_t = W_hy h_t + b_y
其中:
– h_t表示 t 时刻的隐藏状态
– σ为激活函数(通常使用 tanh 或 ReLU)
– W_*为可学习参数矩阵
– b_*为偏置项
前向传播过程
- 初始化隐藏状态 h_0(通常为零向量)
- 对于序列中的每个时间步 t:
- 计算当前隐藏状态 h_t
- 基于 h_t 计算输出 y_t
- 汇总所有时间步的输出
反向传播(BPTT)
RNN 通过时间反向传播算法更新参数,其梯度计算需考虑所有时间步的贡献:
∂L/∂W = Σ_{t=1}^T ∂L_t/∂W
痛点分析
梯度消失 / 爆炸问题
由于 RNN 需要将梯度沿时间步连续相乘,当序列较长时会出现:
– 梯度消失:梯度值指数级衰减,导致早期时间步的参数无法有效更新
– 梯度爆炸:梯度值指数级增长,造成数值不稳定
数学上可通过分析雅可比矩阵的特征值解释这种现象:
||∂h_t/∂h_{t-1}|| = ||diag(σ'(...))W_hh||
长期依赖问题
标准 RNN 难以学习超过 10 个时间步的长程依赖关系,这在语言建模等任务中表现尤为明显。
实战代码(PyTorch 实现)
import torch
import torch.nn as nn
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# x shape: (batch, seq_len, input_size)
h0 = torch.zeros(1, x.size(0), self.hidden_size)
out, _ = self.rnn(x, h0)
out = self.fc(out[:, -1, :]) # 只取最后时间步的输出
return out
完整训练流程包含:
-
数据预处理
# 示例:构造正弦波序列数据 def create_dataset(seq_length=50): time_steps = np.linspace(0, 10*np.pi, 1000) data = np.sin(time_steps) sequences = [data[i:i+seq_length] for i in range(len(data)-seq_length)] return torch.FloatTensor(sequences).unsqueeze(-1) -
训练循环
model = SimpleRNN(input_size=1, hidden_size=32, output_size=1) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.01) for epoch in range(100): outputs = model(train_data[:, :-1]) loss = criterion(outputs, train_data[:, 1:]) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 0.5) # 梯度裁剪 optimizer.step()
优化方案
LSTM 结构
长短期记忆网络通过门控机制解决梯度问题:
遗忘门:f_t = σ(W_f[h_{t-1}, x_t] + b_f)
输入门:i_t = σ(W_i[h_{t-1}, x_t] + b_i)
输出门:o_t = σ(W_o[h_{t-1}, x_t] + b_o)
候选记忆:\tilde{C}_t = tanh(W_C[h_{t-1}, x_t] + b_C)
记忆单元:C_t = f_t ⊙ C_{t-1} + i_t ⊙ \tilde{C}_t
隐藏状态:h_t = o_t ⊙ tanh(C_t)
GRU 结构
门控循环单元简化了 LSTM 的结构:
更新门:z_t = σ(W_z[h_{t-1}, x_t] + b_z)
重置门:r_t = σ(W_r[h_{t-1}, x_t] + b_r)
候选激活:\tilde{h}_t = tanh(W[r_t ⊙ h_{t-1}, x_t] + b)
隐藏状态:h_t = (1-z_t) ⊙ h_{t-1} + z_t ⊙ \tilde{h}_t
性能对比实验显示:
– 在字符级语言建模任务中,LSTM 比基础 RNN perplexity 降低 37%
– GRU 训练速度比 LSTM 快 20%,在短序列任务中表现相当
避坑指南
- 梯度裁剪缺失
- 现象:训练出现 NaN 值
-
解决:添加
torch.nn.utils.clip_grad_norm_ -
序列填充不当
- 现象:batch 内序列长度差异导致性能下降
-
解决:使用
pack_padded_sequence处理变长序列 -
初始化策略错误
- 现象:模型收敛缓慢
-
解决:对 RNN 参数使用正交初始化
-
学习率设置过高
- 现象:损失值剧烈震荡
-
解决:采用学习率 warmup 策略
-
忽略序列顺序
- 现象:时序预测性能差
- 解决:确保数据保持原始时序关系
扩展思考
建议尝试以下改进方向:
- 在自定义数据集(如股票价格、传感器读数)上测试 RNN 的预测能力
- 结合注意力机制增强长序列建模能力
- 探索双向 RNN 在 NLP 任务中的应用
- 比较不同序列长度下的模型表现
- 实现层次化 RNN 处理多尺度时序特征
学习建议
- 精读《神经网络与深度学习》第 6 章(邱锡鹏)
- 实践 PyTorch 官方 RNN 教程
- 研究原始论文《Learning long-term dependencies with gradient descent is difficult》
- 参加 Kaggle 时间序列竞赛积累实战经验
通过系统理解 RNN 的原理局限和优化方法,开发者可以更高效地将其应用于各类序列建模任务,后续可进一步学习 Transformer 等更先进的序列建模架构。
