这是一篇从零梳理 Transformer 的学习笔记。前半部分讲清 Encoder–Decoder 总览、Embedding / 张量、Self-Attention、Multi-Head、FFN、残差与层归一化、位置编码、Decoder 与 Mask;后半部分落到我在 F:\transformer_practice 里从零手写的模块——从 Embedding 到 Encoder Block、分类模型,再到 Seq2Seq 训练与自回归推理。架构理解主要参考 CSDN 详解与论文 Attention Is All You Need,再结合自己的推导与代码注释。所有框架图引用与中文注释均为我在学习过程中整理。

前言

2017 年 Google 提出 Transformer 之后,Self-Attention 几乎成了 NLP(以及后来多模态)的默认底座。和 RNN 比,它最大的优势是可以并行计算整段序列;和 CNN 比,它用注意力直接建模任意距离的依赖。

这篇笔记按我自己的学习路径展开:

  1. 先把模型当成黑盒,建立 Encoder–Decoder 直觉
  2. 补齐 Embedding、张量形状这些前置概念
  3. 拆解 Self-Attention → Multi-Head → FFN → Add & Norm → Positional Encoding
  4. 搞清 Decoder、Cross-Attention 与两种 Mask
  5. transformer_practice 里从零手写并跑通:分类 + Seq2Seq 生成
  6. 对照完成度,按 A→I 分阶段路径推进:Mask 全链路 → 真实数据 → Tokenizer → Beam Search → 规模升级 → 小型 BERT/GPT/T5

架构学习主要参考:Transformer 模型详解(CSDN),再按自己的理解增删。


一、先把 Transformer 看成黑盒

1.1 输入一句话,输出另一句话

总的来看:把一个句子作为输入,Transformer 把它翻译 / 生成成另一段序列输出。中间发生了什么,先当黑盒。

Transformer 黑盒模式

1.2 本质仍是 Encoder–Decoder

把中间拆开:左边是编码组件(多层 Encoder 堆叠),右边是解码组件(相同层数的 Decoder 堆叠)。论文默认各 6 层,实际可改。

Encoder-Decoder 架构

组件 子层 作用
Encoder Self-Attention + FFN 读懂输入句,得到上下文表示
Decoder Masked Self-Attention + Cross-Attention + FFN 看已生成内容 + 看 Encoder 输出,预测下一个 token

一句话:Encoder 负责“理解输入是什么”,Decoder 负责“一步步写出答案”;Cross-Attention 把两边接起来。

整体架构图(也是本文封面):

Transformer 整体架构

我自己的串讲:

1
2
3
4
5
6
7
输入 x
→ Embedding + Positional Encoding
→ Encoder × N(Multi-Head Self-Attention → Add&Norm → FFN → Add&Norm)
→ Encoder 最终输出 H,作为 Decoder Cross-Attention 的 K、V
→ Decoder 输入:右移后的目标序列(防止偷看未来)
→ Masked Multi-Head → Add&Norm → Cross-Attention(Q=Decoder, K/V=H) → Add&Norm → FFN
→ Linear + Softmax → 取最大概率得到下一个词

二、前置概念:Embedding 与张量

Transformer 不能直接处理汉字,只能处理数字。Embedding 的作用,就是把每个词变成一串数字。

2.1 Embedding:查表把词变成向量

例如“我爱学习”:

向量(论文里通常 512 维)
[0.2, 0.7, -0.1, …]
[0.8, -0.2, 0.4, …]
学习 [0.3, 0.9, 0.6, …]

表里的数字是训练学出来的特征。三个词拼起来就是形状 3×512 的矩阵:

  • $L$:序列长度(几个 token)
  • $d_{\text{model}}$:每个 token 用多少维表示(论文 = 512)

Embedding 只发生在最底层。 底部 Encoder 吃的是词向量;上面各层吃的是上一层 Encoder 的输出。

2.2 张量的本质:批量句子要补齐长度

现实里句子长短不一:

1
2
句子1:我 爱 你                 → 3 个词
句子2:我 非常 喜欢 机器 学习 → 5 个词

为了一次处理,常统一到固定长度(比如 L=5),短的补 [PAD]。若一次处理 $n$ 句,就是:

1
(batch_size, sequence_length, embedding_dimension)

这就是 PyTorch 里常见的三维 Tensor——多维数字数组


三、Self-Attention:让机器知道 “it” 指谁

3.1 动机

句子:

The animal didn’t cross the street because it was too tired

人知道 itanimal,机器不容易。Self-Attention 让模型在编码某个词时,也能看句子里其他位置的词,把相关信息融进来。

Self-Attention 关注 it 与 animal

3.2 公式:Scaled Dot-Product Attention

Self-Attention 里,Q、K、V 都来自同一输入 $X$。

Scaled Dot-Product Attention

3.3 逐步拆解(向量视角)

六步走完 Self-Attention

① 线性映射出 Q、K、V

每个词向量 $x_i$ 分别乘三个可学习矩阵 $W^Q, W^K, W^V$,得到 $q_i, k_i, v_i$。论文里词向量 512 维,单头 QKV 常取 64 维。

生成 QKV

② 算注意力分数

编码 “Thinking” 时,用它的 $q_1$ 与每个位置的 $k_j$ 做点积:$q_1\cdot k_1,\ q_1\cdot k_2,\ \ldots$

点积得分数

③ 除以 $\sqrt{d_k}$

防止点积过大,让 Softmax 梯度更稳。

④ Softmax 归一化

分数变成和为 1 的正数权重——当前位置通常最高,但相关词也会分到权重。

Softmax 权重

⑤ 权重 × Value

权重大的位置,对应 $v$ 贡献大;权重小的几乎被忽略。

⑥ 加权求和

得到该位置 Self-Attention 的输出,再送进 FFN。

加权求和输出

3.4 矩阵形式(真正写代码时用这个)

把所有词排成矩阵 $X$,一次算出:

再:

矩阵算 QKV

矩阵形式 Attention

我在 attention.py 里用小例子验证过同一条链路:

1
2
3
4
Q = W_Q(X); K = W_K(X); V = W_V(X)
scores = Q @ K.T / math.sqrt(d)
weights = torch.softmax(scores, dim=-1)
out = weights @ V

四、Multi-Head Attention:多个角度看关联

4.1 在干什么

Multi-Head = 同一份 $X$,用 h 套不同的 QKV 参数 做 h 次 Attention,再 Concat + Linear($W^O$)

论文设定:$h=8$,$dk=d_v=d{\text{model}}/h=64$。

Multi-Head 结构

粗略流程:

1
2
3
4
5
6
7
8
同一个 X "Thinking Machines"

┌─────┴─────┐
Head0 Head1
各自 W 各自 W
QKV→Attn QKV→Attn
└─────┬─────┘
Concat → Linear → Multi-Head 输出

本质:参数总量可控的前提下,把 QKV 映射到高维空间的不同子空间分别算 Attention,最后再拼回来。每个 head 看不同角度的关联(一个盯 animal,一个盯 tired),拼起来表示更丰富。

Multi-Head 完整流程


五、FFN、残差、层归一化

5.1 Position-wise FFN

一个 Encoder Layer 粗看是:

1
2
3
4
输入 X
→ Multi-Head Self-Attention (词与词之间交流)
→ Position-wise FFN (每个词自己再非线性加工)
→ 输出

Position-wise:每个位置各自过同一个 FFN(共享 $W_1,b_1,W_2,b_2$),不是每个词一套网络。

论文:$d{\text{model}}=512$,$d{ff}=2048$,即:

先扩维再压回,是为了在更大特征空间里组合特征,同时保持主通道 512,方便残差相加、堆下一层。

5.2 Add & Norm

每个子层都是:

我把它读成:

1
2
3
4
1. SubLayer 学“新增信息”
2. 原来的 x 从旁边绕过去(残差)
3. 两者相加
4. LayerNorm 稳住数值尺度

完整 Encoder Layer:

1
2
3
4
5
6
输入 X
→ Multi-Head Self-Attention
→ Add & Norm ← X + Attention(X)
→ FFN
→ Add & Norm ← Y + FFN(Y)
→ Encoder Layer 输出

为了残差能直接相加,子层与嵌入的输出维度都要保持 $d_{\text{model}}=512$。


六、Positional Encoding:告诉模型“你站在哪”

Self-Attention 本身没有顺序(不像 RNN 一个一个扫)。所以要在进入 Encoder 之前,给每个位置加一个位置向量:

论文用固定的正弦 / 余弦:

维度与 Embedding 相同,可直接相加。

Embedding + PE

Embedding 告诉模型“你是谁”,Positional Encoding 告诉模型“你站在哪”。 PE 只在进入 Encoder 堆栈前加一次,不是 Attention 内部的东西。

我在 position.py 里按论文公式写过偶数维 sin、奇数维 cos,验证过形状仍是 [seq_len, d_model]。工程里部分脚本为了先跑通,还用过更简化的 sin(position)/cos(position)——思想一样,尺度细节以后可对齐论文


七、Decoder 与 Mask

7.1 Decoder 在干什么

Decoder 自回归示意

推理时 Decoder 自回归生成:

1
2
已生成的内容 → Decoder → 预测下一个 token → 拼进输入 → 继续
直到输出 <EOS>

Decoder 每个 Layer 三块:

  1. Masked Self-Attention:只看已经生成的部分
  2. Encoder–Decoder Attention(Cross-Attention):Q 来自 Decoder,K/V 来自 Encoder 最终输出
  3. FFN

7.2 两种 Mask

Mask 的本质:在 Softmax 之前,把“不该看的位置”的分数改成极小数(如 $-\infty$),Softmax 后权重≈0。

类型 解决什么问题 做法
Padding Mask 补齐用的 PAD 没意义,不该被注意 PAD 对应列 / 位置打成 $-\infty$
Sequence / Causal Mask Decoder 不能偷看未来词 上三角(未来位置)打成 $-\infty$,常用下三角 tril 保留可见区

因果掩码直觉:

1
生成第 3 个词时,只能看见第 1、2、3 个位置,看不见第 4、5…

我在 mask.py 里写成:

1
2
3
4
5
6
7
8
def create_causal_mask(seq_len, device):
# 下三角为 1:允许看到自己及过去
mask = torch.tril(torch.ones(seq_len, seq_len, device=device))
return mask.unsqueeze(0).unsqueeze(0) # [1,1,L,L]

def create_padding_mask(tokens, pad_id=0):
# True 表示真实 token
return (tokens != pad_id).unsqueeze(1).unsqueeze(2) # [B,1,1,L]

Attention 里配合:

1
2
if mask is not None:
scores = scores.masked_fill(mask == 0, float("-inf"))

7.3 最后的 Linear + Softmax

Decoder 输出仍是向量,要变成词:

1
2
3
4
Decoder 输出向量
→ Linear → vocab 维 logits
→ Softmax → 概率
→ 取 argmax(或采样 / Beam Search)→ 一个词

论文还有两个训练细节:Embedding 与最后 Linear 共享权重;Embedding 会乘 $\sqrt{d_{\text{model}}}$ 做尺度;子层与 Embedding+PE 后使用 Dropout=0.1,并配合 Label Smoothing=0.1


八、从零实现:transformer_practice

理论串完后,我在 F:\transformer_practice不调用 HuggingFace 封装,用 PyTorch 从零搭模块。目标是理解完整架构,而不是追 SOTA。

8.1 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
transformer_practice/
├── embedding.py # Token → 向量
├── position.py # 位置编码
├── attention.py # Scaled Dot-Product 小实验
├── multi_head_demo.py # 多头拆分直觉
├── transformer.py # Encoder Block(MHA + FFN + Add&Norm)
├── encoder.py # Encoder Stack
├── model.py / train.py / inference.py # 分类
├── decoder_layer.py / decoder_stack.py # Decoder
├── transformer_seq2seq.py
├── seq2seq_train.py / seq2seq_inference.py
├── mask.py
├── model.pth / seq2seq_model.pth

8.2 Encoder Block:我写的核心前向

transformer.py 里的 TransformerBlock 对应“一层 Encoder”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class TransformerBlock(nn.Module):
def __init__(self, d_model=4, num_heads=2):
...
self.W_Q = nn.Linear(d_model, d_model)
self.W_K = nn.Linear(d_model, d_model)
self.W_V = nn.Linear(d_model, d_model)
self.W_O = nn.Linear(d_model, d_model)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.ffn1 = nn.Linear(d_model, 8) # 小规模演示:4→8→4
self.ffn2 = nn.Linear(8, d_model)

def forward(self, X, mask=None):
# X: [B, L, d_model]
Q, K, V = self.W_Q(X), self.W_K(X), self.W_V(X)
# reshape → [B, heads, L, head_dim]
...
scores = Q @ K.transpose(-2, -1) / math.sqrt(self.head_dim)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
weights = torch.softmax(scores, dim=-1)
attn = (weights @ V).transpose(1, 2).reshape(B, L, d_model)
out = self.W_O(attn)
x = self.norm1(X + out) # Add & Norm
ffn = self.ffn2(torch.relu(self.ffn1(x)))
x = self.norm2(x + ffn) # Add & Norm
return x
我对这段代码标的几个点
  • 先 Linear 再按 head 拆分:等价于多套 $W_i^Q$ 的一种高效写法
  • transpose(1,2):让 head 维靠前,方便 [B,H,L,L] 的 score
  • mask 在 Softmax 前 masked_fill,与论文一致
  • 当前演示规模是 d_model=4, heads=2,结构对齐论文,参数量远小于 512/8/6

8.3 分类模型:Encoder + Mean Pooling

1
Token → Embedding → +PE → Encoder×N → mean(dim=序列) → Linear → 类别

train.py 已跑通,accuracy = 1.0(玩具数据),权重存 model.pthinference.py 可加载预测。

8.4 Seq2Seq:Encoder–Decoder + 自回归

结构:

1
2
3
Encoder tokens → Emb+PE → Encoder → H
Decoder tokens → Emb+PE → Decoder(Masked SA + Cross-Attn(Q←Dec, K/V←H) + FFN)
→ Linear → vocab logits

训练(seq2seq_train.py):

1
2
3
loss: 4.8 → 0.08
预测与 target 完全匹配
保存:seq2seq_model.pth

推理(seq2seq_inference.py):从 [BOS] 起步循环:

1
2
3
encoder: [5, 27, 91]
decoder: [BOS] → 8 → 15 → 20 → 2(EOS)
最终序列: [1, 8, 15, 20, 2]

这说明:在玩具设定下,Decoder 已经具备自回归生成能力。接下来的瓶颈不在“能不能生成”,而在 Mask 是否严谨、数据是否真实、规模是否接近论文。

8.5 当前完成度

模块 完成度 说明
Embedding / PE / Attention / MHA ██████████ 100% 结构与公式对齐
Encoder / Classifier / Seq2Seq 训练推理 ██████████ 100% 玩具数据已跑通
Decoder ████████░░ 80% 主路径有,正式 Mask 集成仍可加强
Mask ████░░░░░░ 40% mask.py 已写,需贯穿全链路
真实数据 / Tokenizer / GPT·BERT ░░░░░░░░░░ 0% 下一阶段

九、阶段性判断与后续具体路径

9.1 现阶段能下的结论

可以比较稳妥地写:

我已经从公式层面打通 Transformer 主路径,并在 PyTorch 中从零实现了 Encoder Block、分类头与 Encoder–Decoder Seq2Seq;在小规模模拟数据上,分类准确率与 Seq2Seq 拟合都验证了链路正确。距离论文级完整实现,主要缺口在 Mask 体系贯穿、真实语料与 Tokenizer、以及模型规模

当前不是“推倒重来”,而是按缺口补齐。总路线一句话:

1
2
3
4
5
6
7
Mask 全链路打通
→ Decoder / Attention 接口收口
→ 真实数据 + Padding + Batch
→ Tokenizer
→ Beam Search 推理
→ 规模升到论文量级
→ 分叉:小型 BERT / GPT / T5

原则和做 U-Net 对照实验时一样:一次只推进一个阶段、每阶段有可验证的验收标准,不要同时改 Mask、换数据集又升 d_model

9.2 阶段总览(先看地图)

阶段 目标 主要改动文件 验收标准
A Mask 系统可用 mask.py causal / padding 形状正确,可视化下三角与 PAD 列为 0
B Attention 统一吃 mask transformer.pydecoder_layer.py Softmax 前 masked_fill;非法位置权重≈0
C Encoder/Decoder 全链路传 mask encoder.pydecoder_stack.pytransformer_seq2seq.py 训练/推理都传入 mask,PAD 不再影响表示
D 带 mask 的玩具 Seq2Seq 复训 seq2seq_train.pyseq2seq_inference.py 有 PAD 的 batch 仍能拟合;生成不偷看未来
E 真实数据管线 新建 dataset.py / collate DataLoader 出齐 src/tgt/mask
F Tokenizer 新建 tokenizer 相关 句子→ids→模型→ids→句子闭环
G Beam Search 新建 beam_search.py 或扩推理脚本 同输入可出多候选并选最优
H 规模升级 配置与 FFN 维度 接近 512/8/6,能稳定训几个 epoch
I 三条产品线分叉 新目录或新入口脚本 选定 BERT / GPT / T5 之一做出最小可跑 Demo

下面按阶段写出具体怎么做


9.3 阶段 A:把 Mask 做成可复用工具

要解决的问题

  • Padding:短句补的 PAD 不该被注意
  • Causal:Decoder 第 $t$ 步不能看见 $t+1$ 及以后

建议文件:继续完善 mask.py(已有雏形)

1
2
3
4
# 目标 API(保持简单、形状统一)
create_causal_mask(seq_len, device) # → [1, 1, L, L],下三角为 1
create_padding_mask(tokens, pad_id=0) # → [B, 1, 1, L],真实 token 为 1
combine_masks(causal, padding) # Decoder Self-Attn 常用:两者相乘/逻辑与
形状约定(写进注释,后面少踩坑)

Attention score 形状一般是 [B, H, Lq, Lk],所以 mask 广播维建议:

Mask建议形状挡住谁
Causal[1,1,L,L]未来位置(上三角)
Padding(对 Key)[B,1,1,Lk]PAD 作为被关注对象
组合后[B,1,L,L] 或可广播到 scoreDecoder Self-Attn

约定:1=可见,0=屏蔽;在 score 上 masked_fill(mask==0, -inf)

验收

  1. 打印 create_causal_mask(5),确认是下三角 1
  2. tokens=[[5,27,0,0]] 的 padding mask,最后两列全 0
  3. 组合后:既不能看未来,也不能看 PAD

9.4 阶段 B:Attention 接口统一支持 mask

现状

  • transformer.py 的 Block 已支持 mask=None + masked_fill
  • decoder_layer.py 内部 attention() 也支持 mask
  • 但上层调用是否每次都传入、Encoder 是否向下传递,还不完整

要做的事

  1. 统一签名:所有算 Attention 的地方都是 attention(Q, K, V, mask=None)
  2. Softmax 之前屏蔽;用 float("-inf") 比极大负数更干净(Decoder 里已有写法,Encoder Block 可对齐)
  3. 可选:对 attention weights 再 Dropout(论文子层做法)

涉及文件

1
2
3
transformer.py      ← Encoder Block
decoder_layer.py ← Masked SA + Cross-Attn
(若还有独立 attention 函数,一并改)

验收

构造一个「故意把未来位置设为可见会泄题」的单元测试:加上 causal mask 后,第 1 行对第 3、4 列的权重必须≈0。


9.5 阶段 C:Encoder / Decoder 堆栈真正把 mask 传下去

这是当前工程里最关键的缺口之一。

现状问题(对照代码)

1
2
3
4
# encoder.py 当前大致是:
def forward(self, x):
for layer in self.layers:
x = layer(x) # ← 没有传 mask

即使 Block 支持 mask,Encoder 不往下传等于没用。

目标调用链

1
2
3
4
5
6
7
train / infer
→ create_padding_mask(src) / create_causal_mask(tgt_len)
→ TransformerSeq2Seq(..., encoder_mask, decoder_mask)
→ Encoder(x, mask=encoder_mask)
→ 每个 TransformerBlock(..., mask=encoder_mask)
→ Decoder(x, memory, self_mask=decoder_mask, cross_mask=encoder_mask)
→ 每个 DecoderLayer(...)

建议修改清单

文件 改动
encoder.py forward(self, x, mask=None)layer(x, mask=mask)
decoder_stack.py 确认每层都收到 self_mask / cross_mask
transformer_seq2seq.py forward 已有 mask 参数则打通;训练时不要再传 None 敷衍
seq2seq_train.py batch 里构造 mask 再喂模型
seq2seq_inference.py 逐步生成时,每步按当前长度重建 causal mask

Cross-Attention 的 mask 通常是 Encoder 侧 padding mask(挡住源句 PAD),不是 Decoder 的因果掩码。因果掩码只给 Decoder 的 Self-Attention。

验收

  1. 源句带 PAD:Encoder 输出中,PAD 位置不应再被其它位置强烈关注(可打印 attention 权重做一次可视化)
  2. 推理时拉长已生成序列:每步 mask 尺寸随 decoder_tokens.shape[1] 增长

9.6 阶段 D:用「带 PAD 的玩具数据」复训一轮

在上真实语料前,先用故意补 PAD 的假数据验证 Mask 真的生效。

做法

  1. 把现在定长、无 PAD 的 encoder_tokens 改成不等长 + collate 补 PAD
  2. target 的 PAD 位置在 CrossEntropy 里用 ignore_index=pad_id
  3. 训练后检查:
    • loss 仍能下降
    • 推理生成序列合理
    • (可选)把 causal mask 去掉做反例:训练会“作弊”,或生成异常——用来证明 mask 有用

建议命令习惯

1
2
3
cd F:\transformer_practice
python seq2seq_train.py
python seq2seq_inference.py

每完成一阶段保留权重命名,例如 seq2seq_model_masked.pth,避免覆盖最早能跑通的 checkpoint。


9.7 阶段 E:真实 Seq2Seq 数据管线

目标任务(建议先选一个)

候选 输入 → 输出 为什么适合现阶段
英→中 / 中→英 小平行语料 翻译 最贴近原论文场景
文本摘要小样本 长 → 短 练 Encoder–Decoder
拼写纠错 / 拼音→汉字 序列转换 数据好造

新建模块建议

1
2
3
4
5
6
7
8
9
10
11
dataset.py
- 读原文/译文
- 调 Tokenizer(阶段 F 可先用空格/`jieba` 临时方案)
- __getitem__ 返回 src_ids, tgt_ids

collate_fn
- pad 到 batch 内最大长度
- 返回:
src, tgt_in, tgt_out,
src_padding_mask,
tgt_self_mask(causal ⊗ tgt_padding)

训练脚本改造点

1
2
3
4
5
DataLoader → 每个 batch 带齐 tensors + masks
optimizer(可先 Adam)
可选:学习率 warmup(论文有,可放阶段 H)
每个 epoch 验证:loss / 粗略准确率 / 抽几条翻译打印
保存 best_model(看验证 loss,学 U-Net 那套 best checkpoint)

数据可以很小(几百~几千句)先跑通。先求 pipeline 正确,再求效果。

验收

  • 一个 batch 里既能有短句也能有长句
  • ignore_index 后 PAD 不贡献 loss
  • 验证集上能打印「源句 / 真值 / 预测」三条对照

9.8 阶段 F:Tokenizer(替换手写 token id)

现状vocab_size=100 + 手工数字 id,只能验证结构。

路径(由易到难)

1
2
3
4
5
临时:空格分词 / 字符级 vocab

进阶:HuggingFace tokenizers / SentencePiece

搞清:BPE vs WordPiece vs Unigram 差在哪

建议闭环

1
2
3
4
5
6
7
原始句子
→ Tokenizer.encode
→ input_ids (+ special: BOS/EOS/PAD/UNK)
→ Transformer
→ output_ids
→ Tokenizer.decode
→ 可读句子

验收

  1. 任意一句中文/英文,encode→decode 可还原(或按 tokenizer 规则合理还原)
  2. 词表大小、UNK 比例心里有数
  3. 训练脚本不再出现「写死的 [5,27,91]」作为唯一数据源

现状seq2seq_inference.py):每步取 argmax,贪心一条路走到底。

升级目标

1
2
3
4
每步保留 beam_size 条部分序列
按累计 log 概率排序
可加长度惩罚,避免永远偏好短句
最终返回 top-1 或 top-k

建议新建 beam_search.py

1
2
3
4
5
def beam_search(model, src, bos, eos, beam_size=4, max_len=50):
# 维护 (score, token_list) 堆
# 每步扩展 → 截断到 beam_size
# 遇到 EOS 的假设进入完成列表
...

验收

  • 同一 srcbeam_size=1 应退化成接近贪心
  • beam_size=4 能给出不同于贪心的候选,并按分数排序
  • 仍遵守 causal mask(不能因搜多条就泄露未来)

9.10 阶段 H:模型规模向论文靠拢

当前演示配置 vs 论文

现在(练习) 论文原版量级
d_model 4 512
num_heads 2 8
d_k 2 64
d_ff 8(示意) 2048
num_layers 2~6 6
Dropout 部分有 0.1 0.1
位置编码 有简化版 sin/cos 公式版

建议升配节奏(仍一次动一处)

1
2
3
① d_model=64, heads=4, layers=2, d_ff=256   ← 先在真实小数据上稳
② d_model=256, heads=8, layers=4, d_ff=1024
③ d_model=512, heads=8, layers=6, d_ff=2048 ← 对齐论文结构

同时把 position.py / 模型内 PE 统一成论文 sin/cos(含 10000^{2i/d}),去掉仅 sin(pos) 的占位写法。

升规模后显存和训练时间会上去。可先 CPU 小配置验证正确性,再 GPU 训。学习率、warmup、Label Smoothing 也可在这一阶段按论文补上。

验收

  • 配置改大后 forward 形状正确:[B,L,512] 贯穿
  • 小语料上 loss 能下降(不要求立刻赶超预训练模型)
  • 同一套 Mask/Tokenizer 代码无需推倒重来

9.11 阶段 I:三条最终方向(择一路做深)

Mask + 真实数据 + 推理稳定之后,再分叉——不要三线同时开

方向 1:Encoder 系 ≈ 小型 BERT
  • 只保留 Encoder Stack
  • 任务:Masked Language Modeling(随机遮词预测)
  • 下游:[CLS] 向量做分类
  • 适合巩固双向 Self-Attention + Padding Mask
方向 2:Decoder 系 ≈ 小型 GPT
  • 只保留 Decoder(或 Encoder Block + 强制 causal mask)
  • 任务:Language Modeling(预测下一个 token)
  • 下游:续写 / 对话玩具
  • 适合巩固因果掩码与自回归训练
方向 3:Encoder–Decoder ≈ 小型 T5
  • 保持现在的 Seq2Seq 骨架
  • 任务:翻译 / 摘要 / 改写
  • 最贴近当前 transformer_seq2seq.py 的自然延伸

若与泡沫图像项目并行:CV 侧继续 Boundary / 实例分割;NLP 侧建议优先走 方向 3(T5 式 Seq2Seq),因为现有代码资产最多,迁移成本最低。想补「纯生成」再开 GPT 线。


9.12 推荐时间盒与里程碑(可按周执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
第 1 周:阶段 A + B + C
交付:mask 可视化 + Encoder/Decoder 传 mask 的单元测试

第 2 周:阶段 D
交付:带 PAD 玩具数据复训成功,权重 seq2seq_model_masked.pth

第 3 周:阶段 E(可用简陋分词)
交付:DataLoader 跑通,验证集打印源/目标/预测

第 4 周:阶段 F + G
交付:Tokenizer 闭环 + Beam Search 推理脚本

第 5~6 周:阶段 H
交付:中等规模配置在真实小语料上稳定训练

之后:阶段 I 三选一做最小 Demo
交付:README + 推理示例 + 与论文结构对照表

9.13 路径总图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
【已完成】
Embedding / PE / Attention / MHA / Encoder
Classifier(acc=1.0)
Seq2Seq 训练(loss↓)+ 贪心自回归推理
mask.py 初版、Decoder 主结构



【正在补的正式能力】
A Mask 工具完善
B Attention 统一 mask
C 堆栈透传 mask
D 带 PAD 复训验证



【工程化】
E 真实 Dataset/DataLoader
F Tokenizer
G Beam Search
H 论文级规模 + 标准 PE/优化器细节



【产品向分叉】
I-1 小型 BERT(MLM+分类)
I-2 小型 GPT(LM 生成)
I-3 小型 T5(翻译/摘要)← 与当前 Seq2Seq 最顺

十、总结

把整条学习线收个尾:

  1. 总览:Transformer = Encoder 堆栈 + Decoder 堆栈;并行算整句,靠注意力建长距离依赖
  2. 表示:Embedding 解决“词是谁”,PE 解决“词在哪”,合成 $X\in\mathbb{R}^{L\times d}$
  3. Self-Attention:$QK^T/\sqrt{d_k}$ → Softmax → 加权 $V$;矩阵化才能高效训练
  4. Multi-Head:多子空间看关联,Concat 后再 $W^O$
  5. FFN / 残差 / LN:交流后各自加工;LayerNorm(x+SubLayer(x)) 让深层可训
  6. Decoder / Mask:因果掩码防未来泄露;Cross-Attention 用 Encoder 的 K、V
  7. 从零代码transformer_practice 已跑通分类与 Seq2Seq 生成
  8. 后续路径:Mask 全链路 → 带 PAD 复训 → 真实数据 → Tokenizer → Beam Search → 规模升级 → BERT/GPT/T5 三选一做深

从“会背公式”到“能手写出带 mask 的 forward,并训练出能自回归吐 token 的模型”,再到“按验收标准一步步补到论文级工程”,这条线和学 U-Net 时一样——核心始终是:每个张量从哪来、变成什么形状、这一层到底在解决什么问题,以及下一步只改一个变量。


参考资料

  • Vaswani A. et al. Attention Is All You Need (NeurIPS 2017)
  • Transformer 模型详解(CSDN,架构主参考):https://blog.csdn.net/benzhujie1245com/article/details/117173090
  • 个人整理:transformer架构.mdTransformer 从零实现项目进度总结与后续计划.md
  • 手写工程:F:\transformer_practice
  • 关联学习:U-Net / Attention U-Net 图像分割笔记(同系列,CV 侧注意力对照)