BERT vs. GPT:编码器 (Encoder) 与解码器 (Decoder) 模型终极指南

BERT vs. GPT The Ultimate Guide to Encoder and Decoder Models

如果你正在构建 AI 应用,在 BERT 和 GPT 之间进行选择不仅仅是个人喜好问题——这是一个关于你的模型需要阅读还是写作的架构性决策。

虽然两者都源于 Google 在 2018 年推出的革命性 Transformer 架构,但它们使用该引擎的不同部分来解决根本不同的问题。误解这种区别会导致性能低下和计算资源的浪费。本指南将剖析两者的机制、用例和代码实现。

核心概念:Transformer

原始的 Transformer 架构包含两个堆栈:

  1. 编码器 (The Encoder): 处理输入。它的设计初衷是理解上下文。
  2. 解码器 (The Decoder): 生成输出。它的设计初衷是预测下一步。

现代大语言模型 (LLM) 通常只专攻这等式的一半。

BERT (Bidirectional Encoder Representations from Transformers)

BERT 是一个仅含编码器 (Encoder-only) 的模型。它是双向的 (bi-directional),这意味着它同时从左和右查看单词的上下文。

  • 类比: 一位正在阅读手稿的学者。他们一次性看到整个句子,可以根据某个歧义词的前文后文来理解它的具体含义。
  • 超能力: 理解、分类、搜索。

GPT (Generative Pre-trained Transformer)

GPT 是一个仅含解码器 (Decoder-only) 的模型。它是自回归的 (auto-regressive)(单向),这意味着它从左到右阅读,无法“看到”未来的 token。它仅根据历史记录来预测下一个单词。

  • 类比: 一位正在现场起草演讲稿的撰稿人。他们完全专注于在前一个词之后哪个词最流畅,以保持连贯性。
  • 超能力: 生成、聊天、文本补全。

架构可视化

区别在于信息如何流经注意力机制 (Attention Mechanism)。

graph TD
    subgraph "BERT (编码器 Encoder)"
    A["输入: 'The bank of the river'"] --> B["自注意力机制 (双向)"]
    B --> C["'bank' 同时看到了 'The', 'of', 'river'"]
    C --> D["输出: 上下文嵌入 (Embedding)"]
    end

    subgraph "GPT (解码器 Decoder)"
    E["输入: 'The bank of'"] --> F["掩码自注意力机制 (单向)"]
    F --> G["'of' 只看到了 'The', 'bank'"]
    G --> H["输出: 预测下一个词 'the'"]
    end
    
    style A fill:#e1f5fe,stroke:#01579b
    style E fill:#fff3e0,stroke:#e65100

功能对比矩阵

特性 BERT (编码器) GPT (解码器)
方向性 双向 (左 <-> 右) 单向 (左 -> 右)
主要任务 理解 / 判别 生成
预训练目标 掩码语言建模 (Masked LM – 完形填空) 因果语言建模 (Causal LM – 预测下一个 token)
最佳用例 情感分析, 命名实体识别 (NER), 垃圾邮件检测, 语义搜索 聊天机器人, 代码生成, 故事创作, 摘要生成
输入限制 固定 (通常为 512 tokens) 灵活 (可变的上下文窗口)

实现:代码示例

为了观察实际运作中的差异,我们使用 Hugging Face 的 Python transformers 库。

1. 使用 BERT 进行理解 (特征提取)

我们使用 BERT 将文本转化为代表其含义的向量(数字)。请注意,我们并不要求它“说话”。

from transformers import BertTokenizer, BertModel
import torch

# 1. 初始化 BERT (编码器)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

text = "The bank of the river."
inputs = tokenizer(text, return_tensors="pt")

# 2. 前向传播以获取隐藏状态 (Hidden States)
with torch.no_grad():
    outputs = model(**inputs)

# 'last_hidden_state' 包含每个 token 的上下文嵌入 (Contextual Embedding)
# 形状: [Batch_Size, Sequence_Length, Hidden_Size]
embeddings = outputs.last_hidden_state

print(f"Vector Shape: {embeddings.shape}")
# 输出示例: torch.Size([1, 7, 768]) 

2. 使用 GPT 进行生成

我们使用 GPT 来生成文本。该模型需要一个循环(或 generate 工具函数)来一次预测一个 token。

from transformers import GPT2Tokenizer, GPT2LMHeadModel

# 1. 初始化 GPT (解码器)
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
model = GPT2LMHeadModel.from_pretrained('gpt2')

prompt = "The future of AI is"
inputs = tokenizer(prompt, return_tensors="pt")

# 2. 生成文本 (自回归循环由内部处理)
output_sequences = model.generate(
    input_ids=inputs['input_ids'],
    max_length=20,
    temperature=0.7,
    num_return_sequences=1,
    do_sample=True
)

generated_text = tokenizer.decode(output_sequences[0], skip_special_tokens=True)
print(f"Generated: {generated_text}")
# 输出示例: Generated: The future of AI is likely to be shaped by the development of new technologies...

循序渐进:如何选择

遵循此逻辑流程为你的项目选择正确的架构。

  1. 定义输出:
    • 输出是一个标签吗(例如:“正面”,“垃圾邮件”,“类别 A”)? -> 使用 BERT
    • 输出是一个数字吗(例如:基于新闻预测股价)? -> 使用 BERT
    • 输出是新文本吗? -> 使用 GPT
  2. 评估上下文需求:
    • 句子开头的含义是否依赖于句子的结尾?(例如:DNA 序列分析,复杂的法律条款解释)。
    • 行动: 如果是,编码器模型的双向特性更具优势。
  3. 考虑延迟:
    • 编码器模型在分类任务上通常更快,因为它们一次性处理输入。
    • 解码器模型在生成任务上较慢,因为每生成一个单词都必须运行一次模型。

背后的数学

核心区别在于概率计算方式。

GPT (自回归 Auto-regressive):
一个序列 $W$ 的概率是条件概率的乘积:

BERT (掩码自编码 Masked Auto-encoding):
BERT 在给定序列中所有其他 token 的情况下预测被掩盖的 token $w_i$:

专业技巧

  • 嵌入质量 (Embedding Quality): 不要使用原始的 GPT 嵌入进行语义搜索或聚类。因为 GPT 只向左看,最后一个词的嵌入往往过度代表了最近的上下文。BERT(或 S-BERT)能生成显著更好的句子嵌入。
  • “编码器-解码器”的中间地带: 如果你需要输入文本并输出文本(例如:翻译或摘要),请使用 T5BART。这些模型利用了两个堆栈——一个编码器用于读取源文本,一个解码器用于生成翻译。
  • 指令微调 (Instruction Tuning): 现代的“聊天”模型(如 ChatGPT 或 Llama 3)是经过微调以表现得像理解指令一样的仅解码器 (Decoder-only) 模型。虽然它们是解码器,但其巨大的规模使它们能够执行过去属于编码器领域的推理任务。

总结: 当你需要机器进行分析、分类或搜索时,使用 编码器 (BERT)。当你需要机器进行创作、聊天或扩展内容时,使用 解码器 (GPT)