608 字
2 分钟
BERT 与双向预训练:NLP 预训练模型的崛起
2018 年是 NLP 预训练模型元年。
这一年,OpenAI 发布了 GPT-1,Google 发布了 BERT。两条技术路线从此分道扬镳:GPT 选择单向自回归生成,BERT 选择双向编码理解。
BERT 证明了双向预训练的强大力量,深刻影响了整个 NLP 领域。
本文要点
- BERT 论文背景与动机
- 双向 Transformer 架构
- 掩码语言模型(MLM)
- 下一句预测(NSP)
- 预训练+微调范式
- BERT vs GPT 对比
一、论文背景
1.1 研究动机
flowchart TB
subgraph BERT解决的问题
A[2018 年前的困境]
A --> A1[预训练模型主要是单向的(GPT、ELMo)]
A --> A2[单向限制了对上下文的理解]
A --> A3[任务特定架构需要从头设计]
B[BERT 的创新]
B --> B1[双向上下文理解]
B --> B2[统一的预训练+微调范式]
B --> B3[刷新 11 项 NLP 任务 SOTA]
C[核心洞察]
C --> C1["「语言理解需要同时看到左右两侧的上下文」"]
end
1.2 论文信息
论文:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding作者:Jacob Devlin, Ming-Wei Chang, Kenton Lee, Kristina Toutanova机构:Google AI Language发表:NAACL 2019(2018 年 10 月预印)引用:10万+ 次二、双向 Transformer 架构
2.1 BERT vs GPT 架构对比
flowchart TB
subgraph GPT 单向
A1["Token 1"] --> A2["Token 2"]
A2 --> A3["Token 3"]
A3 --> A4["Token 4"]
B1["只能看到左边"] --> B2["自回归生成"]
end
subgraph BERT 双向
C1["Token 1"]
C2["Token 2"]
C3["Token 3"]
C4["Token 4"]
C1 <--> C2 <--> C3 <--> C4
D1["可以看到左右两边"] --> D2["深度理解"]
end
| 维度 | GPT | BERT ||---------------|-----------------|---------------------------|| Transformer | Decoder only | Encoder only || 注意力 | 单向(因果掩码)| 双向(无掩码) || 训练目标 | 语言模型 | MLM + NSP || 适用任务 | 生成 | 理解 || 输出 | 自回归生成 | 编码表示 |2.2 BERT 模型规格
flowchart TB
subgraph BERT模型配置
A[BERT-Base]
A --> A1[参数量:110M]
A --> A2[层数:L = 12]
A --> A3[隐藏维度:H = 768]
A --> A4[注意力头:A = 12]
B[BERT-Large]
B --> B1[参数量:340M]
B --> B2[层数:L = 24]
B --> B3[隐藏维度:H = 1024]
B --> B4[注意力头:A = 16]
C[输入]
C --> C1[最大长度:512 tokens]
C --> C2[词表大小:30,000]
C --> C3[训练数据:BookCorpus + Wikipedia(约 33 亿词)]
end
三、掩码语言模型(MLM)
3.1 核心思想
flowchart LR
A["I [MASK] to the store"] --> B[BERT Encoder]
B --> C["I went to the store"]
D["随机遮盖 15% 的 token"] --> E["预测被遮盖的词"]
E --> F["双向上下文理解"]
flowchart TB
subgraph MLM训练策略
A[输入处理(随机选择 15% 的 token)]
A --> B[80% 替换为 [MASK]]
A --> C[10% 替换为随机词]
A --> D[10% 保持不变]
B --> B1[""I went to the store" → "I [MASK] to the store""]
C --> C1[""I went to the store" → "I apple to the store""]
D --> D1[""I went to the store" → "I went to the store""]
E[为什么这样设计?]
E --> E1[避免 [MASK] 和微调时的分布差异]
E --> E2[随机替换增加难度,学习鲁棒表示]
E --> E3[保持部分原始信息]
F[损失函数]
F --> F1["L_MLM = -Σ log P(masked_token | context)"]
end
3.2 代码实现
import torchimport torch.nn as nn
class MaskedLanguageModel(nn.Module): """BERT 的掩码语言模型头"""
def __init__(self, hidden_size, vocab_size): super().__init__() self.transform = nn.Sequential( nn.Linear(hidden_size, hidden_size), nn.GELU(), nn.LayerNorm(hidden_size) ) self.decoder = nn.Linear(hidden_size, vocab_size, bias=False)
def forward(self, hidden_states, masked_positions): """ Args: hidden_states: BERT 输出 [batch, seq_len, hidden_size] masked_positions: 被遮盖的位置 [batch, num_masks] """ # 获取被遮盖位置的隐藏状态 batch_size, num_masks = masked_positions.size()
# 收集被遮盖位置的表示 masked_hidden = torch.gather( hidden_states, dim=1, index=masked_positions.unsqueeze(-1).expand(-1, -1, hidden_states.size(-1)) )
# 预测 transformed = self.transform(masked_hidden) logits = self.decoder(transformed)
return logits
def create_masked_input(input_ids, vocab_size, mask_token_id, mask_prob=0.15): """创建遮盖输入""" labels = input_ids.clone()
# 随机选择要遮盖的位置 probability_matrix = torch.full(input_ids.shape, mask_prob) masked_indices = torch.bernoulli(probability_matrix).bool()
# 只在被遮盖的位置计算损失 labels[~masked_indices] = -100 # PyTorch 忽略 -100
# 80% 替换为 [MASK] indices_replaced = torch.bernoulli(torch.full(input_ids.shape, 0.8)).bool() & masked_indices input_ids[indices_replaced] = mask_token_id
# 10% 替换为随机词 indices_random = torch.bernoulli(torch.full(input_ids.shape, 0.5)).bool() & masked_indices & ~indices_replaced random_words = torch.randint(vocab_size, input_ids.shape, dtype=torch.long) input_ids[indices_random] = random_words[indices_random]
# 10% 保持不变
return input_ids, labels四、下一句预测(NSP)
4.1 任务设计
flowchart TB
subgraph NSP 任务
A["[CLS] Sentence A [SEP] Sentence B [SEP]"] --> B[BERT]
B --> C["[CLS] 表示"]
C --> D["分类:是否连续?"]
end
E["IsNext"] --> F["正样本:连续句子"]
G["NotNext"] --> H["负样本:随机句子"]
flowchart TB
subgraph NSP训练策略
A[输入格式]
A --> A1["[CLS] Sentence A [SEP] Sentence B [SEP]"]
B[正样本构建]
B --> B1[从语料中选取连续的两个句子]
B --> B2[标签:IsNext]
C[负样本构建]
C --> C1[第一句保持不变]
C --> C2[第二句从语料中随机选取]
C --> C3[标签:NotNext]
D[训练比例]
D --> D1[50% IsNext + 50% NotNext]
E[目的]
E --> E1[学习句子间关系]
E --> E2[增强篇章理解能力]
E --> E3[对 QA、NLI 等任务有帮助]
F[注]
F --> F1[后续研究(如 RoBERTa)发现 NSP 可能不是必需的]
end
4.2 代码实现
class NextSentencePrediction(nn.Module): """下一句预测头"""
def __init__(self, hidden_size): super().__init__() self.classifier = nn.Sequential( nn.Linear(hidden_size, hidden_size), nn.Tanh(), nn.Linear(hidden_size, 2) # IsNext / NotNext )
def forward(self, cls_output): """ Args: cls_output: [CLS] token 的表示 [batch, hidden_size] """ return self.classifier(cls_output)
class BERTPreTraining(nn.Module): """BERT 预训练模型"""
def __init__(self, bert, vocab_size): super().__init__() self.bert = bert self.mlm = MaskedLanguageModel(bert.config.hidden_size, vocab_size) self.nsp = NextSentencePrediction(bert.config.hidden_size)
def forward(self, input_ids, token_type_ids, attention_mask, masked_positions): # BERT 编码 sequence_output, pooled_output = self.bert( input_ids=input_ids, token_type_ids=token_type_ids, attention_mask=attention_mask )
# MLM 预测 mlm_logits = self.mlm(sequence_output, masked_positions)
# NSP 预测 nsp_logits = self.nsp(pooled_output)
return mlm_logits, nsp_logits五、输入表示
5.1 Tokenization
flowchart LR
A["输入文本"] --> B[WordPiece 分词]
B --> C["添加特殊 token"]
C --> D["Segment 嵌入"]
D --> E["位置嵌入"]
E --> F["最终输入"]
flowchart TB
subgraph BERT输入表示
A["输入 = Token Embedding + Segment Embedding + Position Embedding"]
B[Token Embedding]
B --> B1[WordPiece 分词(30,000 词表)]
B --> B2["[CLS]:句首,用于分类任务"]
B --> B3["[SEP]:句子分隔符"]
B --> B4["[MASK]:预训练时的遮盖标记"]
C[Segment Embedding]
C --> C1[区分句子 A 和句子 B]
C --> C2[句子 A:EA,句子 B:EB]
C --> C3[单句任务:全部 EA]
D[Position Embedding]
D --> D1[学习的位置嵌入(非正弦余弦)]
D --> D2[最大长度 512]
E[示例]
E --> E1["输入:[CLS] I love dogs [SEP] They are cute [SEP]"]
E --> E2["Token: [101, 1045, 2293, 8799, 102, 2027, 2024, 10125, 102]"]
E --> E3["Segment: [0, 0, 0, 0, 0, 1, 1, 1, 1]"]
E --> E4["Position: [0, 1, 2, 3, 4, 5, 6, 7, 8]"]
end
六、预训练+微调范式
6.1 微调方式
flowchart TB
subgraph 预训练
A[大规模无标注文本] --> B[BERT 预训练]
B --> C[预训练模型]
end
subgraph 微调
C --> D[下游任务]
D --> E[分类:取 [CLS] 表示]
D --> F[NER:每个 token 分类]
D --> G[QA:预测答案起止]
D --> H[相似度:句对分类]
end
flowchart TB
subgraph 不同任务的微调方式
A[单句分类(情感分析、语义相似度)]
A --> A1["输入:[CLS] 句子 [SEP]"]
A --> A2["输出:[CLS] 的表示 → 分类层"]
B[句对分类(NLI、QA)]
B --> B1["输入:[CLS] 句子A [SEP] 句子B [SEP]"]
B --> B2["输出:[CLS] 的表示 → 分类层"]
C[序列标注(NER、POS)]
C --> C1["输入:[CLS] 句子 [SEP]"]
C --> C2["输出:每个 token 的表示 → 分类层"]
D[问答(SQuAD)]
D --> D1["输入:[CLS] 问题 [SEP] 文章 [SEP]"]
D --> D2["输出:预测答案的起止位置"]
E[微调技巧]
E --> E1[学习率:2e-5 到 5e-5]
E --> E2[Epochs:2-4]
E --> E3[批量大小:16 或 32]
E --> E4[预训练学习率是微调学习率的 1/10 到 1/100]
end
6.2 微调代码示例
from transformers import BertForSequenceClassification, BertTokenizer
# 加载预训练模型model = BertForSequenceClassification.from_pretrained( 'bert-base-uncased', num_labels=2 # 二分类)tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 准备输入text = "This movie is fantastic!"inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)
# 微调训练循环optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
for epoch in range(3): for batch in train_dataloader: outputs = model(**batch) loss = outputs.loss
loss.backward() optimizer.step() optimizer.zero_grad()
# 推理with torch.no_grad(): outputs = model(**inputs) predictions = torch.argmax(outputs.logits, dim=-1)七、实验结果
7.1 GLUE 基准
xychart-beta
title "GLUE 基准测试分数"
x-axis ["Pre-BERT SOTA", "BERT-Base", "BERT-Large"]
y-axis "平均分数" 75 --> 85
bar [76.2, 79.6, 82.1]
| 任务 | Pre-BERT | BERT-Base | BERT-Large ||-----------|-------------|-------------|------------------|| MNLI | 86.6 | 87.6 | 88.6 || QQP | 66.1 | 89.2 | 90.3 || QNLI | 87.4 | 90.1 | 91.1 || SST-2 | 93.2 | 93.5 | 94.9 || CoLA | 35.0 | 52.1 | 60.5 || STS-B | 81.0 | 85.8 | 86.5 || MRPC | 86.0 | 88.9 | 89.3 || RTE | 61.7 | 66.4 | 70.1 || 平均 | 76.2 | 79.6 | 82.1 |7.2 SQuAD 问答
SQuAD v1.1:• BERT-Base:F1 88.5,EM 80.4• BERT-Large:F1 91.1,EM 84.1• Pre-BERT SOTA:F1 85.8,EM 78.0
SQuAD v2.0(包含无答案问题):• BERT-Large:F1 83.1,EM 80.0• Pre-BERT SOTA:F1 66.3,EM 63.4
提升显著,证明了预训练表示的力量八、BERT vs GPT 深度对比
flowchart TB
subgraph BERT vs GPT全面对比
A[架构]
A --> A1[BERT: Encoder only]
A --> A2[GPT: Decoder only]
B[注意力]
B --> B1[BERT: 双向]
B --> B2[GPT: 单向]
C[预训练任务]
C --> C1[BERT: MLM + NSP]
C --> C2[GPT: 语言模型]
D[核心能力]
D --> D1[BERT: 理解]
D --> D2[GPT: 生成]
E[典型应用]
E --> E1[BERT: 分类、NER、QA]
E --> E2[GPT: 文本生成、对话]
F[微调方式]
F --> F1[BERT: 添加任务头]
F --> F2[GPT: Few-Shot / 微调]
G[上下文长度]
G --> G1[BERT: 512]
G --> G2[GPT: 1024+]
H[训练效率]
H --> H1[BERT: 较慢(MLM)]
H --> H2[GPT: 较快(自回归)]
I[表示质量]
I --> I1[BERT: 深度双向]
I --> I2[GPT: 单向累积]
J[优势]
J --> J1[BERT: 理解任务强]
J --> J2[GPT: 生成任务强]
K[劣势]
K --> K1[BERT: 不擅长生成]
K --> K2[GPT: 不擅长理解]
end
8.1 选择建议
选择 BERT 当:• 任务是理解型(分类、NER、QA)• 需要双向上下文• 有标注数据进行微调• 对准确率要求高
选择 GPT 当:• 任务是生成型(写作、对话、翻译)• 需要开放域生成• 标注数据少• 需要 Few-Shot 能力九、BERT 的后续发展
BERT 衍生模型:
RoBERTa (Facebook, 2019)• 移除 NSP 任务• 更大的批量和数据• 动态掩码• 性能进一步提升
ALBERT (Google, 2019)• 参数共享,减少参数量• 跨层参数共享• 句子顺序预测替代 NSP
DistilBERT (HuggingFace, 2019)• 蒸馏压缩• 保留 97% 性能,减少 40% 参数
ELECTRA (Google, 2020)• 替换词检测任务• 更高效的预训练
DeBERTa (Microsoft, 2020)• 解耦注意力• 更强的性能常见问题 FAQ
Q1:BERT 为什么不能用于生成?
A:BERT 是 Encoder,没有因果掩码,可以看到未来的 token。生成需要自回归地逐词预测,BERT 的双向特性反而会导致「作弊」。
Q2:MLM 为什么比语言模型更难?
A:MLM 需要预测被遮盖的词,只能依赖上下文。语言模型可以「复制」前面的内容。但 MLM 学到的表示更丰富。
Q3:BERT 的 [CLS] 是什么?
A:[CLS] 是句首的特殊 token,其表示被设计用于聚合整个句子的信息,常用于分类任务。
Q4:为什么 BERT 有最大长度限制?
A:位置嵌入是学习的,最大长度 512。超过需要截断或使用 Longformer、BigBird 等变体。
Q5:BERT 还值得学吗?
A:BERT 的思想(双向编码、预训练+微调)仍然重要。理解 BERT 有助于理解现代 NLP。
小结
BERT 证明了双向预训练在语言理解任务上的强大能力。
核心贡献:
flowchart TB
subgraph BERT核心总结
A[双向 Transformer] --> A1[打破单向限制]
B[掩码语言模型] --> B1[学习深度上下文表示]
C[预训练+微调] --> C1[统一的迁移学习范式]
D[11 项 SOTA] --> D1[证明预训练的力量]
E[影响深远] --> E1[开启 NLP 预训练时代]
end
参考资料
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时
相关文章 智能推荐
1
OpenAI o1/o3 与推理模型的崛起
AI 深度解读 OpenAI o1/o3 系统(2024-2025)——推理模型的新范式、RLVR 训练方法、Test-Time Compute Scaling,以及在 ARC-AGI 上的突破性表现。
2
Chinchilla 与训练token数量的秘密
AI 深度解读 DeepMind Chinchilla 论文(2022)——揭示训练tokens数量与模型大小的关系,重新定义 Scaling Law
3
PaLM 与 Scaling Law:大模型时代的开启
AI 深度解读 Google PaLM 论文(2022)——540B 参数、Pathways 系统、Scaling Law 验证、涌现能力发现,以及大模型时代的开启。
4
LLaMA 与开源大模型革命:Meta 的开放之路
AI 深度解读 Meta LLaMA 论文(2023)——7B-65B 参数规模、公开数据集训练、Chinchilla Scaling Law 验证,以及 LLaMA 2 的开源协议演进。
5
LLaMA 2:Meta 开源模型的里程碑
AI 深度解读 Meta LLaMA 2 论文(2023)——开源大模型、RLHF、ChatGPT 竞争对手






