资讯动态

Transformer零基础入门:注意力机制、多头与编码解码流程拆解

发布时间:2026/10/22 20:05:55 来源:尧图企业网站定制
我最初看Transformer资料的时候最大的挫败感来自“每个字都认识连起来不知道在讲什么”。什么Query、Key、Value什么多头注意力、位置编码公式一摆人就懵了。直到我把那篇经典的《Illustrated Transformer》图解博客反复啃了三遍脑子里才终于有了一个“画面”原来注意力机制就是“按需取用”多头就是“多角度观察”Transformer的整体流程说白了就是“先理解再生成”。这篇内容就是按那个思路把Transformer核心拆成大白话和可复现的细节哪怕你是零基础小白也能轻松入门。我建议你收藏起来遇到不懂的模块随时回来翻。1. 别被公式吓到先建立一幅“注意力”的画面1.1 翻译一句话时人脑到底在做什么要理解Transformer先别急着看矩阵运算先想一个问题你在做翻译的时候大脑是怎么工作的比如把英文“I love you”翻译成中文你看到“love”这个词不会只盯着“love”本身你会下意识把“I”和“you”拉进来“I”是动作发出者“you”是接受者这样才知道“love”翻译成“爱”而不是“喜欢”更合适顺序也不会颠倒。这个“拉进来”的过程本质就是一种注意力的分配你在处理当前词时给句子里的其他每个词分配了不同的关注权重有的话关联强有的话关联弱。Transformer里的“注意力机制”干的就是这件事。它让模型在处理某个词时可以动态地、按权重地去看句子里的所有其他词。注意是“所有”其他词不是一个固定窗口这是它和早期循环神经网络最不一样的地方。早期模型更像“一个字一个字往后读记住之前的信息”读到最后前面的内容可能忘得差不多了而Transformer是“一句话里每个词都直接和所有其他词对话”信息不会因为离得太远就流失。这也是它能成为今天几乎所有大模型底座的核心理由。1.2 图书馆查资料的三步流程注意力机制听起来抽象但你可以想象自己在一座图书馆里查资料。你心里有一个想查的问题比如“怎么做好番茄炒蛋”。这就是Query可以理解为“查询”也就是你最关心的那个信息需求。图书馆里每本书都有书名、关键词我们把这些索引信息叫作Key也就是“键”。书里真正的内容做法、技巧、注意事项叫作Value也就是“值”。你的查资料过程是这样的你带着Query走到书架前把Query和每一本书的Key做对比“这本是不是讲家常菜的”“那本是菜谱还是营养学”对比结果是一个匹配程度分数。匹配度越高的书你会越多地翻阅它、引用它匹配度低的书你扫一眼就跳过。最后你脑子里形成的“番茄炒蛋做法”其实是所有这些书内容的加权汇总权重就是刚才算出的匹配度分数。注意力机制的计算过程一模一样Query先和每个Key算一个相似度分数再把这些分数变成权重然后用权重去加权求和所有Value。谁和当前需求越相关谁对最终输出的贡献就越大。好消息是这一切都是模型自己学出来的不是我们手工指定的规则。1.3 注意力公式与缩放因子的直觉理解了画面现在可以看公式了。自注意力机制的核心公式长这样Attention(Q, K, V) softmax(Q × K的转置 / √d_k) × V拆开看Q表示查询矩阵代表“现在想关注什么”。K表示键矩阵代表“被用来匹配的标签”。V表示值矩阵代表“真正内容所在”。Q和K做点积算出每对词之间的匹配分数。分数越高说明这两个位置越该互相注意。除以√d_k是为了防止分数过大把softmax推向饱和区。softmax把分数变成一组非负且总和为1的权重。最后用权重和V做加权平均得到当前词的输出表示。那为什么要除以√d_k而不是随便除个数我用自己的经历说明。Q和K的点积结果会随着向量维度增大而变大。如果向量维度是64点积结果可能远大于1softmax输入太大会让输出变得“非黑即白”也就是概率几乎集中到某一个位置梯度变得非常平缓模型几乎学不动。除以√d_k相当于把结果拉回方差接近1的量级让softmax落在相对敏感的区域梯度能正常回流。这个细节不少零基础教程会轻轻带过但实际排查代码时常常就是因为少了这一除训练就是不对。2. 从词到“带位置信息的向量”输入表示怎么做2.1 词向量把词变成一串数字计算机不能直接处理“love”“you”这些单词一切要从词嵌入开始也就是把词转成固定维度的向量。比如设置一个d_model也就是模型宽度在经典Transformer里是512。那么每个词进来都会对应一个512维的浮点数向量。你可以粗暴地把这个向量理解为“这个词在语义空间中的坐标”。意思相近的词向量之间的距离往往更近。比如“猫”和“狗”肯定比“猫”和“汽车”在语义上更接近。在实际操作里句子先被切分成token然后查词表把每个token映射到自己的嵌入向量。假设一句话有4个词那么输入矩阵的形状就是4×512每一行是一个词的向量。这个矩阵接下来会进入Transformer的各个层后面所有计算都基于它。这里有个初学者常犯的直觉错误以为嵌入向量是模型直接查表拿到的“真实答案”。其实嵌入层里的向量只是初始化值训练过程中会不断被更新。最终学到的向量方向才是模型对语义的编码结果而不是一开始就给定的“标准答案”。2.2 位置编码给每个词发一个“座位号”循环神经网络天然有顺序概念因为一个字是挨着一个字输入的。但Transformer是并行计算一句话所有词同时进去如果不额外告知位置模型根本不知道“我爱你”和“你爱我”有什么区别。词的集合一样语义却完全不同。解决办法就是给每个词的位置也编一个向量然后直接把位置向量加到词向量上。位置向量是哪里来的经典论文里用的是三角函数PE(pos, 2i) sin(pos / 10000的(2i/d_model)次方) PE(pos, 2i1) cos(pos / 10000的(2i/d_model)次方)这里的pos是词在句子里的位置i表示维度下标。我当时一直想不通为什么这么一大串三角函数能表示位置。后来换个角度才明白不同维度的正弦波拥有不同频率就像钟面上指针转的速度不一样。靠一组高频、低频组合的波形每个位置都能得到一个独一无二的“指纹”。同时三角函数还自带一个好处模型可以通过线性变换来学习相对位置关系这比直接用0到N的自然整数编码要灵活得多整数值没有上界而且不同位置之间的关系无法被周期性地建模。真正动手做模型时位置编码有两种用法一种是固定写死不加训练另一种是让模型自己学习一套位置嵌入。早期Transformer论文用的是固定三角函数后来很多大规模模型改用可学习的位置嵌入效果也没有差太多。关键点在于无论哪种都必须记得把位置信息和词向量加起来再送入模型漏掉这一步模型连“语序”都感知不到。2.3 输入层之后的三种张量Q、K、V从哪来不少教程直接画三个矩阵W_Q、W_K、W_V然后说“每个词都乘一下”但为什么是三个矩阵这其实是从输入去“投影”出三种不同的信息。还是以4×512的输入矩阵X为例。我们会初始化三个形状为512×64的可学习权重矩阵W_Q、W_K、W_V。注意这里的64是d_k是每个注意头里的维度不是512。然后直接做矩阵乘法Q X × W_Q得到4×64的查询矩阵K X × W_K得到4×64的键矩阵V X × W_V得到4×64的值矩阵这里的逻辑是同一个输入词向量通过乘以不同矩阵看清三种不同角色。Query代表“我打算索取什么”Key代表“我能提供什么索引”Value代表“我实际能给出的内容”。你可以把它想象成一个人同时戴着三副眼镜去看同一个词每一副眼镜突出了不同侧面。这里有个维度细节特别容易搞混我在带新人时几乎每人都会踩一次。X的列数是d_model512投影到Q、K、V时每个头的维度是d_k64。也就是说我们用一个大矩阵512×64去把512维的词向量压到64维的“注意力空间”。后面多头注意力会把8个头的64维结果拼起来重新拼回512维。理解这个“先压再拼”的流程整个Transformer的维度变化就通了一半。3. 核心部件逐个拆自注意力、多头与残差3.1 自注意力一次完整计算现在我们把上面几块组合起来看一次完整的自注意力计算到底长什么样。还是那个句子4个词输入矩阵X是4×512。第一步通过三个投影矩阵得到Q、K、V它们的形状都是4×64。第二步把K转置变成64×4然后做Q × K的转置。这里的矩阵乘法是4×64乘以64×4得到4×4的分数矩阵。分数矩阵里第i行第j列的含义是句子第i个词对第j个词的关注分数。每行代表一个词的注意力分布。第三步除以√d_k也就是√648。然后对每一行做softmax。这一步很关键softmax是按行操作的每一行的值会被压缩到0到1之间且加起来等于1。第四步用这个4×4的权重矩阵去乘VV的形状是4×64。结果是4×64的矩阵。这个结果里的第i行就是第i个词综合了整句话信息后的新表示。你发现没有这个过程的输入和输出长度完全一样。正是这种“不管句子多长每个词都跟所有词交互一次”的特性让Transformer天然擅长捕捉长距离依赖。但它也有代价计算复杂度是O(n²)级别的句子越长这个两两交互的矩阵会平方级膨胀。这也是后来各种稀疏注意力、线性注意力优化的出发点。3.2 多头注意力同一个意思八个角度看如果只做一次上面的注意力计算模型只能在一个维度上判断词与词的关系。但实际语言太复杂了。比如“这只动物没吃到鱼因为猫在睡觉”动物指的是谁如果只看语义相似度不一定能分辨出指代关系。看到没“因为”后面的cat才是决定“the animal”所指的关键。多头注意力就是把计算并行重复好几遍。经典Transformer用8个头每个头独立拥有一套W_Q、W_K、W_V只是维度不同。还是4×512的输入经过8组投影后得到8组4×64的结果。每一组分别做独立的注意力计算最后把这8个4×64的结果在最后一维拼起来又变回4×512。为什么要多头我打一个比方。如果一个人带着单一思路读文章可能会漏掉隐藏的联系。多头注意力相当于同时开了8个视角有的头更关注语法角色有的头更关注指代关系有的头更关注相邻词共现。最后所有的视角融合到一起信息就全面了。不过多头也不意味着头数越多越好。每个头计算都有成本头数太多单个头的维度太小反而学不到什么东西。实际工程里选择8、12、16都比较常见关键看模型总宽度和训练数据量是否匹配。很多大规模模型最终其实就是“多头的注意力层堆叠”。3.3 残差连接与LayerNorm让深层网络“不散架”知道了多头注意力还不够经典Transformer里每个注意力层之后还会紧跟两个固定动作残差连接和LayerNorm。残差连接说白了就是“把输入原样加到输出上”。假设输入的向量是x经过注意力层后的输出是Attention(x)那么真正的输出是x Attention(x)。这个操作看起来简单作用巨大。它让梯度在深层网络中有一条“直接回流”的高速路避免网络层数太深就显得梯度消失、无法训练。你试想一个20层的网络信息经过这么多次非线性变换可能早就“面目全非”了有了残差连接每一层至少保证不会比输入更差。LayerNorm做的事情是对每一条样本的每一个位置做标准化。具体来说它会把某个位置向量的所有维度变成均值为0、方差为1的分布然后再缩放和平移一层。这样做的好处是每一层的数值范围都相对稳定不至于因为网络加深而越来越爆炸。顺序也有讲究。Transformer里采用的是“先归一化再加残差”的做法不过在具体实现中有些代码库会把顺序反过来叫“Post-Norm”和“Pre-Norm”。早期论文用的是Post-Norm也就是“注意力后加残差再LayerNorm”后面很多大规模模型改用Pre-Norm出现在每层处理之前做归一化。两种各有优劣初学者不需要纠结只要知道这是一个工程调优的选择就好。4. 整个Transformer是怎么拼起来的Encoder与Decoder4.1 Encoder句子编码成特征单个注意力层讲完了现在看整个模型。经典Transformer由两部分组成Encoder编码器Decoder解码器。Encoder的职责是“读句子理解成特征”。每一个Encoder层包含两个主要部分一个是多头自注意力层另一个是前馈神经网络层。前馈神经网络就是两个全连接层加一个激活函数它对每个位置独立处理可以理解成对注意力提取到的信息做一次“深度加工”。这两部分各自都带了残差连接和LayerNorm。经典模型把Encoder层堆叠6次。每一层都在不断“提炼”信息第一层可能关注基本的词义和搭配高层就能捕捉更复杂的语义关系。输入句子越复杂、越歧义堆叠深层的优势就越明显。这就是为什么“深”对Transformer这么重要。一个关键现象是 Encoder内部的自注意力不遮掩任何位置所有词互相可见。因为理解句子不需要做防偷看所有信息都可以光明正大地参考。4.2 Decoder带“防偷看”的生成器Decoder的任务是逐词生成输出。它比Encoder多了一些限制。Decode的基础结构和Encoder很像也有多头自注意力层、前馈神经网络层也有各自的残差连接和LayerNorm。但有两个关键不同。第一个是掩码自注意力也就是Masked Self-Attention。生成翻译时模型必须一个一个词地出结果。比如翻译“我爱你”第一个词生成“我”的时候它不应该提前看到“你”和“爱”。所以Decoder里的自注意力会用一个上三角掩码把“未来位置”的信息屏蔽掉。计算注意力分数时未来位置的分数会被设置为负无穷经过softmax后权重就变成0。这就是“防偷看”机制。第二个是交叉注意力也就是Cross-Attention。Decoder不只是自己埋头生成它还需要参考Encoder对原句的理解。交叉注意力的Query来自Decoder当前层的输出而Key和Value来自Encoder最后一层的输出。你可以把Encoder看成“已经读完全文的记忆库”Decoder每生成一个词都会去这个记忆库里检索最相关的信息。这一步就是机器翻译里“忠实原文”的保证。整个生成过程也叫自回归。模型先输入一个起始符预测第一个词把第一个词当输入预测第二个词如此反复直到预测出结束符为止。4.3 一个极简示例帮助理解用“I love you”翻译成“我爱你”走一遍完整流程你马上就有感觉。Encoder读到“I love you”三个词的嵌入向量加上位置编码经过6层Encoder后每个词都变成了一个融合了整句信息的上下文向量。这里模型已经隐约知道“love”的发出者是“I”对象是“you”。Decoder收到一个起始符输出第一个词的预测时先用掩码自注意力看当前序列里只有起始符自己然后通过交叉注意力去Encoder的输出中问“这个句子的核心动词是什么动作的发出者是谁”得到线索后预测出“我”。生成第二个词时Decoder输入变成了“起始符我”掩码自注意力允许它在这两个位置之间做注意力交叉注意力再去原句里重点检查“动作是什么”于是预测出“爱”。以此类推。整个过程非常像一个“边查笔记边说话”的人笔记是Encoder的理解说话内容是Decoder的产出。5. 实操环节用代码与手推把流程验证一遍5.1 几十行代码模拟一次自注意力看图读了十遍不如跑一次代码来得快。下面这个例子用Python和numpy模拟一个极简版的自注意力计算验证维度变化也方便你对照公式理解。import numpy as np def softmax(x, axis-1): # 防止指数溢出减去每行最大值再求exp e_x np.exp(x - np.max(x, axisaxis, keepdimsTrue)) return e_x / np.sum(e_x, axisaxis, keepdimsTrue) def attention(Q, K, V): d_k Q.shape[-1] scores Q K.T / np.sqrt(d_k) weights softmax(scores) return weights V np.random.seed(42) d_model 8 # 模型宽度 d_k 4 # 单头维度 seq_len 4 # 句子长度 # 随机造一个“句子”4个词每个词8维 X np.random.randn(seq_len, d_model) # 三个可学习投影矩阵 W_Q np.random.randn(d_model, d_k) W_K np.random.randn(d_model, d_k) W_V np.random.randn(d_model, d_k) Q X W_Q K X W_K V X W_V out attention(Q, K, V) print(Q shape:, Q.shape) print(K shape:, K.shape) print(V shape:, V.shape) print(输出 shape:, out.shape)这段代码里输入X是4×8投影后得到4×4的Q、K、V。attentions中首先计算QK.T得到4×4的注意力分数矩阵除以√4也就是2softmax后得到权重再和V做矩阵乘法最后输出还是4×4。你如果打印中间结果会看到每一行的权重之和都等于1这就是“每个词对其他词的关注度分配”。实际工程代码里Q、K、V的维度通常保持相同以便处理方便。但代码验证时你完全可以把d_k和d_v故意设置成不同值。再回到库里查资料那个比喻查询的维度和内容维度不必须一致。只是经典实现里用了相同值减少复杂度。5.2 怎么把图解中“盒子和箭头”画成自己的推导图当初那篇经典图解对我帮助最大的一点是它画出了每一步张量形状的变化。你完全可以自己复刻这种图不需要任何绘图工具纸笔就够。我的练习方法是把一句话的输入尺寸写在最左边比如“4×512”然后每过一个模块就用箭头标出新尺寸。第一层词嵌入4×512。加位置编码还是4×512。经过3组投影Q、K、V各是4×64。Q乘以K的转置4×4。除以√d_k并softmax还是4×4。乘以V4×64。8个头的输出拼接4×512。残差连接加LayerNorm还是4×512。前馈网络里面有中间扩大的维度比如2048输出又回到512。等你把这条链画完看一眼依然是一张整齐的“盒子箭头图”Transformer在你心里就再也不是零散公式而是一台有固定流水线的机器。任何维度问题只要你不确定就重新在纸上走一遍尺寸基本不会错。5.3 新手常见问题与排查我在学习初期踩过不少坑也帮不少人排查过类似问题把最高频的几个整理成一张表常见问题可能原因排查方法代码里维度报错混淆了d_model和d_k投影后形状对不上从后往前检查每个张量的最后一维确认投影矩阵形状注意力分数特别大softmax后权重几乎非0即1忘记除以√d_k检查有没有做缩放scaled dot-product attention里缩放不是可选项模型训练时梯度不稳定Loss忽高忽低缺少LayerNorm或残差结构有问题检查每个子层后是否做了Add Norm顺序是否一致训练和推理时结果差别很大Decoder掩码在训练时可能漏加或被忽略确认训练时是否使用teacher forcing预测时是否自回归逐步生成输入顺序颠倒后结果几乎没变化位置编码缺失或没有被正确加到输入上检查词向量和位置向量是否确实做了相加操作另外给大家一个排错经验不要凭肉眼去盯Loss曲线猜问题先在很小的数据集上跑过拟合测试。如果你用几十条样本都无法让Loss降到很低那大概率是模型结构问题或维度问题而不是数据问题。这个小技巧帮我省下的调试时间至少抵得上自己瞎试三天。6. 学完这些之后这些概念的实际用处6.1 从Transformer衍生出的方向Transformer核心机制尤其是自注意力已经不只是机器翻译的专利。现在几乎所有主流大规模预训练模型无论生成文本、总结摘要、对话系统还是代码生成底层都离不开这类结构。有一种理解是Transformer提供了一种“通用特征抽取器”。它不限定输入形式你可以把文本转成token序列也可以把图像切成patch序列甚至可以把音频波形切成片段序列。只要是“序列到序列”的逻辑Transformer都能套进去。这个特点催生了各种跨模态的方向比如用同一套注意力机制同时处理文字和图像输入或者在文本基础上生成图片。很多当初看来很复杂的视觉任务最终用的核心反而就是“图像切片后当成词来处理”的思路。这个范式的迁移能力直接导致了一个结果预训练加微调成了主流工作模式。先用大规模语料让模型学通用知识再用小规模标注数据做下游任务适配。Transformer之所以适合这种模式因为它有很强的共性表达能力和良好的可扩展性堆层数、加数据就能持续提升效果。6.2 为什么“多头”是工程和效果的权衡很多人刚学会多头注意力时容易陷入一个误区多头越多越好。实际上多头的核心价值在于“用更多子空间去建模不同类型的关系”但每个头的维度是d_model除以头数。头数越多每个头的维度越小能表达的信息就越有限甚至会变得冗余。在实际模型配置里d_model512h8每个头维度就是64。如果你把头数改成16每个头只有32维信息表达能力下降效果不一定提升反而计算成本上去了。这个“先降维再拼接”的套路是工程里典型的高效实现技巧。所以理解多头注意力不只看个数还要看每个头“容量”是否够用。6.3 进阶路线从这张图走向更多源码复现如果你已经能完整手推一遍上面的流程下一步就不应该只停留在看图和回顾公式了。我的建议是两条路并行。第一条路去找一份极简的Transformer实现最好是单文件、纯矩阵运算那种逐行读并打上自己的注释。不要只抄代码要能回答“这一行对应的是公式里的哪一部分”。尤其是mask、padding mask和attention mask的区别光靠概念记忆很容易忘动手写一遍才记得牢。第二条路尝试在小数据集上训练一个翻译或者文本分类模型。不需要用大规模语料只需验证流程能跑通。配置好一个微型Transformer用几百上千条样本观察Loss是否下降生成是否有初步语义。这个过程会帮你看清“配置参数”“训练过程”“推理阶段”三者的工程差异。我自己的经验是一旦跑通这个mini全流程再回头看各种大模型的技术报告就不再是“看天书”了。刚开始接触Transformer时我一直觉得那些图很复杂直到自己动手把每个张量形状标出来再把示例代码跑通才对“注意力”有了真正踏实的理解。如果让我给你一个最朴素的学习顺序建议那就是先看经典图解建立画面再手推一遍维度变化最后用几十行代码验证自己的理解。三件事做完Transformer核心就算真正入门了。之后再遇到那些复杂变体只需要在这张图的基础上做加减法整个体系就不会再散。最后分享一个小习惯我至今都在用每次读到一篇模型论文第一件事不是看理论分析而是先找模型结构图把输入张量的起始形状标出来顺着数据流往下走。哪一步维度变了、哪里多了一个分支、哪里加了mask全部高亮出来。这个方法对我理解模型特别有效你可以试试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑