假设你是一位老师,手里有一本神奇的练习册:每一页都是一篇真实文章,但有些词语被涂改液盖住了,只留下一个空位。学生要做的事情很简单——结合空位前后的句子线索,猜出被盖住的那个词。做完一万道这样的“完形填空”,学生不仅记住了词,还摸清了语法、搭配、逻辑关系,甚至能判断两句话是否连贯。这就是语言理解能力。 2018年,Google 的 BERT 模型就像这样一位学生,它的练习方法就是 MLM(Masked Language Model,掩码语言模型)。BERT 用“掩码填空”的方式读完了维基百科和几千本小说,然后轻松拿下十几项 NLP 任务的第一名。这篇教程将带你完整拆解 BERT MLM:老师怎么出题(掩码策略)、学生怎么做题(双向编码)、附加题是什么(NSP)、题库怎么选(预训练数据)、训练怎么安排(超参数),以及如何把练习能力迁移到真实考场(微调)。全程不堆公式,先讲常识,再补细节。

岗位关联

对 AI 从业者而言,BERT MLM 是预训练范式的基石,理解它才能看懂 GPT、RoBERTa、ELECTRA 等一系列模型的来龙去脉,也是进入 NLP 工程岗位的必备知识。

目录
  1. 可视化演示
  2. 预训练范式
  3. MLM核心机制
  4. 掩码策略细节
  5. 双向编码结构
  6. NSP任务
  7. 预训练数据
  8. 超参数配置
  9. 能力与迁移
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示Agent工具编排中的冲突检测与解决机制,包括优先级仲裁、资源锁定与回退策略,直观理解多工具并行执行中的冲突管理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

工具冲突解决机制 冲突检测:多工具同时请求相同资源时触发冲突 优先级仲裁:按工具优先级或依赖关系决定执行顺序 回退重试:冲突工具回退后按指数退避策略重试 resolve = PriorityArbiter(conflicts) + Backoff(retry_count) 工具冲突解决保障Agent多工具并行安全执行

预训练范式

想象一位班主任的经典安排:开学先发一本巨大的练习册,让学生每天刷题,不针对任何考试——这叫打基础;临近考试再发真题卷,让学生针对性练习——这叫冲刺。BERT 的训练也是两段式:预训练就像刷练习册,在无标注的普通文本上做“完形填空”,获得通用的语言能力;微调就像考前冲刺,用少量带标签的数据把能力导向特定任务。

为什么练习册的答案不需要老师批改?因为答案就在题目本身里:挖掉的词就是答案,句子就是题目。这种“自己给自己出题、自己给自己评分”的方式叫自监督学习。技术上说,预训练阶段我们只需要海量原始文本,不需要人工标注,成本极低。而早期的 word2vec 虽然也能从文本中学习词向量,但它生成的是“静态词向量”——同一个词在任何语境下都是同一个向量,“bank“在”河岸“和”银行”里长得一模一样,就像练习册只让学生背单词表而不给句子,学到的只是孤立的词义。

BERT 预训练的目标函数如下:

练习册越厚、刷得越久,学生的语感越扎实。预训练阶段正是靠这种“刷题”让模型积累了丰富的语言先验,后续微调时只需要很少的标注数据就能在具体任务上表现优异——这就是“预训练 + 微调”范式统治 NLP 多年的原因。

LMLM=−∑i∈Mlog⁡P(wi∣x∖M)\displaystyle \mathcal{L}_{\mathrm{MLM}} = -\sum_{i \in \mathcal{M}} \log P\left(w_i \mid \mathbf{x}_{\setminus \mathcal{M}}\right)
这个公式是在说:对每一个被挖掉的词,我们要让模型猜对的概率尽量大,所有位置的“猜错惩罚”加起来就是总损失。
  1. 总损失符号$\mathcal{L}_{\mathrm{MLM}}$ 表示整个掩码语言模型任务的损失,损失越小,模型猜得越准。
    LMLM\mathcal{L}_{\mathrm{MLM}}
  2. 负对数概率$-\log P(w_i \mid \cdots)$ 对正确词 $w_i$ 的预测概率取负对数,概率越大损失越小,概率趋近于 1 时损失趋近于 0。
    −log⁡P(wi∣x∖M)-\log P\left(w_i \mid \mathbf{x}_{\setminus \mathcal{M}}\right)
  3. 对所有掩码位置求和$\sum_{i \in \mathcal{M}}$ 把句子中所有被挖空的位置逐一累加,$\mathcal{M}$ 就是“掩码位置集合”。
    ∑i∈M\sum_{i \in \mathcal{M}}
  4. 条件中的上下文$\mathbf{x}_{\setminus \mathcal{M}}$ 表示“去掉被挖掉那些词之后的整个句子”,这正是模型做题时能看到的残缺文本。
    x∖M\mathbf{x}_{\setminus \mathcal{M}}

MLM核心机制

上一节我们知道了预训练的总体思路,现在翻开练习册真正做一道题。老师用涂改液把句子中的一个词盖住,学生要填回来。注意:这不是让学生背答案,而是让他利用上下文线索推理。比如“猫在?上睡觉”,答案“垫”是从“猫”“在”“上”“睡觉”这些线索共同推出来的。

MLM(Masked Language Model,掩码语言模型) 把这个过程数学化:输入句子时随机遮掉 15% 的词,模型看到的是带洞的句子,输出层在每个洞的位置都给出一个词表上的概率分布,再与真实被遮词计算损失。这里的“语言模型”不再是“预测下一个词”的自回归模型,而是“根据全局上下文还原被遮词”的完形填空模型。一个关键区别就此诞生:GPT 那类自回归模型做题时只能从左往右读,像学生被要求“只能看空位左边的文字”;而 BERT 的 MLM 允许学生同时看空位左右两侧,这就是深度双向编码的核心优势。

预测阶段的计算可以写成 softmax 形式:

回到比喻:学生之所以能填对“垫”,是因为他没有被蒙住双眼,左边“猫在”和右边“上睡觉”都看得清清楚楚。MLM 通过“完形填空”这种看似简单的任务,强制模型把左右两侧信息同时编码进每个位置的表示里——这正是 BERT 与单向语言模型最本质的差异。

P(wi∣x∖i)=exp⁡(ewi⊤hi)∑v∈Vexp⁡(ev⊤hi)\displaystyle P\left(w_i \mid \mathbf{x}_{\setminus i}\right) = \frac{\exp\left(\mathbf{e}_{w_i}^{\top} \mathbf{h}_i\right)}{\sum_{v \in \mathcal{V}} \exp\left(\mathbf{e}_{v}^{\top} \mathbf{h}_i\right)}
这个公式计算的是:在所有候选词中,词 $w_i$ 成为这个空位答案的概率有多大。模型最后会选概率最大的词填进去。
  1. softmax 归一化把一组得分转换成总和为 1 的概率分布,这样每个候选词都得到“成为答案的概率”。
    exp⁡(⋅)∑v∈Vexp⁡(⋅)\frac{\exp(\cdot)}{\sum_{v \in \mathcal{V}} \exp(\cdot)}
  2. 当前位置表示$\mathbf{h}_i$ 是第 $i$ 个空位经过多层 Transformer 编码后得到的语义向量,相当于学生对“这个空位需要什么词”的完整理解。
    hi\mathbf{h}_i
  3. 词向量点积$\mathbf{e}_{w_i}^{\top}\mathbf{h}_i$ 计算候选词 $w_i$ 的向量与空位语义向量的匹配度,匹配度越高,得分越大。
    ewi⊤hi\mathbf{e}_{w_i}^{\top} \mathbf{h}_i
  4. 全词表归一化分母分母对所有候选词 $v \in \mathcal{V}$ 求和,迫使模型在“所有可能答案”中做对比选择。
    ∑v∈Vexp⁡(ev⊤hi)\sum_{v \in \mathcal{V}} \exp\left(\mathbf{e}_{v}^{\top} \mathbf{h}_i\right)

掩码策略细节

出题也是门艺术。如果老师把整页文字全涂黑,学生没法做题;如果只涂一两个字,又学不到东西。BERT 论文的实验发现,遮掉 15% 的词效果最好——信息保留得足够多,模型仍能利用上下文推断,同时又有足够的“题量”让模型学习。涂得太多,句子语义被破坏,模型只能瞎猜,学到的反而是噪声。

更精妙的是涂改方式。对于被选中的那 15% 的词,老师有三种处理方式:80% 用涂改液完全盖住,变成 [MASK] 符号;10% 用红笔写一个随机的错词;10% 保持原词不动。为什么不全部用涂改液?因为微调阶段下游任务的输入里根本没有 [MASK] 符号——考试时试卷上干干净净,没有任何涂改痕迹。如果预训练时模型总是见到 [MASK],它就会养成“看到这个符号才认真推理”的坏习惯。随机词干扰项迫使模型必须真正理解上下文才能识别“这个词不对劲”;保留原词则让模型知道“原词也可能出现在这个位置”,不能依赖“这里被动过手脚”的信号。三种手段合在一起,就是为了弥合预训练与微调之间的输入分布差异。

掩码替换规则的数学表达:

这就像老师故意在练习册里混入“看似没被涂改、但其实是干扰”的题,逼学生不看涂改痕迹、只看语义线索。这套精妙的出题策略,是 BERT 预训练能成功的关键细节之一。

x^i={[MASK]若 r<0.8xrand若 0.8≤r<0.9xi若 r≥0.9\displaystyle \hat{x}_i = \begin{cases} [\mathrm{MASK}] & \text{若 } r < 0.8 \\ x_{\mathrm{rand}} & \text{若 } 0.8 \le r < 0.9 \\ x_i & \text{若 } r \ge 0.9 \end{cases}
对每个被选中的词,掷一个均匀随机数 $r$,落在哪个区间就采用哪种处理方式。
  1. 随机数采样$r$ 是每个被选中位置独立抽取的一个 $[0,1)$ 均匀随机数,决定这一题的“出题方式”。
    r∈[0,1)r \in [0,1)
  2. 三种处理分支$r < 0.8$ 时替换成 [MASK];$0.8 \le r < 0.9$ 时替换成随机词 $x_{\mathrm{rand}}$;$r \ge 0.9$ 时保留原词 $x_i$。
    {[MASK]r<0.8xrand0.8≤r<0.9xir≥0.9\begin{cases} [\mathrm{MASK}] & r < 0.8 \\ x_{\mathrm{rand}} & 0.8 \le r < 0.9 \\ x_i & r \ge 0.9 \end{cases}
  3. 策略核心意图$x_{\mathrm{rand}}$ 是干扰项,$x_i$ 是“伪装原样”,两者让模型不能依赖掩码记号,只能依赖上下文语义。
    xrand≠xix_{\mathrm{rand}} \neq x_i

双向编码结构

学生是怎么做到“左右开弓”的?秘诀在于 BERT 的身体构造——Transformer 编码器。可以把编码器想象成一层“全知视角”的透镜:每个词的位置都能直接看到句子中所有其他词,不管它在左边还是右边。传统 LSTM 必须一个词一个词按顺序读,读完左边才能读右边,信息传递路径很长;而 Transformer 的自注意力机制让每个词一步到位地“环顾全场”。

具体来说,对句子中第 $i$ 个位置,模型会计算它与其他所有位置的“相关度”,然后用相关度作为权重,把其他位置的表示加权融合进自己的表示里。这就是“猫在?上睡觉”中“?“位置能看到”猫”“在”“上”“睡觉”并综合它们的原因。同时也是模型只做 15% 掩码的原因——位置向量之间彼此可见,双向编码才有意义。

注意力更新公式:

另外,BERT 的输入向量由三部分相加:词嵌入(这个词本身叫什么)、位置嵌入(这个词在句子第几个位置)、段落嵌入(这个词属于句子 A 还是句子 B)。三个向量相加得到初始表示,再送入多层编码器。学生做题时不仅知道“这个词是什么”,还知道“它排在哪、属于哪句话”,信息自然更完整。

hiℓ=∑j=1nsoftmaxj((WQhiℓ−1)⊤(WKhjℓ−1)dk)(WVhjℓ−1)\displaystyle \mathbf{h}_i^{\ell} = \sum_{j=1}^{n} \mathrm{softmax}_j\left(\frac{(\mathbf{W}_Q \mathbf{h}_i^{\ell-1})^{\top} (\mathbf{W}_K \mathbf{h}_j^{\ell-1})}{\sqrt{d_k}}\right) (\mathbf{W}_V \mathbf{h}_j^{\ell-1})
第 $\ell$ 层中第 $i$ 个位置的表示,是所有位置表示按相关度加权求和的结果——相关度高低由查询向量与键向量的点积决定。
  1. 层的递进$\mathbf{h}_i^{\ell}$ 表示第 $\ell$ 层(编码器第 $\ell$ 层)处理后第 $i$ 个词的向量,它是由上一层向量 $\mathbf{h}_i^{\ell-1}$ 变换而来。
    hiℓ,hiℓ−1\mathbf{h}_i^{\ell}, \quad \mathbf{h}_i^{\ell-1}
  2. 查询与键$\mathbf{W}_Q \mathbf{h}_i^{\ell-1}$ 是“查询”(我在找什么),$\mathbf{W}_K \mathbf{h}_j^{\ell-1}$ 是“键”(我能提供什么),两者点积衡量 $i$ 与 $j$ 的相关度。
    (WQhiℓ−1)⊤(WKhjℓ−1)(\mathbf{W}_Q \mathbf{h}_i^{\ell-1})^{\top} (\mathbf{W}_K \mathbf{h}_j^{\ell-1})
  3. 缩放与归一化除以 $\sqrt{d_k}$ 防止点积过大,再对 $j=1 \dots n$ 做 softmax,得到每个位置 $j$ 对位置 $i$ 的注意力权重。
    softmaxj(⋅dk)\mathrm{softmax}_j\left(\frac{\cdot}{\sqrt{d_k}}\right)
  4. 加权求和用权重对所有位置的值向量 $\mathbf{W}_V \mathbf{h}_j^{\ell-1}$ 做加权平均,结果就是“看完全场后”的新表示。
    ∑j=1nαij(WVhjℓ−1)\sum_{j=1}^{n} \alpha_{ij} (\mathbf{W}_V \mathbf{h}_j^{\ell-1})

NSP任务

完形填空练的是词级理解,但语言理解不止于此。两句话放在一起,它们是前后连贯的,还是东拼西凑的?老师为此增加了附加题——NSP(Next Sentence Prediction,下一句预测)。这是一道判断题:给出句子 A 和句子 B,判断 B 是不是 A 在原文中的下一句。

这道题的输入格式很特别:[CLS] A [SEP] B [SEP]。开头的 [CLS] 相当于一个“汇总卡位”,它不携带具体词义,而是经过多层编码后,把整段对话(两个句子)的信息压缩成一个向量,再送到二分类层判断“IsNext“还是”NotNext”。训练时正负例各占一半:正例是真实相邻的句子对,负例是从语料中随机拼接的句子对。模型必须学会捕捉句子之间的衔接关系,比如主语一致、因果连贯、场景延续等。

NSP 的损失函数是标准的二分类交叉熵:

NSP 与 MLM 在训练时同时优化,总损失是两者之和。做完形填空让学生掌握词级语义,做句子接龙判断让学生掌握篇章逻辑。这两项能力合在一起,BERT 才能在下游的问答、推理、阅读理解任务中游刃有余——就像学生既会填词,又懂段落衔接,写阅读题自然不慌。

LNSP=−ylog⁡p−(1−y)log⁡(1−p),p=σ(w⊤h[CLS]+b)\displaystyle \mathcal{L}_{\mathrm{NSP}} = -y \log p - (1 - y) \log (1 - p), \quad p = \sigma\left(\mathbf{w}^{\top} \mathbf{h}_{[\mathrm{CLS}]} + b\right)
$y=1$ 表示 B 真是 A 的下一句,$p$ 是模型给出的“是下一句”的概率。预测越准,损失越小。
  1. 真实标签$y \in \{0,1\}$,正例为 1,负例为 0,由老师(数据构造)提前标注好。
    y∈{0,1}y \in \{0, 1\}
  2. 预测概率$p$ 由 [CLS] 位置的向量 $\mathbf{h}_{[\mathrm{CLS}]}$ 经过线性层和 sigmoid 函数 $\sigma$ 压缩到 $(0,1)$ 区间。
    p=σ(w⊤h[CLS]+b)p = \sigma\left(\mathbf{w}^{\top} \mathbf{h}_{[\mathrm{CLS}]} + b\right)
  3. 交叉熵损失$-y \log p$ 惩罚“该说 IsNext 却说错了”的情况,$-(1-y)\log(1-p)$ 惩罚反方向错误。
    −ylog⁡p−(1−y)log⁡(1−p)-y \log p - (1 - y) \log (1 - p)

预训练数据

练习册的质量取决于题库。老师给 BERT 选了两类材料:英文维基百科和BooksCorpus 书籍语料。为什么选这两样?维基百科知识密度高、表达规范、覆盖领域广,适合学习“世界知识”;书籍语料是完整的长篇文本,上下文动辄几百上千词,适合学习“长距离依赖”和篇章结构。相比之下,新闻文章虽然也不少,但单篇较短、结构单一,不利于构造 NSP 任务和长上下文建模。

数据规模有多大?英文维基百科约 25 亿词,BooksCorpus 约 8 亿词,合计约 33 亿词。想象一本练习册有 33 亿道填空题,难怪 BERT 的语感这么好。数据还需要预处理:先清洗掉 HTML 标签、列表、消歧义页等噪声,再用 WordPiece 分词把单词切成子词单元(比如“playing“切成”play“和”##ing”),这样既能控制词表大小,又能处理未登录词。预训练时每次从文档中随机截取一段连续文本构造成训练样本,保证句子间的连贯性,方便同时做 MLM 和 NSP。

数据规模的估算:

题库选得好,学生见过的句型、话题、表达方式足够多样,迁移到实际考场时才不会“没见过世面”。BERT 之后的研究也反复证明:预训练数据的规模与质量,往往比模型结构的小改动更能带来性能提升。

∣D∣tokens≈2.5×109+0.8×109=3.3×109\displaystyle |D|_{\text{tokens}} \approx 2.5 \times 10^9 + 0.8 \times 10^9 = 3.3 \times 10^9
维基百科的约 25 亿词加上书籍语料的约 8 亿词,构成了预训练的全部题库。
  1. 维基百科部分$2.5 \times 10^9$ 即 25 亿词,提供规范、稠密的知识叙述。
    2.5×1092.5 \times 10^9
  2. 书籍语料部分$0.8 \times 10^9$ 即 8 亿词,提供长篇、多样的小说语言。
    0.8×1090.8 \times 10^9
  3. 总量相加$3.3 \times 10^9$ 即 33 亿词,是模型预训练见到的总词数。
    3.3×1093.3 \times 10^9

超参数配置

有了练习册和出题方式,还得安排训练计划。BERT 的训练超参数就是这张计划表:每天做多少题(批次大小)、每次学多快(学习率)、总共练多久(训练步数)、用什么方法巩固(优化器)。

具体参数上,BERT-base 使用 12 层 Transformer、768 维隐藏层、12 个注意力头,总参数量约 1.1 亿;BERT-large 则升级到 24 层、1024 维、16 头,参数量约 3.4 亿。训练时批次大小设为 256,每个样本序列长度为 512 个 token,也就是每步模型要同时处理大约 12.8 万个词的“题量”。优化器选用 Adam,学习率设为 1e-4,并且采用 warmup 策略:前 10,000 步学习率从很小的值线性升到峰值,之后再线性衰减。这就像学生刚开始刷题时先做简单题热身,再进入高难度冲刺,避免一上来强度过大导致“训练不稳定”。

Adam 优化器的参数更新公式:

整个预训练大约进行 100 万步,在 16 块 TPU 上训练约 4 天。回头看比喻:合适的题量(批次)、合适的节奏(学习率)、合适的复习周期(训练步数),加上科学的巩固方法(Adam 优化),学生才能稳定地越学越好。

θt=θt−1−ηt⋅m^tv^t+ϵ,ηt=ηmin⁡+tTwarmup(ηmax⁡−ηmin⁡)\displaystyle \theta_t = \theta_{t-1} - \eta_t \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}, \qquad \eta_t = \eta_{\min} + \frac{t}{T_{\mathrm{warmup}}}(\eta_{\max} - \eta_{\min})
每一步都用历史梯度的一阶动量 $\hat{m}_t$ 和二阶动量 $\hat{v}_t$ 调整更新方向和步长,学习率 $\eta_t$ 在前 $T_{\mathrm{warmup}}$ 步内线性升温。
  1. 动量估计$\hat{m}_t$ 是梯度的一阶矩估计(相当于速度),$\hat{v}_t$ 是二阶矩估计(相当于加速度的尺度),它们让更新更平滑。
    m^t,v^t\hat{m}_t, \quad \hat{v}_t
  2. 参数更新$\theta_t = \theta_{t-1} - \eta_t \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$,分母加 $\epsilon$ 防止除零。
    θt=θt−1−ηt⋅m^tv^t+ϵ\theta_t = \theta_{t-1} - \eta_t \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}
  3. 学习率预热$\eta_t$ 在 $t < T_{\mathrm{warmup}}$ 时从 $\eta_{\min}$ 线性升到 $\eta_{\max}$,防止训练初期大步幅乱撞。
    ηt=ηmin⁡+tTwarmup(ηmax⁡−ηmin⁡)\eta_t = \eta_{\min} + \frac{t}{T_{\mathrm{warmup}}}(\eta_{\max} - \eta_{\min})

能力与迁移

练习册刷完了,该上考场了。BERT 的优势在于:它学到的不是某一项具体技能,而是通用语言理解能力。到了下游任务,只需要在 BERT 顶上接一层简单的“任务头”(比如分类层、指针网络),然后用较小的学习率对整个模型做少样本微调——相当于考前做几套真题,把已有能力快速适配到具体题型。

以文本分类为例,输入句子经过 BERT 得到每个位置的表示,取 [CLS] 位置的向量送进分类层,用标注数据计算交叉熵损失,然后梯度回传到整个 BERT 模型,所有参数一起小幅更新。因为预训练已经让参数落到一个很好的初始点,微调只需几个 epoch 就能收敛,即使标注数据只有几千条也能表现良好。以 BERT 为代表的两段式范式刷新了 GLUE 榜单上的十几项任务,成为 NLP 的新标准。

微调阶段的损失函数:

BERT 之后的演进也值得了解:RoBERTa 去掉了 NSP 并加大数据量,性能进一步提升;ALBERT 通过参数共享减小模型体积;ELECTRA 改用“判别被替换词”的任务解决掩码效率问题。当然 BERT 也有局限——它是理解型选手,不适合文本生成;掩码符号造成的预训练/微调不一致依然存在。但无论如何,MLM 这套“完形填空炼语言能力”的思路,已经深刻改变了 NLP 的生态。

Lft(θ)=Ltask(y,fθ(x)),θinit=θpre\displaystyle \mathcal{L}_{\mathrm{ft}}(\theta) = \mathcal{L}_{\mathrm{task}}\left(y, f_\theta(\mathbf{x})\right), \quad \theta_{\mathrm{init}} = \theta_{\mathrm{pre}}
微调就是用任务损失继续训练整个模型,初始参数不是随机初始化,而是预训练得到的 $\theta_{\mathrm{pre}}$。
  1. 预训练参数作起点$\theta_{\mathrm{init}} = \theta_{\mathrm{pre}}$,微调开始时模型的权重就是预训练结束的权重。
    θinit=θpre\theta_{\mathrm{init}} = \theta_{\mathrm{pre}}
  2. 任务损失$\mathcal{L}_{\mathrm{task}}(y, f_\theta(\mathbf{x}))$ 是下游任务自己的损失,比如分类就取交叉熵,问答就取 span 预测损失。
    Ltask(y,fθ(x))\mathcal{L}_{\mathrm{task}}\left(y, f_\theta(\mathbf{x})\right)
  3. 全参数更新微调时 $\theta$ 全部参与梯度下降,不是只练任务头,这样任务信号能反向调整底层语义表示。
    θ←θ−η∇θLft\theta \leftarrow \theta - \eta \nabla_\theta \mathcal{L}_{\mathrm{ft}}

AI 应用场景

智能客服意图识别

阶段:预训练 + 微调(迁移学习)

解决的问题:传统客服系统依赖关键词规则匹配,用户换个说法就识别失败,且标注数据稀缺。利用 BERT 预训练模型作为初始权重,只需几百条人工标注的意图样本即可微调出高准确率分类器。

具体案例:电商平台客服收到“我的订单什么时候到?”和“快递到哪了?”两种表达,微调后的 BERT 都能正确分类为“物流查询”意图,因为 MLM 预训练让模型理解了“订单/快递/到货”的语义关联。

搜索引擎问答相关性

阶段:下游任务微调(文本匹配)

解决的问题:传统 BM25 等检索算法只做字面匹配,“感冒吃什么药”与“咳嗽用药指南”字面重合度低但语义相关,容易漏召回。BERT 的双向表示能将 query 和 doc 各自编码后进行相关度打分。

具体案例:医疗搜索中,用户输入“发烧了怎么退热”,系统用微调后的 BERT 匹配到“体温升高时的物理降温方法”等语义相似的文档,显著提升相关结果排序质量。

文本相似度计算

阶段:特征提取 + 下游嵌入复用

解决的问题:判断两段话是否意思相近,传统方法依赖词袋重合度,无法处理同义改写。BERT 预训练的编码器可直接输出句向量,用于相似度计算。

具体案例:新闻聚合平台用 BERT 句向量对稿件去重,两篇表述不同但内容相同的报道,向量余弦相似度很高,能被自动识别合并。

Python 演示

python69 行
"""TinyMLM:用共现统计演示 BERT 的掩码策略与双向上下文预测"""import randomfrom collections import defaultdict  class TinyMLM:    def __init__(self, seed=42):        random.seed(seed)        self.vocab = set()        self.cooccur = defaultdict(lambda: defaultdict(int))     def train(self, sentences):        for sent in sentences:            for token in sent.strip().split():                self.vocab.add(token)        for sent in sentences:            tokens = sent.strip().split()            for i, token in enumerate(tokens):                for j, other in enumerate(tokens):                    if i != j:                        self.cooccur[other][token] += 1     def mask_sentence(self, sentence, mask_prob=0.15):        tokens = sentence.strip().split()        masked, targets = [], {}        for i, token in enumerate(tokens):            if random.random() < mask_prob:                targets[i] = token                r = random.random()                if r < 0.8:                    masked.append('[MASK]')                elif r < 0.9:                    masked.append(random.choice(sorted(self.vocab)))                else:                    masked.append(token)            else:                masked.append(token)        return masked, targets     def predict(self, masked, mask_idx):        scores = defaultdict(int)        for j, ctx in enumerate(masked):            if j == mask_idx or ctx == '[MASK]':                continue            for cand, count in self.cooccur[ctx].items():                scores[cand] += count        return max(scores, key=scores.get) if scores else '[UNK]'  def main():    corpus = [        '我 爱 听 音乐', '我 爱 看 电影',        '他 爱 听 广播', '她 喜欢 听 音乐',        '我们 喜欢 看 电影', '我 喜欢 看 书',        '你 喜欢 听 播客',    ]    model = TinyMLM(seed=7)    model.train(corpus)    for sent in ['我 爱 听 音乐', '我 喜欢 看 电影']:        masked, targets = model.mask_sentence(sent)        print('原始:', sent)        print('掩码:', ' '.join(masked))        for idx, truth in targets.items():            guess = model.predict(masked, idx)            print(f'位置 {idx}: 真值={truth!r} 预测={guess!r} 命中={truth == guess}')  if __name__ == '__main__':    main()
目的:用一个极简的共现统计模型模拟 BERT MLM 的完整流程——先按 80/10/10 规则生成掩码样本,再根据左右上下文词预测被遮词,直观展示双向上下文的力量。
  1. self.cooccur = defaultdict(lambda: defaultdict(int))嵌套字典,统计“上下文词 → 候选词 → 共现次数”,作为预测依据。
  2. for i, token in enumerate(tokens):` 配合 `if i != j一个词与句子中所有其他词互为上下文,这正是“双向”的朴素实现。
  3. if random.random() < mask_prob:以 15% 概率决定是否对这个词出题,对应 BERT 的掩码比例。
  4. masked.append('[MASK]')80% 情况用掩码符号盖住原词。
  5. masked.append(random.choice(sorted(self.vocab)))10% 情况写入一个随机干扰词。
  6. else: masked.append(token)10% 情况保留原词,模拟“没被涂改但也要判断”的场景。
  7. max(scores, key=scores.get) if scores else '[UNK]'选择与左右上下文共现总次数最高的词作为预测答案。

常见误区

误区BERT 会做掩码填空,那应该也能写文章吧?我把 `[MASK]` 放在句尾,让它一格一格往后“生成”不就行了?

正解BERT 的结构是 Transformer 编码器,它没有自回归生成能力。MLM 只能还原被挖掉的词,不能从左到右逐词生成新文本。能生成文本的是 GPT 那类自回归解码器。比喻:做完形填空的学生会填词,但不代表他会写作文——填词考的是理解,写作文考的是生成,两者是不同能力。

为何常见:MLM 的名字里带“语言模型”,而很多人直觉上认为“语言模型 = 会生成文本”,忽略了 BERT 实际用的是编码器结构。

误区既然掩码让模型学习,那挖掉 50% 甚至 80% 的词,题目更多,模型不是学得更快更全吗?

正解掩码比例过大时,句子信息被严重破坏,很多词几乎无法从残留上下文推断,模型只能靠猜,训练信号变成噪声,效果反而急剧下降。BERT 论文通过实验验证 15% 附近最优。比喻:练习册如果把整页都涂黑,学生根本无从下笔,题目就失去了训练意义。

为何常见:人们容易把“题量”等同于“学习量”,忽略了题目的前提是上下文信息足够完整、答案可推断。

误区BERT 预训练时用 `[MASK]` 挖词,微调下游任务时也应该在输入里加 `[MASK]`,保持输入格式一致。

正解微调阶段下游任务的输入是干净完整的句子,完全没有 [MASK] 符号。正因为预训练与微调存在这种输入分布差异,BERT 才设计了 80/10/10 策略——只让 80% 的选中词变成 [MASK],让模型不依赖掩码记号,而是依赖语义上下文。比喻:练习册里全是涂改痕迹,考场上却干干净净,学生必须学会在没有涂改痕迹时也能答题。

为何常见:默认两阶段输入应该保持一致,忽略了 [MASK] 是预训练特有的“作弊标记”,微调时并不存在。

误区NSP 是个二分类任务,那应该就是判断句子是正面还是负面情绪吧?

正解NSP(Next Sentence Prediction)判断的是“句子 B 是否是句子 A 在原文中的下一句”,与情感极性毫无关系。它的目的是让模型理解句子之间的连续性和衔接关系,补足 MLM 只关注词级语义的短板。比喻:这叫“句子接龙判断题”,考的是两句话是否前后连贯,而不是那句话好不好听。

为何常见:一听到“二分类”就联想到情感分析这类最常见的分类任务,忽略了 NSP 的输入是“句子对”这一关键特征。

面试题

Q1. BERT 的 MLM 任务本质是什么?

单选题 · easy

  • A.预测下一个单词
  • B.还原被遮的词语← 正确答案
  • C.判断文本的情感
  • D.回译复杂长句子

答案:B。MLM 是根据上下文还原被掩码遮住的词,本质是“完形填空”;选 A 混淆了它和 GPT 式的自回归下一词预测;C 是分类任务;D 是翻译任务,均与 MLM 目标不符。

Q2. BERT 在 MLM 中的掩码比例是多少?

单选题 · easy

  • A.大约是百分之五
  • B.大约是百分之十
  • C.大约是百分之十五← 正确答案
  • D.大约是百分之二十

答案:C。BERT 论文通过实验选定 15% 的 token 作为掩码候选。比例太小则题量不足,太大则句子信息被破坏、无法有效学习。5%、10%、20% 都不是论文采用的配置。

Q3. BERT 预训练主要使用了哪类语料?

单选题 · easy

  • A.维基百科和图书语料← 正确答案
  • B.短视频评论数据集
  • C.电话语音转写记录
  • D.GitHub 开源项目代码

答案:A。BERT 使用英文维基百科和 BooksCorpus 书籍语料,合计约 33 亿词。评论数据噪声大、语音转写缺少规范标点、代码语言与自然语言差异大,都不是预训练的自然语言语料。

Q4. 掩码词中 10% 换成随机词的理由是什么?

单选题 · mid

  • A.加快预训练收敛速度
  • B.缓解两阶段不一致问题← 正确答案
  • C.减少内存显存占用
  • D.提高随机采样的概率

答案:B。微调阶段输入中没有 [MASK] 符号,若预训练总是出现 [MASK],模型会依赖它。随机词迫使模型靠语义判断“这个词不对劲”,从而弥合预训练与微调的输入分布差异。这与收敛速度、显存、采样概率无关。

Q5. BERT 的输入嵌入由哪些部分组合而成?

多选题 · mid

  • A.词嵌入矩阵的映射← 正确答案
  • B.位置嵌入的位置码← 正确答案
  • C.段落嵌入的分段码← 正确答案
  • D.语法分析树的编码

答案:ABC。BERT 输入向量 = 词嵌入 + 位置嵌入 + 段落嵌入。词嵌入表达词义,位置嵌入表达次序,段落嵌入区分句子 A/B。D 的语法树编码不是 BERT 输入的一部分。

Q6. NSP 任务的主要作用是什么?

单选题 · mid

  • A.给模型训练提供语料
  • B.学习判断句子间关系← 正确答案
  • C.替换 MLM 的掩码策略
  • D.用来做文本生成任务

答案:B。NSP 判断句子 B 是否是句子 A 的下一句,是二分类的“句子接龙判断题”,目的是让模型学习句间连贯关系。它不提供语料、不替换掩码、更不用于生成。

Q7. BERT“双向编码”最直接的体现是什么?

单选题 · mid

  • A.只能看到左侧的词
  • B.自注意力同时看两侧← 正确答案
  • C.把两个 LSTM 拼接
  • D.只依赖全局统计信息

答案:B。BERT 的 Transformer 编码器使用自注意力,每个位置能同时聚合左右两侧所有位置的表示。A 是 GPT 的单向因果注意力;C 是 ELMo 的双向 LSTM 拼接方案,不是真正的深层双向;D 与模型结构无关。

Q8. 关于 BERT 预训练与微调,以下说法正确的是?

多选题 · hard

  • A.微调时仍使用较小学习率← 正确答案
  • B.微调阶段不再使用掩码词← 正确答案
  • C.预训练参数全部保持冻结
  • D.掩码策略缓解两阶段差异← 正确答案

答案:ABD。微调用较小学习率(如 2e-5)全参数更新,不冻结;下游任务输入没有 [MASK];80/10/10 掩码策略正是为了缓解两阶段输入分布不一致。因此 A、B、D 正确,C 错误。

Q9. 被选中词中 10% 保持原词的原因是什么?

单选题 · hard

  • A.训练模型学会照抄原词
  • B.让模型不依赖掩码符号← 正确答案
  • C.让解码器更容易去输出
  • D.让词表大小更加稳定

答案:B。保留 10% 原词是为了让模型知道“即使这个词没被改动,也可能成为预测目标”,从而不依赖“这里被改动了”的信号,而是真正根据上下文理解语义。这与照抄原词、解码器输出、词表稳定都没有关系。

Q10. BERT 相比 ELMo 和 GPT 的优势体现在哪些方面?

多选题 · hard

  • A.深层双向上下文建模能力← 正确答案
  • B.使用编码器做掩码训练← 正确答案
  • C.不依赖单向自回归解码← 正确答案
  • D.完全取消注意力机制

答案:ABC。BERT 用 Transformer 编码器和 MLM 实现深层双向建模,突破了 ELMo 浅层拼接和 GPT 单向自回归的限制。D 完全说反了——BERT 正是靠自注意力机制实现双向建模的。