2017年那篇《Attention Is All You Need》论文彻底改变了自然语言处理的游戏规则。作为从业者,我至今记得第一次看到这个架构时的震撼——它用自注意力机制完全取代了传统的RNN和CNN结构。现在五年过去了,Transformer不仅成为NLP领域的标配,更在计算机视觉、语音识别等领域大放异彩。
理解Transformer对程序员的价值在于:
自注意力(Self-Attention)是Transformer最核心的创新。我用一个简单例子说明它的工作原理:
假设我们要处理句子"猫喜欢吃鱼"。传统方法会按顺序处理每个词,而自注意力会让每个词都与其他所有词建立联系。具体计算分为三步:
将每个词转换为Query、Key、Value三个向量:
计算注意力分数(以"吃"为例):
code复制分数("吃","猫") = Q_吃 · K_猫
分数("吃","喜欢") = Q_吃 · K_喜欢
分数("吃","鱼") = Q_吃 · K_鱼
加权求和得到新表示:
code复制new_吃 = softmax(分数) × [V_猫, V_喜欢, V_鱼]
实际实现时会使用多头注意力(Multi-Head Attention),相当于多个不同的"视角"同时计算注意力。代码实现关键点:
python复制# 伪代码展示多头注意力实现
class MultiHeadAttention:
def __init__(self, d_model, num_heads):
self.W_q = Linear(d_model, d_model) # Query变换
self.W_k = Linear(d_model, d_model) # Key变换
self.W_v = Linear(d_model, d_model) # Value变换
self.W_o = Linear(d_model, d_model) # 输出变换
def forward(self, x):
Q = self.W_q(x) # [batch, seq_len, d_model]
K = self.W_k(x)
V = self.W_v(x)
# 分割为多个头
Q = split_heads(Q) # [batch, num_heads, seq_len, depth]
K = split_heads(K)
V = split_heads(V)
# 计算缩放点积注意力
scores = matmul(Q, K.transpose(-2, -1)) / sqrt(depth)
weights = softmax(scores)
output = matmul(weights, V)
# 合并多头输出
output = combine_heads(output)
return self.W_o(output)
由于Transformer没有递归结构,需要显式地加入位置信息。原始论文使用正弦位置编码:
code复制PE(pos,2i) = sin(pos/10000^(2i/d_model))
PE(pos,2i+1) = cos(pos/10000^(2i+1/d_model))
这种编码的优势:
注意:现在很多模型改用可学习的位置嵌入,效果也不错但可能影响外推能力
一个标准的Transformer编码器层包含:
关键实现细节:
python复制class EncoderLayer:
def __init__(self, d_model, num_heads, d_ff, dropout):
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.ffn = PositionwiseFeedForward(d_model, d_ff)
self.norm1 = LayerNorm(d_model)
self.norm2 = LayerNorm(d_model)
self.dropout = Dropout(dropout)
def forward(self, x, mask):
# 子层1:自注意力
attn_output = self.self_attn(x, x, x, mask)
x = x + self.dropout(attn_output)
x = self.norm1(x)
# 子层2:前馈网络
ffn_output = self.ffn(x)
x = x + self.dropout(ffn_output)
x = self.norm2(x)
return x
解码器比编码器多了两个关键组件:
掩码的实现方式:
python复制def create_look_ahead_mask(size):
mask = torch.triu(torch.ones(size, size), diagonal=1)
return mask == 1 # 上三角为True的位置会被屏蔽
Transformer使用特殊的学习率warmup策略:
code复制lr = d_model^-0.5 * min(step^-0.5, step*warmup_steps^-1.5)
实际训练中发现:
问题1:训练时loss震荡严重
问题2:验证集表现差
问题3:长文本效果差
原始Transformer的O(n²)复杂度问题催生了许多改进:
Transformer在CV领域的成功应用:
一个有趣的发现:图像patch的嵌入方式与词嵌入非常相似,这说明Transformer确实捕捉到了某种通用的表示学习机制。
对于想真正理解Transformer的同学,我建议:
关键验证点:
我在第一次实现时犯过的错误:
现在回头看,这些错误恰恰帮助我深入理解了架构设计的精妙之处。理解Transformer最好的方式就是动手实现它,哪怕是一个简化版本。