问题与目标
普通全连接网络将每条输入当成固定长度特征,并不自带“前后顺序”。时序信号、事件流和文本都需要区分顺序;RNN 在每个时间步中更新隐藏状态,将已经读取的信息传给下一步。
本篇只使用自建数值序列,判断前半段均值是否高于后半段。输入形状为 (batch, time, features),分别用 RNN 和 LSTM 输出二分类 logits。文本分词、Embedding 和 GRU 留到 NLP 阶段。

每个时间步使用同一组循环参数,当前隐藏状态同时接收当前输入和上一步状态。
核心概念
同一组参数在时间步之间共享
基础 RNN 可直观写成:
hₜ = tanh(xₜ Wₓₕ + hₜ₋₁ Wₕₕ + b)
xₜ 是当前输入,hₜ₋₁ 是上一步状态,hₜ 是新状态。同一组权重用于所有时间步,因此模型可处理不同长度的序列,但批处理时仍需要 padding 或打包变长序列。
输出序列与最终状态不是一回事
PyTorch nn.RNN(..., batch_first=True) 返回:
output:每个时间步最后一层的隐藏状态,形状(N, T, H)。h_n:每一层最后状态,形状(layers, N, H),双向网络还包含方向维度。
序列分类可使用最终状态,序列标注则通常需要每个时间步的 output。有 padding 时,直接取 output[:, -1] 可能取到填充位置。
LSTM 增加了单元状态和门控
LSTM 通过遗忘门、输入门和输出门控制信息保留与读取,同时维护隐藏状态 h 和单元状态 c。相比基础 RNN,它为长距离梯度传递提供了更稳定的路径,但不代表可以无损保存任意长序列。
序列顺序本身就是特征
如果将时间步随机打乱,样本中的时序关系就被破坏。DataLoader 打乱的是“样本顺序”,不是每条样本内部的时间步。
可运行实现
import torch
from torch import nn
torch.manual_seed(42)
sequence_count, steps = 640, 12
sequences = torch.randn(sequence_count, steps, 1)
labels = (
sequences[:, : steps // 2].mean(dim=1)
> sequences[:, steps // 2 :].mean(dim=1)
).long().squeeze(1)
train_x, valid_x = sequences[:512], sequences[512:]
train_y, valid_y = labels[:512], labels[512:]
class SequenceClassifier(nn.Module):
def __init__(self, cell: str) -> None:
super().__init__()
recurrent = nn.RNN if cell == "rnn" else nn.LSTM
self.encoder = recurrent(1, 16, batch_first=True)
self.classifier = nn.Linear(16, 2)
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
_, state = self.encoder(inputs)
hidden = state[0] if isinstance(state, tuple) else state
return self.classifier(hidden[-1])
for cell in ("rnn", "lstm"):
model = SequenceClassifier(cell)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.CrossEntropyLoss()
for _ in range(80):
model.train()
optimizer.zero_grad()
loss = loss_fn(model(train_x), train_y)
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
model.eval()
with torch.inference_mode():
accuracy = (model(valid_x).argmax(1) == valid_y).float().mean()
print(cell, "valid accuracy:", round(accuracy.item(), 3))
标签依赖前后两段的相对均值,必须保留时间顺序。两个模型都可以学会这个小任务,但一次分数不能证明 LSTM 在所有序列上更好。可将序列拉长、加入无关噪声,再观察收敛速度和稳定性。
常见问题与排查
- 把
(N, T, F)传给默认batch_first=False的层:明确设置batch_first=True或转换维度。 - LSTM 状态解包错误:LSTM 返回
(h_n, c_n),RNN 只返回h_n。 - 变长序列直接取最后一位:最后一位可能是 padding,需记录真实长度或使用 packed sequence。
- 隐藏状态未在批次边界断开:状态延续还是重置取决于样本是否连续,不能无意跨样本泄漏。
- 序列更长就直接增大隐藏宽度:先分析有效依赖距离、训练成本和梯度。
小结
RNN 通过隐藏状态将历史信息逐步传向后续时间步,LSTM 再用门控和单元状态改善长距离信息保留。它们的核心契约是时间、批次和特征三个维度,而核心局限是需要按时间步顺序计算,并将过去信息持续压缩进有限状态。
许可协议:CC BY-NC 4.0
更新于 1 小时前
觉得文章有帮助?点个赞吧!
0 条评论


