分詞器文件
分詞管線
並獲得增強的文件體驗
開始使用
分詞管線
當呼叫 Tokenizer.encode
或 Tokenizer.encode_batch
時,輸入的文字會經過以下管線:
歸一化
預分詞
模型
後處理
我們將詳細介紹每個步驟中發生的情況,以及當你想要解碼(decode)<decoding>
某些詞符 ID 時的處理過程,並說明 🤗 Tokenizers 庫如何允許你根據需求自定義這些步驟。如果你已經熟悉這些步驟,並希望透過程式碼示例來學習,可以直接跳轉到我們的從零開始構建 BERT 示例<example>
。
對於需要 Tokenizer
的示例,我們將使用在快速入門
中訓練好的分詞器,你可以透過以下方式載入:
from tokenizers import Tokenizer
tokenizer = Tokenizer.from_file("data/tokenizer-wiki.json")
歸一化
簡而言之,歸一化是對原始字串應用的一系列操作,以使其更規整或“更乾淨”。常見的操作包括去除空白、移除重音字元或將所有文字轉換為小寫。如果你熟悉Unicode 歸一化,它也是大多數分詞器中常用的歸一化操作。
在 🤗 Tokenizers 庫中,每個歸一化操作都由一個 Normalizer
表示,你可以透過使用 normalizers.Sequence
來組合多個操作。下面是一個應用 NFD Unicode 歸一化並移除重音的歸一化器示例:
from tokenizers import normalizers
from tokenizers.normalizers import NFD, StripAccents
normalizer = normalizers.Sequence([NFD(), StripAccents()])
你可以透過將其應用於任何字串來手動測試該歸一化器:
normalizer.normalize_str("Héllò hôw are ü?")
# "Hello how are u?"
在構建 Tokenizer
時,你可以透過更改相應的屬性來自定義其歸一化器:
tokenizer.normalizer = normalizer
當然,如果你更改了分詞器應用歸一化的方式,之後可能需要從頭開始重新訓練它。
預分詞
預分詞是將文字分割成更小物件的操作,這些物件為訓練結束時詞符的最終形態設定了上限。一個好的理解方式是,預分詞器會將你的文字分割成“單詞”,然後,你最終的詞符將是這些單詞的一部分。
一種簡單的預分詞方法是按空格和標點符號進行分割,這由 pre_tokenizers.Whitespace
預分詞器完成:
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
組合在一起。例如,下面是一個按空格、標點和數字進行分割,並將數字拆分為單個數字的預分詞器:
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
的預分詞器:
tokenizer.pre_tokenizer = pre_tokenizer
當然,如果你更改了預分詞器的方式,之後可能需要從頭開始重新訓練你的分詞器。
模型
一旦輸入文字經過歸一化和預分詞處理,Tokenizer
會在預分詞後的詞符上應用模型。這是管線中需要在你的語料庫上進行訓練的部分(或者如果你使用的是預訓練分詞器,則這部分已經訓練好了)。
模型的作用是使用其學到的規則將你的“單詞”分割成詞符。它還負責將這些詞符對映到模型詞彙表中對應的 ID。
這個模型在初始化 Tokenizer
時傳遞,所以你已經知道如何自定義這部分了。目前,🤗 Tokenizers 庫支援:
models.BPE
models.Unigram
models.WordLevel
models.WordPiece
有關每個模型及其行為的更多詳細資訊,你可以檢視這裡。
後處理
後處理是分詞管線的最後一步,用於在返回 Encoding
之前對其進行任何額外的轉換,例如新增可能的特殊詞符。
正如我們在快速入門中看到的,我們可以透過設定相應的屬性來自定義 Tokenizer
的後處理器。例如,以下是如何進行後處理以使輸入適用於 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]", 1), ("[SEP]", 2)],
)
請注意,與預分詞器或歸一化器不同,更改後處理器後你不需要重新訓練分詞器。
整合:從零開始構建 BERT 分詞器
讓我們將所有這些部分組合起來,構建一個 BERT 分詞器。首先,BERT 依賴於 WordPiece,所以我們用這個模型例項化一個新的 Tokenizer
:
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
bert_tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
然後我們知道 BERT 透過移除重音和轉換為小寫來預處理文字。我們還使用一個 Unicode 歸一化器:
from tokenizers import normalizers
from tokenizers.normalizers import NFD, Lowercase, StripAccents
bert_tokenizer.normalizer = normalizers.Sequence([NFD(), Lowercase(), StripAccents()])
預分詞器只是按空白和標點符號進行分割:
from tokenizers.pre_tokenizers import Whitespace
bert_tokenizer.pre_tokenizer = Whitespace()
後處理使用我們在上一節中看到的模板:
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 上進行訓練:
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 轉換回詞符(使用分詞器的詞彙表)並移除所有特殊詞符,然後用空格連線這些詞符:
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
為例,預設解碼會得到:
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 ."
但透過將其更改為適當的解碼器,我們得到:
from tokenizers import decoders
bert_tokenizer.decoder = decoders.WordPiece()
bert_tokenizer.decode(output.ids)
# "welcome to the tokenizers library."