Skip to content

分词器

在进入模型之前,文本必须先被切成模型认识的「token」。分词器(Tokenizer) 就是文本 ↔ token id 之间的翻译官,也是很多人踩坑的第一站。

1. 分词器做的三件事

原始文本 "I love NLP!"
   │  ① 切分 Split : 切成子词  ["I", "Ġlove", "ĠN", "LP", "!"]
   │  ② 映射 ID   : 查词表     [40,  2697,  6865, 3137, 0]
   │  ③ 后处理     : 加特殊符号/注意力掩码/截断填充

input_ids = [40, 2697, 6865, 3137, 0]   → 送进模型的就是这串整数

反过来,模型输出 token id,还要靠分词器 decode 回人类可读文本。编码/解码必须用同一个分词器,否则会乱码。

2. 为什么是「子词」而不是「词」

词级的问题:  "unbelievable" 没见过 → OOV(未知词) 直接抓瞎
子词级(BPE/WordPiece): unbelievable → un + believ + able   部件都认识!
  • BPE(GPT 系)、WordPiece(BERT 系)、SentencePiece/Unigram(LLaMA 等)思路略不同,目标一致:常用词整体成 token,生僻词拆成常见子词,兼顾词表大小与覆盖率。
  • 中文常按切( ),或字+子词混合。

3. 一行代码用起来的 AutoTokenizer

python
from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("gpt2")        # 自动匹配该模型的 BPE 分词器
enc = tok("I love NLP!", add_special_tokens=True)  # GPT-2 基本不额外加特殊符
print("tokens :", tok.convert_ids_to_tokens(enc["input_ids"]))
print("ids    :", enc["input_ids"])
print("decode :", tok.decode(enc["input_ids"]))    # 还原成文本

不同模型用各自的分词器,绝不能混用(拿 GPT-2 的分词器配 BERT 模型,结果全错)。

4. 批量、填充与截断

真实训练要一次处理多条长短不一的句子,需要 padding(补齐)和 truncation(截断):

python
batch = tok(
    ["I love NLP", "Transformers are great for many tasks"],
    padding=True,      # 短的用 <pad> 补到同长
    truncation=True,   # 长的截断到 model_max_length
    return_tensors="pt",   # 直接返回 PyTorch 张量
)
print(batch["input_ids"].shape)       # [2, 最大长度]
print(batch["attention_mask"])        # 1=真实token, 0=padding(注意力要屏蔽它)

attention_mask 至关重要:它告诉模型哪些是补出来的空位,注意力必须屏蔽,否则结果被污染。

5. 特殊 token 一览

[BOS]/<bos>  序列开始        [EOS]/<eos>  序列结束(生成时"写到这里就停")
[PAD]/<pad>  填充补齐         [CLS] BERT 句首聚合   [SEP] BERT 分隔
[MASK]       BERT 完形填空要预测的位置    [UNK] 未知词兜底

不同模型的特殊符不同(GPT-2 用 endoftext 作为 EOS,很多对话模型还有 im_start / im_end 之类的 ChatML 控制符)。微调时要确保这些 token 正确注册,否则模型学不到「何时停止」。

6. token 数 = 成本与长度上限

上下文长度按 token 计, 不是按字符/单词:
  "internationalization" → 1~几个 token(子词)   中文一个字常 1~2 token
  API 计费、显存占用、能否塞进 prompt, 都取决于 token 数

想估算成本或判断会不会被截断,先 len(tok(text)["input_ids"]) 数一数。

7. 训练自己的分词器(选读)

python
from tokenizers import Tokenizer, models, trainers
tok = Tokenizer(models.BPE())
trainer = trainers.BpeTrainer(vocab_size=4000, special_tokens=["<unk>","<pad>","<eos>"])
tok.train_from_iterator(["我们的语料 每一行 ...", ...], trainer)

小语料(如 NanoGPT 的字符级实验)时,我们甚至直接把每个字符当作一个 token,用最简单的词表——实战章节会这么干。

小结

  • 分词器负责 文本 ↔ token id 双向翻译,编解码必须同一套。
  • 子词(BPE/WordPiece)解决未知词与词表膨胀问题。
  • padding/truncation/attention_mask 是批处理的三件套。
  • 特殊 token(尤其 EOS/PAD)关系生成能否正确停止。
  • token 数决定长度上限与成本。

下一步

有了 token,看看怎么加载模型、一句话出结果 → 模型与推理