动手学深度学习——BERT预训练数据代码
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_positions 和 mlm_labels pad 到统一长度。
这时就需要一个权重向量告诉损失函数:
-
哪些位置是真实 MLM 目标
-
哪些只是 pad 出来的占位
这就是:
mlm_weights
的作用。
它和前面序列任务里的 valid length 思路是类似的。
22. 数据集类通常怎么组织
李沐这里常见会封装一个 Dataset,大致思路是:
-
读取原始文本
-
切成段落和句子
-
构造 NSP 句对
-
构造 MLM 样本
-
转成词表索引
-
pad 到统一长度
-
保存所有字段
然后 __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,
还因为它把:
-
句对关系学习
-
被遮住词恢复
这两件事巧妙地变成了大规模无监督文本上的训练任务。
所以这一节其实是在告诉你:
模型结构和数据构造,是同样重要的两个轮子。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)