> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/llm_internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/llm_internals/di-yi-bu-fen-ji-chu-pian/01_introduction/1.2_rnn_cnn_limits.md).

# 1.2 RNN 与 CNN：成就与瓶颈

理解 RNN 和 CNN 在序列建模中的成就与局限，是理解 Transformer 为什么这样设计的前提。每一个 Transformer 的核心设计决策，都可以追溯到对这些前驱架构的某个具体不足的改进。

## 1.2.1 循环神经网络：用“记忆”处理序列

循环神经网络（Recurrent Neural Network，RNN）是第一个真正为序列数据设计的神经网络架构。它的核心思想极为自然：**像人类阅读一样，逐个处理序列中的元素，并维护一个“记忆”（隐藏状态）来积累已经看过的信息。**

具体而言，RNN 在每个时间步 $$t$$ 接收当前输入 $$x\_t$$ 和上一步的**隐藏状态**（Hidden State）$$h\_{t-1}$$。**隐藏状态是网络内部维护的一个数学向量，用于浓缩和表征迄今为止处理过的所有历史信息。** RNN 结合这两个输入，计算出新的隐藏状态：

$$h\_t = \sigma(W\_h h\_{t-1} + W\_x x\_t + b)$$

其中 $$\sigma$$ 是激活函数（通常为 tanh），$$W\_h$$ 和 $$W\_x$$ 是权重矩阵。关键在于：**所有时间步共享同一组参数**（$$W\_h$$、$$W\_x$$ 和 $$b$$），这使得 RNN 天然地能够处理任意长度的序列——第一个核心挑战得以解决。

下图将 RNN 沿时间展开，展示了隐藏状态如何在时间步之间依次传递（$$h\_0$$ 初始化为零向量）：

```mermaid
graph LR
    h0["h₀<br/>(零向量)"] --> A["RNN<br/>hₜ=σ(Wₕh+Wₓx+b)"]
    x1["x₁"] --> A
    A --> h1["h₁"]
    h1 --> B["RNN<br/>hₜ=σ(Wₕh+Wₓx+b)"]
    x2["x₂"] --> B
    B --> h2["h₂"]
    h2 --> C["RNN<br/>hₜ=σ(Wₕh+Wₓx+b)"]
    x3["x₃"] --> C
    C --> h3["h₃"]
    h3 -..-> D[" ··· "]
    D -..-> E["RNN<br/>hₜ=σ(Wₕh+Wₓx+b)"]
    xn["xₙ"] --> E
    E --> hn["hₙ"]

    style A fill:#4a90d9,color:#fff
    style B fill:#4a90d9,color:#fff
    style C fill:#4a90d9,color:#fff
    style E fill:#4a90d9,color:#fff
    style h0 fill:#ecf0f1,stroke:#7f8c8d
```

图 1-2：RNN 沿时间展开示意图，每个时间步共享同一组参数 $$(W\_h, W\_x, b)$$

正如前文所述，隐藏状态 $$h\_t$$ 理论上编码了从序列开头到当前位置的所有信息，可以被视为一个“压缩的历史摘要”。在序列结束时，最终的隐藏状态可以用于分类任务；在每一步，隐藏状态也可以用于序列标注或生成下一个输出。

RNN 在 2010 年代初取得了显著的成功：语言建模、语音识别、机器翻译等任务的性能都得到了大幅提升。然而，当人们试图将 RNN 应用于更复杂的真实场景（尤其是需要全局语境的任务）以及更长的序列时，几个根本性的问题暴露了出来。

## 1.2.2 单向视野：无法“预知未来”

如果仅仅按照从左到右的单向顺序去处理序列，在诸如机器翻译这样的任务中会遇到显著的语义盲区。如前所述，当 RNN 逐词处理序列并输出当前词的隐藏状态时，**它只能看到之前的输入，对后文一无所知**。假设我们要翻译“I arrived in London yesterday.”，当模型编码“arrived”这个词时，它的状态中只有“I arrived”的信息，并不清楚其后的“London”（目的地）和“yesterday”（时间）。但在语言表达中，当前词的真实含义、形态甚至具体用词往往强烈依赖于**后文**的完整语境。**单向 RNN 无法“预知未来”，这使得它在理解序列时极易产生偏差，进而导致基于此表示的生成极有可能“翻译不好”。**

为了解决“只能看到过去”的单向视野问题，研究人员随后引入了**双向 RNN（Bidirectional RNN）**。它通过同时运行一个正向（从左到右）和一个逆向（从右向左）的 RNN，将两个方向的隐藏状态拼接起来，使得序列中每个位置的表征都能同时包含过去与未来的完整上下文：$$\vec{h}*i = \text{RNN}*\text{fwd}(x\_1, \ldots, x\_i)$$，$$\overleftarrow{h}*i = \text{RNN}*\text{bwd}(x\_n, \ldots, x\_i)$$，最终表示为 $$\[\vec{h}\_i; \overleftarrow{h}\_i]$$。这一改进大幅弥补了单向视野的缺陷，成为了理解类任务（如序列标注、句子分类）的重要基石。但即便有了双向结构，RNN 仍然面临着梯度消失问题（在两个方向上都存在），且依然无法实现完全并行化。

## 1.2.3 梯度消失：RNN 的致命弱点

RNN 的训练依赖**时间反向传播**（Backpropagation Through Time，BPTT）：将 RNN 沿时间展开为一个很深的前馈网络，然后用标准的反向传播算法计算梯度。

问题出在梯度的链式传播上。当梯度从时间步 $$T$$ 回传到时间步 $$1$$ 时，需要连续乘以 $$T$$ 次权重矩阵 $$W\_h$$ 的转置。如果 $$W\_h$$ 的谱范数（最大奇异值）小于 1，这种连乘会导致梯度**指数级衰减**——这就是**梯度消失**（Vanishing Gradient）问题。

$$\frac{\partial h\_T}{\partial h\_1} = \prod\_{t=2}^{T} \frac{\partial h\_t}{\partial h\_{t-1}} \approx (W\_h)^{T-1}$$

上式为简化表达，省略了激活函数 $$\sigma$$ 的 Jacobian 矩阵。准确形式应为 $$\frac{\partial h\_T}{\partial h\_1} = \prod\_{t=2}^{T} \text{diag}(\sigma'(z\_t)) W\_h$$，其中 $$z\_t = W\_h h\_{t-1} + W\_x x\_t$$。关键的是，即使激活函数的导数（如 tanh 的 $$\sigma'(z) \leq 1$$）也会进一步缩小梯度。当序列长度 $$T$$ 较大时，如果权重矩阵的谱范数 $$\rho(W\_h) < 1$$，连乘后的梯度按 $$\rho(W\_h)^{T-1}$$ 衰减，趋近于零；如果 $$\rho(W\_h) > 1$$，则梯度**爆炸**。这意味着 RNN 实际上**无法学习长距离依赖**：距离较远的两个词之间的关联信号在回传过程中会消失殆尽。

Bengio 等人在 1994 年的经典论文中系统分析了这一问题，指出这不是工程细节，而是 RNN 架构的**内在数学缺陷**。

## 1.2.4 LSTM 与 GRU：改善梯度消失

为了缓解梯度消失问题，Hochreiter 和 Schmidhuber 在 1997 年提出了**长短期记忆网络**（Long Short-Term Memory，LSTM）。

LSTM 的核心创新是引入了**门控机制**（Gating Mechanism）和一条独立的**记忆单元通路**（Cell State）。记忆单元 $$c\_t$$ 通过线性的自循环路径传递信息，不经过激活函数的压缩。

门控机制则由三个门组成：遗忘门 $$f\_t$$、输入门 $$i\_t$$ 和输出门 $$o\_t$$。这三个门的本质是**结构完全相同的全连接层**。它们都接收当前输入 $$x\_t$$ 和上一时刻的隐状态 $$h\_{t-1}$$，通过仿射变换（乘以各自的权重矩阵再加上偏置）后，再经过一个 **Sigmoid 激活函数（**$$\sigma$$**）**：

$$f\_t = \sigma(W\_{fx} x\_t + W\_{fh} h\_{t-1} + b\_f)$$ $$i\_t = \sigma(W\_{ix} x\_t + W\_{ih} h\_{t-1} + b\_i)$$ $$o\_t = \sigma(W\_{ox} x\_t + W\_{oh} h\_{t-1} + b\_o)$$

> \[!TIP] **门控的值到底长什么样？**
>
> 无论是 $$f\_t$$、$$i\_t$$ 还是 $$o\_t$$，由于它们最后经过了 Sigmoid 函数的挤压，它们的输出不是一个单独的数字，而是一个**和记忆单元** $$c\_t$$ **维度完全一样的向量**（例如 512 维）。在这个向量里，每一个数字都被极其严格地限制在 **0 到 1 之间**（比如 `[0.01, 0.98, 0.5, ...]`）。 当这个向量跟记忆单元做逐元素相乘时：如果在某个维度上门控值为接近 0 的数字（如 0.01），就意味着这个维度的信息被“关门”阻断了；如果是接近 1 的数字（如 0.98），信息被放行；如果是 0.5，则通过一半。

对于主干道上的记忆更新和隐状态输出： $$c\_t = f\_t \odot c\_{t-1} + i\_t \odot \tilde{c}\_t$$ $$h\_t = o\_t \odot \tanh(c\_t)$$

我们可以这样直观地理解 LSTM 中这三个门控（阀门）的具体分工（符号 $$\odot$$ 表示逐元素相乘 Hadamard Product）：

1. **遗忘门** $$f\_t$$**（负责“除旧”）**：控制上一时刻的老记忆（$$c\_{t-1}$$）有多少能保留下来。
2. **输入门** $$i\_t$$**（负责“纳新”）**：控制由当前输入提取出的新知识（候选记忆 $$\tilde{c}\_t$$）有多少能写到当前记忆里。 *经过前面这两个门的加和，真正的“当前长期记忆”* $$c\_t$$ *此时已经更新完毕。*
3. **输出门** $$o\_t$$**（负责“展示”）**：控制刚刚更新好的内部记忆 $$c\_t$$，有多少应该被立刻“公开”出去作为当前的短期输出（隐状态 $$h\_t$$）。

> \[!NOTE] **为什么既有记忆单元** $$c\_t$$ **又有隐藏状态** $$h\_t$$**？**
>
> 在传统 RNN 中，唯一的隐状态 $$h\_t$$ 要同时承担**长期记忆转运**和**当前短期对外输出**双重任务，导致其在每次更新时极易被新输入冲刷覆盖。LSTM 的核心精妙之处在于将这两种职能解耦：
>
> 1. **内部长期私密日记本（**$$c\_t$$**）**：它是模型私有的底稿，里面记录了所有的背景故事和时间线，主要通过简单的线性加法平滑更新。这种设计为梯度回传提供了一条无阻碍的“状态高速公路”（State Highway），从根本上缓解了梯度消失。
> 2. **外部当前工作简报（**$$h\_t$$**）**：为了不把繁杂的历史底稿全部暴露给当前时刻的计算节点，$$c\_t$$ 会被 `tanh` 函数整流，再由负责“展示”的**输出门** $$o\_t$$ 进行过滤。模型会根据当前上下文动态决定：“虽然我有一大堆历史记忆（$$c\_t$$），但在这个时间步只会向外展示对当前预测有关联的那一小部分”，从而将其按比例提取作为 $$h\_t$$ 对外输出。这不仅用于当前步的任务预测，也会作为最新焦点传递给下一个时间步。
>
> **那为什么不直接拿** $$c\_t$$ **当作当前时间步的输出，彻底干掉** $$h\_t$$ **呢？**
>
> 1. **保持长程友好的梯度通道**：$$c\_t$$ 之所以能解决梯度消失，就是因为它在时间线上的更新几乎只有加法（$$+$$），没有被任意非线性函数（如 sigmoid 或 tanh）反复挤压。如果直接强迫 $$c\_t$$ 去负责每个时刻的具体输出任务，为了适应输出的非线性边界，它自身的数值分布就会被严重扭曲干扰，这就毁了这条专用于长程传递的“加法状态高速公路”。
> 2. **数值范围的稳定性**：$$c\_t$$ 是一直累加的，如果没有干预，它里面的数值可能会长得非常大或非常小。直接把这种无界的数值暴露给下一层网络是不稳定的。引入 $$h\_t = o\_t \odot \tanh(c\_t)$$ 后，`tanh` 强行把底稿的数值压缩到 `[-1, 1]` 的安全区间内，而输出门 $$o\_t$$ 则进一步屏蔽掉那些对当前无用的剧烈波动。
> 3. **与残差网络（ResNet）的共鸣**：$$c\_t = f\_t \odot c\_{t-1} + \dots$$ 这种主要依靠 **加法（**$$+$$**）** 传递前一层状态的设计，直接启发了 18 年后（2015年）的 Highway Networks，也与同年提出的残差网络（ResNet）异曲同工。它们在数学本质上是一致的，都是在深层计算图（LSTM 是时间深度，ResNet 是空间深度）中添加一条线性的恒等映射旁路（Identity Connection/Highway），让梯度信息在反向传播时能畅通无阻地直接跨越层级回传，从而解决困扰深度学习多年的梯度消失难题。

下图展示了 LSTM 单元的内部结构，详细还原了各个门控与这两条主线的协同运算过程：

```mermaid
graph TD
    %% 输入
    x_t["xₜ<br/>(当前输入向量)"]
    h_prev["hₜ₋₁<br/>(上一隐状态向量)"]
    c_prev["cₜ₋₁<br/>(上一记忆向量)"]

    %% 输出
    c_next["cₜ<br/>(当前记忆向量)"]
    h_next["hₜ<br/>(当前隐状态向量)"]

    %% 门控网络层
    f_gate["遗忘门 fₜ<br/>(Sigmoid)"]
    i_gate["输入门 iₜ<br/>(Sigmoid)"]
    c_cand["候选记忆 c̃ₜ<br/>(tanh)"]
    o_gate["输出门 oₜ<br/>(Sigmoid)"]

    %% 运算节点
    op_mul_f(("⊗"))
    op_mul_i(("⊗"))
    op_add(("⊕"))
    op_tanh("tanh")
    op_mul_o(("⊗"))

    %% 连线：输入到各个门
    x_t --> f_gate & i_gate & c_cand & o_gate
    h_prev --> f_gate & i_gate & c_cand & o_gate

    %% 连线：遗忘机制 (主干道)
    c_prev -->|"状态高速公路"| op_mul_f
    f_gate -.->|"控制遗忘比例<br/>例: [0.01, 0.98, ..., 0.5]"| op_mul_f

    %% 连线：写入机制
    i_gate -.->|"控制写入比例<br/>例: [0.85, 0.05, ..., 0.7]"| op_mul_i
    c_cand -->|"提取的新知识<br/>例: [0.6, -0.9, ..., 0.2]"| op_mul_i

    op_mul_f --> op_add
    op_mul_i --> op_add

    op_add -->|"更新后的记忆"| c_next

    %% 连线：输出机制
    op_add --> op_tanh
    op_tanh --> op_mul_o
    o_gate -.->|"控制输出比例<br/>例: [0.95, 0.12, ..., 0.4]"| op_mul_o
    op_mul_o --> h_next

    %% 样式美化
    style c_prev fill:#f39c12,color:#fff,stroke:#e67e22,stroke-width:2px
    style c_next fill:#f39c12,color:#fff,stroke:#e67e22,stroke-width:2px
    style f_gate fill:#e74c3c,color:#fff,stroke:#c0392b,stroke-width:2px
    style i_gate fill:#2ecc71,color:#fff,stroke:#27ae60,stroke-width:2px
    style o_gate fill:#3498db,color:#fff,stroke:#2980b9,stroke-width:2px
    style c_cand fill:#9b59b6,color:#fff,stroke:#8e44ad,stroke-width:2px
    style op_mul_f fill:#ecf0f1,stroke:#7f8c8d,stroke-width:2px
    style op_mul_i fill:#ecf0f1,stroke:#7f8c8d,stroke-width:2px
    style op_add fill:#ecf0f1,stroke:#7f8c8d,stroke-width:2px
    style op_mul_o fill:#ecf0f1,stroke:#7f8c8d,stroke-width:2px
    style op_tanh fill:#ecf0f1,stroke:#7f8c8d,stroke-width:2px
```

图 1-3：LSTM 单元结构，记忆单元 $$c\_t$$ 通过线性通路传递信息

这种设计的精妙之处在于：当遗忘门接近 1 且输入门接近 0 时，$$c\_t \approx c\_{t-1}$$，信息可以几乎无损地沿时间传递。这条“高速公路”使得梯度能够跨越较长的距离而不至于消失，从而显著改善了长距离依赖的学习能力。

2014 年提出的**门控循环单元**（Gated Recurrent Unit，GRU）进一步简化了 LSTM 的结构，将遗忘门和输入门合并为一个“更新门”，减少了参数和计算量，同时保持了相近的性能。

LSTM 和 GRU 极大地扩展了 RNN 的能力边界，在 2014-2017 年间主导了几乎所有的 NLP 任务。但它们并没有从根本上解决 RNN 家族的另一个致命缺陷——**串行计算瓶颈**。计算效率太低，就意味着无法真正实用。

## 1.2.5 串行计算：无法逾越的效率壁垒

无论是基础 RNN、LSTM 还是 GRU，它们都有一个共同的结构特征：$$h\_t$$ 的计算必须等待 $$h\_{t-1}$$ 完成。这种**严格的顺序依赖**意味着序列中的元素无法并行处理。

对于长度为 $$n$$ 的序列，RNN 的前向计算需要 $$O(n)$$ 个顺序步骤。在训练阶段，这意味着 GPU 上数千个计算核心中，每个时间步只有一小部分被有效利用。随着训练数据和模型规模的增长，这种效率瓶颈变得越来越不可接受。

下面的对比清楚地说明了这一问题：

| 特性      | RNN/LSTM   | 理想方案       |
| ------- | ---------- | ---------- |
| 顺序操作数   | $$O(n)$$   | $$O(1)$$   |
| 并行度     | 低（串行）      | 高（全并行）     |
| 长距离依赖路径 | $$O(n)$$ 步 | $$O(1)$$ 步 |
| GPU 利用率 | 低          | 高          |

表 1-1：RNN/LSTM 与理想方案的性能对比

正是这种对并行计算的渴望，推动了两个方向的探索：一是将 CNN 引入序列建模，二是注意力机制的诞生。

## 1.2.6 CNN 在序列建模中的尝试

卷积神经网络（Convolutional Neural Network，CNN）最初为图像处理设计，但研究者发现一维卷积同样可以对序列进行建模，而且**天然支持并行计算**。

一维 CNN 通过滑动固定大小的滤波器（核）在序列上提取局部特征，不同位置的卷积运算可以同时进行。2017 年前后，Facebook 提出的 ConvS2S（卷积序列到序列模型）证明了纯 CNN 架构也能胜任机器翻译任务，且训练速度远快于 RNN。

然而，CNN 的根本局限在于其**有限的感受野**（Receptive Field）。单层 CNN 只能看到局部窗口内的几个元素。要捕捉距离较远的依赖关系，必须堆叠多层卷积，使感受野逐层扩大。对于普通连续卷积，距离为 $$d$$ 的两个元素需要大约 $$O(d/k)$$ 层卷积（$$k$$ 为核大小）才能使两者的感受野相互覆盖；只有采用空洞卷积或指数扩张的结构时，路径长度才可能降到 $$O(\log\_k n)$$。这意味着 CNN 仍必须经过多层才能跨越长距离，梯度消失问题没有被根本消除。

此外，CNN 对序列中元素的绝对位置不太敏感——同样的滤波器在不同位置提取的是相同模式的特征，这在某些需要区分位置的任务中是一个缺点。

RNN 的串行瓶颈和 CNN 的局部视野限制，共同指向了一个关键需求：**是否存在一种机制，既能实现全并行计算，又能在任意两个位置之间建立直接连接？** 答案是 [注意力机制](/llm_internals/di-yi-bu-fen-ji-chu-pian/01_introduction/1.3_attention_birth.md)。
