tokenizers คำย่อย

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

บทช่วยสอนนี้สาธิตวิธีสร้างคำศัพท์ย่อยจากชุดข้อมูล และใช้เพื่อสร้างข้อความ text.BertTokenizer จากคำศัพท์

ข้อได้เปรียบหลักของ tokenizer ของ subword คือมันสอดแทรกระหว่าง tokenization แบบ word-based และ character-based คำทั่วไปจะมีช่องว่างในคำศัพท์ แต่ tokenizer สามารถถอยกลับไปเป็นคำและอักขระแต่ละตัวสำหรับคำที่ไม่รู้จัก

ภาพรวม

แพ็คเกจ tensorflow_text รวมการใช้งาน TensorFlow ของโทเคนไนเซอร์ทั่วไปหลายตัว ซึ่งรวมถึงตัวสร้างโทเค็นแบบคำย่อยสามตัว:

  • text.BertTokenizer - คลาส BertTokenizer เป็นอินเทอร์เฟซระดับสูง ประกอบด้วยอัลกอริธึมการแยกโทเค็นของ BERT และ WordPieceTokenizer ใช้ ประโยค เป็นอินพุตและส่งคืน token-IDs
  • text.WordpeiceTokenizer - คลาส WordPieceTokenizer เป็นอินเทอร์เฟซระดับล่าง ใช้ อัลกอริทึม WordPiece เท่านั้น คุณต้องสร้างมาตรฐานและแบ่งข้อความเป็นคำก่อนเรียก ใช้ คำ เป็นอินพุตและส่งคืนรหัสโทเค็น
  • text.SentencepieceTokenizer - SentencepieceTokenizer ต้องการการตั้งค่าที่ซับซ้อนมากขึ้น ตัวเริ่มต้นของมันต้องใช้แบบจำลองประโยคที่ได้รับการฝึกฝนมาล่วงหน้า ดูที่เก็บ google/sentencepiece สำหรับคำแนะนำในการสร้างโมเดลเหล่านี้ สามารถรับ ประโยค เป็นอินพุตเมื่อสร้างโทเค็น

บทช่วยสอนนี้สร้างคำศัพท์ Wordpiece ในลักษณะจากบนลงล่าง โดยเริ่มจากคำที่มีอยู่ กระบวนการนี้ใช้ไม่ได้กับภาษาญี่ปุ่น จีน หรือเกาหลี เนื่องจากภาษาเหล่านี้ไม่มีหน่วยอักขระหลายตัวที่ชัดเจน หากต้องการแปลงเป็นภาษาเหล่านี้ ให้พิจารณาการใช้ text.SentencepieceTokenizer , text.UnicodeCharTokenizer หรือ วิธีการ นี้

ติดตั้ง

pip install -q -U tensorflow-text
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
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']

ชุดข้อมูลนี้สร้างคู่ประโยคภาษาโปรตุเกส/อังกฤษ:

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 .

สังเกตบางสิ่งเกี่ยวกับประโยคตัวอย่างด้านบน:

  • เป็นตัวพิมพ์เล็ก
  • มีช่องว่างรอบเครื่องหมายวรรคตอน
  • ยังไม่ชัดเจนว่ากำลังใช้การทำให้เป็นมาตรฐานของยูนิโค้ดหรือไม่
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

สร้างคำศัพท์

ส่วนนี้จะสร้างคำศัพท์ wordpiece จากชุดข้อมูล หากคุณมีไฟล์คำศัพท์อยู่แล้วและต้องการดูวิธีสร้าง text.BertTokenizer หรือ text.Wordpiece tokenizer กับมัน คุณสามารถข้ามไปที่ส่วน Build the tokenizer ได้

รหัสการสร้างคำศัพท์จะรวมอยู่ในแพ็คเกจ pip tensorflow_text ไม่ได้นำเข้าโดยค่าเริ่มต้น คุณต้องนำเข้าด้วยตนเอง:

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 21s, sys: 2.76 s, total: 1min 23s
Wall time: 1min 17s

นี่คือบางส่วนของคำศัพท์ที่ได้

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 57.6 s, sys: 2.34 s, total: 59.9 s
Wall time: 54.2 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 พร้อมแกน (batch, word, word-piece) :

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

การปรับแต่งและการส่งออก

บทช่วยสอนนี้สร้างข้อความ tokenizer และ detokenizer ที่ใช้โดยบทช่วยสอน Transformer ส่วนนี้จะเพิ่มวิธีการและขั้นตอนการประมวลผลเพื่อทำให้บทช่วยสอนนั้นง่ายขึ้น และส่งออก tokenizers โดยใช้ tf.saved_model เพื่อให้สามารถนำเข้าโดยบทช่วยสอนอื่น ๆ

การปรับโทเค็นเอง Custom

บทแนะนำดาวน์สตรีมทั้งสองคาดหวังว่าข้อความที่แปลงเป็นโทเค็นจะรวมโทเค็น [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)>

การล้างพิษแบบกำหนดเอง

ก่อนส่งออก tokenizers มีสองสิ่งที่คุณสามารถล้างข้อมูลสำหรับบทช่วยสอนดาวน์สตรีม:

  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 wrappers ที่จำเป็นสำหรับการส่งออก

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

ส่งออก tokenizers เป็น 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/variables/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.data-00000-of-00001 (deflated 51%)
  adding: ted_hrlr_translate_pt_en_converter/variables/variables.index (deflated 33%)
  adding: ted_hrlr_translate_pt_en_converter/assets/ (stored 0%)
  adding: ted_hrlr_translate_pt_en_converter/assets/pt_vocab.txt (deflated 57%)
  adding: ted_hrlr_translate_pt_en_converter/assets/en_vocab.txt (deflated 54%)
  adding: ted_hrlr_translate_pt_en_converter/saved_model.pb (deflated 91%)
du -h *.zip
184K    ted_hrlr_translate_pt_en_converter.zip

ทางเลือก: อัลกอริทึม

เป็นที่น่าสังเกตว่ามีอัลกอริธึม WordPiece สองเวอร์ชัน: จากล่างขึ้นบนและบนลงล่าง ในทั้งสองกรณี เป้าหมายจะเหมือนกัน: "จากคลังข้อมูลการฝึกอบรมและโทเค็นที่ต้องการจำนวนหนึ่ง ปัญหาการเพิ่มประสิทธิภาพคือการเลือกชิ้นคำ D เพื่อให้คลังข้อมูลผลลัพธ์มีจำนวนน้อยที่สุดเมื่อแบ่งกลุ่มตามแบบจำลองชิ้นคำที่เลือก "

อัลกอริธึม WordPiece จากล่างขึ้นบนดั้งเดิม นั้นใช้ การเข้ารหัสคู่ไบต์ เช่นเดียวกับ BPE มันเริ่มต้นด้วยตัวอักษรและรวม bigrams ทั่วไปซ้ำ ๆ เพื่อสร้างคำและคำ

เครื่องมือสร้างคำศัพท์ของ 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. เก็บ s ใดๆ ที่มี a c > T เช่น ถ้า T = 100 และเรามี (pers, 231); (dogs, 259); (##rint; 76) เราก็จะเก็บ pers and dogs ไว้
  6. เมื่อเก็บ s ไว้ ให้ลบการนับออกจากส่วนนำหน้าทั้งหมด นี่คือเหตุผลในการจัดเรียง s ตามความยาวในขั้นตอนที่ 4 นี่เป็นส่วนสำคัญของอัลกอริทึม เพราะไม่เช่นนั้น คำต่างๆ จะถูกนับซ้ำ ตัวอย่างเช่น สมมติว่าเรารักษา human ไว้และเราไปถึง (huma, 116) เรารู้ว่า 113 คนจาก 116 คนมาจาก 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 จะไม่ถูกนำไปใช้

เหตุใดจึงไม่ลบการนับสำหรับทุก สตริงย่อย ไม่ใช่แค่ทุก คำนำหน้า เพราะจากนั้นเราสามารถลบการนับออกได้หลายครั้ง สมมติว่าเรากำลังประมวลผล s ของความยาว 5 และเราเก็บทั้ง (##denia, 129) และ (##eniab, 137) โดยที่ 65 ค่าเหล่านั้นมาจากคำว่า undeniable ได้ ถ้าเราลบออกจาก ทุก สตริงย่อย เราจะลบ 65 จากสตริงย่อย ##enia สองครั้ง แม้ว่าเราควรลบเพียงครั้งเดียว อย่างไรก็ตาม หากเราลบเฉพาะคำนำหน้า จะถูกลบเพียงครั้งเดียวเท่านั้น

ครั้งที่สอง (และสาม ... ) การวนซ้ำ

เพื่อแก้ปัญหา overgeneration ที่กล่าวถึงข้างต้น เราทำอัลกอริธึมซ้ำหลายครั้ง

การวนซ้ำครั้งต่อมาจะเหมือนกับครั้งแรก โดยมีความแตกต่างที่สำคัญอย่างหนึ่ง: ในขั้นตอนที่ 2 แทนที่จะพิจารณา ทุก สตริงย่อย เราใช้อัลกอริธึมการแปลงโทเค็น WordPiece โดยใช้คำศัพท์จากการทำซ้ำครั้งก่อน และพิจารณาเฉพาะสตริงย่อยที่ เริ่มต้น จากจุดแยก

ตัวอย่างเช่น สมมติว่าเรากำลังดำเนินการขั้นตอนที่ 2 ของอัลกอริทึมและพบคำว่า undeniable ได้ ในการทำซ้ำครั้งแรก เราจะพิจารณาทุกสตริงย่อย เช่น {u, un, und, ..., undeniable, ##n, ##nd, ..., ##ndeniable, ...}

สำหรับการทำซ้ำครั้งที่สอง เราจะพิจารณาเฉพาะส่วนย่อยของสิ่งเหล่านี้ สมมติว่าหลังจากการวนซ้ำครั้งแรก ชิ้นคำที่เกี่ยวข้องคือ:

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

อัลกอริธึม WordPiece จะแบ่งส่วนนี้เป็น un ##deni ##able (ดูข้อมูลเพิ่มเติมในส่วน การใช้ WordPiece ) ในกรณีนี้ เราจะพิจารณาเฉพาะสตริงย่อยที่ เริ่มต้น ที่จุดการแบ่งส่วน เราจะยังคงพิจารณาทุกตำแหน่ง สิ้นสุดที่ เป็นไปได้ ดังนั้นในระหว่างการทำซ้ำครั้งที่สอง เซตของ s สำหรับการ undeniable คือ:

{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 ได้

ก่อนอื่นเราค้นหา undeniable ในพจนานุกรม WordPiece ของเรา และหากมี แสดงว่าเสร็จแล้ว หากไม่เป็นเช่นนั้น เราจะลดจุดสิ้นสุดหนึ่งอักขระ และทำซ้ำ เช่น undeniabl ได้

ในที่สุด เราจะพบโทเค็นย่อยในคำศัพท์ของเรา หรือค้นหาโทเค็นย่อยที่มีอักขระตัวเดียว (โดยทั่วไปแล้วเราคิดว่าตัวละครทุกตัวที่อยู่ในคำศัพท์ของเราถึงแม้ว่าอาจจะไม่เป็นกรณีสำหรับอักขระ Unicode ที่หายาก. ถ้าเราพบอักขระ Unicode ที่หายากที่ไม่ได้อยู่ในคำศัพท์ที่เราก็ map คำทั้ง <unk> )

ในกรณีนี้ เราพบ un ในคำศัพท์ของเรา นั่นคือประโยคแรกของเรา จากนั้นเราข้ามไปที่จุดสิ้นสุดของ un และประมวลผลซ้ำ เช่น พยายามค้นหา ##deniable จากนั้น ##deniabl เป็นต้น สิ่งนี้จะทำซ้ำจนกว่าเราจะแบ่งคำทั้งหมด

ปรีชา

ตามสัญชาตญาณ โทเค็น WordPiece พยายามตอบสนองวัตถุประสงค์สองประการที่แตกต่างกัน:

  1. tokenize ข้อมูลลงในจำนวนน้อยชิ้นที่สุดเท่าที่ทำได้ สิ่งสำคัญคือต้องจำไว้ว่าอัลกอริธึม WordPiece ไม่ได้ "ต้องการ" แยกคำ มิฉะนั้น มันจะแยกทุกคำเป็นอักขระ เช่น human -> {h, ##u, ##m, ##a, #n} นี่เป็นสิ่งสำคัญอย่างหนึ่งที่ทำให้ WordPiece แตกต่างจากตัวแยกทางสัณฐานวิทยา ซึ่งจะแยกหน่วยคำทางภาษาแม้กระทั่งสำหรับคำทั่วไป (เช่น unwanted -> {un, want, ed} )

  2. เมื่อต้องแยกคำออกเป็นชิ้น ๆ ให้แบ่งออกเป็นส่วน ๆ ที่มีจำนวนมากที่สุดในข้อมูลการฝึก ตัวอย่างเช่น เหตุผลที่คำ undeniable ได้ถูกแบ่งออกเป็น {un, ##deni, ##able} แทนที่จะเป็นทางเลือกอื่นเช่น {unde, ##niab, ##le} คือการนับสำหรับ un และ ##able ใน โดยเฉพาะอย่างยิ่งจะสูงมาก เนื่องจากสิ่งเหล่านี้เป็นคำนำหน้าและคำต่อท้ายทั่วไป แม้ว่าจำนวนสำหรับ ##le จะต้องมากกว่า ##able การนับ unde และ ##niab จะทำให้การ ##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)

ตอนนี้คุณสามารถเข้าถึงตารางค้นหาที่ใช้ใน tokenizer ได้โดยตรง

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)