如果你关注 AI,一定听过这些词:GPT-4、ChatGPT、BERT、Claude、LLaMA。这些模型名字各不相同,但它们的底层架构都指向同一个东西——Transformer

        2017 年,Google 团队在论文《Attention Is All You Need》中首次提出 Transformer。当时没人能预料到,这篇论文会彻底改变人工智能的走向。ChatGPT 能"听懂人话"、Midjourney 能"画图"、Sora 能"生成视频",底层都离不开 Transformer 的思想

        这篇文章会带你从零理解 Transformer 的每一个组件,并用 Python 代码亲手实现一个迷你版 Transformer


一、Transformer 之前,世界是什么样?

1.1 RNN 的困境

        在 Transformer 出现之前,处理文本主要靠 RNN(循环神经网络) 及其变体 LSTM、GRU。

Python

# RNN 的工作方式:一个词一个词地"啃"
"""
输入句子:"我 爱 中国"
RNN 处理流程:
  第1步:读 "我"    → 产生隐藏状态 h1
  第2步:读 "爱" + h1 → 产生隐藏状态 h2
  第3步:读 "中国" + h2 → 产生隐藏状态 h3
"""

RNN 的致命缺陷

Python

# 问题1:无法并行计算
# 必须等第1个词处理完,才能处理第2个词
# 一个 1000 词的句子,要串行跑 1000 步

# 问题2:长距离依赖丢失
# 句子开头的信息,传到句子末尾时已经"衰减"得差不多了
sentence = "这只猫,它毛色纯白、眼睛湛蓝、性格温顺、喜欢晒太阳、爱吃三文鱼...(中间100个词)...非常可爱"
# RNN 读到"非常可爱"时,已经很难记住主语是"猫"了

1.2 Transformer 的革命性改进

        Transformer 的核心思想就一句话:让每个词都能直接"看到"句子中所有其他词

Plain Text

RNN 看句子:  我 → 爱 → 中国           (串行,像排队传递)
Transformer: 我 ↔ 爱 ↔ 中国           (并行,像开会讨论)

这带来了两个巨大优势

  • ✅ 并行计算:所有词同时处理,训练速度大幅提升
  • ✅ 长距离依赖:无论词和词之间隔多远,都能直接建立联系

二、Transformer 的总体架构

        Transformer 由 编码器(Encoder) 和 解码器(Decoder) 两部分组成:

Plain Text

                    ┌─────────────────────┐
    输入文本         │     Encoder 编码器    │
  "我 爱 中国"  ──→ │  (理解输入什么意思)    │
                    └──────────┬──────────┘
                               │ 编码后的语义表示
                               ↓
                    ┌─────────────────────┐
                    │     Decoder 解码器    │  ──→  输出文本
                    │  (生成输出)           │      "I love China"
                    └─────────────────────┘
  • GPT 系列只用了解码器(所以叫 GPT = Generative Pre-trained Transformer)
  • BERT 只用了编码器(所以适合做"理解"类任务,如分类、问答)

下面我们重点拆解编码器,因为它是所有 Transformer 变体的核心。


三、编码器的核心组件:逐层拆解

3.1 第一层:输入嵌入(Input Embedding)

问题:计算机不认识"中国"这两个字,它只认识数字。怎么办?

方案:把每个词转换成一个固定长度的向量(一串数字,比如 512 个浮点数)。

Python

import torch
import torch.nn as nn
import math

# ============================================
# 1. 输入嵌入层:把词变成数字向量
# ============================================
class InputEmbedding(nn.Module):
    """
    将每个词映射为一个稠密向量。
    比如:"中国" → [0.12, -0.85, 0.33, ..., 0.67](512维)
    """
    def __init__(self, vocab_size: int, d_model: int):
        """
        vocab_size: 词汇表大小(比如 50000 个词)
        d_model:    每个词对应的向量维度(论文中是 512)
        """
        super().__init__()
        # nn.Embedding 就是一个查找表:
        #   输入:词的编号(比如 3721)
        #   输出:这个词对应的 512 维向量
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.d_model = d_model

    def forward(self, x):
        # x 的形状: (batch_size, seq_len)
        # 比如: (32, 100) → 32个句子,每句100个词
        # 输出形状: (batch_size, seq_len, d_model)
        # 比如: (32, 100, 512) → 每个词都变成了512维向量

        # 论文中把嵌入向量乘以 sqrt(d_model),为了让数值稳定
        return self.embedding(x) * math.sqrt(self.d_model)


# 测试一下
vocab_size = 10000  # 假设词汇表有 10000 个词
d_model = 512       # 每个词表示成 512 维向量
embedding_layer = InputEmbedding(vocab_size, d_model)

# 模拟输入:2 个句子,每句 10 个词(每个词用数字编号表示)
sample_input = torch.randint(0, vocab_size, (2, 10))
embedded = embedding_layer(sample_input)

print(f"输入形状: {sample_input.shape}")   # torch.Size([2, 10])
print(f"嵌入后形状: {embedded.shape}")     # torch.Size([2, 10, 512])
print(f"解释:2个句子,每句10个词,每个词变成512维向量 ✅")

3.2 第二层:位置编码(Positional Encoding)

关键问题:Transformer 是并行处理所有词的,它不知道"我爱你"和"你爱我"有什么区别!

方案:给每个位置(第1个词、第2个词...)加上一个独特的"位置签名"

Python

# ============================================
# 2. 位置编码:告诉模型每个词在句子的第几个位置
# ============================================
class PositionalEncoding(nn.Module):
    """
    使用正弦和余弦函数生成位置编码。
    每个位置都有一个独特的"位置签名"向量,
    直接加到词向量上,让模型知道词的顺序。
    """
    def __init__(self, d_model: int, max_len: int = 5000):
        """
        d_model: 嵌入维度(512)
        max_len: 支持的最大句子长度
        """
        super().__init__()

        # 创建一个 (max_len, d_model) 的矩阵,存放所有位置编码
        pe = torch.zeros(max_len, d_model)

        # position: 每个位置的编号 [0, 1, 2, ..., max_len-1]
        position = torch.arange(0, max_len).unsqueeze(1).float()

        # div_term: 用于控制正弦/余弦的频率
        # 偶数维度用 sin,奇数维度用 cos
        div_term = torch.exp(
            torch.arange(0, d_model, 2).float()
            * (-math.log(10000.0) / d_model)
        )

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

        # 增加 batch 维度,方便后续直接相加
        pe = pe.unsqueeze(0)  # (1, max_len, d_model)

        # 注册为 buffer(不参与训练,但会保存到模型中)
        self.register_buffer('pe', pe)

    def forward(self, x):
        """
        x 形状: (batch_size, seq_len, d_model)
        把位置编码加到词向量上
        """
        # x + pe 就是"含有位置信息的词向量"
        # 比如:"我"在位置0的向量 = 原始"我"的向量 + 位置0的编码
        x = x + self.pe[:, :x.size(1), :]
        return x


# 测试位置编码
pos_encoder = PositionalEncoding(d_model=512)
sample = torch.zeros(2, 10, 512)  # 2个句子,每句10个词
pos_encoded = pos_encoder(sample)
print(f"位置编码后形状: {pos_encoded.shape}")  # torch.Size([2, 10, 512])
print(f"位置编码已加入,不同位置的词有了不同的'位置签名' ✅")

3.3 第三层:自注意力机制(Self-Attention)—— 核心中的核心

这才是 Transformer 的"灵魂"

直观理解

读这句话:"太可爱了,我忍不住想摸"

人类读的时候,大脑会自动把"它"和前面的"猫"联系起来。自注意力机制就是让模型学会这种"关联"

三个关键概念:Q、K、V

Python

# 把自注意力想象成"搜索引擎":
# 
# Q(Query,查询):你想搜什么?  —— "我正在处理哪个词?"
# K(Key,键):   被搜索的内容标签 —— "其他词的特征是什么?"
# V(Value,值): 被搜索的内容本身 —— "其他词的实际内容是什么?"
#
# 流程:
#   Q 和每个 K 计算相似度 → 得到"注意力分数"
#   注意力分数 × V → 加权求和 → 得到"这个词应该关注哪些其他词"
代码实现

Python

# ============================================
# 3. 自注意力机制:Transformer 的灵魂
# ============================================
class SelfAttention(nn.Module):
    """
    缩放点积注意力(Scaled Dot-Product Attention)

    公式:Attention(Q, K, V) = softmax(Q·K^T / sqrt(d_k)) · V
    """
    def __init__(self, d_model: int, n_heads: int):
        """
        d_model: 模型维度(512)
        n_heads: 多头注意力的头数(8)
        """
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads  # 每个头的维度 = 512 / 8 = 64

        # 三个线性变换层:把输入分别映射成 Q、K、V
        self.W_q = nn.Linear(d_model, d_model)  # 生成 Query
        self.W_k = nn.Linear(d_model, d_model)  # 生成 Key
        self.W_v = nn.Linear(d_model, d_model)  # 生成 Value

        # 最终的输出映射
        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, x, mask=None):
        """
        x 形状: (batch_size, seq_len, d_model)
        返回: (batch_size, seq_len, d_model)
        """
        batch_size, seq_len, _ = x.shape

        # ===== 第1步:生成 Q、K、V =====
        # 把输入 x 通过三个不同的线性变换,得到 Q、K、V
        Q = self.W_q(x)  # (batch, seq_len, d_model)
        K = self.W_k(x)  # (batch, seq_len, d_model)
        V = self.W_v(x)  # (batch, seq_len, d_model)

        # ===== 第2步:拆成多头 =====
        # 把 d_model 拆成 n_heads 个头,每个头独立计算注意力
        # (batch, seq_len, d_model) → (batch, n_heads, seq_len, d_k)
        Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        K = K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        V = V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)

        # ===== 第3步:计算注意力分数 =====
        # Q 和 K 做点积,得到"每个词对其他词的关注度"
        # Q @ K^T → (batch, n_heads, seq_len, seq_len)
        attention_scores = torch.matmul(Q, K.transpose(-2, -1))

        # 缩放!除以 sqrt(d_k) 防止点积结果太大,导致 softmax 梯度消失
        attention_scores = attention_scores / math.sqrt(self.d_k)

        # 如果有 mask(比如 padding 的位置),把对应位置设为 -∞
        # 这样 softmax 之后这些位置的权重就是 0
        if mask is not None:
            attention_scores = attention_scores.masked_fill(
                mask == 0, float('-inf')
            )

        # ===== 第4步:softmax 得到注意力权重 =====
        # 每个词对各个词的关注度,加起来等于 1
        attention_weights = torch.softmax(attention_scores, dim=-1)

        # ===== 第5步:加权求和 =====
        # 用注意力权重 × V,得到"包含上下文的词表示"
        # (batch, n_heads, seq_len, d_k)
        output = torch.matmul(attention_weights, V)

        # ===== 第6步:合并多头 =====
        # (batch, n_heads, seq_len, d_k) → (batch, seq_len, d_model)
        output = output.transpose(1, 2).contiguous().view(
            batch_size, seq_len, self.d_model
        )

        # ===== 第7步:最终线性映射 =====
        output = self.W_o(output)

        return output


# 测试自注意力
attention = SelfAttention(d_model=512, n_heads=8)
sample = torch.randn(2, 10, 512)  # 2个句子,每句10个词,512维
output = attention(sample)
print(f"输入形状: {sample.shape}")    # torch.Size([2, 10, 512])
print(f"输出形状: {output.shape}")    # torch.Size([2, 10, 512])
print(f"每个词都"看"到了句子中所有其他词,并融合了上下文信息 ✅")
直观可视化:词与词之间学到了什么

Python

# 假设句子是:"我 爱 中国"
# 注意力矩阵可能是这样的(每个值表示"行"对"列"的关注度):

"""
        我      爱     中国
我    0.30   0.20   0.50    ← "我" 最关注 "中国"("我"是谁?我是"中国人")
爱    0.25   0.15   0.60    ← "爱" 最关注 "中国"(爱什么?爱"中国")
中国  0.40   0.35   0.25    ← "中国" 最关注 "我"(什么跟"中国"相关?"我")
"""

3.4 多头注意力(Multi-Head Attention)

为什么要多头?

一个头可能只关注"语法关系",另一个头关注"语义关系",还有一个关注"位置关系"。 多个头 = 从多个角度同时理解句子。

Python

# 前面的 SelfAttention 类已经实现了多头注意力
# 关键代码就是这一行:
# Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)

# 理解:把 512 维的向量切成 8 份,每份 64 维
# 每个头独立计算注意力,最后再把 8 个头的输出拼回 512 维

# 这就是论文标题的含义:
#   "Attention Is All You Need" = 注意力机制就是全部你需要的东西

3.5 前馈网络(Feed-Forward Network)

        注意力层让词之间互相"交流",前馈网络则让每个词"独立思考"

Python

# ============================================
# 4. 前馈网络:每个词独立思考,增加模型的表达能力
# ============================================
class FeedForward(nn.Module):
    """
    两层全连接网络 + ReLU 激活函数

    公式:FFN(x) = ReLU(x·W1 + b1)·W2 + b2

    先升维(d_model → d_ff,比如 512 → 2048),
    再降维(d_ff → d_model),
    中间用 ReLU 激活。
    这个"先膨胀再收缩"的结构,让模型有更强的表达能力。
    """
    def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1):
        """
        d_model: 模型维度(512)
        d_ff:    前馈网络内部维度(2048,论文中设为 4 倍 d_model)
        """
        super().__init__()
        self.linear1 = nn.Linear(d_model, d_ff)   # 512 → 2048,升维
        self.linear2 = nn.Linear(d_ff, d_model)   # 2048 → 512,降维
        self.dropout = nn.Dropout(dropout)         # 防止过拟合

    def forward(self, x):
        # x: (batch, seq_len, d_model)
        x = self.linear1(x)   # 升维到 2048
        x = torch.relu(x)     # 非线性激活
        x = self.dropout(x)   # 随机丢弃一些神经元
        x = self.linear2(x)   # 降维回 512
        return x


# 测试
ff = FeedForward(d_model=512, d_ff=2048)
sample = torch.randn(2, 10, 512)
output = ff(sample)
print(f"FFN 输入: {sample.shape} → 输出: {output.shape}")
print(f"512→2048→512,先扩展再压缩,增强了模型表达能力 ✅")

3.6 残差连接 + 层归一化(Add & Norm)

        层层叠加,够深才够强——但越深越难训练。残差连接就是来解决这个问题的。

Python

# ============================================
# 5. 残差连接 + 层归一化:确保深层网络能稳定训练
# ============================================
class AddAndNorm(nn.Module):
    """
    Add(残差连接):把输入和输出加起来
       → 就像"抄近道",让梯度能直接传回浅层,防止梯度消失

    Norm(层归一化):把数值稳定在一个范围内
       → 让训练更稳定、更快收敛
    """
    def __init__(self, d_model: int, dropout: float = 0.1):
        super().__init__()
        self.norm = nn.LayerNorm(d_model)  # 层归一化
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, sublayer_output):
        """
        x:              原始输入(残差)
        sublayer_output: 子层输出(注意力层或前馈网络层)
        """
        # 残差连接:原始输入 + 子层输出,让信息绕过子层直达后面
        # 然后做层归一化
        return self.norm(x + self.dropout(sublayer_output))

3.7 组装:一个完整的 Encoder 层

Python

# ============================================
# 6. 组装一个 Encoder 层
# ============================================
class EncoderLayer(nn.Module):
    """
    一个 Encoder 层 = 多头自注意力 + 前馈网络
    每个子层后面都有残差连接和层归一化

    结构:
    x → [Self-Attention] → Add&Norm → [FeedForward] → Add&Norm → 输出
    """
    def __init__(self, d_model: int, n_heads: int, d_ff: int, dropout: float = 0.1):
        super().__init__()
        self.attention = SelfAttention(d_model, n_heads)       # 多头自注意力
        self.feed_forward = FeedForward(d_model, d_ff, dropout) # 前馈网络
        self.add_norm1 = AddAndNorm(d_model, dropout)           # 注意力后的残差+归一化
        self.add_norm2 = AddAndNorm(d_model, dropout)           # 前馈后的残差+归一化

    def forward(self, x, mask=None):
        # 第1步:自注意力 → 残差连接 → 层归一化
        attn_output = self.attention(x, mask)
        x = self.add_norm1(x, attn_output)

        # 第2步:前馈网络 → 残差连接 → 层归一化
        ff_output = self.feed_forward(x)
        x = self.add_norm2(x, ff_output)

        return x


# ============================================
# 7. 组装完整的 Encoder(多个 Encoder 层堆叠)
# ============================================
class Encoder(nn.Module):
    """
    完整的编码器 = 输入嵌入 + 位置编码 + N 个 Encoder 层

    论文中 N=6,即堆叠 6 层
    """
    def __init__(
        self,
        vocab_size: int,   # 词汇表大小
        d_model: int,      # 模型维度(512)
        n_layers: int,     # Encoder 层数(6)
        n_heads: int,      # 注意力头数(8)
        d_ff: int,         # 前馈网络维度(2048)
        max_len: int,      # 最大句子长度
        dropout: float = 0.1
    ):
        super().__init__()
        self.embedding = InputEmbedding(vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model, max_len)

        # 堆叠 n_layers 个 Encoder 层
        self.layers = nn.ModuleList([
            EncoderLayer(d_model, n_heads, d_ff, dropout)
            for _ in range(n_layers)
        ])

        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # ① 词嵌入
        x = self.embedding(x)
        # ② 位置编码
        x = self.positional_encoding(x)
        # ③ 随机丢弃(正则化)
        x = self.dropout(x)
        # ④ 逐层传递
        for layer in self.layers:
            x = layer(x, mask)
        return x


# ============================================
# 8. 跑一个完整的 Encoder
# ============================================
def test_transformer_encoder():
    """测试迷你 Transformer Encoder"""
    vocab_size = 10000  # 词汇表 10000 个词
    d_model = 512       # 每个词用 512 维向量表示
    n_layers = 6        # 6 层 Encoder(论文标准配置)
    n_heads = 8         # 8 个注意力头
    d_ff = 2048         # 前馈网络 2048 维
    max_len = 100       # 最长 100 个词

    encoder = Encoder(vocab_size, d_model, n_layers, n_heads, d_ff, max_len)

    # 模拟输入:2 个句子,每句 20 个词
    sample_input = torch.randint(0, vocab_size, (2, 20))
    output = encoder(sample_input)

    print(f"输入形状: {sample_input.shape}")  # torch.Size([2, 20])
    print(f"输出形状: {output.shape}")         # torch.Size([2, 20, 512])
    print()
    print("Encoder 工作流程:")
    print("  ① 词嵌入:每个词 → 512维向量")
    print("  ② 位置编码:加入位置信息")
    print("  ③ 6层Encoder:每层包含自注意力+前馈网络")
    print("  ④ 输出:每个词都融合了整句话的上下文信息 ✅")
    print()

    # 计算参数量
    total_params = sum(p.numel() for p in encoder.parameters())
    print(f"总参数量: {total_params:,}")
    print(f"(GPT-3 有 1750 亿参数,我们的迷你版只有 {total_params:,} 个)")


test_transformer_encoder()

四、一张图总结 Transformer 的数据流动

Plain Text

输入:"我 爱 中国"
  │
  ├─→ ① 词嵌入:每个词 → 512维向量
  │     "我"   → [0.12, -0.85, 0.33, ...]
  │     "爱"   → [0.67, 0.21, -0.44, ...]
  │     "中国" → [-0.33, 0.78, 0.15, ...]
  │
  ├─→ ② 位置编码:加上位置签名
  │     "我" 位置0 → 加上 sin/cos 编码
  │     "爱" 位置1 → 加上 sin/cos 编码
  │     "中国" 位置2 → 加上 sin/cos 编码
  │
  └─→ ③ 6层 Encoder 处理:
       ┌─────────────────────────────────────┐
       │ 第1层:Self-Attention → FFN        │
       │ 第2层:Self-Attention → FFN        │
       │ ...                                 │
       │ 第6层:Self-Attention → FFN        │
       └─────────────────────────────────────┘
         ↓
    输出:每个词都融合了整句话的上下文
    "中国" 的向量里,包含了"我"和"爱"的信息

五、GPT 和 BERT 的区别

维度 BERT GPT
用的部分 只用 Encoder 只用 Decoder
看信息的方向 双向(能看到前后所有词) 单向(只能看到前面的词)
适合任务 理解:分类、问答、实体识别 生成:写文章、对话、翻译
训练方式 完形填空(猜被遮住的词) 预测下一个词
代表模型 BERT、RoBERTa GPT-3、GPT-4、ChatGPT

六、为什么 Transformer 是"革命性"的?

Python

# 对比总结

"""
Transformer 之前(RNN/LSTM 时代):
  ❌ 串行处理:1000个词要跑1000步,太慢了
  ❌ 长距离遗忘:句子太长,开头的信息就丢了
  ❌ 难以扩展:层数深了训练不稳定

Transformer 之后:
  ✅ 并行处理:所有词同时计算,速度飞起
  ✅ 任意距离:第1个词和第1000个词直接建立联系
  ✅ 可无限堆叠:层数越多效果越好(GPT-3 有 96 层)
  ✅ 通用架构:能处理文本、图像、语音、视频、代码...
"""


结语

Transformer 的本质,就是让机器学会"读句子时关注哪些词"。这看似简单的想法,却引发了 AI 领域的革命。

从 2017 年的一篇论文,到 2023 年 ChatGPT 横空出世,再到今天 AI 能写代码、画图、做视频,这一切的起点都是——Attention Is All You Need

理解 Transformer,就是拿到了理解现代 AI 的钥匙。希望这篇文章能帮你打开这扇门。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐