אסימונים למילות משנה

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

הדרכה זו מדגימה כיצד ניתן ליצור אוצר מילים subword מן הנתונים, ולהשתמש בו כדי לבנות text.BertTokenizer מאוצר המילים.

היתרון העיקרי של טוקנייזר מילות משנה הוא בכך שהוא משלב בין טוקניזציה מבוססת מילים ומבוססת תווים. מילים נפוצות מקבלות משבצת באוצר המילים, אך הטוקנייזר יכול לחזור לחלקי מילים ולתווים בודדים עבור מילים לא ידועות.

סקירה כללית

tensorflow_text החבילה כוללת הטמעות TensorFlow של tokenizers הנפוצה רבה. זה כולל שלושה אסימונים בסגנון מילות משנה:

  • text.BertTokenizer - The BertTokenizer בכיתה הוא ממשק ברמה גבוהה. היא כוללת אלגוריתם פיצול האסימון של ברט לבין WordPieceTokenizer . זה לוקח משפטים כקלט ומחזירה-ID האסימון.
  • text.WordpieceTokenizer - The WordPieceTokenizer בכיתה הוא ממשק ברמה נמוכה. זה רק מיישם את אלגוריתם WordPiece . עליך לתקן ולפצל את הטקסט למילים לפני שתקרא לו. זה לוקח מילים כקלט ומחזירה-ID האסימון.
  • text.SentencepieceTokenizer - The SentencepieceTokenizer דורש התקנה מורכבת יותר. האתחול שלו דורש מודל משפטי מאומן מראש. עיין מאגר Google / sentencepiece כדי לקבל הוראות כיצד לבנות אחד מן המודלים האלה. זה יכול לקבל משפטים כקלט כאשר Tokenizing.

מדריך זה בונה אוצר מילים של Wordpiece בצורה מלמעלה למטה, החל ממילים קיימות. תהליך זה אינו עובד עבור יפנית, סינית או קוריאנית מכיוון שלשפות אלו אין יחידות מרובות תווים ברורות. כדי tokenize בשפות הבאות conside באמצעות 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 .

שימו לב לכמה דברים לגבי המשפטים לדוגמה למעלה:

  • הם באותיות קטנות.
  • יש רווחים מסביב לסימני הפיסוק.
  • לא ברור אם או באיזו נורמליזציה של Unicode נעשה שימוש.
train_en = train_examples.map(lambda pt, en: en)
train_pt = train_examples.map(lambda pt, en: pt)

צור את אוצר המילים

קטע זה יוצר אוצר מילים מתוך מערך נתונים. אם כבר יש לך קובץ אוצר מילים רק רוצה לראות איך לבנות text.BertTokenizer או text.Wordpiece tokenizer עם זה אז אתה יכול לדלג אל לבנות את tokenizer סעיף.

קוד דור אוצר המילים כלול 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 30s, sys: 2.21 s, total: 1min 32s
Wall time: 1min 28s

הנה כמה פרוסות מאוצר המילים שנוצר.

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 1min 3s, sys: 2.21 s, total: 1min 6s
Wall time: 1min 2s
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]

אם אתה מחליף את מזהי אסימון עם ייצוגי הטקסט שלהם (באמצעות 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 כך שניתן יהיה המיובאים על ידי מדריכים אחרים.

אסימון מותאם אישית

הדרכות במורד הזרם בשני לצפות בטקסט tokenized לכלול [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')

ייצוא tokenizers בתור saved_model :

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.saved_model.save(tokenizers, model_name)
2021-11-02 15:20:31.762976: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.

טען מחדש את 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/saved_model.pb (deflated 91%)
  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%)
du -h *.zip
184K    ted_hrlr_translate_pt_en_converter.zip

אופציונלי: האלגוריתם

ראוי לציין כאן שיש שתי גרסאות של אלגוריתם WordPiece: מלמטה למעלה ומלמעלה למטה. בשני המקרים המטרה היא זהה: "בהינתן קורפוס אימון ומספר אסימונים רצויים D, בעיית האופטימיזציה היא לבחור חלקי מילים D כך שהקורפוס המתקבל יהיה מינימלי במספר חלקי המילים כאשר מפולחים לפי מודל מילת המילים הנבחר. "

מקור אלגוריתם WordPiece מלמטה למעלה , מבוסס על קידוד-זוג בייט . כמו BPE, זה מתחיל באלפבית, ומשלב באופן איטרטיבי ביגרמות נפוצות ליצירת חלקי מילים ומילים.

TensorFlow גנרטור אוצר המילים של הטקסט מלווה את היישום מלמעלה למטה מן ברט . מתחילים במילים ומפרקים אותן לרכיבים קטנים יותר עד שהן מגיעות לסף התדר, או שלא ניתן לפרק אותן יותר. הסעיף הבא מתאר זאת בפירוט. עבור יפנית, סינית וקוריאנית גישה זו מלמעלה למטה לא עובדת מכיוון שאין יחידות מילים מפורשות מלכתחילה. למי אתה צריך גישה שונה .

בחירת אוצר המילים

החלק העליון כלפי מטה 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 כי יש c > T . לדוגמא, אם T = 100 ויש לנו (pers, 231); (dogs, 259); (##rint; 76) , אז היינו לשמור pers ו 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 פעמים, למרות שאנחנו צריכים להחסיר פעם אחת בלבד. עם זאת, אם נגרע רק מקידומות, זה ייגרע בצורה נכונה רק פעם אחת.

איטרציה שנייה (ושלישית...).

כדי לפתור את בעיית יצירת היתר שהוזכרה לעיל, אנו מבצעים איטרציות מרובות של האלגוריתם.

חזרות עוקבות זהה לראשון, עם הבחנה חשובה אחת: בשלב 2, במקום בהתחשב בכל מחרוזת, אנו מיישמים את האלגוריתם tokenization 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}

אחרת האלגוריתם זהה. בדוגמה זו, את הגרסה הראשונה, אלגוריתם מייצר את suprious אסימונים ##ndeni ו ##iable . כעת, האסימונים הללו לעולם אינם נחשבים, ולכן הם לא יופקו באיטרציה השנייה. אנו מבצעים מספר איטרציות רק כדי לוודא שהתוצאות מתכנסות (למרות שאין ערובה להתכנסות מילולית).

החלת WordPiece

לאחר שנוצר אוצר מילים של WordPiece, עלינו להיות מסוגלים להחיל אותו על נתונים חדשים. האלגוריתם הוא יישום פשוט חמדני-ההתאמה הארוכה ביותר.

לדוגמה, שקול פילוח המילה undeniable .

אנחנו הראשונים בדיקת undeniable במילון WordPiece שלנו, ואם ההווה של אותו, סיימנו. אם לא, אנחנו הפחתה נקודת הסיום על ידי דמות אחת, וחזור, למשל, undeniabl .

בסופו של דבר, או שנמצא תת-אסימון באוצר המילים שלנו, או שנרד לתת-אסימון של תו בודד. (באופן כללי, אנו מניחים כי כל דמות נמצאת באוצר המילים שלנו, למרות עוצמתו זה לא יהיה המקרה עבור תווי Unicode נדיר. אם אנו נתקלים תו יוניקוד נדיר זה לא בלקסיקון אנחנו פשוט למפות את המילה כולה <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 תעשינה את זה tokenization "רצוי" פחות לאלגוריתם.

אופציונלי: 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)