Google I / O가 5 월 18 ~ 20 일에 돌아옵니다! 공간을 예약하고 일정을 짜세요 지금 등록하세요

서브 워드 토크 나이저

TensorFlow.org에서보기 Google Colab에서 실행 GitHub에서 소스보기 노트북 다운로드

이 튜토리얼 데이터 세트에서 subword 어휘를 생성하고 구축하는 데 사용하는 방법을 보여줍니다 text.BertTokenizer 어휘에서.

하위 단어 토크 나이저의 주요 장점은 단어 기반 토큰 화와 문자 기반 토큰 화 사이를 보간한다는 것입니다. 일반적인 단어는 어휘에 슬롯이 있지만 토크 나이 저는 알 수없는 단어에 대해 단어 조각과 개별 문자로 대체 할 수 있습니다.

개요

tensorflow_text 패키지에는 많은 일반적인 토크 나이저의 TensorFlow 구현이 포함되어 있습니다. 여기에는 세 개의 하위 단어 스타일 토크 나이저가 포함됩니다.

  • text.BertTokenizer - BertTokenizer 클래스는 더 높은 수준의 인터페이스입니다. 여기에는 BERT의 토큰 분할 알고리즘과 WordPieceTokenizer 됩니다. 문장 을 입력으로 받아 토큰 ID를 반환 합니다 .
  • text.WordpeiceTokenizer - WordPieceTokenizer 클래스는 하위 수준 인터페이스입니다. WordPiece 알고리즘 만 구현합니다. 텍스트를 호출하기 전에 표준화하고 단어로 분할해야합니다. 단어 를 입력으로 받아 토큰 ID를 반환합니다.
  • text.SentencepieceTokenizer - SentencepieceTokenizer 는 더 복잡한 설정이 필요합니다. 이니셜 라이저에는 사전 훈련 된 문장 모델이 필요합니다. 이러한 모델 중 하나를 빌드하는 방법에 대한 지침은 google / sentencepiece 저장소 를 참조하세요. 토큰화할 때 문장 을 입력으로받을 수 있습니다.

이 튜토리얼은 기존 단어에서 시작하여 하향식 방식으로 Wordpiece 어휘를 빌드합니다. 일본어, 중국어 또는 한국어에는 명확한 다중 문자 단위가 없기 때문에이 프로세스는 작동하지 않습니다. 이러한 언어를 토큰 화하려면 text.SentencepieceTokenizer , text.UnicodeCharTokenizer 또는 이 접근 방식을 사용하는 것이 text.SentencepieceTokenizer .

설정

pip install -q tensorflow_datasets
# `BertTokenizer.detokenize` is not in `tf-text` stable yet (currently 2.4.3).
pip install -q tensorflow_text_nightly
# tf-text-nightly resquires tf-nightly
pip install -q tf-nightly
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)

어휘 생성

이 섹션에서는 데이터 세트에서 단어 조각 어휘를 생성합니다. 이미 어휘 파일이 있고 text.BertTokenizer 또는 text.Wordpiece 토크 나이저를 빌드하는 방법 text.Wordpiece 토크 나이저 빌드 섹션으로 건너 뛸 수 있습니다.

어휘 생성 코드는 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: 3.67 s, total: 1min 27s
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 1min, sys: 2.49 s, total: 1min 3s
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 메소드를 통해 실행하십시오. 처음에는 축 (batch, word, word-piece) 이있는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 사용)으로 tf.gather 첫 번째 예에서 "searchability""serendipity" "searchability" 단어가 "search ##ability""s ##ere ##nd ##ip ##ity" 로 분해되었음을 알 "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)

수출

다음 코드 블록은 내보내기에 필요한 text.BertTokenizer 인스턴스, 사용자 지정 논리 및 @tf.function 래퍼를 포함하는 CustomTokenizer 클래스를 빌드합니다.

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 다시로드하고 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 알고리즘에는 Bottom-up과 top-down의 두 가지 버전이 있다는 점에 주목할 가치가 있습니다. 두 경우 모두 목표는 동일합니다. "훈련 말뭉치와 원하는 토큰 D의 수가 주어지면 최적화 문제는 선택한 단어 조각 모델에 따라 분할 될 때 결과 말뭉치 수가 최소가되도록 D 단어 조각을 선택하는 것입니다. "

원래상향식 WordPiece 알고리즘바이트 쌍 인코딩을 기반으로합니다. BPE와 마찬가지로 알파벳으로 시작하고 반복적으로 공통 바이그램을 결합하여 단어 조각과 단어를 형성합니다.

TensorFlow Text의 어휘 생성기는 BERT의 하향식 구현을 따릅니다. 단어로 시작하여 빈도 임계 값에 도달 할 때까지 더 작은 구성 요소로 분할하거나 더 이상 분할 할 수 없습니다. 다음 섹션에서는 이에 대해 자세히 설명합니다. 일본어, 중국어 및 한국어의 경우 시작할 명시적인 단어 단위가 없기 때문에이 하향식 접근 방식이 작동하지 않습니다. 이들에게는 다른 접근 방식 이 필요합니다.

어휘 선택

하향식 WordPiece 생성 알고리즘은 (단어, 개수) 쌍 집합과 임계 값 T 가져 와서 어휘 V 반환합니다.

알고리즘은 반복적입니다. 일반적으로 k = 4k 반복에 대해 실행되지만 처음 두 개만 실제로 중요합니다. 세 번째와 네 번째 (및 그 이후)는 두 번째와 동일합니다. 이진 검색의 각 단계는 k 반복에 대해 처음부터 알고리즘을 실행합니다.

아래에 설명 된 반복 :

첫 번째 반복

  1. (w, c) 로 표시되는 입력의 모든 단어와 개수 쌍을 반복합니다.
  2. 각 단어 w 대해 s 로 표시된 모든 하위 문자열을 생성합니다. 예를 들어 human 이라는 단어 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 (humas, 3) 의 개수는 113+3=116 됩니다.
  4. 모든 하위 문자열의 개수를 수집 한 후에는 가장 긴 s 부터 시작 하여 (s, c) 쌍을 반복합니다.
  5. c > T 가있는 모든 s 를 유지하십시오. 예를 들어, T = 100 이고 우리는 (pers, 231); (dogs, 259); (##rint; 76) , 그러면 우리는 persdogs 유지합니다.
  6. s 가 유지되면 모든 접두사에서 개수를 뺍니다. 이것이 4 단계에서 모든 s 를 길이별로 정렬하는 이유입니다. 그렇지 않으면 단어가 이중으로 계산되기 때문에 이것은 알고리즘의 중요한 부분입니다. 예를 들어, 우리가 human 유지했고 (huma, 116) 도달했다고 가정 해 봅시다. 우리는 그 116113 humas human 에게서 humas 3humas 에서 humas 있습니다. 그러나 이제 human 이 우리 어휘에 있으므로 humanhuma ##n 으로 huma ##n 않을 것임을 알고 있습니다. 따라서 일단 human 이 유지되면 huma유효 개수는 3 입니다.

이 알고리즘은 단어 조각 세트 생성 s (전체 단어 일 것이다 많은 w 우리는 우리의 WordPiece 어휘로 사용할 수 있습니다).

그러나 문제가 있습니다.이 알고리즘은 단어 조각을 심각하게 과도하게 생성합니다. 그 이유는 접두사 토큰 수만 빼기 때문입니다. 따라서 human 이라는 단어를 유지하면 h, hu, hu, huma 의 개수는 제외되지만 ##u, ##um, ##uma, ##uman 등은 제외됩니다. 따라서 ##uman 이 적용되지 않더라도 human##uman 을 단어 조각으로 생성 할 수 있습니다.

그렇다면 모든 접두사 뿐만 아니라 모든 하위 문자열 의 개수를 빼지 않는 이유는 무엇입니까? 그러면 우리는 카운트에서 여러 번 뺄 수 있기 때문입니다. 길이가 5 인 s 를 처리하고 (##denia, 129)(##eniab, 137) 둘 다 유지한다고 가정 해 보겠습니다. 여기서 65undeniable 이라는 단어에서 나왔습니다. 모든 부분 문자열에서 빼면 한 번만 빼야하지만 부분 문자열 ##enia 에서 65 를 두 번 뺍니다. 그러나 접두사에서 빼면 한 번만 빼면됩니다.

두 번째 (및 세 번째 ...) 반복

위에서 언급 한 과잉 생성 문제를 해결하기 위해 알고리즘을 여러 번 반복합니다.

후속 반복은 첫 번째 반복과 동일하지만 한 가지 중요한 차이점이 있습니다. 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 단어를 분할하는 것을 고려하십시오.

먼저 WordPiece 사전에서 거부 할 undeniable 조회하고, 존재하는 경우 완료됩니다. 그렇지 않은 경우에는 끝점을 한 문자 씩 줄이고 반복합니다 (예 : undeniabl .

결국 우리는 어휘에서 하위 토큰을 찾거나 단일 문자 하위 토큰으로 내려갑니다. (일반적으로, 우리는이 힘이 드문 유니 코드 문자의 경우하지 않지만 모든 문자는, 우리의 어휘에 있다고 가정합니다. 우리는 어휘에없는 희귀 한 유니 코드 문자가 발생하면 우리는 단순히 단어 전체에 매핑 <unk> ).

이 경우 우리 어휘에서 un 을 찾습니다. 이것이 우리의 첫 번째 단어 조각입니다. 그런 다음 un 의 끝으로 이동하여 처리를 반복합니다. 예를 들어 ##deniable , ##deniabl 등을 찾습니다. 이것은 전체 단어를 분할 할 때까지 반복됩니다.

직관

직관적으로 WordPiece 토큰 화는 두 가지 다른 목표를 충족하려고합니다.

  1. 데이터를 가능한 한 최소한 의 조각으로 토큰 화하십시오. WordPiece 알고리즘은 단어를 분할하고 싶지 않다는 것을 명심하는 것이 중요합니다. 그렇지 않으면 모든 단어를 문자로 분할합니다 (예 : human -> {h, ##u, ##m, ##a, #n} . 이것은 WordPiece가 일반적인 단어 (예 : unwanted -> {un, want, ed} )에 대해서도 언어 적 형태소를 분리하는 형태 학적 분리기와 다른 중요한 요소 중 하나입니다.

  2. 단어를 조각으로 분할해야하는 경우 학습 데이터에서 최대 개수가있는 조각으로 분할합니다. 예를 들어 undeniable 이라는 단어가 {unde, ##niab, ##le} 과 같은 대안이 아닌 {un, ##deni, ##able} 로 분리되는 이유는 다음에서 un##able 의 개수가 이것은 일반적인 접두사 및 접미사이기 때문에 매우 높을 것입니다. ##le 의 개수가 ##able 보다 높아야하지만 unde##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)