1. 先搞清楚Transformer到底在解决什么问题
1.1 老办法为什么吃力:想想RNN是怎么读句子的
很多人第一次听说Transformer,是在了解机器翻译或者语言模型的时候。在这之前,很长一段时间里,主流模型是循环神经网络这一大家子,RNN、LSTM、GRU都算。
RNN处理句子有点像流水线上的工人:第一个词先进来,处理完,把“记忆”传给第二个词,第二个词处理完再传给第三个。好处是它天生适合有先后顺序的数据,坏处也很明显——它必须一个字一个字地往后走,没法跳过。
这就带来一个特别实际的麻烦:长距离依赖。我经常拿这句话举例子:“小明昨天把钥匙落在办公室里,结果今天早上他站在门口翻了半天包,最后只能去找物业。”你读这句话,很快就能判断“他”指的是“小明”,因为人是靠整句话的语义来理解指代的。但RNN从第一个词开始处理,等走到“他”的时候,前面关于“小明”和“钥匙”的信息早就在传递过程中被冲淡了。就像一群人玩传话游戏,传到后面,最初的信息已经严重失真。
工程师们想过很多改进办法,LSTM、GRU本质上都是给这个流水线加“记忆开关”,让重要信息能多留一会儿。可问题依然存在:信息是串行流动的,哪怕中间某个词携带了关键信息,传到最后也会打折扣,而且一个字一个字处理,GPU的并行计算能力完全发挥不出来,训练效率上不去。
1.2 Transformer的破局思路:我不挨个看了,我一起看
Transformer的想法完全换了个路子:一句话里的词,不再是按顺序一个一个处理,而是全部同时丢进模型,大家一起看。而且词与词之间到底谁和谁关系大,模型直接去“找重点”,不需要把信息一层层长距离传递。
这个思路放到生活里特别容易理解。RNN像开一场只能一个人发言的会议,每句话都得靠前面那个人转述,传到后面就变味了。Transformer更像所有人围在一张白板前,白板上写满了句子里的每一个词,谁跟谁有关系,大家直接看,直接标记,不用等谁传话。这样做最直接的收益就是:不管两个词在句子里隔得多远,模型都能在一步之内建立联系,长距离依赖问题基本被抹平了。
另一个容易被忽视的好处是训练效率。因为所有词同时进入模型,GPU那种几千个计算单元同时干活的能力终于被用上了。这也是当年那篇论文提出之后,整个行业迅速转向Transformer的很重要的现实原因——不是因为它只有理论上的优雅,而是真的训练得快、跑得动。
所以现在再回头看Transformer的作用,可以总结成一句话:它让模型学会“整句一起看,并且知道重点在哪”。这句话虽然简单,但几乎所有后续的大模型技术,都是在这个思路上长出来的。
2. 注意力机制:把“找重点”这件事变成数学
2.1 从图书馆找资料理解Q、K、V
既然要整句一起看,那怎么让模型知道谁是重点?这就轮到这个架构里最出名的概念——注意力机制。
网上讲注意力机制的资料很多,一上来就是Query、Key、Value、softmax、点积,看得人头疼。我换个方式,用图书馆找资料来类比。
想象你去图书馆想查“巴黎旅游攻略”。你自己心里有一个需求,这个需求就是Query。图书馆每一本书的书脊上都有一个标签,这个标签就是Key,比如《法国美食指南》《欧洲历史》《巴黎地铁地图》。你扫一眼书架,会下意识把心里的需求和每个标签做匹配:跟“巴黎旅游”相关的书,你会重点关注;不相关的书,你会自动忽略。找到几本重点书之后,你翻开它们,提取里面真正有用的内容,这些书里的正文就是Value。
Transformer里的注意力机制,干的事跟这个完全一样。句子里的每个词都会变成三个向量:Query、Key、Value。你可以粗暴理解为,Query是“我想找什么”,Key是“我是谁、我有什么标签”,Value是“我真正能提供的内容”。模型计算“某个词应该关注谁”时,就是用这个词的Query去跟句子其他词的Key做匹配,匹配度越高,注意力权重就越大,最后去提取对应Value的信息。
2.2 打分、归一化、加权:三步完成一次注意力
具体计算过程,拆开看其实只有三步。
第一步是打分。用Query跟每个Key算一个相似度分数,Transformer里常用的是点积,就是两个向量对应元素相乘再相加。点积的结果越大,说明这两个向量方向越接近,也就是“这个Key越符合我的需求”。为了保护计算稳定性,分数还会除以一个缩放因子,一般是词向量维度的平方根。这样做不是因为什么玄学,而是如果维度很大,点积结果会跟着变大,后面的归一化步骤就容易出问题,算出来的权重会过于极端。
第二步是归一化,一般用softmax把这组分数变成总和为1的概率分布。这样做的意义是让模型能“区分重点和非重点”,分数最高的词会拿到最大的权重,但其他词也不是完全没用,只是影响小一些。大白话理解softmax,就是“把所有分数排个队,然后放成一堆,谁的分数高谁分到的蛋糕就大”。
第三步是加权求和。根据这些权重,把每个词对应的Value向量一个个乘上权重再加起来,得到一个新的向量。这个新向量就是这个词在“看过整句话之后”的综合表示。它不像原来那样只代表自己,而是已经带着上下文信息了。比如“苹果”这个词,如果它出现在“苹果公司发布了新手机”里,经过注意力机制之后,它聚合到的上下文信息就更偏向“科技公司”;如果出现在“我削了一个苹果”里,聚合到的信息就更偏向“水果”。
2.3 多头注意力:多开几盏灯,多看几个角度
只看一次注意力够不够?不够。因为一句话里的关系是复杂的。比如“经理告诉秘书,她明天可以休假”,模型需要搞清楚“她”是谁,又要搞清楚“明天休假”是经理还是秘书的事,还要搞清楚谁是动作的发出者。单一注意力只能找到一种模式,很容易顾此失彼。
所以Transformer用了一个办法:多头注意力。你可以把它想成开会时同时开了好几盏射灯,每盏灯从不同角度照着同一句话。有的灯专门照亮语法关系,主谓宾一眼看清;有的灯专门照亮指代关系,谁指向谁清清楚楚;有的灯专门照亮情感色彩,哪些词是负面的哪些是中性的。每一盏灯其实都是一组独立的Q、K、V变换,最后把所有灯的结果拼在一起,再接一个线性层融合。
多头设置最大的价值,就是让模型不再“只从单一角度看问题”,而是有多个“视野”。这也是为什么实际模型里头的数量往往是8、12、32这样的数字——不是拍脑袋定的,是权衡过表达能力和计算成本之后的选择。头太少,看问题的角度太单一;头太多,算力和显存扛不住。
3. 位置编码:不给座位号,顺序就乱套了
3.1 并行输入带来的“没有顺序”的麻烦
Transformer把整句话同时丢进去,这个设计解决了效率问题,但也制造了一个新麻烦:模型分不清词的先后顺序。
对自然语言来说,顺序几乎是语义的一半。“我骂了他”和“他骂了我”,词一模一样,意思完全相反。词嵌入让每个词都有一个向量表示,但“我”的向量不管出现在句首还是句尾都是一样的,模型根本分不清“我骂了他”和“他骂了我”里的“我”和“他”谁先谁后。如果没有额外信息,Transformer看到的这两句话,就像两堆一样的积木,根本分不出差别。
这问题怎么解决?得给每个词一个“座位号”,让模型知道“这个词在第几个位置”。位置编码干的就是这件事。
3.2 位置编码怎么加?固定公式和可学习版本
最早提出Transformer的那篇论文,用的是一种固定的三角函数编码。简单来说,每个位置都会生成一个跟词向量维度一样的向量,这个向量的每一维,用正弦函数和余弦函数交替生成,频率不同。位置0、位置1、位置2……每个位置生成的向量都不相同,而且两个相近位置向量也相近,位置差得越远,向量差别越大。最后把这个位置向量直接加到原来的词向量上。这样做的好处是简单、确定、不需要训练,而且大致能表达“相对位置”的关系。
后来很多模型不满足于固定编码,开始让位置向量也参与训练。比如可学习位置编码,就是初始化一堆位置向量,然后随着模型一起训练,让模型自己决定什么样的位置信息最有用。再后来还有旋转位置编码这类更复杂的方案,核心思路是用旋转矩阵把位置信息融进Query和Key的计算里,好处是模型在推理时见到比训练时更长的句子,也不会一下子崩溃。这部分细节可以后面再深入,但核心逻辑不变:模型必须知道每个词站在第几排。
3.3 为什么说位置编码是Transformer的命门之一
因为注意力自身对顺序完全无感,所有顺序信息都靠位置编码硬塞进去。位置编码要是做得不好,模型对句子顺序的理解就是乱的。这也是为什么后来这么多改进模型都在位置编码上做文章。实际使用中,一个常见的现象是:模型在长文本上效果变差,往往不是注意力算不过来,而是位置编码在训练时没见过那么长的序列,一到长文本就“认生”。有些模型想尽办法做长度外推,本质都是想解决“位置编码没见过这么远的座位号”这个问题。
所以下次看到模型的长文本能力不行,别先怀疑模型容量,先想想位置编码是不是拖了后腿。我自己调试模型时就踩过这个坑:同一个模型,短句子效果挺好,输入一长就明显变笨,最后查下来不是注意力的问题,而是训练时序列长度设得太短,位置编码根本没有机会学习长距离的位置关系。
4. 自注意力与整体架构:模型到底怎么工作
4.1 自注意力:句子内部互相“看”
前面讲的注意力机制,其实是在一个最简单的场景里:一句话里的每个词,去看句子里的其他词。这种“自己看自己”的方式,专业名词叫自注意力。
自注意力做的事情,就是让句子里的每个词,都去跟句子里的所有词(包括它自己)算一遍关系,然后更新自己的表示。你可以把它理解为一场全员参加的讨论会:每个人站起来说一句“我的Query是什么”,然后所有人都拿着自己的Key去匹配,最后每个人发言结束后,都综合了全场所有人的观点再更新自己的笔记。经过这一轮操作,每个词都不再是孤立的存在,而是携带着整个句子的信息。
有个特别直观的例子,英文里的“it”在很多句子里到底指代什么。比如“The animal didn't cross the street because it was too tired”,人一看就知道“it”指动物。自注意力算出来的注意力权重,会把“it”和“animal”之间的分数拉得很高,这样“it”在更新自己的向量时,就会大量吸收“animal”的信息,模型最终理解“it”时就不再是模糊的代词,而是带着“动物”这个实体的特征。
4.2 Encoder-Decoder:一边读一边写
解决了“句子内部找关系”之后,很多任务还需要模型做“生成”。机器翻译、文本摘要、对话回复,都是输入一句话,输出另一句话。Transformer为此设计了一个结构:编码器部分负责读,解码器部分负责写。
编码器比较好理解:把整个输入句子读进来,经过若干层自注意力和前馈网络,最后生成一组表示。这组表示浓缩了输入句子的含义,可以理解成“读完全文的笔记”。解码器则会利用两部分信息:一是它自己已经生成的输出内容,二是编码器给的笔记。每生成一个词,它都会结合这两部分信息,决定下一个词最可能是什么。翻译任务里,系统就是先读完整句中文,然后一个词一个词地生成英文,生成每个词时参考已经生成的前面几个词和整体中文含义。
解码器里还有一个容易被忽略但特别关键的设计:掩码。训练时,为了让解码器学会“预测下一个词”,会故意把它真正要预测的那个词遮住,让它只能看到这个词之前的已经生成的词。如果不遮住,模型直接看到答案,那就不是在学“预测”,而是在学“抄答案”。这个细节后面讲训练时还会提到。
4.3 训练时模型到底在学什么
很多人看完上面的结构,会问一句:这些权重到底是怎么来的?答案是通过大量数据训练出来的。
训练的核心思路一句话就能概括:给模型看大量文本,让它不断做预测,预测错了就调整参数,下次少错一点。比如给模型一句话的前面部分,让它预测下一个词;模型给出的预测会变成一个概率分布,哪个词概率高就代表模型更倾向于选哪个。然后拿真实的下一个词跟预测结果对比,算出一个误差,这个误差会通过反向传播一层层传回去,告诉每一层的参数“刚才你们给的权重偏了,往这个方向调一点”。
这个过程特别像做题对答案:自己做一遍,拿正确答案对照,错的越离谱,印象就越深刻,下次再见到同类题就越容易做对。Transformer训练时,注意力矩阵里的权重、位置编码、词向量,全都在这个“对答案”的过程中被不断修正。一开始模型的注意力是完全混乱的,什么都关注,慢慢地它会发现“关注‘它’和‘动物’之间的关系,能帮我更好地预测下一个词”,于是注意力权重就朝着那个方向收敛。
这里有个很有意思的点:我们从来没有直接教模型“你应该搞懂指代关系”,但它通过优化预测准确率,自己学会了这种能力。这其实是整个深度学习领域一个反复出现的现象——复杂能力是从简单的预测目标里涌现出来的,而不是被人为设计出来的。
5. 实操层面:怎么从看懂原理到真正跑起来
5.1 预训练+微调:先上通识课,再做专项培训
聊了这么多原理,可能有朋友要问:那我自己想用Transformer,该从哪里开始?我的建议是:除非是专门做研究,否则别想着从头训练一个Transformer。
为什么?因为从头训练需要的数据量和算力极其惊人。一个完整的预训练模型,要在几十TB甚至更大的文本上跑几十天,成本根本不是个人开发者能承受的。这就好比你想写一本百科全书,不需要自己重新发明汉字和语法,直接用已有的语言能力来写就行。在实际的工程生态里,普遍的做法是“预训练+微调”。
预训练阶段,模型在大规模通用文本上学一遍,目标是掌握语言的通用规律。这个阶段像通识教育,语文、数学、常识都学一点。学完之后,模型已经知道“苹果”通常和“削”“吃”“手机公司”这些词有各种关系,但它还不知道你的具体任务是什么。微调阶段,拿你自己领域的数据再去训练它。如果你要做一个电商评论情感分析的模型,就把“这部手机续航很棒”这类评论和对应的“正向”标签丢进去,让模型把已经学到的语言能力跟你这个具体任务对接上。这就像通识教育读完,再去做岗位培训,效率高得多。
5.2 自己上手最容易踩的三个坑
实操过程中,我发现新手碰到的最大问题往往不是概念不懂,而是一跑就出错。这里分享三个我自己踩过、也看别人反复踩的坑。
第一个坑是序列长度和显存的关系。Transformer的注意力计算量,跟序列长度的平方成正比。序列长度从512涨到1024,注意力的计算量不是翻倍,是直接翻四倍。很多人刚上手时习惯把输入拉满,结果一跑就显存溢出。技巧是先小后大:先用短序列把流程跑通,再慢慢加长序列,心里对显存消耗要有数。
第二个坑是学习率设置。Transformer对学习率很敏感,尤其是训练初期,模型权重还没稳定下来,学习率大了会直接训飞,loss变成NaN。很多成熟框架都默认启用了预热策略,训练前几千步用很小的学习率慢慢走,等模型稳定了再恢复到正常学习率。新手自己搭训练脚本,经常忘了这回事,结果就是莫名其妙地训炸了。
第三个坑是一上来就想动全部参数。有些朋友微调的时候,直接把预训练模型的每一层都设成可训练,用一份不太大的数据去训,结果发现越训越差。这里经验是:数据量不够大的时候,冻结大部分底层参数,只训练最后的分类层和一部分顶层参数,往往效果更好。因为底层学到的是通用语言特征,毁掉它很容易,重新学回来很难。这有点像你刚毕业就想去改教材,不如先老老实实把手头项目做好。
6. 关于Transformer的常见困惑,一次说明白
6.1 Embedding:文字变成数字的第一步
几乎每个初学者都会问:模型又看不懂文字,怎么把“你好”喂进去?答案是先做词嵌入,也就是把每个词映射成一个数字向量。
可以理解成英文词典里的每个词都有一个固定的编号,但这还不够,因为“猫”和“狗”都应该和“动物”更接近,而它们离“汽车”更远。词嵌入要做的,就是把词映射到一个高维空间里,让意思相近的词在空间里靠得近,意思不同的词离得远。这个映射关系也是在训练中学习的。Transformer第一步就是把每个词对应的索引,经过嵌入层,变成一串连续的向量。后面的所有注意力计算,都是在这些向量上做的。
6.2 Mask:训练时不能“偷看答案”
掩码这个概念在解码器和一些自编码模型里出现频率很高。它本质上是一个遮罩,告诉模型“哪些位置可以看,哪些位置不能看”。
解码器生成第5个词的时候,如果训练时把这第5个词的正确答案摆在它眼前,它根本用不着学什么预测,直接抄答案就行。所以训练时会给“未来位置”打上一个掩码,让注意力只能看到当前位置之前的内容。这种“三角形掩码”是解码器里很标准的设计。理解这一点对调试模型很有用:如果你的生成模型在训练时效果很好、测试时一塌糊涂,很可能是掩码逻辑写错了,模型在训练时偷看了太多不该看的内容。
6.3 归一化与残差连接:训练稳定的两个功臣
Transformer层里还有两个容易忽略、但至关重要的部分:层归一化和残差连接。
残差连接就是把每一层的输入和输出加到一起,再传给下一层。好处是梯度在反向传播时能有一条“高速公路”,直接穿过很多层而不消失,这让几十层甚至上百层的Transformer能够真正被训练起来。你可以理解为做笔记时,新学的内容写在老内容旁边,而不是把旧笔记全擦掉重写。
层归一化则是把每层的数据拉回到一个合理的范围,让数值不那么悬殊,训练过程更稳定。它属于那种“看不见但一直在兜底”的组件。很多模型训练失败,排除到最后,往往就是初始化策略或者归一化位置的问题。
6.4 维度、层数、头数这些超参数怎么理解
Transformer的超参数,往往是新手看代码时最懵的部分。其实它们之间是有关联的。
维度是每个词向量的长度,可以粗略理解成每个词能携带的信息容量。维度越大容量越大,但计算量也越大。层数代表模型“反复思考”的次数,每一层都做一次注意力整合和一次信息变换,层数越多,模型能建立的关系就越复杂,但也更容易过拟合。头数代表注意力从几个角度去观察。层数和维度通常决定了模型的总参数量,头数则影响注意力表达的丰富程度。
实际选型时,不用纠结追求某一个指标的极致。这些超参数之间需要匹配。比如维度定得很大,头数却很少,模型的表达能力会受限;头数很多,但维度不够,每个头分到的容量又会太少。我见过不少新手在超参数上调得头疼,最后发现最稳妥的做法是:先抄成熟配置,跑通再调,别一开始就自己发明新组合。
7. 结尾:关于怎么学Transformer,我的一点个人经验
很多人学Transformer,一上来就啃那篇《Attention Is All You Need》原文,被公式劝退。我一开始也是这样,先死磕论文,反而被绕晕。后来换了个方法:先在脑子里建立直觉,弄明白“一起看、找重点、给座位号”这三个核心逻辑,再回去看论文和代码,突然就顺了。
如果这篇文章你只能记住一句话,我希望是前面那句:Transformer的关键,就是“整句一起看,并且知道重点在哪”。其它所有机制,多头、位置编码、掩码、残差连接,全都是在为这句话服务。
如果你准备动手继续深入,我的建议是拿一个小项目练手。选一个简单的文本分类任务,用开源的预训练模型库加载一个现成模型,跑通整个流程,然后把注意力权重可视化出来,看看模型在判断这段话时到底关注了哪些词。这一步做完了,你对Transformer的理解就会从“听说过”变成“真见过”。剩下的,就是在这个基础上慢慢往深处挖了。