第 02 · 分词 · 8 min
从文本到标记
文本如何变成数字。BPE、子词,以及为什么大语言模型难以计算字母数量。
为什么需要 Token?
语言模型无法直接处理文本。它处理的是数字。每次你与大语言模型对话,第一步都是将你的文本转换成一个整数序列——即 Token ID。
词元化(Tokenization)就是实现这种转换的分割过程。
为什么不一个词一个 Token?
乍一看,你可能会设想:一个词 = 一个 Token。简单明了。
但这行不通:
- 语言中有数百万个可能的词(变形、新词、专有名词、错别字……)。每词一 Token 需要一个巨大的词表。
- 模型对它从未见过的词无能为力。
- 有些语言(中文、日文)词与词之间根本没有空格。
几乎所有现代大语言模型采用的解决方案是:子词(Subword)。
子词词元化
使用子词词元器:
- 高频词成为单个 Token("的"、"是"、"在")
- 罕见词被拆分成更小的片段("tokenization" → "token" + "ization")
- 未知字符总是可以分解到单个字母
结果:一个大小合理的词表(通常在 30,000 到 200,000 个 Token 之间),可以表示任意文本。
使用最广泛的算法叫做 BPE(字节对编码)。它从单个字符开始,在训练语料库中迭代地合并最频繁出现的字符对。
三十秒理解 BPE
设想一个只有三个词的迷你语料库:low、lower、lowest。我们先在字符级别进行词元化:
l o w
l o w e r
l o w e s t
在每次迭代中,我们寻找最频繁出现的相邻 Token 对。这里,l o 出现了三次——我们将它合并为 lo:
lo w
lo w e r
lo w e s t
现在 lo w 是最频繁的对。我们再合并:
low
low e r
low e s t
如此继续,直到达到目标词表大小。频繁出现的片段(low)变成单个 Token。罕见的(est)则保持分解状态。这正是 BPE 所做的——只不过在数十亿词上进行,而不是三个,并且在现代模型中是基于字节而非字符(byte-level BPE),这保证了任何输入都不会"超出词表"。
几个值得了解的近亲算法:WordPiece(BERT)、SentencePiece(T5、Llama)、Unigram LM(mT5)。它们共享同一个思想——子词词表——只是合并启发式不同。
特殊 Token
除了文本子词之外,词元器还保留了一些特殊 Token,它们在自然文本中永远不会出现:
<|im_start|>、<|im_end|>(OpenAI),[INST]…[/INST](Llama),<|user|>/<|assistant|>——用于分隔对话轮次。<|endoftext|>——文档结尾。<|fim_prefix|>、<|fim_middle|>——用于中间填充(fill-in-the-middle),常用于代码补全。
正是这些标记把*"用户说了 X,助手回复 Y"*这样的对话转换成模型可处理的单一线性 Token 序列。当你向 ChatGPT 发送消息时,这些标记会在词元化之前被自动加上。
亲自试试
右侧显示出子词:每个词元都是一个可复用的片段,不一定是一个完整的单词。常见词只占一个词元,而生僻词会被拆成多块。
有几点值得注意:
- 短而高频的词很少被拆分。
- 长词或罕见词往往会被分成多个片段。
- 词前面的空格是 Token 的一部分(这就是为什么
· hello和hello是不同的 Token)。 - 对于中文,词元化的方式与英文有所不同——模型在训练中接触过的语言越多,词元化效率就越高。
实际影响
Token 这件事有很多出乎意料的影响:
- 大语言模型数字母很差。"'草莓'里有几个'莓'字?"——它们经常答错,因为单词是以几个 Token 而非单独字母的形式进入模型的。
- API 价格按 Token 计算,而不是按词数计算。词元化效率较低的语言处理起来会贵一些。
- 上下文窗口(
128k tokens、200k tokens……)也以 Token 为单位衡量。一部 10 万字的书大约相当于 15 万个 Token。
对模型来说,"tokenization"和"token·iza·tion"是同一回事。它只看到这些片段。
接下来
你的文本现在已经变成了一个整数序列,这些整数将进入模型。模型内部的第一步:它们被转换成向量,在一个数百维的空间中,意义成为几何上的位置。
这就是下一章的主题。
更新于