📚 课程目录

第21课:位置编码与多头注意力

📖 本课概述

Transformer没有循环结构,位置编码告诉它"顺序"信息。本课深入各种位置编码方法和多头注意力的细节实现。

📍 一、为什么需要位置编码

自注意力是置换不变的:Attention(π(x)) = π(Attention(x))

没有位置信息,"我爱你"和"你爱我"无法区分!

📐 二、正弦位置编码

PE(pos, 2i) = sin(pos / 10000^(2i/d_model))

PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

优点:可以推广到任意长度

2.1 相对位置关系

PE(pos+k)可以表示为PE(pos)的线性函数

这使模型可以学习相对位置关系

🔬 三、可学习位置编码

BERT和GPT使用可学习的位置嵌入:nn.Embedding(max_len, d_model)

优点:更灵活,可能学得更好

缺点:不能推广到训练时未见过的长度

🔄 四、旋转位置编码(RoPE)

将位置信息融入注意力计算

q·k 的内积只依赖相对位置(m-n)

LLaMA等现代模型使用RoPE

4.1 PyTorch实现位置编码


import torch
import torch.nn as nn
import math

# 位置编码实现
class SinusoidalPositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe.unsqueeze(0))
    
    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

class LearnedPositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=512):
        super().__init__()
        self.pe = nn.Parameter(torch.randn(1, max_len, d_model) * 0.02)
    
    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

# RoPE实现
class RotaryPositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=2048):
        super().__init__()
        inv_freq = 1.0 / (10000 ** (torch.arange(0, d_model, 2).float() / d_model))
        self.register_buffer('inv_freq', inv_freq)
    
    def forward(self, x):
        seq_len = x.size(1)
        t = torch.arange(seq_len, device=x.device).float()
        freqs = torch.outer(t, self.inv_freq)
        emb = torch.cat([freqs, freqs], dim=-1)
        cos_emb = emb.cos().unsqueeze(0)
        sin_emb = emb.sin().unsqueeze(0)
        return x * cos_emb + self._rotate_half(x) * sin_emb
    
    def _rotate_half(self, x):
        x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
        return torch.cat([-x2, x1], dim=-1)

# 对比
d_model = 64
seq_len = 20
x = torch.randn(1, seq_len, d_model)

encodings = {
    "正弦PE": SinusoidalPositionalEncoding(d_model),
    "可学习PE": LearnedPositionalEncoding(d_model),
    "RoPE": RotaryPositionalEncoding(d_model),
}

for name, enc in encodings.items():
    out = enc(x)
    diff = (out - x).abs().mean().item()
    params = sum(p.numel() for p in enc.parameters())
    print(f"{name}: 编码后差异={diff:.6f}, 可训练参数={params}")

# 正弦PE的性质:线性关系
print("\n=== 正弦PE的线性关系 ===")
sin_pe = SinusoidalPositionalEncoding(d_model)
pe = sin_pe.pe[0, :10, :8]  # 前10位置,前8维度
print("位置编码(前10位置,前8维):")
for pos in range(5):
    vals = [f"{pe[pos, d]:.4f}" for d in range(8)]
    print(f"  pos={pos}: {vals}")

# 不同距离的PE相似度
print("\nPE点积相似度(位置0 vs 位置k):")
for k in [1, 2, 5, 10, 20]:
    sim = torch.dot(pe[0], sin_pe.pe[0, k, :8]).item()
    print(f"  pos0·pos{k}: {sim:.4f}")
🟢 运行结果 ✅验证通过 正弦PE: 编码后差异=0.566654, 可训练参数=0 可学习PE: 编码后差异=0.016069, 可训练参数=32768 RoPE: 编码后差异=0.315417, 可训练参数=0 === 正弦PE的线性关系 === 位置编码(前10位置,前8维): pos=0: ['0.0000', '1.0000', '0.0000', '1.0000', '0.0000', '1.0000', '0.0000', '1.0000'] pos=1: ['0.8415', '0.5403', '0.6816', '0.7318', '0.5332', '0.8460', '0.4093', '0.9124'] pos=2: ['0.9093', '-0.4161', '0.9975', '0.0709', '0.9021', '0.4315', '0.7469', '0.6649'] pos=3: ['0.1411', '-0.9900', '0.7783', '-0.6279', '0.9933', '-0.1160', '0.9536', '0.3010'] pos=4: ['-0.7568', '-0.6536', '0.1415', '-0.9899', '0.7785', '-0.6277', '0.9933', '-0.1157'] PE点积相似度(位置0 vs 位置k): pos0·pos1: 3.0305 pos0·pos2: 0.7512 pos0·pos5: -1.9954 pos0·pos10: -0.1767 pos0·pos20: -0.6496

🔬 深入拓展

核心要点回顾

本课的核心知识点构成了序列模型学习的重要一环。让我们从更高维度审视这些知识之间的关系:

常见陷阱与最佳实践

⚠️ 初学者常犯的错误:

💡 最佳实践:

与前后课程的联系

深度学习是一个整体——每一课的知识都不是孤立的:

📖 延伸阅读

💡 继续深入的学习路径:

📝 练习

练习1:ALiBi位置编码

实现Attention with Linear Biases,分析其外推能力。

练习2:相对位置编码

实现相对位置编码,与绝对位置编码对比。

练习3:长度外推实验

测试不同位置编码在短序列训练、长序列推理时的表现。

💡 位置编码实践

位置编码选择

常见错误与解决方案

⚠️ 需要避免的典型错误:

  1. 不了解模型假设就盲目使用
  2. 没有建立基线就追求复杂方法
  3. 忽视数据质量和预处理
  4. 过度调参而不理解原理
  5. 只看训练指标忽略验证指标

性能优化建议

💡 提升模型性能的系统方法:

  1. 确保数据管道正确且高效
  2. 建立简单但正确的基线模型
  3. 分析基线的错误类型
  4. 针对性地改进(数据/模型/训练)
  5. 持续迭代,每步验证

🔬 深度拓展:从理论到实践

核心概念网络

理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:

关键洞察与直觉

建立正确的直觉比记住公式更重要:

1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)

2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的

3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构

4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)

与其他课程的关联

本课作为序列模型阶段的核心内容,与前后课程紧密关联:

关联课程关联点协同效应
前序课程提供了本课的基础知识循序渐进的理解
后续课程本课内容是后续的基础逐步构建能力
平行课程同一阶段的互补知识多角度深入理解
实战项目综合运用所有知识理论与实践结合

面试常见问题

💡 准备面试时,确保能回答以下问题:

  1. 用简单语言解释本课核心概念
  2. 画出关键算法/结构的流程图
  3. 比较不同方法的优缺点
  4. 举出实际应用场景
  5. 讨论常见误解和注意事项

进阶学习路径

掌握本课内容后,可以通过以下方式继续深入:

📍

成就解锁:位置大师

位置编码赋予Transformer感知顺序的能力!