目录
子词分词器
在学习不同的模型如GPT-1、Bert时可以发现它们使用了不同的子词分词器(subword tokenizer) :BPE(Bype Pair Encoding)和WordPiece。那这两个子词分词器有什么异同呢?
在具体介绍BPE和WordPiece之前,先来看下为什么要用子词分词器。我们知道一个句子是由一些词语组成的,对于英语,将一个句子分成词语最简单的方法是按照空格来划分,比如“I don't like rainy days; how about you?",将变成["I", "don't", "like", "rainy", "days;", "how", "about", "you?"]。我们注意到"days;"和"you?"这两个token里包含了标点符号,就会想到把标点符号给拆分出来,因为如果每一个单词加上标点符号都看做不同的词,最终词典会很大,模型要学习的词也非常多。拆分标点后得到["I", "don", "'", "t", "like", "rainy", "days", ";", "how", "about", "you", "?"], 这个时候我们很可能就注意到原来句子里的"don't"现在的形式有点奇怪,我们知道它表示"do not", 那切分成["Do", "n't"]是不是更好呢?这就是制定一个规则来说明如何切分一些词语了,spaCy 和 Moses就是两个流行的基于规则的分词器。
但是使用了基于规则的分词器后,生成的词表的大小还是太大,这会使模型输入和输出层的嵌入矩阵非常大,使模型不仅内存占用大,时间复杂度也高。一般transformer模型的词表大小都不会超过50000(尤其是只有一个语言时)。那不直接使用普通的分词方法,我们只用字符(character)呢,毕竟英语只有26个字母,加上其他符号后这个量也很小,内存占用和时间复杂度都急剧降低。但是这会使模型很难学习到有意义的输入表示,比如学习字母"l"的上下文表示肯定比"like"的上下文表示要难的多,所以使用字符分词效果不好。于是就有了介于词级别(word-level)和字符级别(character-level)之间的子词级别(subword-level)的子词分词器。
子词分词器的基本原则是:频繁被使用的词不应该被切分为更细的子词,而罕见的词语应该被切分为更有意义的子词。比如"annoyingly"可能会被当做一个罕见词,它可以被分成"annoying"和"ly"两个子词。
子词分词器可以控制词表的大小的同时学习到有意义的上下文表示,并且它允许模型去处理训练时没有见过的词(out-of-vocabulary (OOV)问题),因为它可以让模型把没见过的词切分成已知的词。
铺垫了这么长的为什么要使用子词分词器,终于到了介绍BPE和WordPeice如何实现的时候了。注意子词分词器训练的语料与使用它的模型用的语料是相同的。
BPE(Bype Pair Encoding)
BPE是2015年被Sennrich等人在《Neural Machine Translation of Rare Words with Subword Units》中提出的。
-
BPE首先使用一个普通的分词器将语料切分成词,分词器可以选择前面提到的基于空格或基于规则的分词器。
-
分完词后就得到了一个包含所有唯一单词的词表,并统计词表中每个单词出现的频次。
-
创建一个包含了步骤2中的词表中所有符号的基础词汇表。
-
从基础词汇表中选择两个符号根据合并规则形成一个新的符号,并更新基础词表。(BPE根据新符号出现的频率来合并的)
-
重复第4步,直到基础词表的大小达到想要的大小。(词表的大小是一个预先定义的超参数)
假设我们经过第2步后,我们得到了如下词和他们的出现频率。
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
在第3步中我们就可以得到基础词汇表:["b", "g", "h", "n", "p", "s", "u"]
我们也把第2步中统计好的每个单词表示成切分成基础词汇表中的每个符号的形式:
("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)
BPE会统计每个可能的符号对出现的次数,然后选择出现次数最多的加入到基础词表中,在例子中,"hu"出现了15次(在hug中出现了10次,在hugs中出现了5次),而出现次数最多的是"ug"为20次(10+5+5),所以将"ug"加入到基础词表得到["b", "g", "h", "n", "p", "s", "u","ug"],而单词频次表变成:
("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)
我们接着重复将"un"加入到基础词表,再一次重复会将hug加入基础词表,此时,基础词表为["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]。
("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)
此时我们已经进行了3次合并,假设我们预先定义了只进行3次merge,那么BPE编码就结束了。学习到的合并规则将会应用在新的单词,比如"bug"将被表示为["b", "ug"], "mug"将表示为 ["<unk>", "ug"]
, 因为"m"没有在我们的基础词表中出现,所以用"<unk>"替换了
。当然实际应用时单个字母不会被表示为"<unk>",因为基础词表中肯定包含了所有的字母,但是一些特殊表情训练时就很可能没有见过了。
在GPT-1的论文中提到它使用了40000次merge, 因为它的基础字符数是478个,所以它最终的词表大小就为40478。
Byte-level BPE
BPE的算法最开始的基础词表也可能会很大,比如如果将所有的unicode 字符都放入基础词表,一开始的词表大小就有十几万了。 在GPT-2论文中使用了bytes作为基础词表,因此它就可以表示任意单词,并且其基础词表只有256的大小,再加上一个特殊的结束符号,使用50000大小的merge,最终GPT-2的词表大小为50257.(GPT-2在merge的时候还有一些特殊规则,比如不将不同类型的bytes合并等)
WordPiece
WordPiece是Schuster等人2012年在《Japanese and korean voice search》一文中提出,在Bert爆火之后很流行。
-
首先使用一个普通的分词器将语料切分成词,分词器可以选择前面提到的基于空格或基于规则的分词器。
-
分完词后就得到了一个包含所有唯一单词的词表。
-
创建一个包含了步骤2中的词表中所有符号的基础词汇表。
-
从所有可能的组合中选择加入词表后能最大程度地增加语言模型在训练数据上的似然概率的组合
-
重复第4步,直到基础词表的大小达到想要的大小。(词表的大小是一个预先定义的超参数)
WordPiece和BPE主要是将子词加入到基础词表的方式不一样,WordPiece将一个子词加入到基础词典时也会考虑它会损失些什么信息。
总结
BPE和WordPiece都是常用的子词分词器,子词分词器可以控制词表的大小的同时学习到有意义的上下文表示,所以现在的深度学习模型一般会使用子词分词器。
相同点:
-
都要指定一个词典的大小
-
都包含更细粒度的子词,所以可以控制词典的大小的同时,缓解OOV问题
不同点:
-
将词加入基础词典的选择策略不一样,BPE是选择频率最高的字符对,WordPiece是选择使训练数据的似然概率最大的字符对。
参考资料
1. Sennrich, Rico, Barry Haddow, and Alexandra Birch. 2016. “Neural Machine Translation of Rare Words with Subword Units.” In Proceedings of the 54th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers). doi:10.18653/v1/p16-1162.
2. Schuster, Mike, and Kaisuke Nakajima. "Japanese and korean voice search." In 2012 IEEE international conference on acoustics, speech and signal processing (ICASSP), pp. 5149-5152. IEEE, 2012.
4. Summary of the tokenizers BPE 的例子来源于此文。
5. https://towardsdatascience.com/a-comprehensive-guide-to-subword-tokenisers-4bbd3bad9a7c