Tokenizer
1 为什么LLM需要Tokenization
人类的幼崽在学会说话后,就已经有了对于这个世界的先验知识。比如,它知道“香蕉”是一种黄色的好吃的水果,它知道“水果”是什么,它能够利用已经学会的词汇解释新的词汇,它知道“香蕉”和“苹果”在潜在的表意空间中很接近。
但是,如果我们想要教会一个一无所知的机器去阅读文本、理解语义,我们是否还需要从单词开始教起?机器暂时还没有眼睛,所以我们无法拿着香蕉告诉它这是香蕉。但是,我们至少可以告诉它,“香蕉”是一个单独的词汇,这个句子应该这样读:“香蕉”是一种水果,而不是:香“蕉是”一种水果。
同时,我们希望尽可能多的告诉它人类语言的基本规律。
例如,“香蕉”可能经常和“苹果”一起出现(或者“牛奶”)。
对于没有任何语言结构知识的模型来说,直接输入一段文本无疑于输入一段乱码。
而我们希望像 BERT 或 GPT 这样的模型,在理解语义之前,首先需要在一个较低级别的层次上学习语法知识,这一点可以利用特殊的结构设计实现。
分词让我们更方便地做到以下两点:
将输入的长串文本转为更细粒度的划分,即token
将输入转为向量,通过向量之间的关系表示词与词之间的关系,可以作为更高级别 NLP 任务(例如文本分类)的输入
这样,使得输入的一段话,成为拥有上下文语义背景的一段话。例如:”香蕉是一种水果“。我们知道香蕉是什么,水果是什么(或者类似什么)。
2 分词粒度
在教会LLM说话之前,需要准备一个词表,要尽可能高效地表征人类语言的基础用法。因此,在评价分词粒度好坏的时候,一般是以下几个角度:
词表大小是否合适?
词表能否有效表征语义?
2.1 字符级别
一个字母(英文)或者一个字(中文)就是一个字符,每个字符作为一个token。

优点:
词表很小。像英文只有26个字符,中文用5000个常用汉字也能适用于大部分场景
缺点:
为每个字符学习嵌入向量时,每个向量容纳了太多的语义在内,表征能力不足
基本上没有利用词汇的边界信息和语义信息(然而对于中文来说一个字是有表意功能的,因此可以使用字符级别的分词,有时可能效果更好)
长文本需要一个超长的向量表示,容易遇到性能瓶颈
2.2 word级别
对于英文及拉丁语系的语言来说,词与词之间有着天然的空格作为分割。而对于中文来说,词与词之间连在一起,需要首先经过分词算法。
优点:
符合人类的阅读习惯,保留了词汇的边界信息和语义信息
缺点:
由于长尾效应,词汇表可能过于庞大
词表中的低频词/稀疏词在模型训练过程中无法得到充分训练,进而模型不能充分理解这些词的语义
仍然有很多新词,容易OOV(Out-of-vocabulary)
不同词性的词,比如”banana”和“bananas”很难得到有效的表示(无法理解关联性)
2.3 子词级别
subword/子词级,它介于字符和单词之间。是当前最广泛的使用方法。
统计每个词的词频。
对于频率高的词,不进行拆分
对于频率低的词,拆分成小的有意义的词
对于后缀,一般前面会增加一个特殊标记,例如将
tokenization实际会拆成token和##ization,##标记ization是一个后缀
优点:
用一个适量的词表大小几乎就可以表示所有词
模型甚至可以理解没见过的词
缺点:
无法解决单词拼写错误的问题
3 常用Tokenize算法
3.1 BPE
Byte Pair Encoding
步骤:
准备足够大的语料库C
定义好需要的词表大小V_size
将单词拆分成字符序列,在每个单词末尾添加后缀</w>,并统计单词频率
停止符</w>表明subword是词后缀,如
st不加</w>可以出现在词首,如st ar;加了</w>表明该子词位于词尾,如we st</w>,二者意义截然不同
将语料库中所有单词拆分为单个字符,用所有单个字符建立最初的词典,并统计每个字符的频率,本阶段的 subword 的粒度是字符
挑出频次最高的符号对 ,比如说 t 和 h 组成的 th,将新字符加入词表,然后将语料中所有该字符对融合,即所有 t 和 h 都变为 th。新字符依然可以参与后续的 merge,有点类似哈夫曼树,BPE 实际上就是一种贪心算法
重复5操作,直到词表中单词数达到设定量V_size或下一个最高频数为 1
例子
获取语料库,这样一段话为例:“ FloydHub is the fastest way to build, train and deploy deep learning models. Build deep learning models in the cloud. Train deep learning models. ”
拆分,加后缀,统计词频:

词频 建立词表,统计字符频率,排序:

字符频率 以第一次迭代为例,将字符频率最高的
d和e替换为de,后面依次迭代:
第一次迭代 更新词表

更新词表
如果将词表大小设置为 10,最终的结果为:
优点
可以很有效地平衡词典大小和编码步骤数。
随着合并的次数增加,词表大小通常先增加后减小。迭代次数太小,大部分还是字母,几乎就是字符级别的分词;迭代次数多,又变成了word级别的分词。所以词表大小要取一个中间值。

BPE 一般适用在欧美语言拉丁语系中,因为欧美语言大多是字符形式,涉及前缀、后缀的单词比较多。而中文的汉字一般不用 BPE 进行编码,因为中文是字无法进行拆分。对中文的处理通常只有分词和分字两种。理论上分词效果更好,更好的区别语义。分字效率高、简洁,因为常用的字不过 5000 字,词表更加简短。
Byte-level BPE
Byte-level BPE使用原始字节作为基本 token,能够表示任意文本,避免OOV的情况。
可以与各种文本编码兼容,确保广泛的适用性
实现
调包
手撕
3.2 WordPiece
与BPE思想类似,WordPiece在开始时也是按照字符级别初始化一个词表。
最大的区别在于选择两个子词进行合并的规则: BPE 按频率,WordPiece 按能够使得 LM 概率最大的相邻子词加入词表
概率计算方式:
WordPiece倾向于融合那些,各自的子部分在词表中更少出现的组合。
例如:尽管("un", "##able")出现的次数很多,但是WordPiece不会选择融合,因为有很多un和很多able在其它地方出现,保持它们的独立能够更好地表征语意。相反,像("hu", "##gging")这样的组合更容易被融合。
和BPE的另一个区别在于推理阶段的分词方式。 WordPiece最后仅仅保存最终的词表,而BPE保留了融合的规则。WordPiece会根据首字母找到最长的subword进行分割,而BPE则是根据保留的规则进行分割。 举个例子,对于hugs,最长的subword是hug,因此WordPiece会将其分割为hug和s,而BPE则会将其分割为hu和gs。
实现
调包
手撕
3.3 Unigram
与BPE和WordPiece不同的是,Unigram的词表是从大到小变化的。 即先初始化一个大词表(可以用词频或者BPE初始化),根据评估准则不断丢弃词表,直到满足限定条件。Unigram 算法考虑了句子的不同分词可能,因而能够输出带概率的多个子词分段。 评判标准:Unigram loss 尝试删去一个 token,并计算对应的 unigram loss,删除使得 loss 增加最少的 token,因为它们可能是“最不被需要的”。 这个过程的计算量可能很大,所以我们可以一次移除p%的token,p是一个可以设置的超参,通常为10或20。 假设我们已经算得了词频:
并获得了所有substring
在Unigram模型中,我们假设每个token都是独立的,依据前面n个token推测第n+1个token的概率就是第n+1个token的概率。 概率则是由词频决定的,因此
其中, 是所有token的词频之和。 给定一个单词,我们可以计算其概率:
一个单词会被tokenize成概率最高的情况。
那么,"pug"会被分成"p"和"ug"或者"pu"和"g"。
loss计算
所有loss是:
我们需要计算移除每个token后对于loss的影响,然后移除使得loss增加最少的token。
实现
调包
手撕
3.4 SentencePiece
BPE,WordPiece,Unigram的缺点:
假设输入文本使用空格来分隔单词,如果不是则需要提前分词
为了解决这个问题:
SentencePiece将输入视为输入字节流,包括空格
然后使用 Byte-level BPE 或 unigram 算法来构建适当的词汇表
4 应用与实践:训练古汉语Tokenizer并与Qwen Tokenizer融合
4.1 为什么要词表拓展
词表拓展可以提高模型的泛化能力,使得模型能够处理更多的未知词汇
词表拓展可以提高模型的性能,使得模型能够更好地处理特定领域的文本
例如,Llama3的Tokenizer使用的是SentencePiece,词表大小为128256,且大部分是英文。如果用Llama3的Tokenizer处理古汉语文本,基本上都会退化成字节级别的分词,这样会导致丢失部分语义信息。
以《离骚》中的一句为例:
输出:
表现形式就是出现'�'。
如果我们需要训练一个古汉语专用的LLM(例如XunziALLM),可能需要对它的Tokenizer进行拓展。
4.2 古汉语词表拓展
4.2.1 准备语料
作为toy dataset,我们使用《离骚》全篇作为语料库,将其保存为corpus.txt。
4.2.2 训练Tokenizer
4.2.3 与Llama3 Tokenizer融合
4.2.4 性能测试
使用融合后的Tokenizer对《离骚》进行分词,输出为:
可以看到,'�'少了很多。
5 总结
Transformer的输入是什么?
Tokenize/分词的作用?
三种不同级别的Tokenizers及其优缺点
字符级别
word级别
subword级别
四种Subword-based Tokenizers(拆分方法)
BPE/BBPE
WordPiece
Unigram
SentencePiece(使用BBPE或者Unigram)
6 参考资料
https://cloud.tencent.com/developer/article/2317900
https://jinhanlei.github.io/posts/Transformers快速入门-二-用Tokenizer从零开始训练词表/
https://huggingface.co/learn/nlp-course/chapter6
https://github.com/QwenLM/Qwen/blob/main/tokenization_note_zh.md
Last updated