返回教程列表
进阶AI教程
2026/6/25

Transformer 架构原理详解

Transformer 是现代大语言模型的基石。本文介绍自注意力机制、多头注意力、位置编码、前馈网络与残差连接的原理,并配合 PyTorch 实现要点。

47

阅读

👍

0

点赞

💬

0

评论

# Transformer 架构原理详解

1. 概述

Transformer 是一种完全基于注意力机制(Attention)来建模序列依赖的模型结构,最早在《Attention Is All You Need》中提出。它摒弃了传统的循环(RNN)或卷积(CNN)结构,仅使用 **自注意力**、**多头注意力**、**位置编码** 与 **前馈网络**,配合残差连接和层归一化,实现高效并行训练,并成为大语言模型(LLM)的基石。

---

2. 自注意力机制(Self‑Attention)

  • **核心思想**:对序列中的每个位置 query,计算它与所有位置 key 的相似度(点积),再用 softmax 归一化得到权重,最后将权重加权和到对应的 value 上。
  • **计算公式**(简写):
  • \[

    \text{Attention}(Q,K,V)=\operatorname{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V

    \]

    - \(Q, K, V\):分别由输入 \(X\) 经过线性投影得到,维度为 \((n, d)\)。

    - \(\sqrt{d_k}\) 为缩放因子,防止点积值过大导致梯度饱和。

  • **优势**:任意位置之间直接交互,信息流动不受距离限制,适合建模长程依赖。
  • ---

    3. 多头注意力(Multi‑Head Attention)

  • **为什么多头**:将注意力映射到多个子空间,使模型能够在不同的表示子空间中捕获不同类型的关联信息(如语法、语义、位置等)。
  • **实现步骤**:
  • 1. 将 \(Q,K,V\) 分别通过 \(h\) 组独立的线性投影得到 \(h\) 组 \((Q_i,K_i,V_i)\),每组维度为 \((n, d/h)\)。

    2. 对每组并行计算注意力得到 \( \text{head}_i = \text{Attention}(Q_i,K_i,V_i) \),形状 \((n, d/h)\)。

    3. 将所有 head 拼接后再次线性投影得到最终输出 \( \text{MultiHead}(Q,K,V) = \text{Concat}(\text{head}_1,\dots,\text{head}_h)W^O\)。

  • **常用超参**:头数 \(h=8\),每个头的维度 \(d_k = d / h\)。
  • ---

    4. 位置编码(Positional Encoding)

  • **必要性**:注意力本身是置换不变的(对输入顺序不敏感),必须显式注入序列位置信息。
  • **常见方案**:正弦/余弦位置编码(原始论文):
  • \[

    \begin{aligned}

    PE_{(pos,2i)} &= \sin\!\left(\frac{pos}{10000^{2i/d}}\right)\\

    PE_{(pos,2i+1)} &= \cos\!\left(\frac{pos}{10000^{2i/d}}\right)

    \end{aligned}

    \]

    - 其中 \(pos\) 为 token 在序列中的位置,\(i\) 为维度索引。生成的编码与词向量直接相加或拼接。

  • **可学习的位置编码**:在某些实现中,将位置向量当作可训练参数,效果与正弦编码相当。
  • ---

    5. 前馈网络(FFN)与残差连接

  • **FFN**:两层线性变换,中间加入激活函数(常用 GELU):
  • \[

    \text{FFN}(x)=\operatorname{GELU}(xW_1+b_1)W_2+b_2

    \]

    - 通常第一层维度为 \(d_{\text{model}} \rightarrow d_{\text{ff}}\)(如 3072),第二层恢复到 \(d_{\text{model}}\)。

  • **残差连接**(Residual):
  • \[

    x' = x + \text{SubLayer}(x)

    \]

    - 使得梯度能够直接回传,帮助深层网络稳定训练。

  • **层归一化**(LayerNorm):在每个子层的输出上先做 LayerNorm,再加残差:
  • \[

    \text{output} = \text{LayerNorm}(x + \text{SubLayer}(x))

    \]

    ---

    6. PyTorch 实现要点

    下面给出一个 **简化但完整** 的实现示例,演示多头注意力、位置编码以及一个完整的 Transformer Encoder Block。

    import math

    import torch

    import torch.nn as nn

    import torch.nn.functional as F

    class PositionalEncoding(nn.Module):

    def __init__(self, d_model, max_len=5000):

    super().__init__()

    # 生成 (max_len, d_model) 的位置编码

    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, dtype=torch.float) * (-math.log(10000.0) / d_model)

    )

    pe[:, 0::2] = torch.sin(position * div_term) # 偶数维度

    pe[:, 1::2] = torch.cos(position * div_term) # 奇数维度

    pe = pe.unsqueeze(0) # (1, max_len, d_model)

    self.register_buffer('pe', pe)

    def forward(self, x):

    # x shape: (batch, seq_len, d_model)

    x = x + self.pe[:, :x.size(1), :]

    return x

    class MultiHeadAttention(nn.Module):

    def __init__(self, d_model, num_heads):

    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)

    def split_heads(self, x):

    # x: (batch, seq_len, d_model)

    batch, seq_len, _ = x.size()

    x = x.view(batch, seq_len, self.num_heads, self.d_k).transpose(1, 2)

    return x # (batch, heads, seq_len, d_k)

    def forward(self, query, key, value, mask=None):

    # 线性投影

    Q = self.split_heads(self.W_q(query))

    K = self.split_heads(self.W_k(key))

    V = self.split_heads(self.W_v(value))

    # 注意力分数

    scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

    if mask is not None:

    scores = scores.masked_fill(mask == 0, -1e9)

    attn_weights = F.softmax(scores, dim=-1)

    # 加权求和

    attn_output = torch.matmul(attn_weights, V) # (batch, heads, seq_len,

    评论 (0)

    暂无评论,来说点什么吧