分词器(Tokenizer)通俗详解

本文基于 GPT_teacher-3.37M-cn 这个教学项目,把分词器相关的核心概念、原理和常见疑问整理成一篇完整的解释文章。文中所有示例数据均来自本项目真实的 tokenizer/tokenizer.json


目录

  1. 分词器是什么
  2. 分词器就是一个 JSON 文件吗
  3. 核心数据:vocab 与 merges
  4. 分词是查表吗
  5. 分词器训练:如何从语料总结规则
  6. 分词器和模型的绑定关系
  7. 能否直接用通用分词器
  8. 分词器算不算一个模型
  9. 数据的数量与质量意味着什么
  10. 不同分词算法的区别
  11. 核心结论速览

1. 分词器是什么

分词器(Tokenizer)负责在「文本」和「token id 序列」之间做双向转换:

  • 编码(encode):把人类文本切成一个个 token,再映射成整数 id,喂给模型。
  • 解码(decode):把模型输出的 id 序列还原回文本。

模型本身只会处理数字(张量),不认识文字。分词器就是模型的「输入输出翻译层」。


2. 分词器就是一个 JSON 文件吗

在本项目里,分词器保存为 tokenizer/tokenizer.json(约 65KB)。但要分清:

  • JSON 只是它的保存格式,本质是一个训练出来的 BPE 分词器
  • 它由 src/build_tokenizer.py 用 HuggingFace tokenizers 库训练得到,模型类型是 BPE,字节级预分词(ByteLevel)。

这个 JSON 的顶层字段:

1
version, normalizer, pre_tokenizer, post_processor, decoder, model, added_tokens

其中最重要的是 model 字段,它包含 vocabmerges,其余都是「周边配置」。

加载时一行代码即可还原成完整的分词器对象:

1
2
from tokenizers import Tokenizer
tok = Tokenizer.from_file("tokenizer/tokenizer.json")

3. 核心数据:vocab 与 merges

分词器 JSON 里最重要的就是这两部分。

3.1 model.vocab —— 词表(token → id 映射)

一张字典表,把每个 token 映射到唯一整数 id。本项目实测共 821 条:

1
2
'<bos>' -> 0   '<eos>' -> 1   '<pad>' -> 2   '<unk>' -> 3   # 特殊符号排最前
'0' -> 4 '1' -> 5 ... ':' -> 11 'E' -> 12 ... # 单字节/单字符
  • 作用:编码的最后一步查这张表把 token 变 id;解码时反查。
  • 注意:本项目用 ByteLevel(字节级)BPE,中文先按 UTF-8 拆成字节,再把字节映射成可打印字符。所以词表里看到的是 ï ¼ æ ĺ 这类「字节片段」,而不是直接的汉字,但组合起来能还原任何中文。

3.2 model.merges —— BPE 合并规则(有序)

BPE 的灵魂,一份有序的「合并配方」,本项目实测共 715 条:

1
2
['ï', '¼']      # 第 1 条:优先把 ï 和 ¼ 合并
['ï¼', 'Į'] # 第 7 条:再把上一步结果 ï¼ 和 Į 合并(ï¼Į 即中文逗号「,」的字节组合)

关键点:

  1. 顺序 = 优先级。排在前面的合并先执行,这是训练时按共现频率从高到低学出来的。
  2. 合并会滚雪球:先合成的子词能继续和别的合并成更大的 token。

3.3 为什么它俩最重要

  • 换了它俩 = 换了整个词表和切分方式,vocab_size 一变,模型维度就得跟着变、必须重训。
  • 其他字段(normalizer / pre_tokenizer / post_processor / decoder)只是周边配置,重要性远不如这两个。

一句话vocab 是「有哪些 token、各自编号多少」,merges 是「怎么把小片段一步步拼成这些 token 的规则和顺序」。


4. 分词是查表吗

不是单纯查表,而是「先按 merges 规则做合并计算,最后才查 vocab 表」。 查表只是最后一步。

完整编码流程(以「注意力」为例,实测切成 3 个 token):

  1. 拆字节:「注意力」→ UTF-8 字节 → ByteLevel 映射成基础字符(初始每个字节是独立 token)。
  2. 按 merges 反复合并(重点,不是查表):按优先级顺序把相邻能合的对不断合并。
  3. 查 vocab 表:最终每个 token 换成 id。只有这一步是纯查表。

本项目真实编码结果:

1
2
3
'注意力'      -> tokens: ['注æĦı', 'åĬ', 'Ľ']                         ids: [203, 110, 99]
'你好世界' -> tokens: ['ä½ł', '好', 'ä¸', 'ĸ', 'ç', 'ķ', 'Į'] ids: [224, 251, 109, 94, 66, 93, 84]
'量子纠缠XYZ' -> tokens: [..., '<unk>', '<unk>', '<unk>'] ids: [..., 3, 3, 3]

从这三个例子能看出:

  • 「注意」的字节被合并成了一个 token(语料里高频共现)。
  • 「世界」却被拆成好几个 token(语料里没学到合并规则)——同样是中文,切法完全不同,这是「算」出来的,不是查固定表。
  • 末尾 XYZ 直接变成 <unk>——小词表里没有大写 X/Y/Z,查不到就兜底成 <unk>

准确说法:分词 = 规则驱动的贪心合并(merges 的有序优先级)+ 末端查表(vocab 取 id、查不到给 <unk>。更像「拼积木」而不是「查字典」。


5. 分词器训练:如何从语料总结规则

BPE 靠「数频率、贪心合并」总结规则——谁最常挨在一起,就先把谁合并成一个 token。是否成词,完全由「在语料里共现的频率」决定,而不是靠语义。

本项目真实语料统计:

语料中出现次数 结果
注意 80 次 被合并成一个 token
世界 0 次 从未共现,拆成多个字节 token

BPE 训练算法(不断重复这几步):

  1. 初始化:把文本拆到最小单位(本项目是字节),每个字节是一个独立 token。
  2. 统计相邻对频率:扫全部语料,数每一对相邻 token 出现多少次。
  3. 合并最高频的那一对:合并成新 token,记入 merges(所以 merges 按频率排序)。
  4. 重复 2-3:合并后重新统计(「注意」已是整体,可继续和「力」组队),直到达到设定 vocab_size 或无可合并对。

由此能理解几个现象:

  • 换语料,同一个词的切法就变。
  • 高频词往往是 1 个 token;生僻词被切成好几块。
  • 英文里 the / ing / tion 常是独立 token,因为它们超高频。

一句话:BPE 是纯粹的统计压缩——频率是它唯一的「老师」,它不理解「世界」是个词,只是发现(或没发现)这两个字节老爱粘在一起。


6. 分词器和模型的绑定关系

模型绑定分词器(一对一、锁死),但一个分词器可以服务多个模型(一对多、可复用)。

  • 模型 → 分词器:强依赖、单向绑定。
    模型的嵌入层和输出头都按 vocab_size 构建:

    1
    2
    self.tok_emb = nn.Embedding(vocab_size, n_embd)   # src/model.py
    self.head = nn.Linear(n_embd, vocab_size, bias=False)

    vocab_size 直接取自 tok.vocab_size。训练好后就锁定——换分词器会导致维度对不上,best.pt 直接报错或输出乱码,必须重新训练

  • 分词器 → 模型:独立、可复用。
    分词器本身不含任何模型信息,只是文本 ↔ token 的映射规则,谁都能拿来用。同一个分词器可服务于基座模型及其各种微调版本、不同规模的模型,甚至跨项目复用。

类比:分词器像「字典/编码标准」,是相对通用的基础设施;模型是「基于这套编码学出来的大脑」,一旦学成就跟这套编码绑定。字典能给很多人用,但每个大脑只认自己学习时用的那本字典。

分词器语料 ≠ 模型语料

正常情况下,分词器的训练数据不必和模型的训练数据完全一致,两者是解耦的:

  • 分词器只学「字符/子词的组合统计规律」,不关心知识内容。
  • 大模型实践中,分词器一般在一份「大而杂」的通用语料上先单独训一次定下词表,之后的预训练、微调可能用不同数据,但都复用这同一个分词器。
  • 唯一硬约束:分词器一旦定下并开始训模型,两者就锁定,不能中途更换。
  • 现实建议:分词器语料应「覆盖」模型数据的语言/领域分布,否则会大量 <unk>、切分过碎。即「不必一致,但要覆盖得上」。

7. 能否直接用通用分词器

可以,而且是正规做法,只要满足两点:

  1. 词表覆盖你的语言/领域:训中文模型就用中文/多语言分词器,别拿纯英文分词器切中文。
  2. 在训练前定好:因为 vocab_size 决定模型维度,必须「先定分词器,再建模型、开训」,不能训到一半再换。

现实中大量项目直接复用开源分词器(如 GPT 系的 tiktoken、Llama/Qwen 的 SentencePiece 词表等),省去自训成本、获得更成熟的词表。

在本项目中换分词器的步骤:

  1. 把通用中文 tokenizer.json 放到 tokenizer/ 下(或改 config.ymltokenizer.path);
  2. vocab_size 会从 821 变成如 32000,模型维度随之改变;
  3. 必须重新训练(旧 best.pt 维度对不上)。

利弊权衡

  • 优点:覆盖广、几乎不出 <unk>、无需自训、更接近真实大模型做法。
  • 代价:词表变大(几百 → 几万),会显著增大模型嵌入层和输出头的参数量。对这个仅 3.37M 的教学小模型来说,可能让参数暴涨、小数据更难训好——所以本项目故意训了个 821 的迷你词表来匹配它的迷你规模。

8. 分词器算不算一个模型

它是一个「训练出来的统计模型」,但不是「神经网络模型」。 两者都叫 model,但内涵不同。

为什么算模型:它从数据训练得到、有「参数」(词表 + 合并规则),HuggingFace 里其内部结构就叫 models.BPE

为什么和 GPT 那种模型不是一回事

维度 分词器(BPE) 神经网络模型(GPT)
本质 查表 + 贪心合并规则 千百万浮点权重的矩阵运算
学的是 字符/子词的共现统计 语言的语义与推理
梯度下降 无(纯统计计数) 有(反向传播)
运行时 确定性查表,无浮点计算 大量矩阵乘法、概率采样
是否「理解」 不理解含义,只切分 建模语义

准确说法:广义上它是「统计模型/规则模型」;严格 AI 语境下它是预处理组件,不是神经网络模型。分词器是「学出来的编码规则表」,神经网络才是「会算会推理的大脑」。


9. 数据的数量与质量意味着什么

分词器的质量确实高度依赖语料,但要精确区分:

BPE 分词没有「准确/错误」之分——它只是压缩编码,把「世界」切成 1 个还是 7 个 token 都不算错。更准确的说法是分词的**「质量/效率」**。

语料对分词器的影响:

  1. 数量不足 → 词表窄、切得碎、大量 <unk>(本项目 600 条语料 → 词表仅 821)。语料越大,常见词越可能合成整块,编码越紧凑。
  2. 质量/分布影响 → 词表偏向语料领域。语料脏会学到无用的合并规则、浪费词表名额。想要通用分词器就得用又大又杂的语料。

分词器 vs 模型,对数据要求不同

  • 分词器:只需语料「量大、覆盖广」,不在乎内容对不对——哪怕语料是胡说八道,只要字词分布够广也能训出好用的分词器。
  • 模型:才真正在乎数据的「知识正确性、答案质量」。

结论

  • 分词器的「覆盖度和编码效率」← 靠语料的数量 + 分布广度
  • 模型回答的「准确性」← 靠语料的质量 + 数量 + 多样性
  • 「变种问题答不对」主要是模型侧(参数小 + 数据少)的问题,分词器基本不背这个锅。

一句话:分词器要的是「见得多、见得杂」;模型要的是「学得对、学得广」。


10. 不同分词算法的区别

分词算法决定「用什么策略把文本切成 token」——切分思路、粒度、对生僻词/多语言的处理、编解码是否可逆。

1. BPE(本项目用的)

  • 反复合并最高频的相邻对。简单高效、压缩率好;GPT 系、Llama 用它。
  • 叠加 Byte-Level 后永远不会真正 <unk>(任何字符都能用字节表示),代价是中文可能切得较碎。

2. WordPiece(BERT 用)

  • 合并时看「能最大提升语言模型似然」的对(概率打分,而非纯计数)。生僻词用 ## 前缀拼接(如 play ##ing)。

3. Unigram / SentencePiece(T5、Llama、多语言常用)

  • 反着来:先建大候选词表,再逐步删掉贡献最小的 token。把空格也当普通字符,天然适合中文/日文等无空格语言、多语言友好。

4. 字符级 / 字节级(本项目 fallback ByteTokenizer

  • 不做任何合并,每个字符/字节就是一个 token。词表极小(本项目 259)、绝不 <unk>,但序列超长、效率最低。

算法选择的影响维度

维度 影响
切分粒度 同句话切成几个 token → 影响序列长度、算力、上下文容量
生僻词处理 字节级永不失败;WordPiece 靠 ##;纯词级会 <unk>
多语言能力 SentencePiece/字节级对无空格语言、多语言更友好
压缩效率 BPE/Unigram 好;字符级最差
可逆性 字节级/SentencePiece 能无损还原;某些处理会丢空格

算法不改变「分词器和模型绑定、要靠语料训练」这些根本规律,它只决定切分的策略和风格。对中文而言,Byte-Level BPESentencePiece(Unigram) 是最主流的两个选择。

一句话:语料决定分词器「见过什么、切得好不好」;算法决定它「用什么套路切、怎么处理没见过的字、切完能不能还原」。


11. 核心结论速览

  1. 分词器负责「文本 ↔ token id」的双向转换,是模型的输入输出翻译层。
  2. 本项目分词器保存为 tokenizer.json,核心资产是 vocab(词表)和 merges(合并规则)。
  3. 分词 = 规则驱动的贪心合并 + 末端查表,不是单纯查字典。
  4. BPE 训练靠「数频率、贪心合并」,频率是唯一的老师,与语义无关。
  5. 模型绑定分词器(锁死),但分词器可复用于多个模型。
  6. 分词器语料不必等于模型语料,但要覆盖得上。
  7. 可以直接用通用分词器,但必须在训练前定好、且换了要重训。
  8. 分词器是统计模型,不是神经网络模型。
  9. 分词器吃「数量 + 广度」,模型吃「质量 + 数量 + 多样性」。
  10. 不同算法(BPE / WordPiece / Unigram / 字节级)决定切分策略、生僻词处理和多语言能力。