
核心结论
数据预处理不是“把文本洗干净再分词”这么简单。对现代大模型来说,它是一条完整的数据策展流水线:从原始数据抽取文本,做质量过滤、去重、PII 与安全处理、评测集去污染,再训练或选择 Tokenizer,最后构造带有模板、mask、EOS 和 packing 规则的训练样本。
更准确的一句话是:
原始数据 -> 数据策展 -> Tokenizer -> 样本构造 -> 训练与评估
Tokenizer 不是独立工具,而是模型输入协议的一部分。它决定文本如何被压缩成 token、特殊标记如何表示、对话模板如何展开、哪些 token 参与 loss。数据清洗也不是越彻底越好,而是在保留语义、降低噪声、满足安全与治理要求之间做可验证的取舍。
第 0 层:30 秒理解
数据预处理像“数据炼厂”,不是简单洗菜。
- 原始数据:网页、PDF、代码、论坛、对话、表格、日志,通常混有模板、重复、广告、乱码、隐私和污染样本。
- 数据策展:抽取正文、过滤低质样本、去重、处理 PII 和安全问题、去除评测污染、记录来源和许可证。
- Tokenizer:把文本变成 token ID,同时定义特殊 token、空格规则、byte fallback、chat template 等输入协议。
- 样本构造:把 token ID 变成模型真正训练的 input_ids、attention_mask、labels、loss_mask。
- 评估闭环:通过验证集、消融实验和数据质量指标判断预处理是否真的有效。

最重要的原则:
不要追求“清洗越干净越好”,而要追求“信息保留、噪声可控、来源可追踪、训练可复现”。
第 1 层:基础概念
1.1 Tokenizer 到底做什么
Tokenizer 通常包含四件事:
因此,Tokenizer 输出的不只是 ID 序列,而是一套“模型如何理解输入”的协议。
1.2 数据清洗和数据策展的区别
| 数据清洗 | 单条文本是否规范、可读、无明显噪声 | Unicode 规范化、HTML 转文本、空白折叠 |
| 数据过滤 | 样本是否应进入训练集 | 语言识别、质量分类、长度过滤、安全过滤 |
| 数据去重 | 数据分布是否被重复内容扭曲 | exact hash、MinHash、n-gram overlap |
| 数据去污染 | 训练集是否泄漏评测集 | benchmark overlap、答案泄漏、近似重复 |
| 数据治理 | 是否合规、可追踪、可审计 | PII、许可证、来源、数据卡 |
| 样本构造 | 模型到底学习哪些 token | chat template、packing、loss mask |
现代大模型训练里,数据清洗只是数据策展的一部分。
第 2 层:Tokenizer 原理

2.1 BPE
BPE 的核心思想是:从字符或字节开始,反复合并高频相邻片段,得到更长的子词 token。
它的优点是简单、高效、覆盖能力强。许多 GPT 系 tokenizer 都与 BPE 或 byte-level BPE 有关。byte-level BPE 的优势是几乎可以覆盖任意输入,不容易出现 [UNK];代价是罕见字符、乱码、多字节文本可能被拆成更多 token。
需要注意:真实 BPE 训练不是把一整段文本简单拆成字符再合并。工程实现还涉及预分词、词频、merge table、byte fallback、特殊 token、空格编码和 decode 可逆性。
2.2 WordPiece
WordPiece 常见于 BERT 系模型。它的词表构建考虑词片组合的统计价值,编码时通常使用 longest-match-first:尽量匹配词表中最长的子词片段。
WordPiece 适合解释 BERT 时代的英文/多语言模型,但它不等于“BPE 的另一个名字”。尤其要注意:
- 如果字符或子词无法覆盖,仍可能产生 [UNK]。
- 大小写版本、accent stripping、中文字符处理都会影响结果。
- 换 tokenizer 会改变模型 embedding 对齐,不能随意替换。
2.3 SentencePiece 与 Unigram
SentencePiece 是一个 tokenizer 工具包,常用于直接处理原始 Unicode 文本,不要求先按空格分词。它支持 BPE,也支持 Unigram Language Model。
Unigram 的思路不是逐步合并最高频 pair,而是先构造较大的候选子词表,再根据似然和损失贡献逐步剪枝。它天然适合多语言场景,但多语言效果仍取决于数据配比、脚本覆盖和词表预算。
2.4 词表大小怎么选
词表大小不是按模型参数简单套公式。它主要受这些因素影响:
- 语言数量:多语言和混合脚本通常需要更大词表。
- 代码比例:代码、数学、路径、标识符会改变 token 分布。
- byte fallback:覆盖能力增强,但低频文本可能 token 更多。
- 上下文长度:token 越多,等价文本占用上下文越多。
- 推理延迟:短序列减少 attention 成本,但大词表增加 embedding/LM head 成本。
- 兼容性:预训练模型的 tokenizer 通常不能随意更换。
实践建议:
| 微调已有模型 | 不要重训 tokenizer,保持与基座模型一致 |
| 从零训练英文模型 | 可从 32K 到 50K 附近做实验 |
| 多语言/代码模型 | 评估 100K+ 词表是否改善压缩率和公平性 |
| 低延迟推理 | 同时评估 tokens/sec、bytes/token 和 LM head 成本 |
| 专业领域 | 先看术语 fertility,再决定是否扩词表或继续训练 |
2.5 Tokenizer 质量指标
| bytes/token | 平均每个 token 覆盖多少字节,越高通常压缩越好 |
| fertility | 一个词或片段平均被拆成多少 token |
| UNK rate | [UNK] 出现比例,非 byte-level tokenizer 需关注 |
| round-trip | decode(encode(text)) 是否保留足够信息 |
| special token correctness | BOS/EOS/PAD/role/tool token 是否正确 |
| chat template consistency | 训练和推理模板是否一致 |
| encoding latency | 大规模预处理和在线服务的编码开销 |
一个简单的 tokenizer 评估函数:
def tokenizer_report(tokenizer, texts, unk_token=None):
total_bytes = 0
total_tokens = 0
unk_count = 0
roundtrip_mismatch = 0
if unk_token is None:
unk_token = getattr(tokenizer, "unk_token", None)
for text in texts:
ids = tokenizer.encode(text, add_special_tokens=False)
decoded = tokenizer.decode(ids)
total_bytes += len(text.encode("utf-8"))
total_tokens += max(1, len(ids))
if unk_token is not None:
tokens = tokenizer.convert_ids_to_tokens(ids)
unk_count += sum(1 for token in tokens if token == unk_token)
if decoded.replace(" ", "") != text.replace(" ", ""):
roundtrip_mismatch += 1
return {
"bytes_per_token": total_bytes / max(1, total_tokens),
"avg_tokens": total_tokens / max(1, len(texts)),
"unk_rate": unk_count / max(1, total_tokens),
"roundtrip_mismatch_rate": roundtrip_mismatch / max(1, len(texts)),
}
这段代码只是轻量检查,不能替代下游训练消融。
第 3 层:数据清洗策略
3.1 默认策略:少删,多标注,多验证
现代 LLM 数据清洗应避免“看起来脏就删”。以下内容经常有语义:
| URL | 来源、引用、实体、API、代码依赖 |
| 大小写 | 缩写、实体、标题、代码变量 |
| 标点 | 句法、数学、代码、Markdown、JSON |
| emoji | 情绪、社交语境、用户意图 |
| 数字和日期 | 金融、医学、法律、问答中的核心信息 |
| 停用词 | 否定、指代、语法和生成流畅性 |
更安全的清洗模板:
import html
import re
import unicodedata
def normalize_text(
text,
*,
unicode_form="NFKC",
lowercase=False,
strip_urls=False,
collapse_spaces=True,
):
text = html.unescape(text)
text = unicodedata.normalize(unicode_form, text)
if lowercase:
text = text.lower()
if strip_urls:
text = re.sub(r"https?://\\S+|www\\.\\S+", " <URL> ", text)
if collapse_spaces:
text = re.sub(r"\\s+", " ", text).strip()
return text
注意:这里默认不小写、不删除 URL,而是可选替换为 <URL>。是否启用要由任务决定。
3.2 按任务选择清洗策略
| 文本分类 | 轻量规范化、去明显乱码、保留标点 | 强行词干化、删除所有停用词 |
| NER/信息抽取 | 保留大小写、数字、标点、实体格式 | 小写化、实体标准化过度 |
| 代码模型 | 保留缩进、符号、路径、大小写 | 删除特殊字符、折叠所有空白 |
| 对话/SFT | 保留角色、语气、emoji、换行 | 把多轮消息拼成无边界纯文本 |
| 法律/医学/金融 | 保留日期、金额、单位、引用 | 把数字统一替换成 NUMBER |
| 网页预训练 | 正文抽取、去模板、去重、质量过滤 | 只用正则清洗 HTML |
3.3 文本抽取优先于正则替换
网页数据的主要噪声通常来自 boilerplate,而不是几个特殊字符。优先处理:
- HTML 正文抽取。
- 广告、导航、页脚、cookie banner。
- OCR 和编码错误。
- 重复模板。
- 自动生成和低质量机器翻译页面。
如果没有做好文本抽取,后面的 tokenizer 再好也只是把噪声高效编码成 token。
第 4 层:现代数据策展流水线

4.1 质量过滤
质量过滤可以结合规则、统计特征和模型分类器:
- 文档长度、平均行长、标点比例、数字比例。
- 语言识别置信度。
- 重复 n-gram 比例。
- 困惑度或轻量语言模型分数。
- 质量分类器或教育价值分类器。
- 来源级别信誉和历史表现。
关键点:不要把质量过滤当成单个阈值。更好的做法是按来源、语言、领域分别观察分布,再做分桶采样和消融。
4.2 去重
去重是大模型数据预处理的核心步骤。
| exact dedup | hash 完全相同文本 | 删除完全重复样本 |
| near dedup | MinHash、SimHash、n-gram overlap | 删除转载、模板、轻微改写 |
| span dedup | 段落或片段级匹配 | 降低模板句和重复段落 |
| split dedup | train/val/test 之间去重 | 防止验证集泄漏 |
重复数据会让模型记忆增强、浪费训练 token,并抬高评测污染风险。
4.3 评测集去污染
如果训练集包含 benchmark 题目、答案或近似改写,模型评估会虚高。常见去污染方法:
- exact match。
- n-gram overlap。
- URL/source overlap。
- embedding similarity。
- answer leakage 检查。
- benchmark 版本记录。
去污染不是只在最终训练前做一次,而应该在数据版本迭代中持续执行。
4.4 PII、安全和许可证
现代数据预处理必须包含治理层:
- PII:邮箱、电话、身份证号、地址、密钥、API token。
- 安全:有害内容、违法内容、极端内容、恶意代码。
- 许可证:来源、使用范围、再分发限制、robots 和站点条款。
- 数据卡:来源、过滤规则、已知偏差、适用范围、不可用场景。
这些不是“额外合规文档”,而是模型能否可靠发布的一部分。
4.5 数据混合和采样配比
数据不是越多越好。高质量数据、小而干净的数据、合适的采样配比,常常比盲目扩大规模更有效。
需要记录:
- 各来源 token 占比。
- 各语言 token 占比。
- 代码、数学、网页、论文、书籍、论坛、对话占比。
- 高质量分类器分数分布。
- 去重前后 token 数。
- 每次训练使用的数据版本和采样权重。
第 5 层:样本构造
Tokenizer 之后,真正进入模型的是训练样本,而不是裸 token。

5.1 Chat template
对话模型训练时,消息结构通常是:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "解释 BPE 的核心思想。"},
{"role": "assistant", "content": "BPE 会反复合并高频相邻片段…"},
]
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=False,
)
关键要求:
- 训练和推理必须使用同一个模板。
- role token、BOS、EOS、stop token 要版本化。
- 工具调用和多模态占位符不能靠普通字符串随意拼接。
5.2 Loss mask
SFT 中通常只监督 assistant 的回答,不应该让模型学习“预测 user 的输入”。因此要构造 labels 或 loss_mask:
labels = input_ids.clone()
# assistant_mask=True 的位置参与 loss,其他位置设为 -100。
labels[~assistant_mask] = –100
如果 loss mask 错了,模型可能看似 loss 下降,但学到的是复述用户输入或模板格式。
5.3 Packing 和文档边界
为了提高训练效率,多个短样本常被 packing 到一个上下文窗口中。但 packing 要处理:
- 文档之间是否插入 EOS。
- attention 是否允许跨文档。
- labels 是否跨样本污染。
- padding side 是否与模型一致。
- 长文档是截断、滑窗还是分块。
预处理文档里必须写清这些规则,否则同一批 token 可能对应完全不同的训练目标。
第 6 层:工程落地清单
6.1 最小可用流水线
收集数据
-> 文本抽取
-> Unicode/空白规范化
-> 语言识别与来源过滤
-> exact/near dedup
-> PII/安全/许可证处理
-> 评测集去污染
-> tokenizer 训练或复用
-> chat template / sample packing
-> 数据卡与版本化
-> 小规模训练消融
6.2 数据版本必须记录什么
| 数据来源 | URL、数据集名、抓取时间、许可证 |
| 清洗规则 | normalization、过滤阈值、保留/删除字段 |
| 去重规则 | exact hash、MinHash 参数、去重粒度 |
| 安全治理 | PII 策略、安全分类器版本、人工审核规则 |
| Tokenizer | 算法、词表、特殊 token、chat template 版本 |
| 样本构造 | max length、packing、EOS、attention mask、loss mask |
| 采样配比 | 来源、语言、领域、质量分桶的 token 占比 |
| 评估 | 去污染规则、验证集版本、消融结果 |
6.3 实验验证
预处理策略必须通过实验验证:
第 7 层:常见问题诊断
| token 数异常变多 | tokenizer 对某语言/领域 fertility 高 | 增加领域数据训练 tokenizer,或换 byte fallback/更大词表 |
| [UNK] 很多 | 字符覆盖不足、规范化错误 | 检查 Unicode、byte fallback、词表覆盖 |
| 训练 loss 下降但生成格式混乱 | chat template 或 EOS 不一致 | 对齐训练/推理模板,检查 special token |
| 模型复述用户输入 | SFT loss mask 错 | 只监督 assistant 内容 |
| 评测成绩异常高 | benchmark 泄漏 | 做去污染和重复检测 |
| 模型记忆网页模板 | 去重和 boilerplate 清理不足 | 加强 near dedup 和正文抽取 |
| 某语言表现很差 | 数据配比或 tokenizer fertility 不公平 | 调整采样配比和词表覆盖 |
| 代码能力差 | 清洗删除了符号/缩进/路径 | 代码数据使用独立清洗规则 |
总结
数据预处理的核心不是“清洗得更干净”,而是“构造更可信的训练输入”。一篇现代版 Tokenizer 与数据清洗文章应该同时讲清:
- Tokenizer 的算法差异和输入协议作用。
- 词表大小、压缩率、延迟、多语言覆盖之间的权衡。
- 清洗策略为什么必须任务相关。
- 去重、去污染、PII、安全、许可证为什么是训练质量的一部分。
- chat template、packing、loss mask 为什么决定模型实际学习目标。
- 数据版本和实验消融为什么比经验口号更可靠。
如果只记一句话:
Tokenizer 负责把文本变成模型能读的协议,数据策展负责保证模型读到的是可信、干净、可追踪、不过度污染的样本。
参考资料
- Hugging Face Tokenizers 文档:https://huggingface.co/docs/tokenizers/index
- Hugging Face Transformers Tokenizer Summary:https://huggingface.co/docs/transformers/tokenizer_summary
- Hugging Face Chat Templates:https://huggingface.co/docs/transformers/chat_templating
- OpenAI tiktoken:https://github.com/openai/tiktoken
- SentencePiece 项目:https://github.com/google/sentencepiece
- Kudo and Richardson, SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing:https://aclanthology.org/D18-2012/
- Meta Llama 3 model card and prompt formats:https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3/
- Penedo et al., The FineWeb Datasets: Decanting the Web for the Finest Text Data at Scale:https://arxiv.org/abs/2406.17557
- Li et al., DataComp-LM: In search of the next generation of training sets for language models:https://arxiv.org/abs/2406.11794
- Penedo et al., The RefinedWeb Dataset for Falcon LLM:https://arxiv.org/abs/2306.01116
- Soldaini et al., Dolma: an Open Corpus of Three Trillion Tokens for Language Model Pretraining Research:https://arxiv.org/abs/2402.00159
- Together AI, RedPajama-Data-V2:https://www.together.ai/blog/redpajama-data-v2




