NLP基础与预训练语言模型¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
学习目标:理解NLP的发展脉络、文本表示演进、以及预训练语言模型三大架构(Encoder-only、Decoder-only、Encoder-Decoder),为深入学习大语言模型打下坚实基础。
📌 定位说明:本章覆盖从传统NLP到预训练语言模型(PLM)的完整知识体系。对标 happy-llm Ch1(NLP基础概念)和 Ch3(预训练语言模型),我们的内容更系统、代码更完整、对比更深入。
目录¶
- 1. NLP发展全景
- 2. 文本表示:从One-hot到Contextual
- 3. 经典NLP任务与方法
- 4. 预训练语言模型的诞生
- 5. Encoder-only架构:BERT系列
- 6. Decoder-only架构:GPT系列
- 7. Encoder-Decoder架构:T5/BART
- 8. 三种架构深度对比
- 9. 从PLM到LLM:范式转变
- 10. 代码实战:PLM三架构对比实验
1. NLP发展全景¶
1.1 NLP发展的四个阶段¶
时间线:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
~2013 2013-2018 2018-2022 2022~
规则与统计时代 词向量时代 预训练模型时代 大模型时代
专家系统 Word2Vec BERT GPT-4
HMM/CRF GloVe GPT-2 LLaMA
n-gram LM ELMo T5/BART Claude
SVM分类 Attention XLNet Gemini
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第一阶段:规则与统计方法(~2013)
- 特点:人工设计规则和特征,统计模型学习参数
- 代表方法:
- 正则表达式、语法规则
- 隐马尔可夫模型(HMM)用于词性标注、命名实体识别
- 条件随机场(CRF)用于序列标注
- n-gram语言模型
- TF-IDF + SVM/朴素贝叶斯用于文本分类
- 局限:依赖人工特征工程,泛化能力差
第二阶段:词向量与深度学习(2013-2018)
- 特点:自动从数据中学习特征表示
- 代表方法:
- Word2Vec(2013):分布式词向量
- GloVe(2014):全局词向量
- TextCNN(2014):CNN用于文本分类
- Seq2Seq + Attention(2015-2017)
- ELMo(2018):上下文相关词向量
第三阶段:预训练语言模型(2018-2022)
- 特点:大规模预训练 + 微调(Pre-train & Fine-tune)
- 代表模型:BERT、GPT-2、T5、BART、RoBERTa
- 核心范式:先在大量无标注文本上预训练,再在下游任务上微调
第四阶段:大语言模型(2022~)
- 特点:规模法则(Scaling Laws)、涌现能力(Emergent Abilities)
- 代表模型:GPT-4、Claude、LLaMA、Gemini
- 核心范式:预训练 → 指令微调 → RLHF → 提示工程
1.2 NLP任务分类体系¶
NLP任务全景图
├── 文本分类
│ ├── 情感分析 (正面/负面/中性)
│ ├── 主题分类 (新闻类别)
│ ├── 意图识别 (对话系统)
│ └── 垃圾邮件检测
├── 序列标注
│ ├── 命名实体识别 NER (人名/地名/机构名)
│ ├── 词性标注 POS Tagging
│ └── 中文分词
├── 文本生成
│ ├── 机器翻译
│ ├── 文本摘要 (抽取式/生成式)
│ ├── 对话生成
│ └── 文本续写
├── 信息抽取
│ ├── 关系抽取
│ ├── 事件抽取
│ └── 三元组抽取 (主/谓/宾)
├── 文本匹配
│ ├── 语义相似度
│ ├── 自然语言推理 NLI
│ └── 问答匹配
└── 问答系统
├── 抽取式问答 (SQuAD)
├── 生成式问答
└── 知识库问答 KBQA
2. 文本表示:从One-hot到Contextual¶
文本表示是NLP的基石——如何把人类语言转化为计算机可处理的数值向量。
2.1 One-hot编码¶
最简单的文本表示:每个词用一个只有一个1其余全0的向量表示。
import numpy as np
# 假设词表: ["猫", "狗", "鱼", "鸟"]
vocab = {"猫": 0, "狗": 1, "鱼": 2, "鸟": 3}
vocab_size = len(vocab)
def one_hot_encode(word, vocab, vocab_size):
"""One-hot编码"""
vec = np.zeros(vocab_size)
if word in vocab:
vec[vocab[word]] = 1.0
return vec
# 编码示例
cat_vec = one_hot_encode("猫", vocab, vocab_size)
dog_vec = one_hot_encode("狗", vocab, vocab_size)
print(f"猫: {cat_vec}") # [1. 0. 0. 0.]
print(f"狗: {dog_vec}") # [0. 1. 0. 0.]
# 问题:余弦相似度为0——"猫"和"狗"毫无关系?
cos_sim = np.dot(cat_vec, dog_vec) / (np.linalg.norm(cat_vec) * np.linalg.norm(dog_vec))
print(f"猫-狗 余弦相似度: {cos_sim}") # 0.0
One-hot的致命缺陷: 1. 维度灾难:词表大小=向量维度,中文词表可达50万+ 2. 语义缺失:任意两个不同词的相似度都是0 3. 无法泛化:见过"猫很可爱"无法推广到"狗很可爱"
2.2 词袋模型与TF-IDF¶
from collections import Counter
import math
class TFIDF:
"""TF-IDF文本表示"""
def __init__(self):
self.vocab = {}
self.idf = {}
def fit(self, documents):
"""构建词表和计算IDF"""
# 构建词表
all_words = set()
for doc in documents:
all_words.update(doc.split())
self.vocab = {word: i for i, word in enumerate(sorted(all_words))}
# 计算IDF
N = len(documents)
for word in self.vocab:
# 包含该词的文档数
df = sum(1 for doc in documents if word in doc.split())
self.idf[word] = math.log(N / (1 + df)) + 1 # 平滑IDF
def transform(self, document):
"""将文档转换为TF-IDF向量"""
words = document.split()
tf = Counter(words) # Counter统计元素出现次数
total = len(words)
vec = np.zeros(len(self.vocab))
for word, count in tf.items():
if word in self.vocab:
tf_val = count / total
vec[self.vocab[word]] = tf_val * self.idf.get(word, 1.0)
return vec
# 示例
docs = [
"猫 喜欢 吃 鱼",
"狗 喜欢 吃 骨头",
"猫 和 狗 都是 宠物"
]
tfidf = TFIDF()
tfidf.fit(docs)
for doc in docs:
vec = tfidf.transform(doc)
print(f"'{doc}' → 非零维度: {np.count_nonzero(vec)}, 向量模长: {np.linalg.norm(vec):.4f}")
TF-IDF的优势与局限: - ✅ 考虑了词频(TF)和文档频率(IDF) - ✅ 高频停用词("的"、"了")权重被压低 - ❌ 仍然是稀疏向量,无法捕捉语义 - ❌ 忽略词序:"猫追狗"和"狗追猫"表示相同
2.3 分布式词向量:Word2Vec¶
核心思想(Distributional Hypothesis):一个词的含义由它的上下文决定。
"You shall know a word by the company it keeps." — J.R. Firth, 1957
import torch
import torch.nn as nn
import torch.optim as optim
class SkipGram(nn.Module):
"""
Word2Vec Skip-Gram模型
给定中心词,预测上下文词
"""
def __init__(self, vocab_size, embedding_dim):
super().__init__() # super()调用父类方法
# 中心词嵌入矩阵
self.center_embeddings = nn.Embedding(vocab_size, embedding_dim)
# 上下文词嵌入矩阵
self.context_embeddings = nn.Embedding(vocab_size, embedding_dim)
# 初始化
nn.init.xavier_uniform_(self.center_embeddings.weight)
nn.init.xavier_uniform_(self.context_embeddings.weight)
def forward(self, center_ids, context_ids, negative_ids):
"""
Negative Sampling训练
Args:
center_ids: [batch_size] 中心词ID
context_ids: [batch_size] 正样本上下文词ID
negative_ids: [batch_size, num_neg] 负样本ID
"""
# 获取嵌入向量
center = self.center_embeddings(center_ids) # [B, D]
context = self.context_embeddings(context_ids) # [B, D]
negatives = self.context_embeddings(negative_ids) # [B, num_neg, D]
# 正样本得分: center · context
pos_score = torch.sum(center * context, dim=-1) # [B]
pos_loss = -torch.log(torch.sigmoid(pos_score) + 1e-8)
# 负样本得分: center · negative
neg_score = torch.bmm(negatives, center.unsqueeze(-1)).squeeze(-1) # [B, num_neg] # unsqueeze增加一个维度
neg_loss = -torch.log(torch.sigmoid(-neg_score) + 1e-8).sum(dim=-1)
return (pos_loss + neg_loss).mean()
def get_embedding(self, word_id):
"""获取词的最终向量(使用中心词嵌入)"""
return self.center_embeddings(torch.tensor([word_id])).detach()
# --- 训练示例 ---
# 构建简单语料
corpus = "the cat sat on the mat the dog sat on the rug".split()
vocab = {w: i for i, w in enumerate(set(corpus))}
vocab_size = len(vocab)
embedding_dim = 32
model = SkipGram(vocab_size, embedding_dim)
optimizer = optim.Adam(model.parameters(), lr=0.01)
print(f"词表大小: {vocab_size}, 嵌入维度: {embedding_dim}")
print(f"词表: {vocab}")
# 生成训练样本(简化版,window_size=2)
window_size = 2
training_pairs = []
for i, word in enumerate(corpus): # enumerate同时获取索引和元素
center_id = vocab[word]
for j in range(max(0, i - window_size), min(len(corpus), i + window_size + 1)):
if i != j:
context_id = vocab[corpus[j]]
# 随机负采样
neg_ids = []
while len(neg_ids) < 5:
neg = torch.randint(0, vocab_size, (1,)).item()
if neg != context_id:
neg_ids.append(neg)
training_pairs.append((center_id, context_id, neg_ids))
print(f"训练样本数: {len(training_pairs)}")
# 训练
for epoch in range(100):
total_loss = 0
for center, context, negs in training_pairs:
loss = model(
torch.tensor([center]),
torch.tensor([context]),
torch.tensor([negs])
)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1}, Loss: {total_loss/len(training_pairs):.4f}")
# 词向量相似度
def cosine_similarity(v1, v2):
return torch.cosine_similarity(v1, v2, dim=-1).item()
cat_emb = model.get_embedding(vocab["cat"])
dog_emb = model.get_embedding(vocab["dog"])
mat_emb = model.get_embedding(vocab["mat"])
print(f"\n词向量相似度:")
print(f" cat-dog: {cosine_similarity(cat_emb, dog_emb):.4f}")
print(f" cat-mat: {cosine_similarity(cat_emb, mat_emb):.4f}")
Word2Vec的核心贡献: - 将词从稀疏的one-hot映射到低维稠密向量空间 - 语义相似的词在向量空间中距离接近 - 著名的类比推理:\(\vec{king} - \vec{man} + \vec{woman} \approx \vec{queen}\)
2.4 从静态到动态:ELMo¶
Word2Vec的局限:一个词只有一个固定向量,无法处理一词多义。 - "Apple推出了新iPhone" → Apple表示苹果公司 - "I ate an apple" → apple表示水果
ELMo(Embeddings from Language Models, 2018) 的解决方案:
ELMo架构:
最终表示 = γ(s₀·h₀ + s₁·h₁ + s₂·h₂)
↑
┌────────────┼────────────┐
h₀(词嵌入) h₁(前向LSTM层1 h₂(前向LSTM层2
+反向LSTM层1) +反向LSTM层2)
↑ ↑ ↑
字符CNN 双向LSTM层1 双向LSTM层2
↑ ↑ ↑
输入词 隐藏状态传递 隐藏状态传递
ELMo的关键创新: 1. 上下文相关:同一个词在不同句子中有不同向量 2. 多层表示:底层捕捉语法信息,高层捕捉语义信息 3. 双向建模:同时考虑左侧和右侧上下文
# ELMo的核心思想示意(简化版)
class SimpleBiLM(nn.Module):
"""简化版双向语言模型(ELMo核心思想)"""
def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 前向LSTM
self.forward_lstm = nn.LSTM(
embed_dim, hidden_dim, num_layers,
batch_first=True, dropout=0.1
)
# 反向LSTM
self.backward_lstm = nn.LSTM(
embed_dim, hidden_dim, num_layers,
batch_first=True, dropout=0.1
)
# 层权重(可学习的标量混合参数)
self.layer_weights = nn.Parameter(torch.ones(num_layers + 1) / (num_layers + 1))
self.gamma = nn.Parameter(torch.ones(1))
def forward(self, input_ids):
"""
Args:
input_ids: [batch_size, seq_len]
Returns:
contextualized: [batch_size, seq_len, hidden_dim * 2]
"""
embeds = self.embedding(input_ids) # [B, L, E]
# 前向
fwd_out, _ = self.forward_lstm(embeds) # [B, L, H]
# 反向(翻转序列)
bwd_out, _ = self.backward_lstm(embeds.flip(1))
bwd_out = bwd_out.flip(1) # 翻回来 [B, L, H]
# 拼接双向表示
contextualized = torch.cat([fwd_out, bwd_out], dim=-1) # [B, L, 2H]
return contextualized
# ELMo提供了上下文相关的词表示
# 同一个 "bank" 在 "river bank" 和 "bank account" 中有不同向量
2.5 文本表示演进总结¶
文本表示方法演进对比
┌──────────────────┬──────────────┬──────────────┬──────────────┐
│ 方法 │ 表示类型 │ 语义能力 │ 上下文感知 │
├──────────────────┼──────────────┼──────────────┼──────────────┤
│ One-hot │ 稀疏,高维 │ ✗ 无语义 │ ✗ 无 │
│ TF-IDF │ 稀疏,高维 │ △ 词频统计 │ ✗ 无 │
│ Word2Vec/GloVe │ 稠密,低维 │ ✓ 分布式语义 │ ✗ 静态 │
│ ELMo │ 稠密,动态 │ ✓ 深层语义 │ ✓ 上下文相关 │
│ BERT/GPT │ 稠密,动态 │ ✓✓ 深层语义 │ ✓✓ 全上下文 │
└──────────────────┴──────────────┴──────────────┴──────────────┘
3. 经典NLP任务与方法¶
3.1 文本分类¶
import torch
import torch.nn as nn
class TextCNN(nn.Module):
"""
TextCNN (Kim, 2014) — 经典文本分类模型
用不同大小的卷积核捕捉n-gram特征
"""
def __init__(self, vocab_size, embed_dim, num_classes,
filter_sizes=(3, 4, 5), num_filters=100):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 多尺度卷积
self.convs = nn.ModuleList([
nn.Conv1d(embed_dim, num_filters, fs)
for fs in filter_sizes
])
self.dropout = nn.Dropout(0.5)
self.fc = nn.Linear(num_filters * len(filter_sizes), num_classes)
def forward(self, x):
"""
Args: x [batch_size, seq_len]
Returns: logits [batch_size, num_classes]
"""
# [B, L, E] → [B, E, L] (Conv1d需要 channels-first)
embedded = self.embedding(x).transpose(1, 2)
# 多尺度卷积 + ReLU + 最大池化
pooled = []
for conv in self.convs:
h = torch.relu(conv(embedded)) # [B, num_filters, L-fs+1]
h = torch.max(h, dim=-1).values # [B, num_filters]
pooled.append(h)
# 拼接所有尺度的特征
cat = torch.cat(pooled, dim=-1) # [B, num_filters * len(filter_sizes)]
return self.fc(self.dropout(cat))
# 使用示例
model = TextCNN(vocab_size=10000, embed_dim=128, num_classes=4)
dummy_input = torch.randint(0, 10000, (8, 50)) # batch=8, seq_len=50
output = model(dummy_input)
print(f"TextCNN输出形状: {output.shape}") # [8, 4]
3.2 序列标注:命名实体识别¶
class BiLSTM_CRF(nn.Module):
"""
BiLSTM-CRF — 经典序列标注模型
BiLSTM提取特征,CRF建模标签间的转移约束
"""
def __init__(self, vocab_size, embed_dim, hidden_dim, num_tags):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(
embed_dim, hidden_dim // 2,
bidirectional=True, batch_first=True
)
self.hidden2tag = nn.Linear(hidden_dim, num_tags)
# CRF转移矩阵: transitions[i][j] = 从标签j转移到标签i的分数
self.transitions = nn.Parameter(torch.randn(num_tags, num_tags))
self.num_tags = num_tags
def _get_emissions(self, x):
"""获取发射分数"""
embeds = self.embedding(x)
lstm_out, _ = self.lstm(embeds)
return self.hidden2tag(lstm_out) # [B, L, num_tags]
def forward(self, x):
"""推理时使用维特比解码"""
emissions = self._get_emissions(x) # [B, L, T]
# 简化版:逐batch维特比解码
batch_size, seq_len, num_tags = emissions.shape
best_paths = []
for b in range(batch_size):
# 维特比算法
viterbi = emissions[b, 0] # [T]
backpointers = []
for t in range(1, seq_len):
# viterbi_expand: [T_prev] + transitions: [T_cur, T_prev] → [T_cur, T_prev]
scores = viterbi.unsqueeze(0) + self.transitions # [T, T]
scores = scores + emissions[b, t].unsqueeze(1) # [T, T]
best_scores, best_ids = scores.max(dim=1) # [T]
viterbi = best_scores
backpointers.append(best_ids)
# 回溯
best_last = viterbi.argmax().item()
best_path = [best_last]
for bp in reversed(backpointers):
best_path.append(bp[best_path[-1]].item()) # [-1]负索引取最后一个元素
best_path.reverse()
best_paths.append(best_path)
return best_paths
# NER标签示例: BIO标注
tag2id = {"O": 0, "B-PER": 1, "I-PER": 2, "B-ORG": 3, "I-ORG": 4, "B-LOC": 5, "I-LOC": 6}
model = BiLSTM_CRF(vocab_size=5000, embed_dim=128, hidden_dim=256, num_tags=len(tag2id))
dummy_input = torch.randint(0, 5000, (4, 20))
predictions = model(dummy_input)
print(f"BiLSTM-CRF预测: 4个序列, 各{len(predictions[0])}个标签")
3.3 Seq2Seq与注意力机制¶
class Seq2SeqWithAttention(nn.Module):
"""
带注意力的Seq2Seq(Bahdanau Attention)
经典的编码器-解码器架构,为Transformer奠定基础
"""
def __init__(self, src_vocab, tgt_vocab, embed_dim, hidden_dim):
super().__init__()
# 编码器
self.src_embedding = nn.Embedding(src_vocab, embed_dim)
self.encoder = nn.GRU(embed_dim, hidden_dim, bidirectional=True, batch_first=True)
# 解码器
self.tgt_embedding = nn.Embedding(tgt_vocab, embed_dim)
self.decoder = nn.GRU(embed_dim + hidden_dim * 2, hidden_dim, batch_first=True)
# 注意力
self.attn = nn.Linear(hidden_dim * 3, hidden_dim)
self.v = nn.Linear(hidden_dim, 1, bias=False)
# 输出
self.output = nn.Linear(hidden_dim, tgt_vocab)
def attention(self, decoder_hidden, encoder_outputs):
"""
Bahdanau (Additive) Attention
Args:
decoder_hidden: [B, 1, H]
encoder_outputs: [B, src_len, 2H]
Returns:
context: [B, 1, 2H]
"""
src_len = encoder_outputs.shape[1]
hidden_expanded = decoder_hidden.repeat(1, src_len, 1) # [B, src_len, H]
# 拼接 + 打分
energy = torch.tanh(self.attn(
torch.cat([hidden_expanded, encoder_outputs], dim=-1) # [B, src_len, 3H]
))
scores = self.v(energy).squeeze(-1) # [B, src_len]
weights = torch.softmax(scores, dim=-1) # [B, src_len]
context = torch.bmm(weights.unsqueeze(1), encoder_outputs) # [B, 1, 2H]
return context, weights
print("Seq2Seq with Attention: 注意力机制是Transformer的前身")
print("关键洞察: 让解码器在每一步'看'编码器的不同位置")
4. 预训练语言模型的诞生¶
4.1 预训练-微调范式¶
预训练-微调(Pre-train & Fine-tune)范式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
阶段1: 预训练(无监督/自监督)
┌──────────────────────────────────┐
│ 大规模无标注文本(数十GB~TB) │
│ ↓ │
│ 自监督目标(MLM / CLM / DAE) │
│ ↓ │
│ 学习通用语言知识 │
│ → 语法、语义、世界知识、推理 │
└──────────────────────────────────┘
阶段2: 微调(有监督)
┌──────────────────────────────────┐
│ 少量标注数据(数千~数万条) │
│ ↓ │
│ 特定任务目标(分类/NER/QA...) │
│ ↓ │
│ 适配下游任务 │
└──────────────────────────────────┘
4.2 三种预训练目标¶
| 预训练目标 | 全称 | 代表模型 | 示意 |
|---|---|---|---|
| MLM | Masked Language Model | BERT | [CLS] 我 [MASK] 北京 [SEP] → 预测 "在" |
| CLM | Causal Language Model | GPT | 我 在 北京 → 预测 "的" |
| DAE | Denoising AutoEncoder | T5 | 输入加噪 → 恢复原文 |
# 三种预训练目标的直观对比
print("""
┌─────────────────────────────────────────────────────────┐
│ MLM (BERT): 完形填空 │
│ 输入: "小明在[MASK]读大学, 他的专业是[MASK]科学" │
│ 目标: 预测被遮住的词 → "北京", "计算机" │
│ 特点: 双向上下文, 15%的词被随机遮盖 │
├─────────────────────────────────────────────────────────┤
│ CLM (GPT): 从左到右续写 │
│ 输入: "小明在北京读大学" │
│ 目标: P(在|小明) × P(北京|小明,在) × P(读|小明,在,北京)│
│ 特点: 单向(从左到右), 自回归生成 │
├─────────────────────────────────────────────────────────┤
│ DAE (T5): 去噪重建 │
│ 输入: "小明在<X>读大学, 他的专业是<Y>" │
│ 目标: "<X> 北京 <Y> 计算机科学" │
│ 特点: Encoder看全文, Decoder生成被替换的片段 │
└─────────────────────────────────────────────────────────┘
""")
5. Encoder-only架构:BERT系列¶
5.1 BERT核心设计¶
BERT (Bidirectional Encoder Representations from Transformers)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
输入: [CLS] 我 爱 [MASK] 京 [SEP] 天气 真 好 [SEP]
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
Token: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9
+
Segment: SA SA SA SA SA SA SB SB SB SB
+
Position: P0 P1 P2 P3 P4 P5 P6 P7 P8 P9
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
┌──────────────────────────────────────────────────┐
│ Transformer Encoder × 12/24 │
│ (双向Self-Attention, 每个位置 │
│ 都能看到所有其他位置) │
└──────────────────────────────────────────────────┘
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
输出: T0 T1 T2 T3 T4 T5 T6 T7 T8 T9
↓ ↓
[CLS]用于 [MASK]位置
句子级任务 用于MLM预测
5.2 BERT微调实战¶
import torch
import torch.nn as nn
class BERTForClassification(nn.Module):
"""
BERT用于文本分类(微调范式的典型用法)
"""
def __init__(self, hidden_size=768, num_classes=2, dropout=0.1):
super().__init__()
# 实际使用时这里加载预训练的BERT
# self.bert = BertModel.from_pretrained('bert-base-chinese')
# 简化版:模拟BERT encoder
encoder_layer = nn.TransformerEncoderLayer(
d_model=hidden_size, nhead=12,
dim_feedforward=3072, dropout=dropout,
batch_first=True
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=12)
self.embedding = nn.Embedding(21128, hidden_size) # BERT中文词表大小
self.pos_embedding = nn.Embedding(512, hidden_size)
# 分类头
self.classifier = nn.Sequential(
nn.Dropout(dropout),
nn.Linear(hidden_size, hidden_size),
nn.Tanh(),
nn.Dropout(dropout),
nn.Linear(hidden_size, num_classes)
)
def forward(self, input_ids, attention_mask=None):
"""
Args:
input_ids: [B, L] token IDs
attention_mask: [B, L] 1=有效token, 0=padding
"""
B, L = input_ids.shape
positions = torch.arange(L, device=input_ids.device).unsqueeze(0).expand(B, L)
# Token + Position Embedding
x = self.embedding(input_ids) + self.pos_embedding(positions)
# Transformer Encoder
if attention_mask is not None:
# 将 0/1 mask 转换为 True/False(True表示被遮蔽)
src_key_padding_mask = (attention_mask == 0)
else:
src_key_padding_mask = None
encoded = self.encoder(x, src_key_padding_mask=src_key_padding_mask)
# 取 [CLS] 位置的输出做分类
cls_output = encoded[:, 0, :] # [B, H]
return self.classifier(cls_output)
# 使用示例
model = BERTForClassification(hidden_size=768, num_classes=3)
dummy_ids = torch.randint(0, 21128, (4, 128))
dummy_mask = torch.ones(4, 128)
logits = model(dummy_ids, dummy_mask)
print(f"BERT分类输出: {logits.shape}") # [4, 3]
5.3 BERT系列演进¶
| 模型 | 改进点 | 关键思想 |
|---|---|---|
| BERT (2018) | 基础版 | MLM + NSP, 双向编码 |
| RoBERTa (2019) | 训练优化 | 去掉NSP,动态Masking,更多数据/更长训练 |
| ALBERT (2019) | 参数效率 | 因式分解嵌入,跨层参数共享 |
| DistilBERT (2019) | 知识蒸馏 | 6层蒸馏12层,速度2x,性能保留97% |
| ELECTRA (2020) | 训练效率 | 替换Token检测(RTD),比MLM效率更高 |
| DeBERTa (2021) | 注意力增强 | 解耦注意力(内容+位置分开计算) |
6. Decoder-only架构:GPT系列¶
6.1 GPT核心设计¶
GPT (Generative Pre-trained Transformer)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
输入: 今天 天气 → 真 → 好
↓ ↓ ↓ ↓
┌──────────────────────────────┐
│ Transformer Decoder × N │
│ (因果自注意力: │
│ 每个位置只能看到 │
│ 自己及之前的位置) │
│ │
│ 今天 → 能看到: [今天] │
│ 天气 → 能看到: [今天,天气] │
│ 真 → 能看到: [今天,..,真]│
└──────────────────────────────┘
↓ ↓ ↓ ↓
预测: 天气 真 好 [EOS]
P(天气|今天) × P(真|今天,天气) × P(好|今天,天气,真)×...
GPT与BERT的根本区别: - BERT是理解模型:双向编码,擅长分类/抽取 - GPT是生成模型:自回归,擅长文本生成
6.2 GPT系列演进¶
# GPT系列参数规模演进
gpt_evolution = {
"GPT-1 (2018)": {"参数": "117M", "数据": "5GB", "关键创新": "预训练+微调范式"},
"GPT-2 (2019)": {"参数": "1.5B", "数据": "40GB", "关键创新": "Zero-shot多任务"},
"GPT-3 (2020)": {"参数": "175B", "数据": "570GB", "关键创新": "In-context Learning"},
"GPT-3.5 (2022)": {"参数": "~175B", "数据": "更大", "关键创新": "RLHF/InstructGPT"},
"GPT-4 (2023)": {"参数": "未公开", "数据": "未公开", "关键创新": "多模态/MoE传闻"},
}
print("GPT系列关键里程碑:")
print("=" * 60)
for name, info in gpt_evolution.items():
print(f"{name}:")
print(f" 参数量: {info['参数']}")
print(f" 训练数据: {info['数据']}")
print(f" 关键创新: {info['关键创新']}")
print()
print("核心洞察:")
print("GPT-1→2:规模增大 → 零样本能力出现")
print("GPT-2→3:10x规模 → In-context Learning涌现")
print("GPT-3→3.5:RLHF对齐 → 真正可用的AI助手")
print("GPT-3.5→4:多模态 + 推理能力飞跃")
6.3 In-context Learning:GPT-3的涌现能力¶
# In-context Learning示例:不需要微调,直接在prompt中提供示例
# Zero-shot(零样本)
zero_shot_prompt = """
请将以下英文翻译成中文:
The weather is beautiful today.
"""
# One-shot(单样本)
one_shot_prompt = """
英文翻译成中文示例:
English: Hello, how are you?
中文: 你好,你怎么样?
请翻译:
English: The weather is beautiful today.
中文:
"""
# Few-shot(少样本)
few_shot_prompt = """
将英文情感分类为正面或负面:
"This movie is amazing!" → 正面
"I hated every minute of it." → 负面
"The food was delicious and service was great." → 正面
"The product broke after one day." →
"""
print("In-context Learning的三种模式:")
print(" Zero-shot: 不给示例,直接问")
print(" One-shot: 给1个示例")
print(" Few-shot: 给2-5个示例")
print()
print("关键洞察:GPT-3不需要更新参数就能适应新任务!")
print("这是'涌现能力'的典型体现。")
7. Encoder-Decoder架构:T5/BART¶
7.1 T5:统一文本到文本框架¶
T5 (Text-to-Text Transfer Transformer)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
核心思想:将所有NLP任务统一为 文本→文本 格式
分类: "classify: I love this movie" → "positive"
翻译: "translate English to Chinese: Hello" → "你好"
摘要: "summarize: <长文本>" → "<短摘要>"
问答: "question: What is AI? context: <上下文>" → "AI is..."
NER: "tag: 小明在北京上学" → "小明:PER 北京:LOC"
# T5的统一框架思想
class T5Conceptual(nn.Module):
"""
T5概念性实现:展示Encoder-Decoder统一框架
"""
def __init__(self, vocab_size, d_model, nhead, num_layers):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_embedding = nn.Embedding(512, d_model)
# 编码器
encoder_layer = nn.TransformerEncoderLayer(
d_model, nhead, d_model * 4, batch_first=True
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers)
# 解码器
decoder_layer = nn.TransformerDecoderLayer(
d_model, nhead, d_model * 4, batch_first=True
)
self.decoder = nn.TransformerDecoder(decoder_layer, num_layers)
# 输出投影
self.output_proj = nn.Linear(d_model, vocab_size)
def forward(self, src_ids, tgt_ids):
"""
Encoder-Decoder前向传播
src_ids: 编码器输入 [B, src_len]
tgt_ids: 解码器输入 [B, tgt_len]
"""
B, src_len = src_ids.shape
_, tgt_len = tgt_ids.shape
# 嵌入
src_pos = torch.arange(src_len, device=src_ids.device).unsqueeze(0)
tgt_pos = torch.arange(tgt_len, device=tgt_ids.device).unsqueeze(0)
src = self.embedding(src_ids) + self.pos_embedding(src_pos)
tgt = self.embedding(tgt_ids) + self.pos_embedding(tgt_pos)
# 编码
memory = self.encoder(src) # [B, src_len, D]
# 因果mask(解码器只能看到之前的token)
causal_mask = nn.Transformer.generate_square_subsequent_mask(tgt_len)
causal_mask = causal_mask.to(src_ids.device)
# 解码
decoded = self.decoder(tgt, memory, tgt_mask=causal_mask) # [B, tgt_len, D]
return self.output_proj(decoded) # [B, tgt_len, vocab_size]
model = T5Conceptual(vocab_size=32000, d_model=512, nhead=8, num_layers=6)
src = torch.randint(0, 32000, (2, 30))
tgt = torch.randint(0, 32000, (2, 20))
output = model(src, tgt)
print(f"T5输出形状: {output.shape}") # [2, 20, 32000]
7.2 BART:去噪自编码器¶
BART预训练:对输入加噪,让模型恢复原文
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5种噪声策略:
┌─────────────────────────────────────┐
│ 1. Token Masking : A B _ D E │
│ 2. Token Deletion : A B D E │
│ 3. Text Infilling : A _ D E (用1个_ │
│ 替换连续多个) │
│ 4. Sentence Permutation: 打乱句子序 │
│ 5. Document Rotation: 旋转文档起点 │
└─────────────────────────────────────┘
原文: A B C D E
BART = BERT的双向编码 + GPT的自回归解码
→ 兼具理解和生成能力
8. 三种架构深度对比¶
8.1 架构对比表¶
comparison = """
┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ │ Encoder-only │ Decoder-only │ Encoder-Decoder │
│ │ (BERT系列) │ (GPT系列) │ (T5/BART) │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 注意力方向 │ 双向(全可见) │ 单向(因果mask) │ 编码器双向 │
│ │ │ │ 解码器单向+交叉 │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 预训练目标 │ MLM(完形填空) │ CLM(下一词预测) │ DAE(去噪重建) │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 输入 │ 完整文本 │ 前缀/提示 │ 源文本 │
│ 输出 │ 每位置的表示 │ 自回归生成 │ 目标文本 │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 擅长任务 │ 分类/NER/匹配 │ 文本生成/对话 │ 翻译/摘要/QA │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 微调方式 │ 加分类头微调 │ 自回归微调 │ Seq2Seq微调 │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 代表模型 │ BERT, RoBERTa │ GPT-4, LLaMA │ T5, BART, mT5 │
│ │ DeBERTa, ELECTRA │ Claude, Gemini │ FLAN-T5 │
├──────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ 当前地位 │ NLU任务首选 │ LLM主流架构 │ 翻译/摘要经典 │
│ │ (但被LLM逐渐 │ (统治性地位) │ (但被LLM逐渐 │
│ │ 取代) │ │ 取代) │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┘
"""
print(comparison)
8.2 为什么Decoder-only成为LLM主流?¶
reasons = """
Decoder-only架构(GPT/LLaMA/Claude)为何成为主流?
1. 统一输入输出格式
- Encoder-Decoder需要区分"源"和"目标"
- Decoder-only只需要一个连续序列,更简洁
- 所有任务统一为: prompt → completion
2. 规模扩展效率更高
- 只有一个Transformer栈,参数利用更高效
- 相同参数量下,Decoder-only通常效果更好
- KV Cache加速推理,Encoder-Decoder需要额外存编码器cache
3. In-context Learning天然适配
- 自回归生成 = 自然的多轮对话
- Few-shot提示 = 把示例放在前面然后续写
- BERT的[CLS] + Fine-tune范式在LLM时代不够灵活
4. 训练数据利用率
- CLM利用每一个token位置做预测(N-1个预测目标)
- MLM只预测15%被mask的token
- 在海量数据上,CLM的数据利用率更高
5. 生态与工程优势
- 推理只需要一个统一的Decoder,工程简单
- KV Cache可以高效缓存,支持流式输出
- 但BERT在检索、Embedding等任务上仍有独特优势!
"""
print(reasons)
8.3 代码对比实验¶
import torch
import torch.nn as nn
class MiniEncoder(nn.Module):
"""最小Encoder-only(BERT风格)"""
def __init__(self, vocab_size=1000, d_model=128, nhead=4, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, d_model*4, batch_first=True)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers)
self.mlm_head = nn.Linear(d_model, vocab_size)
def forward(self, x, mask_positions=None):
h = self.encoder(self.embedding(x))
if mask_positions is not None:
# 只在mask位置预测
return self.mlm_head(h[:, mask_positions, :])
return h # 返回所有位置的表示
class MiniDecoder(nn.Module):
"""最小Decoder-only(GPT风格)"""
def __init__(self, vocab_size=1000, d_model=128, nhead=4, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
decoder_layer = nn.TransformerEncoderLayer(d_model, nhead, d_model*4, batch_first=True)
self.decoder = nn.TransformerEncoder(decoder_layer, num_layers)
self.lm_head = nn.Linear(d_model, vocab_size)
def forward(self, x):
L = x.shape[1]
# 因果mask: 每个位置只能看到自己和之前
causal_mask = torch.triu(torch.ones(L, L), diagonal=1).bool().to(x.device)
h = self.decoder(self.embedding(x), src_key_padding_mask=None,
mask=causal_mask)
return self.lm_head(h) # 每个位置预测下一个token
class MiniEncDec(nn.Module):
"""最小Encoder-Decoder(T5风格)"""
def __init__(self, vocab_size=1000, d_model=128, nhead=4, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.transformer = nn.Transformer(
d_model, nhead, num_layers, num_layers, d_model*4, batch_first=True
)
self.output = nn.Linear(d_model, vocab_size)
def forward(self, src, tgt):
L = tgt.shape[1]
causal_mask = torch.triu(torch.ones(L, L), diagonal=1).bool().to(src.device)
src_emb = self.embedding(src)
tgt_emb = self.embedding(tgt)
h = self.transformer(src_emb, tgt_emb, tgt_mask=causal_mask)
return self.output(h)
# 对比三种架构
print("三种架构参数量对比(相同配置):")
print("=" * 50)
for name, model in [
("Encoder-only (BERT)", MiniEncoder()),
("Decoder-only (GPT)", MiniDecoder()),
("Encoder-Decoder (T5)", MiniEncDec())
]:
params = sum(p.numel() for p in model.parameters())
print(f" {name}: {params:,} 参数")
# 推理对比
print("\n推理特性对比:")
print(" Encoder-only: 一次性编码整个序列 → 产出固定表示")
print(" Decoder-only: 自回归逐token生成 → 可配合KV Cache")
print(" Encoder-Decoder: 先编码源序列, 再自回归解码目标序列")
9. 从PLM到LLM:范式转变¶
9.1 Scaling Laws(规模法则)¶
OpenAI Scaling Laws (Kaplan et al., 2020)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
核心发现: 模型性能(loss)随三个因素幂律下降
L(N) ∝ N^{-αN} N = 模型参数量
L(D) ∝ D^{-αD} D = 训练数据量
L(C) ∝ C^{-αC} C = 计算量
关键推论:
1. 更大的模型在同样的计算预算下通常更高效
2. 数据和模型大小需要同步增长
3. 当计算预算固定时, 存在最优的模型大小
9.2 涌现能力(Emergent Abilities)¶
emergent_abilities = """
涌现能力: 在小模型中不存在, 在大模型中突然出现的能力
┌────────────────────────────────────────────────┐
│ 准确率 │
│ 100% │ ╭──── GPT-4 │
│ │ ╭─╯ │
│ 50% │ ╭──╯ ← 涌现点 │
│ │ ╭──╯ (能力突然跳跃) │
│ 0% │──────────╯ │
│ └────────────────────────── 模型规模 │
│ 1B 10B 100B 1T │
└────────────────────────────────────────────────┘
已观测到的涌现能力:
├── 算术推理 (多位数加法)
├── 思维链 (Chain-of-Thought)
├── 指令遵循 (Instruction Following)
├── 代码生成
├── 多步推理
└── 世界知识问答
注: 涌现能力是否真的存在引发争论(Wei et al. vs Schaeffer et al.)
有研究认为用不同的评测指标可能不存在"突变"
"""
print(emergent_abilities)
9.3 范式对比¶
| 维度 | PLM时代 (2018-2022) | LLM时代 (2022~) |
|---|---|---|
| 典型模型 | BERT-base (110M) | GPT-4 (~1T?), LLaMA-70B |
| 使用方式 | Pre-train → Fine-tune | Pre-train → Instruct-tune → RLHF → Prompt |
| 适配方法 | 全参数微调 | LoRA/QLoRA, In-context Learning |
| 评估方式 | 固定benchmark (GLUE/SQuAD) | 多维度 (MMLU/HumanEval/GSM8K) |
| 核心能力 | 特定任务表现 | 通用能力 + 涌现能力 |
| 计算需求 | 几张GPU可微调 | 预训练需要数千GPU |
10. 代码实战:PLM三架构对比实验¶
10.1 实验设计¶
"""
实验: 在文本分类任务上对比三种架构
数据集: 简单的情感分类 (正面/负面)
目标: 理解不同架构在NLU任务上的差异
"""
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import random
import numpy as np
# 设置随机种子
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
set_seed()
# 模拟数据集
class SentimentDataset(Dataset):
"""简单情感分类数据集"""
# 正面关键词和负面关键词
POS_WORDS = ["good", "great", "excellent", "wonderful", "amazing", "love", "best", "happy"]
NEG_WORDS = ["bad", "terrible", "awful", "horrible", "hate", "worst", "sad", "poor"]
NEUTRAL_WORDS = ["the", "is", "a", "this", "that", "it", "very", "really", "so", "and"]
def __init__(self, num_samples=500, seq_len=20, vocab_size=100):
self.data = []
all_words = self.POS_WORDS + self.NEG_WORDS + self.NEUTRAL_WORDS
self.word2id = {w: i+1 for i, w in enumerate(all_words)} # 0 for PAD
self.vocab_size = max(self.word2id.values()) + 1
for _ in range(num_samples):
label = random.choice([0, 1]) # 0=负面, 1=正面
words = []
for _ in range(seq_len):
if random.random() < 0.3: # 30%概率选情感词
if label == 1:
words.append(random.choice(self.POS_WORDS))
else:
words.append(random.choice(self.NEG_WORDS))
else:
words.append(random.choice(self.NEUTRAL_WORDS))
ids = [self.word2id.get(w, 0) for w in words]
self.data.append((torch.tensor(ids), label))
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
return self.data[idx]
dataset = SentimentDataset(num_samples=1000, seq_len=20)
train_set, val_set = torch.utils.data.random_split(dataset, [800, 200])
train_loader = DataLoader(train_set, batch_size=32, shuffle=True)
val_loader = DataLoader(val_set, batch_size=32)
print(f"词表大小: {dataset.vocab_size}")
print(f"训练集: {len(train_set)}, 验证集: {len(val_set)}")
10.2 三种架构定义¶
class EncoderClassifier(nn.Module):
"""Encoder-only分类器(BERT风格)"""
def __init__(self, vocab_size, d_model=64, nhead=4, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, d_model*4,
batch_first=True, dropout=0.1)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers)
self.classifier = nn.Linear(d_model, 2)
def forward(self, x):
h = self.encoder(self.embedding(x))
# 平均池化作为句子表示
h_mean = h.mean(dim=1)
return self.classifier(h_mean)
class DecoderClassifier(nn.Module):
"""Decoder-only分类器(GPT风格)"""
def __init__(self, vocab_size, d_model=64, nhead=4, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, d_model*4,
batch_first=True, dropout=0.1)
self.decoder = nn.TransformerEncoder(encoder_layer, num_layers)
self.classifier = nn.Linear(d_model, 2)
def forward(self, x):
L = x.shape[1]
causal_mask = torch.triu(torch.ones(L, L, device=x.device), diagonal=1).bool()
h = self.decoder(self.embedding(x), mask=causal_mask)
# 取最后一个位置(因为因果注意力,最后位置看到全部信息)
h_last = h[:, -1, :]
return self.classifier(h_last)
class EncDecClassifier(nn.Module):
"""Encoder-Decoder分类器(T5风格)"""
def __init__(self, vocab_size, d_model=64, nhead=4, num_layers=2):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
self.transformer = nn.Transformer(d_model, nhead, num_layers, num_layers,
d_model*4, batch_first=True, dropout=0.1)
self.classifier = nn.Linear(d_model, 2)
# 用一个可学习的query token
self.query = nn.Parameter(torch.randn(1, 1, d_model))
def forward(self, x):
B = x.shape[0]
src = self.embedding(x)
# 用query token做解码
tgt = self.query.expand(B, -1, -1)
h = self.transformer(src, tgt)
return self.classifier(h.squeeze(1))
10.3 训练与对比¶
def train_and_evaluate(model, name, train_loader, val_loader, epochs=20, lr=1e-3):
"""训练并评估模型"""
optimizer = optim.Adam(model.parameters(), lr=lr)
criterion = nn.CrossEntropyLoss()
history = {"train_loss": [], "val_acc": []}
for epoch in range(epochs):
# 训练
model.train()
total_loss = 0
for x, y in train_loader:
logits = model(x)
loss = criterion(logits, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
# 验证
model.eval()
correct = 0
total = 0
with torch.no_grad(): # 禁用梯度计算,节省内存(推理时使用)
for x, y in val_loader:
logits = model(x)
preds = logits.argmax(dim=-1)
correct += (preds == y).sum().item()
total += len(y)
acc = correct / total
avg_loss = total_loss / len(train_loader)
history["train_loss"].append(avg_loss)
history["val_acc"].append(acc)
if (epoch + 1) % 5 == 0:
print(f" [{name}] Epoch {epoch+1}: loss={avg_loss:.4f}, val_acc={acc:.4f}")
return history
# 运行对比实验
print("=" * 60)
print("三种PLM架构在文本分类任务上的对比实验")
print("=" * 60)
vocab_size = dataset.vocab_size
results = {}
for name, ModelClass in [
("Encoder-only (BERT)", EncoderClassifier),
("Decoder-only (GPT)", DecoderClassifier),
("Encoder-Decoder (T5)", EncDecClassifier)
]:
print(f"\n--- {name} ---")
model = ModelClass(vocab_size)
params = sum(p.numel() for p in model.parameters())
print(f" 参数量: {params:,}")
history = train_and_evaluate(model, name, train_loader, val_loader)
results[name] = history
print(f" 最终验证准确率: {max(history['val_acc']):.4f}")
print("\n" + "=" * 60)
print("实验结论:")
print(" 1. Encoder-only在分类任务上通常效果最好(双向注意力优势)")
print(" 2. Decoder-only仅用最后位置的表示,信息利用不够充分")
print(" 3. Encoder-Decoder参数更多,但在简单任务上可能过度复杂")
print(" 4. 在实际大规模模型中,Decoder-only通过规模弥补了单向局限")
练习题¶
练习1:词向量实验¶
实现CBOW(Continuous Bag of Words)模型,比较和Skip-Gram的区别。
练习2:预训练目标实验¶
分别实现MLM和CLM预训练目标,在小语料上训练,比较学到的表示质量。
练习3:架构分析¶
- 为什么BERT不适合文本生成任务?从注意力mask的角度解释。
- 如果要让Decoder-only做好分类任务,有哪些策略?(提示:pool策略、特殊token)
- Encoder-Decoder架构在什么场景下仍然优于Decoder-only?
练习4:论文阅读¶
阅读以下论文,用自己的话总结关键贡献: 1. Word2Vec原始论文:"Efficient Estimation of Word Representations in Vector Space" (Mikolov et al., 2013) 2. BERT论文:"BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding" (Devlin et al., 2019) 3. GPT-3论文:"Language Models are Few-Shot Learners" (Brown et al., 2020)
📝 本章小结¶
| 知识点 | 掌握程度检查 |
|---|---|
| NLP四个发展阶段 | 能否画出时间线并说出每阶段代表方法? |
| 文本表示演进 | 能否解释One-hot→Word2Vec→ELMo→BERT各解决什么问题? |
| 预训练-微调范式 | 能否解释为什么Pre-train & Fine-tune优于从头训练? |
| 三种PLM架构 | 能否画出BERT/GPT/T5的架构图并说明差异? |
| MLM vs CLM vs DAE | 能否解释三种预训练目标的工作方式和适用场景? |
| Decoder-only主流原因 | 能否说出至少3个Decoder-only成为LLM主流的原因? |
| 涌现能力 | 能否解释什么是涌现能力并举例? |
🔗 后续学习路径¶
- 深入Transformer实现 → 03-手写Transformer完整实现
- 大模型预训练技术 → 03-大模型预训练
- 高效微调 → 01-高效微调技术
- 从零搭建LLM → 07-从零搭建小型LLM
📚 参考资料¶
- Mikolov et al. "Efficient Estimation of Word Representations in Vector Space" (2013) — Word2Vec
- Pennington et al. "GloVe: Global Vectors for Word Representation" (2014) — GloVe
- Peters et al. "Deep contextualized word representations" (2018) — ELMo
- Devlin et al. "BERT: Pre-training of Deep Bidirectional Transformers" (2019) — BERT
- Radford et al. "Language Models are Unsupervised Multitask Learners" (2019) — GPT-2
- Brown et al. "Language Models are Few-Shot Learners" (2020) — GPT-3
- Raffel et al. "Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer" (2020) — T5
- Lewis et al. "BART: Denoising Sequence-to-Sequence Pre-training" (2020) — BART
- Kaplan et al. "Scaling Laws for Neural Language Models" (2020) — Scaling Laws
- Wei et al. "Emergent Abilities of Large Language Models" (2022) — Emergent Abilities