本章主要介绍不同的子词分法
一、前言
标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。
在 NLP 任务中,通常处理的数据是原始文本。
1 | Jim Henson was a puppeteer |
但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。
二、Word-based
最先想到的是基于词的(word-based)。

实现也非常简单,直接使用Python中的split()函数即可。
但每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。
如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。
这样导致的问题是:词表可能会非常的大,造成了空间的浪费。
三、Character-based
基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处:
- 词汇量要小得多。
- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建

这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义。
另一件要考虑的事情是,模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token),造成了大量的计算开销。
四、subword
子词分词结合上述两种方法,提出优化:
子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。
例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。

在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记。
五、Byte Pair Encoding
GPT-2和RoBERTa使用的Subword算法都是BPE。
BPE获得Subword的步骤如下:
1、准备足够大的训练语料,并确定期望的Subword词表大小
2、将单词拆分为成最小单元。比如英文中26个字母加上各种符号,这些作为初始词表
3、在语料上统计单词内相邻单元对的频数,选取频数最高的单元对合并成新的Subword单元
4、重复第3步直到达到第1步设定的Subword词表大小或下一个最高频数为1
举例来说:
假设有语料集经过统计后表示为{'low':5,'lower':2,'newest':6,'widest':3}
,其中数字代表的是对应单词在语料中的频数
1、拆分单词成最小单元,并初始化词表。这里,最小单元为字符,因而,可得到


需要注意的是,在将单词拆分成最小单元时,要在单词序列后加上””(具体实现上可以使用其它符号)来表示中止符。在子词解码时,中止符可以区分单词边界。
2、在语料上统计相邻单元的频数。这里,最高频连续子词对”e”和”s”出现了6+3=9次,将其合并成”es”,有:


由于语料中不存在’s’子词了,因此将其从词表中删除。同时加入新的子词’es’。一增一减,词表大小保持不变
3、继续统计相邻子词的频数。此时,最高频连续子词对”es”和”t”出现了6+3=9次, 将其合并成”est”,有:


4、接着,最高频连续子词对为”est”和”“,有:


5、继续上述迭代直到达到预设的Subword词表大小或下一个最高频的字节对出现频率为1
从上面的示例可以知道,每次合并后词表大小可能出现3种变化:
- +1,表明加入合并后的新子词,同时原来的2个子词还保留(2个字词分开出现在语料中)
- +0,表明加入合并后的新子词,同时原来的2个子词中一个保留,一个被消解(一个子词完全随着另一个子词的出现而紧跟着出现)
- -1,表明加入合并后的新子词,同时原来的2个子词都被消解(2个子词同时连续出现)
实际上,随着合并的次数增加,词表大小通常先增加后减小。
在得到Subword词表后,针对每一个单词,可以采用如下的方式来进行编码:
1、将词典中的所有子词按照长度由大到小进行排序
2、对于单词w,依次遍历排好序的词典。查看当前子词是否是该单词的子字符串,如果是,则输出当前子词,并对剩余单词字符串继续匹配
3、如果遍历完字典后,仍然有子字符串没有匹配,则将剩余字符串替换为特殊符号输出,如”
4、单词的表示即为上述所有输出子词
解码过程比较简单,如果相邻子词间没有中止符,则将两子词直接拼接,否则两子词之间添加分隔符
六、WordPiece
Bert在分词的时候使用的是WordPiece算法。
与BPE算法类似,WordPiece算法也是每次从词表中选出两个子词合并成新的子词。与BPE的最大区别在于,如何选择两个子词进行合并:BPE选择频数最高的相邻子词合并,而WordPiece选择能够提升语言模型概率最大的相邻子词加入词表。
WordPiece在一个基础的char级别的vocabulary上训练一个语言模型,不断的迭代,每次选择让句子的似然概率更大的两个词进行组合。