2017年,Google在论文《Attention Is All You Need》中提出了Transformer模型,使用Self-Attention结构取代了在NLP任务中常用的RNN网络结构。相比RNN,其最大优点是可以并行计算,大幅提升了训练效率。Transformer的提出开启了预训练语言模型和大模型时代的新篇章。
将Transformer视为一个黑盒,在机器翻译任务中,它接收一种语言的句子作为输入,输出另一种语言的翻译结果:
输入: "Hello world" (英文) → Transformer黑盒 → 输出: "你好世界" (中文)Transformer本质上是一个Encoder-Decoder架构,中间部分可以分为两个组件:
原始论文中使用了6层编码器和6层解码器,实际应用中可以根据任务需求调整层数。
每个编码器由两个子层组成:
每个编码器结构相同,但使用不同的权重参数。
编码器的输入先流入Self-Attention层,它可以让编码器在对特定词进行编码时使用输入句子中其他词的信息。例如翻译一个词时,不仅关注当前词,还会关注其他词的信息。然后,Self-Attention层的输出会流入前馈网络。
解码器也有编码器中的两层,但之间还有一个额外的注意力层:
交叉注意力类似于seq2seq模型中的注意力机制,让解码器能够聚焦于源序列的关键位置。
和通常的NLP任务一样,首先使用词嵌入算法将每个词转换为词向量。在Transformer论文中,词嵌入向量的维度是 512。
"我" → [0.2, -0.5, 0.1, ..., 0.3] (512维向量)
"喜欢" → [-0.1, 0.4, 0.7, ..., -0.2] (512维向量)
"AI" → [0.5, 0.3, -0.2, ..., 0.1] (512维向量)嵌入仅发生在最底层的编码器中。所有编码器都会接收到一个大小为512的向量列表:
这个列表大小(序列长度)是可以设置的超参数,通常设置为训练数据集中最长句子的长度。
对输入序列完成嵌入操作后,每个词都会流经编码器的两个子层:
输入向量 → Self-Attention层 → Feed Forward层 → 输出到下一层编码器通过一个例子来理解Self-Attention:
句子:The animal didn't cross the street because it was too tired
问题:这个句子中的 it 指的是什么?是指 animal 还是 street?
对人来说这是一个简单的问题,但对算法来说却不那么简单。
当模型在处理 it 时,Self-Attention机制使其能够将 it 和 animal 关联起来:
处理 "it" 时:
- 注意力权重: animal ←─── 0.6 ───→ it
- 注意力权重: street ←─── 0.1 ───→ it
- 注意力权重: tired ←─── 0.2 ───→ it
模型判断 "it" 更可能指代 "animal"当模型处理每个词时,Self-Attention机制使得模型不仅能够关注当前位置的词,而且能够关注句子中其他位置的词,从而可以更好地编码这个词。
如果你熟悉RNN,可以对比理解:RNN通过维护隐状态将已处理的先前词/向量的表示与当前正在处理的词/向量进行合并,而Transformer使用Self-Attention机制将其他词的理解融入到当前词中。
Self-Attention的基本计算结构称为 Scaled Dot-Product Attention。
对于Self-Attention来说,Q(Query)、K(Key)和V(Value)三个矩阵均来自同一输入,按照以下步骤计算:
Step 1: 通过权重矩阵投影得到 Q, K, V
Q = X × W_Q
K = X × W_K
V = X × W_V
Step 2: 计算 Q 和 K^T 的点积
Scores = Q × K^T
Step 3: 除以 √d_k 进行缩放(防止点积结果过大)
Scaled = Scores / √d_k
Step 4: Softmax归一化为概率分布
Weights = Softmax(Scaled)
Step 5: 用权重对 V 做加权求和
Output = Weights × V完整公式:
计算注意力分数时,Query与Key的相似度决定了关注程度,然后用这个关注度对Value进行加权求和。
假设输入只有两个词:"Thinking" 和 "Machines",每个词被嵌入为512维向量。
第1步:为每个输入向量创建三个向量:Query向量、Key向量和Value向量。
它们是通过词向量分别和3个权重矩阵(W_Q、W_K、W_V)相乘得到的,这3个矩阵通过训练获得。
新向量的维数通常小于词向量的维数。例如新向量为64维,而embedding向量为512维。这是为了使多头注意力计算保持一致的结构性选择。
第2步:计算注意力分数。
假设正在计算第一个词"Thinking"的自注意力。需要根据"Thinking"这个词,对句子中的每个词都计算一个分数。这些分数决定了在编码"Thinking"时,需要对句子中其他位置的每个词放置多少注意力。
Scores[Thinking, Thinking] = Q_Thinking · K_Thinking = 112
Scores[Thinking, Machines] = Q_Thinking · K_Machines = 96第3步:将分数除以 √d_k(假设d_k=64,则除以8)。
Scaled[Thinking, Thinking] = 112 / 8 = 14
Scaled[Thinking, Machines] = 96 / 8 = 12第4步:Softmax归一化。
Softmax([14, 12]) ≈ [0.88, 0.12]这些Softmax分数决定了在编码当前位置的词时,对所有位置的词分别有多少注意力。当前位置的词通常有最高分数,但与当前词相关的其他词也会获得较高分数。
第5步:将每个Softmax分数分别与每个Value向量相乘,然后求和。
Output_Thinking = 0.88 × V_Thinking + 0.12 × V_Machines这样就完成了自注意力的计算。生成的向量会输入到前馈网络中。
实际实现中,上述计算是以矩阵形式进行的,以便实现更快的并行处理速度:
输入矩阵 X: (seq_len, d_model)
Q = X × W_Q → (seq_len, d_k)
K = X × W_K → (seq_len, d_k)
V = X × W_V → (seq_len, d_v)
Attention = Softmax(Q × K^T / √d_k) × V
→ (seq_len, seq_len) × (seq_len, d_v)
→ (seq_len, d_v)矩阵X中的每一行表示输入句子中每一个词的词向量。矩阵Q、K和V中的每一行分别表示Query向量、Key向量和Value向量。
在Transformer论文中,通过添加多头注意力机制进一步完善了自注意力层:
每一组注意力用于将输入映射到不同的子表示空间,这使得模型可以在不同子表示空间中关注不同的位置。
回到前面的例子:当我们对句子中的"it"进行编码时:
模型对"it"的表示,融入了"animal"和"tired"的部分表达,理解更加全面。
Multi-Head Attention的本质是:在参数总量保持不变的情况下,将同样的Query、Key、Value映射到原来的高维空间的不同子空间中进行Attention计算,在最后一步再合并不同子空间中的Attention信息。
这样做的好处:
位置前馈网络就是一个全连接前馈网络,每个位置的词都单独经过这个完全相同的前馈神经网络。
其由两个线性变换组成:
也可以写作:
其中:
FFN对每个位置独立做特征变换,增加了模型的非线性表达能力。
编码器结构中每个子层(Self-Attention层和FFN层)都有一个残差连接,再执行层标准化操作:
输出 = LayerNorm(x + Sublayer(x))残差连接的作用:
对每个样本的特征维度进行归一化,使其均值为0、方差为1:
为了方便残差连接,编码器和解码器中的所有子层和嵌入层的输出维度需要保持一致(d_model = 512)。
到目前为止描述的模型中缺少一个关键要素:表示序列中词顺序的方法。
Self-Attention机制本身是完全对称的——交换两个输入词的位置,输出也会相应交换,模型无法区分序列顺序。为了解决这个问题,Transformer为每个输入的词嵌入向量添加一个位置向量。
Transformer原文使用正弦余弦函数生成位置编码:
其中:
最终输入为词嵌入与位置编码相加:
最终输入 = token_embedding + positional_encoding现代大模型通常使用以下位置编码变体:
通过前面的介绍,已经了解:
解码阶段的每一步:
Encoder-Decoder Attention层的工作原理和多头自注意力机制类似,不同之处在于:
这样解码器在生成每个词时,都能关注到源序列中最相关的部分。
Mask表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer模型里面涉及两种mask:
问题:每个批次输入序列的长度不同,需要对较短的序列进行填充(padding)。
<pad>)。这些填充位置没有实际意义,Attention机制不应该把注意力放在这些位置上。具体实现:把这些位置的注意力分数加上一个非常大的负数(负无穷),经过Softmax后,这些位置的概率就会接近0。
问题:Decoder在训练时,为了防止看到未来的信息而"作弊",需要对当前位置之后的信息进行屏蔽。
做法:产生一个上三角矩阵,上三角的值全为负无穷。把这个矩阵作用在每个序列上,当前位置只能关注到它及之前的位置。
Sequence Mask 矩阵示例(4×4):
pos0 pos1 pos2 pos3
pos0 [ 0, -∞, -∞, -∞] → pos0只能看自己
pos1 [ 0, 0, -∞, -∞] → pos1能看pos0和pos1
pos2 [ 0, 0, 0, -∞] → pos2能看pos0~pos2
pos3 [ 0, 0, 0, 0] → pos3能看pos0~pos3解码器栈的输出是一个float向量。如何把这个向量转换为一个具体的词?
通过一个线性层(全连接神经网络),将解码器栈的输出向量映射到一个更长的向量——logits向量。
假设模型的输出词汇表有10000个英文单词,则logits向量有10000个数字,每个数表示一个单词的分数。
Softmax层把分数转换为概率:
Logits: [2.5, -1.2, 0.3, 5.1, ..., 1.0] (10000维)
↓ Softmax
Probs: [0.01, 0.001, 0.005, 0.85, ..., 0.02]
↓ Argmax
Output: "hello" (概率最高的词)A:
A:
A: 当d_k很大时,点积结果的方差会随d_k增大而增大,导致Softmax进入饱和区(梯度极小)。除以√d_k后,方差重新变为1,Softmax保持在合理区间,梯度健康,训练更稳定。
A:
A:
总体而言,FFN层参数最多,其次是注意力层。
A: O(n² × d),其中n是序列长度。主要开销在计算n×n的注意力矩阵。相比之下,RNN是O(n × d²)。短序列时Transformer更快,长序列时计算量增长较快。
A:
Transformer的核心设计可以概括为:
| 组件 | 作用 |
|---|---|
| Self-Attention | 让每个位置都能关注全局信息,直接建模任意位置依赖 |
| Multi-Head Attention | 在多个子空间并行计算注意力,捕捉不同角度的关联 |
| FFN | 对每个位置进行非线性特征变换,增强表达能力 |
| 残差连接 | 缓解梯度消失,帮助深层网络训练 |
| LayerNorm | 稳定每层的特征分布 |
| 位置编码 | 注入序列顺序信息 |
| Mask机制 | 处理填充和保证因果性 |
Transformer凭借其并行计算能力和强大的长程依赖建模能力,成为了现代大语言模型的基础架构,从BERT到GPT,从T5到LLaMA,无一不是基于Transformer的思想发展而来。