Appearance
章节3:预训练语言模型
学习目标
- 理解 ELMo 如何通过双向 LSTM 生成上下文相关的词向量
- 掌握 BERT 的 Masked LM 与 Next Sentence Prediction 预训练任务
- 了解 GPT 自回归语言模型的单向生成机制
- 区分 RoBERTa、ALBERT 等变体的优化策略
- 熟悉 MLM、PLM 等不同掩码策略及其设计动机
3.1 ELMo:上下文词向量
3.1.1 静态词向量的局限
Word2Vec / GloVe 为每个词分配固定向量,无法解决一词多义:
"苹果" → 同一个向量 → 水果 / 手机品牌3.1.2 ELMo 的核心思想
ELMo(Embeddings from Language Models, AllenAI 2018)使用双向 LSTM 语言模型,根据上下文动态生成词向量。
前向 LSTM: P(t_1,...,t_N) = Π P(t_k | t_1,...,t_{k-1})
后向 LSTM: P(t_1,...,t_N) = Π P(t_k | t_{k+1},...,t_N)
ELMo向量 = γ · (s_layer0 + s_layer1·h1 + s_layer2·h2 + ...) # 加权各层python
# ELMo 使用示意(需安装 allennlp 或使用 HuggingFace)
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
model = AutoModel.from_pretrained("bert-base-uncased")
# ELMo 类似的双向动态表示已在 BERT 中融合
# 同一词在不同上下文中的表示不同
text1 = "I like **apple** juice" # 苹果汁
text2 = "I bought an **apple** phone" # iPhone
# BERT 会为两处的 "apple" 生成不同的隐层向量3.1.3 ELMo 的关键贡献
- ✅ 上下文动态表示 — 解决了静态向量的一词多义问题
- ✅ 多层表示融合 — 低层捕捉句法,高层捕捉语义
- ❗ 使用 LSTM(非 Transformer),后续被 BERT 超越
3.2 BERT:双向预训练模型
3.2.1 BERT 架构(Google, 2018)
BERT = Bidirectional Encoder Representations from Transformers
核心创新:双向 Transformer 编码器 + 两个预训练任务。
[CLS] 我 爱 NLP [SEP]
↓
[Transformer Encoder]
(12层 / 24层)
↓
[CLS] → 分类向量
每个位置 → 上下文词向量3.2.2 预训练任务一:Masked Language Model(MLM)
随机遮蔽15%的词,让模型根据上下文预测被遮蔽的词。
输入: 我 [MASK] 自然语言处理
目标: 预测 → "爱"python
from transformers import BertTokenizer, BertForMaskedLM
import torch
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertForMaskedLM.from_pretrained("bert-base-chinese")
text = "我 [MASK] 自然语言处理"
inputs = tokenizer(text, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
predictions = outputs.logits
masked_index = inputs["input_ids"][0].tolist().index(tokenizer.mask_token_id)
predicted_id = predictions[0, masked_index].argmax(dim=-1).item()
print("预测结果:", tokenizer.decode([predicted_id]))
# 输出: 爱(或类似合理的词)3.2.3 预训练任务二:Next Sentence Prediction(NSP)
判断两个句子是否为连续的上下文。
输入: [CLS] 我 爱 NLP [SEP] NLP 很 有趣 [SEP] → 标签: IsNext
输入: [CLS] 我 爱 NLP [SEP] 北京 是 首都 [SEP] → 标签: NotNext3.2.4 BERT 的输入表示
Input = Token Embedding + Segment Embedding + Position Embedding
↓ ↓ ↓
word piece 句子A/B标记 位置编码python
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
inputs = tokenizer("我爱自然语言处理", "NLP很有趣",
return_tensors="pt", padding=True, truncation=True)
print("input_ids:", inputs["input_ids"])
print("token_type_ids:", inputs["token_type_ids"]) # 区分句子A/B
print("attention_mask:", inputs["attention_mask"])3.3 GPT:自回归语言模型
3.3.1 GPT 的核心思想
GPT(OpenAI, 2018)使用单向 Transformer 解码器,从左到右逐个预测下一个词。
P(t_1,...,t_N) = Π P(t_k | t_1,...,t_{k-1})
输入: 我 爱 → 预测: 自然
输入: 我 爱 自然 → 预测: 语言
输入: 我 爱 自然 语言 → 预测: 处理3.3.2 BERT vs GPT 对比
| 维度 | BERT | GPT |
|---|---|---|
| 架构 | Transformer 编码器(双向) | Transformer 解码器(单向) |
| 预训练任务 | MLM + NSP | 自回归语言建模 |
| 注意力 | 可看左右双向 | 只能看左侧(因果掩码) |
| 适合任务 | 理解类(分类、NER、QA) | 生成类(文本生成、对话) |
| 代表模型 | BERT, RoBERTa, ALBERT | GPT-2, GPT-3, GPT-4 |
python
from transformers import GPT2Tokenizer, GPT2LMHeadModel
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")
text = "Natural language processing is"
inputs = tokenizer(text, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs, labels=inputs["input_ids"])
logits = outputs.logits
# 生成下一个词
next_token_logits = logits[0, -1, :]
next_token_id = next_token_logits.argmax(dim=-1).item()
print("下一个词:", tokenizer.decode([next_token_id]))3.4 优化变体:RoBERTa 与 ALBERT
3.4.1 RoBERTa(Facebook, 2019)
A Robustly Optimized BERT Pretraining Approach
对 BERT 的改进:
| 改进点 | BERT | RoBERTa |
|---|---|---|
| 训练数据 | 16GB(BooksCorpus+Wikipedia) | 160GB(+ 大规模额外语料) |
| 动态掩码 | 静态(预处理时确定) | 动态(每个 epoch 重新掩码) |
| NSP | 使用 | 去掉(实验证明没必要) |
| 训练步数 | 100 万步 | 500 万步 |
| Batch Size | 256 | 8000 |
python
from transformers import RobertaTokenizer, RobertaForMaskedLM
tokenizer = RobertaTokenizer.from_pretrained("roberta-base")
model = RobertaForMaskedLM.from_pretrained("roberta-base")3.4.2 ALBERT(Google, 2019)
A Lite BERT — 减少参数量同时保持性能
两大创新:
分解式嵌入参数化(Factorized Embedding)
- BERT: VocabSize × HiddenSize(大词表导致嵌入矩阵巨大)
- ALBERT: VocabSize × SmallEmbedding + SmallEmbedding × HiddenSize(分解为两个小矩阵)
跨层参数共享(Cross-layer Parameter Sharing)
- BERT 每层 Transformer 权重独立
- ALBERT 所有层共享同一组参数
python
from transformers import AlbertTokenizer, AlbertForMaskedLM
tokenizer = AlbertTokenizer.from_pretrained("albert-base-v2")
model = AlbertForMaskedLM.from_pretrained("albert-base-v2")模型参数对比:
| 模型 | 参数量 | 速度 | 性能 |
|---|---|---|---|
| BERT-base | 110M | 基准 | 基准 |
| RoBERTa-base | 125M | 稍慢 | ↑ 提升 |
| ALBERT-base | 12M | 更快 | ≈ 持平 |
| ALBERT-xxlarge | 235M | — | ≈ BERT-large 性能 |
3.5 预训练任务与掩码策略
3.5.1 MLM(Masked Language Model)
BERT 使用的策略:随机遮蔽 15% 的词。
遮蔽策略细节:
- 80% →
[MASK] - 10% → 随机替换为其他词
- 10% → 保持不变
为什么要保留10%不变? 让模型知道即使输入"正确"也要输出正确——缓解预训练与微调阶段的 mismatch。
3.5.2 PLM(Permutation Language Model)
XLNet(2019)提出的排列语言模型:
原始序列: [x1, x2, x3, x4]
排列之一: [x3, x2, x4, x1]
→ 预测 x1 时可以看到 x3, x2, x4(但看不到 x1 自己)PLM 在保持自回归生成的前提下实现了双向上下文的建模。
3.5.3 其他掩码策略
| 策略 | 代表模型 | 描述 |
|---|---|---|
| MLM | BERT, RoBERTa, ALBERT | 随机遮蔽部分输入词 |
| PLM | XLNet | 排列预测顺序实现双向 |
| UniLM | UniLM | 统一 MLM + 自回归 LM |
| SpanBERT | SpanBERT | 遮蔽连续片段而非单个词 |
| ELECTRA | ELECTRA | 判别式(替换检测)替代生成式 |
python
# 不同掩码策略的效果对比示意
strategies = {
"MLM (BERT)": "遮蔽15%词,预测被遮蔽词",
"PLM (XLNet)": "排列分解,每个位置只看排列中的上文",
"SpanBERT": "遮蔽连续词片段(如连续3个词)",
"ELECTRA": "用小生成器替换词,用判别器判断哪些被替换"
}
for name, desc in strategies.items():
print(f"{name}: {desc}")小结
- ELMo 首次提出上下文词向量,使用双向 LSTM 解决一词多义。
- BERT 通过 MLM + NSP 双向预训练,成为 NLP 的基石模型。
- GPT 使用单向自回归方式,擅长文本生成任务。
- RoBERTa 通过更大数据、动态掩码、去掉 NSP 优化 BERT。
- ALBERT 用参数分解和跨层共享大幅减少参数量。
- 不同的掩码策略(MLM / PLM / SpanBERT)各有设计动机和适用场景。
练习
- 使用 BERT(
bert-base-chinese)对句子"我在[银行]存钱"和"我在[银行]散步"中的"银行"提取向量,观察差异。 - 用 GPT-2 完成"人工智能的未来是______"的生成,尝试不同 temperature 值(0.1 / 0.8 / 1.5)观察输出变化。
- 对比 BERT-base 和 RoBERTa-base 对同一句 MLM 预测的差异,分析 RoBERTa 改进是否有效。
- 查阅 SpanBERT 论文,简述其"Span Boundary Objective"(SBO)如何辅助片段预测。
- 挑战题: 用 HuggingFace
TrainerAPI 在自定义小语料上微调 BERT(MLM),训练完成后观察模型对领域词汇的预测能力。