Transformer 实战

把整章写过的模块组装起来,亲手训练一个 Transformer 并看它学会翻译。

知识课 · 约 35 分钟 · 更新于 2026-08-14

任务:教 Transformer 翻译日期

是时候把亲手写过的所有模块组装起来、真实地训练一次了。选什么任务?要求很明确: 词表小、规律清晰,CPU 上几分钟就能看到效果。我们选 日期格式翻译 :

选它有两个理由:

  • 它 具备真实机器翻译的全部要素 :源序列、目标序列、长度可变, 生成时还要对齐原文
  • 它自带 一词多义 :同一个数字 12(源侧按字符切分,占相邻两个 token), 出现在月份位要译成「十二月」,出现在日期位要译成「十二日」,正好用来检验 「同一个词、不同上下文、不同表示」这一最初的目标

数据准备

构建源 / 目标两侧的小词表。源侧按字符切分,每个数字与分隔符各占一个 token; 目标侧的中文数字与「年月日」同样逐字成 token。两侧词表都含 <SOS> 、 <EOS> 、 <pad> 。 随机生成几千条日期对,编码成 ID 序列,用 DataLoader 组 batch:

损失侧别忘了对 <pad> 用 ignore_index (「训练与推理通路」讲过:注意力掩码管输入侧,ignore_index 管标签侧)。

组装模型

把此前实现的全部模块整合为一份完整代码,顺序是缩放点积注意力 → 多头注意力 → 位置编码 → Add & Norm → FFN → EncoderLayer / DecoderLayer → Encoder / Decoder → Transformer。任务很小,超参数也用小号的:

这只几十万参数的小模型,核心结构与千亿参数级别的大模型完全相同。

训练:看着乱码变成翻译

标准训练五步法(前向、算损失、清梯度、反向、更新)跑若干 epoch。训练中途每隔一段就用 translate 翻译同一个输入,观察输出的变化:

把这次训练 倒带重放 :横轴是训练轮次,纵轴是当轮的损失,曲线上每个点都是一次快照。 拖动滑块或点击时间线,就能看到损失一路下降的同时,模型对同一个输入 2026-6-12 当时给出的译文,从满屏乱码到格式对了数字还错,再到完全正确。 损失值与各阶段译文都取自上面那次真实训练的日志。

评估:学到的是规律还是背诵?

在 训练集之外 的新日期上测试。日期组合有上万种,训练只见过几千条, 如果模型在没见过的日期上也翻得对,说明它学到的是「数字怎么读、年月日怎么排」的 规律 , 而不是背下了训练样本:

打开模型内部:注意力在「对齐」

训练完成后,把交叉注意力的权重画出来:生成每个中文字时,解码器都在「看」原文的哪些 token?

「六」对齐月份位的「6」,「十二」对齐日期位的「1 2」;生成「年」「月」这类单位字时,注意力转向源串的分隔符「-」,因为它标出了年、月、日三段的边界,模型据此判断此刻该补哪个单位,末尾的「日」则回看日期的最后一位来收尾。这种「翻译时该看原文哪里」的对齐没有人教,是模型从数据里自己学出来的,也兑现了整个系列「注意力就是该关注谁」的出发点。图中权重为训练后模型对齐模式的代表性示意。

兑现承诺:检验一词多义

「编码器」中许下的承诺,现在兑现。取同一个数字 12(相邻两个 token)分别充当月份和日期的两条输入, 并 刻意让这个 12 落在相同的绝对位置 ,只让上下文变化。训练时源日期的写法在漂移、 位置随之改变,模型因此学会依赖上下文;这里反过来固定住位置,是为了把编码向量的差异干净地归因于上下文。 对比两件事:

把代码里那两个向量和它们的余弦相似度摆成可以对比的画面,再多换几个有歧义的数字各看一组:

切换上方的数字,几个有歧义的 token 都是同一回事:同一个字落在月份位和日期位,编码器给出的向量方向不同,最终译法也随之不同。向量与余弦为训练后模型输出模式的代表性示意。

同一个数字 12:两条输入里它的位置相同,编码器输出的向量却 显著不同 , 差异只能来自上下文;最终一个被译成「十二月」、一个被译成「十二日」。 同一个词在不同上下文中获得了不同的表示,并据此产生了不同的行为 ,从「静态词向量无法区分一词多义」出发的那个问题,到这里画上句号。

回看:多头分化了吗

「多头注意力」中还留过一个观察:未训练时各头只是「随机地不同」,训练后才会分化出有意义的模式。 现在用自己训练好的模型验证:

训练前,四个头都是接近均匀的随机花纹,彼此只是随机地不同,谈不上分工;训练后它们各自分化出可解释的模式,头 1 沿对角成带、只看邻近的字,头 2 把注意力投向两个「-」、用分隔符定位年月日的边界,头 3 回看前一个字,头 4 让同一段里的数字彼此相连。从随机到各有分工,正是「多头注意力」里那个观察在自己训练出的模型上的确认。各头权重为训练后注意力模式的代表性示意。

全程回顾,以及下一站

从「静态词向量无法表达上下文」这个问题出发,一路走到这里:

最后留一个眺望。Transformer 原本为翻译而生,但它的两半各自都能独当一面:

  • 只取 编码器 ,擅长「读懂」,代表是 BERT 一族,用于理解类任务
  • 只取 解码器 ,擅长「续写」,代表是 GPT 一族;今天的大语言模型,正是把这个「只会接下一个词」的解码器堆到极致的产物

你已经亲手实现过它们的全部核心部件。下一站,从这里出发。