分词器文档

快速教程

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

快速入门

让我们快速了解一下 🤗 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 模型实例化一个:

Python
Rust
Node
from tokenizers import Tokenizer
from tokenizers.models import BPE
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))

为了在 wikitext 文件上训练我们的分词器,我们需要实例化一个 [trainer]{.title-ref},在这种情况下是 BpeTrainer

Python
Rust
Node
from tokenizers.trainers import BpeTrainer
trainer = BpeTrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])

我们可以设置训练参数,如 vocab_sizemin_frequency(这里保留它们的默认值 30,000 和 0),但最重要的部分是提供我们计划稍后使用的 special_tokens(它们在训练期间根本不使用),以便将它们插入到词汇表中。

你写入特殊词符列表的顺序很重要:在这里 "[UNK]" 将获得 ID 0,"[CLS]" 将获得 ID 1,依此类推。

我们现在就可以训练我们的分词器,但它还不是最优的。如果没有一个预分词器来将我们的输入分割成单词,我们可能会得到跨越多个单词的词符:例如,我们可能会得到一个 "it is" 词符,因为这两个词经常相邻出现。使用预分词器将确保没有词符比预分词器返回的单词更大。在这里,我们想要训练一个子词 BPE 分词器,我们将使用最简单的预分词器,即按空格分割。

Python
Rust
Node
from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()

现在,我们可以用任何我们想使用的文件列表来调用 Tokenizer.train 方法:

Python
Rust
Node
files = [f"data/wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]
tokenizer.train(files, trainer)

在完整的 wikitext 数据集上训练我们的分词器应该只需要几秒钟!要将分词器保存在一个包含其所有配置和词汇表的文件中,只需使用 Tokenizer.save 方法:

Python
Rust
Node
tokenizer.save("data/tokenizer-wiki.json")

你可以使用 Tokenizer.from_file 类方法从该文件重新加载你的分词器:

Python
Rust
Node
tokenizer = Tokenizer.from_file("data/tokenizer-wiki.json")

使用分词器

现在我们已经训练好了一个分词器,我们可以使用 Tokenizer.encode 方法在任何我们想要的文本上使用它:

Python
Rust
Node
output = tokenizer.encode("Hello, y'all! How are you 😁 ?")

这将分词器的完整流水线应用于文本,返回一个 Encoding 对象。要了解更多关于这个流水线的信息,以及如何应用(或自定义)其中的部分,请查看此页面

这个 Encoding 对象包含了你的深度学习模型(或其他模型)所需的所有属性。tokens 属性包含文本分割成的词符:

Python
Rust
Node
print(output.tokens)
# ["Hello", ",", "y", "'", "all", "!", "How", "are", "you", "[UNK]", "?"]

同样,ids 属性将包含这些词符在分词器词汇表中的索引:

Python
Rust
Node
print(output.ids)
# [27253, 16, 93, 11, 5097, 5, 7961, 5112, 6218, 0, 35]

🤗 Tokenizers 库的一个重要特性是它带有完整的对齐跟踪,这意味着你总是可以找到原始句子中对应于给定词符的部分。这些信息存储在我们的 Encoding 对象的 offsets 属性中。例如,假设我们想找回导致 "[UNK]" 词符出现的原因,它是列表中索引为 9 的词符,我们只需请求该索引处的偏移量:

Python
Rust
Node
print(output.offsets[9])
# (26, 27)

这些是原始句子中对应于表情符号的索引:

Python
Rust
Node
sentence = "Hello, y'all! How are you 😁 ?"
sentence[26:27]
# "😁"

后处理

我们可能希望分词器能自动添加特殊词符,比如 "[CLS]""[SEP]"。为此,我们使用后处理器。TemplateProcessing 是最常用的,你只需指定处理单个句子和句子对的模板,以及特殊词符及其 ID。

当我们构建分词器时,我们将 "[CLS]""[SEP]" 放在特殊词符列表的第 1 和第 2 个位置,所以这应该是它们的 ID。为了仔细检查,我们可以使用 Tokenizer.token_to_id 方法:

Python
Rust
Node
tokenizer.token_to_id("[SEP]")
# 2

以下是我们如何设置后处理以获得传统的 BERT 输入:

Python
Rust
Node
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。

为了检查这是否正常工作,让我们尝试对与之前相同的句子进行编码:

Python
Rust
Node
output = tokenizer.encode("Hello, y'all! How are you 😁 ?")
print(output.tokens)
# ["[CLS]", "Hello", ",", "y", "'", "all", "!", "How", "are", "you", "[UNK]", "?", "[SEP]"]

要检查一对句子的结果,我们只需将两个句子传递给 Tokenizer.encode

Python
Rust
Node
output = tokenizer.encode("Hello, y'all!", "How are you 😁 ?")
print(output.tokens)
# ["[CLS]", "Hello", ",", "y", "'", "all", "!", "[SEP]", "How", "are", "you", "[UNK]", "?", "[SEP]"]

然后,你可以用以下方式检查分配给每个词符的类型 ID 是否正确:

Python
Rust
Node
print(output.type_ids)
# [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]

如果你使用 Tokenizer.save 保存你的分词器,后处理器也会被一起保存。

批量编码多个句子

为了充分利用 🤗 Tokenizers 库的速度,最好使用 Tokenizer.encode_batch 方法批量处理你的文本:

Python
Rust
Node
output = tokenizer.encode_batch(["Hello, y'all!", "How are you 😁 ?"])

输出是 Encoding 对象的列表,就像我们之前看到的一样。你可以同时处理任意数量的文本,只要它们能装入内存。

要处理一批句子对,请向 Tokenizer.encode_batch 方法传递两个列表:句子 A 的列表和句子 B 的列表:

Python
Rust
Node
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):

Python
Rust
Node
tokenizer.enable_padding(pad_id=3, pad_token="[PAD]")

我们可以设置填充的 direction(默认为右侧)或指定的 length,如果我们想将每个样本填充到该特定长度(这里我们不设置它,以填充到最长文本的大小)。

Python
Rust
Node
output = tokenizer.encode_batch(["Hello, y'all!", "How are you 😁 ?"])
print(output[1].tokens)
# ["[CLS]", "How", "are", "you", "[UNK]", "?", "[SEP]", "[PAD]"]

在这种情况下,分词器生成的 attention mask 会考虑填充:

Python
Rust
Node
print(output[1].attention_mask)
# [1, 1, 1, 1, 1, 1, 1, 0]

预训练

Python
Rust
Node

使用预训练分词器

你可以从 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
< > 在 GitHub 上更新