08月25, 2020

分词器tokenizers介绍

Byte Pair Encoding (BPE) 字节对编码

概述

字节对编码最早是在信号压缩领域提出的,后来被应用于分词任务中。在信号压缩领域中BPE过程可视化如下:

1_x1Y_n3sXGygUPSdfXTm9pQ

接下来重点介绍将BPE应用于分词任务的流程: 实现流程

  • 根据语料库建立一个词典,词典中仅包含单个字符,如英文中就是a-z
  • 统计语料库中出现次数最多的字符对(词典中两项的组合),然后将字符对加入到词典中
  • 重复步骤2直到到达规定的步骤数目或者词典尺寸缩小到了指定的值。

分类

主要包含的 Tokenizer 有:

  • ByteLevelBPETokenizer,字节级别的 BPE (Byte Pair Encoding) 训练和 tokenize,facebook 那几个预训练模型比如 Roberta 就用的这个,应对多语言比较方便;

  • CharBPETokenizer,比字节高一个级别,字级别的 BPE,其实就是最经典 Sennrich 教授用的那个,subword-nmt;

  • SentencePieceTokenizer,现在越来越搞不清这些命名之间的关系了,不过看代码应该是在 BPE 过程之前加了一个 pretokenization 过程,先对文本进行预处理再 BPE;

  • BertWordPieceTokenizer,应该就是基于 unigram language model 那篇论文来做的。

BPE的优点

可以很有效地平衡词典尺寸和编码步骤数(将句子编码所需要的token数量) BPE存在的缺点:

对于同一个句子, 例如Hello world,如图所示,可能会有不同的Subword序列。不同的Subword序列会产生完全不同的id序列表示,这种歧义可能在解码阶段无法解决。在翻译任务中,不同的id序列可能翻译出不同的句子,这显然是错误的。
在训练任务中,如果能对不同的Subword进行训练的话,将增加模型的健壮性,能够容忍更多的噪声,而BPE的贪心算法无法对随机分布进行学习。

Huggingface tokenizers库的介绍和使用

tokenizers是集合了当前最常用的分词器集合,效率和易用性也是其关注的范畴。

使用示例:

# Tokenizers provides ultra-fast implementations of most current tokenizers:
>>> from tokenizers import (ByteLevelBPETokenizer,
                            CharBPETokenizer,
                            SentencePieceBPETokenizer,
                            BertWordPieceTokenizer)
# Ultra-fast => they can encode 1GB of text in ~20sec on a standard server's CPU
# Tokenizers can be easily instantiated from standard files
>>> tokenizer = BertWordPieceTokenizer("bert-base-uncased-vocab.txt", lowercase=True)

# Tokenizers provide exhaustive outputs: tokens, mapping to original string, attention/special token masks.
# They also handle model's max input lengths as well as padding (to directly encode in padded batches)
>>> output = tokenizer.encode("Hello, y'all! How are you ?")

>>> print(output.ids, output.tokens, output.offsets)
[101, 7592, 1010, 1061, 1005, 2035, 999, 2129, 2024, 2017, 100, 1029, 102]
['[CLS]', 'hello', ',', 'y', "'", 'all', '!', 'how', 'are', 'you', '[UNK]', '?', '[SEP]']
[(0, 0), (0, 5), (5, 6), (7, 8), (8, 9), (9, 12), (12, 13), (14, 17), (18, 21), (22, 25), (26, 27),(28, 29), (0, 0)]
# Here is an example using the offsets mapping to retrieve the string corresponding to the 10th token:
>>> output.original_str[output.offsets[10]]

自己训练分词器

# You can also train a BPE/Byte-levelBPE/WordPiece vocabulary on your own files
>>> tokenizer = ByteLevelBPETokenizer()
>>> tokenizer.train(["wiki.test.raw"], vocab_size=20000)
[00:00:00] Tokenize words                 ████████████████████████████████████████   20993/20993
[00:00:00] Count pairs                    ████████████████████████████████████████   20993/20993
[00:00:03] Compute merges      

>>> tokenizer.save_model(".")
# saved vocab.json merges.txt

参考

本文链接:http://57km.cc/post/what and how to use tokenizers package.html

-- EOF --

Comments