标记器文档

标记化管道

Hugging Face's logo
加入 Hugging Face 社区

并获得增强文档体验的访问权限

以开始使用

标记化管道

当调用 Tokenizer.encodeTokenizer.encode_batch 时,输入文本将经过以下管道

  • 规范化
  • 预标记化
  • 模型
  • 后处理

我们将详细了解在每个步骤中发生的具体情况,以及您希望 解码 <decoding> 一些标记 ID 时的情况,以及 🤗 标记器库如何让您根据需要自定义每个步骤。如果您已经熟悉这些步骤,并想通过查看代码来学习,请跳到 我们的 BERT 从头开始示例 <example>

对于需要 Tokenizer 的示例,我们将使用我们在 快速浏览 中训练的标记器,您可以使用以下方法加载它

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

规范化

简而言之,规范化是对原始字符串应用的一组操作,以使其不那么随机或更“干净”。常见操作包括剥离空格、删除重音符号或将所有文本转换为小写。如果您熟悉 Unicode 规范化,它也是大多数标记器中应用的非常常见的规范化操作。

每个规范化操作在 🤗 标记器库中由一个 Normalizer 表示,您可以通过使用 normalizers.Sequence 将多个规范化操作组合在一起。以下是一个应用 NFD Unicode 规范化并删除重音符号的规范化器示例

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

您可以通过将规范化器应用于任何字符串来手动测试它

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

在构建 Tokenizer 时,您可以通过更改相应的属性来自定义其规范化器

Python
Rust
节点
tokenizer.normalizer = normalizer

当然,如果您更改了标记器应用规范化的方式,您可能应该事后从头开始重新训练它。

预标记化

预标记化是将文本分割成较小对象的步骤,这些对象为最终标记在训练结束时的上限提供了上限。一个好的理解方式是,预标记器会将文本分割成“单词”,然后您的最终标记将是这些单词的一部分。

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

Python
Rust
节点
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))]

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

您可以将任何 PreTokenizer 组合在一起。例如,以下是一个预标记器,它将在空格、标点符号和数字上分割,并将数字分离成单独的数字

Python
Rust
节点
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
节点
tokenizer.pre_tokenizer = pre_tokenizer

当然,如果您更改了预标记器的方式,您可能应该事后从头开始重新训练标记器。

模型

一旦输入文本被规范化和预标记化,Tokenizer 就会在预标记上应用模型。这是管道中需要在您的语料库上进行训练的部分(或者如果您使用的是预训练的标记器,则该部分已经过训练)。

模型的作用是使用它学习的规则将“单词”分割成标记。它还负责将这些标记映射到模型词典中的对应 ID。

此模型在初始化 Tokenizer 时传递,因此您已经知道如何自定义此部分。目前,🤗 标记器库支持

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

有关每个模型及其行为的更多详细信息,您可以查看 此处

后处理

后处理是标记化流程的最后一步,它在返回 `Encoding` 之前执行任何额外的转换,例如添加潜在的特殊标记。

正如我们在快速浏览中所见,我们可以通过设置相应的属性来自定义 `Tokenizer` 的后处理程序。例如,以下是我们将后处理程序用于使输入适合 BERT 模型的方法

Python
Rust
节点
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
节点
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
bert_tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))

然后我们知道 BERT 通过删除重音符号和小写字母来预处理文本。我们还使用 unicode 规范器

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

预标记器只是按空格和标点符号进行拆分

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

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

Python
Rust
节点
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 上对其进行训练,就像在 `quicktour` 中一样

Python
Rust
节点
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
节点
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
节点
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
节点
from tokenizers import decoders
bert_tokenizer.decoder = decoders.WordPiece()
bert_tokenizer.decode(output.ids)
# "welcome to the tokenizers library."