预训练语言模型 BERT 原理解析
内容总览
| 模块 | 核心问题 | 关键词 |
|---|---|---|
| BERT 为什么重要 | 为什么它会成为 NLP 通用方案 | 预训练、微调、双向编码 |
| BERT 的前置基础 | 传统方法有什么局限 | Word2Vec、RNN、Transformer |
| Self-Attention 原理 | 模型如何理解上下文关系 | Q、K、V、Softmax |
| Transformer 架构 | BERT 建立在哪个骨架上 | 编码器、多头注意力、位置编码 |
| BERT 训练方法 | 模型如何学到语言知识 | MLM、NSP |
| 下游任务使用 | 预训练模型如何迁移到任务 | 分类、阅读理解、微调 |
一句话理解 BERT
BERT(Bidirectional Encoder Representations from Transformers)本质上是一个基于 Transformer Encoder 的双向预训练语言模型。它先在大规模无标注文本上学习通用语言表示,再将这些表示迁移到分类、阅读理解、序列标注等下游任务中。
一、为什么要学 BERT
1. BERT 的定位
- BERT 是自然语言处理中的通用解决方案之一,适合文本分类、情感分析、问答、句子关系判断等任务。
- 它最大的价值不在于直接“做任务”,而在于先学会高质量的文本特征表达。
- 相比传统模型需要从头为每个任务设计特征,BERT 通过“预训练 + 微调”显著降低了任务开发门槛。
- 从课程视角看,BERT 不只是一个模型名,更是一套**“先学语言、再做任务”**的通用方法论。
2. BERT 的几个关键优势
- 双向上下文建模:同时利用左侧和右侧上下文,而不是只看单侧。
- 上下文相关词向量:同一个词在不同句子里会得到不同表示。
- 迁移能力强:先在大语料上训练,再迁移到具体任务。
- 工程可用性强:官方与社区提供大量开源预训练模型和工具链。
3. 课程里的工程背景
课程里强调,BERT 不只是理论模型,也是一套可以直接落地的工程方案:
- Google 提供了开源实现与预训练模型;
- 常见代码入口包括
modeling.py、run_classifier.py、tokenization.py; - 实际开发中重点通常不是“从零发明模型”,而是理解输入输出形式,再把预训练模型接到具体任务上;
- 课程也特别强调应优先使用具备完整调试能力的 IDE,而不是只在 Notebook 里停留在演示层面。
4. 一个总览对比
| 方案 | 主要优点 | 主要问题 | BERT 相对优势 |
|---|---|---|---|
| Word2Vec | 简单高效,能学词向量 | 词向量静态,不看上下文 | 能生成上下文相关表示 |
| RNN | 适合序列建模 | 串行计算、长依赖弱 | 并行训练、全局建模 |
| Transformer | 全局注意力、并行性强 | 需要额外补位置信息 | 为 BERT 提供最优骨架 |
| BERT | 预训练 + 微调,通用性强 | 训练成本高 | 下游任务迁移效果强 |
二、BERT 之前的方法为什么不够好
1. RNN 的问题
RNN 适合处理序列,但有明显局限:
- 每个时间步依赖上一步隐藏状态,天然串行,难以并行计算。
- 随着序列变长,早期信息容易衰减,长距离依赖建模能力有限。
- 网络层数通常不敢堆太深,训练效率和表达能力都受限制。
2. Word2Vec 的问题
Word2Vec 的词向量是静态的:
- 一个词训练完以后向量固定不变。
- 无法很好处理一词多义。
- 不能根据上下文动态调整语义表示。
例如同一个词出现在不同语境时,真正需要的表示应该不同,而静态词向量做不到这一点。
3. Transformer 为什么成为突破口
Transformer 引入 Self-Attention 后:
- 可以一次看完整个序列;
- 可以直接建模任意两个词之间的关系;
- 可以通过矩阵运算实现并行训练。
这为 BERT 提供了理想的底层架构。
三、Transformer 是 BERT 的基础骨架
1. Transformer 的整体结构
经典 Transformer 由两部分组成:
- Encoder(编码器)
- Decoder(解码器)
而 BERT 只使用其中的 Encoder 部分,因为它的目标是获得输入文本的高质量表示,而不是像机器翻译那样逐词生成输出。
2. 为什么 BERT 只用 Encoder
- Encoder 可以看到整个输入句子。
- 它更适合做特征抽取与语义编码。
- ==BERT 的核心任务是“理解文本”,不是“自回归生成文本”==。
可以把 Transformer 的两部分粗略区分为:
- Encoder 更偏“理解输入”;
- Decoder 更偏“根据已有信息一步步生成输出”。
因此,BERT 只保留 Encoder,是因为它的重点是学到高质量文本表示;如果任务目标主要是生成文本,那么通常更依赖 Decoder 结构。
因此可以把 BERT 理解为:
用 Transformer 的编码器结构,专门训练一个强大的文本理解器。
四、Self-Attention 是 BERT 的核心机制
1. Self-Attention 在做什么
Self-Attention 的目标是:
计算序列中每个词与其他所有词之间的关系强弱,再融合全局上下文,生成新的词表示。
这意味着一个词最终的编码不只由它自己决定,还会受到全句其他词的影响。
可以把 Self-Attention 理解成:句子里每个词都在问,“为了更好理解我自己,我应该重点参考句子中的哪些词?”
例如句子:
小明把书放在桌子上,然后他离开了教室。
当模型处理“他”这个词时,它会和“小明”“书”“桌子”“教室”等词都计算相关性。如果最终“小明”的权重最高,模型就更容易知道“他”指代的是“小明”。
这也是 Self-Attention 能处理代词指代和长距离依赖的直观原因。
2. Q、K、V 三个矩阵的含义
输入词向量经过三个不同线性变换后,得到:
- Query(Q):当前词“想关注什么”
- Key(K):当前词“能提供什么索引信息”
- Value(V):当前词真正承载的特征内容
可以粗略理解为:
- Q 负责发问
- K 负责匹配
- V 负责提供信息
3. Self-Attention 的计算流程
第一步:词向量输入
- 先把词或字转换成 embedding 向量。
- 每个位置都会得到一个基础表示。
第二步:生成 Q、K、V
- 每个输入向量分别乘以参数矩阵,得到对应的 q、k、v。
第三步:计算相关性分数
- 用当前词的 q 与所有词的 k 做内积。
- 内积越大,说明当前词与对方词越相关。
第四步:通过 Softmax 转成权重
- 原始分数只是”强弱”,还不能直接做加权。
- 用 Softmax 把它们归一化成概率分布。
- 权重越大,说明那个词对当前词编码影响越大。
第五步:对 V 加权求和
- 用这些权重对所有 value 向量做加权求和。
- 得到当前词新的上下文表示。
如果把整个过程压缩成一句话,就是:
先用 Q 和所有 K 算”我该关注谁”,再用 Softmax 把关注程度变成权重,最后对所有 V 做加权求和,得到新的上下文表示。
也可以进一步记成:
- Q / K 决定”看谁”
- Softmax 决定”看多少”
- V 决定”真正拿到什么信息”
5. Self-Attention 的数学表达
给定输入序列 ,其中每个 :
线性变换:
其中 ,
注意力输出:
逐位置展开形式: 对于位置 的输出:
其中注意力权重:
缩放因子的作用:
- 防止 内积结果过大,导致 Softmax 进入梯度消失区
- 使训练更稳定,梯度更容易回传
4. Self-Attention 带来的本质变化
- 词表示不再固定,而是依赖上下文动态生成。
- 每个词都可以直接与全句其他词交互。
- 可以更好处理代词指代、语义歧义、长距离依赖等问题。
五、Softmax、多头注意力与特征增强
1. Softmax 的作用
Self-Attention 中先得到相关性分数,但这些分数不能直接表示“贡献比例”。
Softmax 的作用就是:
- 把分数变成可比较的权重;
- 突出更重要的词;
- 保证所有权重之和为 1。
2. Multi-Head Attention 为什么有效
如果只用一组 Q/K/V,模型只能从一种角度理解句子。
多头注意力通过多组独立的 Q/K/V:
- 并行学习多种关系模式;
- 有的头更关注语法关系;
- 有的头更关注语义搭配;
- 有的头更关注远距离依赖。
可以把多头注意力理解成:同时安排多个“观察员”从不同角度分析同一句话。
- 有的观察员更关注句法结构;
- 有的观察员更关注语义搭配;
- 有的观察员更关注长距离依赖;
- 有的观察员更关注整句主题。
最终将多个头的结果拼接,再通过线性层融合,得到更丰富的表示。
3. Multi-Head Attention 的数学表达
设有 个注意力头,每个头学习独立的参数:
第 个头的输出:
其中 ,
多头拼接与输出投影:
其中 是输出投影矩阵。
典型维度设置(BERT Base):
- (12 个头)
- (每头 64 维)
- 验证:
4. 为什么要堆叠多层
单层注意力只能做一次上下文融合。
多层堆叠后:
- 底层学局部关系;
- 中层学短语和句法结构;
- 高层学更抽象的语义信息。
这也是 BERT 能形成深层语言理解能力的关键。
六、位置编码、残差连接与归一化
1. 为什么需要位置编码
Self-Attention 只看词与词的关系,不天然保留顺序。
因为 Attention 本身只知道”谁和谁相关”,却不知道”谁在前、谁在后”。
如果没有位置编码,模型很难区分:
- “今天下午去打篮球”
- “下午去打篮球今天”
因此必须显式加入位置信息。
2. 位置编码怎么加入
经典 Transformer 中:
- 为每个位置生成一个位置向量;
- 再与词向量直接相加。
这样模型在关注词语内容的同时,也能感知词语在句子中的顺序位置。
3. 位置编码的数学表达(原始 Transformer)
原始 Transformer 使用正弦/余弦函数生成位置编码:
对于位置 和维度 :
这种设计的好处:
- 不同位置产生不同的编码;
- 相对位置关系可以被学习(因为 可以表示为 的线性函数);
- 可以外推到比训练时更长的序列。
BERT 的位置编码:
BERT 不使用上述三角函数形式,而是学习一个可训练的位置嵌入矩阵 ,其中 是最大序列长度(如 BERT Base 为 512)。
输入表示为:
其中:
- :词/token 嵌入
- :位置嵌入
- :句子 A/B 段嵌入(用于区分句对任务)
3. 残差连接的作用
每个子层后都会保留原始输入,与子层输出相加。
如果每一层都完全覆盖上一层信息,深层网络会越来越难训练,因此残差连接相当于给原始信息保留了一条直通通道。
作用包括:
- 避免深层网络退化;
- 缓解梯度消失;
- 保留原始信息,方便模型自主选择是否使用新特征。
4. 残差连接的数学表达
对于任意子层(如 Self-Attention 或 Feed-Forward),输出为:
其中:
- 是子层输入
- 是子层的变换(如 Self-Attention)
- 加法 是残差连接
- LayerNorm 在残差相加后应用
梯度回传视角:
损失函数对 的梯度:
其中的 保证即使 很小,梯度也能直接回传。
4. Layer Normalization 的作用
LayerNorm 会对单个样本内部的特征做归一化。
它可以让每层输出的数值分布更稳定,减少训练过程中的震荡,帮助深层模型更容易收敛。
它的作用是:
- 稳定训练;
- 加快收敛;
- 让深层网络更容易优化。
5. Layer Normalization 的数学表达
给定输入向量 ,LayerNorm 的计算为:
其中:
- 是均值
- 是方差
- 是很小的常数(如 ),防止除零
- 是可学习的缩放和平移参数
- 是归一化后的输出
与 BatchNorm 的关键区别:
BatchNorm 在批次维度上计算统计量:
LayerNorm 在特征维度上计算统计量:
因此 LayerNorm 不依赖批次大小,更适合序列模型。
5. Layer Normalization vs Batch Normalization
| 对比维度 | LayerNorm | BatchNorm |
|---|---|---|
| 归一化方向 | 对单个样本的所有特征做归一化 | 对一批样本的同一特征做归一化 |
| 依赖批次大小 | 不依赖,单样本即可计算 | 依赖,批次太小效果会变差 |
| 适用场景 | RNN、Transformer、BERT | CNN 图像任务更常见 |
| 训练/测试一致性 | 训练和测试行为一致 | 训练用批次统计,测试用移动平均 |
| 为什么 BERT 用 LayerNorm | 序列长度可变、批次可能较小,LayerNorm 更稳定 | 图像任务中批次通常较大且固定 |
直观理解:
- BatchNorm 像是"横向"归一化——看一批样本中同一个特征的平均和方差;
- LayerNorm 像是"纵向"归一化——看单个样本内部所有特征的平均和方差。
对于 BERT 这种处理变长序列的模型,LayerNorm 不需要依赖批次统计量,因此更适合。
七、BERT 到底学到了什么
1. BERT 学的不是“标签”,而是语言表示
BERT 预训练阶段不依赖人工标注类别,而是通过设计自监督任务,让模型从海量文本中学习:
- 词与词的关系
- 句与句的关系
- 上下文中的语义表达
2. BERT 输出的是上下文相关表示
和 Word2Vec 最大区别在于:
- Word2Vec:每个词一个固定向量
- BERT:同一个词在不同上下文下向量不同
这使得 BERT 更适合真实语言理解任务。
八、BERT 的两大预训练任务
1. MLM:Masked Language Model
核心思想
随机遮住输入中一部分词,让模型根据上下文预测原词。
课程中的关键点
- 通常随机选取 15% 的词进行处理。
- 被选中的词不一定全部替换成
[MASK],也可能:- 替换成
[MASK] - 替换成其他词
- 保持原词不变
- 替换成
中文场景下怎么理解
在中文场景里,很多实现会更自然地以"字"或子词(subword)作为基本处理单位,因此 mask 时常常表现为对单字或若干子词进行遮蔽。
这说明 MLM 并不是死记某个完整单词,而是在当前 token 粒度上学习上下文恢复能力。
它训练了什么能力
MLM 迫使模型同时利用左右上下文,因此能学到真正的双向语义表示。
例如模型看到一句话中间某个词被遮住,只有理解整句语义,才能较准确地恢复原词。
MLM 的数学表达
给定输入序列 ,随机 mask 部分位置 :
模型预测:
其中:
- 是被 mask 后的输入
- 是 BERT 对位置 的输出表示
- 是词汇表投影矩阵
- 是词表大小(BERT Base 约 30522)
损失函数(交叉熵):
其中 是位置 的真实词。
15% mask 策略的细节:
对被选中的 15% token:
- 80% 替换为
[MASK] - 10% 替换为随机词
- 10% 保持不变
这样设计的原因:
- 如果总是用
[MASK],训练和微调分布不一致 - 随机替换和保持不变让模型不能过度依赖
[MASK]标记
2. NSP:Next Sentence Prediction
核心思想
给模型两个句子,判断第二个句子是不是第一个句子的下一句。
输入形式
- 使用
[CLS]放在句首,作为分类汇总位置; - 用
[SEP]分隔两个句子; - 最终基于
[CLS]对应向量做二分类判断。
它训练了什么能力
- 学习句子间连贯性;
- 学习段落级语义关系;
- 为问答、检索、句对匹配等任务提供基础。
NSP 的数学表达
给定句子对 ,输入格式为:
模型预测:
其中:
- 是
[CLS]位置的输出表示 - 是分类参数
- 是 sigmoid 函数(二分类)
损失函数:
其中 是真实标签。
总预训练损失:
九、BERT 中几个特殊标记的作用
1. [CLS] —— 全局表示聚合标记
位置: 始终放在输入序列的开头。
核心作用: 聚合整句或句对的全局语义表示,用于句子级任务。
为什么 [CLS] 能代表整句?
在 Self-Attention 机制中,[CLS] 位置的 Query 会与序列中所有 token 的 Key 计算注意力权重:
其中:
这意味着 [CLS] 的输出表示是全句所有 token 的加权和,权重由注意力机制学习得到。经过多层 Transformer 堆叠后,[CLS] 最终编码了整句的语义信息。
典型使用场景:
| 任务 | 使用方式 |
|---|---|
| 文本分类 | 取 接 softmax 分类层 |
| NSP(下一句预测) | 取 接二分类层 |
| 句子相似度 | 取句对的 计算相似度 |
| 情感分析 | 取 接情感分类层 |
输入示例:
单句输入:
[CLS] 今 天 天 气 很 好 [SEP]
句对输入(如 NSP):
[CLS] 今 天 天 气 很 好 [SEP] 我 想 出 去 散 步 [SEP]
└── 句子 A ──┘ └── 句子 B ──┘
2. [SEP] —— 句子分隔标记
位置: 放在句子末尾,用于分隔句子或标记序列结束。
核心作用:
- 明确标识句子边界
- 配合 Segment Embedding 区分不同句子
- 在某些任务中标记序列结束
在 NSP 任务中的作用:
NSP 需要判断句子 B 是否是句子 A 的下一句,输入格式为:
[CLS] 句子 A [SEP] 句子 B [SEP]
模型通过 [SEP] 识别:
- 句子 A 从哪里结束
- 句子 B 从哪里开始
- 两个句子的相对位置关系
在阅读理解中的作用:
输入"文章 + 问题"时:
[CLS] 文章段落 [SEP] 问题内容 [SEP]
└─ 文章 ──┘ └─ 问题 ─┘
[SEP] 帮助模型区分:
- 哪部分是待定位的文章
- 哪部分是查询问题
Segment Embedding 配合:
BERT 为每个 token 添加句子段嵌入 :
- 句子 A 的 token(包括
[CLS]和第一个[SEP]): - 句子 B 的 token(包括第二个
[SEP]):
完整输入表示:
这样模型可以明确区分两个句子的身份。
单句任务中的 [SEP]:
即使只有一个句子,末尾也需要加 [SEP]:
[CLS] 这 是 一 个 句 子 [SEP]
这是为了保持输入格式的一致性。
3. [MASK] —— 掩码标记
位置: 预训练时随机替换输入中的部分 token。
核心作用: 让模型通过上下文预测被遮蔽的词,训练双向语义理解能力。
15% Mask 策略:
对被选中的 15% token:
- 80% → 替换为
[MASK] - 10% → 替换为随机词
- 10% → 保持不变(不替换)
为什么这样设计?
| 策略 | 原因 |
|---|---|
80% 用 [MASK] | 让模型学习根据上下文预测 |
| 10% 随机替换 | 防止模型过度依赖 [MASK] 标记 |
| 10% 保持不变 | 让模型学会"不需要预测"的情况,同时使训练/微调分布一致 |
如果总是用 [MASK],微调时没有这个标记会导致分布不一致,影响迁移效果。
[MASK] 的数学表达:
给定被 mask 的位置集合 ,模型预测:
其中 是位置 的输出,它编码了左右双向的上下文信息。
十、BERT 如何用于下游任务
课程里强调的一个关键思想是端到端训练:
- 传统方法可能先单独训练词向量,再把词向量交给分类器;
- 在 BERT 范式下,文本编码和下游任务训练通常放在同一个框架中联合优化;
- 也就是说,BERT 不只是提供静态特征,而是会和任务头一起被微调。
1. 文本分类
典型流程:
- 输入文本前加
[CLS]; - 文本经过 BERT 编码;
- 取
[CLS]的输出向量; - 接一个线性分类层完成情感分析、主题分类等任务。
文本分类的数学表达
给定输入序列 ,BERT 输出 [CLS] 位置的表示 :
分类预测:
其中:
- , 是类别数
- (BERT Base 为 768 维)
损失函数:
其中 是真实标签的 one-hot 编码。
微调时的参数更新:
同时更新 BERT 参数 和分类层参数 :
文本分类任务详解
任务定义: 给定一段文本,判断它属于哪个类别(如情感正/负、新闻主题、垃圾邮件识别等)。
输入格式:
单句分类:
[CLS] 这 部 电 影 太 精 彩 了 [SEP]
│ │
└── 整句表示 → 分类标签 │
│
Segment A ←─┘
句对分类(如语义相似度):
[CLS] 句子 A [SEP] 句子 B [SEP] → 是否相似/蕴含关系
模型架构:
输入文本
│
▼
┌─────────────────────────┐
│ BERT Encoder │ 12 层 Transformer (Base)
│ (双向上下文编码) │ 或 24 层 (Large)
└─────────────────────────┘
│
▼
[CLS] 向量 (768 维)
│
▼
┌─────────────────────────┐
│ 线性分类层 (Dense) │ W_c: 768 → C
│ + Dropout (可选) │
└─────────────────────────┘
│
▼
Softmax → 类别概率分布
训练流程(一个 batch):
-
数据准备:
- 输入文本 tokenize → token IDs
- 添加
[CLS],[SEP] - 生成 Attention Mask(区分真实 token 和 padding)
- 生成 Segment IDs(句对任务)
-
前向传播:
token_ids → BERT → [CLS]_embedding → Dense → logits → softmax → probs -
损失计算:
-
反向传播:
- 计算梯度
- 同时更新 BERT 和分类层参数
典型超参数设置:
| 参数 | BERT Base | 说明 |
|---|---|---|
| 层数 | 12 | Transformer 层数 |
| 768 | 隐藏层维度 | |
| 学习率 | 2e-5 ~ 5e-5 | 比预训练小 10 倍 |
| Batch Size | 16 ~ 32 | 受显存限制 |
| Epoch | 3 ~ 10 | 数据少时用更多 epoch |
| Dropout | 0.1 ~ 0.3 | 防止过拟合 |
训练技巧:
- 学习率预热(Warmup): 前 10% 步数线性增加学习率,再衰减
- 梯度裁剪(Gradient Clipping): 防止梯度爆炸,常用 max_norm=1.0
- 分层学习率: BERT 底层用更小学习率,顶层用较大学习率
- 早停(Early Stopping): 验证集性能不再提升时停止
代码伪代码示例:
# 前向传播
outputs = bert_model(
input_ids=input_ids, # [batch, seq_len]
attention_mask=attn_mask, # [batch, seq_len]
token_type_ids=seg_ids # [batch, seq_len] (句对任务)
)
cls_embedding = outputs.last_hidden_state[:, 0, :] # 取 [CLS]
logits = classifier(cls_embedding) # [batch, num_labels]
loss = cross_entropy(logits, labels)
# 反向传播
loss.backward()
optimizer.step()
多标签分类 vs 多类别分类
多类别(单标签):
- 输出互斥,用 softmax
- 如:情感分析(正/负/中性)
多标签:
- 输出不互斥,用 sigmoid
- 如:新闻主题(可同时属于"科技"和"财经")
损失用 Binary Cross-Entropy:
2. 阅读理解(Machine Reading Comprehension)
课程中提到的做法是:
- 输入”文章 + 问题”;
- 模型分别预测答案的起始位置与终止位置;
- 本质上是对序列中每个 token 做位置分类。
也就是说,BERT 不一定直接”生成答案文本”,而是从原文中定位答案片段。
可以把它理解成两个位置分类问题:
- 哪个 token 最可能是答案起点;
- 哪个 token 最可能是答案终点。
训练时通常会分别学习一组”起始位置”和”终止位置”的判别参数,去和序列中每个 token 的表示计算得分。
一旦找到这两个位置,就能从原文中截取出答案片段。这也是课程里”阅读理解题”训练实例的核心思想。
阅读理解任务详解
任务定义(SQuAD 格式):
给定:
- 文章(Context/Passage):
- 问题(Question):
输出:
- 答案在原文中的起止位置:,其中
典型数据集:
- SQuAD 1.1/2.0(Stanford Question Answering Dataset)
- CMRC(中文机器阅读理解数据集)
- DuReader(百度阅读理解数据集)
输入格式:
[CLS] 问题内容 [SEP] 文章段落 [SEP]
│ │ │ │
│ │ │ │
│ │ └── 文章 ──┤
│ │ │
│ └── 问题 ──────────────┤
│ │
└──────── Segment A ─────────┤
│
Segment B ←─┘
注意: 实际实现中,问题和文章的顺序可能有两种:
[CLS] + Q + [SEP] + D + [SEP](常见于 HuggingFace)[CLS] + D + [SEP] + Q + [SEP](部分实现)
两种格式都有效,关键是训练和测试保持一致。
模型架构:
┌─────────────────────────────────────────┐
│ 问题:什么是机器学习? │
│ 文章:机器学习是人工智能的一个分支... │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Tokenize + 拼接 │
│ [CLS] 什 么 是...[SEP] 机 器 学 习...[SEP] │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ BERT Encoder │
│ 输出每个 token 的上下文表示 h_i │
└─────────────────────────────────────────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Start │ │ End │
│ Classifier│ │ Classifier│
│ W_start │ │ W_end │
└──────────┘ └──────────┘
│ │
▼ ▼
P_start(i) P_end(j)
│ │
└─────────┬─────────┘
▼
答案:[s, e] = argmax P_start(s) × P_end(e)
阅读理解的数学表达
给定文章 和问题 ,输入拼接为 。
BERT 输出每个位置的表示:
其中只有文章部分的 token 参与答案预测(即 到 )。
起始位置预测:
终止位置预测:
其中:
- 是序列中位置 的输出表示
- 是起始/终止判别参数
- 表示位置 是答案起点的概率
损失函数:
其中 是真实答案的起止位置。
预测时:
通常约束答案长度(如 个 token),避免预测过长的答案。
阅读理解训练流程
1. 数据准备:
对于每个训练样本 :
- Tokenize 问题和文章
- 拼接为统一输入格式
- 生成 Attention Mask
- 生成 Segment IDs(区分问题段和文章段)
- 将答案位置 映射到 token 级别
2. 前向传播:
[问题 + 文章] → BERT → {h_0, h_1, ..., h_L}
│
┌───────────┴───────────┐
▼ ▼
W_start · h + b_start W_end · h + b_start
│ │
▼ ▼
softmax softmax
│ │
▼ ▼
P_start(·) P_end(·)
3. 损失计算:
4. 反向传播:
同时更新 BERT 参数和两个分类器参数:
训练技巧与注意事项
1. 最大序列长度:
BERT 限制最大长度为 512,但文章可能更长。处理策略:
- 滑动窗口: 将长文章切分为多个片段,每个片段与问题拼接
- 截断: 只保留文章前 512 个 token(可能丢失信息)
- 稀疏注意力: 使用 Longformer 等支持长序列的模型
2. 无答案情况(SQuAD 2.0):
有些问题在文章中没有答案。处理方式:
- 添加”无答案”分类头
- 或用特殊位置(如 )表示无答案
3. 评估指标:
- EM(Exact Match): 预测答案与真实答案完全匹配的比例
- F1 Score: 预测答案与真实答案的 token 重叠度
4. 代码伪代码示例:
# 前向传播
outputs = bert_model(
input_ids=input_ids, # [batch, seq_len]
attention_mask=attn_mask, # [batch, seq_len]
token_type_ids=seg_ids # [batch, seq_len]
)
sequence_output = outputs.last_hidden_state # [batch, seq_len, 768]
# 起始位置和终止位置预测
start_logits = linear_start(sequence_output).squeeze(-1) # [batch, seq_len]
end_logits = linear_end(sequence_output).squeeze(-1) # [batch, seq_len]
# 只考虑文章部分的 token(mask 掉问题和特殊 token)
start_logits = start_logits + attention_mask * -1e9
end_logits = end_logits + attention_mask * -1e9
# 损失计算
loss_fct = CrossEntropyLoss()
start_loss = loss_fct(start_logits, start_positions)
end_loss = loss_fct(end_logits, end_positions)
total_loss = (start_loss + end_loss) / 2
# 反向传播
total_loss.backward()
optimizer.step()
5. 预测后处理:
# 找到最可能的起止位置
start_idx = torch.argmax(start_logits, dim=-1)
end_idx = torch.argmax(end_logits, dim=-1)
# 约束:start <= end,且长度不超过阈值
for i in range(batch_size):
if start_idx[i] > end_idx[i]:
end_idx[i] = start_idx[i] # 或跳过此样本
# 截断过长答案
if end_idx[i] - start_idx[i] > max_length:
end_idx[i] = start_idx[i] + max_length
# 将 token 索引转回文本
answers = []
for i in range(batch_size):
answer_tokens = tokens[start_idx[i]:end_idx[i]+1]
answers.append(tokenizer.convert_tokens_to_string(answer_tokens))
阅读理解 vs 文本分类 对比
| 维度 | 文本分类 | 阅读理解 |
|---|---|---|
| 输入 | 单句或句对 | 文章 + 问题 |
| 输出 | 类别标签 | 答案起止位置 |
| 关键向量 | [CLS] | 全文每个 token |
| 分类头 | 1 个 Dense 层 | 2 个 Dense 层(start/end) |
| 损失函数 | 交叉熵 | 位置交叉熵 × 2 |
| 评估指标 | Accuracy/F1 | EM/F1 |
| 典型数据集 | IMDB, AG News | SQuAD, CMRC |
3. 其他任务
BERT 还可以迁移到:
- 命名实体识别
- 句子匹配
- 问答系统
- 信息抽取
- 文本相似度计算
十一、BERT 的训练与使用思路
1. 预训练阶段
- 在大规模无标注语料上训练;
- 目标是学到通用语言知识;
- 重点优化 Transformer Encoder 中的参数。
2. 微调阶段
- 把预训练好的 BERT 接到具体任务头上;
- 用相对较少的标注数据继续训练;
- 模型参数会针对任务进一步调整。
这里的"微调"通常不只是训练最后一个分类器,而是让任务头和 BERT 参数一起更新,因此模型表示会越来越贴合当前任务。
3. 为什么这种范式有效
因为预训练阶段已经学到了大量通用语义结构,下游任务不再需要从零开始学习语言规律,只需要“在已有理解能力上适配任务”。
十二、BERT 的整体理解框架
可以用下面这条主线串起来:
- 传统方法存在瓶颈:RNN 难并行,Word2Vec 静态表示不足。
- Transformer 提供新骨架:依靠 Self-Attention 建模全局关系。
- BERT 继承 Encoder 结构:专注做上下文语义编码。
- 通过 MLM + NSP 预训练:在无监督语料中学习语言知识。
- 通过微调适配任务:把通用表示迁移到分类、问答等具体应用。
十三、易混淆点总结
| 概念 | 容易混淆点 | 正确认识 |
|---|---|---|
| BERT vs Transformer | 以为 BERT 等于完整 Transformer | BERT 主要使用 Transformer 的 Encoder |
| BERT vs Word2Vec | 都是词向量模型 | Word2Vec 是静态词向量,BERT 是上下文相关表示 |
| Self-Attention vs 普通加权 | 以为只是简单加权平均 | Self-Attention 是基于 Q/K/V 与 Softmax 的动态关系建模 |
| MLM | 以为只是遮词填空技巧 | 本质是训练双向上下文理解能力 |
| NSP | 以为只是句子分类 | 本质是训练句间关系建模能力 |
[CLS] | 以为只是普通 token | 它通常承担整句级任务表示与分类功能 |
十四、适合复习时记住的关键结论
- BERT = Transformer Encoder + 双向预训练。
- 核心机制是 Self-Attention,不是 RNN。
- 核心优势是上下文相关表示,不是静态词向量。
- 核心训练任务是 MLM 和 NSP。
- 核心应用方式是预训练 + 微调。
十五、我的理解版总结
如果用最朴素的话解释 BERT:
它先通过海量文本训练出一个“会结合上下文理解句子”的编码器,再把这个编码器迁移到各种自然语言任务中,因此效果强、适配广、工程价值高。
也可以进一步把它理解成:
- Transformer 解决了“怎么高效看全句”的问题;
- BERT 解决了“怎么学到通用语言表示”的问题;
- 预训练 + 微调解决了“怎么把通用能力迁移到具体任务”的问题。