python神经网络编程入门(三十五)——Transformer 整体架构——一张图看懂全貌

📍 路标 :本篇位于《从零构建 Transformer》系列 第 6/16 章 · 架构篇

系列主线:注意力思想 → 自注意力 → 多头 → 位置编码 → 编解码架构 → 手撕实现 → 预训练模型 → 实战微调 → 知识收官

进度:▸ 基石篇(1-5) ▸ 架构篇(6-10·本篇) ▸ 预训练篇(11-13) ▸ 实战篇(14-16)
上一篇(三十四):位置编码(Positional Encoding)------给词打上"顺序"的标签


引言:零件的仓库已经满了,该组装大楼了

从第 1 章到第 5 章,我们像在车间里一样,一件一件地把零件造了出来:

  • 第 1-2 章:注意力思想与 Q/K/V 三件套------模型学会"让词和词互相对话";
  • 第 3 章:缩放点积注意力------对话时先打分、缩放、归一化、再加权求和;
  • 第 4 章:多头注意力------一个头不够,8 个头从不同角度同时看;
  • 第 5 章:位置编码------给每个词贴上"我是第几个"的标签,补上"顺序"这个救命信息。

零件齐了吗?齐了。但零件堆在仓库里,还不是一栋楼。注意力、多头、位置编码这些"积木",怎么拼成一个能真正做翻译、做生成的完整模型?这就是架构篇(第 6-10 章)要回答的问题。

这一章,我们不写一行复杂的数学,只做一件事:把整栋 Transformer 大楼的平面图,一笔一笔画给你看。看完这一章,你再回头看论文《Attention Is All You Need》里那张著名的架构图,会觉得它亲切得像老朋友。

🎯 本章目标

  1. 用一张全景图,看懂 Transformer 的"左右两大块":编码器与解码器各管什么;
  2. 逐块拆解编码器:为什么它叫"速读冠军",一次就能读完整个句子;
  3. 逐块拆解解码器:为什么它叫"逐字作者",一个词一个词地憋;
  4. 分清楚三种注意力:自注意力、掩码自注意力、交叉注意力,Q/K/V 到底从哪来;
  5. 走一遍完整的数据流:从源句进来,到目标句出去,每一层的张量形状怎么变;
  6. 讲清"编码器并行、解码器串行"这个最容易被问倒的面试题;
  7. 背下经典超参数:N=6、d_model=512、h=8、d_ff=2048;
  8. 亲手搭一个"微型 Transformer 骨架",让架构图的每一块都对应一段能跑的代码。

一、先看全景图:Transformer 就两大块

不卖关子,直接把整栋大楼的平面图拍在桌面上。下面这张图就是 Transformer 的完整架构,请先花 30 秒只看不读,感受一下整体轮廓:

看完轮廓,我们用一句话给整栋楼定性:Transformer = 编码器(左)+ 解码器(右)

  • 左边一列是编码器(Encoder) :输入是源句 (比如英文 "I love cats"),责任是一次读完整句,把整句话"消化"成一串向量记忆,供右边使用;
  • 右边一列是解码器(Decoder) :输入是目标句 (比如中文"我 爱 猫"),责任是一个词一个词地生成:先出"我",再出"爱",再出"猫",每出一个词,都要回头"查"一下左边编码器留给它的记忆。

所以最直白的比喻是:

编码器是"速读冠军":刷一眼,整本书的内容全记住;解码器是"逐字作者":对着速读冠军做好的读书笔记,一个字一个字地把书重新写出来。

再看图 1 的细节。左边编码器一列,从上到下是:源句嵌入+位置编码 → 编码器层1 → 编码器层2 → ...共N层 → 第N层输出。每一层内部长这样:① 多头自注意力 → ② 前馈网络 FFN,中间穿插着残差连接和层归一化。

右边解码器一列,从上到下是:目标词嵌入+位置编码 → 解码器层1 → 解码器层2 → ...共N层 → Linear+Softmax → 输出词。每一层内部长这样:① 掩码多头自注意力 → ② 多头交叉注意力 → ③ 前馈网络 FFN

注意两条紫色的虚线箭头------它们从编码器的"第 N 层输出"指向解码器每一层的"交叉注意力",箭头上写着 K、V 。这一条线是全图最重要的信息流:编码器把"记忆"(K、V)借给解码器,解码器拿着自己的"问题"(Q)去查这份记忆。我们把这条线单独拎出来讲。


二、编码器:一眼读完整个源句的"速读冠军"

先看左半边。把编码器一列单独放大,就是下面这张图:

**编码器的输入是什么?**是源句的每个词。以 "I love cats" 为例,三个词分别查词嵌入表得到向量,再按第 5 章的方法加上位置编码,就得到输入 X ∈ R ( 3 , d m o d e l ) X \in \mathbb{R}^{(3, d_{model})} X∈R(3,dmodel)(3 个词,每词一个 d m o d e l d_{model} dmodel 维向量)。

**编码器每一层做什么?**看图 2,从下往上走一遍:

  1. ① 多头自注意力:三个词互相"对话"。"I" 会看一眼 "love"、看一眼 "cats",决定自己该吸收多少它们的信息。这是第 4 章的老朋友,Q/K/V 都来自输入 X 自己;
  2. 残差连接 :把原始输入 X X X 绕道相加到自注意力的输出上,得到 X + Attn ( X ) X + \text{Attn}(X) X+Attn(X)。为什么要绕这一下?因为这样梯度能"抄近路"往回传,深层网络才不会"梯度消失"(第 7 章会专门展开);
  3. 层归一化 LayerNorm:把向量拉回统一尺度,训练更稳;
  4. ② 前馈网络 FFN:一个"先扩维再缩回"的小神经网络,让每个词独立地再"想一想"(第 7 章展开);
  5. 再来一次残差 + 层归一化:输出。

关键点:每一层的输出形状和输入一模一样 ,都是 ( 3 , d m o d e l ) (3, d_{model}) (3,dmodel)。所以编码器可以一层一层往上叠,叠多少层都行(论文叠了 6 层)。叠完后,最后一层输出的每个位置的向量,都"吸收"了整句话的信息------这就是编码器记忆 ,记作 Z Z Z。

💡 怎么理解编码器输出 Z? 原来 "I" 的向量只代表 "I" 这个单词;经过 6 层自注意力后,"I" 的向量里已经包含了"I 是句子的主语、它爱 cats、cats 是复数"这些上下文信息。我们把这一串"读懂了整句的向量"叫内存/记忆,英文叫 memory,它就是解码器要反复查询的"笔记"。

编码器为什么是"速读冠军"?因为它一次就能处理完整句话------三个词同时进入自注意力,1 个时间步就把整句消化完,不需要像 RNN 那样一个词一个词地读。这个"并行"的优越性,第 7 节会专门对比。


三、解码器:一个词一个词"憋"出来的"逐字作者"

再看右半边。解码器比编码器复杂一点------它内部多了一种注意力 (交叉注意力),还多了一张遮未来的掩码。单层解剖图如下:

解码器的输入是什么?是目标句------但注意,它右移了一位 :不是直接输入"我 爱 猫",而是输入"<BOS> 我 爱"(<BOS> 是"开始"标记)。为什么右移?因为解码器要做的任务是"看到前面的词,预测下一个词 ":看到 <BOS> 预测"我",看到 <BOS> 我 预测"爱",看到 <BOS> 我 爱 预测"猫"。所以它的输入天然比输出"慢半拍"。

**解码器每一层做什么?**看图 3,三件套:

  1. ① 掩码多头自注意力 :和编码器的自注意力几乎一样,Q/K/V 都来自解码器自己,但多了一张"掩码" ------生成第 t t t 个词时,只许看它左边的词(已经生成的),不许偷看右边的词(还没生成的)。图 3 右上角那个红色小三角,就是"上三角遮罩"的示意:未来位置全部被盖住(细节在第 8 章展开,这一章先记住"有这道保险");
  2. ② 多头交叉注意力 :这是解码器独有的、也是全模型最核心的一环。Q 来自解码器自己 ("我下一步该说什么?"),K、V 来自编码器的记忆 Z("源句里有什么内容可以回答我?")。解码器拿着自己的问题,去查编码器做的笔记------具体查哪个词,由图 5 的热力图可见;
  3. ③ 前馈网络 FFN:和编码器一样,每个词独立地"再想一想"。

每一件之后都跟着残差连接 + 层归一化,和编码器的套路完全一致。

解码器为什么是"逐字作者"?因为它一次只能生成一个词 :先看到 <BOS> 输出"我",再把"我"并进输入,看到 <BOS> 我 输出"爱"......如此循环,直到输出 <EOS>(结束标记)。这就是第 7 节要讲的"串行"。

💡 左侧编码器 vs 右侧解码器,一句话总结

  • 编码器:自注意力 读懂源句,并行,一次搞定;
  • 解码器:掩码自注意力 + 交叉注意力 生成目标句,串行 ,逐词憋。
    编码器负责"读书",解码器负责"写书",交叉注意力就是"写书时翻笔记"。

四、三种注意力:Q、K、V 的三个不同来源

讲到这儿,细心的人会发现:全模型里"注意力"出现了三次,但每次的 Q/K/V 来源都不一样。这是新手最容易混的地方,必须用一张对比图彻底讲清楚:

注意力 出现在哪 Q(问题)来自 K、V(答案)来自 特殊之处
自注意力 编码器每层 源句 X 自己 源句 X 自己
掩码自注意力 解码器每层 目标句 Y 自己 目标句 Y 自己 上三角遮罩,不许看未来
交叉注意力 解码器每层 解码器自己 编码器记忆 Z 两个模型之间的"对话"

看懂这张表,你就是架构通了

  • 自注意力:"词和词互相对话"------源句内部交流;
  • 掩码自注意力:"已生成的词之间对话,但不许剧透"------目标句内部交流,且有防偷看保险;
  • 交叉注意力 :"解码器问,编码器答"------两个模块之间的跨语言交流,这是 Transformer 能翻译的根本原因

注意:三种注意力用的是同一个公式 : A = softmax ( Q K ⊤ d k ) V A = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V A=softmax(dk QK⊤)V。公式从头到尾没变,变的只是 Q/K/V 从哪来。这就是图 4 底部那句话:"变的是原料,不变的是配方"


五、一次完整翻译的"流水账":从输入到输出

理论讲完了,现在把整栋楼"通电"------走一遍从源句到目标句的完整流程。翻译 "I love cats" → "我 爱 猫",整个过程如下:

跟着图 5 的箭头,把流水账从头到尾捋一遍:

第 1 步:编码器读源句(只跑 1 次)。

"I love cats" 三个词分别变成向量(嵌入 + 位置编码),一起进入编码器。经过 N 层自注意力 + 前馈,输出三个"读懂了整句"的向量 z 1 , z 2 , z 3 z_1, z_2, z_3 z1,z2,z3,合起来叫记忆 Z Z Z。注意:编码器全程只运行这一次,之后它就在旁边"待命",随时供解码器查询。

第 2 步:解码器生成第一个词。

解码器输入只有 <BOS>。它先做掩码自注意力(只有一个词,没什么可看的),然后做交叉注意力:拿自己的 Q 去查 Z Z Z 里的 z 1 , z 2 , z 3 z_1, z_2, z_3 z1,z2,z3,加权求和后过 FFN,最后经过 Linear + Softmax 输出一个"词表上的概率分布"------挑概率最大的那个词,就是"我"。

第 3 步:解码器生成第二个词。

把"我"接到输入后面,现在是 <BOS> 我。再跑一遍同样的流程,掩码自注意力让"我"看一眼"<BOS>"(但不能看未来的词),交叉注意力再查一次编码器记忆,输出"爱"。

第 4 步、第 5 步:继续憋。

<BOS> 我 爱 → 输出"猫";<BOS> 我 爱 猫 → 输出 <EOS>,生成结束。

流水账的四个要点:

  1. 编码器跑 1 次,解码器跑 N 词次------编码器是"一次读懂",解码器是"逐词生成";
  2. 每一步解码器的输入都比上一步多一个词------这是"自回归"(Autoregressive)的本质:模型吃自己上一步的输出;
  3. 交叉注意力贯穿解码器的每一步------无论生成到第几个词,都要回头查编码器的记忆,这就是图 1 那些紫色 K、V 箭头的含义;
  4. 每一步都输出一个概率分布------Softmax 之后挑 argmax 就是"贪心生成",后面第 13 章讲 GPT 时会详细展开采样技巧。

六、实操验证(一):亲手搭一个"微型 Transformer 骨架"

理论说得再明白,不如亲手跑一遍。这一节我们搭一个微型 Transformer 骨架 :维度压到 d m o d e l = 4 d_{model}=4 dmodel=4,源句 3 个词、目标句 3 个词,但架构图和真实模型完全一致------图上有哪一块,代码里就有哪一段。代码越简单越好,全程只用 NumPy,复用前几章的零件。

python 复制代码
import numpy as np

def softmax_rows(M):
    e = np.exp(M - M.max(axis=1, keepdims=True))
    return e / e.sum(axis=1, keepdims=True)

def scaled_dot_product_attention(Q, K, V, mask=None):
    d_k = K.shape[-1]
    scores = (Q @ K.T) / np.sqrt(d_k)     # 打分 + 缩放(第3章)
    if mask is not None:
        scores = scores + mask            # 加掩码:未来位置变成 -1e9
    weights = softmax_rows(scores)        # 行归一化成权重
    return weights @ V, weights           # 加权和 + 权重(供查看)

def positional_encoding(max_len, d_model):     # 第5章
    pe = np.zeros((max_len, d_model))
    pos = np.arange(max_len)[:, None]
    i = np.arange(d_model // 2)
    inv = 1.0 / (10000.0 ** (2 * i / d_model))
    pe[:, 0::2] = np.sin(pos * inv)
    pe[:, 1::2] = np.cos(pos * inv)
    return pe

def layer_norm(X, eps=1e-5):                    # 归一化(第7章细讲)
    mu = X.mean(axis=-1, keepdims=True)
    var = X.var(axis=-1, keepdims=True)
    return (X - mu) / np.sqrt(var + eps)

def ffn(X, W1, b1, W2, b2):                     # 前馈网络(第7章细讲)
    return np.maximum(0, X @ W1 + b1) @ W2 + b2

np.random.seed(42)
d_model = 4
vocab_tgt = 6   # <BOS> <EOS> 我 爱 猫 <PAD>
W_emb_src = np.random.randn(3, d_model) * 0.1   # 源句 3 个词的表
W_emb_tgt = np.random.randn(vocab_tgt, d_model) * 0.1
Wq = np.random.randn(d_model, d_model) * 0.1
Wk = np.random.randn(d_model, d_model) * 0.1
Wv = np.random.randn(d_model, d_model) * 0.1
W1 = np.random.randn(d_model, d_model * 2) * 0.1; b1 = np.zeros(d_model * 2)
W2 = np.random.randn(d_model * 2, d_model) * 0.1; b2 = np.zeros(d_model)
Wo = np.random.randn(d_model, vocab_tgt) * 0.1  # 输出投影

# ---------- 编码器:一次读完整句 ----------
X = W_emb_src + positional_encoding(3, d_model)[:3]   # 嵌入 + 位置编码
Q = X @ Wq; K = X @ Wk; V = X @ Wv
Z, _ = scaled_dot_product_attention(Q, K, V)          # ① 自注意力
Z = layer_norm(X + Z)                                 # 残差 + 归一化
Z = layer_norm(Z + ffn(Z, W1, b1, W2, b2))            # FFN + 残差 + 归一化
# 到这里 Z 就是"编码器记忆"

# ---------- 解码器:teacher forcing 一次喂完整目标句 ----------
Y = W_emb_tgt[[0, 1, 2]] + positional_encoding(3, d_model)[:3]  # <BOS> 我 爱
mask = np.triu(np.full((3, 3), -1e9), k=1)            # 上三角掩码
Q = Y @ Wq; K = Y @ Wk; V = Y @ Wv
M, _ = scaled_dot_product_attention(Q, K, V, mask)    # ① 掩码自注意力
M = layer_norm(Y + M)
Q = M @ Wq; K = Z @ Wk; V = Z @ Wv                    # ② 交叉注意力
C, attn = scaled_dot_product_attention(Q, K, V)       #    Q 自解码器,K/V 自编码器
C = layer_norm(M + C)
C = layer_norm(C + ffn(C, W1, b1, W2, b2))            # ③ FFN
logits = C @ Wo                                       # 输出层 Linear

跑起来,看看每一层的 Shape------你会发现每一条都神奇地保持 (3, 4)

复制代码
源句嵌入 + 位置编码  X : (3, 4)
编码器·自注意力       : (3, 4)
编码器·残差 + 归一化  : (3, 4)
编码器·前馈 + 残差 + 归一化(= 编码器记忆 Z): (3, 4)
目标句嵌入 + 位置编码  Y : (3, 4)
掩码矩阵(上三角 = -1e9):
[[ 0.e+00 -1.e+09 -1.e+09]
 [ 0.e+00  0.e+00 -1.e+09]
 [ 0.e+00  0.e+00  0.e+00]]
解码器·掩码自注意力    : (3, 4)
解码器·残差 + 归一化   : (3, 4)
解码器·交叉注意力      : (3, 4) (K、V 来自编码器 Z)
解码器·残差 + 前馈 + 归一化: (3, 4)
输出层 Linear → logits : (3, 6) (词表大小)

对着架构图一段一段看,每一行都对应图上的一块

  1. X 是 (3, 4):3 个源词,每个 4 维------对应图 1 左下角"源句嵌入 + 位置编码";
  2. 编码器自注意力输出还是 (3, 4):自注意力不改变形状------对应图 2 的"① 多头自注意力";
  3. 残差 + 归一化、FFN、残差 + 归一化,全都不变形状:所以编码器想叠几层就叠几层------对应图 2 整张图;
  4. Z 是 (3, 4):这就是编码器记忆,供交叉注意力调用------对应图 1 的紫色 K、V 箭头;
  5. 掩码矩阵:对角线及以下全是 0(看得见),右上角全是 -1e9(遮成负无穷,softmax 后权重为 0)------对应图 3 右上角的小三角;
  6. 交叉注意力输出 (3, 4):Q 是解码器的、K/V 是编码器的,形状都不变------对应图 4 第三列;
  7. 最后 logits 变成 (3, 6) :3 个目标词位置,每个预测 6 个词表词的概率------对应图 1 的 Linear + Softmax 输出层。

这就是"一张图看懂全貌"的实操版:架构图上每一个方块,都能在你自己的代码里找到对应的一行。把这段骨架代码跑通,你就已经亲手"搭"过一遍 Transformer 了(第 9 章再把它升级成完整版)。


七、实操验证(二):掩码自注意力 vs 交叉注意力,K/V 从哪来

上面骨架里有一个细节值得单独验证:为什么掩码自注意力和交叉注意力不一样? 我们用两个小实验把它们的区别"钉死"。

实验 A:掩码真的把未来遮死了吗?

构造 4 个词的序列 <BOS> 我 爱 猫,看位置 1("我")在加掩码前后,对后面两个词("爱"、"猫")的注意力权重:

python 复制代码
S = 4
mask4 = np.triu(np.full((S, S), -1e9), k=1)   # 上三角掩码
np.random.seed(7)
Qs = np.random.randn(S, 4) * 0.5
Ks = np.random.randn(S, 4) * 0.5
_, W_no = scaled_dot_product_attention(Qs, Ks, np.ones((S, 4)))     # 不加掩码
_, W_ma = scaled_dot_product_attention(Qs, Ks, np.ones((S, 4)), mask4)  # 加掩码

真实结果:

复制代码
不加掩码------位置 1("我")对后两词的权重: [0.391 0.168] (>0,偷看到了未来)
加了掩码------位置 1("我")对后两词的权重: [0. 0.]     (=0,未来被遮死)

铁证如山:不加掩码时,"我"会偷看"爱"和"猫"(权重 0.391、0.168);加了掩码后,这两个权重被强制压成 0 。这就是解码器"不剧透"的物理实现-------1e9 加进打分矩阵,softmax 之后就是 0。

实验 B:交叉注意力的 K/V 到底来自谁?

在骨架代码里,交叉注意力那两行是:

python 复制代码
Q = M @ Wq   # Q 来自解码器自己(M 是解码器状态)
K = Z @ Wk   # K 来自编码器记忆
V = Z @ Wv   # V 来自编码器记忆

打印三个来源的形状:

复制代码
解码器 Q 的形状: (3, 4) (来自解码器自己)
编码器 K 的形状: (3, 4) (来自编码器记忆)
编码器 V 的形状: (3, 4) (来自编码器记忆)
→ 两个来源不同:解码器"问问题",编码器"提供答案"

对比结论

  • 掩码自注意力 :Q、K、V 都来自解码器自己,是"已生成的词之间对话";
  • 交叉注意力 :Q 来自解码器,K、V 来自编码器,是"解码器问、编码器答"。

同一个注意力公式,换一下 K/V 的来源,就从"自言自语"变成了"跨语言对话"------这就是图 4 那张对比表的意义。


八、交叉注意力到底"看"了源句的哪个词?热力图实锤

实操验证(二)只证明了"K/V 来自编码器",但还差最后一步:解码器生成"我"的时候,真的会重点看源句里的 "I" 吗? 我们把交叉注意力的权重矩阵画成热力图,眼见为实。

为了让规律肉眼可见,我们给源句和目标词各造一组"语义对齐"的简易向量(比如 "I" 和"我"偏同一维、"love" 和"爱"偏同一维),然后跑一遍标准的交叉注意力公式:

python 复制代码
E_src = np.array([[1.0, 0.05, 0.15],   # I    ------ 偏"我"的维度
                  [0.05, 1.0, 0.05],   # love ------ 偏"爱"的维度
                  [0.15, 0.05, 1.0]])  # cats ------ 偏"猫"的维度
E_tgt = np.array([[1.0, 0.08, 0.10],   # 我
                  [0.08, 1.0, 0.08],   # 爱
                  [0.10, 0.08, 1.0]])  # 猫
S = E_tgt @ E_src.T
W = softmax_rows(S)    # 每行归一化成注意力权重

真实算出的权重矩阵:

复制代码
行=目标词(Q),列=源词(K/V):
我: 对 I=0.53  love=0.22  cats=0.25
爱: 对 I=0.23  love=0.54  cats=0.23
猫: 对 I=0.25  love=0.22  cats=0.53

画成热力图:

看图 6,规律不能再明显了:主对角线(我→I、爱→love、猫→cats)一片深红,权重都超过 0.5;斜对角一片浅黄。用一句话说:

解码器生成第 t t t 个目标词时,交叉注意力会自动把注意力集中在源句里"语义最相关"的那几个词上------这就是"查笔记"的微观画面。训练完成后,真实模型学到的交叉注意力权重,形态正是这个样子(只是每一行不再这么"干净",会同时关注多个相关的源词)。

这一步把整个架构的"最后一公里"也打通了:图 1 的紫色 K、V 箭头,图 5 的"回头看"虚线,到这里全部落地成了一张具体的权重矩阵。


九、为什么编码器能并行、解码器必须串行?

这是面试中最爱考、也最容易答混的一个问题。答案其实就藏在两边的输入里:编码器的输入是"完整的一整句",解码器的输入是"当前已生成的部分"。我们看图说话:

**编码器为什么并行?**源句 "I love cats" 在输入的那一刻就是完整的------三个词全都摆在那儿。多头自注意力让它们互相看,一次前向传播就同时算出三个词的新表示。词与词之间没有"谁先谁后"的依赖,天然可以并行。这也是 Transformer 比 RNN 快一个数量级的根本原因:RNN 只能一个词一个词地读(串行),Transformer 一口气读完(并行)。

**解码器为什么串行?**目标句是"边生成边变长"的:生成"爱"之前,世界上还不存在"爱"这个词的位置------它得先靠"<BOS> 我"这两个词算出来。也就是说,第 t t t 个词的输出依赖前 t − 1 t-1 t−1 个词的输出,这种依赖关系决定了解码器推理时只能一个词一个词地来。

但注意一个容易混的点:**训练时解码器反而是"并行"的!**请把图 7 右半边和骨架代码对照一下------骨架里我们直接喂入了完整的 <BOS> 我 爱(三个位置同时算),这叫 Teacher Forcing(老师强迫):训练时正确答案已知,我们一次性把整句喂进去,让三个位置同时预测,再用掩码确保"每个位置只依赖它左边的词"。所以:

  • 训练时 :喂完整目标句 + 掩码 → 三个位置并行算(快);
  • 推理时 :没有正确答案 → 只能逐词生成,串行(慢,但这是唯一的路)。

💡 一句话记住:编码器输入是"完整的书",所以并行;解码器推理时输入是"自己刚写的前半本书",所以串行;训练时输入是"老师给的完整答案",所以也能并行(加掩码防剧透即可)。


十、经典超参数:N=6、d_model=512、h=8 从哪来?

看论文《Attention Is All You Need》时,Table 3 里有一串数字:N=6、d_model=512、h=8、d_ff=2048。这些数字是"标准配置"(Base 版),几乎所有入门教程都会用到。它们分别管什么?看图 8:

每个超参数在架构图里的位置:

  • N = 6:图 1 里"...共 N 层"的 N。编码器 6 层 + 解码器 6 层,共 12 个"大块"(每一块含 2~3 个注意力/前馈子层);
  • d_model = 512:所有词向量、所有层输出的维度。图 1 里每个方块进出的向量都是 512 维;
  • h = 8:多头注意力把头数。第 4 章讲过,512 维切成 8 份,每份 64 维,8 个头并行看;
  • d_k = d_v = 64:每个头的维度,等于 d_model ÷ h = 512 ÷ 8;
  • d_ff = 2048:前馈网络"先扩维"到 2048(4 倍 d_model),再缩回 512。图 2 的"② 前馈网络"里就藏着这个 4 倍;
  • Dropout = 0.1:每个子层的输出随机丢弃 10%,防过拟合。

**这些数字能算出参数量吗?**能。以编码器单层为例:自注意力有 Q/K/V 三个投影矩阵,每个 512×512,加上输出投影一个 512×512,共 4 × 512 2 4 \times 512^2 4×5122;前馈有两个矩阵 512×2048 和 2048×512,共 2 × 512 × 2048 2 \times 512 \times 2048 2×512×2048。代入验证脚本算出的真实数字:

复制代码
编码器单层参数量 = 4×d_model² + 2×d_model×d_ff = 3145728  (约 315 万)
解码器单层参数量 = 6×d_model² + 2×d_model×d_ff = 3670016  (约 367 万,多出两个交叉注意力的投影)
全部 6 层(编+解)参数量 ≈ 4089 万

看到没有------解码器单层比编码器单层多约 52 万参数,就差在"交叉注意力"那组额外的 Q/K/V 投影上,这正好和我们前面讲的"解码器多一种注意力"对上了。约 4089 万的骨干参数量(还没算嵌入层和输出层),这就是论文里"约 6500 万总参数"的骨干来源。

🎤 记忆口诀N=6、d=512、h=8、ff=2048------四个数字背下来,你就能随口描述一个标准 Transformer,也就能看懂任何一篇论文的开始部分了。


十一、常见坑与自查

  • 以为编码器和解码器结构一样 :编码器是"自注意力 + 前馈"两件套;解码器是"掩码自注意力 + 交叉注意力 + 前馈"三件套,多一个交叉注意力,多一张掩码
  • 以为交叉注意力是"另一种公式" :公式和自注意力完全一样 ( A = softmax ( Q K ⊤ / d k ) V A=\text{softmax}(QK^\top/\sqrt{d_k})V A=softmax(QK⊤/dk )V),变的是 Q/K/V 的来源------Q 来自解码器,K/V 来自编码器;
  • 把掩码自注意力和交叉注意力的顺序记反 :解码器每层 做掩码自注意力(处理自己),做交叉注意力(查源句),顺序不能换;
  • 以为编码器也是逐词生成的 :编码器一次读完整句 (并行);只有解码器推理时逐词生成(串行);而解码器训练时因为 teacher forcing 也可以并行;
  • 以为解码器的输入就是目标句本身 :是右移一位 的目标句------前面补 <BOS>,真正的任务是"用前 t t t 个词预测第 t + 1 t+1 t+1 个词";
  • 以为"掩码"是模型学出来的 :掩码是手工构造的固定矩阵np.triu 生成的上三角),不是参数,不参与训练;
  • 以为交叉注意力只看一个源词 :真实模型的每一行权重通常分散在多个相关的源词上(图 6 只是为了直观才显得"干净"),它做的是"加权求和",不是"只挑一个";
  • 把 N 和 h 混为一谈 :N 是层数 (纵向堆叠),h 是头数(横向切分),一个在"楼层"维度,一个在"维度"维度;
  • 以为参数量只和 d_model 有关 :FFN 的 d_ff 贡献更大( 2 × 512 × 2048 ≈ 210 2 \times 512 \times 2048 \approx 210 2×512×2048≈210 万,比自注意力的 4 × 512 2 ≈ 105 4 \times 512^2 \approx 105 4×5122≈105 万还多一倍)。

小结

这一章我们把五章的零件组装成了整栋大楼,核心收获八条:

  • 整体架构 :Transformer = 编码器(读懂源句)+ 解码器(生成目标句),右边每层都要通过交叉注意力"查"左边的记忆;
  • 编码器自注意力 + 前馈 + 残差 + 归一化 × N 层,一次读完整句(并行),输出"编码器记忆" Z;
  • 解码器掩码自注意力 + 交叉注意力 + 前馈 + 残差 + 归一化 × N 层,逐词生成(串行),输入是右移一位的目标句;
  • 三种注意力 :自注意力(Q=K=V=自己)、掩码自注意力(同左 + 上三角遮罩)、交叉注意力(Q 自解码器,K/V 自编码器)------配方不变,原料不同
  • 数据流:编码器跑 1 次 → 解码器逐词跑 N 次,每一步都"回头看"编码器记忆,最后 Linear+Softmax 出概率分布;
  • 并行 vs 串行:编码器并行(输入完整),解码器推理串行(依赖前序输出),训练时 teacher forcing 可并行;
  • 经典超参数:N=6、d_model=512、h=8、d_k=d_v=64、d_ff=2048、Dropout=0.1,骨干参数量约 4089 万;
  • 实操验证:微型骨架让架构图每一块都对应一行代码,掩码把未来权重压成 0,交叉注意力热力图证明"生成哪个词就看哪个源词"。

架构篇的"平面图"已经拿到手了。但你可能已经注意到,这一章里反复出现"残差连接""层归一化""前馈网络"这几个词,而我们始终是"先记住它在哪、有什么作用",没有展开讲它到底怎么算 。下一章,我们就钻进编码器内部,把这栋楼的第一块承重墙------残差连接、LayerNorm 与前馈网络------一块砖一块砖地拆开看。


下一篇(三十六):Encoder 内部------残差连接、LayerNorm 与前馈网络

相关推荐
_codemonster6 小时前
Transformer的核心机制
人工智能·深度学习·transformer
AI即插即用1 天前
即插即用系列 | IEEE TMI PLG-HN:原型学习引导的 CNN-Transformer 混合网络,攻克乳腺肿瘤分割难题
人工智能·深度学习·神经网络·学习·目标检测·cnn·transformer
Lee_jerome1 天前
python神经网络编程入门(三十三)——多头注意力(Multi-Head Attention)
深度学习·nlp·transformer·注意力机制·多头注意力·self-attention
爱知菜1 天前
Transformer vs Diffusion:为什么扩散模型更擅长捕捉细节?
人工智能·深度学习·transformer·扩散模型
ʜᴇɴʀʏ1 天前
ICCV 2025 | STEP-DETR:基于超级教师与伪标签引导文本查询的半监督目标检测
人工智能·目标检测·计算机视觉·transformer
杀生丸学AI2 天前
【前馈三维重建】SAF3R:前馈式3D重建Transformer的动态稀疏注意力(加速)
深度学习·3d·transformer
梦想的初衷~2 天前
人工智能核心模型详解教程:注意力机制、Transformer、CV、NLP与多模态大模型
人工智能·深度学习·transformer
大模型丫丫3 天前
深入解析:HashMap、HashTable 与 ConcurrentHashMap 的核心区别
人工智能·transformer
Lee_jerome3 天前
python神经网络编程入门(三十)——Transformer 从 RNN 到注意力:模型为什么需要“瞄一眼“
rnn·深度学习·nlp·transformer·attention·注意力机制·序列建模