Tokenizers 文档

分词管道

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始入门

分词管道

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

  • 归一化 (normalization)
  • 预分词 (pre-tokenization)
  • 模型 (model)
  • 后处理 (post-processing)

我们将详细了解每个步骤中发生的情况,以及何时需要 decode <decoding> 一些 token id,以及 🤗 Tokenizers 库如何允许您根据需要自定义每个步骤。如果您已经熟悉这些步骤并想通过查看一些代码来学习,请跳转到 我们的从零开始的 BERT 示例 <example>

对于需要 Tokenizer 的示例,我们将使用在 快速入门 中训练的分词器,您可以使用以下代码加载它

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

归一化 (Normalization)

简而言之,归一化 (Normalization) 是一组应用于原始字符串的操作,使其不那么随机或更“干净”。 常见操作包括去除空格、删除重音字符或将所有文本转换为小写。 如果您熟悉 Unicode 归一化,它也是大多数分词器中应用的一种非常常见的归一化操作。

每个归一化操作在 🤗 Tokenizers 库中都由 Normalizer 表示,您可以使用 normalizers.Sequence 组合其中几个。 以下是一个应用 NFD Unicode 归一化并删除重音符号的 normalizer 示例

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

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

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

在构建 Tokenizer 时,您只需更改相应的属性即可自定义其 normalizer

Python
Rust
Node
tokenizer.normalizer = normalizer

当然,如果您更改 tokenizer 应用归一化的方式,您可能应该之后从头开始重新训练它。

预分词 (Pre-Tokenization)

预分词 (Pre-tokenization) 是将文本拆分为更小的对象,这些对象为训练结束时您的 token 设置了上限。 考虑这个问题的一个好方法是,预分词器会将您的文本拆分为“单词”,然后,您的最终 token 将是这些单词的一部分。

预分词输入的一种简单方法是按空格和标点符号拆分,这由 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))]

输出是一个元组列表,每个元组包含一个单词及其在原始句子中的跨度(用于确定最终 Encodingoffsets)。 请注意,按标点符号拆分将拆分此示例中的缩略词,如 "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

当然,如果您更改预分词器的方式,您可能应该之后从头开始重新训练您的分词器。

模型 (Model)

一旦输入文本被归一化和预分词,Tokenizer 就会对预 token 应用模型。 这是管道中需要在您的语料库上进行训练的部分(如果您使用的是预训练的分词器,则已训练)。

模型的作用是使用它学习的规则将您的“单词”拆分为 token。 它还负责将这些 token 映射到模型词汇表中相应的 ID。

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

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

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

后处理 (Post-Processing)

后处理 (Post-processing) 是分词管道的最后一步,用于在返回 Encoding 之前对其执行任何额外的转换,例如添加潜在的特殊 token。

正如我们在快速入门中看到的,我们可以通过设置相应的属性来自定义 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)],
)

请注意,与预分词器或 normalizer 相反,您无需在更改 tokenizer 的后处理器后重新训练它。

全部整合:从零开始构建 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 normalizer

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")

解码 (Decoding)

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

decoder 将首先将 ID 转换回 token(使用 tokenizer 的词汇表)并删除所有特殊 token,然后用空格连接这些 token

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 ?"

如果您使用的模型添加了特殊字符来表示给定“单词”的子 token(例如 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 ."

但是通过将其更改为正确的 decoder,我们得到

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