分词器文档
快速教程
并获得增强的文档体验
开始使用
快速入门
让我们快速了解一下 🤗 Tokenizers 库的特性。该库提供了当今最常用分词器的实现,既易于使用又速度飞快。
从零开始构建分词器
为了说明 🤗 Tokenizers 库的速度有多快,让我们在 wikitext-103 数据集(516M 文本)上训练一个新的分词器,只需几秒钟。首先,你需要下载该数据集并解压缩:
wget https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-103-raw-v1.zip unzip wikitext-103-raw-v1.zip
训练分词器
在本教程中,我们将构建和训练一个字节对编码(BPE)分词器。有关不同类型分词器的更多信息,请查看 🤗 Transformers 文档中的这份指南。在这里,训练分词器意味着它将通过以下方式学习合并规则:
- 从训练语料库中存在的所有字符作为初始词符开始。
- 识别最常见的词符对并将其合并为一个词符。
- 重复此过程,直到词汇表(例如,词符数量)达到我们想要的大小。
该库的主要 API 是 Tokenizer
类,以下是我们如何用 BPE 模型实例化一个:
from tokenizers import Tokenizer
from tokenizers.models import BPE
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
为了在 wikitext 文件上训练我们的分词器,我们需要实例化一个 [trainer]{.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
类方法从该文件重新加载你的分词器:
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]
🤗 Tokenizers 库的一个重要特性是它带有完整的对齐跟踪,这意味着你总是可以找到原始句子中对应于给定词符的部分。这些信息存储在我们的 Encoding
对象的 offsets
属性中。例如,假设我们想找回导致 "[UNK]"
词符出现的原因,它是列表中索引为 9 的词符,我们只需请求该索引处的偏移量:
print(output.offsets[9])
# (26, 27)
这些是原始句子中对应于表情符号的索引:
sentence = "Hello, y'all! How are you 😁 ?"
sentence[26:27]
# "😁"
后处理
我们可能希望分词器能自动添加特殊词符,比如 "[CLS]"
或 "[SEP]"
。为此,我们使用后处理器。TemplateProcessing
是最常用的,你只需指定处理单个句子和句子对的模板,以及特殊词符及其 ID。
当我们构建分词器时,我们将 "[CLS]"
和 "[SEP]"
放在特殊词符列表的第 1 和第 2 个位置,所以这应该是它们的 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
代表我们希望输入各部分的 type IDs
:它默认为 0(这就是为什么我们没有 $A:0
),在这里我们将其设置为 1,用于第二个句子的词符和最后一个 "[SEP]"
词符。
最后,我们指定我们使用的特殊词符及其在分词器词汇表中的 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]")
我们可以设置填充的 direction
(默认为右侧)或指定的 length
,如果我们想将每个样本填充到该特定长度(这里我们不设置它,以填充到最长文本的大小)。
output = tokenizer.encode_batch(["Hello, y'all!", "How are you 😁 ?"])
print(output[1].tokens)
# ["[CLS]", "How", "are", "you", "[UNK]", "?", "[SEP]", "[PAD]"]
在这种情况下,分词器生成的 attention mask
会考虑填充:
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