摘要: 告别RNN的序列依赖,拥抱并行化的注意力机制。本文将通过图解和PyTorch代码,带你一步步拆解Transformer的核心结构,让你彻底搞懂让BERT和GPT如此强大的“幕后功臣”。
大家好,我是[你的博客名]。
如果你关注人工智能,你一定听过GPT-4、BERT这些如雷贯耳的名字。它们在自然语言处理(NLP)领域掀起了革命,而这一切的基石,都源于一个在2017年横空出世的模型——Transformer。
很多朋友可能对RNN或LSTM有所了解,但一提到Transformer就觉得它复杂、难懂。别担心!这篇文章的使命,就是带你拨开云雾,从最核心的**自注意力机制(Self-Attention)**开始,结合图解和代码,让你不仅能看懂,更能亲手实现一个Transformer。
准备好了吗?让我们开始这场探索之旅!
一、为什么需要Transformer?RNN的“天花板”
在Transformer出现之前,RNN(循环神经网络)及其变体(如LSTM、GRU)是处理序列数据的王者。它们通过一个循环结构,依次处理序列中的每个元素,并将信息从前一步传递到后一步。
(图注:RNN/LSTM通过循环传递信息,擅长处理序列数据)
但这种结构有两个致命的“天花板”:
- 无法并行计算: 当前时间步的计算依赖于上一步的结果,这使得模型在长序列上训练得非常慢。
- 长距离依赖问题: 理论上LSTM可以捕捉长距离依赖,但实际上,信息在经过多个时间步的传递后,会逐渐丢失或“稀释”,导致模型难以关联距离很远的词语。
为了打破这些限制,Google的研究者们在论文《Attention Is All You Need》中提出了一个全新的架构——Transformer,它完全抛弃了循环结构,完全依赖于一种叫做“注意力”的机制。
二、Transformer架构概览:一个“积木城堡”
首先,我们来看一下Transformer的宏观结构。它本质上是一个**Encoder-Decoder(编码器-解码器)**架构。
(图注:Transformer由编码器(左侧)和解码器(右侧)堆叠而成)
- 编码器(Encoder): 负责“理解”输入序列。它将输入的单词序列(比如一个英文句子)转换成一系列富含上下文信息的数字表示(向量)。
- 解码器(Decoder): 负责“生成”输出序列。它利用编码器的输出和已经生成的部分结果,来预测下一个单词(比如生成法文翻译)。
编码器和解码器都不是单一的模块,而是由N个相同的层堆叠而成(原论文中N=6)。接下来,我们将把这些“积木”一块块拆开看。
三、核心构件1:输入处理与位置编码
计算机不认识单词,只认识数字。所以第一步,我们需要将单词转换成向量。
1. 词嵌入(Word Embedding)
这一步很简单,我们通过一个嵌入层(Embedding Layer)将每个单词映射到一个固定维度的向量。
2. 位置编码(Positional Encoding)
Transformer没有RNN的循环结构,它一次性“看到”所有单词。这就带来一个问题:它无法感知单词的顺序。例如,“我打你”和“你打我”在它看来可能是一样的。
为了解决这个问题,研究者引入了位置编码(Positional Encoding)。这是一种特殊设计的向量,它包含了单词在句子中的位置信息。这个位置编码会直接加到词嵌入向量上。
(图注:将单词的嵌入向量与它的位置编码向量相加)
它使用不同频率的正弦和余弦函数来生成,公式如下:
P
E
(
p
o
s
,
2
i
)
=
sin
(
p
o
s
/
1000
0
2
i
/
d
model
)
PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d_{\text{model}}})
PE(pos,2i)=sin(pos/100002i/dmodel)
P
E
(
p
o
s
,
2
i
+
1
)
=
cos
(
p
o
s
/
1000
0
2
i
/
d
model
)
PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d_{\text{model}}})
PE(pos,2i+1)=cos(pos/100002i/dmodel)
其中 pos
是单词的位置,i
是向量的维度索引。这种设计的好处是,模型可以学习到单词之间的相对位置关系。
PyTorch代码实现:
import torch
import torch.nn as nn
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super(PositionalEncoding, self).__init__()
# 创建一个足够长的位置编码矩阵
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
# 使用sin和cos函数填充
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
# 增加一个batch维度,并注册为buffer(不参与梯度更新)
pe = pe.unsqueeze(0).transpose(0, 1)
self.register_buffer('pe', pe)
def forward(self, x):
# x的维度是 [seq_len, batch_size, d_model]
# 将位置编码加到输入向量上
x = x + self.pe[:x.size(0), :]
return x
四、灵魂所在:自注意力机制(Self-Attention)
这是Transformer最核心、最天才的部分!
直观理解: 当我们阅读“The animal didn’t cross the street because it was too tired”这句话时,我们的大脑会自动将“it”和“The animal”关联起来。自注意力机制就是模拟这个过程,让模型在处理一个单词时,能够“关注”到句子中其他所有单词,并计算出每个单词对于当前单词的重要性(即“注意力分数”)。
Q, K, V 的故事:
自注意力机制为每个输入向量创建了三个新的向量:Query (查询), Key (键), 和 Value (值)。这三个向量是通过将输入向量分别乘以三个独立的权重矩阵(
W
Q
,
W
K
,
W
V
W_Q, W_K, W_V
WQ,WK,WV)得到的。
- Query (Q): 代表当前单词,可以理解为“我要查询谁和我关系最密切”。
- Key (K): 代表句子中所有单词的“标签”,用来和Query进行匹配。
- Value (V): 代表句子中所有单词的“内容”。
(图注:每个输入向量都生成Q, K, V三个向量)
计算过程(四步走):
-
计算分数(Score): 将你的Query向量与所有单词的Key向量进行点积。这个分数代表了Query和每个Key的相似度或相关性。
Score = Q · K^T
-
缩放(Scale): 将分数除以一个缩放因子(通常是Key向量维度的平方根 d k \sqrt{d_k} dk)。这可以防止在维度过高时点积结果过大,导致梯度消失。
Scaled_Score = Score / sqrt(d_k)
-
Softmax: 对缩放后的分数进行Softmax操作,将其转换为0到1之间的概率分布。这些就是“注意力权重”,表示每个单词对当前单词的贡献度。
-
加权求和(Weighted Sum): 将注意力权重与每个单词的Value向量相乘,然后求和。这样,与当前单词关系越密切的单词(注意力权重越高),其Value向量在最终结果中的占比就越大。
(图注:自注意力的完整计算流程,也是Transformer的核心公式)
五、强力升级:多头注意力(Multi-Head Attention)
一次自注意力只让模型从一个“角度”去看待单词间的关系。但实际上,单词间的关系是多样的(比如语法关系、语义关系等)。
**多头注意力(Multi-Head Attention)**就是让模型拥有多个“头”(独立的Q, K, V权重矩阵),从不同角度并行地进行自注意力计算。
(图注:将Q, K, V拆分成多个头,并行计算,最后拼接)
它的过程如下:
- 将原始的Q, K, V矩阵在特征维度上分割成
h
个小块(h
是头的数量)。 - 每个“头”独立地执行上面描述的自注意力计算,得到各自的输出。
- 将所有头的输出拼接(Concatenate)起来。
- 将拼接后的结果通过一个最终的线性层( W O W_O WO)进行变换,得到最终的输出。
这样做的好处是,它允许模型在不同的表示子空间中共同学习信息,极大地增强了模型的表达能力。
PyTorch代码实现 (Multi-Head Attention):
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
assert d_model % num_heads == 0
self.d_k = d_model // num_heads
self.num_heads = num_heads
self.linears = nn.ModuleList([nn.Linear(d_model, d_model) for _ in range(4)]) # W_q, W_k, W_v, W_o
self.attn = None
def forward(self, query, key, value, mask=None):
# query, key, value: [batch_size, seq_len, d_model]
if mask is not None:
mask = mask.unsqueeze(1) # For broadcasting across heads
batch_size = query.size(0)
# 1. Linear projections & split into heads
query, key, value = [l(x).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
for l, x in zip(self.linears, (query, key, value))]
# 2. Apply attention on all the projected vectors in batch.
# q, k, v are now [batch_size, num_heads, seq_len, d_k]
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9) # Apply mask
p_attn = F.softmax(scores, dim=-1)
# 3. Apply attention to V and concatenate heads
x = torch.matmul(p_attn, value)
x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
# 4. Final linear layer
return self.linears[-1](x)
六、组装编码器和解码器
现在我们有了核心部件,可以组装一个完整的**编码器层(Encoder Layer)和解码器层(Decoder Layer)**了。
编码器层有两个子层:
- 多头注意力层
- 一个简单的全连接前馈网络(Feed-Forward Network)
在每个子层之后,都跟着一个残差连接(Residual Connection)和层归一化(Layer Normalization)。这被称为“Add & Norm”,是训练深度模型的关键技巧,能有效防止梯度消失和爆炸。
解码器层有三个子层:
- 带掩码的多头注意力(Masked Multi-Head Attention): 这是解码器特有的。在生成翻译时,模型只能看到已经生成的词,不能“偷看”未来的词。这个掩码(Mask)就是用来实现这一点的。
- 编码器-解码器注意力(Encoder-Decoder Attention): 它的Query来自上一个解码器子层,但Key和Value来自编码器的最终输出。这是连接编码器和解码器的桥梁,让解码器能够“关注”输入句子的所有部分。
- 全连接前馈网络。
同样,每个子层后都有“Add & Norm”。
七、最后的冲刺:输出层
当解码器栈输出了最终的向量序列后,我们只需要再加一个线性层和一个Softmax层,就能将其转换为一个在整个词汇表上的概率分布。概率最高的那个词,就是我们预测的下一个词!
总结
恭喜你!我们已经把Transformer这个庞大的“城堡”完整地游览了一遍。让我们回顾一下它的精髓:
- 完全基于注意力: 抛弃了RNN的循环结构,实现了高效的并行计算。
- 自注意力机制: 通过Q, K, V计算单词间的依赖关系,有效捕捉长距离依赖。
- 多头注意力: 从不同角度捕捉信息,增强模型表达能力。
- 位置编码: 解决了无序性问题,将位置信息融入模型。
- Encoder-Decoder架构: 经典的积木式搭建,清晰且强大。
正是这些创新,使得Transformer成为了现代NLP的基石。无论是用于理解的BERT(主要使用Encoder),还是用于生成的GPT(主要使用Decoder),它们的核心思想都源于此。
希望这篇图文并茂、代码详实的文章能帮你真正理解Transformer。如果你有任何问题,欢迎在评论区留言讨论!
参考资料:
- 论文原文: Attention Is All You Need
- The Illustrated Transformer by Jay Alammar (强烈推荐)