跳到主要内容
SUN / 笔记
← 全部文章

RNN 与自然语言推断

1. RNN 的状态递推

序列模型在时间步 tt 接收输入 xtx_t,并把上一时刻状态 ht−1h_{t-1} 传给当前时刻:

ht=ϕ(Wxhxt+Whhht−1+bh) h_t=\phi(W_{xh}x_t+W_{hh}h_{t-1}+b_h)
yt=Whyht+by y_t=W_{hy}h_t+b_y

同一组参数在所有时间步共享,因此 RNN 可以处理不同长度的序列。隐藏状态不是一个独立数据库,而是模型根据历史输入压缩出的连续表示。

flowchart LR
    X1[x1] --> H1[h1]
    H1 --> H2[h2]
    X2[x2] --> H2
    H2 --> H3[h3]
    X3[x3] --> H3

2. PyTorch 中的输入和输出形状

python
import torch
from torch import nn


class SequenceClassifier(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super().__init__()
        self.rnn = nn.RNN(
            input_size=input_size,
            hidden_size=hidden_size,
            batch_first=True,
        )
        self.classifier = nn.Linear(hidden_size, num_classes)

    def forward(self, x):
        outputs, h_n = self.rnn(x)
        final_state = h_n[-1]
        return self.classifier(final_state)


x = torch.rand(32, 5, 10)  # batch, sequence, feature
model = SequenceClassifier(10, 20, 2)
logits = model(x)           # (32, 2)

当 batch_first=True:

  • 输入:(batch, sequence_length, input_size);
  • 每步输出:(batch, sequence_length, hidden_size * directions);
  • 最终状态 h_n:(layers * directions, batch, hidden_size)。

源笔记使用 outputs[:, -1, :] 作为序列表示,对等长、无 padding 的简单输入可行。对变长序列,最后一列可能只是 <pad>;应根据真实长度取状态、使用 pack_padded_sequence,或通过 mask 做池化。

3. 为什么基本 RNN 难以处理长依赖

反向传播需要沿时间轴不断乘以雅可比矩阵:

  • 连乘范数持续小于 1,容易梯度消失;
  • 连乘范数持续大于 1,容易梯度爆炸;
  • 每个时间步依赖前一步,限制训练并行性。

常见应对:

  • LSTM / GRU 用门控控制信息保留与更新;
  • 梯度裁剪限制爆炸;
  • 合理初始化、归一化和学习率;
  • 能并行建模的场景可考虑 CNN 或 Transformer。

4. LSTM、GRU 和双向结构

结构 核心机制 特点
基本 RNN 单一隐藏状态 结构简单,长依赖能力有限
LSTM 输入门、遗忘门、输出门和 cell state 参数较多,显式控制长期记忆
GRU 更新门、重置门 比 LSTM 更简洁,常有相近表现
双向 RNN 正向与反向各处理一次 适合可同时看到完整上下文的任务,不适合因果在线生成

模型选择应由数据规模、时延、序列长度和因果约束决定,而不是默认某一种总是更好。

5. 自然语言推断任务

NLI 输入一对句子:前提(premise)与假设(hypothesis),输出二者关系:

  • entailment:前提支持假设;
  • contradiction:前提与假设矛盾;
  • neutral:前提不足以判断假设真伪。

SNLI 数据处理链:

flowchart LR
    A[原始句对与标签] --> B[清洗与分词]
    B --> C[构建词表]
    C --> D[截断 / padding]
    D --> E[前提张量 + 假设张量]
    E --> F[DataLoader]
    F --> G[三分类模型]

源 Notebook 的关键形状是:

  • 前提:(batch_size, num_steps);
  • 假设:(batch_size, num_steps);
  • 标签:(batch_size,)。

padding token 不应参与有效信息聚合。自行实现注意力或池化时,需要显式 mask。

6. Decomposable Attention 的三阶段

natural-language-inference-attention.ipynb 不使用循环网络,而是采用可分解注意力模型:

  1. Attend:计算前提 token 与假设 token 的两两相似度,并生成对齐表示。
  2. Compare:分别比较每个 token 与其对齐结果。
  3. Aggregate:对比较结果聚合,再完成三分类。
flowchart TB
    P[Premise tokens] --> A[Attend]
    H[Hypothesis tokens] --> A
    A --> CP[Compare premise]
    A --> CH[Compare hypothesis]
    CP --> AG[Aggregate]
    CH --> AG
    AG --> Y[Entailment / Contradiction / Neutral]

它说明了一个重要思想:句对任务不一定先把各自压缩为单个向量;先做跨句 token 对齐,再聚合局部比较结果,往往更直接。

7. NLI 训练容易踩的坑

  • 数据划分或句子对重复造成泄漏。
  • 把 <pad> 纳入平均池化或注意力分数。
  • 未保存词表与 token 到索引映射,导致权重无法复现。
  • 把类别编号含义写死但未保存映射。
  • 只看 accuracy,忽略三类召回率和混淆矩阵。
  • 用测试集反复调最大序列长度、词表大小或模型结构。
  • 预训练词向量与词表索引没有正确对齐。

8. 何时使用哪种序列建模方式

场景 可优先考虑
小规模时间序列、状态递推明确 GRU / LSTM
在线、严格因果、低时延 单向 RNN/GRU 或因果模型
句对局部对齐 Decomposable Attention 或交叉注意力
长序列、训练并行性重要 Transformer 或高效注意力变体
资源受限、序列较短 小型 GRU / 1D CNN

来源映射

  • RNN.md
  • 自然语言推断.md
  • natural-language-inference-and-dataset.ipynb
  • natural-language-inference-attention.ipynb
  • d2l-zh\pytorch\chapter_recurrent-neural-networks\
  • d2l-zh\pytorch\chapter_natural-language-processing-applications\

其中两份 NLI Notebook 与完整 d2l-zh 目录主要属于教材学习材料,本笔记只提炼其概念和实验结构,不把教材内容视作个人原创代码。

关联笔记

相关文章

强化学习笔记 →上下文工程 →Mission: 掌握多模态 LoRA 与 QLoRA →