Skip to content
Self-Knowing

Transformer

约 2979 个字 预计阅读时间 10 分钟

视频链接https://www.youtube.com/watch?v=ugWDIIOHtPA

Transformer 是一种 Sequence-to-Sequence(Seq2Seq)模型,与 RNN 和 CNN 一样,接收一个序列作为输入,输出另一个序列。但它有一个特别之处:在模型内部大量使用了 Self-Attention 这种特殊的层(Layer)。

在 Self-Attention 出现之前,序列建模主要依赖两种架构。

RNN(循环神经网络):

  • 输入是一个序列(如 \(A_1, A_2, A_3, A_4\)),输出也是同一个长度的序列
  • 每个位置的输出能考虑整个句子,因为有双向RNN(Bi-Directional RNN)
  • 缺点:输出之间不能并行计算:必须先算完第1个才能算第2个

CNN(卷积神经网络):

  • 用多个 Filter 扫过序列,每个 Filter 关注相邻的几个向量
  • 优点:Filter之间可以并行计算
  • 缺点:单层 CNN 只考虑局部信息(如只考虑3个向量),要看到长距离依赖需要叠很多层

RNN与CNN在序列建模上的区别:RNN能看全局但不可并行,CNN可并行但感受野有限

RNN 和 CNN 的根本矛盾

RNN 能看全局但串行慢,CNN 能并行但只能看局部。我们需要一种既能并行计算、又能让每个输出看到整个输入序列的层——这就是 Self-Attention。

Self-Attention

Q、K、V 的生成

Self-Attention 的第一步,是把序列中的每一个输入向量 \(A_i\) 转换成三个不同的向量:

  • Query (\(Q\)):负责"去匹配"其他位置的键——相当于搜索引擎中的"查询词"
  • Key (\(K\)):负责"被匹配"——相当于数据库中的"索引键"
  • Value (\(V\)):包含要被抽取的实际信息——相当于"被检索到的内容"

这三个向量是通过三个不同的可学习矩阵 \(W^Q, W^K, W^V\) 对输入做线性变换得到的:

\[Q_i = W^Q A_i, \quad K_i = W^K A_i, \quad V_i = W^V A_i\]

其中: - \(A_i\) 是第 \(i\) 个位置的输入向量 - \(W^Q, W^K, W^V\) 是三个可学习的权重矩阵 - \(Q_i, K_i, V_i\) 分别是第 \(i\) 个位置的 Query、Key、Value

每个输入 \(A_i\) 通过三个不同的矩阵 \(W^Q, W^K, W^V\) 生成对应的 Q、K、V

Scaled Dot-Product Attention

有了每个位置的 Q、K、V 后,计算 Self-Attention 的核心步骤如下:

步骤一:计算 Attention 权重。对每个 Query \(Q_i\),拿它去和所有 Key \(K_j\)(包括自己)做 Dot-Product,得到注意力分数 \(\alpha_{ij}\)

\[\alpha_{ij} = \frac{Q_i \cdot K_j}{\sqrt{d}}\]

其中 \(d\) 是 Q 和 K 的维度。除以 \(\sqrt{d}\) 是为了防止当 \(d\) 很大时,点积的数值过大导致后续 Softmax 梯度消失——这也是为什么这个方法叫做 Scaled Dot-Product Attention。

为什么除以 \(\sqrt{d}\)

当 Query 和 Key 的向量维度 \(d\) 很大时(如512),它们的点积会变大。点积大意味着 Softmax 后的概率分布会变得非常尖锐(几乎所有概率集中在一个位置),梯度接近零,学习不起作用。除以 \(\sqrt{d}\) 是为了把数值拉回合理的范围。

每个 Query 对每个 Key 做 Dot-Product 得到 Attention Score \(\alpha\)

步骤二:Softmax 归一化。对所有 \(\alpha_{i1}, \alpha_{i2}, \ldots, \alpha_{in}\) 应用 Softmax 函数,得到归一化后的注意力权重 \(\hat{\alpha}_{ij}\)

\[\hat{\alpha}_{ij} = \frac{\exp(\alpha_{ij})}{\sum_{k=1}^{n} \exp(\alpha_{ik})}\]

经过 Softmax 后,\(\sum_j \hat{\alpha}_{ij} = 1\),即每个位置对所有位置的关注度总和为 1。

对 Attention Scores 使用 Softmax 归一化

步骤三:加权求和。用归一化的注意力权重 \(\hat{\alpha}_{ij}\) 对所有 Value \(V_j\) 做加权求和,得到第 \(i\) 个位置的输出 \(B_i\)

\[B_i = \sum_{j=1}^{n} \hat{\alpha}_{ij} \cdot V_j\]

这意味着 \(B_i\) 融合了整个序列的信息——每个位置的信息按其对位置 \(i\) 的相关性被加权聚合。

用 Softmax 后的权重 \(\hat{\alpha}\) 对所有 V 加权求和,得到输出 B

矩阵形式的 Self-Attention

以上过程可以用矩阵运算一次性完成。设输入序列矩阵为 \(A\),则:

  1. \(Q = A \cdot W^Q\)\(K = A \cdot W^K\)\(V = A \cdot W^V\)
  2. \(\text{Attention}(Q, K, V) = \text{Softmax}\left(\frac{QK^T}{\sqrt{d}}\right) \cdot V\)

重要:Self-Attention 的完整矩阵公式

\[\text{Attention}(Q, K, V) = \text{Softmax}\left(\frac{QK^T}{\sqrt{d}}\right) V\]

其中: - \(Q, K, V\) 分别是 Query、Key、Value 矩阵(每行对应序列中的一个位置) - \(QK^T\) 计算了所有 Query 和所有 Key 之间的点积(得到一个方阵) - \(\sqrt{d}\) 是缩放因子,\(d\) 是 Q 和 K 的向量维度 - Softmax 沿 Key 维度进行,使每行和为 1 - 最终结果乘以 \(V\) 得到加权的 Value 聚合

Self-Attention 的矩阵形式:\(QK^T\) → Softmax → 乘 \(V\) 得到输出

Multi-Head Attention

单一的 Self-Attention 只能关注一种"相关性模式"。Multi-Head Attention 通过多个独立的注意力头,让模型同时关注不同类型的关联:

  • 每个头有自己的 \(W^Q, W^K, W^V\) 矩阵
  • 每个头独立计算 Self-Attention,得到各自的输出
  • 将所有头的输出拼接(Concatenate)起来,再通过一个线性变换 \(W^O\) 得到最终输出

Multi-Head Attention 的设计动机

不同头可以学会关注不同的关系类型:有的头可能关注局部语法关系(如形容词修饰哪个名词),有的头可能关注长距离语义关系(如代词指代哪个实体),有的头可能关注位置关系。多个头让模型在同一个位置上具备多种"视角"。

Multi-Head Attention:多个独立的注意力头并行计算后拼接

头部数(\(h\))是一个超参数,Transformer 原论文中 \(h=8\),实际使用中常见 \(h=8, 12, 16\) 等。

Positional Encoding

Self-Attention 的一个根本局限是没有内置的位置信息——对于 Self-Attention 来说,"我爱你"和"你爱我"在向量运算上没有区别,因为每个位置都与其他所有位置做无差别的全局交互。

解决办法是位置编码(Positional Encoding):在输入向量 \(A_i\) 上叠加一个代表位置索引 \(i\) 的向量 \(e_i\),使得:

\[\tilde{A}_i = A_i + e_i\]

其中 \(e_i\) 有很多种生成方式:

  • Sinusoidal Encoding(原论文方案):使用不同频率的正弦/余弦函数生成,不同维度对应不同频率
  • Learned Positional Encoding:将位置编码设为可学习参数,端到端训练
  • 相对位置编码:编码的不是绝对位置,而是两个位置之间的相对距离

Positional Encoding:为每个输入位置叠加一个独特的位置向量

Transformer 的完整架构

原始 Transformer 是一个 Encoder-Decoder 的 Seq2Seq 架构,典型任务是机器翻译。输入端读入源语言句子,Encoder 把它编码成一串上下文化表示;输出端 Decoder 根据 Encoder 的结果和已经生成的目标词,逐步预测下一个词。

img

完整的数据流可以按下面的顺序理解:

  1. 输入序列先变成向量:每个 token 经过 Embedding 得到词向量,再加上 Positional Encoding,让模型知道 token 在序列中的位置。
  2. Encoder 读取整个输入序列:输入向量依次通过 \(N\) 个相同的 Encoder Layer。原论文中 \(N=6\)
  3. Encoder 输出一组上下文表示:输出仍然是一串向量,长度与输入序列相同,但每个位置都已经融合了全句信息。
  4. Decoder 逐步生成目标序列:Decoder 接收已经生成的目标端 token,同时通过 Cross-Attention 读取 Encoder 的输出。
  5. Linear + Softmax 得到词概率:Decoder 最后一层输出会经过线性层和 Softmax,变成词表上每个词的概率分布。

Transformer 完整架构:Encoder-Decoder 结构,多层 Self-Attention + FFN

重要:不要把“完整 Transformer”和“现在常说的大模型结构”混为一谈

  • 原论文里的 Transformer 是 Encoder-Decoder 架构;BERT 主要使用 Encoder 部分,适合理解、分类、匹配等任务;GPT 类模型主要使用 Decoder-only 架构,适合自回归生成。理解原始 Encoder-Decoder 结构,可以帮助我们看清后续这些变体到底省略、保留或强化了哪一部分。

Encoder:把输入序列编码成上下文表示

Encoder 的任务不是直接生成词,而是把输入序列变成一组更有上下文的向量表示。每个 Encoder Layer 由两个核心子层组成:

  1. Multi-Head Self-Attention:让输入序列中的每个位置都可以关注其他所有位置。
  2. Position-wise Feed-Forward Network(FFN):对每个位置的向量分别做同一个两层前馈变换,增加非线性表达能力。

每个子层外面都包着一层 Add & Norm

\[\text{AddNorm}(x)=\text{LayerNorm}(x+\text{Sublayer}(x))\]

这里的 \(x+\text{Sublayer}(x)\) 是残差连接,作用是保留原始输入并稳定梯度;LayerNorm 则对每个位置的特征维度做归一化,让深层堆叠更容易训练。

因此,一个 Encoder Layer 的计算可以概括为:

\[H_1=\text{LayerNorm}(X+\text{MultiHeadSelfAttention}(X))\]
\[H_2=\text{LayerNorm}(H_1+\text{FFN}(H_1))\]

其中:

  • \(X\) 是进入当前 Encoder Layer 的序列表示。
  • \(H_1\) 是经过自注意力和 Add & Norm 后的结果。
  • \(H_2\) 是经过 FFN 和 Add & Norm 后的结果,也是该层 Encoder 的输出。

Encoder 中的 Self-Attention 是双向的:每个位置可以看见输入序列里的所有位置,包括自己、前面的词和后面的词。这一点和 Decoder 的 Masked Self-Attention 不同。Encoder 的目标是理解整个输入,因此不需要遮住未来信息。

FFN 为什么叫 Position-wise?

FFN 不是把不同位置混在一起处理,而是对每个位置的向量单独套用同一组参数。位置之间的信息交换主要发生在 Self-Attention 子层;FFN 的作用是在每个位置内部做更强的特征变换。

Decoder:在已生成内容的基础上预测下一个词

Decoder 的结构比 Encoder 多一个注意力子层。每个 Decoder Layer 通常包含三个核心子层:

  1. Masked Multi-Head Self-Attention:只允许当前位置关注已经生成的目标端 token,不能看未来 token。
  2. Cross-Attention(Encoder-Decoder Attention):让 Decoder 读取 Encoder 的输出。
  3. Position-wise FFN:和 Encoder 中一样,对每个位置做前馈变换。

Decoder 的第一层注意力为什么要加 Mask?因为生成任务是自回归的:预测第 \(i\) 个词时,只能依赖第 \(1\)\(i-1\) 个已经生成的词。如果训练时让模型看到标准答案里未来位置的词,它就会“偷看答案”,推理时性能会崩掉。因此 Masked Self-Attention 会把未来位置的注意力分数屏蔽掉。

Cross-Attention 是理解 Decoder 的关键。它不是普通的 Self-Attention,因为 Q、K、V 的来源不同:

  • Query 来自 Decoder 当前层的表示:表示“我现在生成到这里,需要从输入句子里找什么信息?”
  • Key 和 Value 来自 Encoder 的最终输出:表示“输入句子的每个位置有什么信息可以被读取?”
  • 输出回到 Decoder 流程中:Decoder 借此把源语言信息和目标端已生成信息结合起来。

所以 Decoder Layer 的计算逻辑可以简化成:

  1. 先在目标端内部做 Masked Self-Attention,整理“已经生成的上下文”。
  2. 再用 Cross-Attention 去 Encoder 输出中查找与当前生成位置相关的源端信息。
  3. 最后用 FFN 对每个位置做非线性变换。
  4. 每个子层后都接 Add & Norm,保持训练稳定。

训练和推理时 Decoder 的用法不同

训练时,目标序列通常一次性输入 Decoder,但通过 Mask 保证每个位置只能看到它之前的词。推理时没有完整目标序列,模型必须从起始符开始,一个词一个词生成;每生成一个新词,再把它作为下一步 Decoder 的输入。

Add & Norm、FFN 和输出层的位置

Transformer 图中反复出现的 Add & Norm 可以理解为每个子层外面的“稳定器”:Add 是残差连接,Norm 是 Layer Normalization。无论是 Encoder 的 Self-Attention、Encoder 的 FFN,还是 Decoder 的 Masked Self-Attention、Cross-Attention、FFN,后面都会接 Add & Norm。

FFN 一般写作:

\[\text{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2\]

实际实现里也常把 ReLU 换成 GELU。它的输入输出维度通常保持一致,中间层维度更大,用来提升每个 token 表示的表达能力。

Decoder 最后一层输出后,还要接一个 Linear + Softmax。Linear 把隐藏向量映射到词表大小,Softmax 把它变成概率分布。概率最高的词,或者采样得到的词,就是当前步的生成结果。


Created: May 12, 2026
Last update: May 12, 2026

Discussion