Transformer模型自2017年由Google提出以来,已经成为自然语言处理领域的基石架构。作为面试官,我经常在二面环节用这些问题考察候选人对模型本质的理解。下面我将用最通俗的方式解析这21个核心问题,每个解释都附带实际案例和数学原理。
想象你正在阅读一份技术报告。如果只有一个人阅读,他可能只关注技术细节而忽略商业价值,或者相反。多头注意力就像组建了一个专家团队:
每个专家(注意力头)都有自己的专长领域,通过分工合作实现全面理解。从数学角度看,多头注意力将输入向量投影到多个子空间:
code复制多头注意力公式:
MultiHead(Q,K,V) = Concat(head_1,...,head_h)W^O
其中 head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
实际应用中,头数(h)通常设为8,每个头的维度(d_k)为64(当d_model=512时)。这种设计使得模型参数量仅线性增长(增加投影矩阵),却能获得指数级的关系捕捉能力。
经验提示:在实现时要注意对每个头的输出进行适当缩放,避免拼接后数值范围差异过大。
用相亲场景类比:
如果QK使用相同矩阵,就像用你的标准来改写对方的自我介绍,会导致信息失真。数学上,使用独立矩阵可以保证:
实验表明,共享QK矩阵会使模型性能下降约15-20%。在实现时,虽然可以共享QK矩阵节省参数,但会严重限制模型表达能力。
点积公式:
code复制Attention(Q,K,V) = softmax(QK^T/√d_k)V
对比两种注意力计算方式:
| 比较维度 | 点积注意力 | 加法注意力 |
|---|---|---|
| 计算复杂度 | O(n^2·d) | O(n^2·d^2) |
| 并行效率 | 高(矩阵乘法优化) | 低(逐元素计算) |
| 梯度传播 | 稳定 | 容易出现梯度消失 |
| 实际效果 | 更sharp的注意力分布 | 更平滑但区分度低 |
点积注意力的优势在于:
实测技巧:当序列长度超过512时,可以考虑使用局部注意力或稀疏注意力变体来降低计算开销。
这是为了控制点积结果的方差。假设q和k是独立随机变量,均值为0,方差为1,则q·k的方差为d_k。缩放后:
code复制Var(q·k/√d_k) = d_k/(√d_k)^2 = 1
不进行缩放的后果:
实际调试中发现,当d_k=64时,缩放因子≈8。如果忘记缩放,训练初期loss可能无法正常下降。
标准做法是在softmax前将padding位置赋值为极小数(如-1e9)。具体步骤:
python复制# PyTorch实现示例
attention_scores = torch.matmul(Q, K.transpose(-1, -2))
attention_scores = attention_scores / math.sqrt(d_k)
attention_scores = attention_scores + attention_mask # mask已预处理
attention_weights = F.softmax(attention_scores, dim=-1)
常见错误:
假设d_model=512,h=8,则每个头维度d_k=512/8=64。这样设计是因为:
总计算量保持不变:
参数效率更高:
更丰富的表示空间:
不同头可以专注于不同特征子空间
实际部署时,头数选择需要权衡:
标准Encoder层包含:
数据流动示意图:
code复制输入 → 多头注意力 → 残差连接 → LayerNorm → FFN → 残差连接 → LayerNorm → 输出
关键设计考量:
在BERT等模型中,通常堆叠12-24个这样的Encoder层。
这是为了保持输入向量与位置编码的量级一致。数学推导:
假设:
则:
code复制Var(Wx) = d_model × (1/d_model) = 1
因此需要将词嵌入乘以√d_model来保持方差一致。这个细节经常被忽略,但实际上对模型初始阶段的稳定性很重要。
| 类型 | 公式 | 优点 | 缺点 |
|---|---|---|---|
| 正弦式 | PE(pos,2i)=sin(pos/10000^(2i/d)) | 可外推 | 缺乏方向性 |
| 学习式 | 可训练参数 | 灵活 | 长度受限 |
| 相对位置 | 考虑相对距离 | 适合长文本 | 实现复杂 |
| RoPE | 旋转矩阵 | 保持距离度量 | 计算稍复杂 |
实践中发现:
根本原因在于序列数据的特性:
对比实验显示:
实现要点:
python复制# 正确的LayerNorm实现
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-6):
super().__init__()
self.gamma = nn.Parameter(torch.ones(features))
self.beta = nn.Parameter(torch.zeros(features))
self.eps = eps
def forward(self, x):
mean = x.mean(-1, keepdim=True)
std = x.std(-1, keepdim=True)
return self.gamma * (x - mean) / (std + self.eps) + self.beta
标准FFN结构:
code复制FFN(x) = max(0, xW1 + b1)W2 + b2
典型配置:
改进方案:
核心是交叉注意力:
实现要点:
python复制# 交叉注意力实现
class CrossAttention(nn.Module):
def __init__(self, d_model, h):
super().__init__()
self.attn = MultiHeadAttention(d_model, h)
def forward(self, x, memory, mask=None):
return self.attn(x, memory, memory, mask)
训练技巧:
Transformer的并行性体现在:
但在Decoder端:
| 对比项 | WordPiece | BPE |
|---|---|---|
| 合并标准 | 似然最大 | 频率最高 |
| 实现 | 需要语言模型 | 直接统计 |
| 效果 | 更语义化 | 更通用 |
Dropout设置建议:
典型的三阶段设置:
Adam优化器参数:
实际训练中,学习率对最终效果影响极大,差异可达5-10个点。
与传统Transformer的区别:
这种设计是因为:
在实现语言模型时,选择正确的Mask策略至关重要。对于生成任务,通常还是采用传统的因果Mask。