从词元化到Transformer中的注意力机制
最近我正在学习Transformer的基础知识。如果你和我一样——在这个看似崭新的领域里挣扎,或是被细节搞得头疼——但仍想为下一个ASR/NLP项目收集灵感并稍作理解机制,那么这篇文章正适合你。我不擅长数学,因此会尝试用简单的方式解释。
介绍
注意力机制出现在Transformer的多个部分。目前,我将重点放在编码层内部的机制。

暂时忘掉这张图。我们来谈谈句子中的语义:“我在桌子上吃面包。” (I eat bread on the table) 作为英语学习者,你大概能感觉到这些词之间的关系。例如:
- “eat”与”bread”的相关性高于与”桌子”,因为我是正常人,尽管从技术上讲桌子也能吃
- “bread”与”table”存在某种关联,因为
- “I”与两者都有关系,但在这个句子中,动词”eat”和宾语”bread”比”bread”和”table”更重要。去掉”table”句子仍然成立
- “I”与自身存在关联
现在仅选取”eat”、“bread”和”table”来观察它们之间的关系。我们将跳过词元化过程,将每个单词视为一个词元(故下文中的”token”= “单词”,“句子”指输入序列)。
预备知识:NumPy中的线性代数
为找出词元间的关系,我将通过计算每个词元间的注意力分数来模拟这个过程。这需要一些线性代数基础。你不需要深入数学原理,但应该知道如何使用。
线性代数是处理向量和矩阵的数学分支。在Python中,我们可以使用numpy库执行线性代数运算。以下是numpy处理基础线性代数任务的简单示例:
- 点积(一维数组)
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.multiply(a, b)
print(c) # -> 1*4 + 2*5 + 3*6 = 32
print(a@b) # -> 1*4 + 2*5 + 3*6 = 32 - 矩阵乘法(二维数组)
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
C = A @ B
print("A:\n", A)
print("B:\n", B)
print("A @ B:\n", C) # -> [[19 22] [43 50]]以上就是后续章节所需的全部知识。
你不需要打开IDE进行计算,只需在终端输入
python即可获得Python交互环境。
词元化与编码
众所周知,句子中的每个单词都与其他单词存在某种关联。例如在”我吃面包而不是桌子”这句话中,“吃”显然与”面包”的关联度高于”桌子”。但如何从数学和计算角度衡量这些关系呢?
在原始Transformer论文中,定义了d_model=512作为模型的维度。该维度用于描述词嵌入——包含单词特征(如身份标识、上下文、句法角色和语义)的向量表示(我将在其他文章中详细解释)。
但词嵌入本身无法捕获词语的上下文关系,尤其是在像Transformer编码器这样的非自回归模型中(并行处理输入序列,而非像RNN那样逐步或序列接续处理)。Transformer中的词元通过相互”关注”建立联系,我们称之为”成对注意力”(或自注意力)。
为简化起见,我们将从示例矩阵入手,设 d_model=4。(实际应用中维度可能为8,但在引入多头机制前,我们将持续使用 d_model=4。)同时设定 tokens=3 以代表句子中的3个单词(此处假设 ),每个单词具有三个不同的特征。假设已完成位置编码等预处理步骤,则使用一个形状为 的 矩阵来表示这三个编码后的词元。
每行代表1个词元
自注意力机制
Q, K, V(它们是什么?)
通过词嵌入和位置编码中的余弦相似度,我们已了解单词在句子中的特性及其位置关系。但由于仍需以自回归方式处理词元(如您所知,大语言模型逐词生成句子),若不知如何预测下一个词元,便无法预先训练词元间的关系。此时注意力得分便发挥作用。
==完整的注意力得分公式为:,因此在开始前需明确:(1) 为何需要注意力得分 (2) 如何计算 Q, K, V 及其本质==
注意力得分与注意力机制
词嵌入中的余弦相似度与注意力层中的注意力得分存在差异:
- 注意力机制评估两个词元间语义关系的概率,确保关系以相对重要性权重而非任意原始得分的形式表达。
- 虽然得分随输入动态变化,但转换过程(点积+缩放+softmax)是固定的,且可学习权重控制着数值的调整方式。
- 为确保机制数学稳定、易于训练和解释,需将概率范围限制在特定阈值内。
但问题在于:如何实现这三点?答案不仅在于 Q, K, V 的推导方式,更关键的是它们如何通过可训练权重矩阵塑形。科学家选择将其抽象为查询(Query)、键(Key)和值(Value),而非简单的词元对词元矩阵,因为这种抽象使机制兼具灵活性与可扩展性——Q 聚焦于查询主体,K 定义信息索引方式,V 决定实际承载的内容。相比之下,直接的词元对比矩阵会将模型禁锢于僵化的相似性检查,降低训练过程中的控制力与适应性。
- Q=查询(Query),代表寻找的主体或目标,它提问:“我是谁?”
- K=键(Key),代表被审视的对象,它提问:“我在看什么?”
- V=值(Value),揭示 K 携带的信息,它提问:“我所见为何物?”
可将输入嵌入 视作原始食材,后续将提及的权重矩阵 则是食谱,而生成的 Q, K, V 便是准备就绪、可供注意力机制享用的菜肴。
可训练权重 W_Q, W_K 与 W_V
或许有人会说,即使没有训练过的权重,仍可计算 Q, K, V 并用于衡量词元间相似性。但实际上,若缺乏定制的 ,模型无法优化该相似性,也无法决定应强调并传递哪些信息(值)。
此外,与FFN(前馈神经网络,即小型MLP)、层归一化和嵌入层一样,这些权重矩阵是可训练参数。它们通过优化器逐步反向传播更新。在推理阶段(训练后),它们固定不变,但每个编码器层(及该层内的每个注意力头)均保留其独有的 集合。此设计使模型能学习不同层和头中的多样化注意力模式,并在网络多个层级影响信息流动。
既然已理解 的重要性及其训练方式,下一步便是观察其实际应用。实践中,每个词元嵌入 会先经这些权重投影生成 Q, K, V,继而作为计算注意力得分的基础。我们将定义Q、K和V,使它们各自与输入的形状保持一致,以确保架构中各层的一致性,并在该层处理完成后得到相同形状的输出。
为了计算形状均为(4,4)的Q、K和V(即输入矩阵的形状),显然我们需要一个的矩阵来进行推导。
X * W_Q = Q
了解了训练权重所代表的内容后,首要任务是计算Q、K、V。
从图中可以看出,将生成一个新的概率向量,显然“吃”与“面包”之间的关系最具潜力。
原始注意力分数
💡为什么是?
(1) 将分数转化为权重(输出为概率分布)
(2) 因为点积注意力在实践中更快且更节省空间,它可以利用高度优化的矩阵乘法代码实现,但会显著增长并使softmax结果失衡。[[^1]]
在计算原始注意力分数后,我们了解了一个查询与每个键的“对齐”强度。然而,这些分数无界且缺乏一致的尺度,使其不适合作为注意力机制中的最终权重。为解决此问题,我们引入softmax函数:
Softmax将任意分数向量转换为概率分布:每个权重变为正数,且每行总和为1。这种归一化确保注意力权重稳定、可解释,并在不同标记间具有可比性。因此,每个标记的表示成为其他标记的混合体,混合比例由这些动态的概率权重决定。
Softmax归一化
得到原始注意力分数后,我们逐行应用softmax进行归一化:
我们将通过代码实现此过程,而非深入探讨Softmax算法细节。attention_scores[0]代表标记1与所有标记的关系,标记2和标记3同理。
这确保每个查询的注意力权重形成概率分布(全部为正,每行总和为1)。
可直接实现如下:
attention_scores[0] = softmax(attention_scores[0]) # Q1与所有标记的注意力权重 attention_scores[1] = softmax(attention_scores[1]) # Q2与所有标记的注意力权重 attention_scores[2] = softmax(attention_scores[2]) # Q3与所有标记的注意力权重
此时,attention_scores的每一行以归一化概率形式,展示了查询对所有标记键的关注程度。
拼接并恢复至原始输入尺寸(n, d_512)
接下来,使用归一化后的注意力权重计算值向量的加权和:
其中A=(n,n)(分数经行向softmax处理);W_o=(n, d_model)为同样经过预训练的权重矩阵。
此处代表经过注意力处理后的新嵌入序列——每个标记现在是整个序列值向量的混合体,按注意力权重进行缩放。
在多头注意力中,我们并行执行相同步骤于个头。每个头沿特征维度拼接后得到:
由于设计上满足,拼接后的张量形状为。 在我们的示例中,,,H=2,每个头有,且
拼接后,通常应用输出投影:
此步骤确保最终表示与原始输入形状相同,即,保持模型层间的一致性。
太长不看
下图展示了自注意力层(设定维度d=512、头数h=8、序列长度s=20)的逐步计算流程:

我还绘制了流程示意图。为保持清晰度,初始阶段保持注意力计算未拆分状态,仅在最后引入多头拆分机制:
.drawio_light.png)
了解单层自注意力的逐步计算后,自然会产生疑问:为何要将机制拆分为多个注意力头?
多头注意力并非随意设计,而是经过实践验证的方案。若不进行拆分,注意力机制仍可运作,但模型会丧失多样性视角(如语法、语义、长距离依赖等词元关系的不同层面)与计算效率。原始Transformer采用h=8且d_model=512(每个头维度d_k=64)的配置,实现了平衡——每个头保持较小维度,整体表征丰富度高,且训练稳定性强。
- 输入X (s, d_model) 与参数矩阵W_Q, W_K, W_V (d_model, d_model) 相乘 → 得到Q,K,V (s, d_model)
- 维度重塑 → Q,K,V (s, h, d_k) → 分离出每个头的Q_i,K_i,V_i (s, d_k)
- 单头注意力计算: → 生成(s, s)注意力图
- 单头输出:(s, s)矩阵与(s, d_k)矩阵相乘 → 得到(s, d_k)
- 多头拼接:(s, h * d_k) = (s, d_model) → 可选W_O投影 → 最终输出(s, d_model)
最终输出的注意力矩阵是矩阵,每行代表单个词元的注意力矩阵。由于词元向量尚未完整,需将(n, 64)组合为(n, 512)维度。
通过完整执行该流程,我们不仅对序列施加可训练权重,还将输入投射到多个子空间:在每个子空间内进行注意力计算后重新整合视角——既为模型提供多样化的上下文信号,又保持d_model维度以供下一层使用。
参考文献
^1: Vaswani, Ashish, Noam Shazeer, Niki Parmar, et al. ‘Attention Is All You Need’. arXiv
.03762. Preprint, arXiv, 2 August 2023. https://doi.org/10.48550/arXiv.1706.03762.↩︎