深入讲解字符级、词级、子词级三种分词方案的权衡,以「the cat sleeps」为例展示 token 到整数 ID 的映射过程,为后续嵌入层和注意力层奠基。
在 Phase 1 结束时我们得到了 Tensor 结构,它将支撑整个项目接下来的工作。现在该来解决一个表面看起来更简单、但值得在动手编码之前停下来思考一下的设计问题了:如何把文本转换成数字。
神经网络并不处理文本。它处理的是数字,更准确地说,是向量和矩阵上的线性代数运算。这意味着在任何内容接近 embedding 层、attention 层或其他任何结构之前,我们输入的文本需要变成一串整数。这就是 tokenizer 的唯一职责:成为人类能理解的语言与模型能操作的数值表示之间的桥梁。
tokenization 基本上有三种粒度层级,每种方式解决问题的手法不同,权衡也很明显。
以短语 "the cat sleeps" 为例,你可以看到每种方法如何不同地处理这个问题。
第一种是字符级(char-level),每个字符(包括空格)成为一个 token:
Text: "the cat sleeps"
Tokens: ['t', 'h', 'e', ' ', 'c', 'a', 't', ' ', 's', 'l', 'e', 'e', 'p', 's']
IDs: [1, 0, 5, 2, 8, 1, 0, 4, 1, 7, 6, 3, 9, 4]
这里的词表(vocabulary)是语料库中出现的所有不同字符(字母、空格、标点),英语的符号数量在 60 到 100 之间,实现也很直接,但代价是序列会变得很长,因为每个词会变成多个 token。
第二种是词级(word-level),每个词是一个单独的 token(通常也将标点分开):
Text: "the cat sleeps"
Tokens: ['the', 'cat', 'sleeps']
IDs: [12, 340, 891]
词表变成语料库中所有不同的词,数量很快就会达到数万条。而且模型在训练时没有见过的任何新词(比如一个专有名词)根本没有对应的 ID——这就是经典的 OOV(out of vocabulary,词表外)问题。
第三种是实际生产模型(以 GPT-2 及以上为例)所使用的方式:通过 BPE(Byte Pair Encoding,字节对编码)实现子词级(subword)。其逻辑是从字符级开始,不断合并出现频率最高的字符对,直到形成一个固定大小的词表(例如 10,000 个 token),这样常用词成为一个 token,稀有词或复合词则拆分成更小的片段:
Text: "the cat sleeping peacefully"
Tokens: ['the', 'Ġcat', 'Ġsleep', 'ing', 'Ġpeace', 'ful', 'ly']
IDs: [12, 340, 55, 891, 203, 77, 88]
(这里的 Ġ 是 GPT-2 的约定,用来标记词首空格)。注意 "sleeping" 变成了两个 token("sleep" + "ing"),"peacefully" 也是("peace" + "ful" + "ly"),算法学会了这些片段足够常见,值得拥有自己的 token,而不需要为语言中的每个完整词都建立一个条目。这是一种中间路线,同时解决了字符级的长序列问题和词级的 OOV 问题,代价是实现复杂度高得多。
在这个阶段,我选择了字符级,不是因为它是"正确"的方法(它不是生产环境所使用的),而是因为这里的目标是在不引入 BPE 合并算法复杂度的干扰下,理解端到端的机制。字符级能快速完成编码-解码循环,而且实现只需几行代码。在一个旨在巩固概念的阶段,这才是最重要的——不是去和生产级 tokenizers 竞争。
设计分为两个类:Vocabulary,负责字符与 ID 之间的映射(以及反向映射);以及 Tokenizer,使用这个词表将文本转换为 ID 并反向转换。这样分离的原因是——它为以后切换 tokenization 策略留出了空间(例如从字符级切换到 BPE),而不需要改动 Tokenizer 的消费者。
词表不能每次运行都重建。如果 Vocabulary 每次程序运行时都重新生成,那么分配给每个字符的 ID 在不同会话间可能会不同,在这种情况下,用一个词表训练的模型如果换用另一个词表就会失效,因为每个学到的权重直接依赖于它所代表的 ID。
词表在数据集准备阶段构建一次,之后持久化,在所有后续训练或推理中只加载(绝不重建)。而编码和解码则始终被频繁使用,始终针对同一个冻结的词表。
tokenizer 完成后,文本序列已经变成了整数序列,但这些整数对模型来说仍然没有意义——它们只是索引。下一阶段要解决的是:把每个 ID 转换成一个密集向量,能够在向量空间中承载某种语义 notion。这就是通往 embeddings 的入口。