Normalization and pre-tokenization

下是tokenization pipline的流程:

图片描述
分词的pipline

在根据其模型将文本分割为子词之前,分词器执行两个步骤:归一化 & 预分词。

Normalization

Normalization 进行常规清理工作,例如去除不必要的空白字符、转换为小写以及/或去除重音符号。一个 tokenizer 对象含有底层的 normalizer 属性,这个属性有一个方法 normalize_str() 的作用就是进行归一化的。

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))

'hello how are u?'  # 针对 bert-base-uncased 的归一化输出

不同的预训练模型的归一化方法有差别

预分词

相同的,一个 tokenizer 对象含有底层的 pre_tokenizer 属性,这个属性有一个方法 pre_tokenize_str() 的作用就是进行预分词的:

tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")

[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))]

看看T5 模型的分词器:

tokenizer = AutoTokenizer.from_pretrained("t5-small")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")

[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))]

这个分词器是包含偏移映射的。同样的,不同预训练模型的预分词方法有差别

上述 T5 模型使用的分词算法是 SentencePiece。它可以与其他分词算法一起使用。

Byte-Pair Encoding tokenization

训练:

  • 首先将语料中的所有单词拆分为字符序列,添加词尾的标记。
  • 然后统计相邻字符对儿的频数。
  • 接下来进行迭代。在每一次迭代中找到频数最高的字符对。然后选择合并,即将这个频数最高的字符对儿合并成一个新 token。接着更新语料,更新词汇表,更新合并规则。
  • 如此循环迭代若干次便得到了最终结果。

应用:

应用上述训练好的分词器的步骤。第一步,将输入的单词拆分为字符。第二步应用上述的合并规则得到数量更少的 token 集合。这个 token 就是最终的结果。

一个实例。通过一个迭代合并 3 次的实例展示 BPE 算法过程。假设我的预料是 “low low lower lowest”。训练分词器的过程:

1. 初始化

  1. 初始化语料:将每个单词拆分为字符序列,添加词尾标记 </w> 以区分词边界。
l o w </w>  # "low"
l o w </w>  # "low"
l o w e r </w>  # "lower"
l o w e s t </w>  # "lowest"
  1. 初始词汇表:
{ "l", "o", "w", "e", "r", "s", "t", "</w>" }

2. 统计相邻字符对频数

l o # 出现 3 次(两个“low”和“lower”)。
o w # 出现 3 次。
w </w> # 出现 2 次(两个“low”)。
w e # 出现 2 次(“lower”和“lowest”)。
e r # 出现 1 次(“lower”)。
e s # 出现 1 次(“lowest”)。
s t # 出现 1 次(“lowest”)。
r </w> # 出现 1 次(“lower”)。
t </w> # 出现 1 次(“lowest”)。

3.第一次迭代

选择最高频字符对:l oo w 都出现 3 次,随机选择 l o 合并。

合并:将 l o 合并为新 token lo更新语料

lo w </w>  # "low"
lo w </w>  # "low"
lo w e r </w>  # "lower"
lo w e s t </w>  # "lowest"

更新词汇表

{ "l", "o", "w", "e", "r", "s", "t", "</w>", "lo" }

记录合并规则l olo

4. 第二次迭代

重新统计字符对频率:

lo w  # 出现 4 次。
w </w>  # 出现 2 次。
w e  # 出现 2 次。
e r  # 出现 1 次。
e s  # 出现 1 次。
s t  # 出现 1 次。
r </w>  # 出现 1 次。
t </w>  # 出现 1 次。

选择最高频字符对:lo w(4 次)。合并:将 lo w 合并为 low

更新语料

low </w>  # "low"
low </w>  # "low"
low e r </w>  # "lower"
low e s t </w>  # "lowest"

更新词汇表:添加 low

{ "l", "o", "w", "e", "r", "s", "t", "</w>", "lo", "low" }

记录合并规则lo wlow

5. 第三次迭代

重新统计字符对频率:

low </w>  # 出现 2 次。
low e  # 出现 2 次。
e r  # 出现 1 次。
e s  # 出现 1 次。
s t  # 出现 1 次。
r </w>  # 出现 1 次。
t </w>  # 出现 1 次。

选择最高频字符对:假设选择 low </w>(2 次)。 合并:将 low </w> 合并为 low</w>

更新语料

low</w>  # "low"
low</w>  # "low"
low e r </w>  # "lower"
low e s t </w>  # "lowest"

更新词汇表:添加 low</w>

{ "l", "o", "w", "e", "r", "s", "t", "</w>", "lo", "low", "low</w>" }

记录合并规则low </w>low</w>

至此3次训练BPE分词器完成。

6. 训练结果

学习到的内容包括:合并规则 & 词汇表(vocab.txt)。

1. 合并规则:

  1. l olo
  2. lo wlow
  3. low </w>low</w>

2. 词汇表(vocab.txt):

包含基础字符和合并后的字符,这是 BPE 的标准:{ "l", "o", "w", "e", "r", "s", "t", "</w>", "lo", "low", "low</w>" }

应用训练好的 BPE 分词器(推理过程)

假如输入文本:lowest, 首先拆为字符:l o w e s t </w>。 然后应用合并规则:

  • 规则 1:l ololo w e s t </w>
  • 规则 2:lo wlowlow e s t </w>
  • 规则 3:无 low </w>,停止合并。

最终得到输入lowest的BPE分词结果:(low e s t </w>)。

KAQ:预训练和推理过程的Tokenization的不同?

上述就体现了,预训练是得到vocab和合并规则的过程,推理过程是根据vocab和合并规则进行分词的过程。

其他常用的分词算法

WordPiece & Unigram

@ tokenization 为什么需要合并规则

目的是将频繁出现的字符或子词组合成更大的单元(token),以优化词汇表大小和表示效率。