一、起源:BERT 为什么会出现
1. 时代背景(2018 年之前)
在 BERT 之前,NLP 的主流做法是为每个任务单独标注数据、单独训练一个模型:
- 情感分析训一个模型,命名实体识别又训一个,问答再训一个......
- 数据标注昂贵,小任务很难有充足标注数据
- 词向量(Word2Vec、GloVe)是静态的:一个词只有一个向量,"苹果"在"吃水果"和"苹果公司"中表达完全相同------无法结合上下文
2. 关键技术铺垫
| 时间 | 工作 | 贡献 |
|---|---|---|
| 2017 | Transformer(Google, 《Attention Is All You Need》) | 提出自注意力机制,抛弃循环结构,可高度并行 |
| 2018 初 | ELMo(AI2) | 首次提出"预训练语言模型":根据上下文动态生成词向量(但仍基于 LSTM) |
| 2018 6月 | GPT-1(OpenAI) | 首次用 Transformer Decoder 做预训练,但只能单向(从左往右) |
| 2018 10月 | BERT(Google) | 用 Transformer Encoder + 双向预训练,11 项 NLP 基准全面刷新纪录(SQuAD、GLUE 等) |
BERT 的论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》核心主张一句话概括:
"单向的语言模型(如 GPT)限制了预训练表示的能力。我们应该让模型在每个层都同时融合左右上下文。"
但双向预训练有一个技术难题:如果像传统语言模型那样"预测下一个词",双向模型会直接看到答案(左边看到右边),任务失去意义。BERT 的解决方案就是著名的 MLM(掩码语言模型)。
二、原理细节
1. 整体架构
BERT = 纯 Encoder 的 Transformer 堆叠
输入文本 → Token 嵌入 + 位置嵌入 + 段落嵌入 → [Encoder × N] → 上下文向量
两个官方版本:
| 版本 | 层数 | 隐藏维度 | 注意力头数 | 参数量 |
|---|---|---|---|---|
| BERT-Base | 12 | 768 | 12 | 1.1 亿 |
| BERT-Large | 24 | 1024 | 16 | 3.4 亿 |
最大输入长度:512 个 token。
2. 输入表示(三个嵌入相加)
BERT 的每个输入 token 的最终向量 = Token Embedding + Segment Embedding + Position Embedding
特殊 token:
[CLS]:放在句首,它的最终输出向量被当作"整句话的表示",用于分类任务[SEP]:句子分隔符,用于区分句子对[MASK]:预训练时替换被遮住的词
3. 分词:WordPiece
BERT 不用整词切分,而是用 WordPiece:把词拆成子词(subword)。
- 例:
unbelievable→un+##believ+##able(##表示接在前一个词后面) - 词表约 3 万,生僻词/新词总能拆成已知子词,不会遇到 OOV(未登录词)问题
4. 预训练任务一:MLM(Masked Language Model)
这是 BERT 的灵魂。
做法:随机选 15% 的 token,对它们:
- 80% 替换为
[MASK] - 10% 替换为随机词
- 10% 保持不变
然后让模型预测这些位置原本是什么词。
为什么要这样设计?
- 全部换成
[MASK]→ 预训练和微调不一致(微调时输入没有[MASK]) - 加入 10% 随机词和 10% 原词 → 迫使模型不能死记"看到 MASK 才预测",而是真正依赖上下文,同时让模型学会纠正错误输入
- 双向性由此实现:预测被遮词时,左右两边的词都在场
代价:每次只预测 15% 的 token,收敛比 GPT 慢,需要更多训练步。
5. 预训练任务二:NSP(Next Sentence Prediction)
输入两个句子 A、B,模型判断 B 是否真是 A 的下一句(50% 是,50% 是随机句子)。
目的:让模型学会句子间关系(对问答、语义匹配有用)。
后来的 RoBERTa 等研究证明 NSP 收益很小甚至有害,将其去掉后性能反而更好------但它是理解 BERT 设计思路的重要一环。
6. 预训练数据与规模
- BooksCorpus(8 亿词)+ 英文维基百科(25 亿词)
- Base 训练 100 万步(约 16GB 文本,4 天,16 块 TPU)
- 这么大的工程普通人做不了 → 所以实践方式是下载预训练权重,做微调
7. 自注意力在 BERT 里做了什么
每个 token 的向量会与所有其他 token 做注意力计算:
"它" ← 同时关注 "小明"、"书包"、"放学"......
多层注意力叠加后,"它" 的表示就融合了指代对象的语义------这就是"双向上下文理解"的底层机制。
三、实际微调使用(实战)
1. 通用范式
下载 HuggingFace 上的 bert-base-chinese / bert-base-uncased
↓
准备任务数据(如:句子 + 标签)
↓
加任务头(线性分类层等),接上预训练模型
↓
小学习率(2e-5 ~ 5e-5)训练 2~4 个 epoch
2. 任务一:文本分类(最常用)
例如情感分析:在 [CLS] 输出向量上加一个线性层。
python
import torch
from transformers import BertTokenizer, BertForSequenceClassification
# 1. 加载预训练模型和分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertForSequenceClassification.from_pretrained(
"bert-base-chinese", num_labels=2 # 二分类:正面/负面
)
# 2. 数据编码
texts = ["这部电影太精彩了", "浪费了我两个小时"]
labels = torch.tensor([1, 0]) # 1=正面, 0=负面
enc = tokenizer(texts, padding=True, truncation=True,
max_length=128, return_tensors="pt")
# enc: input_ids, attention_mask, token_type_ids
# 3. 训练
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
model.train()
for _ in range(3): # 3 个 epoch
outputs = model(**enc, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
print("loss:", loss.item())
# 4. 推理
model.eval()
with torch.no_grad():
logits = model(**enc).logits
print("预测:", logits.argmax(dim=-1)) # tensor([1, 0])
关键超参数经验:
- 学习率:2e-5 ~ 5e-5(不能大,否则破坏预训练权重)
- batch size:16 或 32
- epoch:2~4(多了容易过拟合)
- max_length:按任务定,多数分类任务 128~256 足够
3. 任务二:命名实体识别(NER)
对每个 token 的输出向量做分类,而不是只用 [CLS]:
输入: 李小明 在 北京 大学 读书
输出: B-PER O B-LOC I-LOC O
用 BertForTokenClassification,数据要标注到 token 级(注意中文分词与 WordPiece 的切分对齐问题,这是实战中最繁琐的部分)。
4. 任务三:抽取式问答(QA)
用 BertForQuestionAnswering:
- 输入 = 问题 +
[SEP]+ passage - 输出两个指针:答案开始位置 和 答案结束位置 的概率分布
- 取 argmax(start)、argmax(end),切出原文片段即为答案
5. 任务四:句子对匹配(语义相似度)
输入 A + B,用 [CLS] 判断"是否相似"。这是搜索引擎 query-doc 相关性判断的经典用法。
6. 常见坑
| 问题 | 建议 |
|---|---|
| 学习率过大,loss 震荡 | 降到 2e-5 以下,加 warmup |
| 过拟合(训练集好、验证集差) | 减少 epoch、加 dropout、增数据 |
| 类别不平衡 | 加权 loss 或重采样 |
| 中文切分对不齐标签 | 用 tokenizer 的 offset_mapping 对齐 |
| 512 token 限制 | 长文档用滑窗切分、mean pooling 后拼接 |
7. 快速上手工具(不写训练代码)
bash
pip install transformers datasets
配合 Trainer API 或自动微调工具(如 AutoGluon、LlamaFactory 类框架)可零代码微调。
四、应用:BERT 在哪些场景
BERT 是理解型模型,强项是"读懂"而非"写作":
-
搜索引擎与推荐
- query 与文档的相关性排序(Google 2019 年起将 BERT 用于搜索排序)
- 语义检索:query 和 doc 分别编码成向量,做向量相似度检索(双塔模型)
-
文本分类:情感分析、垃圾邮件识别、新闻分类、审核打标
-
信息抽取:NER(人名/地名/机构名)、关系抽取、事件抽取------金融、医疗、法律领域结构化数据抽取的标配底座
-
智能问答:FAQ 匹配、客服机器人(问题改写 + 检索 + 抽取式回答)
-
语义匹配:查重、相似问句归并、搜索词联想
-
作为 Embedding 底座:句向量 + 向量数据库,是很多 RAG 检索链路的组成部分
-
垂直领域微调:医疗 BERT(PubMedBERT)、法律 BERT、金融 BERT------在领域语料上继续预训练(领域自适应预训练,DAPT)后再微调,效果显著好于直接用通用 BERT
五、总结与延伸
BERT 的历史地位:证明了"大规模预训练 + 下游微调"这条路线可行,直接开启了 NLP 的预训练时代。
它之后的演进脉络(帮你定位 BERT 在模型谱系中的位置):
- RoBERTa:去掉 NSP、更大批量更长训练 → 同规模更强
- ALBERT:参数共享 + 矩阵分解,轻量版
- DeBERTa:解耦注意力,理解任务一度霸榜
- 生成方向:GPT 系列(Decoder 单向)最终通向大语言模型时代
- 检索方向:Sentence-BERT(SBERT)把 BERT 改造成高效的句向量生成器,至今仍是很多 RAG 系统的检索核心