Appearance
完整架构
这一页把前面所有零件组装成一台完整机器——2017 年原论文里那个用于翻译的 Transformer。建议对照下面这张全景图,从上到下、从左到右走一遍。
1. 全景图
┌─────────────── Encoder (左) ───────────────┐ ┌──────────── Decoder (右) ────────────┐
│ │ │ │
输入 "I love you" 目标(右移) "<bos> 我 爱"
│ │ │ │
▼ │ ▼ │
[Input Embedding + PE] │ [Output Embedding + PE] │
│ │ │ │
▼ │ ▼ │
┌──────────────────────┐ │ ┌──────────────────────┐ │
│ Masked Self-Attn │ ← 编码器无掩码(可见全句)│ │ Masked Self-Attn │ ← 因果掩码,只看过去 │
│ Add & Norm │ │ │ Add & Norm │ │
│ Cross-Attention ────┼────── K,V 来自编码器 ─┼──►│ Cross-Attention │ ← Q来自解码器 │
│ Add & Norm │ │ │ Add & Norm │ │
│ FFN + Add & Norm │ │ │ FFN + Add & Norm │ │
└──────────┬───────────┘ │ └──────────┬───────────┘ │
× N 层 (堆叠) │ × N 层 (堆叠) │
│ │ │ │
最终输出(记忆K,V) │ ▼ │
└─────────────►(供解码器 Cross-Attn)│ [Linear → Softmax] │
│ │ │
│ ▼ │
│ 预测下一个词 "爱 / 你 / <eos>" │
└────────────────────────────────────────────┘ └────────────────────────────────────────┘一句话读图:编码器把源句压缩成一份富含语义的表示(K/V),解码器一边「回看」这个表示、一边因果地逐词生成目标句。
2. 数据如何流动(一步步)
以「I love you → 我爱你」为例:
- 编码器输入:
I love you分词 → 查词嵌入 → 加位置编码 →[3, d_model]。 - 编码器 N 层:每层「自注意力 + FFN」,让三个词充分交换信息,得到富含上下文的表示。
- 解码器输入(训练时用「右移」):
<bos> 我 爱(即目标序列右移一位,末尾砍掉),让模型在已知前文时预测下一个词。 - 解码器每层三件事:
- 带因果掩码的自注意力(看已生成的目标词)
- 交叉注意力(用目标当前状态去「查询」编码器的源句表示)
- FFN 加工
- 输出层:最后一层解码器输出
[T, d_model]→ Linear 映射到词表大小 → Softmax 得到每个位置「下一个词」的概率分布。
3. 训练时并行、生成时串行
这是最容易混淆、也最重要的一点:
训练: 目标句已知 → 整句一次性喂进解码器(靠因果掩码保证不偷看) → 并行算所有位置损失 ✅快
生成: 目标句未知 → 只能一次产一个词,把新词再接回去产下一个 → 自回归,串行 🔁
<bos> → 我 → 爱 → 你 → <eos>- 训练时「右移 + 因果掩码」让所有位置的损失能并行计算,这是 Transformer 训练飞快的关键。
- 推理生成时无法并行,只能一个词一个词地蹦(kv cache 就是为加速这个过程,见进阶页)。
4. 关键超参数(原始 base 模型)
python
config = dict(
N = 6, # 编码器/解码器各堆 6 层
d_model = 512, # 主干宽度
d_ff = 2048, # FFN 中间层 = 4×d_model
h = 8, # 注意力头数, 每头 512/8=64 维
dropout = 0.1, # 子层与嵌入上的 dropout
)
# 参数量约 65M(base); big 模型 d_model=1024,h=16,约 213M5. 训练细节速览
- 损失:词表上的交叉熵(对每个目标位置)。
- 优化器:Adam,配合 warmup + 衰减 的学习率调度(Post-Norm 尤其需要 warmup)。
- Label Smoothing:轻微平滑标签,缓解过拟合、提升 BLEU。
- 正则:dropout + 残差,让 6~N 层稳定训练。
6. 用 PyTorch 把整台机器串起来
python
import torch, torch.nn as nn
class EncoderLayer(nn.Module):
def __init__(self, d_model, h, d_ff, p=0.1):
super().__init__()
self.attn = nn.MultiheadAttention(d_model, h, dropout=p, batch_first=True)
self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.GELU(), nn.Linear(d_ff, d_model))
self.n1, self.n2 = nn.LayerNorm(d_model), nn.LayerNorm(d_model)
self.drop = nn.Dropout(p)
def forward(self, x, mask=None):
a, _ = self.attn(x, x, x, attn_mask=mask, need_weights=False)
x = self.n1(x + self.drop(a))
x = self.n2(x + self.drop(self.ffn(x)))
return x
class DecoderLayer(nn.Module):
def __init__(self, d_model, h, d_ff, p=0.1):
super().__init__()
self.self_attn = nn.MultiheadAttention(d_model, h, dropout=p, batch_first=True)
self.cross_attn = nn.MultiheadAttention(d_model, h, dropout=p, batch_first=True)
self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.GELU(), nn.Linear(d_ff, d_model))
self.n1, self.n2, self.n3 = (nn.LayerNorm(d_model) for _ in range(3))
self.drop = nn.Dropout(p)
def forward(self, x, memory, src_mask=None, tgt_mask=None):
a, _ = self.self_attn(x, x, x, attn_mask=tgt_mask, need_weights=False)
x = self.n1(x + self.drop(a))
c, _ = self.cross_attn(x, memory, memory, need_weights=False) # Q=目标, K/V=源
x = self.n2(x + self.drop(c))
x = self.n3(x + self.drop(self.ffn(x)))
return x
# 各堆 N 层,再接 embedding / 输出 Linear,即构成完整 Transformer(下节实战会给出可训练版本)7. 三处掩码别搞混
| 掩码 | 出现位置 | 作用 |
|---|---|---|
| padding mask | 编/解码输入 | 屏蔽补零的空位(<pad>) |
| causal mask | 解码器自注意力 | 禁止看未来词 |
| cross 无需因果掩码 | 解码器交叉注意力 | 源句是完整的,可全部看 |
小结
- 完整 Transformer = 编码器(理解源句)+ 解码器(回看源句、逐词生成目标)。
- 解码器每层三件套:因果自注意力 + 交叉注意力 + FFN。
- 训练并行、生成串行 是其行为模式的核心区别。
- 掩码分三类,别在交叉注意力上错加因果掩码。
下一步
想更细地看清编码器与解码器各自的职责分工 → Encoder与Decoder。