1. 1. Transformer 个人入门
    1. 1.1. 序列的描述
      1. 1.1.1. Token、序列长度 N
      2. 1.1.2. 特征维度 D
      3. 1.1.3. 序列的矩阵表示
      4. 1.1.4. Batch(批大小)与 N 的区别
      5. 1.1.5. 具体示例
        1. 1.1.5.1. 语言序列
        2. 1.1.5.2. 动态温度 / 压力演化序列
      6. 1.1.6. 小结
    2. 1.2. Transformer 的基本原理
      1. 1.2.1. 中间表示:不是结果,而是“状态”
      2. 1.2.2. Transformer 想学到什么,而传统方法学不到什么?
        1. 1.2.2.1. 自然语言示例:上下文相关的语义
        2. 1.2.2.2. 时间序列示例:从观测值到系统状态
      3. 1.2.3. Transformer 是如何“使用” \(y_n\) 的?
        1. 1.2.3.1. 自回归语言模型(GPT):\(y_n \rightarrow\) 概率分布
        2. 1.2.3.2. 分类 / 回归任务:\(y \rightarrow\) 决策
        3. 1.2.3.3. 时间序列预测:\(y_t \rightarrow\) 未来
      4. 1.2.4. 小结
    3. 1.3. 注意力计算
      1. 1.3.1. 从加权求和的直觉开始
      2. 1.3.2. 用相似度来确定注意力权重
      3. 1.3.3. 为什么还需要 Q、K、V?
      4. 1.3.4. 动态权重 vs 固定权重(Transformer 的关键差异)
      5. 1.3.5. 多头注意力(Multi-Head Attention)
      6. 1.3.6. 小结
    4. 1.4. 实际 Transformer 架构:深层网络的基石
      1. 1.4.1. 残差连接(Residual Connections)
        1. 1.4.1.1. 破碎梯度问题与解决方案
        2. 1.4.1.2. Transformer 中的应用
      2. 1.4.2. 基于位置的前馈网络(Position-wise FFN)
      3. 1.4.3. 位置编码(Positional Encoding)
        1. 1.4.3.1. 为什么是相加而不是拼接?
        2. 1.4.3.2. 位置编码的设计原则
        3. 1.4.3.3. 正弦位置编码(Sinusoidal Position Embedding)
    5. 1.5. 三种 Transformer 架构
      1. 1.5.1. Encoder-only:理解与特征提取专家
        1. 1.5.1.1. 输入输出的同构性
        2. 1.5.1.2. 变长序列与批量处理(Padding & Masking)
        3. 1.5.1.3. 长度限制(Context Window)
        4. 1.5.1.4. 应用场景:从分类到系统建模
        5. 1.5.1.5. 总结
      2. 1.5.2. Decoder-Only:生成式艺术与自回归专家
        1. 1.5.2.1. 训练的悖论:如何并行训练串行逻辑?
        2. 1.5.2.2. 核心机制:因果掩码(Causal Mask)
        3. 1.5.2.3. 推理(Inference)与训练的差异
        4. 1.5.2.4. Decoder-Only 的应用:不仅是写小说,也能做时序预测
        5. 1.5.2.5. 总结
      3. 1.5.3. Encoder-Decoder:全能的翻译官
        1. 1.5.3.1. 核心机制:交叉注意力(Cross-Attention)
        2. 1.5.3.2. 解耦的艺术:\(N \to M\) 的映射
        3. 1.5.3.3. 工作流程演示
        4. 1.5.3.4. 训练与推理的差异
        5. 1.5.3.5. 实战案例:使用 Encoder-Decoder 做长时序预测
      4. 1.5.4. 总结对比

Transformer 个人入门

Transformer 个人入门

序列的描述

Transformer 是一种用于处理序列(sequence)的模型,因此在学习 Transformer 之前,我们首先需要明确:什么是序列,以及序列在计算机中是如何被表示的。下面我们分别以语言数据和时序数据为例来说明。

下面这两种数据,本质上都是序列:

1
2
God said Let there be light and there was light
[0s, 300K, 1Mpa], [1s, 310K, 1.1Mpa], [2s, 315K, 1.2Mpa]

Token、序列长度 N

在计算机中处理序列时,我们会将序列中的基本元素进行向量化:用一个向量来描述序列中的一个基本单元。

  • 在语言序列中,这个基本单元可以是一个词(word)
  • 在时序数据中,这个基本单元可以是某一个时间点的观测值(例如由时间、温度、压力组成的一组数)

我们把这样一个向量化后的基本单元称为一个 token。例如:

  • 对于第一个句子,如果我们把每个词作为一个 token,那么这个序列一共有 10 个 token
  • 对于第二个时序数据,一共有 3 个时间点,因此这个序列就有 3 个 token

我们用 N 表示序列中 token 的数量,即序列长度。序列中的第 (n) 个 token 是一个向量,记作 \(\vec{x}_n\)

特征维度 D

用于描述一个 token 的向量,其维度我们用 D 表示。

  • 在语言模型中,Embedding 模型负责将词映射为向量。例如,如果每个词都用一个 128 维向量表示,那么,\(D=128\)
  • 在上面的时序数据示例中,每个时间点由时间、温度、压力三个数值构成,因此 \(D = 3\)

向量中第 (\(i\)) 个维度的数值 (\(x_{ni}\)),称为 特征(feature)。例如在时序数据中,1s310K1.1Mpa 都是某个 token 的特征。

序列的矩阵表示

N(序列长度)D(特征维度),我们就可以完整地描述一个序列。

因此,一个序列在数学上可以表示为一个 \(N\times D\) 的矩阵,即 N 行、D 列

在后面学习 Transformer 时我们会看到:

  • N 决定了 self-attention 中注意力矩阵的大小(\(N \times N\)),模型的计算量和内存消耗与 \(N^2\) 成正比
  • D 决定了线性层权重矩阵的规模(通常是 \(D \times D\)),决定模型参数量。实际中,在进入 Transformer 之前,往往会通过线性层将特征维度映射到更大的 D。

Batch(批大小)与 N 的区别

在训练或推理时,我们通常会一次性向模型输入多个相互独立的序列。这个并行处理的序列数量称为 Batch Size,记为 B

需要特别强调的是:

  • B 与模型结构和参数量无关
  • 增大 B 只是把同样的计算复制多份并行执行

在刚接触 Transformer 时,Batch 和 N 非常容易被混淆,但它们的作用完全不同。

一个关键的区分方式是问自己:

Transformer 的 Attention 是在哪个维度上进行两两交互的?

答案是:在 N 这个维度上,而不是 Batch 维度

因此,在理解 Transformer 的核心机制时,可以暂时忽略 Batch,只关注单个序列即可

具体示例

语言序列

1
2
3
Batch = 44 句话)
N = 128 (每句话 128 个 token)
D = 768 (每个 token 的向量长度)

张量形状为:

1
[Batch, N, D] = [4, 128, 768]

动态温度 / 压力演化序列

1
2
3
Batch = 1616 条实验数据)
N = 10001000 个时间点)
D = 3 (时间、温度、压力)

张量形状为:

1
[Batch, N, D] = [16, 1000, 3]

小结

  • N:序列内部的长度(token / 时间点)
  • D:每个 token 的特征维度
  • Batch:并行处理的序列数量

Transformer 的核心复杂度来自 N 维度上的两两交互,而不是 Batch。

Transformer 的基本原理

在理解了什么是序列之后,我们接下来要回答的问题是:Transformer 对序列做了什么?

从最抽象的角度看,Transformer 的基本构件是一个作用在序列上的函数。它以一个序列表示作为输入,并输出一个长度不变、维度不变,但语义更丰富的新序列表示

形式化地说,设输入序列表示为一个 \(N \times D\) 的矩阵 \(\boldsymbol{X}\),其中 \(N\) 是序列长度,\(D\) 是每个 token 的特征维度。Transformer 的一层可以表示为: \[ \tilde{\boldsymbol{X}} = \operatorname{TransformerLayer}(\boldsymbol{X}) \] 也就是说,我们在某个嵌入空间中有一组 token: \[ x_1, x_2, \ldots, x_N \] Transformer 会将它们映射为另一组数量相同的 token: \[ y_1, y_2, \ldots, y_N \] 这些新的 token 位于一个新的嵌入空间中,该空间被训练为能够捕捉更丰富的语义或结构信息

中间表示:不是结果,而是“状态”

需要特别强调的是,\(\boldsymbol{y}_n\) 并不是模型的最终输出。它是一个中间表示(intermediate representation),隐式地编码了输入序列 \(\boldsymbol{x}_1,\ldots,\boldsymbol{x}_N\) 之间的复杂关系。

这些关系包括但不限于 token 在上下文中的语义角色、与其他 token 的依赖关系、对下游任务有判别意义的方向和结构等。正是基于这种已经“理解了序列”的中间表示,我们才能在其后接入不同的任务头(output head),完成不同的应用,例如大语言模型中的文本生成、时间序列中的未来状态预测、分类、回归、异常检测等任务。

这些最终输出层的结构可以非常简单(通常只是若干全连接层),原因在于序列的核心结构信息已经被隐式地编码进了 \(\boldsymbol{y}\)。后续层所做的事情,并不是“再去理解序列”,而只是“把已经理解好的信息读出来”。

Transformer 想学到什么,而传统方法学不到什么?

为了理解这一点,我们仍然分别以自然语言时间序列作为例子。

自然语言示例:上下文相关的语义

考虑下面两个句子:

1
2
I swam across the river to get to the other bank.
I walked across the road to get cash from the bank.

单独看 bank 这个词,它是高度多义的,可以表示“河岸”,也可以表示“银行”。只有结合上下文,才能确定其真实含义。更进一步,并不是所有上下文词对 disambiguation 的贡献是相同的。在第一个句子中,swamriver 对判断 bank 是“河岸”尤为关键。在第二个句子中,cash 对判断 bank 是“金融机构”起决定性作用。也就是说,不同上下文中,不同位置的词具有不同的重要性。且这种“重要性分布”是随输入句子而变化的。而在传统神经网络(如固定窗口的 MLP 或 CNN)中,每个输入维度对输出的影响由固定权重决定。一旦训练完成,权重便不再随具体输入而改变。很难动态地刻画“这一次,应该更关注哪些词”。

Transformer 输出的 \(y_n\) 学到了什么呢?

bank 所在位置对应的输出 \(y_n\) 为例,它不再只是 bank 的“词典意义”,而是一个上下文相关的表示,隐式包含了当前语境下的语义指向、与其他 token 的依赖关系、对下游任务(翻译、生成、分类)最有用的特征方向等。这种表示对人类是不可直接解读的,但在向量空间中,它已经把“这是河岸”与“这是银行”拉到了完全不同的区域。

时间序列示例:从观测值到系统状态

再看一个时间序列的例子,例如某物理系统的启动过程:

1
x_t = [t, T_t, P_t]

其中 \(t\) 为时间,\(T_t\) 为 温度,\(P_t\) 为压力。原始输入 \(x_t\) 只是瞬时观测值,并不直接包含比如系统当前是否处于启动阶段、温度上升是线性的还是即将发生跃迁、当前状态是否接近不稳定区域等信息。经过 Transformer 处理后,得到的:

\[ y_t = f(x_1, x_2, \ldots, x_t) \] 可以被理解为:在看到完整历史之后,对系统当前“状态”的表示。这个状态向量可能隐式编码了长期趋势与短期波动、相对于历史均值的偏移、是否接近某个动力学拐点等。同样,这些信息并没有显式写成“标签”,而是被压缩进了 \(y_t\) 所在的嵌入空间中。

Transformer 是如何“使用” \(y_n\) 的?

自回归语言模型(GPT):\(y_n \rightarrow\) 概率分布

在 GPT 类模型中,流程如下:

1
2
3
4
5
tokens → embedding → Transformer → y_1, ..., y_n

Linear + Softmax

下一个 token 的概率

数学上: \[ P(\text{next token}) = \operatorname{softmax}(W y_n) \] 其中 \(W\) 为词表投影矩阵,\(y_n\) 为最后一个位置的上下文状态表示。需要强调的是:并不是把 \(y_n\)“翻译成文字”,而是用它来计算“下一个 token 的概率分布”。这种方式之所以有效,是因为训练目标本身就是让 \(y_n\) 包含足够的信息,使得通过一个简单的线性映射,就能选出正确的下一个词。因此语义并不体现在向量是否“可读”,而体现在它是否对预测任务是充分的

分类 / 回归任务:\(y \rightarrow\) 决策

例如句子情感分类,常见做法是:使用 [CLS] 对应的 \(y_{\text{CLS}}\),或所有 \(y_i\) 的 pooling 表示。然后接一个简单的分类头:

1
y → Linear → label

标签可以是正面 / 负面、是否异常、所属阶段等。这里的关键在于 \(y\) 是完成判别任务所需的“充分统计量”。

时间序列预测:\(y_t \rightarrow\) 未来

例如预测下一个时间点的温度:

1
2
3
4
5
x_1, ..., x_t → Transformer → y_1, ..., y_t

Regression Head

预测 x_{t+1}

此时 \(y_t\) 表示的是:在看到完整历史之后,对当前系统状态的内部表示。而不是原始温度或某个可直接解释的物理量。

小结

所以 Transformer 的核心并不是“生成新 token”,而是通过上下文建模,把局部观测重写为全局感知的状态表示。

  • 在语言中,\(y_i\) 表示“这个词在这句话里的含义”
  • 在时间序列中,\(y_t\) 表示“这个时刻在整个演化过程中的状态”

这些表示本身不面向人类,而是为后续预测、决策和生成服务。

注意力计算

我们从逻辑上,一步一步理解 Transformer 中注意力机制(attention)的引入动机与计算过程。

在前一节中我们提到,Transformer 学到的输出表示 \(y_n\) 相对于输入表示 \(x_n\) 的一个核心变化在于:它编码了序列中 token 之间的相互关系,例如“哪个 token 对当前 token 更重要”。这种“重要性”的刻画,正是注意力(attention)的本质。

从加权求和的直觉开始

对第 \(n\) 个输出向量 \(y_n\) 来说,输入序列中不同的 token \(x_m\) 对它的影响程度应该是不同的。一个自然的建模方式是,将 \(y_n\) 定义为输入 token 的加权和: \[ y_n = \sum_{m=1}^N a_{nm}\, x_m \] \(a_{nm}\) 表示“第 \(n\) 个输出对第 \(m\) 个输入的关注程度”,被称为注意力权重。为了让它具有“分配注意力”的含义,我们通常要求: \[ a_{nm} \ge 0, \quad \sum_{m=1}^N a_{nm} = 1 \] 这样,\(a_{n1},\ldots,a_{nN}\) 就构成了一个概率分布,表示在计算 \(y_n\) 时,对各个输入 token 的关注比例。

用相似度来确定注意力权重

那么,注意力权重 \(a_{nm}\) 应该如何确定呢?

直觉上,如果两个 token 的表示越相似,那么它们之间的关联就越强。在线性向量空间中,点积是一种最简单、也最常用的相似度度量方式。

对于序列中第 \(n\) 个 token 和第 \(m\) 个 token,其相似度可以表示为: \[ x_n^\top x_m \] 如果我们希望计算第 \(n\) 个 token 对所有 token 的依赖关系,可以将所有 token 堆叠成一个矩阵: \[ X \in \mathbb{R}^{N \times D} \] 那么,相似度向量可以写为: \[ X x_n^\top \in \mathbb{R}^{N} \] 其中第 \(m\) 个分量表示 \(x_n\)\(x_m\) 的相似度。

为了将相似度分数转化为满足非负、归一化约束的注意力权重,我们对相似度向量使用 Softmax 函数。

Softmax 的定义为: \[ \operatorname{Softmax}(\boldsymbol{x})_j = \frac{\exp(x_j)}{\sum_{k=1}^{K}\exp(x_k)} \] 它将一个 \(K\) 维实向量映射为一个离散概率分布。

如果对矩阵中的每一行分别应用 Softmax(行归一化),我们就可以写出整体的输出矩阵形式: \[ Y = \operatorname{Softmax}(X X^\top)\, X \] 这里:

  • \(\operatorname{Softmax}(\cdot)\)逐行作用的算子
  • 每一行表示“一个 token 对所有 token 的注意力分布”

这一过程称为自注意力(self-attention)。由于相似度度量使用的是点积,因此也称为点积自注意力(dot-product self-attention)

为什么还需要 Q、K、V?

上述形式虽然已经能表达 token 之间的关系,但它有两个明显的问题:

  1. 没有可学习参数

    从输入到输出的变换是固定的,模型无法从数据中学习“应该关注哪些特征”。

  2. 所有特征对相似度贡献相同

    点积默认认为向量的每一维都同等重要,但在实际任务中,我们往往希望模型在不同子空间中关注不同模式。

为了解决这两个问题,Transformer 引入了三组可学习的线性映射: \[ \begin{aligned} Q &= X W^{(q)} \\ K &= X W^{(k)} \\ V &= X W^{(v)} \end{aligned} \] 其中:

  • \(Q\):Query(查询)
  • \(K\):Key(键)
  • \(V\):Value(值)
  • \(W^{(q)}, W^{(k)}, W^{(v)} \in \mathbb{R}^{D \times D}\) 为可训练参数

于是,注意力计算可以写为: \[ Y = \operatorname{Softmax}(QK^\top)\, V \] 其中:

  • \(QK^\top \in \mathbb{R}^{N \times N}\)
  • \(Y \in \mathbb{R}^{N \times D}\)

可以用信息检索来类比这一过程:

  • Query(Q):当前 token 想要寻找的信息
  • Key(K):每个 token 提供的“索引特征”
  • Value(V):真正要聚合的信息内容

注意力的本质就是:通过 Query–Key 的相似度,决定从哪些 Value 中取信息,以及取多少。

如果 Query 和 Key 的每个分量都是均值为 0、方差为 1 的独立随机变量,那么它们点积的方差将随维度 \(D\) 线性增长。这会导致:

  • 点积数值过大
  • Softmax 输出分布过于尖锐
  • 梯度不稳定

因此,在 Transformer 中对点积进行缩放,得到: \[ \operatorname{Attention}(Q,K,V) = \operatorname{Softmax}\!\left(\frac{QK^\top}{\sqrt{D}}\right)V \] 这就是缩放点积自注意力

动态权重 vs 固定权重(Transformer 的关键差异)

与传统神经网络相比,注意力机制引入了一个关键变化:

  • 传统神经网络:激活值 × 固定权重
  • Transformer 注意力:激活值 × 依赖输入数据的注意力权重

如果某个注意力权重接近 0,那么对应的输入路径几乎不会对输出产生影响。而这些权重是随输入变化的,而不是训练后固定的。这也是 Transformer 强大迁移能力的重要原因之一:它学习的不是“某个固定的函数”,而是一种对输入序列结构的响应规则,更接近于学习了一个“泛函”。

需要再次强调的是:注意力输出 \(Y\) 的维度仍然是 \(N \times D\),它本身不是最终任务输出。它的作用是将输入序列中的关系结构显式地提取出来,并编码进表示中。在此基础上,模型才能通过后续层去最小化具体任务的 loss。

多头注意力(Multi-Head Attention)

单一的 QKV 投影可能会将多种关系模式平均在一起,例如语法关系、语义相似性、时间或阶段依赖等。为此,Transformer 引入了多头注意力。设:

  • \(d_{\text{model}} = D\)

  • 使用 \(h\) 个注意力头

  • 每个头的维度为: \[ d_k = d_v = \frac{D}{h} \]

关键点是:多头注意力不是把 \(X\) 拆分,而是用同一个 \(X\),通过不同的投影矩阵映射到不同子空间。

设有 3 个 token,每个 token 的维度为 4: \[ X = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 2 & 0 & 2 \\ 1 & 1 & 1 & 1 \end{bmatrix} \] 使用 2 个注意力头,则: \[ d_k = d_v = 2 \] 每个头都有独立的 \(W_Q^{(h)}, W_K^{(h)}, W_V^{(h)} \in \mathbb{R}^{4 \times 2}\),比如: \[ W_Q^{(1)} = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 0 \\ 0 & 1 \end{bmatrix} \]

以第一个头为例: \[ Q^{(1)} = X W_Q^{(1)} =\begin{bmatrix} 1·1+1·1 & 0·1+0·1 \\ 0+0 & 2+2 \\ 1+1 & 1+1 \end{bmatrix} = \begin{bmatrix} 2 & 0 \\ 0 & 4 \\ 2 & 2 \end{bmatrix} \in \mathbb{R}^{3 \times 2}Q^{(1)} \] 后续步骤与单头注意力完全相同。

设每个头的输出为 \(Y^{(h)} \in \mathbb{R}^{N \times d_v}\),最终输出为: \[ \operatorname{Concat}(Y^{(1)}, \ldots, Y^{(h)}) \in \mathbb{R}^{N \times D} \] 从而恢复原始维度。

小结

  • 注意力机制通过数据依赖的加权建模 token 之间的关系
  • Q/K/V 引入了可学习的特征子空间
  • 缩放点积保证了数值稳定性
  • 多头注意力允许模型在不同子空间中并行建模多种关系模式

这一结构构成了 Transformer 的核心计算单元。

实际 Transformer 架构:深层网络的基石

在掌握了注意力机制的核心原理后,我们需要将视线转向 Transformer 的整体架构设计。深度神经网络之所以强大,很大程度上源于其“深度”带来的多层表征能力。实验观察表明(深度学习往往是一门实验学科),增加网络层数通常能显著提升模型的泛化能力。然而,随着层数的增加,训练难度呈现指数级上升,其中最核心的挑战便是梯度问题

残差连接(Residual Connections)

破碎梯度问题与解决方案

在早期的深度神经网络中,通过简单的层层堆叠来训练极深的网络往往以失败告终。主要原因之一被称为破碎梯度(Shattered Gradients)。它是指在没有残差连接的深层网络中,梯度在反向传播时,其空间相关性(Spatial Correlation)会随着层数的增加呈指数级衰减。这意味着,参数空间中相近的两个点,其梯度方向可能完全没有关联,类似于白噪声。这就导致了损失函数的曲面(Loss Landscape)不再是平滑的山丘,而是充满了锯齿状的“悬崖和深谷”。基于梯度的优化算法(如 SGD、Adam)通常假设梯度在参数空间是平滑变化的,因此在这种支离破碎的损失面上,优化算法会彻底失效。

为了解决这一问题,何凯明等人提出了残差连接(Residual Connections),这一技术成为了现代深度学习的基建式标准。

考虑一个由三层变换组成的神经网络: \[ \begin{aligned} z_1 &= F_1(x) \\ z_2 &= F_2(z_1) \\ z_3 &= F_3(z_2) \end{aligned} \] 这里的函数 \(F(\cdot)\) 可以是包含线性变换、激活函数(如 ReLU)和归一化层的复合操作。残差连接的核心思想是构建“恒等映射(Identity Mapping)”,将每一层的输入直接加到输出上: \[ \begin{aligned} z_1 &= F_1(x) + x\\ z_2 &= F_2(z_1) + z_1\\ z_3 &= F_3(z_2) + z_2 \end{aligned} \] 这种结构 \(F(x) + x\) 被称为残差块(Residual Block)

从反向传播的角度来看,加法操作创造了一条“梯度高速公路”,使得梯度可以无损地流向更浅的层,极大地缓解了梯度消失和破碎梯度问题,使得训练成百上千层的网络成为可能。这也隐含了一个硬性约束:残差网络的输入变量和输出变量必须具有相同的维度,以便进行加法运算。

Transformer 中的应用

Transformer 架构深度集成并改良了残差连接。通常,我们将自注意力层或前馈网络层包裹在残差结构中,并配合层归一化(Layer Normalization)。根据归一化位置的不同,分为两种主流架构:

  1. Post-Norm(原始论文架构)\[ Z = \operatorname{LayerNorm}(F(X) + X) \] 归一化在残差相加之后进行。这种方式理论上性能上限高,但训练初期不稳定,需要配合 Warmup 策略。

  2. Pre-Norm(现代主流,如 GPT-3, Llama)\[ Z = F(\operatorname{LayerNorm}(X)) + X \] 归一化在进入子层之前进行。这种结构训练更加稳定,梯度流更顺畅,是目前大模型的首选。

基于位置的前馈网络(Position-wise FFN)

虽然注意力机制非常强大,但它在本质上是线性组合的加权求和。

  • 注意力层的局限:自注意力机制通过计算 \(V\)(Value)向量的加权和来生成输出。虽然权重本身是通过 Softmax 非线性计算得出的,但最终的输出向量仍然位于输入 \(V\) 向量所张成的线性子空间内。这限制了模型对特征进行深层非线性变换的能力。
  • FFN 的作用:为了增强模型的表达能力(Expressivity),我们需要在注意力层之后引入一个标准的非线性神经网络。在 Transformer 中,这被称为基于位置的前馈网络(Position-wise Feed-Forward Networks, FFN)

虽然结构上它是多层感知机(MLP),但被称为“基于位置”是因为:该网络对序列中的每一个 Token 独立地应用相同的权重参数

如果说注意力机制负责处理 Token 之间的横向关系(混合不同位置的信息),那么 FFN 则负责处理 Token 内部的纵向特征(挖掘并扩展特征的深度)。FFN 通常包含两个线性层和一个非线性激活函数(如 ReLU 或 GeLU):

\[ \operatorname{FFN}(x) = \operatorname{Activation}(xW_1 + b_1)W_2 + b_2 \]

因此,一个完整的 Transformer Block 的输出可以表示为(以 Pre-Norm 为例):

\[ \begin{aligned} X' &= X + \operatorname{SelfAttention}(\operatorname{LayerNorm}(X)) \\ \tilde{X} &= X' + \operatorname{FFN}(\operatorname{LayerNorm}(X')) \end{aligned} \]

位置编码(Positional Encoding)

Transformer 的自注意力机制具有排列等变性(Permutation Equivariance)。简单来说,如果你打乱输入句子的单词顺序,自注意力层输出的向量集合也会以同样的顺序被打乱,但向量本身的值不会发生变化(假设没有因果掩码)。

这意味着,对于 Transformer 而言,“我爱你”和“你爱我”在没有额外信息的情况下,仅仅是单词集合的简单重排,模型无法捕捉到语序带来的逻辑差异。为了解决这个问题,我们需要显式地注入位置信息

为什么是相加而不是拼接?

我们构建一个与位置相关的位置向量 \(r_n\),将其注入到输入嵌入 \(x_n\) 中。 \[ \tilde{x}_n = x_n + r_n \]

常有的疑问是:直接相加难道不会破坏原本的词向量信息吗?为什么不使用拼接(Concat)?

  1. 维度效率:拼接会增加向量维度,导致计算量增加。
  2. 高维正交性:在高维空间(如 Transformer 常用的 512 或 768 维)中,两个随机选择的向量在大概率上是近似正交的。这意味着位置信息和语义信息虽然被加在了一起,但在高维向量空间中,它们实际上占据了不同的子空间。模型可以通过线性变换轻松地将它们“解耦”并分别利用。

位置编码的设计原则

构建 \(r_n\) 的方式有很多,但一个理想的位置编码应满足以下条件: 1. 唯一性:每个位置的编码必须是独一无二的。 2. 有界性:数值不能无限增大,否则会破坏梯度的稳定性(排除了直接使用整数 \(1, 2, 3...\) 的方案)。 3. 泛化性:能够处理比训练数据更长的序列。 4. 相对位置感知:能够体现 Token 之间的距离关系(即相对位置比绝对位置更重要)。

正弦位置编码(Sinusoidal Position Embedding)

原始 Transformer 论文采用了一种基于正弦和余弦函数的解析式编码。对于位置 \(pos\) 和维度 \(i\),其计算公式为:

\[ \begin{aligned} PE_{(pos, 2i)} &= \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \\ PE_{(pos, 2i+1)} &= \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \end{aligned} \]

为什么选择这种形式?

这类似于用不同转速的指针来表示时间。低频维度(\(i\) 较小)变化缓慢,如同“时针”;高频维度(\(i\) 较大)变化迅速,如同“秒针”。

更重要的是,这种编码具有优雅的数学性质:对于任意固定的偏移量 \(k\)\(PE(pos+k)\) 可以表示为 \(PE(pos)\) 的线性函数(旋转变换)。这意味着模型可以很容易地通过线性投影来学习到单词之间的相对距离,而不仅仅是死记硬背绝对位置。

三种 Transformer 架构

在构建了基础的 Transformer 层之后,我们如何将它们组合起来解决实际问题?根据堆叠方式和注意力机制的不同,Transformer 衍生出了三大主流架构。首先,我们来看看最擅长“理解”的架构。

Encoder-only:理解与特征提取专家

Encoder-only(仅编码器) 架构,顾名思义,是仅利用 Transformer 的编码器部分组成的网络。最著名的代表就是 BERT(Bidirectional Encoder Representations from Transformers)及其衍生家族(RoBERTa, DeBERTa 等)。

这种架构最核心的特性在于其双向注意力机制(Bi-directional Attention)。与人类阅读文本类似,它允许模型在处理一个 Token 时,同时“看到”它左边和右边的所有内容。这使得它非常擅长提取上下文强依赖的特征。

输入输出的同构性

对于单纯的 Encoder 架构,其最显著的结构特点是输入与输出的序列长度严格一致

  • 输入形状[Batch_Size, Sequence_Length, Input_Dim]
  • 输出形状[Batch_Size, Sequence_Length, Output_Dim]

结构决定性质:无论是内部的自注意力层(Self-Attention)还是前馈网络(FFN),它们都只在特征维度(即最后一个维度)进行变换,而不会改变序列长度 \(N\)。换句话说,你输入多少个 Token,模型就输出多少个对应的向量。每个输出向量都是对应输入 Token 融合了全序列上下文信息后的深度表征。

变长序列与批量处理(Padding & Masking)

虽然 Encoder 架构本身不限制输入长度必须固定,但在实际的工程实现(尤其是 GPU 并行计算)中,我们需要处理“参差不齐”的数据。

问题背景:GPU 喜欢规整的矩阵运算。假设一个 Batch 包含两句话: 1. "Hello" (长度 3) 2. "Natural Language Processing is fun" (长度 8)

解决方案:Padding(填充)

为了将它们打包进同一个 Tensor,我们需要将短句填充至当前 Batch 中最长句子的长度(或预设的最大长度,如 128)。

  1. [CLS] Hello [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] (长度补齐为 8)
  2. [CLS] Natural Language Processing is fun [SEP] (长度为 8)

注:[CLS][SEP] 是 BERT 等模型的特殊标记,分别表示序列开始和结束/分隔。

关键机制:Attention Mask(注意力掩码)

物理上补齐了,但逻辑上不能让模型“学到”这些无意义的 [PAD]。因此,我们需要传入一个 Attention Mask 矩阵。 Mask 的作用是在计算 Attention Score(\(QK^T\))时,将 [PAD] 位置对应的分数设为负无穷(\(-\infty\))。这样经过 Softmax 归一化后,这些位置的注意力权重归零,模型就会彻底忽略填充部分的信息。

长度限制(Context Window)

虽然 Encoder 可以处理变长输入,但这个长度 \(L\) 并不是无限的,它受限于模型预训练时设定的最大位置编码长度(Max Position Embeddings)

  • 典型限制:以 BERT 为例,最大长度通常限制为 512 个 Token。
  • 约束条件:输入序列长度 \(L\) 需满足 \(1 \le L \le 512\)
  • 截断处理:如果输入文本超过此限制,通常需要进行截断(Truncation),丢弃超出的部分。这是因为模型在第 513 个位置没有对应的位置向量(Position Embedding),无法进行计算。

应用场景:从分类到系统建模

Encoder-only 架构本质上是一个强大的非线性函数拟合器。它一次性读取所有输入,并行计算,不依赖于时间步的迭代(即非自回归)。

场景 A:序列标注与回归(Sequence Labeling/Regression)

我们可以利用 Encoder 学习输入变量之间的复杂相互作用,进行“多对多”的映射。

  • 任务:系统状态重构(根据环境参数推导系统状态)。
  • 输入\([100, 4]\) 的矩阵。
    • 100 个时间点(Time Steps)。
    • 4 个特征维度(时间、环境压力、环境温度、电压)。
  • 过程:Encoder 利用注意力机制,捕捉不同时间点、不同环境参数之间的非线性耦合关系。
  • 输出\([100, 2]\) 的矩阵。
    • 对应 100 个时间点的系统内部状态(动态温度、动态压力)。

在这个例子中,模型不预测“未来”,而是推导“当下”。它像一个复杂的传感器校准函数:\(Y = F(X)\)。只要提供完整的条件 \(X\),模型就能直接算出对应的结果 \(Y\),这与经典的深度学习回归任务逻辑完全一致。

场景 B:序列分类(Sequence Classification)

这是 NLP 中最常见的用法(如情感分析)。虽然输出是 \(N\) 个向量,但我们通常只取第一个 Token([CLS])的向量,或者对所有向量取平均(Mean Pooling),将其压缩为一个向量,再接一个全连接层进行分类。

总结

Encoder-only 架构就像一位精通全局的分析师。 * 优势:能同时看到上下文(双向),理解能力极强,特征提取丰富。 * 局限:不能像人类说话一样逐字生成(不擅长生成式任务)。 * 定位:适用于分类、回归、实体识别、系统建模等需要“深度理解”的任务。

Decoder-Only:生成式艺术与自回归专家

Decoder-Only(仅解码器) 架构是目前生成式 AI(如 GPT 系列、Llama、Claude 背后的核心)的主流选择。如果说 Encoder 是为了“理解”并压缩信息,那么 Decoder 就是为了“生成”并演绎信息。

它的核心工作模式是自回归(Autoregressive):即根据已经出现的词(\(x_1, ..., x_{n-1}\))来预测下一个词(\(x_n\))。生成的 \(x_n\) 随后会被追加到输入序列中,作为预测 \(x_{n+1}\) 的依据。

训练的悖论:如何并行训练串行逻辑?

初学者常常会有这样的疑惑: > Decoder-Only 架构怎么训练呢?既然它是生成一个字,再把这个字喂回去生成下一个字,那我们训练的时候,难道也要手动把序列切分成好多段,一个一个喂给模型吗?如果是这样,训练大模型岂不是要几百年?

此外,Transformer 的核心特性是输入序列长度等于输出序列长度。但在推理时,我们明明只需要最后一个预测出的 Token,为什么训练时每个位置都在输出?

答案在于:Teacher Forcing(教师强制)与 Mask(掩码)技术的结合。

在训练阶段,我们拥有“上帝视角”。我们不需要等模型一个个生成,而是直接把完整的正确答案(Ground Truth)一次性喂给模型。

假设我们要训练模型学习序列:[A, B, C, D, E]

数据构建策略(错位预测):我们将序列错开一位,构造输入和目标:

  • 输入 (Input)[A, B, C, D]
  • 目标 (Target)[B, C, D, E]

训练目标是最大化以下概率的乘积: \[ P(B|A) \cdot P(C|A,B) \cdot P(D|A,B,C) \cdot P(E|A,B,C,D) \] 模型需要同时学会:看到 A 预测 B,看到 AB 预测 C,看到 ABC 预测 D…… 这一切都在一次前向传播中完成。

核心机制:因果掩码(Causal Mask)

如果像 Encoder 那样直接把 [A, B, C, D] 扔进 Self-Attention 层,会发生什么?

在没有掩码的情况下,矩阵乘法会让所有 Token 互相“看见”。当模型试图根据位置 1 的 A 预测位置 2 的 B 时,它可以通过 Attention 机制偷看到位置 2、3、4 的内容。这构成了信息泄露(Data Leakage),导致模型无法学会预测,只会学会“抄袭”。

为了防止作弊,我们需要引入遮掩矩阵(Mask Matrix)

假设输入序列是 [A, B, C, D]。Attention 分数矩阵(\(QK^T\))会被加上一个 Mask:

关注 A (t=1) 关注 B (t=2) 关注 C (t=3) 关注 D (t=4)
A 生成特征时 \(-\infty\) \(-\infty\) \(-\infty\)
B 生成特征时 \(-\infty\) \(-\infty\)
C 生成特征时 \(-\infty\)
D 生成特征时

数学原理:Softmax 函数具有性质 \(e^{-\infty} \approx 0\)\[ \operatorname{Attention}(Q, K, V) = \operatorname{softmax}\left(\frac{QK^T + \text{Mask}}{\sqrt{d_k}}\right)V \] 这意味着,右上角的 \(-\infty\) 经过 Softmax 后变成了 0

  • 对于位置 C (t=3)
    • 它想看 A、B、C:权重正常计算。
    • 它想看 D:Mask 是 \(-\infty\),权重归零。
    • 结论:位置 3 输出的向量,虽然在物理计算上和 D 在同一个矩阵里,但在逻辑信息流上完全不知道 D 的存在。

并行训练的魔法(Teacher Forcing):这就是 Decoder-Only 训练快的原因。

  1. 不使用 Mask:你需要 for 循环 4 次,每次算一个 Token,GPU 显存利用率极低,因为每次只算一个小向量。
  2. 使用 Mask:你把 [A, B, C, D] 打包成一个矩阵扔进 GPU。GPU 进行一次巨大的矩阵乘法,然后利用 Mask 把右上角“抹零”。
    • 一次计算,就同时得到了 h1 (只含A的信息), h2 (只含AB的信息), h3 (只含ABC的信息)。
    • 这就是用空间的冗余(更大的矩阵)换取了时间的高效(并行计算)

推理(Inference)与训练的差异

当模型训练好后,投入使用(推理)时,情况发生了变化。因为在预测未来时,我们没有“未来”的 Ground Truth 可以喂给模型。

此时,Decoder 变成了一个滑动窗口预测机器

  1. 输入[A, B, C]
  2. 输出[v1, v2, v3]
    • v1 (基于 A 预测 B):废话,因为输入里已经有 B 了。
    • v2 (基于 AB 预测 C):废话,因为输入里已经有 C 了。
    • v3 (基于 ABC 预测 D):有用! 这是我们未知的未来。
  3. 取值:我们只取 v3,通过采样策略(Argmax 或 Top-k Sampling)得到 Token D
  4. 循环:将 D 拼接到输入最后,变成 [A, B, C, D],重复步骤 1。

代码视角

1
2
3
4
5
6
7
# input_seq 形状: [Batch, Length]
output = model(input_seq)
# output 形状: [Batch, Length, Vocab_Size]

# 训练时:我们需要 output 与 target 计算 Loss
# 推理时:我们只关心最后一个时间步的 logits
next_token_logits = output[:, -1, :]

我们可以发现,推理时每一步都要把 [A, B, C] 重算一遍,非常浪费。工程上使用了 KV Cache 技术,将之前计算过的 Key 和 Value 向量缓存起来,每一步只需计算新进来的 Token D 的 QKV,然后与缓存拼接即可。但这属于工程优化,不改变架构的数学逻辑。

Decoder-Only 的应用:不仅是写小说,也能做时序预测

我们通常认为时序预测(Time Series Forecasting)是 Encoder 的强项(如上文所述的系统状态回归)。但在某些场景下,使用 Decoder-Only 架构进行生成式预测具有独特的优势。

什么时候用 Decoder 做时序预测?

当不仅需要预测数值,还需要建模未来的不确定性,或者未来的预测值会反过来强烈影响更远的未来时,Decoder 是更好的选择。

数据构造的区别:

  • Encoder 方式:特征是 \(X\),目标是 \(Y\)\(X\)\(Y\) 是分离的。
  • Decoder 方式:万物皆 Token。我们将“特征”和“目标值”混合编排成一个序列。

实战案例:风力发电功率预测

假设我们要预测未来 3 小时的风电功率。

  • 协变量(Covariates/Condition):风速(Wind)、风向(Dir)。
  • 目标变量(Target):功率(Power)。

在 Decoder 架构中,我们不像 Encoder 那样输入 [Wind, Dir] 输出 [Power],而是构造一个交错序列。假设一个时间步 \(t\) 的数据包含 \((W_t, D_t, P_t)\)

我们将向量构造为: \[ \text{Token}_t = \text{Embedding}(W_t, D_t, P_t) \] 或者更细粒度地拆分: \[ \text{Seq} = [W_1, D_1, P_1, W_2, D_2, P_2, \dots, W_t, D_t] \xrightarrow{\text{predict}} P_t \] Decoder 的预测过程:

  1. 输入[t-99, ..., t-1, t] 时刻的历史数据(包含风速、风向和已知的功率)。
  2. 生成 t+1:模型预测 Power_{t+1}
  3. 自回归反馈:关键点来了。模型将自己刚刚预测的 Power_{t+1} 当作已知事实,拼接上 t+2 时刻的天气预报(风速、风向),作为新的输入,去预测 Power_{t+2}

优势所在:这种方式让模型能够学习联合概率分布 \(P(y_{t+1}, y_{t+2} | x_{1:t})\)。比如,如果 t+1 时刻模型预测功率突然暴跌(可能意味着风机故障),那么在预测 t+2 时,Decoder 会“看到”这个暴跌的历史,从而推断 t+2 的功率也维持低位。相比之下,非自回归的 Encoder 往往是独立预测每个时间点,容易出现 t+1 极低但 t+2 突然恢复正常的违背物理规律的跳变。

总结

Decoder-Only 是一种“活在当下,预测未来”的架构。它通过掩码机制实现了高效训练,通过自回归机制实现了连贯的推理。无论是生成文本还是预测动态变化的时序数据,它都遵循着“根据过去,生成下一个”的朴素哲学。

Encoder-Decoder:全能的翻译官

Encoder-Decoder(编码器-解码器) 架构是 Transformer 论文(Attention Is All You Need)中最初提出的原始形态。它完美地结合了前两种架构的优势,形成了一种“阅读-理解-生成”的完整工作流。

简而言之,模型的左半部分是编码器,负责“读”;右半部分是解码器,负责“写”。

核心机制:交叉注意力(Cross-Attention)

在 Encoder-Decoder 架构中,两者通过交叉注意力(Cross-Attention)层进行连接:

  1. Encoder(编码器):处理完整的输入序列,输出一个高维的特征表示(Context Vector)。这个输出被转化为 Key (\(K\))Value (\(Value\))
  2. Decoder(解码器):在生成每一个 Token 时,生成自己的 Query (\(Q\))
  3. 交互:解码器用自己的 \(Q\) 去查询编码器的 \(K\),计算注意力权重,然后加权聚合 \(V\)

这意味着:解码器负责提问(Q:“我现在需要什么信息?”),编码器负责提供线索(K/V:“原文里有这些信息”)

解耦的艺术:\(N \to M\) 的映射

Encoder-Decoder 架构最强大的地方在于:输入序列长度 (\(N\)) 和输出序列长度 (\(M\)) 是完全解耦的(Decoupled)。

  • Encoder-Only: 输入 10 个词,输出 10 个向量 (\(N \to N\))。
  • Encoder-Decoder: 输入 10 个词,可以输出 5 个词,也可以输出 20 个词,甚至 100 个词 (\(N \to M\))。

这就像“同声传译”的工作模式:

  • Encoder(左边,阅读者)
    • 任务:负责“读懂”原文。
    • 机制:采用双向注意力(Bidirectional Attention),可以看到整个句子,没有 Mask。
    • 输出:它将长为 \(N\) 的输入序列压缩成了一个包含丰富语义的“记忆库”(Context Memory)。
  • Decoder(右边,写作者)
    • 任务:负责“写出”译文。
    • 机制:和 GPT 一样,采用自回归生成,有 Causal Mask(只能看历史)。
    • 特殊装备:它多了一层 Cross-Attention,用来随时查阅 Encoder 的记忆库。

工作流程演示

让我们看一个具体的例子: * Encoder 输入I love Artificial Intelligence (4 个 Token) * Decoder 输出我 爱 AI (3 个 Token)

第一阶段:编码(Encoder 工作)

Encoder 读入这 4 个 Token,经过多层计算,输出 4 个上下文向量。任务结束。 这 4 个向量会一直静止在显存里,供 Decoder 随时查阅。

第二阶段:解码(Decoder 工作)

Decoder 开始逐个生成:

  1. Step 1:输入 [Start]。它通过 Cross-Attention 去“看” Encoder 那 4 个向量,结合 Start 信号,认为应该翻译主语,输出
  2. Step 2:输入 [Start, 我]。再次通过 Cross-Attention 回看原文,发现 "love" 还没翻译,输出
  3. Step 3:输入 [Start, 我, 爱]。再次查阅原文,将 "Artificial Intelligence" 概括为 AI
  4. Step 4:输入 [Start, 我, 爱, AI]。预测出 [End]。停止。

训练与推理的差异

你可能会问:“Decoder 是不是也要像之前说的那样,一步步循环?”

  • 推理时 (Inference):是的,必须一步步循环(自回归),生成一个词,喂进去,再生成下一个。
  • 训练时 (Training)不需要!依然并行!

在训练时(例如机器翻译任务),我们手里既有原文(Source),也有标准答案译文(Target)。我们可以使用 Teacher Forcing 技术:

  1. Encoder:一次性吃进整句 I love AI
  2. Decoder:一次性吃进整句 [Start, 我, 爱, AI]
    • 利用 Causal Mask 保证在位置 1 () 看不到位置 2 ()。
    • 利用 Cross-Attention 让每一层都能看到 Encoder 的输出。
  3. 计算 Loss:模型一次性并行吐出 [我, 爱, AI, End],并与真实标签计算误差。

实战案例:使用 Encoder-Decoder 做长时序预测

在时序预测领域,Encoder-Decoder 架构(也常被称为 Seq2Seq 模型)非常适合解决“输入历史长度与预测未来长度不一致”的问题。

场景设定

我们监测一个复杂的工业设备。

  • 已知历史 (\(X\)):过去 30秒 的高频传感器数据(输入长度 \(N=30\))。
  • 预测目标 (\(Y\)):未来 70秒 的系统行为趋势(输出长度 \(M=70\))。

注意,这里 \(N \neq M\),且差异巨大,单纯的 Encoder 很难直接处理(因为它倾向于输出 30 个向量),单纯的 Decoder 则可能遗忘早期的历史信息。

Encoder-Decoder 的解决方案:

  1. Encoder 部分(特征压缩)
    • 输入:形状为 [Batch, 30, Features] 的张量。
    • 动作:Encoder 通过自注意力机制,分析这 30 秒内的波动、周期和突变。它不需要输出预测值,而是将这 30 秒的“物理规律”压缩成一组 Context Vectors(比如形状为 [Batch, 30, Hidden_Dim])。
    • 意义:这组向量代表了系统在 \(t=30\) 时刻的“状态指纹”。
  2. Decoder 部分(轨迹生成)
    • 启动:我们需要给 Decoder 一个“启动信号”。通常使用历史序列的最后一个值(\(t=30\) 的值)或者一个特殊的 [GO] Token 作为解码器的第一个输入。
    • 生成过程
      • \(t=31\):Decoder 接收启动信号,通过 Cross-Attention “回顾” Encoder 提取的 30 秒历史状态,结合自身逻辑,预测出第 31 秒的值。
      • \(t=32\):Decoder 接收第 31 秒的预测值(或真实值,如果在训练时),再次“回顾”那 30 秒的历史状态,预测第 32 秒。
      • ...
      • \(t=100\):重复直至生成第 100 秒的数据。

为什么比单纯 Encoder 好?

如果用 BERT 类(Encoder-only)模型,你输入 30 个点,它输出 30 个特征,你必须在最后加一个全连接层把 30 个特征强行映射成 70 个点,这破坏了时序的连贯性。而 Encoder-Decoder 允许 Decoder 根据需要动态地从 Encoder 的历史记忆中提取信息,一步步“推演”出未来的 70 秒,更加符合物理系统的因果演化逻辑。

总结对比

最后,我们将三种架构汇总对比,清晰地展示它们的区别与适用场景:

架构特性 Encoder-Only Decoder-Only Encoder-Decoder
代表模型 BERT, RoBERTa GPT 系列, Llama, Claude Transformer (原版), T5, BART
核心注意力 双向自注意力 (Bidirectional) 单向掩码自注意力 (Causal Masked) 双向 Encoder + 单向 Decoder + 交叉注意力
输入输出长度 严格相等 (\(N \to N\))
(输入 10 个词,输出 10 个向量)
严格相等 (\(N \to N\))
(但在推理时,通过循环生成变为 \(N \to N+M\))
完全解耦 (\(N \to M\))
(输入 10 个,可以输出任意个)
训练方式 掩码填空 (Masked LM)
(完形填空)
下一个词预测 (Next Token Prediction)
(文字接龙)
序列到序列 (Seq2Seq)
(结合了理解与生成)
典型应用 理解任务
情感分析、实体识别、系统状态分类/回归
生成任务
文本创作、代码生成、短期时序预测
转换任务
机器翻译、文本摘要、长时序预测