第04回 记事的门派——RNN 与 LSTM/GRU

字字相随如走马,一招一式续前缘。
若无心法留余火,转眼长情化断烟。

上回我们让词“认亲”——嵌入把“吃饭”和“用餐”拉到一桌,语义终于不再陌路。
可看官很快又发现新麻烦:

“词有了向量,句子怎么办?‘我 不 爱 你’只多一个‘不’,意思却翻天覆地。词袋不看顺序,岂不误事?”

正是。语言是“按顺序发生的事”。要读懂一句话,模型得学会“记事”。
这便轮到本回主角登台:循环神经网络(RNN),以及它的两位名门弟子:LSTMGRU


一、RNN 是什么:一盏灯接一盏灯,走到哪算到哪

RNN 的江湖形象,最像“走夜路提灯”。

你读一个词,灯里就多一缕光;再读下一个词,带着上一刻的光继续走。

把这“灯里的光”叫作隐藏状态 hth_t。它每一步都更新一次:

ht=f(ht1,xt)h_t = f(h_{t-1}, x_t)

这里 xtx_t 是第 tt 个词的向量(上回的嵌入),ff 是同一套更新规则,步步复用。

RNN 的好处一眼就明白:

  • 顺序保住了:先看“我”,再看“不”,再看“爱”,顺序不同,状态就不同
  • 计算省:每步只和上一状态打交道,推理时是线性的

可 RNN 也有老毛病:记性会散。句子一长,早先的信息像风里烛火,越吹越弱,这就是常说的“长期依赖难题”(背后牵涉到梯度在长链路上衰减/爆炸的现象,你只需记住直觉:太长就记不牢)。

于是江湖就发明了“带门的记忆匣子”:LSTM 与 GRU。


二、LSTM:三道门,一只匣,专治“记不住”

LSTM 可以理解成:
在 RNN 的“灯”旁边,再配一只更稳的“记忆匣”(常被写作 ctc_t),并用几道“门”来决定:

  • 哪些旧记忆该忘
  • 哪些新信息该收
  • 最后把哪部分拿出来用

你不用背公式,把它当作三道门的规矩即可:

  1. 忘门:旧事有的该放下
  2. 收门:新事有的要记牢
  3. 出门:对外说话时,挑合适的记忆上台

所以 LSTM 像老账房:
不是所有流水都往册子里抄,而是“该记的记,该扔的扔”,于是能撑更长的叙事。

2024 年还有人专门把 LSTM 这套门派再扩展,提出 xLSTM,用更强的门控与结构改造,去争取更好的长序列能力与可扩展性。1


三、GRU:两道门,轻装上阵

GRU 是 LSTM 的“轻功版”:
门少一点,结构简一点,训练常更利索。

你可以把它看作:

  • 仍然有“要不要更新”的门
  • 也有“要不要忘”的门
  • 但不再单独拎出一只 ctc_t 记忆匣,整体更紧凑

在很多实际任务里,GRU 与 LSTM 表现接近,选择更多取决于:

  • 速度与资源
  • 数据规模
  • 任务是否确实需要很长的记忆

四、2024–2026 的回潮:RNN 为何又被人惦记

看官可能听说过一句江湖传闻:
“Transformer 之后,RNN 不是退隐了吗?”

其实近两年(尤其 2024 起),对“非注意力序列模型”的兴趣大涨:
一方面是长上下文成本太高;另一方面是大家开始重新打磨“线性复杂度”的路线。

这股风里,有几条支流与你本回所学能对上眼:

  • xLSTM:把 LSTM 的门控与记忆结构做现代化扩展1
  • “Were RNNs All We Needed?”:从历史与算法相似性出发回看 LSTM/GRU,并提出更“精简”的 RNN 变体,强调训练/推理效率的潜力2
  • RWKV:把“可并行训练”和“RNN 式线性推理”揉成一体,被视为“Transformer 时代的 RNN 路线”之一3

你无需在本回就吃透这些新派,只要抓住主线:
RNN 的“状态”是个很有价值的容器。后面我们讲 Mamba/SSM 时,会再次见到“用状态承载历史”的思想。


五、极简代码:用 LSTM 做一句话情感分类(PyTorch 可跑)

下一回要做情感分析实战,本回先把“会记事的模型”练起来。
下面这段代码用一个玩具数据集演示:
把一句话(分词后)输入 LSTM,取最后一步的状态做二分类。

import random

import torch
import torch.nn as nn
import torch.nn.functional as F


def build_vocab(samples):
    vocab = {"<pad>": 0, "<unk>": 1}
    for text, _ in samples:
        for w in text.split():
            if w not in vocab:
                vocab[w] = len(vocab)
    return vocab


def encode(text, vocab, max_len):
    ids = [vocab.get(w, vocab["<unk>"]) for w in text.split()]
    ids = ids[:max_len]
    ids += [vocab["<pad>"]] * (max_len - len(ids))
    return ids


class LSTMClassifier(nn.Module):
    def __init__(self, vocab_size, dim=32, hidden=64, num_classes=2):
        super().__init__()
        self.emb = nn.Embedding(vocab_size, dim, padding_idx=0)
        self.lstm = nn.LSTM(input_size=dim, hidden_size=hidden, batch_first=True)
        self.fc = nn.Linear(hidden, num_classes)

    def forward(self, x):
        e = self.emb(x)
        out, _ = self.lstm(e)
        last = out[:, -1, :]
        return self.fc(last)


if __name__ == "__main__":
    torch.manual_seed(0)
    random.seed(0)

    samples = [
        ("这 家 店 真 好吃", 1),
        ("味道 很 棒 还 会 再 来", 1),
        ("太 难吃 了 不 会 再 来", 0),
        ("服务 很 差 失望", 0),
        ("好吃 便宜 推荐", 1),
        ("踩 雷 了 真 糟糕", 0),
    ]

    vocab = build_vocab(samples)
    max_len = 8

    x = torch.tensor([encode(t, vocab, max_len) for t, _ in samples], dtype=torch.long)
    y = torch.tensor([label for _, label in samples], dtype=torch.long)

    model = LSTMClassifier(vocab_size=len(vocab))
    opt = torch.optim.Adam(model.parameters(), lr=0.03)

    for step in range(200):
        logits = model(x)
        loss = F.cross_entropy(logits, y)
        opt.zero_grad()
        loss.backward()
        opt.step()

    with torch.no_grad():
        pred = model(x).argmax(dim=1)
        acc = (pred == y).float().mean().item()
        print("acc:", round(acc, 3))
        for (t, label), p in zip(samples, pred.tolist()):
            print(f"text={t}  label={label}  pred={p}")

这段“玩具练功”当然不代表真实效果,但它把关键流程走全了:

  • 词 → id → embedding
  • embedding 序列 → LSTM 逐步更新状态
  • 取最后状态 → 分类

下一回我们会把数据、特征与评估讲得更像“实战”,让你知道:
模型不仅要会练,还得会打。


六、小结:本回解了“顺序”,下回要讲“如何评卷”

本回把“句子为什么要按顺序处理”讲清了:

  • RNN:一步一步走,状态携带历史
  • LSTM:加门加匣,专治记不住
  • GRU:轻装版本,常更省更快

同时也埋下一颗伏笔:
2024–2026 许多“后 Transformer 路线”,仍在围绕“状态如何承载更长历史”做文章。

可江湖规矩是:武功要看实战。
下一回我们就拿“情感分析”当擂台——
不只讲模型,还讲数据怎么切、怎么评、怎么避免自欺欺人。

欲知后事如何,且听下回分解。


引用与溯源

Footnotes

  1. Beck, M., et al. xLSTM: Extended Long Short-Term Memory arXiv:2405.04517(2024-05,v2 2024-12)https://arxiv.org/abs/2405.04517 2

  2. Were RNNs All We Needed? arXiv:2410.01201(2024-10)https://arxiv.org/abs/2410.01201

  3. Peng, B., et al. RWKV: Reinventing RNNs for the Transformer Era arXiv:2305.13048(2023-05)https://arxiv.org/abs/2305.13048