1. 前言

上一篇我们已经把 BERT 模型结构 搭起来了,知道了它主要由三部分组成:

  • 输入表示层

  • 多层 Transformer Encoder

  • 预训练任务头(MLM、NSP)

但模型有了,还不能直接训练。
因为 BERT 的预训练不是普通的“喂句子进去就行”,
它需要一套专门构造过的数据形式。

也就是说,在真正开始预训练之前,我们必须先解决一个关键问题:

BERT 训练样本到底长什么样?

这一节的核心,就是把这个问题彻底讲清楚:

  • 原始文本怎么变成句子对

  • 下一句预测(NSP)样本怎么构造

  • 掩码语言模型(MLM)样本怎么构造

  • token、segment、valid_len、pred_positions、mlm_labels 分别是什么

  • 最终怎样整理成训练所需张量

如果一句话概括这一节的灵魂,那就是:

把普通文本,加工成适合 BERT 两个预训练任务的结构化样本。


2. 为什么 BERT 预训练数据不能直接拿普通文本就喂

因为 BERT 的训练目标不是单一的。

它同时要做两件事:

第一,MLM

随机遮住部分 token,让模型预测被遮住的词。

第二,NSP

给模型两个句子,让它判断第二句是不是第一句的真实后续。

这就意味着,原始文本至少要经过两层加工:

  • 从“连续文本”切成“句子”

  • 从“句子”组织成“句对”

  • 再从“句对”构造 mask 位置和标签

所以 BERT 预训练数据处理,本质上不是简单 tokenize,
而是:

围绕预训练目标定制样本结构。


3. BERT 预训练数据的核心目标是什么

如果把整个数据处理目标压缩成一句话,就是:

把原始文本变成一批可以同时支持 MLM 和 NSP 的训练样本。

每个训练样本通常要包含这些内容:

  • token ids

  • segment ids

  • valid length

  • mask 位置

  • mask 标签

  • NSP 标签

这说明 BERT 数据样本和前面语言模型、Seq2Seq 的数据样本都不一样。
它不是单纯“一串 token + 一个标签”,而是一种更复杂的多任务输入格式。


4. 原始文本第一步要做什么

第一步通常是:

按段落读取文本,再按句子切分。

为什么要句子级切分?

因为 NSP 任务的单位是“句对”。
如果你连句子边界都不知道,就没法判断:

  • 哪个句子是真实下一句

  • 哪个句子是随机负样本

所以 BERT 数据处理的起点通常不是“长 token 流”,
而是:

句子列表

例如一个段落可能先被切成:

[
  ['i', 'love', 'deep', 'learning'],
  ['it', 'is', 'very', 'interesting'],
  ['transformers', 'are', 'powerful']
]

这样后面才方便组织句对。


5. 为什么通常按段落处理,而不是全文乱配

因为 NSP 任务需要“真实下一句”和“随机句子”的对比。

如果不按段落来,模型很难区分真正相邻的句子关系。
而按段落处理时,至少可以保证:

  • 段落内相邻句子更有可能是自然连续的

  • 段落外随机抽句子更可能是不连续的

所以李沐这里常见的做法是:

先把文本拆成段落,再把段落拆成句子

这样更适合构造 NSP 样本。


6. NSP 样本是怎么构造的

BERT 原始 NSP 任务的逻辑很清楚:

给两个句子 A、B,让模型判断 B 是否真的是 A 的下一句。

构造方式通常是:

50% 概率

取真实相邻句子对:

  • A = 当前句

  • B = 下一句

  • 标签 = 1

50% 概率

取随机句子作为第二句:

  • A = 当前句

  • B = 从别处随机抽一句

  • 标签 = 0

这样模型就能学会句子关系判断。


7. 为什么 NSP 样本要一半真、一半假

因为这是一个二分类任务。
如果正负样本极不平衡,模型很容易学歪。

例如如果 90% 都是真下一句,
模型哪怕无脑全预测“是”,准确率都不低。
这显然没意义。

所以通常会让:

  • 正样本约占一半

  • 负样本约占一半

这样模型才必须真正学习:

句子之间是否连贯

而不是靠类别先验混过去。


8. 句对构造好后,输入格式长什么样

BERT 句对输入通常会组织成:

[CLS] sentence_A [SEP] sentence_B [SEP]

例如:

[CLS] i love deep learning [SEP] it is very interesting [SEP]

这里有几个关键特殊 token:

[CLS]

放最前面,用于句级表示。

第一个 [SEP]

分隔句子 A 和句子 B。

最后一个 [SEP]

标记句子 B 的结束。

所以句对不是简单拼接,而是:

带结构标记的拼接序列


9. segment ids 是怎么构造的

既然输入里有两句,那么模型还要知道:

  • 哪些 token 属于句子 A

  • 哪些 token 属于句子 B

所以会配套生成一串 segments,例如:

0 0 0 0 0 0 1 1 1 1 1

通常规则是:

  • [CLS] 和句子 A、句子 A 后的 [SEP] 都记为 0

  • 句子 B 和句子 B 后的 [SEP] 都记为 1

这样 segment embedding 就能告诉模型:

当前 token 属于哪一段

这对 NSP 和句对任务很重要。


10. 为什么还要限制最大长度 max_len

因为 BERT 训练时,一条样本不能无限长。
通常会设一个最大长度,例如:

max_len = 64

这样做有几个原因:

第一,控制显存和计算量

Transformer 的自注意力复杂度和序列长度平方相关,序列太长会很贵。

第二,便于 batch 训练

统一长度更容易组织张量。

第三,教学实现更清晰

先把基础流程跑通更重要。

所以句对拼起来之后,如果太长,通常会截断。


11. 截断时一般怎么处理

最常见做法是:

如果句对总长度超过上限,就从较长的那一句不断删 token

为什么这样处理?

因为这样能相对公平地保留两边信息,
而不是粗暴只砍句子 B 或只砍句子 A。

所以截断函数常常会不断比较:

  • 句子 A 当前长度

  • 句子 B 当前长度

谁更长就删谁最后一个 token,
直到总长度满足上限。

这是一种简单但很实用的策略。


12. MLM 样本是怎么构造的

BERT 最核心的数据加工其实是 MLM。

构造思路是:

从输入 token 序列里,随机选出一些位置作为预测目标,然后按规则替换其中的 token。

注意这里有两个层次:

第一,选哪些位置参与 MLM 预测

不是所有位置都参与。

第二,选中以后怎么替换

也不是一律替换成 [MASK]

这两个步骤都很重要。


13. 哪些位置不能被选作 mask

在 BERT 里,不是任意位置都能被选。

通常这些特殊 token 不参与 MLM:

  • [CLS]

  • [SEP]

因为它们是结构标记,不是普通词。
如果把它们也拿来做 mask,会让预训练目标变奇怪。

所以 MLM 候选位置通常只从:

真实文本 token 位置

中抽取。


14. 一般会 mask 多少比例的 token

原始 BERT 的经典设置是:

随机选择约 15% 的 token 作为 MLM 预测目标

也就是说,如果一条样本有 20 个普通 token,
大概会挑出 3 个左右做预测。

这个比例不能太高,也不能太低。

太高

输入被破坏得太严重,模型理解上下文会变难。

太低

监督信号太少,训练效率偏低。

所以 15% 是一个很经典的折中。


15. 被选中的 token 一定会换成 [MASK]

不一定,这一点非常关键。

BERT 原始规则通常是这样的:

80% 概率

替换成 [MASK]

10% 概率

替换成一个随机 token

10% 概率

保持原 token 不变

这一步很多人第一次看到会疑惑,
但它有很重要的意义。


16. 为什么不把选中的 token 全都替换成 [MASK]

因为 BERT 预训练时有 [MASK]
但下游微调和真实使用时,输入中通常并没有大量 [MASK]

如果预训练阶段所有预测目标都替成 [MASK]
模型就会过度依赖这种特殊符号,
而和实际使用场景脱节。

所以采用:

  • 大部分 [MASK]

  • 少量随机替换

  • 少量保持原样

这种混合策略,可以让模型既学会填空,
又不至于完全绑定在 [MASK] 这个符号上。

这一步其实非常巧妙。


17. MLM 数据里到底要保存哪些信息

一条样本里,和 MLM 有关的信息通常至少有:

第一,处理后的 token 序列

也就是部分位置已经被替换后的输入。

第二,被预测的位置 pred_positions

告诉模型哪些位置要参与 MLM loss。

第三,这些位置原本的真实 token mlm_labels

也就是标准答案。

所以你可以理解成:

MLM 数据加工不是只改输入,还要同步记录“改了哪里、原来是什么”。**


18. 为什么要单独存 pred_positions

因为 MLM 预测头不会对所有位置都计算损失。
它只关心那些被选中的 mask 位置。

所以必须显式保存:

pred_positions

例如:

[3, 7, 10]

表示第 3、7、10 个位置是 MLM 目标位点。

后面前向传播时,MLM 头会只抽取这些位置对应的编码器输出,
再去预测它们原来的词。


19. 为什么要存 mlm_labels

因为模型最终要学的是:

把被遮住的位置恢复成原来的词

所以不仅要知道“哪些位置被选中了”,
还必须知道“这些位置原来是什么词”。

例如:

  • 输入中第 5 个位置被替成 [MASK]

  • 但真实标签可能是 learning

那么:

mlm_labels = ['learning']

后面 loss 就是拿模型预测和这个真实标签比较。


20. 一条 BERT 预训练样本通常最后包含哪些字段

综合起来,一条样本最终常常会整理成这些内容:

  • token_ids

  • segments

  • valid_len

  • pred_positions

  • mlm_weights

  • mlm_labels

  • nsp_label

这里可以简单解释一下。

token_ids

模型真正输入的 token 序列。

segments

句子 A/B 标识。

valid_len

有效长度,用于 mask padding。

pred_positions

哪些位置参与 MLM。

mlm_weights

哪些 MLM 位置是真实有效的。
因为不同样本被 mask 的数量可能不同,pad 后要靠它区分。

mlm_labels

被 mask 位置原始 token 标签。

nsp_label

句对是否连续。

这就是 BERT 预训练样本比普通 NLP 样本复杂得多的原因。


21. 为什么还要有 mlm_weights

因为不同样本被选中的 mask 数量不一定完全一样。

例如:

  • 样本 1 有 3 个 MLM 位置

  • 样本 2 有 5 个 MLM 位置

为了组成 batch,通常会把 pred_positionsmlm_labels pad 到统一长度。
这时就需要一个权重向量告诉损失函数:

  • 哪些位置是真实 MLM 目标

  • 哪些只是 pad 出来的占位

这就是:

mlm_weights

的作用。

它和前面序列任务里的 valid length 思路是类似的。


22. 数据集类通常怎么组织

李沐这里常见会封装一个 Dataset,大致思路是:

  1. 读取原始文本

  2. 切成段落和句子

  3. 构造 NSP 句对

  4. 构造 MLM 样本

  5. 转成词表索引

  6. pad 到统一长度

  7. 保存所有字段

然后 __getitem__ 返回一条完整样本。

也就是说,BERT 数据集类不是只做“读取文件”,
而是在内部已经完成了:

大部分预训练样本构造逻辑

这和普通分类任务数据集差别很大。


23. 这一节最该掌握什么

如果从学习重点来看,最关键的是这几件事。

23.1 理解 NSP 样本怎么构造

知道正负句对是怎么来的。

23.2 理解 MLM 样本怎么构造

知道为什么不是所有 token 都 mask,也不是全换成 [MASK]

23.3 理解一条样本里有哪些字段

特别是:

  • pred_positions

  • mlm_labels

  • nsp_label

23.4 理解为什么要 padding 和 mask

因为 batch 训练需要统一张量形状。

23.5 理解 BERT 预训练数据本质上是“围绕任务目标定制”的

不是普通 tokenize 后就完事了。


24. 这一节和前后内容怎么衔接

这一节其实是非常关键的桥梁。

前一节:BERT代码

已经把模型本体搭好了。

这一节:BERT预训练数据代码

解决“模型该喂什么样的数据”。

下一节:BERT预训练代码

就可以真正把:

  • 模型

  • 数据

  • 损失

  • 优化

全部接起来训练了。

所以这节本质上是在补齐:

预训练流程里最关键的输入端。


25. 本节总结

这一节我们学习了 BERT 预训练数据代码的核心思想,主要内容可以总结为以下几点。

25.1 BERT 预训练样本需要同时服务于 MLM 和 NSP 两个任务

因此数据结构比普通 NLP 任务复杂得多。

25.2 NSP 通过构造真实下一句和随机句子对来形成二分类样本

这是句对预训练的基础。

25.3 MLM 通过随机选择一部分 token 作为预测目标,并按特殊规则替换输入 token

这是 BERT 双向建模的关键。

25.4 一条 BERT 预训练样本通常包含 token_ids、segments、valid_len、pred_positions、mlm_labels、nsp_label 等多个字段

这些字段共同支撑训练。

25.5 数据处理的本质是“围绕预训练目标组织样本”

不是简单的文本切分而已。


26. 学习感悟

这一节特别重要,因为它会让你真正意识到:

一个强模型的成功,不只是网络结构强,
还取决于训练目标设计和数据构造方式是否足够聪明。

BERT 之所以经典,不只是因为它用了 Transformer,
还因为它把:

  • 句对关系学习

  • 被遮住词恢复

这两件事巧妙地变成了大规模无监督文本上的训练任务。

所以这一节其实是在告诉你:

模型结构和数据构造,是同样重要的两个轮子。

Logo

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

更多推荐