CS336-Lec1 Tokenization
Lec 1: Tokenization
语言模型的本质是对token序列做建模
\[ p(x_1, x_2, \dots, x_T) \] 其中\(x_T\)代表的是一个整数token id,我们还需要有一个抽象接口做双向映射,这个接口就是Tokenizer
encode(str) -> List[int]decode(List[int]) -> str
Tradeoffs:
- Volcabulary size is big: 序列短,但有可能稀有 token 学不到、embedding 巨大。
- Volcabulary size is small: 泛化好,参数省,但是序列长导致Transformer attention \(O(T^2)\) 很贵
Q&A:
为什么词表大 ≠ “更好学”,反而有“稀有 token 学不到”的问题?
如果词表里包含很罕见的整词一个 token:`“supercalifragilisticexpialidocious”,它在训练里可能只出现 1 次或几次,那它对应的 embedding/输出权重基本没训练到,这个时候如果用更小的词表,这个长词会被拆成多个更常见的子词/字符组合,能学的更好。
为什么词表大会导致embedding整体变大?
这个主要是因为输出的logits的形状大小是 \(\text{Vocab_size} \times \text{Hidden_size}\), 词表变大,这一块的参数量翻倍,代价更大
Compression Ratio
1 | num_bytes = len(string.encode("utf-8")) |
\[ \text{compression_ratio} = \frac{\text{num_bytes}}{\text{num_tokens}} \] 该值越大表明每个token能够表示更多的字节,序列更短,信息的压缩程度越高。
Character Tokenizer
核心思想是把字符串拆成Unicode字符,每个字符用 ord()
变成整数。decode 用 chr() 拼回去。
1 | encode: list(map(ord, string)) |
优点是简单、可逆,缺点是词表非常大(Unicode 大约 150K+),稀有字符非常多,浪费词表的容量。
Byte Tokenizer
核心思想就是字符串转 UTF-8 bytes,每个 byte 是 0~255。词表固定 256。
1 | string_bytes = string.encode("utf-8") |
Compression_ratio恒等于1
优点是词表很小,且完全可逆,但是序列太长,对于注意力机制来说是灾难。
Word Tokenizer
核心思想是用正则把文本切成segments,然后给每个 segment 分配一个 id
优点是序列会更短,缺点是词表的大小不固定,还有可能会爆炸,新词需要
UNK(会影响 perplexity & 泛化体验)
BPE Tokenizer
在“byte 的小词表”和“word 的短序列”之间找平衡:
- 常见片段合并成一个 token(序列变短)
- 罕见片段仍可退化成多个 byte
执行步骤,从byte开始,重复做num_merges次:
- 统计当前序列里所有相邻 pair 的出现次数
- 找出现最多的 pair
- 把它合并成新 token(新 id = 256 + i)
- 更新 vocab:
vocab[new] = vocab[a] + vocab[b] - 在序列里把所有该 pair 替换掉
Encode/Decode
- encode:从 bytes 开始,按 merges 顺序把能合的 pair 合掉
- decode:把每个 token id 映射回 bytes,再拼起来 decode utf-8
优点是词表可控(256 + num_merges),序列变短,缺点是朴素的encode会非常慢




