分词器文档

分词管线

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

分词管线

当调用 Tokenizer.encodeTokenizer.encode_batch 时,输入的文本会经过以下管线:

  • 归一化
  • 预分词
  • 模型
  • 后处理

我们将详细介绍每个步骤中发生的情况,以及当你想要解码(decode)<decoding>某些词符 ID 时的处理过程,并说明 🤗 Tokenizers 库如何允许你根据需求自定义这些步骤。如果你已经熟悉这些步骤,并希望通过代码示例来学习,可以直接跳转到我们的从零开始构建 BERT 示例<example>

对于需要 Tokenizer 的示例,我们将使用在快速入门中训练好的分词器,你可以通过以下方式加载:

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

归一化

简而言之,归一化是对原始字符串应用的一系列操作,以使其更规整或“更干净”。常见的操作包括去除空白、移除重音字符或将所有文本转换为小写。如果你熟悉Unicode 归一化,它也是大多数分词器中常用的归一化操作。

在 🤗 Tokenizers 库中,每个归一化操作都由一个 Normalizer 表示,你可以通过使用 normalizers.Sequence 来组合多个操作。下面是一个应用 NFD Unicode 归一化并移除重音的归一化器示例:

Python
Rust
Node
from tokenizers import normalizers
from tokenizers.normalizers import NFD, StripAccents
normalizer = normalizers.Sequence([NFD(), StripAccents()])

你可以通过将其应用于任何字符串来手动测试该归一化器:

Python
Rust
Node
normalizer.normalize_str("Héllò hôw are ü?")
# "Hello how are u?"

在构建 Tokenizer 时,你可以通过更改相应的属性来自定义其归一化器:

Python
Rust
Node
tokenizer.normalizer = normalizer

当然,如果你更改了分词器应用归一化的方式,之后可能需要从头开始重新训练它。

预分词

预分词是将文本分割成更小对象的操作,这些对象为训练结束时词符的最终形态设定了上限。一个好的理解方式是,预分词器会将你的文本分割成“单词”,然后,你最终的词符将是这些单词的一部分。

一种简单的预分词方法是按空格和标点符号进行分割,这由 pre_tokenizers.Whitespace 预分词器完成:

Python
Rust
Node
from tokenizers.pre_tokenizers import Whitespace
pre_tokenizer = Whitespace()
pre_tokenizer.pre_tokenize_str("Hello! How are you? I'm fine, thank you.")
# [("Hello", (0, 5)), ("!", (5, 6)), ("How", (7, 10)), ("are", (11, 14)), ("you", (15, 18)),
#  ("?", (18, 19)), ("I", (20, 21)), ("'", (21, 22)), ('m', (22, 23)), ("fine", (24, 28)),
#  (",", (28, 29)), ("thank", (30, 35)), ("you", (36, 39)), (".", (39, 40))]

输出是一个元组列表,每个元组包含一个单词及其在原始句子中的跨度(用于确定我们 Encoding 的最终 offsets)。请注意,按标点符号分割会拆分本例中的缩写词,如 "I'm"

你可以将任何 PreTokenizer 组合在一起。例如,下面是一个按空格、标点和数字进行分割,并将数字拆分为单个数字的预分词器:

Python
Rust
Node
from tokenizers import pre_tokenizers
from tokenizers.pre_tokenizers import Digits
pre_tokenizer = pre_tokenizers.Sequence([Whitespace(), Digits(individual_digits=True)])
pre_tokenizer.pre_tokenize_str("Call 911!")
# [("Call", (0, 4)), ("9", (5, 6)), ("1", (6, 7)), ("1", (7, 8)), ("!", (8, 9))]

正如我们在快速入门中看到的,你可以通过更改相应的属性来自定义 Tokenizer 的预分词器:

Python
Rust
Node
tokenizer.pre_tokenizer = pre_tokenizer

当然,如果你更改了预分词器的方式,之后可能需要从头开始重新训练你的分词器。

模型

一旦输入文本经过归一化和预分词处理,Tokenizer 会在预分词后的词符上应用模型。这是管线中需要在你的语料库上进行训练的部分(或者如果你使用的是预训练分词器,则这部分已经训练好了)。

模型的作用是使用其学到的规则将你的“单词”分割成词符。它还负责将这些词符映射到模型词汇表中对应的 ID。

这个模型在初始化 Tokenizer 时传递,所以你已经知道如何自定义这部分了。目前,🤗 Tokenizers 库支持:

  • models.BPE
  • models.Unigram
  • models.WordLevel
  • models.WordPiece

有关每个模型及其行为的更多详细信息,你可以查看这里

后处理

后处理是分词管线的最后一步,用于在返回 Encoding 之前对其进行任何额外的转换,例如添加可能的特殊词符。

正如我们在快速入门中看到的,我们可以通过设置相应的属性来自定义 Tokenizer 的后处理器。例如,以下是如何进行后处理以使输入适用于 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]", 1), ("[SEP]", 2)],
)

请注意,与预分词器或归一化器不同,更改后处理器后你不需要重新训练分词器。

整合:从零开始构建 BERT 分词器

让我们将所有这些部分组合起来,构建一个 BERT 分词器。首先,BERT 依赖于 WordPiece,所以我们用这个模型实例化一个新的 Tokenizer

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

然后我们知道 BERT 通过移除重音和转换为小写来预处理文本。我们还使用一个 Unicode 归一化器:

Python
Rust
Node
from tokenizers import normalizers
from tokenizers.normalizers import NFD, Lowercase, StripAccents
bert_tokenizer.normalizer = normalizers.Sequence([NFD(), Lowercase(), StripAccents()])

预分词器只是按空白和标点符号进行分割:

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

后处理使用我们在上一节中看到的模板:

Python
Rust
Node
from tokenizers.processors import TemplateProcessing
bert_tokenizer.post_processor = TemplateProcessing(
    single="[CLS] $A [SEP]",
    pair="[CLS] $A [SEP] $B:1 [SEP]:1",
    special_tokens=[
        ("[CLS]", 1),
        ("[SEP]", 2),
    ],
)

我们可以使用这个分词器,并像在快速入门中一样,在 wikitext 上进行训练:

Python
Rust
Node
from tokenizers.trainers import WordPieceTrainer
trainer = WordPieceTrainer(vocab_size=30522, special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
files = [f"data/wikitext-103-raw/wiki.{split}.raw" for split in ["test", "train", "valid"]]
bert_tokenizer.train(files, trainer)
bert_tokenizer.save("data/bert-wiki.json")

解码

除了对输入文本进行编码,Tokenizer 还提供了用于解码的 API,即将模型生成的 ID 转换回文本。这通过 Tokenizer.decode(用于单个预测文本)和 Tokenizer.decode_batch(用于一批预测)方法完成。

decoder 会首先将 ID 转换回词符(使用分词器的词汇表)并移除所有特殊词符,然后用空格连接这些词符:

Python
Rust
Node
output = tokenizer.encode("Hello, y'all! How are you 😁 ?")
print(output.ids)
# [1, 27253, 16, 93, 11, 5097, 5, 7961, 5112, 6218, 0, 35, 2]
tokenizer.decode([1, 27253, 16, 93, 11, 5097, 5, 7961, 5112, 6218, 0, 35, 2])
# "Hello , y ' all ! How are you ?"

如果你使用的模型添加了特殊字符来表示给定“单词”的子词(例如 WordPiece 中的 "##"),你需要自定义 decoder 以正确处理它们。如果我们以前面的 bert_tokenizer 为例,默认解码会得到:

Python
Rust
Node
output = bert_tokenizer.encode("Welcome to the 🤗 Tokenizers library.")
print(output.tokens)
# ["[CLS]", "welcome", "to", "the", "[UNK]", "tok", "##eni", "##zer", "##s", "library", ".", "[SEP]"]
bert_tokenizer.decode(output.ids)
# "welcome to the tok ##eni ##zer ##s library ."

但通过将其更改为适当的解码器,我们得到:

Python
Rust
Node
from tokenizers import decoders
bert_tokenizer.decoder = decoders.WordPiece()
bert_tokenizer.decode(output.ids)
# "welcome to the tokenizers library."
< > 在 GitHub 上更新