首页 / PyTorch 入门教程 / 循环神经网络 RNN 与 LSTM/GRU

PyTorch 入门教程

循环神经网络 RNN 与 LSTM/GRU

本教程共 60 篇 · 第 22 篇 · 更新于 2026-08-17 · 约 3 分钟阅读

PyTorchRNNLSTMGRU序列模型隐状态门控机制

本节目标:搞懂循环神经网络(RNN)处理序列的思路,理解 LSTM 和 GRU 的门控机制解决了什么问题,并会用 nn.RNNnn.LSTMnn.GRU 跑通最小示例。

前面学的全连接网络和 CNN,输入都是一次性看完的:一张图进去,一个结果出来。但如果输入是一段话、一首歌、一条股价曲线呢?顺序很重要,前面的信息会影响后面的理解。

序列数据的难题

读这句话:「我今天出门忘带伞,结果下午被淋成落汤鸡。」读到「落汤鸡」,你得记得前面「忘带伞」这个原因。

这类数据叫序列数据(Sequential Data):文本、语音、股票走势、传感器读数都是。它们有两个特点:顺序有意义,长度不固定。

全连接网络处理序列很尴尬:它没有记忆,每个输入独立处理,前一个词的信息传不到后一个词。把整句话打包成一个大向量?句子长短不一,没法打包。

RNN:边读边记笔记

循环神经网络(Recurrent Neural Network,RNN)的核心是引入隐状态(Hidden State)。你可以把它想成一张草稿纸:每读一个词,就在纸上记一点摘要;读下一个词时,带着这张纸一起读。

用公式说:当前隐状态 h_t 由两部分决定,当前输入 x_t 和上一步的隐状态 h_{t-1},再经过激活函数。

h_t = tanh(W_ih · x_t + W_hh · h_{t-1} + b)

这里有个关键点:每个时间步用的是同一套权重。所以 RNN 的参数量和序列长度无关,序列再长也是这些参数。

RNN 的两个毛病

理想很丰满,现实有两点麻烦。

  1. 梯度消失:序列一长,反向传播时梯度一路连乘,越乘越小,远处的信息学不到。
  2. 梯度爆炸:反过来,权重矩阵的放大效应让梯度越滚越大,训练直接崩掉。

梯度爆炸可以用梯度裁剪(Gradient Clipping)压住,梯度消失则催生了 LSTM 和 GRU。

LSTM:带传送带的记忆

长短期记忆网络(Long Short-Term Memory,LSTM)给 RNN 加了一条「传送带」:细胞状态(Cell State)。信息在传送带上流动,只做少量加减,不容易丢。

传送带上装三个门,输出的都是 0 到 1 之间的数,控制信息进出:

  • 遗忘门(Forget Gate):决定旧记忆丢多少。
  • 输入门(Input Gate):决定新信息记多少。
  • 输出门(Output Gate):决定此刻输出什么。

所以 LSTM 有两个状态在时间步之间传递:细胞状态 c 管长期记忆,隐状态 h 管当前输出。这就像整理笔记:先划掉过时的,再写上新的,最后决定今天讲什么。

GRU:更轻量的版本

门控循环单元(Gated Recurrent Unit,GRU)是 LSTM 的简化版。它把遗忘门和输入门合并成一个更新门,取消独立的细胞状态,只剩两个门。

结构简化,参数自然少:大约只有 LSTM 的 75%,训练更快。小数据量下,表现经常和 LSTM 差不多。

PyTorch 里怎么用

nn.RNNnn.LSTMnn.GRU 三个模块接口几乎一样,核心参数就三个:input_size(每步输入的维度)、hidden_size(隐状态维度)、num_layers(层数)。

输入形状是最容易搞错的地方。推荐全程用 batch_first=True,形状就是 (batch, seq_len, input_size),和直觉一致。

import torch
import torch.nn as nn

lstm = nn.LSTM(input_size=32, hidden_size=64, batch_first=True)
x = torch.randn(4, 10, 32)  # batch=4,序列长 10,每步 32 维

output, (h_n, c_n) = lstm(x)
print(output.shape)  # torch.Size([4, 10, 64]) 每个时间步的输出
print(h_n.shape)     # torch.Size([1, 4, 64])  最后一步的隐状态
print(c_n.shape)     # torch.Size([1, 4, 64])  最后一步的细胞状态

注意返回值:output 是所有时间步的输出,h_n 是最后一个时间步的隐状态,LSTM 还多一个 c_n。做分类时通常取最后一步的输出:

last = output[:, -1, :]       # (4, 64)
fc = nn.Linear(64, 5)         # 接一个分类头
logits = fc(last)
print(logits.shape)           # torch.Size([4, 5])
Tip

训练 RNN 建议加梯度裁剪,防止梯度爆炸:
nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

LSTM 还是 GRU?

场景建议
数据少、资源有限GRU,参数少、训练快
序列长、依赖远LSTM,细胞状态擅长长期记忆
快速做实验先 GRU 跑通,再换 LSTM 对比
数据量大、追求极致考虑下一章的 Transformer

本章记住一句话:RNN 靠隐状态记笔记,LSTM 用三个门管住一条传送带,GRU 是它的轻量版。序列建模的大门,就从这三个模块打开。