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)
]

这种设计具有以下关键优势

  1. 可泛化到更长序列
    正弦函数具有周期性与平滑性,模型可通过线性组合(如注意力权重)隐式学习相对位置关系(如“(pos+1) 相对于 (pos)”)。实验表明,训练时用 512 长度训练的模型,在推理时可外推至 >1000 长度(虽性能略降),而纯可学习嵌入在未见长度上完全失效。

  2. 支持相对位置建模
    利用三角函数恒等式(如 (\sin(\alpha+\beta), \cos(\alpha+\beta)) 可表示为 (\sin\alpha,\cos\alpha,\sin\beta,\cos\beta) 的线性组合),理论上任意两个位置的相对偏移可被同一组权重线性表出——这为模型理解“距离”提供了数学基础。

  3. 无需额外参数 & 确定性
    避免引入可训练参数,减少过拟合风险;且每次运行结果一致,利于调试与部署。

  4. 与词嵌入维度对齐且归一化良好
    各维振幅恒为 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)

在这里插入图片描述

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐