tokenization 算法介绍——千里之行,始于足下

发布于:2022-11-16 ⋅ 阅读:(2346) ⋅ 点赞:(2)

NLP(自然语言处理),对于它来说,如何有效地编码一段文本,是它首先要考虑的问题。而在编码文本之前,要先把它切割成小块,这些小块叫做 tokens,这个过程叫做 tokenization。所谓“千里之行,始于足下”,一个合适的 tokenization 非常重要,因为它是神经网络的输入,直接影响了NLP任务的效果。

单词、字符与子单词

第一个想法,可以以单词为单位进行切割,每个单词都是一个 token,这个想法叫做 Word Tokenization;第二个想法,可以以字符为单位进行切割,每个字母都是一个 token,这个想法叫做 Character Tokenization

举个例子,要对 “I love you.” 进行 tokenization:

  • Word Tokenization gives: [‘I’, ‘love’, ‘you’, ‘.’]
  • Character Tokenization gives:[‘i’, ‘l’, ‘o’, ‘v’, ‘e’, ‘y’, ‘o’ ‘u’, ‘.’]

Word Tokenization 的问题在于,它无法处理 Out Of Vocabulary (OOV) words。 拿过一段新的文本,如果其中包含词库中未出现的词,只能把它标记为 Unknown (UNK),这样无疑会影响神经网络的准确度。而 Character Tokenization 虽然解决了 OOV 问题,但是它过于冗长。把一些字母组合成一个有意义的单词已经很费功夫了,更遑论把单词组成有意义的句子。

于是人们想,能不能想个中间的法子?——这就是 Subword Tokenization。它把文本切割成一些“子单词”。举个例子,unhappy 可以切割成 [un, happy];disable 可以切割成 [dis, able] 。这样有什么好处呢?在训练 word embedding (更准确地说,token embedding)的时候,可以更容易捕捉单词前缀、后缀的含义,避免了重复捕捉信息。
更重要的一点,它在压缩数据的同时,减少了 UNK 的使用。

我们要按照什么标准切割子单词呢?下面介绍NLP领域常用的 Subword Tokenization 算法。

BPE

Hugging Face: Byte-Pair Encoding tokenization

Byte-Pair Encoding (BPE) 被广泛地用于 Transformer 架构中,如GPT、GPT-2、RoBERTa 等模型。

训练阶段
从训练文本构成的字符集出发,在训练的每一步,寻找出现频率最高的 token pair (by “pair,” here we mean two consecutive tokens in a word),将它们合并;不断进行,直到 token的数量达到预设的值。

tokenization 阶段
训练过程中,那些被合并的 token pair 被记录下来,用来对新来的文本进行 tokenization。

举个栗子:

假设训练语料库如下,单词后的数字表示出现的次数

Vocabulary:["hug", "pug", "pun", "bun", "hugs"]
Corpus: ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

把每个单词都分解成字母,形成一个Base Vocabulary:

Base Vocabulary: ["b", "g", "h", "n", "p", "s", "u"]
Corpus: ("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)

寻找出现频率最高的 token pair,发现是(“u”, “g”),它出现了20次。于是进行合并:(“u”, “g”) -> “ug”

Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)

重复上述步骤,这次(“u”, “n”) 的频率最高:

Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5)

两次合并后,这次(“h”, “ug”) 频率最高:

Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)

假设算法到这里结束了,看一下学习到的“合并规则”:

("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"

当有新的文本需要进行 tokenization 时,先将它分解成单个字母的序列,然后对照着这个合并规则进行合并。
比如单词 bug,分解成 [b, u, g];比对合并规则,发现 u, g 可以合并,且 b, ug 不能合并;因此 bug 的 tokenization 结果是 [b, ug].
再比如单词 mug,它的 tokenization 结果是 [UNK, ug],因为 m 不在 Base Vocabulary 之中。
再比如单词 thug,它的结果是 [UNK, hug]

WordPiece

Hugging Face: WordPiece tokenization

WordPiece 是 BERT 网络的 tokenization 算法,它和 BPE 很像,但有些许区别。

训练阶段
和 BPE 一样,WordPiece 的训练过程也是一个逐渐合并 token pair 的过程。但它用的合并准则有一些不一样。

score= (freq_of_pair) / (freq_of_first_element × freq_of_second_element)

BPE 只关心 token pair 的出现频率,即 freq_of_pair;WordPiece 还考虑了每个 token 的出现频率。

For instance, it won’t necessarily merge (“un”, “##able”) even if that pair occurs very frequently in the vocabulary, because the two pairs “un” and “##able” will likely each appear in a lot of other words and have a high frequency.

这里举了一个例子,即使 unable 出现频率很高,但如果 un 和 able 单个 token 的出现频率都很高,也不会合并它们,这也符合我们的预期。

训练阶段的其他部分与 BPE 相同。

tokenization 阶段
与 BPE 不同,WordPiece 不会保存训练阶段学到的“合并规则”,它只会保存最终的 Vocabulary。对于一个单词,从第一个字母开始,它会寻找 Vocabulary 中最长的子单词,进行切割;直到剩下的字母组成的 token 存在于 Vocabulary 中。

假设最终的 Vocabulary 如下(以 ## 开头的 token 表示该 token 不是单词的开头):

Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]

单词 hugs 的 tokenization 结果是 [“hug”, “##s”]
单词 bugs 的 tokenization 结果是 [“b”, "##u, “##gs”]
如果在某一步无法在 Vocabulary 中找到子单词,那么整个单词会被标记为 UNK,这一点和 BPE 不同,后者只会将不在 Vocabulary 中的 token 标记为 UNK;
比如 mug 就会被标记为 UNK,因为无法找到以 m 开头的 token。

Unigram

Unigram 是一种用于 AlBERT, T5, mBART 等模型的 tokenization 算法。它的思路和 BPE、WordPiece完全不同。

Hugging Face: Unigram tokenization

细节不再展开了,建议读 Hugging Face 的Tutorial。

总结对比

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到