词表是从哪来的?
四步讲完 BPE,以及为什么最终生成的词表会永久成为它构建时所用文本的化石。
模型的词表是一张固定列表——通常几万条——在训练开始前就定了下来,之后再不改动。没有人手工写它,它是被一个算法长出来的。
搞清楚它是怎么长出来的,就能解释几件本来看着很随意的事:为什么有些词更费 token,为什么非英语文本更贵,以及为什么语言变了,模型的 tokenizer 却不能更新。
把 BPE 讲透
主流算法是 BPE(Byte Pair Encoding),四步就能说清:
- 从最小的单位起步——单个字节。
- 扫一遍大语料库,统计每一对相邻单位出现多少次,找出最频繁的那一对。
- 把这一对合并成一个新单位。记下这次合并,把它加进词表。
- 重复,直到词表达到目标大小。
就这么回事。跑 5 万次,你就得到一张 5 万条的词表。
看它跑一遍
拿一个只含 low、lower、lowest、slow、slowest 的小语料库。
起始单位:l、o、w、e、r、s、t。
l+o出现频繁 → 合并成lo- 现在
lo+w频繁 → 合并成low e+s在lowest、slowest里都出现 → 合并成eses+t→ 合并成ests+low→ 合并成slow
五次合并之后,词表里 low、est、slow 都成了独立单位。lowest 这个词现在只花两个 token(low + est),而不是六个字符。一个从没出现过的词,比如 glowing,照样能正常切成 token——只是要多花几块。
正是这个特性让 BPE 成立:高频的东西变便宜,罕见的东西依然能表示。
为什么词表是一块化石
第 2 步统计频次的对象是某个特定语料库。每一次合并决定,都是关于那份文本的一个事实。
由此带来几个永久性后果:
英语拿到了最多的整词条目,因为所用语料以英语为主。常见英语单词就是单个 token。同样的意思换另一种语言,尤其是非拉丁字母的书写系统,就会被切成更多块——同样的内容,你要付更多钱、占更多上下文窗口。
领域词汇取决于语料的构成。 如果语料里塞了大量代码,常见的编程标识符就成了单个 token。医学术语、法律术语能不能挣到自己的条目,取决于当初爬了什么。
词表没法更新。 每个模型参数都是针对这些确定的 token ID 训练出来的。加一条新条目,就意味着 embedding 表对不上了。tokenizer 造好之后新造出来的词,永远只能被切碎。
怪东西会被冻进去。 用原始网页爬取数据训出来的 tokenizer,会把一些奇怪的残留物——爬来的论坛用户名、标记语言的碎片——收成单个 token。这些 token 一旦被触发,有时会让模型做出非常古怪的行为,因为它们在词表里占了一席之地,却在随后用于训练的文本里极其罕见。
值得知道的几个变体
Byte-level BPE 从原始字节而不是字符起步,保证任何可能的输入都能表示。不会有「未知字符」,因为最坏情况它也能拆成字节。现在大多数模型用的都是这个。
WordPiece 类似,但它挑合并项的标准是哪一对最能改善训练目标,而不是原始频次。BERT 系列模型用它。
SentencePiece 把输入当成一条原始流,不假设空格分隔单词。这对不用空格的语言——日语、中文、泰语——关系重大。
为什么词表大小是个取舍
词表越大,序列越短——这是好事,因为注意力成本随长度增长得比线性更快。
但词表越大,也意味着更大的 embedding 表和更大的输出层,因为模型必须为每一条目产出一个概率。每多一条,两端都要多花参数。
还有个数据问题:罕见条目在训练里出现得太少,模型学不出它的好表示。过了某个点,多出来的条目就是纯粹的负担。
现在的模型大多落在几万到十几万之间,多语言模型会往上推,让非英语书写系统得到更公平的待遇。
要点回顾
- BPE 通过反复合并最频繁的相邻单位来长出词表。
- 结果是训练语料的化石——在那份语料里高频的东西,永远便宜。
- 训练之后没法更新,因为每个参数都绑死在确定的这些 token ID 上。
- 词表大小是在序列长度和参数数量之间做取舍。
下一篇: 训练与推理