人工智能实践(语言智能)
第1讲:经典 NLP 任务

1.2 分词 Tokenization

字 / 词 / 子词三条路线;BPE、WordPiece、SentencePiece 的差异;中文分词特殊性;tokenizer 选择如何直接影响 LLM 行为

为什么 Tokenizer 比你想象的更重要

几乎每个 LLM 用户都遇到过这些"玄学":为什么 GPT-4 算不对 9.11 > 9.8?为什么同样的中文长文本喂给 Qwen 和 Llama,上下文长度差一倍?为什么 ChatGPT 的 API 按 token 计费,一段中文要比同等信息量的英文贵两三倍?

全部都是 tokenizer 的锅。Tokenizer 是"原始文本"与"模型看到的 ID 序列"之间的翻译层,它决定了模型能在多少空间里表达一个句子、能否把一个数字作为整体处理、能否正确切分一个罕见术语的边界。理解 tokenizer 不是"实现细节",它直接关系到 LLM 行为的每一个可观测指标。


三条分词路线

字级(Character-level)

把每个字符作为一个 token。

  • 优点:词表小(中文约 6000-7000 常用字,英文 26 个字母),无 OOV 问题
  • 缺点:序列极长,模型需要学习更多层次才能从字符组合出语义
  • 典型应用:字符级 RNN、部分早期中文模型

词级(Word-level)

按空格或分词工具切成完整单词。

  • 优点:单位符合人的直觉,序列短
  • 缺点:词表极大(英文 100k+,中文分词后 500k+),长尾词无法覆盖,OOV 一律变成 <UNK>
  • 典型应用:早期 Word2Vec、经典 NLP 管道

子词级(Subword-level)

在字和词之间折中:常用词作为整体,罕见词拆成更小的有意义片段。这是所有现代 LLM 的默认选择


三种子词算法

BPE(Byte-Pair Encoding)

BPE 由 Sennrich 等(2016)引入 NLP,核心思想:从字级开始,贪心合并出现频率最高的相邻字符对,直到达到预设词表大小

初始化

把所有单词拆成字符序列,每个字符是一个基础 token。

统计相邻对

扫描语料,统计所有相邻字符对的出现次数。

合并最频繁对

把出现最多的相邻对合并为一个新 token,加入词表。

重复到目标词表大小

典型设置:30k-100k 个子词 token。

BPE 的变种 byte-level BPE(GPT-2 起广泛使用)以字节而非 Unicode 字符为基础单位,词表里永远不需要未知字符——任何字符串都能被拆成字节序列后再分词。

WordPiece(BERT 使用)

WordPiece 与 BPE 非常相似,区别在合并准则:BPE 合并"出现次数最多的对",WordPiece 合并"使语料似然提升最大的对"。

WordPiece 用 ## 前缀标记"这是一个单词的中间或末尾片段":

playing  →  play  ##ing

SentencePiece(T5、Llama、Qwen 使用)

SentencePiece 与前两者最大的区别:它把空格也当作普通字符。这意味着它不需要前置分词(pre-tokenization),能直接对任何语言的原始字节流训练——对中文、日语、泰语这些没有空格分隔的语言特别友好。

SentencePiece 内部可以用 BPE 或 Unigram LM 作为算法。Unigram LM 的做法反过来:先构建超大词表,再逐步删除对整体似然贡献最小的 token

算法合并准则代表模型空格处理
BPE最频繁的相邻对GPT-2/3/4、RoBERTa需要前置分词(英文按空格)
WordPiece最大化语料似然的对BERT、DistilBERT需要前置分词,用 ## 标记续接
SentencePiece (BPE)把空格作普通字符T5、Llama、Qwen、Mistral原始字节流直接训练,无需前置分词
SentencePiece (Unigram)删除对似然贡献最小的 tokenXLNet、ALBERT、mBART同上

中文分词的特殊性

中文没有天然的词边界空格,这让分词成为一个独立的 NLP 任务。传统中文分词工具:

  • jieba:最流行的 Python 中文分词库,基于前缀词典 + HMM
  • pkuseg:北大团队开发,领域自适应性强(新闻、医学、旅游等多域模型)
  • THULAC / HanLP / LAC:清华、百度等出品的替代方案
import jieba

text = "自然语言处理是人工智能的核心领域"
# 经典分词输出
print(list(jieba.cut(text)))
# ['自然语言', '处理', '是', '人工智能', '的', '核心', '领域']

但现代中文 LLM 并不使用 jieba。Qwen、Yi、DeepSeek、ChatGLM 等全部采用 SentencePiece BPE 直接在字节流上训练,原因有三:

  1. 端到端训练:tokenizer 和模型联合优化,跳过中间错误传递
  2. 多语言统一:一个 tokenizer 能同时处理中英日韩,无需切换分词器
  3. 罕见词/术语更稳健:jieba 对新词、专业术语依赖词典,BPE 能自动拆成可组合的子词

Tokenizer 对 LLM 行为的影响

压缩率与上下文长度

同一段中文在不同 tokenizer 下的 token 数差异巨大:

Tokenizer"自然语言处理是人工智能的核心领域"token 数
GPT-2 原版(英文优化)按字节拆,几乎 1 字 ≈ 2-3 token~30-40
GPT-4 (cl100k_base)中文有较多整词~15-20
Qwen3 tokenizer对中文高度优化~10-12

这直接意味着:同样的 32k 上下文窗口,Qwen3 能塞进的中文内容量可以是早期 tokenizer 的 3 倍。也直接意味着 API 账单上的 token 费用差异。

数字处理的坑

考虑 GPT-3.5 对数字 9.11 vs 9.8 的比较。许多 tokenizer 把 9.11 拆成 9, ., 11 三个 token,把 9.8 拆成 9, ., 8——在模型看来后者的第三个 token 是 8,前者是 11,从数值大小到 token 排序的对应关系彻底断了。Llama 3、Qwen3 等新一代模型为数学专门调整 tokenizer,将每个数字单独作为 token,显著改善算术能力。

语言不公平

大多数开源 tokenizer 以英文语料为主训练,造成低资源语言被严重"罚款"

  • 一条英文句子可能是 20 tokens
  • 同等信息量的印地语可能要 60 tokens,斯瓦希里语可能要 80 tokens

这不仅是成本问题,也让低资源语言在有限上下文中能承载的信息量更少,是 LLM 公平性的重要议题。

实践建议:在为特定领域(法律、医学、中文古籍)做 LLM 应用前,先用 tokenizer.tokenize(your_text) 看看你的领域文本被拆成了什么形状。如果关键术语被拆得稀碎,模型大概率在这类术语上表现不佳——此时考虑继续预训练(continued pre-training)时扩词表或改用领域专用 tokenizer。


实操:查看 tokenizer 的切分行为

from transformers import AutoTokenizer

qwen_tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-1.7B")
llama_tok = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")

text = "北京大学软件与微电子学院位于大兴区"

for name, tok in [("Qwen3", qwen_tok), ("Llama-3", llama_tok)]:
    ids = tok.encode(text)
    pieces = tok.convert_ids_to_tokens(ids)
    print(f"[{name}] token 数 = {len(ids)}")
    print(f"  pieces: {pieces}")

你会清楚看到不同模型家族对同样中文的切分差异——这往往比阅读 10 篇 tokenizer 论文更直观。


本节小结

概念要点
字级 / 词级 / 子词现代 LLM 几乎全部使用子词:词表可控、无 OOV
BPE贪心合并最频繁的相邻对;GPT 系列使用
WordPiece以语料似然最大化为准则合并;BERT 系列使用
SentencePiece把空格当普通字符,直接处理字节流;Llama / Qwen / T5 使用
中文场景jieba 等工具仍用于传统 pipeline;现代 LLM 直接 SentencePiece
Tokenizer 影响上下文压缩率、数字处理、语言公平性——不是实现细节
查看切分tokenizer.tokenize(text) 是诊断 LLM 异常的第一把刀