第14课:Transformers 之 BERT 模型深度解析【双向注意力的文本理解标杆】

文章目录
从ELMo和GPT中破局而出,BERT如何用“双向”撕开自然语言理解的新纪元?
适合读者:初级/中级开发者、AI爱好者、准备面试或已经入门Transformer的任何人
风格:技术深潜,案例实战,代码即用
写在前面:BERT为何是一座里程碑?
在BERT之前,NLP领域的主流预训练方向是单向语言模型——ELMo通过两个单向LSTM拼接实现浅层双向,GPT则坚持从左到右的单向自回归。然而这两种方式都有天然的局限性:要么双向不够彻底,要么无法同时利用左右两侧的上下文。
2018年,Google的研究团队在论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》中提出了一种革命性的方法:双向Transformer编码器,并配合掩码语言模型(MLM) 和下一句预测(NSP) 两个预训练任务,让模型在学习过程中能够同时看到每个词的左侧和右侧信息。
这就是BERT(Bidirectional Encoder Representations from Transformers)。它的核心技术创新在于将双向训练应用于语言建模——模型在处理一个词时,不再像传统单向模型那样只能看到之前的内容,而是可以同时利用该词前后的整个上下文信息。
📌 BERT在11个NLP任务上达到了当时的最高水平(SOTA),是NLP历史上第一个证明“双向深度预训练”可以横扫多种下游任务的模型。
一、从ELMo和GPT说起:BERT的核心改进
1.1 “双向”之争:为什么之前的模型不太够?
在BERT问世之前,NLP领域已经有几款代表性预训练模型:
- ELMo:采用双向LSTM架构,但它本质上是将一个从左到右的LSTM和一个从右到左的LSTM的表示进行拼接。这种“分向拼接”的方式虽然比单向更好,但并不是真正的双向建模——两个方向的信息在训练时仍然是相互独立的。
- GPT:坚持使用Transformer的解码器架构,通过因果掩码(causal mask)实现从左到右的自回归语言模型。它能流畅地生成文本,但生成时只能利用上文信息,无法预知下文。
💡 ELMo和GPT的最大局限:都不是“真正”的双向编码。 前者是两条腿分开走,后者是一条腿朝前迈。无论哪种,模型在处理当前词时都无法同时看到它左右的全部上下文。
1.2 BERT的颠覆:双向Transformer编码器
BERT的设计源自一个简单但关键的问题:如果一个语言模型可以同时看到每个词左右两侧的上下文,它的理解能力会变得有多强?
BERT直接舍弃了解码器部分,只保留了原始Transformer的编码器栈(Encoder stack),并用多层堆叠的方式构建深度双向表示。模型架构如下:
输入: "我 爱 [MASK] 然 语 言 处 理"
↓
Token Embedding(语义向量)
+
Segment Embedding(句子标识向量)
+
Position Embedding(位置向量)
↓
Transformer Encoder × N层
↓
输出向量(每个位置融合了全部上下文)
BERT采用了纯粹的双向Transformer block堆叠——每个位置的词元可以同时关注到句子中所有其他位置的词元(不包括未来的掩码)。
实际上,BERT的架构可以看作把GPT中的解码器换成了编码器,把单向注意力换成了双向注意力。
1.3 BERT vs GPT vs ELMo:谁更擅长理解?
| 模型 | 架构基础 | 注意力方向 | 主要用途 | 理解能力 |
|---|---|---|---|---|
| ELMo | 双向LSTM | 浅层拼接 | 特征提取 | 一般 |
| GPT | Transformer解码器 | 单向(从左到右) | 文本生成 | 一般 |
| BERT | Transformer编码器 | 双向 | 文本理解 | 极强 |
BERT在相同参数规模下的理解类任务上远超GPT-1和ELMo。
二、BERT的预训练任务:MLM + NSP
一个模型真正的“灵魂”在于它的训练目标。BERT的成功离不开两个精心设计的自监督预训练任务。
2.1 MLM(掩码语言模型)——让模型学会“完形填空”
传统单向语言模型无法进行双向训练的根本原因就在于:如果让模型直接预测下一个词,那么为了避免“作弊”,模型只能看到左侧信息,无法同时看到右侧。为了突破这一限制,BERT设计了掩码语言模型(MLM)。
工作机制:
- 从输入序列中随机选择15%的词元作为预测目标。
- 对这15%的目标词元,按以下概率处理:
- 80%:替换为特殊的
[MASK]token; - 10%:替换为词表中的随机词元;
- 10%:保持不变(保留原词)。
- 80%:替换为特殊的
- 模型需要根据未被掩码的上下文,预测原始词元是什么。
举个例子:原始句子是“猫在沙发上睡觉”,随机选中“在”作为目标,就有可能变为“猫[MASK]沙发上睡觉”,模型需要根据“猫”和“沙发上睡觉”推断出原词是“在”。
当被选中的词元被随机替换(比如“在”变成了“狗”),模型接收到的输入是错误的词,但要学的是输出正确的原始词元“在”。
为什么不是100% [MASK]?
BERT原文采用80-10-10策略是为了平衡预训练和下游任务的差异:
- 80%
[MASK]:提供足够明确的监督信号,迫使模型学习根据上下文推断词元。这是训练的核心; - 10%随机词:让模型意识到,在实际的下游推理任务中(没有
[MASK]),输入的词元也可能是错的,模型不能“偷懒”地只依赖特殊token,而必须始终结合上下文做综合判断; - 10%保留原词:防止模型在预训练阶段完全依赖
[MASK]特殊标记,让模型接触到真实的语言面貌,同时对自身token产生有效表征。
面试高频题:“为什么MLM要设计80-10-10的掩码策略?” 答案:全采用
[MASK]会导致训练-推理不一致——下游任务中没有这个标记。引入随机词和保留原词可以迫使模型学会综合上下文信息做出判断,而不是机械地依赖[MASK]。
2.2 NSP(下一句预测)——理解句子间的关系
很多NLP任务(如问答、自然语言推理)需要理解两个句子之间的逻辑关系。为此,BERT额外加入了下一句预测(Next Sentence Prediction, NSP) 任务。
工作机制:
- 从语料库中选取真实的连续句子对(A,B),标记为
IsNext; - 50%概率将B替换成其他文档中的随机句子C,标记为
NotNext; - 模型需要判断输入的句子对是否在原文中连续出现——本质上是一个二分类任务。
输入格式:两个句子用[SEP]分隔,序列开头用[CLS]标记。整个序列中同样应用MLM(部分词元可能被掩码)。最终,[CLS]位置的输出向量被用于预测句子对关系。
后续研究(如RoBERTa)发现NSP在部分任务上的收益有限,甚至可以移除。但对于需要强句子级语义关联的任务(如问答、推理),NSP仍然是有价值的。
2.3 MLM + NSP:模型学到了什么?
| 预训练任务 | 学习目标 | 学到什么 |
|---|---|---|
| MLM | 预测被掩码的词元 | 词元层面的上下文关系、语义理解 |
| NSP | 判断两个句子是否连续 | 句子层面的篇章结构、逻辑连贯性 |
两个任务联合训练,使得BERT同时具备词元级和句子级的深度理解能力。因此BERT在文本分类、命名实体识别(NER) 等任务中表现尤为突出。
三、BERT的模型结构:BASE vs LARGE
3.1 两个标准版本
BERT论文构建并公开了两个不同规模的预训练模型:
| 配置 | BERT-base | BERT-large |
|---|---|---|
| Transformer层数 | 12 | 24 |
| 隐藏层维度(d_model) | 768 | 1024 |
| 注意力头数 | 12 | 16 |
| 前馈网络维度(d_ff) | 3072(768×4) | 4096(1024×4) |
| 总参数量 | 110M | 340M |
📌 BERT-base与GPT-1的参数量相当(GPT-1为117M),区别在于BERT用的是Transformer编码器(双向),GPT用的是解码器(单向)。这一“参数持平但架构不同”的设计,在后续实验中被证明是极其关键的对较量。
3.2 模型结构细节
输入表示
BERT的输入融合了三重嵌入:
- 词元嵌入:将词元ID映射为语义向量;
- 段嵌入:标识词元属于句子A还是句子B(第一个句子或第二个句子);
- 位置嵌入:对应位置索引的可学习向量,而不是正弦余弦编码。
三种嵌入向量逐项相加后,再输入到第一层编码器。
特殊词元:
[CLS](分类标记):放在序列开头,其最终输出向量被用于分类任务;[SEP](分隔标记):用于分隔两个句子。
3.3 为什么需要不同规模?
- BERT-base(110M参数):适合资源有限的实际部署,推理速度较快,在大多数下游任务中已有非常出色的表现。
- BERT-large(340M参数):适合对准确率要求极高的场景,但需要更多的GPU内存和更长的微调时间。其参数量是base的三倍多。
四、BERT的应用场景与微调
4.1 BERT的典型应用任务
BERT是理解类任务的绝对标杆:
- 文本分类:包括情感分析、垃圾邮件过滤、新闻主题分类等。高效做法:在BERT编码器的
[CLS]输出上接一个分类层,只需微调顶层参数。在全量训练数据上fine-tune后准确率远超传统方法。 - 命名实体识别(NER):将文本输入BERT,获取每个词元的向量表示,再用BIO/BIOES标注体系对每个词元进行独立分类(同时可叠加CRF层进一步对齐标签序列)。
- 问答系统:将问题和段落拼接输入BERT,在输出层预测答案起始、结束位置。
- 自然语言推理:判断两个句子是否构成蕴含关系。
4.2 BERT微调实战——二分类情感分析(完整可运行代码)
微调是指在少量有标签的数据集上继续训练模型的全部参数,将BERT从通用语言理解适配到特定任务。下面用Hugging Face Transformers实现一个完整的微调示例。
步骤1:安装依赖
# 安装(如未安装)
# pip install transformers datasets torch scikit-learn
步骤2:完整微调代码(情感分类)
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
from datasets import Dataset
from sklearn.metrics import accuracy_score, classification_report
# ========================= 1. 准备数据 =========================
texts = [
"This movie is fantastic! I absolutely loved every minute of it.",
"What a complete waste of time. Terrible acting and boring plot.",
"Great cinematography but the story was a bit weak. Still recommend.",
"I fell asleep halfway through. So boring and predictable.",
"A masterpiece! The acting, the story, everything is perfect.",
"Not worth the hype. Really disappointed.",
]
labels = [1, 0, 1, 0, 1, 0] # 1 = 正面, 0 = 负面
# 使用datasets.Dataset构建训练集
train_data = Dataset.from_dict({"text": texts, "label": labels})
# ========================= 2. 加载BERT分词器和模型 =========================
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
def tokenize_function(examples):
"""对数据集中的每个样本进行编码(padding + truncation)"""
return tokenizer(
examples["text"],
padding="max_length",
truncation=True,
max_length=128
)
# 对整个数据集进行tokenize(并移除原文本字段)
tokenized_train = train_data.map(tokenize_function, batched=True)
tokenized_train = tokenized_train.remove_columns(["text"])
tokenized_train.set_format("torch")
# ========================= 3. 设置训练参数 =========================
training_args = TrainingArguments(
output_dir="./bert_finetuned", # 模型输出目录
num_train_epochs=3, # 训练轮数
per_device_train_batch_size=2, # 小数据集降低batch size
learning_rate=2e-5, # BERT微调的经典学习率
weight_decay=0.01, # L2正则化防止过拟合
logging_dir="./logs",
logging_steps=10,
save_strategy="epoch",
report_to="none", # 禁用wandb/tensorboard报告
)
def compute_metrics(eval_pred):
"""计算准确率用于辅助观察"""
logits, labels = eval_pred
predictions = torch.argmax(torch.tensor(logits), dim=-1)
acc = accuracy_score(labels.numpy(), predictions.numpy())
return {"accuracy": acc}
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_train,
compute_metrics=compute_metrics,
)
# ========================= 4. 训练 =========================
trainer.train()
# ========================= 5. 测试推理 =========================
test_texts = [
"Absolutely brilliant! I loved it.",
"This is the worst movie ever made.",
]
test_encodings = tokenizer(test_texts, padding=True, truncation=True, return_tensors="pt", max_length=128)
model.eval()
with torch.no_grad():
outputs = model(**test_encodings)
predictions = torch.argmax(outputs.logits, dim=-1)
print("\n=== 预测结果 ===")
for text, pred in zip(test_texts, predictions):
label = "正面" if pred == 1 else "负面"
print(f"文本: {text}\n情感: {label}\n")
# ========================= 6. 保存模型 =========================
model.save_pretrained("./my_bert_sentiment_model")
tokenizer.save_pretrained("./my_bert_sentiment_model")
print("模型已保存至 ./my_bert_sentiment_model")
运行后输出示例:
=== 预测结果 ===
文本: Absolutely brilliant! I loved it.
情感: 正面
文本: This is the worst movie ever made.
情感: 负面
4.3 微调时需要注意的关键点
- 学习率:一般取
2e−5到5e−5,比从头训练低1-2个数量级,避免破坏预训练权重; - 权重衰减:
weight_decay=0.01,对全量参数施加L2正则化; - 轮数:通常2-4轮即可收敛;小数据集上可适当增加,但需警惕过拟合;
- batch size:受GPU显存限制,序列越长可设置的batch size越小。建议动态折算后再定,避免内存溢出;
[CLS]token:分类任务中,[CLS]的输出向量被视为整个序列的聚合表示,直接喂入分类头。
五、BERT vs GPT:两座“高峰”的全面对比
5.1 根本性差异
BERT和GPT不仅是架构不同,它们的预训练目标、注意力机制、适用场景也完全不同:
| 维度 | BERT(编码器-预训练范式) | GPT(解码器-预训练范式) |
|---|---|---|
| 核心架构 | 仅Transformer编码器 | 仅Transformer解码器 |
| 注意力方向 | 双向(每个词能看到全句) | 单向(每个词只能看到左侧上下文) |
| 预训练任务 | MLM(完形填空) + NSP(下一句预测) | CLM(预测下一个词,自回归) |
| 天生优势 | 深度理解文本语义 | 流畅生成连贯文本 |
| 典型应用 | 情感分析、命名实体识别、问答、文本分类 | 文本续写、对话系统、代码生成、翻译 |
| 上游输入特征 | 可利用[CLS]或覆盖每个token的深度双向特征 |
只能利用左侧历史token |
5.2 “苹果手机”VS“苹果水果”:双向理解的价值
BERT的双向能力使其能根据上下文动态消歧。示例:
- 情景A:“我买了一部苹果” → BERT能识别“苹果”指的是手机,因为下文语境提示“买一部”是产品的典型说法;
- 情景B:“吃了一个苹果” → BERT能识别“苹果”是水果,因为上文“吃了”与消化相关。
而GPT在处理同一句话时,只能从左向右顺序处理。例如在“吃了一个苹果”中,在看到“苹果”前,模型已经阅读了“吃了一个”的全部上文,仍然可以判断语义;但失去了双向同时对照的优势,在某些情况下理解精度可能不如BERT。
5.3 为什么GPT不用双向注意力?
根本原因是输出目标:
- GPT用于生成。如果训练时允许看到未来的词,推理时却没这种条件,就会产生训练-推理不一致(Exposure Bias),严重影响生成质量。
- BERT用于理解,只需要判断输入文本的语义,不涉及自回归生成,所以可以放心使用双向上下文,最大化语义丰富度。
5.4 融合趋势
BERT和GPT正在相互吸纳对方的优势。GPT通过RoPE等设计吸收了相对位置编码的前沿成果,而BERT的变种T5、UniLM等也开始融合自回归结构。两条路径逐渐从分立走向统一,推动NLP技术整体前进。
六、常见的BERT面试考点
Q1:BERT为什么使用“MLM+双向编码”而不是“CLM+单向编码”?
答:BERT的目标是构建深度文本理解模型,需要每个词元的表示融合全部上下文信息。如果采用CLM的单向注意力,会损失右侧上下文,无法达到同样的理解效果。
Q2:BERT和GPT在预训练数据量相同的情况下,谁的理解能力更强?
答:在理解类任务(如情感分类、GLUE基准)上,BERT普遍优于同等数据量的单向GPT模型。这已被大量实验证实。
Q3:BERT能否用于文本生成?
答:可以在特定任务上通过编码器-解码器的改造实现生成,但原始的纯编码器BERT无法进行连贯的自回归生成。BERT没有生成结构,需要额外加解码器。
Q4:BERT的[CLS] token为什么能代表整个句子的语义?
答:在预训练阶段,[CLS] token通过自注意力机制聚合了所有其他token的信息,并且NSP任务专门训练这个位置做句子对分类,因此[CLS]位置的输出逐步演化为全句的语义摘要向量。
Q5:BERT的参数规模发展到今天是否太小了?
答:BERT-large(340M参数)相比当今的大模型(如GPT-3的175B)确实小了数百倍,但BERT的得分意义不在“大”。它是预训练-微调范式和双向理解的开山鼻祖,现代更先进的预训练模型(RoBERTa、DeBERTa、ELECTRA)仍深度继承其核心设计思路。
七、课后延伸
动手实验
- 运行本节的情感分析微调代码,使用真实数据集(如SST-2或ChnSentiCorp)训练一个情感分类器。
- 对比双向与单向的效果差异:手动修改BERT为单向解码器风格(提示:通过修改注意力掩码矩阵),在相同数据集上观察效果退化。
- 加载不同版本BERT,理解参数量差异对微调收敛速度的影响。
论文阅读
- BERT原始论文:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding (Google, 2018);
- 特别章节:Section 3.2(Task-Specific Architecture)和Section A(Detailed Experimental Setup),有助于理解BERT如何实现“开箱即用的微调”。
思考题
- 如何将BERT用于多标签分类(如一篇新闻同时属于多个类别)?
- 如果序列长度超过BERT的max_length限制(通常512),该如何处理?
- BERT的增量预训练(continued pre-training)可以提升领域任务表现,它的理论基础是什么?
下节课预告
至此,我们完整解析了文本理解领域的标杆——BERT模型。BERT如何设计双向注意力?MLM和NSP怎么联合训练?如何用短短的代码完成微调?你已经有了答案。
同时我们也清楚,单向生成模型同样不可忽视。GPT系列模型沿着单向自回归路线走向了生成大语言模型的极致。
下一节课我们将拆解:
【第15课:GPT生成模型全解析——从单向注意力到大语言模型】
- 如何只用解码器实现预训练
- 从GPT-1到GPT-4的进化路线
- 自回归生成工程的优化要点
- GPT和BERT如何共筑今天的基座模型生态
学完这节课,你将对NLP领域两个最重要的预训练流派建立起全局理解。
我们第15课见!
附录:常见Transformer微调超参数速查表
| 超参数 | BERT微调推荐值 | 备注 |
|---|---|---|
| 学习率 | 2e−5 ~ 5e−5 |
经典值:2e−5 |
| 权重衰减(weight_decay) | 0.01 |
L2正则化 |
| Batch Size | 16 / 32 |
GPU显存允许下尽量增大 |
| Epochs | 2 ~ 4 |
小数据集多加几轮,但需早停 |
| Max Sequence Length | 512(上限) |
过长超出则需截断 |
| Warmup Ratio | 0.1 |
前10%步数线性预热 |
| Dropout | 0.1 |
可依据数据集规模微调 |
恭喜你!现在你已经掌握了BERT这个革命性模型的核心原理与实战技巧。未来的NLP之路,你已经扎根于Transformer的基石之上。
🔗 Transformers模型架构系列课程导航
去专栏阅读
模块1:Transformers入门基础(第1-6课)
模块核心目标:帮助零基础读者快速入门,搭建Transformers的基础认知框架,了解其起源、发展背景及核心应用场景,掌握必备的前置知识,为后续核心原理学习奠定基础,降低入门门槛。
模块2:Transformers核心架构与原理(第7-13课)
模块核心目标:深入拆解Transformers的核心架构(编码器、解码器),掌握每个子模块的工作原理、作用及实现逻辑,理解各模块之间的协同工作机制,突破理论难点,为后续模型解析与实战奠定基础。
模块3:Transformers经典模型解析(第14-20节课)
模块核心目标:逐个拆解Transformers领域的经典模型(BERT、GPT、T5等),分析每个模型的核心改进、预训练任务、适用场景与优缺点,让读者掌握不同模型的差异,能根据实际任务选择合适的模型,兼顾理论深度与应用落地。
模块4:Transformers实战与优化(第21-26课)
模块核心目标:聚焦实战落地,从环境搭建、工具使用到具体任务实操,让读者掌握Transformers模型的训练、微调、部署方法,学习实战中的优化技巧,解决实际项目中的常见问题,确保每节课都有具体的实操案例,让读者“会应用、能落地”。
模块5:Transformers行业应用与前沿拓展(第27-30课)
模块核心目标:结合不同行业的实际应用场景,讲解Transformers的落地案例,让读者了解其行业应用价值;同时覆盖当前Transformers的前沿趋势,帮助读者把握技术发展方向,提升专栏的前沿性与实用性。
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)