2. Transformer 架构总览

好,我们正式开始。这一章,我要带你从十层楼的高度,看一眼 Transformer 到底长什么样。别急着钻细节,咱们先搞清楚它的骨架。

Transformer 说白了就是一个“编码器-解码器”结构。但跟我之前见过的 Seq2Seq 模型比,它有一个巨大的不同:它没有循环,也没有卷积。全靠注意力机制撑起整个场子。

核心思路:输入一串东西,输出另一串东西。中间全靠“注意力”来建立全局依赖关系。

我第一次看这个结构时,心里是犯嘀咕的:“没有循环?那序列的顺序信息怎么保留?”嗯,这就是位置编码要干的事,我们后面会细聊。先记住:它把“顺序”也当作一种特征,硬塞进了输入里。

2.1 宏观架构:编码器 + 解码器

整个 Transformer 分成两大块:左边的编码器(Encoder)和右边的解码器(Decoder)。编码器负责“理解”输入序列,解码器负责“生成”输出序列。你想想看,这不就是我们做机器翻译时的标准套路吗?

我用下面这张图把核心流程画出来。嗯,这张图是本章最重要的参照物。

Transformer 架构总览 编码器(Encoder) 输入嵌入 + 位置编码 多头自注意力 层归一化 + 残差连接 前馈神经网络 层归一化 + 残差连接 解码器(Decoder) 输出嵌入 + 位置编码 掩码多头自注意力 (防止看见未来) 层归一化 + 残差连接 编码器输出 编码器-解码器交叉注意力 前馈神经网络

注意这个细节:编码器是“单向”理解,解码器则是“自回归”生成——它生成第 t 个词时,只能看到前面 t-1 个词。那个“掩码”就是干这个用的。

图里画得很清楚,我再拆解一下各部件的职责:

  • 编码器(左侧):把输入序列映射成一串连续的表示(数学上就是一串向量)。它不负责生成,只负责“理解”。
  • 解码器(右侧):接收编码器的输出,然后一步步地生成目标序列。每生成一个词,它都会重新“看一眼”编码器的输出。
  • 两者之间的连接:就是那个“交叉注意力”层。它让解码器能够从编码器的输出中提取相关信息。

我曾经踩过这个坑:在实现时,把解码器第一层自注意力的掩码给忘了。结果训练时模型疯狂“作弊”,预测准确率看起来奇高,一测试就原形毕露。掩码一定要加,而且得严格上三角。

2.2 每个编码器层:到底有什么?

Transformer 的编码器通常是“堆叠”的。论文里用的是 6 层,但实际项目中我见过 12 层、24 层甚至更多。每一层都一模一样,但参数不同。

每个编码器层就两大件:

  1. 多头自注意力机制(Multi-Head Self-Attention)——让每个位置“看”到所有其他位置。
  2. 前馈神经网络(Feed-Forward Network)——一个简单的两层全连接网络,给每个位置单独做一次“思考”。

这两大件外面都包了一层“残差连接”和“层归一化”。残差连接帮梯度流通,层归一化让训练稳定。我个人习惯是先层归一化再做子层,这叫 Pre-LN,比原始的 Post-LN 训练起来更稳。

配置参考:以 Base 模型为例,每层维度是 512,前馈网络隐藏层是 2048,注意力头数是 8。这些数字在后面的章节会反复出现。

2.3 每个解码器层:多了什么?

解码器层比编码器层多了一个“交叉注意力”子层。所以它有三件套,按顺序是:

步骤 子层 作用
1 掩码多头自注意力 解码器自己回头看已生成的内容,但不准看未来的词
2 编码器-解码器交叉注意力 从编码器的输出里提取与当前生成位置相关的信息
3 前馈神经网络 非线性变换,增强表达能力

每次做完一个子层,同样要做残差连接和层归一化。你可能会问:“为什么自注意力还要掩码?”因为解码器是自回归的。如果它能看见未来的词,那训练时的损失函数就形同虚设了。嗯,推理时也一样,它只能看见之前生成的东西。

一个实践小技巧:在训练时,可以把所有目标序列一次性喂给解码器,利用掩码实现并行计算。这叫 Teacher Forcing。我刚开始写代码时没注意这个,一个一个跑,慢得让人崩溃。

2.4 嵌入层与位置编码:让模型知道“顺序”

模型输入本质上是一个一个的词(或子词)。这些词先要通过嵌入层(Embedding)变成一个稠密向量。但光有词向量不够——Transformer 没有循环结构,它分不清哪个词在前哪个词在后。

所以我们需要给每个位置“编码”。论文里用的是正弦和余弦函数:

PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

其中 pos 是位置索引,i 是维度索引。这个公式的好处是:模型可以学到相对位置的关系。这些年也出现了很多替代方案,比如可学习位置编码、旋转位置编码(RoPE)。我个人项目里更喜欢 RoPE,它对长序列更友好。

总结一下输入处理流程:

  • 词索引 → 词嵌入(Word Embedding)
  • 位置索引 → 位置编码(Positional Encoding)
  • 两者相加 → 送入第一层编码器

2.5 注意力机制的核心逻辑(一句话版本)

很多教程一上来就扔公式:Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V。我理解你看到这个公式时的心情。别急,我们先把它的直觉说清楚。

注意力机制干的事,说白了就是:给定一个查询(Query),在一堆键值对(Key-Value)中,找到最相关的信息。

  • Q(查询):当前要问的问题。
  • K(键):所有候选答案的标签。
  • V(值):所有候选答案的实际内容。

计算步骤只有三步:

  1. 点积算分:用 Q 和每个 K 做点积,得到一个分数。
  2. 缩放 + Softmax:分数除以 d_k 的平方根,然后归一化成概率分布。
  3. 加权求和:用这个概率分布去加权 V,得到最终的输出。

我个人的理解方式:把 softmax 之后的权重矩阵,想象成一张“热力图”。它告诉我们当前这个位置,到底应该“看”序列里的哪些位置。这一章的标题“注意力权重矩阵”说的就是这张图。

在自注意力里,Q、K、V 都来自同一个序列。在交叉注意力里,Q 来自解码器,K 和 V 来自编码器的输出。

2.6 多头注意力:为什么要有多个头?

一个头只能捕捉一种关系模式。比如有的头关注语法关系,有的头关注语义相似,有的头关注位置距离。如果你只有一个头,所有这些模式就混在一起,难以区分。

多头注意力的做法是:把 Q、K、V 分别映射到 h 个低维子空间,在每个子空间里独立做注意力,最后把所有头的结果拼起来。这样每个头可以学到不同的关注模式。

Base 模型配置:

  • 模型维度 d_model = 512
  • 头数 h = 8
  • 每个头的维度 d_k = d_v = 512 / 8 = 64

你想想看,8 个头,每个头在 64 维的空间里做注意力,相当于有 8 双不同的眼睛在看同一个句子。这比一双眼睛看得全面多了。