第2章:Tokenization基础——分词的基本概念

如果让我选一个在 NLP 里最基础、也最容易被忽视的环节,那一定是分词。说白了,Tokenizer 就是模型的“嘴巴和胃”——它决定了模型能吃什么数据、怎么消化。我见过的不少项目,模型结构调得花里胡哨,结果因为分词没处理好,训练出来的东西就是笑话。

这一章我们就把“分词”这件事扒开揉碎。不扯玄乎的数学,只讲清楚:它是什么?为什么需要它?有哪些主流做法?踩坑的时候该怎么逃?

2.1 什么是 Tokenization?

把一段文本拆成一个个最小语义单元的过程,就是 tokenization。这个最小单元叫 。可以是一个词、一个字符,或者一个子词。

核心定义:Tokenization 是将原始字符串转换成模型能处理的整数索引序列的桥梁。

举个例子:"我爱北京天安门" 如果按字符切,得到 ['我','爱','北','京','天','安','门'];如果按词切,可能得到 ['我','爱','北京','天安门']。不同的切法,模型看到的世界完全不同。

你可能会问:“为什么不直接用词呢?人类不就按词理解吗?”嗯,这里有个坑——自然语言里的词数量太大,英语有几十万,中文更是无穷尽。模型输入层不能无限大,所以我们需要一种折中方案。

2.2 Tokenization 解决了什么痛点?

  • 控制词表大小——从百万级压到几万甚至几千。
  • 处理未登录词(OOV)——比如“特朗普”没见过?子词可以拼成“特朗”+“普”。
  • 平衡语义粒度——字符太散,词太粗,子词刚刚好。
  • 提升模型泛化能力——学会子词组合规律,碰到新词也不慌。

我的经验:有一次处理医疗文本,里面全是“肺腺癌”、“EGFR突变”这种专业术语。如果用全词分词,词表爆了;用字符,模型根本学不会语义。最后用 BPE 训练了子词 tokenizer,效果提升一大截。

2.3 三类主流分词方法

方法类别 代表算法 优点 缺点
词级分词 Jieba、spaCy 语义完整,符合直觉 词表大、OOV问题严重
字符级分词 按unicode切 无OOV,词表极小 序列太长,丢失局部语义
子词分词(Subword) BPE、WordPiece、SentencePiece 平衡词表与语义,处理OOV 训练开销大,需要语料统计

现在主流大模型清一色用子词分词。我最早接触这个是在做翻译模型时,当时用 BPE 把中日韩语料统一处理,省了很多人工切词的事。

2.4 子词分词算法详解

2.4.1 BPE(Byte Pair Encoding)

BPE 的思想很简单:从字符开始,反复合并出现频率最高的相邻 对。直到达到目标词表大小。

# 伪代码示例
vocab = list(所有字符)
while len(vocab) < 目标大小:
    统计所有相邻pair出现频率
    合并频率最高的pair为新的
    更新语料

我曾经犯过的错:训练BPE时没有将数字和字母分开处理,结果模型把“2024年”切成了“2”“0”“2”“4”“年”,后来才知道,对数字做单独加固能大幅减少无意义合并。

2.4.2 WordPiece

类似 BPE,但合并标准不是“频率”,而是互信息(MI)。它倾向于合并那些一起出现时收益最大的 pair。BERT 就用这个。

2.4.3 Unigram Language Model

从大词表开始,逐步删除“最不重要”的 ,保留概率最大的那批。SentencePiece 默认支持这种模式。

2.4.4 SentencePiece

它把文本当作原始字节序列处理,连空格都不放过。这样天然支持多语言,不需要预切词。我个人很喜欢它的“直接训练,不用预分词”思路。

2.5 实际应用中的避坑指南

  • 词表大小怎么定?——通常 8k~64k。太小碎片化严重,太大模型参数多。我一般先尝试 32k。
  • 要不要保留特殊 ?——必须保留 [PAD]、[UNK]、[CLS] 等。忘了留 UNK 的话,模型遇到生僻字直接报错。
  • 中文需要额外处理吗?——如果用 BPE 或 SentencePiece,可以直接在字节级别切,不需要先分词。但如果你用 WordPiece,建议先用 jieba 切开再学,效果更稳定。

一句话总结:选 tokenizer = 选模型的“认知粒度”。训练前花半天调好 tokenizer,比后面花三天调模型有效得多。

Tokenization 知识体系 Tokenization 词级分词 字符级分词 子词分词 BPE WordPiece Unigram SentencePiece 常见陷阱 • 词表大小不匹配 • 忽略了特殊 实践建议:先用 SentencePiece 训练一个 32k 子词模型 → 观察切分结果 → 调整特殊字符处理 → 反复迭代

2.6 动手环节:用 HuggingFace Tokenizers 快速实践

光说不练假把式。下面我们用 HuggingFace 的 tokenizers 库训练一个 BPE 模型。我记得第一次跑这个代码用了不到 5 分钟。

from tokenizers import Tokenizer, models, trainers, pre_tokenizers

# 初始化空模型
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)

# 准备训练数据(这里用示例文本)
trainer = trainers.BpeTrainer(vocab_size=30000, special_tokens=["[UNK]","[CLS]","[SEP]","[PAD]","[MASK]"])
files = ["sample_text.txt"]  # 你得有自己的语料文件

tokenizer.train(files, trainer)

# 保存
tokenizer.save("my_bpe_tokenizer.json")

我建议:刚开始别贪心,用几百万行文本就够了。语料越杂(多领域、多语言),tokenizer 泛化越好。

训练完成后你可以加载它,看看 "我今天很开" 会被切出什么 ——如果看到“我很”被合并成了一个 ,别奇怪,BPE 觉得它俩经常一起出现。

好了,这一章的内容就到这里。记住,Tokenizer 不是调完了就完事的——它需要根据下游任务反复打磨。下个章节我们会聊模型怎么吃到这些 … 嗯,先卖个关子。