Tokenizer 的核心任务是将人类可读的自然语言文本,转换为模型可以处理的离散 token 序列,是大模型性能的基础瓶颈之一。

为什么不直接按字 / 词分词?

  • 按字分词:词汇表太小(中文约 3000 常用字),但序列长度会爆炸("人工智能" 变成 4 个 token),导致计算成本指数级上升,且丢失了词级语义。
  • 按词分词:序列长度最短,但词汇表会无限大(新词、专有名词、错别字无法处理),且中文没有天然空格分隔,分词错误会直接导致语义错误。

我们需要一种介于字和词之间的分词单位 —— 子词(Subword),既能控制词汇表大小,又能处理未登录词(OOV)。

BPE(Byte Pair Encoding):最经典的子词算法

BPE 是 GPT 系列、LLaMA 系列使用的分词算法,核心思想是统计最频繁出现的相邻字符对,将其合并为一个新的 token,迭代直到达到预设的词汇表大小。

完整流程示例

  1. 初始词汇表:所有单个字符 {a, b, c, ..., z, _, ...}_ 表示单词开头)
  2. 统计语料中所有相邻字符对的出现频率
  3. 合并频率最高的字符对,加入词汇表
  4. 重复步骤 2-3,直到词汇表大小达到目标(通常为 32k-128k)

例子

  • 语料:low low low newer newer wider wider
  • 第一次合并:er(出现 4 次)→ 词汇表新增er 每次只合并当前频率最高的一对字符
  • 第二次合并:_n(出现 2 次)→ 词汇表新增_n 合并至最后
  • 最终分词:其为贪心合并
[low] _ [low] _ [low] [_n] [ew] [er] [_n] [ew] [er] _ [wid] [er] _ [wid] [er]

SentencePiece:谷歌的通用分词方案

SentencePiece 是 BPE 的改进版,解决了 BPE 的两个核心问题:

  1. 语言无关性:不需要预先对文本进行分词(如中文分词),直接将输入视为 Unicode 字符流
  2. 空格处理:将空格本身作为一个普通字符处理,而不是单词分隔符

SentencePiece 的两个核心改进

  • Byte-level BPE
    1. 初始词汇表:固定包含 256 个 UTF-8 字节 + 特殊 token(<s>, </s>, <pad> 等)
    2. 文本预处理:将输入文本直接转换为 UTF-8 字节流
    3. BPE 合并:和传统 BPE 完全一样,统计相邻字节对的频率,合并最高频的字节对
    4. 终止条件:直到词汇表大小达到预设目标(通常为 32k-128k)
  • Unigram Language Model概率最大的分词就是最优分词

完整训练流程

  1. 构建初始大词汇表:包含语料中所有出现过的字符,以及所有出现频率超过阈值的 n-gram(通常是 1-6 gram)
  2. 计算每个 token 的概率:使用 EM 算法(期望最大化)计算每个 token 在语料中的最优概率
  3. 计算每个 token 的损失:如果删除这个 token,语料的对数似然会下降多少
  4. 删除损失最小的 token:保留损失最大的 token(即对语料贡献最大的 token)
  5. 重复步骤 2-4:直到词汇表大小达到预设目标
第二步:使用 Viterbi 算法进行分词

训练完成后,我们得到了一个词汇表和每个 token 的概率。对于一个新的句子,我们使用Viterbi 算法快速找到概率最大的分词方式。Viterbi 算法是一个动态规划算法,它可以在 O (nL) 的时间复杂度内找到最优分词,其中 n 是句子长度,L 是词汇表中最长 token 的长度。

特性

Unigram LM

BPE(字节对编码)

分词方式

全局最优(基于概率最大化)

贪心局部最优(基于频率合并)

训练方向

从超大初始词汇表逐步删除

从单个字符逐步合并

概率模型

有明确的概率模型,每个 token 都有概率

无概率模型,只有合并规则

输出能力

可以输出多个候选分词及其概率

只能输出唯一的分词结果

生僻字处理

与 byte_fallback 天然兼容,鲁棒性好

生僻字容易被拆成单个字符,鲁棒性差

多语言支持

对无词边界语言(中文、日文)表现更好

对有词边界语言(英文)表现较好

token 是怎么变成数字的?

这个过程非常简单,分为两步:

  1. 构建词汇表(Vocabulary):分词算法训练完成后,会生成一个固定的词汇表,每个 token 对应一个唯一的整数 ID
  2. 映射(Lookup):将分词后的 token 序列,通过词汇表映射为对应的整数 ID 序列

例子

  • 词汇表:{"我": 100, "爱": 200, "人工": 300, "智能": 400}
  • 文本:"我爱人工智能"
  • 分词结果:["我", "爱", "人工", "智能"]
  • 数字序列:[100, 200, 300, 400]

注意:词汇表中还会包含一些特殊 token,如:

  • <s>:句子开始 </s>:句子结束 <pad>:填充 token(用于将不同长度的序列对齐到相同长度)
  • <unk>:未知 token(虽然现代分词器几乎不会出现)

为什么中文分词会影响模型效果?

  1. 中文没有天然空格分隔:英文单词之间有空格,分词歧义少;中文需要模型自己判断词的边界分词错误会直接导致语义错误,如 "南京市长江大桥" 可能被错误分为 "南京 / 市长 / 江大桥"
  2. 子词粒度的平衡:粒度过细(接近字):序列长度增加,计算成本上升,且丢失词级语义粒度过粗(接近词):词汇表变大,未登录词增多,模型泛化能力下降中文的最优子词粒度与英文不同,需要专门针对中文语料训练分词器
  3. 词汇表覆盖度:中文的常用字约 3000 个,但常用词超过 10 万个,还有大量的专有名词、成语、网络用语如果分词器的词汇表没有覆盖这些词,就会将其拆分为无意义的子词,导致模型无法理解
  4. 歧义消解:中文存在大量的分词歧义,如 "乒乓球拍卖完了" 可以分为 "乒乓球 / 拍卖 / 完了" 或 "乒乓 / 球拍 / 卖完了"分词器的歧义消解能力直接影响模型的语义理解能力
Logo

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

更多推荐