Transformer 是一种基于自注意力(Self-Attention)机制的深度学习模型架构,由 Vaswani 等人在 2017 年的论文
📌 **实际趋势**:- **Decoder-only 模型(如 GPT 系列)**:早期 GPT-2 用正弦 PE,GPT-3 开始转向可学习 PE(配合 ALiBi 等外推技术);- **Encoder-only 模型(如 BERT)**:默认可学习 PE;- **长上下文增强方案**:常结合两者思想,如 RoPE(Rotary Position Embedding)、ALiBi(Atten
Transformer 是一种基于自注意力(Self-Attention)机制的深度学习模型架构,由 Vaswani 等人在 2017 年的论文《Attention is All You Need》中首次提出。它摒弃了传统 RNN/CNN 的序列建模方式,完全依赖注意力机制捕捉输入序列中任意位置之间的依赖关系,具有并行化训练强、长程依赖建模能力强、可扩展性好等优势。Transformer 由编码器(Encoder)和解码器(Decoder)两大部分组成,每部分均由多层堆叠的子模块构成:
- 编码器层:包含多头自注意力(Multi-Head Self-Attention)和前馈神经网络(Feed-Forward Network),每层后接残差连接与层归一化(LayerNorm);
- 解码器层:在编码器基础上增加一个“编码器-解码器交叉注意力”子层,用于关注编码器输出,并通过掩码(Masked Self-Attention)确保预测时仅依赖已生成的前序词元(causal masking)。
其核心组件——缩放点积注意力(Scaled Dot-Product Attention)计算公式为:
[
\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V
]
其中 (Q, K, V) 分别为查询、键、值矩阵,(d_k) 是键向量维度,缩放防止内积过大导致 softmax 梯度饱和。
Transformer 已成为现代大语言模型(如 BERT、GPT、LLaMA、Qwen)的基础架构,广泛应用于机器翻译、文本生成、语音识别、图像理解(ViT)等领域。
import torch
import torch.nn as nn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model=512, num_heads=8, dropout=0.1):
super().__init__()
assert d_model % num_heads == 0
self.d_k = d_model // num_heads
self.num_heads = num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, q, k, v, mask=None):
batch_size = q.size(0)
# Linear projections and reshape: (batch, seq, d_model) → (batch, num_heads, seq, d_k)
q = self.W_q(q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
k = self.W_k(k).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
v = self.W_v(v).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# Scaled dot-product attention
scores = torch.matmul(q, k.transpose(-2, -1)) / (self.d_k ** 0.5)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn = torch.softmax(scores, dim=-1)
attn = self.dropout(attn)
context = torch.matmul(attn, v)
# Concatenate heads and project
context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k)
return self.W_o(context)
Transformer 模型本身不具备序列顺序感知能力(因其自注意力机制是排列不变的,即打乱输入词元顺序不改变输出),因此必须显式注入位置信息。位置编码(Positional Encoding, PE)正是为解决这一问题而设计的。
为什么使用正弦/余弦函数?
Vaswani 等人提出固定形式的正弦/余弦位置编码,其第 (pos) 个位置、第 (i) 维的编码定义为:
[
PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right), \quad
PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)
]
这种设计具有以下关键优势:
-
可泛化到更长序列:
正弦函数具有周期性与平滑性,模型可通过线性组合(如注意力权重)隐式学习相对位置关系(如“(pos+1) 相对于 (pos)”)。实验表明,训练时用 512 长度训练的模型,在推理时可外推至 >1000 长度(虽性能略降),而纯可学习嵌入在未见长度上完全失效。 -
支持相对位置建模:
利用三角函数恒等式(如 (\sin(\alpha+\beta), \cos(\alpha+\beta)) 可表示为 (\sin\alpha,\cos\alpha,\sin\beta,\cos\beta) 的线性组合),理论上任意两个位置的相对偏移可被同一组权重线性表出——这为模型理解“距离”提供了数学基础。 -
无需额外参数 & 确定性:
避免引入可训练参数,减少过拟合风险;且每次运行结果一致,利于调试与部署。 -
与词嵌入维度对齐且归一化良好:
各维振幅恒为 1,避免某维主导,有利于优化稳定性。
能否用可学习的位置嵌入(Learned Position Embedding)替代?
✅ 完全可以,且在实践中广泛使用(尤其在 BERT、ViT、大多数现代 LLM 中)。
例如:BERT 将 [CLS] + token embeddings + position embeddings 三者相加后输入 Encoder,其中 position embedding 是一个形状为 (max_len, d_model) 的可训练张量(如 nn.Embedding(max_position_embeddings, hidden_size))。
🔹 优点:
- 更灵活,能适配任务特定的位置模式(如代码中的缩进层级、表格中的行列结构);
- 训练数据充足时,往往比固定 PE 表现更好(尤其在中等长度序列上);
- 实现简单,与词嵌入统一管理。
🔹 缺点:
- 无法外推:若训练最大长度为 512,推理时输入 1024 长度,则超出索引会报错或需插值(通常导致性能骤降);
- 引入额外参数(约 (L \times d)),增大内存与计算开销(对超长上下文不友好);
- 缺乏显式的归纳偏置,模型需从头学“什么是位置”,样本效率略低。
📌 实际趋势:
- Decoder-only 模型(如 GPT 系列):早期 GPT-2 用正弦 PE,GPT-3 开始转向可学习 PE(配合 ALiBi 等外推技术);
- Encoder-only 模型(如 BERT):默认可学习 PE;
- 长上下文增强方案:常结合两者思想,如 RoPE(Rotary Position Embedding)、ALiBi(Attention with Linear Biases)、YaRN(扩展 RoPE 外推能力)等,既保持外推性,又提升表达力。
# 示例:可学习位置嵌入(PyTorch)
class LearnedPositionEmbedding(nn.Module):
def __init__(self, max_len: int, d_model: int):
super().__init__()
self.pe = nn.Embedding(max_len, d_model)
self.register_buffer('position_ids', torch.arange(max_len).expand((1, -1)))
def forward(self, x): # x: (batch, seq_len, d_model)
pos_ids = self.position_ids[:, :x.size(1)]
return x + self.pe(pos_ids)
# 示例:正弦位置编码(固定,无参)
def get_sinusoidal_pe(max_len: int, d_model: int):
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))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
return pe.unsqueeze(0) # (1, max_len, d_model)

更多推荐



所有评论(0)