Skip to content

序列建模:RNN / LSTM 到注意力 ​

核心问题:输入是「一串」有顺序的东西(文本、语音、时间序列),怎么建模?

这一篇的落点在 注意力机制 —— 它是 Transformer 的种子,也是理解 L4 大模型的前提。

为什么序列不能用普通全连接 ​

问题说明
长度不固定「我爱你」和「今天天气不错我很开心」长度不同
顺序有意义「狗咬人」≠「人咬狗」
长距离依赖「虽然下雨了,但是我还是去了」中间隔着好几个词

一、RNN:一步步读 ​

h₁ = f(x₁, h₀)
h₂ = f(x₂, h₁)
...
hₜ = f(xₜ, hₜ₋₁)

隐状态 h 是「读到目前为止的记忆」。

python
rnn = nn.RNN(input_size=128, hidden_size=256, batch_first=True)
x = torch.randn(8, 20, 128)          # (batch, seq_len, dim)
out, h_n = rnn(x)
print(out.shape, h_n.shape)          # (8,20,256) (1,8,256)

致命问题:梯度要沿时间反传 20 步,长序列上梯度会消失或爆炸,学不到长距离依赖。

二、LSTM / GRU:加门控 ​

LSTM 用三个门控制信息的存/取/丢:

门作用直觉
遗忘门决定丢掉多少旧记忆「这个话题结束了,清空」
输入门决定写入多少新信息「记住这个新事实」
输出门决定输出多少当前记忆「现在该说出多少」
python
lstm = nn.LSTM(input_size=128, hidden_size=256,
               num_layers=2, batch_first=True,
               bidirectional=True, dropout=0.1)
x = torch.randn(8, 20, 128)
out, (h_n, c_n) = lstm(x)
print(out.shape)      # (8, 20, 512) 双向所以 256×2

文本分类的标准做法(BiLSTM + 池化) ​

python
class BiLSTMClassifier(nn.Module):
    def __init__(self, vocab, embed_dim=128, hidden=256, n_cls=2):
        super().__init__()
        self.emb = nn.Embedding(vocab, embed_dim, padding_idx=0)
        self.lstm = nn.LSTM(embed_dim, hidden, batch_first=True,
                            bidirectional=True, num_layers=2, dropout=0.2)
        self.head = nn.Sequential(nn.Dropout(0.3), nn.Linear(hidden * 2, n_cls))

    def forward(self, ids, mask=None):
        x = self.emb(ids)                     # (B, T, E)
        out, _ = self.lstm(x)                 # (B, T, 2H)
        if mask is not None:
            out = out * mask.unsqueeze(-1)    # 屏蔽 padding
        pooled = out.max(dim=1).values        # 最大池化聚合
        return self.head(pooled)

padding 必须屏蔽

一个 batch 里句子长短不一,短句会补 0。不屏蔽的话,max/mean 池化会被补位污染,指标莫名偏低。

RNN 的极限 ​

局限后果
串行计算无法并行,长序列训练极慢
长距离依赖仍会衰减即使 LSTM 也撑不过几百步
固定长度的隐状态所有信息压进一个向量,成为瓶颈

这三点直接催生了注意力机制。

三、注意力:从「压缩」到「按需查找」 ​

一句话理解 ​

注意力 = 用「查询」去和一堆「键」比对,按相关度加权取「值」。

查字典的比喻:你要查一个字(Query),在字典里比对哪些词条(Key)匹配,然后把对应解释(Value)按匹配度加权拿回来。

缩放点积注意力 ​

$$\text{Attention}(Q,K,V) = \text{softmax}!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V$$

python
import torch, torch.nn.functional as F

def scaled_dot_product_attention(Q, K, V, mask=None):
    d_k = Q.size(-1)
    scores = Q @ K.transpose(-2, -1) / (d_k ** 0.5)   # (B, Tq, Tk)
    if mask is not None:
        scores = scores.masked_fill(mask == 0, float("-inf"))
    attn = F.softmax(scores, dim=-1)
    return attn @ V, attn

三项各自的作用:

项作用
$QK^\top$算每个 query 与每个 key 的相似度
$/\sqrt{d_k}$缩放!否则维度大时点积过大,softmax 饱和成 one-hot,梯度消失
$\text{softmax}$变成权重和为 1 的分布
$\times V$按权重加权求和,得到「聚合后的信息」

为什么必须除以 √d

d=64 时,随机向量点积的方差约为 64,标准差 8。softmax 输入跨度过大会输出接近 one-hot → 梯度几乎为 0 → 参数学不动。

三种注意力 ​

类型Q 来自K/V 来自用途
自注意力 Self-Attention输入本身输入本身Transformer 编码
交叉注意力 Cross-Attn解码器编码器输出翻译、多模态对齐
因果自注意力输入本身输入本身(+因果掩码)自回归生成(GPT)

因果掩码(下三角)保证生成第 t 个 token 时看不到未来:

python
T = 4
causal_mask = torch.tril(torch.ones(T, T)).bool()
# tensor([[1,0,0,0],
#         [1,1,0,0],
#         [1,1,1,0],
#         [1,1,1,1]])

四、多头注意力:并行看不同的关系 ​

单个注意力只能学到一种「关注模式」。多头让它同时学多种:

python
class MultiHeadAttention(nn.Module):
    def __init__(self, d_model=512, n_heads=8):
        super().__init__()
        assert d_model % n_heads == 0
        self.h, self.d_k = n_heads, d_model // n_heads
        self.wq, self.wk, self.wv = (nn.Linear(d_model, d_model) for _ in range(3))
        self.wo = nn.Linear(d_model, d_model)

    def forward(self, q, k, v, mask=None):
        B, T, _ = q.shape
        # (B,T,D) → (B,h,T,d_k)
        q, k, v = (x.view(B, T, self.h, self.d_k).transpose(1, 2)
                   for x in (self.wq(q), self.wk(k), self.wv(v)))
        out, _ = scaled_dot_product_attention(q, k, v, mask)   # (B,h,T,d_k)
        out = out.transpose(1, 2).reshape(B, T, self.h * self.d_k)
        return self.wo(out)

不同头学到的分工(可视化观察到的经验):

  • 有的头看相邻词(局部语法)
  • 有的头盯着主谓关系
  • 有的头盯着标点或句首

五、注意力带来的两个关键能力 ​

能力说明
可并行任意两个位置的距离都是 1 步,全部位置同时计算(训练速度质变)
直接长依赖第 1 个词能直接看第 1000 个词,无需逐步传递

代价:计算量 $O(T^2 d)$,序列越长越贵。这就是后来 FlashAttention、稀疏注意力、线性注意力要解决的问题。

六、学习路径建议 ​

步骤做什么
1用 PyTorch 跑通 BiLSTM 文本分类(建立序列任务直觉)
2手写 scaled_dot_product_attention,用 3×3 矩阵手动验证
3可视化注意力权重(画热力图看它关注什么)
4进入 Transformer 组装完整模型
5除非必要,不要在生产用 RNN —— 直接用预训练 Transformer

通过标准 ​

下一步 ​

→ Transformer:现代 AI 的地基

AI 学习路线 · 从入门到专家