前言:同一个 Transformer,两种命运
2017 年,Google 发布了 Transformer。
2018 年,基于 Transformer,两个重量级模型几乎同时诞生:
- 6 月:OpenAI 发布 GPT-1
- 10 月:Google 发布 BERT
它们都用 Transformer,但走了完全不同的路:
- GPT 选择了 Decoder-Only,从左到右生成
- BERT 选择了 Encoder-Only,双向理解
这篇文章,我们来搞懂这两条路的本质区别,以及为什么 GPT 路线最终"赢了"。
一、在开始之前:预训练范式的革命
1.1 传统 NLP 的痛苦
在 BERT 和 GPT 之前,NLP 是这样做的:
每个任务都要从头训练一个模型。数据标注贵,训练成本高,而且知识无法迁移。
1.2 预训练 + 微调范式
GPT 和 BERT 带来的革命:
核心思想:
- 预训练:在海量无标注文本上学习语言的"通用知识"
- 微调:用少量标注数据适配具体任务
这就像:先上大学学通识教育,再根据工作需要专项培训。比每份工作都从小学开始学,效率高太多了。
1.3 关键问题:预训练学什么?
预训练需要一个"任务"来驱动学习,但我们没有标注数据。
解决方案:自监督学习——让模型从文本本身构造监督信号。
GPT 和 BERT 的分歧就在这里:它们选择了不同的自监督任务。
二、GPT:从左到右的预言家
2.1 核心思想:预测下一个词
GPT 的预训练任务非常简单:给定前文,预测下一个词。
这叫做 Causal Language Model (CLM) 或 自回归语言模型。
输入: "今天天气真"
目标: "好"
输入: "今天天气真好"
目标: ","
输入: "今天天气真好,"
目标: "适合"数学形式:
$$ P(x_1, x_2, ..., x_n) = \prod_{i=1}^{n} P(x_i | x_1, x_2, ..., x_{i-1}) $$
用人话说:文本的概率 = 每个词在给定前文条件下的概率的乘积。
2.2 因果掩码:只能看前面
为了实现"预测下一个词",GPT 使用 因果掩码(Causal Mask):每个位置只能看到它之前的位置。
标准 Attention(可以看所有位置):
位置 0: [1, 1, 1, 1]
位置 1: [1, 1, 1, 1]
位置 2: [1, 1, 1, 1]
位置 3: [1, 1, 1, 1]
因果 Attention(只能看前面):
位置 0: [1, 0, 0, 0] ← 只能看自己
位置 1: [1, 1, 0, 0] ← 能看 0, 1
位置 2: [1, 1, 1, 0] ← 能看 0, 1, 2
位置 3: [1, 1, 1, 1] ← 能看所有2.3 GPT 的架构
GPT 使用 Decoder-Only 架构(实际上是 Transformer Decoder 去掉 Cross-Attention):
2.4 GPT 的代码实现
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class GPTBlock(nn.Module):
"""GPT 的一个 Transformer Block"""
def __init__(self, embed_dim, num_heads, ff_dim, dropout=0.1):
super().__init__()
self.ln1 = nn.LayerNorm(embed_dim)
self.attn = nn.MultiheadAttention(embed_dim, num_heads, dropout=dropout, batch_first=True)
self.ln2 = nn.LayerNorm(embed_dim)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.GELU(),
nn.Linear(ff_dim, embed_dim),
nn.Dropout(dropout)
)
self.dropout = nn.Dropout(dropout)
def forward(self, x, attn_mask=None):
# Pre-Norm 架构(GPT-2 开始使用)
# Self-Attention with Causal Mask
attn_out, _ = self.attn(
self.ln1(x), self.ln1(x), self.ln1(x),
attn_mask=attn_mask,
is_causal=True # PyTorch 2.0+ 支持
)
x = x + self.dropout(attn_out)
# Feed Forward
x = x + self.ffn(self.ln2(x))
return x
class GPT(nn.Module):
"""简化版 GPT 模型"""
def __init__(self, vocab_size, embed_dim, num_heads, num_layers, max_len, ff_dim=None):
super().__init__()
ff_dim = ff_dim or embed_dim * 4
self.token_embed = nn.Embedding(vocab_size, embed_dim)
self.pos_embed = nn.Embedding(max_len, embed_dim)
self.blocks = nn.ModuleList([
GPTBlock(embed_dim, num_heads, ff_dim)
for _ in range(num_layers)
])
self.ln_f = nn.LayerNorm(embed_dim)
self.head = nn.Linear(embed_dim, vocab_size, bias=False)
# 权重共享:embedding 和 output head
self.head.weight = self.token_embed.weight
def forward(self, input_ids):
batch, seq_len = input_ids.shape
# Embedding
positions = torch.arange(seq_len, device=input_ids.device)
x = self.token_embed(input_ids) + self.pos_embed(positions)
# Transformer Blocks
for block in self.blocks:
x = block(x)
# Output
x = self.ln_f(x)
logits = self.head(x)
return logits
def generate(self, input_ids, max_new_tokens, temperature=1.0):
"""自回归生成"""
for _ in range(max_new_tokens):
# 只取最后 max_len 个 token(处理超长序列)
logits = self(input_ids[:, -self.pos_embed.num_embeddings:])
# 取最后一个位置的 logits
logits = logits[:, -1, :] / temperature
probs = F.softmax(logits, dim=-1)
# 采样
next_token = torch.multinomial(probs, num_samples=1)
input_ids = torch.cat([input_ids, next_token], dim=-1)
return input_ids
# 训练 GPT
def train_gpt_step(model, input_ids, optimizer):
"""GPT 的训练步骤"""
# input_ids: (batch, seq_len)
# 输入是 [:-1],目标是 [1:]
inputs = input_ids[:, :-1]
targets = input_ids[:, 1:]
# 前向传播
logits = model(inputs)
# 计算损失(交叉熵)
loss = F.cross_entropy(
logits.view(-1, logits.size(-1)),
targets.view(-1)
)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
# 使用示例
if __name__ == "__main__":
# 模型配置
model = GPT(
vocab_size=50257, # GPT-2 的词表大小
embed_dim=768,
num_heads=12,
num_layers=12,
max_len=1024
)
print(f"参数量: {sum(p.numel() for p in model.parameters()):,}")
# 约 124M,和 GPT-2 small 一样2.5 GPT 的演进
| 模型 | 参数量 | 特点 |
|---|---|---|
| GPT-1 (2018) | 117M | 首次证明预训练+微调有效 |
| GPT-2 (2019) | 1.5B | Zero-shot 能力,"太危险不敢开源" |
| GPT-3 (2020) | 175B | In-Context Learning,涌现能力 |
| GPT-4 (2023) | ~1.8T? | 多模态,推理能力大幅提升 |
三、BERT:双向理解的王者
3.1 核心思想:完形填空
BERT 的预训练任务是 Masked Language Model (MLM):随机遮住一些词,让模型预测被遮住的词。
原始文本: "今天天气真好"
输入: "今天 [MASK] 真好"
目标: 预测 [MASK] = "天气"关键区别:BERT 可以同时看到左边和右边的上下文。
$$ P(x_{\text{masked}} | x_{\text{context}}) $$
这就像做完形填空:你可以看整个句子,然后填空。
3.2 双向注意力:全都能看
BERT 使用 Encoder-Only 架构,没有因果掩码:
BERT 的 Attention(所有位置都能看):
位置 0: [1, 1, 1, 1] ← 能看所有
位置 1: [1, 1, 1, 1] ← 能看所有
位置 2: [1, 1, 1, 1] ← 能看所有
位置 3: [1, 1, 1, 1] ← 能看所有3.3 BERT 的预训练任务
BERT 实际上有两个预训练任务:
任务 1:Masked Language Model (MLM)
- 随机选择 15% 的 token
- 其中 80% 替换为 [MASK]
- 10% 替换为随机 token
- 10% 保持不变
- 模型预测原始 token
为什么不全部用 [MASK]?因为微调时没有 [MASK] token,需要让模型学会处理正常文本。
任务 2:Next Sentence Prediction (NSP)
- 输入两个句子 A 和 B
- 50% 的情况 B 是 A 的真实下一句
- 50% 的情况 B 是随机句子
- 模型预测 B 是否是 A 的下一句
输入: [CLS] 今天天气真好 [SEP] 我们去公园吧 [SEP]
标签: IsNext(是下一句)
输入: [CLS] 今天天气真好 [SEP] 股票大涨了 [SEP]
标签: NotNext(不是下一句)后来发现 NSP 任务没什么用,RoBERTa 去掉了它,效果反而更好。
3.4 BERT 的架构
3.5 BERT 的代码实现
class BERTEmbedding(nn.Module):
"""BERT 的 Embedding 层"""
def __init__(self, vocab_size, embed_dim, max_len, num_segments=2):
super().__init__()
self.token_embed = nn.Embedding(vocab_size, embed_dim)
self.pos_embed = nn.Embedding(max_len, embed_dim)
self.segment_embed = nn.Embedding(num_segments, embed_dim)
self.ln = nn.LayerNorm(embed_dim)
self.dropout = nn.Dropout(0.1)
def forward(self, input_ids, segment_ids=None):
seq_len = input_ids.size(1)
positions = torch.arange(seq_len, device=input_ids.device)
x = self.token_embed(input_ids) + self.pos_embed(positions)
if segment_ids is not None:
x = x + self.segment_embed(segment_ids)
return self.dropout(self.ln(x))
class BERTBlock(nn.Module):
"""BERT 的 Transformer Block"""
def __init__(self, embed_dim, num_heads, ff_dim, dropout=0.1):
super().__init__()
self.attn = nn.MultiheadAttention(embed_dim, num_heads, dropout=dropout, batch_first=True)
self.ln1 = nn.LayerNorm(embed_dim)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.GELU(),
nn.Linear(ff_dim, embed_dim),
nn.Dropout(dropout)
)
self.ln2 = nn.LayerNorm(embed_dim)
self.dropout = nn.Dropout(dropout)
def forward(self, x, attention_mask=None):
# Post-Norm 架构(BERT 原版)
# Self-Attention(双向,无因果掩码)
attn_out, _ = self.attn(x, x, x, key_padding_mask=attention_mask)
x = self.ln1(x + self.dropout(attn_out))
# Feed Forward
x = self.ln2(x + self.ffn(x))
return x
class BERT(nn.Module):
"""简化版 BERT 模型"""
def __init__(self, vocab_size, embed_dim, num_heads, num_layers, max_len, ff_dim=None):
super().__init__()
ff_dim = ff_dim or embed_dim * 4
self.embedding = BERTEmbedding(vocab_size, embed_dim, max_len)
self.blocks = nn.ModuleList([
BERTBlock(embed_dim, num_heads, ff_dim)
for _ in range(num_layers)
])
# MLM Head
self.mlm_head = nn.Sequential(
nn.Linear(embed_dim, embed_dim),
nn.GELU(),
nn.LayerNorm(embed_dim),
nn.Linear(embed_dim, vocab_size)
)
# NSP Head
self.nsp_head = nn.Linear(embed_dim, 2)
def forward(self, input_ids, segment_ids=None, attention_mask=None):
x = self.embedding(input_ids, segment_ids)
for block in self.blocks:
x = block(x, attention_mask)
return x
def get_mlm_logits(self, hidden_states):
"""获取 MLM 预测的 logits"""
return self.mlm_head(hidden_states)
def get_nsp_logits(self, hidden_states):
"""获取 NSP 预测的 logits(使用 [CLS] token)"""
cls_hidden = hidden_states[:, 0, :]
return self.nsp_head(cls_hidden)
# 训练 BERT
def train_bert_step(model, input_ids, segment_ids, mlm_labels, nsp_labels, optimizer):
"""BERT 的训练步骤"""
# input_ids: (batch, seq_len),包含 [MASK]
# mlm_labels: (batch, seq_len),-100 表示不计算损失
# nsp_labels: (batch,),0 或 1
# 前向传播
hidden = model(input_ids, segment_ids)
# MLM 损失
mlm_logits = model.get_mlm_logits(hidden)
mlm_loss = F.cross_entropy(
mlm_logits.view(-1, mlm_logits.size(-1)),
mlm_labels.view(-1),
ignore_index=-100 # 忽略非 mask 位置
)
# NSP 损失
nsp_logits = model.get_nsp_logits(hidden)
nsp_loss = F.cross_entropy(nsp_logits, nsp_labels)
# 总损失
loss = mlm_loss + nsp_loss
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
# BERT 微调示例:文本分类
class BERTForClassification(nn.Module):
"""BERT 用于文本分类"""
def __init__(self, bert_model, num_classes):
super().__init__()
self.bert = bert_model
self.classifier = nn.Linear(bert_model.embedding.token_embed.embedding_dim, num_classes)
def forward(self, input_ids, segment_ids=None, attention_mask=None):
hidden = self.bert(input_ids, segment_ids, attention_mask)
# 使用 [CLS] token 的表示进行分类
cls_hidden = hidden[:, 0, :]
logits = self.classifier(cls_hidden)
return logits3.6 BERT 的变体
| 模型 | 改进点 |
|---|---|
| RoBERTa | 去掉 NSP,更多数据,动态 mask |
| ALBERT | 参数共享,因式分解 embedding |
| DistilBERT | 知识蒸馏,模型压缩 |
| ELECTRA | 替换检测任务,更高效 |
| DeBERTa | 解耦注意力,相对位置编码 |
四、GPT vs BERT:本质区别
4.1 注意力模式对比
4.2 训练目标对比
| 维度 | GPT (CLM) | BERT (MLM) |
|---|---|---|
| 预测内容 | 下一个词 | 被遮住的词 |
| 上下文 | 只有左边 | 左边 + 右边 |
| 每个 token 的利用 | 每个 token 都预测下一个 | 只有 15% 被 mask 的 token |
| 训练效率 | 高 | 相对低 |
4.3 能力对比
BERT 的优势:
- 双向上下文,理解更准确
- 在 NLU 任务上效果好
- 提取的表示质量高(用于下游任务)
GPT 的优势:
- 天然支持生成
- 可以用同一个模型处理几乎所有任务
- Scaling 效果更好
4.4 关键差异代码演示
def compare_attention_patterns():
"""可视化 GPT 和 BERT 的注意力模式"""
import matplotlib.pyplot as plt
import numpy as np
seq_len = 5
# GPT: 因果注意力(下三角)
gpt_mask = np.tril(np.ones((seq_len, seq_len)))
# BERT: 全注意力
bert_mask = np.ones((seq_len, seq_len))
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].imshow(gpt_mask, cmap='Blues')
axes[0].set_title('GPT: Causal Attention')
axes[0].set_xlabel('Key Position')
axes[0].set_ylabel('Query Position')
axes[1].imshow(bert_mask, cmap='Blues')
axes[1].set_title('BERT: Bidirectional Attention')
axes[1].set_xlabel('Key Position')
axes[1].set_ylabel('Query Position')
plt.tight_layout()
plt.savefig('attention_patterns.png')
print("GPT 只能看到当前位置及之前的 token")
print("BERT 可以看到所有位置的 token")
def compare_training_signals():
"""对比训练信号的利用效率"""
sentence = "今天天气真的很好"
tokens = list(sentence)
print("=" * 50)
print("GPT 的训练信号(每个位置都有监督):")
print("=" * 50)
for i in range(len(tokens)):
context = "".join(tokens[:i]) if i > 0 else "[START]"
target = tokens[i]
print(f"输入: {context:20s} → 预测: {target}")
print("\n" + "=" * 50)
print("BERT 的训练信号(只有 MASK 位置有监督):")
print("=" * 50)
# 假设 mask 15% ≈ 1-2 个 token
masked_positions = [2, 5] # "天" 和 "的"
masked_sentence = tokens.copy()
for pos in masked_positions:
masked_sentence[pos] = "[MASK]"
print(f"输入: {''.join(masked_sentence)}")
for pos in masked_positions:
print(f"预测位置 {pos}: [MASK] → {tokens[pos]}")
print(f"\nGPT 利用率: {len(tokens)}/{len(tokens)} = 100%")
print(f"BERT 利用率: {len(masked_positions)}/{len(tokens)} = {len(masked_positions)/len(tokens)*100:.1f}%")
compare_training_signals()输出:
==================================================
GPT 的训练信号(每个位置都有监督):
==================================================
输入: [START] → 预测: 今
输入: 今 → 预测: 天
输入: 今天 → 预测: 天
输入: 今天天 → 预测: 气
输入: 今天天气 → 预测: 真
输入: 今天天气真 → 预测: 的
输入: 今天天气真的 → 预测: 很
输入: 今天天气真的很 → 预测: 好
==================================================
BERT 的训练信号(只有 MASK 位置有监督):
==================================================
输入: 今天[MASK]气真[MASK]很好
预测位置 2: [MASK] → 天
预测位置 5: [MASK] → 的
GPT 利用率: 8/8 = 100%
BERT 利用率: 2/8 = 25.0%五、为什么 GPT 路线最终胜出?
5.1 生成能力的碾压
BERT 的致命问题:它不会生成。
# BERT: 只能填空
input = "今天天气 [MASK] 好"
output = "今天天气 很 好" # ✓ 可以
# BERT: 不能续写
input = "今天天气"
output = ??? # ✗ 做不到!# GPT: 天然支持生成
input = "今天天气"
output = "今天天气真好,我们去公园玩吧!" # ✓ 轻松在 ChatGPT 出现之前,BERT 在各种 NLU benchmark 上刷榜。
但用户要的不是 benchmark 分数,是 能对话、能写文章、能写代码 的 AI。
BERT:做不到。
GPT:我可以。
5.2 Scaling Law 的差异
研究发现,Decoder-Only 架构的 scaling 更好:
为什么?可能的原因:
- 训练效率:GPT 每个 token 都有监督信号
- 统一性:所有任务都可以转化为生成
- 参数利用:没有分成 encoder/decoder 两部分
5.3 任务统一:Prompt 的魔力
GPT 的杀手锏:用 prompt 把所有任务都转成生成任务。
# 情感分类
输入: "这部电影太棒了!\n情感: "
输出: "正面"
# 命名实体识别
输入: "马云创立了阿里巴巴\n实体: "
输出: "马云(人名), 阿里巴巴(公司)"
# 翻译
输入: "Translate to Chinese: Hello world\n"
输出: "你好世界"
# 问答
输入: "问题: 地球到月球的距离是多少?\n答案: "
输出: "约38万公里"一个模型,处理所有任务。不需要为每个任务微调一个 BERT。
5.4 涌现能力
当 GPT 足够大时,出现了一些神奇的能力:
给几个例子就能学会] CoT[Chain-of-Thought
一步步推理] IF[Instruction Following
理解并遵循指令] end Small[小模型] -.->|完全不会| ICL Large[大模型] -->|突然会了| ICL style Large fill:#4ecdc4
这些能力在 BERT 上几乎看不到,即使把 BERT 做得很大。
5.5 现状:GPT 路线的全面胜利
几乎所有你听过的大模型都是 Decoder-Only:
- GPT-4, GPT-4o (OpenAI)
- Claude 3.5 (Anthropic)
- LLaMA 3 (Meta)
- Gemini (Google)
- Qwen 2.5 (阿里)
- DeepSeek (深度求索)
5.6 BERT 还有用吗?
有!但角色变了。
BERT 现在主要用于:
文本 Embedding
# 用 BERT 提取句子表示,做相似度搜索 from sentence_transformers import SentenceTransformer model = SentenceTransformer('all-MiniLM-L6-v2') # 基于 BERT embeddings = model.encode(["今天天气真好", "天气不错"])分类任务(当你只需要分类,不需要生成时)
# 情感分类、垃圾邮件检测等 from transformers import pipeline classifier = pipeline("sentiment-analysis") result = classifier("I love this product!")信息抽取
# NER、关系抽取 from transformers import pipeline ner = pipeline("ner") result = ner("马云创立了阿里巴巴")Reranker(RAG 中的重排序)
# 用 BERT 判断 query 和 document 的相关性 from sentence_transformers import CrossEncoder model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2') scores = model.predict([("query", "doc1"), ("query", "doc2")])
六、第三条路:Encoder-Decoder
6.1 T5:Text-to-Text 框架
Google 的 T5 走了第三条路:Encoder-Decoder,但把所有任务都转成文本到文本。
# 翻译
输入: "translate English to German: That is good."
输出: "Das ist gut."
# 分类
输入: "sst2 sentence: This movie is great!"
输出: "positive"
# 问答
输入: "question: What is the capital of France? context: France is a country in Europe. Its capital is Paris."
输出: "Paris"6.2 为什么没成为主流?
- 架构复杂:需要 Encoder 和 Decoder 两部分
- Scaling 效率:同等参数量,效果不如 Decoder-Only
- 推理慢:需要先跑完 Encoder,再自回归生成
不过 T5 在一些场景仍有应用:
- 机器翻译
- 摘要生成
- FLAN-T5(指令微调版)
七、实战:使用 GPT 和 BERT
7.1 使用 HuggingFace Transformers
from transformers import (
GPT2LMHeadModel, GPT2Tokenizer,
BertForMaskedLM, BertTokenizer,
BertForSequenceClassification
)
# ============ GPT-2 文本生成 ============
def gpt2_generation():
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
model = GPT2LMHeadModel.from_pretrained('gpt2')
prompt = "The future of AI is"
inputs = tokenizer(prompt, return_tensors='pt')
outputs = model.generate(
inputs['input_ids'],
max_length=50,
num_return_sequences=1,
temperature=0.7,
do_sample=True
)
print("GPT-2 生成:")
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
# ============ BERT 完形填空 ============
def bert_fill_mask():
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForMaskedLM.from_pretrained('bert-base-uncased')
text = "The capital of France is [MASK]."
inputs = tokenizer(text, return_tensors='pt')
with torch.no_grad():
outputs = model(**inputs)
mask_idx = (inputs['input_ids'] == tokenizer.mask_token_id).nonzero(as_tuple=True)[1]
logits = outputs.logits[0, mask_idx, :]
top_tokens = logits.topk(5).indices[0]
print("BERT 填空预测:")
for token_id in top_tokens:
print(f" {tokenizer.decode([token_id])}")
# ============ BERT 文本分类 ============
def bert_classification():
from transformers import pipeline
classifier = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
results = classifier([
"I absolutely loved this movie!",
"This was a waste of my time.",
"It was okay, nothing special."
])
print("BERT 情感分类:")
for r in results:
print(f" {r['label']}: {r['score']:.3f}")
# ============ BERT Embedding ============
def bert_embedding():
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer('all-MiniLM-L6-v2')
sentences = [
"今天天气真好",
"天气不错,阳光明媚",
"我喜欢吃火锅",
"今天适合出去玩"
]
embeddings = model.encode(sentences)
print("BERT Embedding 相似度:")
sim_matrix = cosine_similarity(embeddings)
for i, s1 in enumerate(sentences):
for j, s2 in enumerate(sentences):
if i < j:
print(f" '{s1}' vs '{s2}': {sim_matrix[i][j]:.3f}")
if __name__ == "__main__":
print("=" * 50)
gpt2_generation()
print("\n" + "=" * 50)
bert_fill_mask()
print("\n" + "=" * 50)
bert_classification()
print("\n" + "=" * 50)
bert_embedding()7.2 使用现代大模型 API
# 现代 GPT 类模型的使用
from openai import OpenAI
from anthropic import Anthropic
# OpenAI GPT-4
def use_gpt4():
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain the difference between GPT and BERT."}
]
)
print(response.choices[0].message.content)
# Anthropic Claude
def use_claude():
client = Anthropic()
response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1024,
messages=[
{"role": "user", "content": "Explain the difference between GPT and BERT."}
]
)
print(response.content[0].text)八、总结
核心对比表
| 维度 | GPT (Decoder-Only) | BERT (Encoder-Only) |
|---|---|---|
| 注意力 | 因果(只看前面) | 双向(全部可见) |
| 预训练任务 | 预测下一个词 | 预测被遮住的词 |
| 训练效率 | 每个 token 都用 | 只有 15% token |
| 天然能力 | 生成 | 理解 |
| Scaling | 效果好 | 效果有限 |
| 现状 | 主流大模型 | Embedding、分类 |
架构选择决策树
GPT/LLaMA/Claude] Q2 -->|需要深度理解| A2[Encoder-Decoder
T5/BART] Q3 -->|是| A3[Encoder-Only
BERT/RoBERTa] Q3 -->|否| A4[取决于具体任务] style A1 fill:#4ecdc4 style A3 fill:#ff6b6b
关键 Takeaway
- GPT 和 BERT 都基于 Transformer,但选择了不同的预训练任务和架构
- GPT 用因果注意力,BERT 用双向注意力,这决定了它们的能力边界
- GPT 天然支持生成,BERT 只能理解,这是 GPT 胜出的关键
- Scaling Law 偏爱 Decoder-Only,这让 GPT 路线越走越远
- BERT 还有用,主要用于 Embedding 和分类任务
- 现代大模型清一色 Decoder-Only,这是经过验证的最优选择
下一步学习
- [ ] Tokenization:大模型的"碎碎念"
- [ ] 预训练:如何"喂"出一个大模型
- [ ] SFT 与 RLHF:让模型学会"听话"
参考资料
- GPT-1 Paper - Improving Language Understanding by Generative Pre-Training
- BERT Paper - BERT: Pre-training of Deep Bidirectional Transformers
- GPT-2 Paper - Language Models are Unsupervised Multitask Learners
- GPT-3 Paper - Language Models are Few-Shot Learners
- T5 Paper - Exploring the Limits of Transfer Learning