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. 初始化
- 初始化语料:将每个单词拆分为字符序列,添加词尾标记 
</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"
- 初始词汇表:
 
{ "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 o 和 o 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 o → lo。
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 w → low。
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. 合并规则:
lo→lolow→lowlow</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:
lo→lo→lowest</w>。 - 规则 2:
low→low→lowest</w>。 - 规则 3:无 
low</w>,停止合并。 
最终得到输入lowest的BPE分词结果:(low e s t </w>)。
KAQ:预训练和推理过程的Tokenization的不同?
上述就体现了,预训练是得到vocab和合并规则的过程,推理过程是根据vocab和合并规则进行分词的过程。
其他常用的分词算法
WordPiece & Unigram
@ tokenization 为什么需要合并规则
目的是将频繁出现的字符或子词组合成更大的单元(token),以优化词汇表大小和表示效率。