快速浏览
让我们快速了解一下 🤗 分词器库的功能。该库提供了当今最常用的分词器,易于使用且速度极快。
从头开始构建分词器
为了说明 🤗 分词器库的速度,让我们在几秒钟内使用 wikitext-103(5.16 亿文本)训练一个新的分词器。首先,您需要下载此数据集并使用以下命令解压缩它:
wget https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-103-raw-v1.zip unzip wikitext-103-raw-v1.zip
训练分词器
在本教程中,我们将构建并训练一个字节对编码 (BPE) 分词器。有关不同类型分词器的更多信息,请查看 🤗 Transformers 文档中的 指南。在这里,训练分词器意味着它将通过以下方式学习合并规则:
- 从训练语料库中存在的所有字符作为标记开始。
- 识别最常见的标记对并将其合并成一个标记。
- 重复此过程,直到词汇表(例如,标记数量)达到我们想要的规模。
库的主要 API 是 class
Tokenizer
,以下是我们如何使用 BPE 模型实例化一个分词器:
from tokenizers import Tokenizer
from tokenizers.models import BPE
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
为了在我们训练的 wikitext 文件上训练分词器,我们需要实例化一个 [训练器]{.title-ref},在这种情况下是一个 BpeTrainer
from tokenizers.trainers import BpeTrainer
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
我们可以设置训练参数,如 vocab_size
或 min_frequency
(这里保留默认值 30,000 和 0),但最重要的部分是提供我们计划稍后使用的 special_tokens
(它们在训练期间根本没有使用),以便它们被插入词汇表中。
您编写特殊标记列表的顺序很重要:这里 "[UNK]"
将获得 ID 0,"[CLS]"
将获得 ID 1,依此类推。
我们现在可以训练分词器,但它不是最佳的。没有预分词器来将我们的输入分成单词,我们可能会得到跨越多个单词的标记:例如,由于这两个词经常出现,我们可能会得到 "it is"
标记。使用预分词器将确保没有标记大于预分词器返回的单词。在这里,我们要训练一个子词 BPE 分词器,我们将使用最简单的预分词器,通过空格分隔。
from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()
现在,我们可以使用 Tokenizer.train
方法调用任何我们想使用的文件列表:
files = [f"data/wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]
tokenizer.train(files, trainer)
这只需要几秒钟就可以在完整的 wikitext 数据集上训练我们的分词器!为了将分词器保存到一个包含其所有配置和词汇表的单个文件中,只需使用 Tokenizer.save
方法:
tokenizer.save("data/tokenizer-wiki.json")
然后可以使用 Tokenizer.from_file
classmethod
从该文件加载分词器:
tokenizer = Tokenizer.from_file("data/tokenizer-wiki.json")
使用分词器
现在我们已经训练了一个分词器,可以使用 Tokenizer.encode
方法在任何我们想要的文本上使用它:
output = tokenizer.encode("Hello, y'all! How are you 😁 ?")
这将分词器的完整管道应用于文本,返回一个 Encoding
对象。要了解有关此管道的更多信息以及如何应用(或自定义)其部分,请查看 此页面。
然后,此 Encoding
对象具有深度学习模型(或其他)所需的所有属性。tokens
属性包含文本在标记中的分段:
print(output.tokens)
# ["Hello", ",", "y", "'", "all", "!", "How", "are", "you", "[UNK]", "?"]
类似地,ids
属性将包含这些标记在分词器词汇表中的索引:
print(output.ids)
# [27253, 16, 93, 11, 5097, 5, 7961, 5112, 6218, 0, 35]
🤗 分词器库的一个重要功能是它带有完整的对齐跟踪,这意味着您始终可以获得对应于给定标记的原始句子部分。这些存储在 Encoding
对象的 offsets
属性中。例如,假设我们要查找导致 "[UNK]"
标记出现的原因,该标记是列表中的第 9 个标记,我们可以直接询问该索引处的偏移量:
print(output.offsets[9])
# (26, 27)
这些是对应于原始句子中表情符号的索引:
sentence = "Hello, y'all! How are you 😁 ?"
sentence[26:27]
# "😁"
后处理
我们可能希望我们的分词器自动添加特殊标记,例如"[CLS]"
或"[SEP]"
。 为此,我们使用后处理器。 TemplateProcessing
是最常用的,您只需要指定对单个句子和句子对进行处理的模板,以及特殊标记及其 ID。
当我们构建分词器时,我们在特殊标记列表的第 1 位和第 2 位设置了"[CLS]"
和"[SEP]"
,因此这应该是它们的 ID。 为了仔细检查,我们可以使用Tokenizer.token_to_id
方法
tokenizer.token_to_id("[SEP]")
# 2
以下是如何设置后处理以提供传统的 BERT 输入
from tokenizers.processors import TemplateProcessing
tokenizer.post_processor = TemplateProcessing(
single="[CLS] $A [SEP]",
pair="[CLS] $A [SEP] $B:1 [SEP]:1",
special_tokens=[
("[CLS]", tokenizer.token_to_id("[CLS]")),
("[SEP]", tokenizer.token_to_id("[SEP]")),
],
)
让我们更详细地了解这段代码。 首先,我们指定单个句子的模板:这些句子应具有"[CLS] $A [SEP]"
形式,其中$A
代表我们的句子。
然后,我们指定句子对的模板,其形式应为"[CLS] $A [SEP] $B [SEP]"
,其中$A
代表第一个句子,$B
代表第二个句子。 在模板中添加的:1
代表我们希望为输入的每个部分使用的类型 ID
:它默认为所有内容为 0(这就是为什么我们没有$A:0
),并且在这里我们将其设置为第二个句子的标记和最后一个"[SEP]"
标记为 1。
最后,我们指定我们在分词器词汇表中使用的特殊标记及其 ID。
要检查是否正常工作,让我们尝试对与之前相同的句子进行编码
output = tokenizer.encode("Hello, y'all! How are you 😁 ?")
print(output.tokens)
# ["[CLS]", "Hello", ",", "y", "'", "all", "!", "How", "are", "you", "[UNK]", "?", "[SEP]"]
要检查句子对的结果,我们只需将两个句子传递给Tokenizer.encode
output = tokenizer.encode("Hello, y'all!", "How are you 😁 ?")
print(output.tokens)
# ["[CLS]", "Hello", ",", "y", "'", "all", "!", "[SEP]", "How", "are", "you", "[UNK]", "?", "[SEP]"]
然后,您可以使用以下方法检查分配给每个标记的类型 ID 是否正确
print(output.type_ids)
# [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
如果您使用Tokenizer.save
保存分词器,则后处理器也会一起保存。
批量编码多个句子
为了获得🤗 Tokenizers 库的全部速度,最好使用Tokenizer.encode_batch
方法按批次处理文本
output = tokenizer.encode_batch(["Hello, y'all!", "How are you 😁 ?"])
然后输出是一个Encoding
对象的列表,与我们之前看到的列表类似。 您可以将任意数量的文本一起处理,只要它适合内存即可。
要处理一批句子对,将两个列表传递给Tokenizer.encode_batch
方法:句子 A 列表和句子 B 列表
output = tokenizer.encode_batch(
[["Hello, y'all!", "How are you 😁 ?"], ["Hello to you too!", "I'm fine, thank you!"]]
)
当编码多个句子时,您可以使用Tokenizer.enable_padding
自动将输出填充到最长的句子,并使用pad_token
及其 ID(我们可以像之前一样使用Tokenizer.token_to_id
仔细检查填充标记的 id)
tokenizer.enable_padding(pad_id=3, pad_token="[PAD]")
我们可以设置填充的方向
(默认为右侧)或给定的长度
,如果我们希望将每个样本填充到该特定数字(这里我们不设置它,以填充到最长文本的大小)。
output = tokenizer.encode_batch(["Hello, y'all!", "How are you 😁 ?"])
print(output[1].tokens)
# ["[CLS]", "How", "are", "you", "[UNK]", "?", "[SEP]", "[PAD]"]
在这种情况下,分词器生成的注意力掩码
会考虑填充
print(output[1].attention_mask)
# [1, 1, 1, 1, 1, 1, 1, 0]
预训练
使用预训练的分词器
您可以从 Hugging Face Hub 加载任何分词器,只要存储库中存在tokenizer.json
文件。
from tokenizers import Tokenizer
tokenizer = Tokenizer.from_pretrained("bert-base-uncased")
从旧词汇文件导入预训练的分词器
只要您有它的词汇文件,您也可以直接导入预训练的分词器。 例如,以下是如何导入经典的预训练 BERT 分词器
from tokenizers import BertWordPieceTokenizer
tokenizer = BertWordPieceTokenizer("bert-base-uncased-vocab.txt", lowercase=True)
只要您下载了文件bert-base-uncased-vocab.txt
wget https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased-vocab.txt