30u30 中文导读 / 30 天目录 / 第 14 天

第 14 天 · 论文每一段,都对应一段能跑的代码

第 3 单元The Annotated TransformerAlexander M. Rush(2022 版:Austin Huang, Suraj Subramanian, Jonathan Sum et al.)博客(Harvard NLP)/ NLP-OSS 2018;2022 年更新版 · 2018
这是导读,不是翻译。先用 10 分钟拿到这篇论文的思想地图,再去读 原论文 →,去原仓库的 14_annotated_transformer/ 目录 → 动手写代码。
给孩子讲:这一天有 10 岁孩子能听懂的讲义,带猜字游戏和动手活动,可打印。打开儿童讲义 → · PDF 简体 · PDF 繁體

一句话

这篇博客按原顺序拆开第 13 天的论文,每段文字后面紧跟实现它的 PyTorch 代码。读完就得到一个能训练、能解码的 Transformer。它补上了论文没写清的工程细节:掩码、批处理、标签平滑和学习率调度。

论文讲了什么

它是什么。这不是一篇新论文,而是一份可以运行的阅读笔记。作者把 Attention Is All You Need 的正文按段落摆出来,每段后面写出对应的 PyTorch 代码。这种写法叫文学编程:文字和代码交错,读者从上往下读,同时就在执行。

第 1 部分:模型结构。代码用一组小类自底向上搭出模型:

第 2 部分:训练。论文第 5 节只有一页,这里展开成完整代码。Batch 类生成两种掩码:源句的填充掩码,以及目标句的"填充 + 后续位置"掩码。subsequent_mask(5) 输出一个 5 × 5 下三角矩阵,第 3 行只有前 3 个位置为真。优化器用 Adam,学习率先预热再衰减。标签平滑用 KLDivLoss 实现:正确词拿 0.9 的概率,剩下 0.1 分给其他词。

第 3 部分:例子。第 1 个例子是复制任务:输入一串随机符号,输出原样复制。几分钟就能训练好,适合检查代码有没有错。第 2 个例子是真实的德译英任务,2022 版使用 Multi30k 数据集和 spaCy 分词。文章最后把各层各头的注意力权重画成热力图。

两个版本。2018 年 4 月的原版由 Rush 发布。2022 年版由 Austin Huang 等人更新:适配新版 PyTorch,把代码整理成可以直接运行的笔记本。今天建议读 2022 版。

值得注意的一处差异。代码的 SublayerConnection 先做层归一化,再进子层,即 x + dropout(sublayer(norm(x)))。论文写的是 norm(x + sublayer(x))。作者在注释里说明,这样写是为了代码简洁。巧的是,后来的大模型大多采用了这种归一化前置的写法。

关键概念

文学编程(Literate Programming)
Knuth 提出的写法:程序按人的阅读顺序组织,解释文字和代码交错。本文把这种写法用在论文复现上。
子层连接(Sublayer Connection)
把归一化、子层、dropout 和残差相加封装成一个模块。编码器每层用 2 次,解码器每层用 3 次。
后续位置掩码(Subsequent Mask)
一个下三角布尔矩阵。解码器第 i 个位置只能看到第 1 到第 i 个位置,训练时可以一次并行算完整句。
标签平滑(Label Smoothing)
训练目标不用 one-hot,而是给正确词 0.9、其余词平分 0.1。困惑度会变差,但 BLEU 会提高。
Noam 学习率调度(Noam Schedule)
学习率 = d_model^-0.5 × min(step^-0.5, step × warmup^-1.5)。先线性升高,到 warmup 步后按平方根倒数下降。
贪心解码(Greedy Decoding)
推理时每一步取概率最高的词,接到输入末尾,再预测下一个词。实现最简单,但不保证整句概率最高。

历史位置:为什么值得读

2017 年论文发表时,官方代码在 Google 的 Tensor2Tensor 库里。那套代码功能全,但层层抽象,初学者很难找到"注意力到底在哪一行"。论文本身又省略了很多实现细节,例如掩码的形状、批次怎么组织。

2018 年,Harvard NLP 的 Alexander Rush 发布了这篇博客,用一套完整、可运行的 PyTorch 代码讲清整个模型。它很快成为学习 Transformer 的标准材料,并带出了一种写作体裁:

它和清单的关系很直接:第 13 天给出设计,第 14 天给出实现。读论文时觉得"差不多懂了",写代码时才会发现形状、掩码和广播处处是坑。这篇博客的价值,就是把这些坑提前标出来。

学前准备

在原仓库怎么学

  1. 先在浏览器打开原博客,再读 README.md,了解本目录和第 13 天的分工。
  2. 读 paper_notes.md,把论文章节和代码类名对上。
  3. 读 implementation.py。类名与原博客一致:clones、subsequent_mask、SublayerConnection、MultiHeadedAttention、EncoderDecoder,可以并排对照。
  4. 做 exercises/ 里的 5 个练习:exercise_01_attention.py 写带掩码的注意力,exercise_02_multihead.py 写多头模块,exercise_03_encoder.py 搭编码器栈,exercise_04_training.py 搭训练流程,exercise_05_inference.py 写解码。答案在 exercises/solutions/。
  5. 运行 train_minimal.py,在复制任务上训练,确认模型能原样输出。
  6. 用 visualization.py 画注意力热力图和位置编码图案。
  7. 用 notebook.ipynb 整体复习,CHEATSHEET.md 速查。

今天再看

这份代码今天仍是讲解 Transformer 最常用的参考实现。但直接拿它做项目,要注意 4 个变化:

这份代码最持久的价值是方法:读论文时同步写代码,用最小任务验证每个部件。这个习惯在 2026 年仍然适用。

容易卡住的地方

延伸阅读