子詞分詞器

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼 下載筆記本

本教學課程示範如何從資料集產生子詞詞彙表,並使用該詞彙表建構 text.BertTokenizer

子詞分詞器的主要優點是它在以字詞為基礎和以字元為基礎的分詞之間進行插值。常見字詞會在詞彙表中取得一個位置,但分詞器可以退回到字詞片段和個別字元以處理未知字詞。

總覽

tensorflow_text 套件包含許多常見分詞器的 TensorFlow 實作。這包括三個子詞樣式分詞器

  • text.BertTokenizer - BertTokenizer 類別是更高等級的介面。它包含 BERT 的符號分割演算法和 WordPieceTokenizer。它會將句子做為輸入,並傳回符號 ID
  • text.WordpieceTokenizer - WordPieceTokenizer 類別是更低等級的介面。它只實作 WordPiece 演算法。您必須先標準化文字並將其分割成字詞,才能呼叫它。它會將字詞做為輸入,並傳回符號 ID。
  • text.SentencepieceTokenizer - SentencepieceTokenizer 需要更複雜的設定。它的初始化工具需要預先訓練的 SentencePiece 模型。如要瞭解如何建構這些模型,請參閱 google/sentencepiece 存放區。符號化時,它可以接受句子做為輸入。

本教學課程以上而下的方式建構 WordPiece 詞彙表,從現有字詞開始。此程序不適用於日文、中文或韓文,因為這些語言沒有明確的多字元單位。如要符號化這些語言,請考慮使用 text.SentencepieceTokenizertext.UnicodeCharTokenizer這個方法

設定

pip install -q -U "tensorflow-text==2.11.*"
pip install -q tensorflow_datasets
import collections
import os
import pathlib
import re
import string
import sys
import tempfile
import time

import numpy as np
import matplotlib.pyplot as plt

import tensorflow_datasets as tfds
import tensorflow_text as text
import tensorflow as tf
2024-06-25 11:38:46.052238: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2024-06-25 11:38:46.780906: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory
2024-06-25 11:38:46.781005: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory
2024-06-25 11:38:46.781016: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.
tf.get_logger().setLevel('ERROR')
pwd = pathlib.Path.cwd()

下載資料集

tfds 擷取葡萄牙文/英文翻譯資料集

examples, metadata = tfds.load('ted_hrlr_translate/pt_to_en', with_info=True,
                               as_supervised=True)
train_examples, val_examples = examples['train'], examples['validation']
2024-06-25 11:38:48.987468: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2024-06-25 11:38:48.987595: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcublas.so.11'; dlerror: libcublas.so.11: cannot open shared object file: No such file or directory
2024-06-25 11:38:48.987663: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcublasLt.so.11'; dlerror: libcublasLt.so.11: cannot open shared object file: No such file or directory
2024-06-25 11:38:48.987726: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcufft.so.10'; dlerror: libcufft.so.10: cannot open shared object file: No such file or directory
2024-06-25 11:38:49.042669: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcusparse.so.11'; dlerror: libcusparse.so.11: cannot open shared object file: No such file or directory
2024-06-25 11:38:49.042862: W tensorflow/core/common_runtime/gpu/gpu_device.cc:1934] Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU. Follow the guide at https://tensorflow.dev.org.tw/install/gpu for how to download and setup the required libraries for your platform.
Skipping registering GPU devices...

此資料集會產生葡萄牙文/英文句子配對

for pt, en in train_examples.take(1):
  print("Portuguese: ", pt.numpy().decode('utf-8'))
  print("English:   ", en.numpy().decode('utf-8'))
Portuguese:  e quando melhoramos a procura , tiramos a única vantagem da impressão , que é a serendipidade .
English:    and when you improve searchability , you actually take away the one advantage of print , which is serendipity .

請注意以上範例句子的一些事項

  • 它們都是小寫。
  • 標點符號周圍有空格。
  • 不清楚是否正在使用 Unicode 正規化,或正在使用哪種正規化。
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

產生詞彙表

本節從資料集產生 WordPiece 詞彙表。如果您已經有詞彙表檔案,只想瞭解如何使用它來建構 text.BertTokenizertext.WordpieceTokenizer 分詞器,則可以跳到「建構分詞器」一節。

詞彙表產生程式碼包含在 tensorflow_text pip 套件中。預設不會匯入,您需要手動匯入

from tensorflow_text.tools.wordpiece_vocab import bert_vocab_from_dataset as bert_vocab

bert_vocab.bert_vocab_from_dataset 函式會產生詞彙表。

您可以設定許多引數來調整其行為。在本教學課程中,您主要會使用預設值。如果您想進一步瞭解選項,請先閱讀關於演算法的內容,然後查看程式碼

這大約需要 2 分鐘。

bert_tokenizer_params=dict(lower_case=True)
reserved_tokens=["[PAD]", "[UNK]", "[START]", "[END]"]

bert_vocab_args = dict(
    # The target vocabulary size
    vocab_size = 8000,
    # Reserved tokens that must be included in the vocabulary
    reserved_tokens=reserved_tokens,
    # Arguments for `text.BertTokenizer`
    bert_tokenizer_params=bert_tokenizer_params,
    # Arguments for `wordpiece_vocab.wordpiece_tokenizer_learner_lib.learn`
    learn_params={},
)
%%time
pt_vocab = bert_vocab.bert_vocab_from_dataset(
    train_pt.batch(1000).prefetch(2),
    **bert_vocab_args
)
CPU times: user 1min 23s, sys: 2.53 s, total: 1min 26s
Wall time: 1min 19s

以下是結果詞彙表的一些片段。

print(pt_vocab[:10])
print(pt_vocab[100:110])
print(pt_vocab[1000:1010])
print(pt_vocab[-10:])
['[PAD]', '[UNK]', '[START]', '[END]', '!', '#', '$', '%', '&', "'"]
['no', 'por', 'mais', 'na', 'eu', 'esta', 'muito', 'isso', 'isto', 'sao']
['90', 'desse', 'efeito', 'malaria', 'normalmente', 'palestra', 'recentemente', '##nca', 'bons', 'chave']
['##–', '##—', '##‘', '##’', '##“', '##”', '##⁄', '##€', '##♪', '##♫']

寫入詞彙表檔案

def write_vocab_file(filepath, vocab):
  with open(filepath, 'w') as f:
    for token in vocab:
      print(token, file=f)
write_vocab_file('pt_vocab.txt', pt_vocab)

使用該函式從英文資料產生詞彙表

%%time
en_vocab = bert_vocab.bert_vocab_from_dataset(
    train_en.batch(1000).prefetch(2),
    **bert_vocab_args
)
CPU times: user 59.2 s, sys: 2.02 s, total: 1min 1s
Wall time: 54.8 s
print(en_vocab[:10])
print(en_vocab[100:110])
print(en_vocab[1000:1010])
print(en_vocab[-10:])
['[PAD]', '[UNK]', '[START]', '[END]', '!', '#', '$', '%', '&', "'"]
['as', 'all', 'at', 'one', 'people', 're', 'like', 'if', 'our', 'from']
['choose', 'consider', 'extraordinary', 'focus', 'generation', 'killed', 'patterns', 'putting', 'scientific', 'wait']
['##_', '##`', '##ย', '##ร', '##อ', '##–', '##—', '##’', '##♪', '##♫']

以下是兩個詞彙表檔案

write_vocab_file('en_vocab.txt', en_vocab)
ls *.txt
en_vocab.txt  pt_vocab.txt

建構分詞器

text.BertTokenizer 可以透過傳遞詞彙表檔案的路徑做為第一個引數來初始化 (如需其他選項,請參閱關於 tf.lookup 的章節)

pt_tokenizer = text.BertTokenizer('pt_vocab.txt', **bert_tokenizer_params)
en_tokenizer = text.BertTokenizer('en_vocab.txt', **bert_tokenizer_params)

現在您可以使用它來編碼一些文字。從英文資料中取出 3 個範例的批次

for pt_examples, en_examples in train_examples.batch(3).take(1):
  for ex in en_examples:
    print(ex.numpy())
b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .'
b'but what if it were active ?'
b"but they did n't test for curiosity ."

透過 BertTokenizer.tokenize 方法執行。一開始,這會傳回具有軸 (批次、字詞、字詞片段)tf.RaggedTensor

# Tokenize the examples -> (batch, word, word-piece)
token_batch = en_tokenizer.tokenize(en_examples)
# Merge the word and word-piece axes -> (batch, tokens)
token_batch = token_batch.merge_dims(-2,-1)

for ex in token_batch.to_list():
  print(ex)
[72, 117, 79, 1259, 1491, 2362, 13, 79, 150, 184, 311, 71, 103, 2308, 74, 2679, 13, 148, 80, 55, 4840, 1434, 2423, 540, 15]
[87, 90, 107, 76, 129, 1852, 30]
[87, 83, 149, 50, 9, 56, 664, 85, 2512, 15]

如果您將符號 ID 替換為其文字表示法 (使用 tf.gather),您可以看到在第一個範例中,字詞 "searchability""serendipity" 已分解為 "search ##ability""s ##ere ##nd ##ip ##ity"

# Lookup each token id in the vocabulary.
txt_tokens = tf.gather(en_vocab, token_batch)
# Join with spaces.
tf.strings.reduce_join(txt_tokens, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'and when you improve search ##ability , you actually take away the one advantage of print , which is s ##ere ##nd ##ip ##ity .',
       b'but what if it were active ?',
       b"but they did n ' t test for curiosity ."], dtype=object)>

如要從擷取的符號重新組裝字詞,請使用 BertTokenizer.detokenize 方法

words = en_tokenizer.detokenize(token_batch)
tf.strings.reduce_join(words, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .',
       b'but what if it were active ?',
       b"but they did n ' t test for curiosity ."], dtype=object)>

自訂與匯出

本教學課程建構 Transformer 教學課程使用的文字分詞器和反分詞器。本節新增方法和處理步驟以簡化該教學課程,並使用 tf.saved_model 匯出分詞器,以便其他教學課程可以匯入。

自訂符號化

下游教學課程都希望符號化的文字包含 [START][END] 符號。

reserved_tokens 會在詞彙表開頭保留空間,因此 [START][END] 對於兩種語言都具有相同的索引

START = tf.argmax(tf.constant(reserved_tokens) == "[START]")
END = tf.argmax(tf.constant(reserved_tokens) == "[END]")

def add_start_end(ragged):
  count = ragged.bounding_shape()[0]
  starts = tf.fill([count,1], START)
  ends = tf.fill([count,1], END)
  return tf.concat([starts, ragged, ends], axis=1)
words = en_tokenizer.detokenize(add_start_end(token_batch))
tf.strings.reduce_join(words, separator=' ', axis=-1)
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'[START] and when you improve searchability , you actually take away the one advantage of print , which is serendipity . [END]',
       b'[START] but what if it were active ? [END]',
       b"[START] but they did n ' t test for curiosity . [END]"],
      dtype=object)>

自訂反符號化

在匯出分詞器之前,您可以為下游教學課程清理一些項目

  1. 它們想要產生乾淨的文字輸出,因此請捨棄保留符號,例如 [START][END][PAD]
  2. 它們對完整字串感興趣,因此請沿著結果的 words 軸套用字串聯結。
def cleanup_text(reserved_tokens, token_txt):
  # Drop the reserved tokens, except for "[UNK]".
  bad_tokens = [re.escape(tok) for tok in reserved_tokens if tok != "[UNK]"]
  bad_token_re = "|".join(bad_tokens)

  bad_cells = tf.strings.regex_full_match(token_txt, bad_token_re)
  result = tf.ragged.boolean_mask(token_txt, ~bad_cells)

  # Join them into strings.
  result = tf.strings.reduce_join(result, separator=' ', axis=-1)

  return result
en_examples.numpy()
array([b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .',
       b'but what if it were active ?',
       b"but they did n't test for curiosity ."], dtype=object)
token_batch = en_tokenizer.tokenize(en_examples).merge_dims(-2,-1)
words = en_tokenizer.detokenize(token_batch)
words
<tf.RaggedTensor [[b'and', b'when', b'you', b'improve', b'searchability', b',', b'you',
  b'actually', b'take', b'away', b'the', b'one', b'advantage', b'of',
  b'print', b',', b'which', b'is', b'serendipity', b'.']              ,
 [b'but', b'what', b'if', b'it', b'were', b'active', b'?'],
 [b'but', b'they', b'did', b'n', b"'", b't', b'test', b'for', b'curiosity',
  b'.']                                                                    ]>
cleanup_text(reserved_tokens, words).numpy()
array([b'and when you improve searchability , you actually take away the one advantage of print , which is serendipity .',
       b'but what if it were active ?',
       b"but they did n ' t test for curiosity ."], dtype=object)

匯出

以下程式碼區塊會建構 CustomTokenizer 類別,以包含 text.BertTokenizer 執行個體、自訂邏輯,以及匯出所需的 @tf.function 包裝函式。

class CustomTokenizer(tf.Module):
  def __init__(self, reserved_tokens, vocab_path):
    self.tokenizer = text.BertTokenizer(vocab_path, lower_case=True)
    self._reserved_tokens = reserved_tokens
    self._vocab_path = tf.saved_model.Asset(vocab_path)

    vocab = pathlib.Path(vocab_path).read_text().splitlines()
    self.vocab = tf.Variable(vocab)

    ## Create the signatures for export:   

    # Include a tokenize signature for a batch of strings. 
    self.tokenize.get_concrete_function(
        tf.TensorSpec(shape=[None], dtype=tf.string))

    # Include `detokenize` and `lookup` signatures for:
    #   * `Tensors` with shapes [tokens] and [batch, tokens]
    #   * `RaggedTensors` with shape [batch, tokens]
    self.detokenize.get_concrete_function(
        tf.TensorSpec(shape=[None, None], dtype=tf.int64))
    self.detokenize.get_concrete_function(
          tf.RaggedTensorSpec(shape=[None, None], dtype=tf.int64))

    self.lookup.get_concrete_function(
        tf.TensorSpec(shape=[None, None], dtype=tf.int64))
    self.lookup.get_concrete_function(
          tf.RaggedTensorSpec(shape=[None, None], dtype=tf.int64))

    # These `get_*` methods take no arguments
    self.get_vocab_size.get_concrete_function()
    self.get_vocab_path.get_concrete_function()
    self.get_reserved_tokens.get_concrete_function()

  @tf.function
  def tokenize(self, strings):
    enc = self.tokenizer.tokenize(strings)
    # Merge the `word` and `word-piece` axes.
    enc = enc.merge_dims(-2,-1)
    enc = add_start_end(enc)
    return enc

  @tf.function
  def detokenize(self, tokenized):
    words = self.tokenizer.detokenize(tokenized)
    return cleanup_text(self._reserved_tokens, words)

  @tf.function
  def lookup(self, token_ids):
    return tf.gather(self.vocab, token_ids)

  @tf.function
  def get_vocab_size(self):
    return tf.shape(self.vocab)[0]

  @tf.function
  def get_vocab_path(self):
    return self._vocab_path

  @tf.function
  def get_reserved_tokens(self):
    return tf.constant(self._reserved_tokens)

為每種語言建構 CustomTokenizer

tokenizers = tf.Module()
tokenizers.pt = CustomTokenizer(reserved_tokens, 'pt_vocab.txt')
tokenizers.en = CustomTokenizer(reserved_tokens, 'en_vocab.txt')

將分詞器匯出為 saved_model

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.saved_model.save(tokenizers, model_name)

重新載入 saved_model 並測試方法

reloaded_tokenizers = tf.saved_model.load(model_name)
reloaded_tokenizers.en.get_vocab_size().numpy()
7010
tokens = reloaded_tokenizers.en.tokenize(['Hello TensorFlow!'])
tokens.numpy()
array([[   2, 4006, 2358,  687, 1192, 2365,    4,    3]])
text_tokens = reloaded_tokenizers.en.lookup(tokens)
text_tokens
<tf.RaggedTensor [[b'[START]', b'hello', b'tens', b'##or', b'##f', b'##low', b'!',
  b'[END]']]>
round_trip = reloaded_tokenizers.en.detokenize(tokens)

print(round_trip.numpy()[0].decode('utf-8'))
hello tensorflow !

將其封存以用於翻譯教學課程

zip -r {model_name}.zip {model_name}
adding: ted_hrlr_translate_pt_en_converter/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/assets/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/assets/en_vocab.txt (deflated 54%)
  adding: ted_hrlr_translate_pt_en_converter/assets/pt_vocab.txt (deflated 57%)
  adding: ted_hrlr_translate_pt_en_converter/fingerprint.pb (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/variables/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.index (deflated 33%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.data-00000-of-00001 (deflated 51%)
  adding: ted_hrlr_translate_pt_en_converter/saved_model.pb (deflated 91%)
du -h *.zip
168K    ted_hrlr_translate_pt_en_converter.zip

選用:演算法

這裡值得注意的是,WordPiece 演算法有兩個版本:由下而上和由上而下。在兩種情況下,目標都相同:「在給定訓練語料庫和所需符號數量 D 的情況下,最佳化問題是選取 D 個字詞片段,以便根據選取的 WordPiece 模型進行分割時,產生的語料庫在字詞片段數量方面達到最小化。」

原始的由下而上 WordPiece 演算法是以 位元組配對編碼為基礎。與 BPE 類似,它從字母開始,並反覆組合常見的雙字母組合以形成字詞片段和字詞。

TensorFlow Text 的詞彙表產生器遵循 BERT 的由上而下實作。從字詞開始,並將它們分解成較小的元件,直到它們達到頻率門檻,或無法進一步分解為止。下一節會詳細說明這一點。對於日文、中文和韓文,這種由上而下的方法不起作用,因為一開始就沒有明確的字詞單位。對於這些語言,您需要不同的方法

選擇詞彙表

由上而下的 WordPiece 產生演算法會接收一組 (字詞、計數) 配對和門檻 T,並傳回詞彙表 V

此演算法是反覆運算的。它會執行 k 次反覆運算,其中通常 k = 4,但只有前兩次反覆運算真正重要。第三次和第四次 (以及之後) 反覆運算與第二次反覆運算完全相同。請注意,二元搜尋的每個步驟都會從頭開始針對 k 次反覆運算執行演算法。

以下說明反覆運算

第一次反覆運算

  1. 反覆運算輸入中的每個字詞和計數配對,表示為 (w, c)
  2. 針對每個字詞 w,產生每個子字串,表示為 s。例如,對於字詞 human,我們會產生 {h, hu, hum, huma, human, ##u, ##um, ##uma, ##uman, ##m, ##ma, ##man, #a, ##an, ##n}
  3. 維護子字串到計數的雜湊對應表,並將每個 s 的計數遞增 c。例如,如果我們的輸入中有 (human, 113)(humas, 3),則 s = huma 的計數將為 113+3=116
  4. 收集每個子字串的計數後,反覆運算 (s, c) 配對,從最長的 s 開始
  5. 保留任何 c > Ts。例如,如果 T = 100,而我們有 (pers, 231); (dogs, 259); (##rint; 76),則我們會保留 persdogs
  6. s 被保留時,從其所有前置字元中減去其計數。這是步驟 4 中依長度排序所有 s 的原因。這是演算法的關鍵部分,否則字詞會被重複計算。例如,假設我們已保留 human,並且我們取得 (huma, 116)。我們知道這些 116 個計數中有 113 個來自 human,而 3 個來自 humas。但是,既然 human 在我們的詞彙表中,我們知道我們永遠不會將 human 分割成 huma ##n。因此,一旦 human 被保留,則 huma 只有 3有效計數。

此演算法將產生一組字詞片段 s (其中許多將是完整字詞 w),我們可以將其用作我們的 WordPiece 詞彙表。

但是,有一個問題:此演算法會嚴重過度產生字詞片段。原因是我們只減去前置字元符號的計數。因此,如果我們保留字詞 human,我們將減去 h, hu, hu, huma 的計數,但不減去 ##u, ##um, ##uma, ##uman 等的計數。因此,我們可能會產生 human##uman 作為字詞片段,即使永遠不會套用 ##uman

那麼,為什麼不減去每個子字串 (而不僅僅是每個前置字元) 的計數?因為這樣我們最終可能會多次減去計數。假設我們正在處理長度為 5 的 s,並且我們保留 (##denia, 129)(##eniab, 137),其中 65 個計數來自字詞 undeniable。如果我們從每個子字串中減去,我們會從子字串 ##enia 中減去 65 兩次,即使我們應該只減去一次。但是,如果我們只從前置字元中減去,則它只會正確減去一次。

第二次 (和第三次...) 反覆運算

為了解決上述過度產生的問題,我們執行演算法的多次反覆運算。

後續反覆運算與第一次反覆運算相同,但有一個重要的區別:在步驟 2 中,我們不是考慮每個子字串,而是使用前一次反覆運算的詞彙表套用 WordPiece 符號化演算法,並且只考慮分割點開始的子字串。

例如,假設我們正在執行演算法的步驟 2,並且遇到字詞 undeniable。在第一次反覆運算中,我們會考慮每個子字串,例如 {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...}

現在,對於第二次反覆運算,我們只會考慮這些子字串的子集。假設在第一次反覆運算之後,相關的字詞片段是

un, ##deni, ##able, ##ndeni, ##iable

WordPiece 演算法會將其分割成 un ##deni ##able (如需更多資訊,請參閱 套用 WordPiece 章節)。在這種情況下,我們只會考慮分割點開始的子字串。我們仍會考慮每個可能的結束位置。因此,在第二次反覆運算期間,undeniables 集合為

{u, un, und, unden, undeni, undenia, undeniab, undeniabl, undeniable, ##d, ##de, ##den, ##deni, ##denia, ##deniab, ##deniabl , ##deniable, ##a, ##ab, ##abl, ##able}

否則,演算法是相同的。在本範例中,在第一次反覆運算中,演算法會產生偽符號 ##ndeni##iable。現在,永遠不會考慮這些符號,因此第二次反覆運算不會產生這些符號。我們執行多次反覆運算只是為了確保結果收斂 (雖然沒有明確的收斂保證)。

套用 WordPiece

產生 WordPiece 詞彙表後,我們需要能夠將其套用至新資料。此演算法是一個簡單的貪婪式最長比對優先應用程式。

例如,考慮分割字詞 undeniable

我們先在我們的 WordPiece 字典中查閱 undeniable,如果它存在,我們就完成了。如果沒有,我們會將端點遞減一個字元,然後重複,例如 undeniabl

最終,我們會在我們的詞彙表中找到一個子符號,或縮減為單一字元子符號。(一般而言,我們假設每個字元都在我們的詞彙表中,儘管對於罕見的 Unicode 字元可能並非如此。如果我們遇到詞彙表中沒有的罕見 Unicode 字元,我們只會將整個字詞對應到 <unk>)。

在這種情況下,我們在我們的詞彙表中找到 un。因此,這是我們的第一個字詞片段。然後我們跳到 un 的末尾並重複處理,例如,嘗試尋找 ##deniable,然後是 ##deniabl 等。重複此步驟,直到我們分割整個字詞為止。

直覺

直覺上,WordPiece 符號化嘗試滿足兩個不同的目標

  1. 將資料符號化為最少數量的片段。請務必記住,WordPiece 演算法「不想要」分割字詞。否則,它只會將每個字詞分割成其字元,例如 human -> {h, ##u, ##m, ##a, #n}。這是使 WordPiece 與詞素分割器不同的關鍵因素之一,詞素分割器即使對於常見字詞也會分割語言詞素 (例如,unwanted -> {un, want, ed})。

  2. 當字詞必須分割成片段時,請將其分割成在訓練資料中具有最大計數的片段。例如,字詞 undeniable 會分割成 {un, ##deni, ##able},而不是分割成 {unde, ##niab, ##le} 等替代方案的原因是,un##able 的計數特別高,因為這些是常見的前置字元和後置字元。即使 ##le 的計數必須高於 ##ableunde##niab 的低計數也會使演算法不太「希望」進行這種符號化。

選用:tf.lookup

如果您需要存取詞彙表或對詞彙表進行更多控制,則值得注意的是,您可以自行建構查閱表,並將其傳遞至 BertTokenizer

當您傳遞字串時,BertTokenizer 會執行以下操作

pt_lookup = tf.lookup.StaticVocabularyTable(
    num_oov_buckets=1,
    initializer=tf.lookup.TextFileInitializer(
        filename='pt_vocab.txt',
        key_dtype=tf.string,
        key_index = tf.lookup.TextFileIndex.WHOLE_LINE,
        value_dtype = tf.int64,
        value_index=tf.lookup.TextFileIndex.LINE_NUMBER)) 
pt_tokenizer = text.BertTokenizer(pt_lookup)

現在您可以直接存取分詞器中使用的查閱表。

pt_lookup.lookup(tf.constant(['é', 'um', 'uma', 'para', 'não']))
<tf.Tensor: shape=(5,), dtype=int64, numpy=array([7765,   85,   86,   87, 7765])>

您不需要使用詞彙表檔案,tf.lookup 具有其他初始化工具選項。如果您的詞彙表在記憶體中,您可以使用 lookup.KeyValueTensorInitializer

pt_lookup = tf.lookup.StaticVocabularyTable(
    num_oov_buckets=1,
    initializer=tf.lookup.KeyValueTensorInitializer(
        keys=pt_vocab,
        values=tf.range(len(pt_vocab), dtype=tf.int64))) 
pt_tokenizer = text.BertTokenizer(pt_lookup)