在这里插入图片描述

文章目录


从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)

工作机制:
  1. 从输入序列中随机选择15%的词元作为预测目标。
  2. 对这15%的目标词元,按以下概率处理:
    • 80%:替换为特殊的[MASK] token;
    • 10%:替换为词表中的随机词元
    • 10%保持不变(保留原词)。
  3. 模型需要根据未被掩码的上下文,预测原始词元是什么

举个例子:原始句子是“猫在沙发上睡觉”,随机选中“在”作为目标,就有可能变为“猫[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的输入融合了三重嵌入

  1. 词元嵌入:将词元ID映射为语义向量;
  2. 段嵌入:标识词元属于句子A还是句子B(第一个句子或第二个句子);
  3. 位置嵌入:对应位置索引的可学习向量,而不是正弦余弦编码。

三种嵌入向量逐项相加后,再输入到第一层编码器。

特殊词元

  • [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−55e−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)仍深度继承其核心设计思路。


七、课后延伸

动手实验

  1. 运行本节的情感分析微调代码,使用真实数据集(如SST-2或ChnSentiCorp)训练一个情感分类器。
  2. 对比双向与单向的效果差异:手动修改BERT为单向解码器风格(提示:通过修改注意力掩码矩阵),在相同数据集上观察效果退化。
  3. 加载不同版本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如何实现“开箱即用的微调”。

思考题

  1. 如何将BERT用于多标签分类(如一篇新闻同时属于多个类别)?
  2. 如果序列长度超过BERT的max_length限制(通常512),该如何处理?
  3. 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的前沿趋势,帮助读者把握技术发展方向,提升专栏的前沿性与实用性。


🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐