Transformer 的出现
在 Transformer 出现之前,自然语言处理的发展时间线如下:
阶段一:传统序列模型 ($RNN \rightarrow LSTM \rightarrow GRU$)
这一阶段主要是为了解决模型“如何记住时序信息”的问题。
时序信息
如何理解一句话 要理解一句话的含义,应该是从头向后,先理解第一个词语,然后把前两个词语组合,理解这个组合,在把前三个词语组合,理解这个组合,以此类推;在理解句意时,每个字或词出现的时间顺序不同会带来不一样的含义,所以理解一句话不应该是把句子拆分成单个字来看。
词袋模型 在早期的自然语言处理中,有一种方法叫“词袋模型”(Bag of Words)。它就像把一句话里的字全部拆散,扔进一个布袋子里摇匀。 在词袋模型看来:
- “我爱你” =
[我, 爱, 你] - “你爱我” =
[你, 爱, 我]
RNN的出现 因为袋子里的字完全一样,机器会觉得这两句话是一个意思!但这显然是荒谬的。传统序列模型(RNN)的发明,就是为了打破这个袋子,让机器明白“谁在前面、谁在后面”决定了句子的生死。
RNN (循环神经网络): 最早的序列模型。
如何工作 RNN 会逐个读取句子中的词语,并在每一步结合当前词和前面的上下文信息,不断更新对句子的理解。通过这种机制,RNN 能够持续建模上下文,从而更准确地把握句子的整体语义。因此 RNN 曾是序列建模领域的主流模型,被广泛应用于各类 NLP 任务。
基础结构
- 实际结构: 在
PyTorch的底层代码中,RNN只有一个运算模块(也就是包含 $W, U$ 权重和 $\tanh$ 的那个细胞),它以时间步(time step)为单位,依次处理输入序列中的每个token。 - 怎么工作: 它本质上是一个
for循环。不管句子有 10 个字还是 1000 个字,它们都必须排队,依次进入这唯一的一个模块被处理。 - 核心特征: 权重共享 (
Weight Sharing)。从句首到句尾,机器始终在复用同一套参数矩阵来提取特征。
沿时间展开图
在每个时间步,RNN 接收当前 token 的向量 $x_t$ 和上一个时间步的隐藏状态 $h_{t-1}$(即隐藏层的输出),计算并生成新的隐藏状态 $h_t$,并将其传递到下一时间步。

说明
上图详细展示了基础 RNN 的结构,但 RNN 还存在更复杂的结构形式。
多层结构: 为了让模型捕捉更复杂的语言特征,可以将多个
RNN层按层次堆叠起来,使不同层学习不同层次的语义信息。- 底层网络: 更容易捕捉局部模式(如词组、短语),底层的神经元直接接触最原始的输入数据(比如一个个词向量),因此它们的注意力主要集中在局部的、基础的、物理层面的特征上。
- 高层网络: 能学习更抽象的语义信息(如句子主题或语境),高层
RNN接收的是底层已经初步“消化”过的特征序列。它们不再纠结于具体的字词长什么样,而是站在更高的视角,关注全局的、抽象的、深层含义。
注:多层
RNN结构中,每一层的输出序列会作为下一层的输入序列,最底层RNN接收原始输入序列,顶层RNN的输出作为最终结果用于后续任务。双向结构: 基础的
RNN在每个时间步只输出一个隐藏状态,该状态仅包含来自上文的信息,而无法利用当前词之后的下文。对于一些任务而言,这是一个明显的限制。比如在序列标注任务中,模型需要为每个token预测一个标签,如果只能参考前文信息,往往难以做出准确判断。
⚠️ 痛点:
- 梯度消失: 底层机制(梯度消失/爆炸): 在训练
RNN时,我们使用的是“沿时间反向传播算法”(BPTT)。因为状态是不断往后传递的,数学上这涉及到连乘效应。如果权重矩阵里的值小于 $1$,连乘几十次后,梯度就会趋近于 $0$(梯度消失,模型不再更新);如果大于 $1$,又会趋近于无穷大(梯度爆炸,模型直接崩溃)。- 信息传递路径太长: 在
RNN中,句首单词和句尾单词之间的距离是 $\mathcal{O}(N)$($N$ 是序列长度)。这意味着两个词隔得越远,它们建立联系需要跨越的计算步骤就越多,信号衰减得就越厉害。
LSTM (长短期记忆网络)
按时间展开结构

改进
为了缓解 RNN 梯度消失或者梯度爆炸的问题,Hochreiter 和 Schmidhuber 于 1997 年提出了长短期记忆网络(Long Short-Term Memory, LSTM),LSTM 在 RNN 的基础上引入了四个新的结构。
记忆单元 (Cell State, $C_t$)
记忆单元负责在序列中长期保存关键信息。它相当于一条“信息通道”,在多个时间步之间直接传递信息。(记忆单元是缓解梯度消失和梯度爆炸问题的核心)
这是 LSTM 区别于普通 RNN 最伟大的发明。传统的 RNN 只有 $h_t$ 这一条路,记忆要在里面被反复运算(矩阵乘法),导致梯度消失。而 $C_t$ 是长期的、全局的记忆。 在这条直通车上,数据只发生极其简单的线性运算(乘法和加法)。

遗忘门 (Forget Gate, $f_t$)
决定要从过去的记忆上擦除什么信息。
例如历史输入为:“小帅是一名程序员,他每天都加班;”,然后当前时间步的输入为“小美”,意味着当前的主语变为了“小美”,后续应该生成和“小美”相关的内容,所以此时记忆单元就应该忘记之前的主语信息“小帅”。遗忘门会根据上一个时间步的隐藏状态 $h_{t-1}$ 和当前时间步的输入 $x_t$,生成一个 $0$ 到 $1$ 之间的控制系数,然后与上一个时间步的记忆单元状态相乘,从而动态调整哪些信息应该被遗忘。
- 计算过程: 把当前的输入 $x_t$ 和上一步的短期记忆 $h_{t-1}$ 放在一起看,然后通过一个
Sigmoid函数,输出一个 $0$ 到 $1$ 之间的数字(比例)。 接近 $0$:代表“这部分老记忆没用了,彻底忘掉!”(比如主语从“他”变成了“她”,老的性别特征就被清空)。 接近 $1$:代表“这个信息很重要,继续保留” - 公式: $$f_t = \sigma(W_f \cdot x_t + U_f \cdot h_{t-1} + b_f)$$

输入门 (Input Gate, $i_t$)
输入门控制要从当前时间步的输入向记忆单元存入多少新的信息。例如上述案例中,当前时间步的输入为“小美”,所以此时记忆单元就应该存入新的主语信息“小美”。当前时间步的信息由当前输入 $x_t$ 和上一个隐藏状态 $h_{t-1}$ 计算而成,同时输入门也由当前输入 $x_t$ 和上一个隐藏状态 $h_{t-1}$ 计算而成,然后新的信息和输入门相乘得到需要存入记忆单元的信息。
- 计算过程: 生成备选新知识 ($\tilde{C}_t$): 使用 $\tanh$ 函数,把当前的新词和语境,提炼成一份“候选记忆草稿”(数值在 $-1$ 到 $1$ 之间)。
决定写入比例 ($i_t$): 使用
Sigmoid函数,算出一个 $0$ 到 $1$ 之间的“写入阀门值”。它决定了刚才那份草稿里,有多少内容是真的有价值、值得被正式写进传送带的。 - 公式: 算阀门: $$i_t = \sigma(W_i \cdot x_t + U_i \cdot h_{t-1} + b_i)$$ 写草稿: $$\tilde{C}_t = \tanh(W_c \cdot x_t + U_c \cdot h_{t-1} + b_c)$$ 老记忆被遗忘门乘法砍掉一部分,新记忆草稿被输入门过滤后加进来: $$C_t = (f_t \ast C_{t-1}) + (i_t \ast \tilde{C}_t)$$

输出门 (Output Gate, $o_t$)
输出门控制从记忆单元中读取多少信息作为当前时间步的隐藏状态进行输出。例如上述输入法智能提示案例中,记忆单元中存入新主语信息“小美”之后,当前时间步就应该从记忆单元中提取该主语信息,生成与“小美”相关的内容。输出门同样由当前时间步的输入 $x_t$ 和上一个时间步 $h_{t-1}$ 的隐藏状态计算而成,如下图所示。
- 运作过程:
算过滤阀门 ($o_t$): 同样通过
Sigmoid函数,结合当前输入 $x_t$ 和上步状态 $h_{t-1}$,算出一个 $0$ 到 $1$ 的比例。这就相当于一张遮罩/滤网,决定了长期记忆里的哪些部分在当前这个时间步是需要暴露出来的。 生成最终输出 ($h_t$): 把最新的细胞状态 $C_t$ 用 $\tanh$ 压扁处理一下(确保数值稳定在 $-1$ 到 $1$ 之间),然后和刚才的滤网 $o_t$ 相乘。 算出来的这个 $h_t$,就是当前时间步的短期记忆。它有两个去处:一是传递给下一个时间步,二是往上送给外层的Linear层去预测当前的单词! - 公式: 算滤网: $$o_t = \sigma(W_o \cdot x_t + U_o \cdot h_{t-1} + b_o)$$ 对外发言: $$h_t = o_t \ast \tanh(C_t)$$

⛔ 缺点
- 参数大爆炸:
LSTM新增了遗忘门、输入门、输出门、记忆单元四个结构。为了实现这四个模块,LSTM内部设置了 4 套完全独立的权重矩阵($W$ 和 $U$)。 如果隐藏层维度比较大,LSTM的参数量会是普通RNN的 4 倍。这直接导致它在运行时极其消耗 GPU 显存。
GRU (门控循环单元):
改进
LSTM 的“精简版”。把 LSTM 的三个门简化成了两个门(重置门、更新门),取消了 LSTM 中独立的记忆单元,只保留隐藏状态,在保持效果差不多的前提下,大大减少了计算量,训练速度更快。
按时序展开结构

重置门 ($r_t$) 在面对当前的新单词时,决定过去的记忆有多少参考价值,帮模型生成一份新的记忆。

- 案例: 假设模型正在阅读一篇小说。 场景 A(上下文连贯): 上文是“小明走进厨房”,当前词 $x_t$ 是“拿起了”。此时,重置门评估后觉得:“前面的‘厨房’和现在‘拿起了’高度相关,可能是拿起了菜刀或苹果。”于是,重置门全开,让过去的记忆全部参与到新记忆的起草中。 场景 B(话题突转): 上文是长篇大论的“量子力学原理解析”,当前词 $x_t$ 是“突然,”。此时,重置门发现话题画风突变,前面的物理学知识对理解接下来的故事毫无用处。于是,重置门果断关闭,把前面的记忆屏蔽掉(重置),只根据“突然,”这个词去起草接下来的语境。
- 公式: 在生成候选草稿($\tilde{h}t$)时,重置门 $r_t$ 悄悄乘在了过去的隐状态 $h{t-1}$ 上: $$\tilde{h}_t = \tanh(W \cdot x_t + U \cdot (r_t \ast h_{t-1}))$$
更新门 ($z_t$) 更新门会在计算当前时间步最终的隐藏状态 $h_t$ 时,分别作用在上一时刻的隐藏状态 $h_{t-1}$ 和当前新计算出的候选隐藏状态 $\tilde{h}_t$,用于控制保留多少旧信息,以及引入多少新信息。

⚠️ 痛点 尽管
GRU极其精简高效,但他还是存在无法解决的问题。$$h_t = \tanh(W \cdot x_t + U \cdot h_{t-1} + b)$$$$y_t = V \cdot h_t + c$$
RNN计算公式:产生一个输出 $y_t$,必须依赖一个当前的隐藏状态 $h_t$;而产生 $h_t$,又必须生吃进去一个当前的输入单词 $x_t$。 这就好比机器内部有两个完全咬合的齿轮,输入转一格,输出才跟着转一格。没有输入的推力,输出齿轮根本转不动;输入转了,输出也绝对不能停。这就是所谓的“时序强绑定”。 不管内部怎么优化,
GRU依然是一个标准的循环网络。标准RNN在处理任务时,要求 输入序列和输出序列必须是严格对齐的(长度相等,词序对应)。这在真实的机器翻译中会遇到问题,现实世界中的翻译很少能有输入输出长度相等,词序对应的。
阶段二:框架的诞生 (Seq2Seq 架构, 2014年)
在此之前,RNN 系列模型很难处理“输入和输出长度不一致”的问题(比如机器翻译,中文的“我爱你”是3个字,英文的“I love you”也是3个单词,但“桌子上的苹果”和“The apple on the table”长度就不一样了)。
为了解决这个问题,Google 提出了 Seq2Seq (Sequence to Sequence) 架构,也叫 Encoder-Decoder (编码器-解码器) 架构。
关键点 Seq2Seq 不是凭空造出来的新网络,它的内部使用的就是 RNN、LSTM 或 GRU。
工作原理 * Encoder 负责把一整句话吃进去,压缩成一个固定长度的向量(也就是上下文向量 Context Vector)。
Decoder拿到这个向量,逐步生成目标序列。
模型结构

编码器 - Encoder
Encoder 底层通常就是一个标准的 RNN、LSTM 或 GRU。
- 工作流程: 只听不说,浓缩精华。 它的任务是阅读一段长度为 $N$ 的输入序列,理解里面的所有词法、句法和逻辑关系,然后把它压缩成一个包含整句话含义的隐藏状态,当接受完最后一个词,并且看到代表句子结束的特殊符号
<EOS>(End of Sentence)时,它会输出最后一个隐藏状态——$h_{final}$,就是 上下文向量 (Context Vector, $C$)。
按时间步展开结构

解码器 - Decoder
它同样是一个标准的 RNN、LSTM 或 GRU。但在参数上,它和 Encoder 是完全独立的(两人不共享权重,因为一个懂中文,一个懂英文)。
- 工作流程: 只说不听,展开思想。 将编码器生成的上下文向量,在没有任何外界输入提示的情况下,“解压缩”成一种新的序列。
在生成开始时,循环神经网络以上下文向量作为初始隐藏状态,并接收一个特殊的起始标记
<sos>(start of sentence)作为第一个时间步的输入,用于预测第一个token。 随后,在每一个时间步,模型都会根据前一时刻的隐藏状态和上一步生成的token,预测当前的输出。这种“将前一步的输出作为下一步输入”的方式被称为自回归生成(Autoregressive Generation),它确保了生成结果的连贯性。 生成过程会持续进行,直到模型生成了一个特殊的结束标记<eos>(end of sentence),表示句子生成完成。
按时间步展开结构

模型的训练和推理
Seq2Seq 在训练(Training)和推理(Inference,也就是实际使用)这两个阶段,差别非常大。编码器(Encoder)在两个阶段的表现是一模一样的。真正的区别,全部集中在解码器(Decoder)接收输入的方式上。
训练
- 编码器:
编码器接收源语言序列“我喜欢你。”,通过嵌入层和循环神经网络(
RNN/LSTM/GRU)的逐步处理,将整句编码为上下文向量。 - 解码器:
训练时目标是让模型快速收敛。如果让解码器像真实情况下那样“自回归”(把上一步的输出当成下一步的输入),假如第一步预测错了(把
I预测成了He),那么第二步就会拿着错误的He继续往下猜,导致后面全盘皆输。模型会在错误的道路上越走越远,根本学不到东西。 为了解决这个问题,引入了一个极其重要的训练技巧:Teacher Forcing(教师强制),如下图所示。

运作机制
就像一个严厉的老师握着学生的手写字。
第一步:输入 <SOS>,模型瞎猜了一个 He。
第二步:老师强行介入! 老师不管上一步猜出了什么,老师直接把**标准答案(Ground Truth)**里的第一个词 I,硬塞给模型作为第二步的输入。
第三步:老师再次介入,把标准答案里的 like 塞给模型作为第三步的输入。
优势 即使模型在中间某一步预测错了,错误也不会像滚雪球一样累积。模型始终在“正确的上下文”引导下学习,训练速度和稳定性大幅提升。
损失计算
解码器每一步输出一个 token 的概率分布,通过交叉熵损失函数衡量模型对真实词的预测质量。训练过程中,每一个时间步都会产生一个损失值。该样本的总损失,就是所有时间步的损失值逐步累加的结果。

推理
- 运作机制:
这就是上一节详细讨论过的自回归(
Autoregressive)。 第一步:输入<SOS>,模型结合上下文向量,吐出I。 第二步:模型只能依靠自己,老老实实把刚刚吐出的I拿过来,作为这一步的输入,推导出like。 直到模型自己决定吐出<EOS>为止。

⚠️ 痛点
在上述
Seq2Seq架构中,编码器会将整个源句压缩为一个固定长度的上下文向量,并将其作为解码器生成目标序列的唯一参考。这种“压缩再解压”的方式虽然结构简洁,但在实际任务中暴露出两个核心问题:
- **信息压缩困难,语义表达受限:**对于编码器而言,用一个定长向量去表达任意复杂的句子,是一项非常困难的任务。尤其在面对长句时,信息很容易在压缩过程中丢失,导致语义表达不完整。这种“信息瓶颈”限制了模型在处理长文本或复杂语义结构时的表现。
- **缺乏动态感知,解码难以精准生成:**解码器始终只能基于同一个上下文向量进行生成。但在实际生成过程中,不同位置的目标词,往往依赖源句中不同的关键信息:生成主语时,可能更依赖源句的开头;生成谓语或宾语时,可能需要参考句中或句末内容。然而在固定表示下,解码器无法“有选择地关注”输入序列的不同部分,只能一视同仁地处理所有信息,从而降低了生成的准确性与灵活性。
阶段三:Transformer 的直接前身 (Seq2Seq + Attention, 2014-2015年)
早期的 Seq2Seq 有一个致命缺陷:信息瓶颈。Encoder 必须把几百个字的整篇文章压缩成一个固定大小的向量,这导致长句子的信息被严重丢失。
于是,Attention(注意力机制) 被发明出来了(最早由 Bahdanau 等人提出):
突破: Decoder 在生成每一个词的时候,不再依赖那个单一的压缩包,而是回头去看 Encoder 输入的所有词,并且自己决定当前应该把“注意力”集中在哪些词上。
这个时候的顶配序列模型是:以 LSTM/GRU 为底座的 Seq2Seq + Attention 机制。
工作原理 注意力机制的核心思想,是解码器在生成目标序列的每一步时,动态地从编码器的各个时间步的隐藏状态中提取当前所需的信息,而不再只依赖一个固定的上下文向量。

这一机制包含以下四个步骤:
- 相关性计算:
在目标序列生成的每一步,解码器都会计算当前时间步的隐藏状态与编码器各个时间步输出之间的相关性。这些相关性衡量了源句中每个位置对当前生成内容的重要程度,从而决定模型应将多少注意力分配给不同的源位置。相关性的计算依赖于特定的函数,通常被称为注意力评分函数(
attention scoring function)。

- 注意力权重计算:
得到所有源位置的注意力评分后,使用
Softmax函数将其归一化为概率分布,作为注意力权重。得分越高的位置,其对应的权重越大,代表模型在当前生成中更关注该位置的信息。

- 上下文向量计算: 将所有编码器输出按照注意力权重进行加权求和,得到一个上下文向量。这个向量就表示当前时间步,模型从源句中提取出的关键信息。

- 解码信息融合:
在得到上下文向量后,解码器将其与当前时间步的隐藏状态进行拼接,以融合两者信息,最终通过线性变换和
Softmax,生成当前时间步目标词的概率分布。

⚠️ 痛点
尽管注意力机制极大地增强了
Seq2Seq模型的建模能力,但由于其核心依然依赖于RNN结构,仍面临两个根本性问题:
- 计算过程无法并行:
RNN的时间步之间存在强依赖,必须顺序执行,限制了训练效率和硬件资源的利用率。- 长期依赖问题仍未根除: 模型需要跨多个时间步传递信息,对于超长序列,训练过程中容易出现梯度消失,难以有效建模长距离依赖关系。
阶段四:革命与颠覆 (Transformer, 2017年)
到了 2017 年,Google 发表了著名的论文《Attention Is All You Need》:
保留了 Seq2Seq 的 Encoder-Decoder 架构。
但是,他们彻底抛弃了里面的 RNN/LSTM/GRU 细胞,全部用“自注意力机制 (Self-Attention)”和前馈神经网络来代替。
通过这种方式,Transformer 不仅显著提升了训练效率,也增强了模型对长距离依赖的建模能力。Transformer 的提出对自然语言处理产生了深远影响。
整体结构
Transformer 在宏观上,完美继承了 Seq2Seq 模型的经典设计理念:“编码器-解码器(Encoder-Decoder)”架构。

在这个架构中,两者的分工依然明确:
- 编码器(
Encoder): 负责阅读、理解和表征输入的原文序列(提取核心思想)。 - 解码器(
Decoder): 负责拿着编码器提炼的核心思想,一步步生成目标序列(如翻译后的外语)。
Transformer 具备以下两个特点:
层层堆叠
在传统的 RNN 时代,往往只用一层(或者很少的几层)网络,靠着在时间轴上不断循环来提取信息。而 Transformer 彻底抛弃了时间轴的循环,它选择在空间深度上疯狂堆砌。
它的编码器和解码器,都不再是单一的模块,而是分别由多个结构完全相同的“层(Layer)”垂直堆叠而成(在标准的 Transformer 原始论文中,包含了 6 个编码器层和 6 个解码器层)。
- 为什么要堆叠这么多层? 你可以把它想象成一个流水线车间。当“我爱你”这三个字同时进入第 1 层时,模型可能只看懂了词性(“我”是代词,“爱”是动词)。然后结果传给第 2 层,模型看懂了主谓宾结构;传到第 4 层,看懂了时态;传到第 6 层时,模型就已经提取出了极其深度的语义特征和情感逻辑。层数越深,模型对复杂语言现象的建模能力就越强。
“全量回顾”的自回归解码
这是 Transformer 在工程实现上与 RNN 最大的不同点!
像 RNN 一样,Transformer 的解码器依然是自回归(Autoregressive)的——也就是说,它必须一个词一个词地往外吐,直到吐出结束标记 <eos> 为止。
但是它每一次生成新词时的输入条件:
- RNN 是怎么做的:
RNN是有“记忆(隐藏状态 $h_t$)”的。当它要生成第 4 个词时,它只需要输入第 3 个词,因为前 3 个词的信息已经浓缩在它的记忆里了。 - Transformer 是怎么做的:
Transformer为了追求极致的并行计算速度,彻底切除了类似RNN的“记忆体”。 既然没有记忆,它怎么知道自己刚才说了什么? 答案是全量输入:当它准备预测第 4 个词时,它必须把前面已经生成的第 1、2、3 个词,作为一整个序列同时重新输入进去! 模型会对着这 3 个词进行一波疯狂的注意力计算,最后输出一个长度同样为 3 的预测序列。但我们在代码里,只取这个输出序列的“最后一个位置”的结果,作为我们想要的第 4 个词。
编码器
编码器由多个结构相同的编码器层(Encoder Layer)堆叠而成。

每个 Encoder Layer 的主要任务都是对其输入序列进行上下文建模,使每个位置的表示都能融合来自整个序列的全局信息。每个 Encoder Layer 都包含两个子层(sublayer),分别是自注意力子层(Self-Attention Sublayer)和前馈神经网络子层(Feed-Forward Sublayer)。

Self-Attention 子层
用于捕捉序列中各位置之间的依赖关系,自注意力机制(Self-Attention)是 Transformer 编码器的核心结构之一,它的作用是在序列内部建立各位置之间的依赖关系,使模型能够为每个位置生成融合全局信息的表示。

之所以被称为“自”注意力,是因为模型在计算每个位置的表示时,所参考的信息全部来自同一个输入序列本身,而不是来自另一个序列。
自注意力计算过程
- 生成
Query、Key、Value向量 自注意力机制的第一步,是将输入序列中的每个位置表示映射为三个不同的向量,分别是查询(Query)、键(Key)和值(Value)。

这些向量的作用如下:
- Query: 表示当前词的用于发起注意力匹配的向量;
- Key: 表示序列中每个位置的内容标识,用于与
Query进行匹配; - Value: 表示该位置携带的信息,用于加权汇总得到新的表示。
自注意力的核心思想是:每个位置用自身的 Query 向量,与整个序列中所有位置的 Key 向量进行相关性计算,从而得到注意力权重,并据此对对应的 Value 向量加权汇总,形成新的表示。
三个向量的计算公式如下:
其中 $W^Q, W^K, W^V$ 均为可学习的参数矩阵。
- 计算位置相关性
完成
Query、Key、Value向量的生成后,模型会使用每个位置的Query向量与所有位置的Key向量进行相关性评分。

评分函数采用向量点积形式。由于在高维空间中,点积的数值可能过大,会影响 Softmax 的稳定性,因此在实际计算中对结果进行了缩放。最终的评分函数为:
其中 $d_k$ 是 Key 向量的维度,用于缩放点积的幅度。这个分数越大,表示第 $i$ 个位置越应该关注第 $j$ 个位置的信息。
对于整个序列,可以通过矩阵运算一次性计算所有位置之间的评分,计算公式如下图所示:

- 计算注意力权重
在得到每个位置与所有位置之间的相关性评分后,模型会使用
Softmax函数进行归一化,确保每个位置对所有位置的关注程度之和为 1,从而形成一个有效的加权分布。

对于整个序列,模型要做的是对之前得到的注意力评分矩阵的每一行进行 Softmax 归一化。

- 加权汇总生成输出
最后,模型会根据注意力权重对所有位置的
Value向量进行加权求和,得到每个位置融合全局信息后的新表示。

对于整个序列,同样可以通过矩阵运算一次性计算所有位置的输出,如下图所示:

综上所述,可得整个自注意力机制的完整的计算公式如下

对应原始论文中的:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$多头自注意力计算过程
自注意力机制通过 Query、Key 和 Value 向量计算每个位置与其他位置之间的依赖关系,使模型能够有效捕捉序列中的全局信息。
然而,自然语言本身具有高度的语义复杂性,一个句子往往同时包含多种类型的语义关系。例如,句子“那只动物没有过马路,因为它太累了”中就涉及多个层面的语言关系:
- “它”指代“那只动物”,属于跨句的代指关系;
- “因为”连接前后两个分句,体现语义上的因果逻辑;
- “过马路”构成动词短语,属于固定的动宾结构。
要准确理解这类句子,模型需要同时识别并建模多种层次和类型的依赖关系。但这些信息很难通过单一视角或一套注意力机制完整捕捉。
为此,Transformer 引入了多头注意力机制(Multi-Head Attention)。其核心思想是通过多组独立的 Query、Key、Value 投影,让不同注意力头分别专注于不同的语义关系,最后将各头的输出拼接融合。
多头注意力的计算过程如下:
- 分别计算各头注意力:
每个
Self-Attention Head独立计算一套注意力输出。

- 合并多头注意力: 多个输出矩阵按维度拼接,再乘以 $W^O$ 得到最终多头注意力的输出。

前馈神经网络层
前馈神经网络(Feed-Forward Network,简称 FFN)是 Transformer 编码器中每个子层的重要组成部分,紧接在多头注意力子层之后。它通过对每个位置的表示进行逐位置、非线性的特征变换,进一步提升模型对复杂语义的建模能力。

一个标准的 FFN 子层包含两个线性变换和一个非线性激活函数,中间通常使用 ReLU 激活。其计算公式如下:
计算图如下:

残差连接与层归一化
在 Transformer 的每个编码器层中,每个子层,包括自注意力子层和前馈神经网络子层,其输出都要经过残差连接(Residual Connection)和层归一化(Layer Normalization)处理。这两者是深层神经网络中常用的结构,用于缓解模型训练中的梯度消失、收敛困难等问题,对于 Transformer 能够堆叠多层至关重要。

残差连接
残差连接(Residual Connection,也称“跳跃连接”或“捷径连接”)最初在计算机视觉领域被提出,用于缓解深层神经网络中的梯度消失问题。其核心思想是:
将子层的输入直接与其输出相加,形成一条跨越子层的“捷径”,其数学形式为:
具体计算过程如图所示:

残差连接确保反向传播时,梯度至少有一条稳定通路可回传,是深层网络可稳定训练的关键结构。
层归一化
每个子层在残差连接之后都会进行层归一化(Layer Normalization,简称 LayerNorm)。它的主要作用是规范输入序列中每个 token 的特征分布(某个 token 的表示可能在不同维度上有较大数值差异),提升模型训练的稳定性。
先把数据拉回正态分布,然后给模型两个可学习参数,让模型根据自己的情况去决定最终的分布情况。

该操作会将每个 token 的向量调整为均值为 $0$、方差为 $1$ 的规范分布,具体效果如下图所示:

具体的计算公式如下:
假如某个 token 的特征向量为 $x = [x^1, x^2, x^3, x^4, …, x^d]$,
均值计算 计算该向量在所有特征维度上的平均值
$$\mu = \frac{1}{d} \sum_{i=1}^{d} x^i$$其中 $d$ 为特征维度(向量长度)。
标准差计算 计算向量各维度的标准差
$$\sigma = \sqrt{\frac{1}{d} \sum_{i=1}^{d} (x^i - \mu)^2}$$标准化变换 将每个特征值转换为均值为 $0$、方差为 $1$ 的标准正态分布;
$$\hat{x}^i = \frac{x^i - \mu}{\sigma + \varepsilon}$$$\varepsilon$ 为一个小的常数,防止出现除以 0 的情况。
缩放与平移 为了让模型可以学习在归一化后的基础上进行适当的调整,保证归一化不会限制模型的表示能力。
$$\text{LayerNorm}(x^i) = \gamma^i \cdot \hat{x}^i + \beta^i$$
位置编码
Transformer 模型完全摒弃了 RNN 结构,意味着它不再按顺序处理序列,而是可以并行处理所有位置的信息。尽管这带来了显著的计算效率提升,却也引发了一个问题:Transformer 无法像 RNN 那样天然地捕捉词语之间的顺序关系。换句话说,在没有额外机制的情况下,Transformer 无法区分“猫吃鱼”和“鱼吃猫”这类语序不同但词汇相同的句子。
为了解决这一问题,Transformer 引入了一个关键机制——位置编码(Positional Encoding)。该机制为每个词引入一个表示其位置信息的向量,并将其与对应的词向量相加,作为模型输入的一部分。这样一来,模型在处理每个词时,既能获取词义信息,也能感知其在句子中的位置,从而具备对基本语序的理解能力。

位置编码最直接的方式是使用绝对位置编号来表示每个词的位置,例如第一个词用 $0$,第二个词用 $1$,依此类推:

这样做虽然简单,但有一个明显的问题,越靠后的 token 位置编码就越大,若直接与词向量相加,会造成数值倾斜,让模型更关注位置,而忽视词义。

为缓解这一问题,可以考虑将位置编号归一化为 $[0, 1]$ 区间,例如用 $\frac{pos}{T-1}$ 表示位置,其中 $T$ 是句子长度。

这种方式虽然使数值范围更平稳,但也引入了一个严重的问题:
相同位置的词在不同长度句子中的位置编码不再一致。
例如,位置 5 在长度为 10 的句子中被编码为 $\frac{5}{9}$,在长度为 1000 的句子中则为 $\frac{5}{999}$。这种依赖输入长度的表示方式会导致模型难以形成稳定的位置感知能力。理想的做法是:每个位置都拥有一个唯一且一致的编码,与句子长度无关。
为了解决上述问题,Transformer 使用了一种基于正弦(sin)和余弦(cos)函数的位置编码方式,具体定义如下:
其中:
- $pos$ 是当前词在序列中的位置;
- $i$ 用于表示位置编码向量的维度索引,$2i$ 表示偶数维,$2i + 1$ 表示奇数维;
- $d_{model}$ 是词向量的维度大小。
序列中的每个位置 $pos$ 对应一个长度为 $d_{model}$ 的位置编码向量。该向量的偶数维度通过正弦函数生成,奇数维度通过余弦函数生成,如下图所示

Transformer 提出的这种编码方式不依赖任何可学习参数,数值稳定,并具备以下优势:
- 所有值都在 $[-1,1]$ 范围内,数值稳定
- 编码方式固定、可预计算,无需训练;
- 相同位置的编码在不同句子中保持一致;
- 编码之间具有数学规律,便于模型在注意力机制中感知词语之间的相对位置关系。
编码器完整结构

解码器
Transformer 解码器的主要功能是:根据编码器的输出,逐步生成目标序列中的每一个词。其生成方式采用自回归机制(autoregressive):每一步的输入由此前已生成的所有词组成,模型将输出一个与当前输入长度相同的序列表示。我们只取最后一个位置的输出,作为当前步的预测结果。这一过程会不断重复,直到生成特殊的结束标记 <eos>,表示序列生成完成。

编码器也由多个结构相同的解码器层堆叠组成。

每个 Decoder Layer 都包含三个子层,分别是 Masked 自注意力子层、编码器-解码器注意力子层(Encoder-Decoder Attention)和前馈神经网络子层(Feed-Forward Network)。

Masked 自注意力子层(Masked Self Attention)
该子层的主要作用是:建模目标序列中当前位置与前文之间的依赖关系,为当前词的生成提供上下文语义支持。
由于 Transformer 不具备像 RNN 那样的隐藏状态传递机制,无法在序列生成过程中保留上下文信息,因此在生成每一个词时,必须将此前已生成的所有词作为输入,通过自注意力机制重新建模上下文关系,以预测下一个词。
此外,从结构上看,Transformer 编解码器都具有一个典型特性:输入多少个词,就输出多少个表示。需要注意的是,在推理阶段,我们只使用解码器最后一个位置的输出作为当前步的预测结果,如下图所示:

如果训练阶段也完全按照推理流程进行,就必须将每个目标序列拆分成多个训练样本,每个样本输入一段前文,只预测一个词。如下图所示:

这种方式虽然逻辑合理,但训练效率极低,完全无法利用 Transformer 并行计算的优势。
为提升效率,Transformer 采用了并行训练策略:一次性输入完整目标序列,同时预测每个位置的词。如下图所示:

但如果不加限制,这种方式会让模型在预测每个位置时“看到”后面的词,即提前访问未来信息,破坏生成任务的因果结构,如下图所示:

为解决这个问题,解码器在自注意力机制中引入了遮盖机制(Mask)。该机制会在计算注意力时,阻止模型访问当前位置之后的词,只允许它依赖自身及前文的信息。这样,即使在并行训练时,模型也只能像逐词生成一样“看见”它应该看到的内容,从而保持训练与推理阶段的一致性。如下图所示:

Mask 机制的实现非常简单:只需将注意力得分矩阵中当前位置对其后续位置的评分设置为 $-\infty$,如下图所示:

这样,在经过 Softmax 运算后,这些位置的权重会趋近于 $0$。最终在加权求和时,来自未来位置的信息几乎不会参与计算,从而实现了“当前词只能看到它前面的词”的约束。如下图所示:

编码器-解码器注意力子层
该子层的主要作用是:建模当前解码位置与源语言序列中各位置之间的依赖关系,帮助模型在生成目标词时有效地参考输入内容,相当于 Seq2Seq 模型中的注意力机制。
编码器-解码器注意力的核心机制与前面讲过的自注意力机制完全一致,区别仅在于:
- $Query$ 来自解码器当前的输入表示,即当前生成状态;
- $Key$ 和 $Value$ 来自编码器的输出表示,即整个源序列的上下文。
也就是说,当前生成位置使用自己的 Query,去“询问”编码器输出中的哪些位置最相关。注意力机制会根据 Query 与所有 Key 的相似度,为每个源位置分配一个权重,然后用这些权重对 Value 进行加权求和,得到当前生成词所需的上下文信息。
解码器完整结构
