TFテキストを使用したBERT前処理

TensorFlow.orgで表示GoogleColabで実行GitHubで表示 ノートブックをダウンロード

概要

テキストの前処理は、生のテキストをモデルの整数入力にエンドツーエンドで変換することです。 NLPモデルには、テキストを前処理するための数百行(数千行ではないにしても)のPythonコードが付随していることがよくあります。テキストの前処理は、多くの場合、モデルにとって課題となります。理由は次のとおりです。

  • トレーニング-スキューを提供します。モデルの入力の前処理ロジックがモデル開発のすべての段階(たとえば、事前トレーニング、微調整、評価、推論)で一貫していることを確認することはますます困難になっています。さまざまなハイパーパラメータ、トークン化、文字列前処理アルゴリズムを使用したり、モデル入力をさまざまな段階で一貫性のない形でパッケージ化したりすると、デバッグが困難で、モデルに壊滅的な影響を与える可能性があります。

  • 効率と柔軟性。前処理はオフラインで実行できますが(たとえば、処理済みの出力をディスク上のファイルに書き出してから、入力パイプラインで前処理済みのデータを再利用することにより)、この方法では追加のファイルの読み取りと書き込みのコストが発生します。動的に行う必要のある前処理の決定がある場合、オフラインでの前処理も不便です。別のオプションを試すには、データセットを再生成する必要があります。

  • 複雑なモデルインターフェース。入力が純粋なテキストである場合、テキストモデルははるかに理解しやすくなります。入力に追加の間接的なエンコード手順が必要な場合、モデルを理解するのは困難です。前処理の複雑さを軽減することは、モデルのデバッグ、提供、および評価で特に高く評価されます。

さらに、より単純なモデルインターフェイスにより、さまざまな未踏のデータセットでモデル(推論やトレーニングなど)を試すことがより便利になります。

TF.Textによるテキストの前処理

TF.Textのテキスト前処理APIを使用して、ユーザーのテキストデータセットをモデルの整数入力に変換できる前処理関数を構築できます。ユーザーは、モデルの一部として前処理を直接パッケージ化して、上記の問題を軽減できます。

このチュートリアルでは、言語は事前訓練タスクをマスキングするためのBERTモデルと入力の入力にテキストデータを変換するためにTF.Text前処理OPSを使用する方法を示しますの「仮面LMとマスキング手順」で説明したBERT:言語のためのディープ双方向変圧器の事前研修を理解。このプロセスには、テキストをサブワード単位にトークン化し、文を結合し、コンテンツを固定サイズにトリミングし、マスクされた言語モデリングタスクのラベルを抽出することが含まれます。

設定

最初に必要なパッケージとライブラリをインポートしましょう。

pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools

我々のデータは、2つのテキストの機能が含まれているし、我々は例を作成することができtf.data.Dataset 。私たちの目標は、私たちが供給できるという機能を作成することですDataset.map()訓練に使用されるようにして。

examples = {
    "text_a": [
      b"Sponge bob Squarepants is an Avenger",
      b"Marvel Avengers"
    ],
    "text_b": [
     b"Barack Obama is the President.",
     b"President is the highest office"
  ],
}

dataset = tf.data.Dataset.from_tensor_slices(examples)
next(iter(dataset))
{'text_a': <tf.Tensor: shape=(), dtype=string, numpy=b'Sponge bob Squarepants is an Avenger'>,
 'text_b': <tf.Tensor: shape=(), dtype=string, numpy=b'Barack Obama is the President.'>}

トークン化

最初のステップは、文字列の前処理を実行し、データセットをトークン化することです。これは、使用して行うことができるtext.BertTokenizerある、 text.Splitterためサブワード又はwordpiecesに文章をトークン化することができるBERTモデルから生成された語彙所与Wordpieceアルゴリズム。あなたはからTF.Textで利用可能な他のサブワードトークナイザについて詳しく学ぶことができ、ここで

語彙は、以前に生成されたBERTチェックポイントからのものにすることも、自分のデータに基づいて自分で生成することもできます。この例では、おもちゃの語彙を作成しましょう。

_VOCAB = [
    # Special tokens
    b"[UNK]", b"[MASK]", b"[RANDOM]", b"[CLS]", b"[SEP]",
    # Suffixes
    b"##ack", b"##ama", b"##ger", b"##gers", b"##onge", b"##pants",  b"##uare",
    b"##vel", b"##ven", b"an", b"A", b"Bar", b"Hates", b"Mar", b"Ob",
    b"Patrick", b"President", b"Sp", b"Sq", b"bob", b"box", b"has", b"highest",
    b"is", b"office", b"the",
]

_START_TOKEN = _VOCAB.index(b"[CLS]")
_END_TOKEN = _VOCAB.index(b"[SEP]")
_MASK_TOKEN = _VOCAB.index(b"[MASK]")
_RANDOM_TOKEN = _VOCAB.index(b"[RANDOM]")
_UNK_TOKEN = _VOCAB.index(b"[UNK]")
_MAX_SEQ_LEN = 8
_MAX_PREDICTIONS_PER_BATCH = 5

_VOCAB_SIZE = len(_VOCAB)

lookup_table = tf.lookup.StaticVocabularyTable(
    tf.lookup.KeyValueTensorInitializer(
      keys=_VOCAB,
      key_dtype=tf.string,
      values=tf.range(
          tf.size(_VOCAB, out_type=tf.int64), dtype=tf.int64),
      value_dtype=tf.int64),
      num_oov_buckets=1
)

レッツ・構造text.BertTokenizer上記の語彙を使用してにテキスト入力をトークン化RaggedTensor .`。

bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.string)
bert_tokenizer.tokenize(examples["text_a"])
<tf.RaggedTensor [[[b'Sp', b'##onge'], [b'bob'], [b'Sq', b'##uare', b'##pants'], [b'is'], [b'an'], [b'A', b'##ven', b'##ger']], [[b'Mar', b'##vel'], [b'A', b'##ven', b'##gers']]]>
bert_tokenizer.tokenize(examples["text_b"])
<tf.RaggedTensor [[[b'Bar', b'##ack'], [b'Ob', b'##ama'], [b'is'], [b'the'], [b'President'], [b'[UNK]']], [[b'President'], [b'is'], [b'the'], [b'highest'], [b'office']]]>

以下からのテキスト出力text.BertTokenizer私たちは、テキストをトークン化されているかを確認できますが、このモデルは、整数のIDが必要です。我々が設定することができtoken_out_typeにPARAMをtf.int64 (語彙へのインデックスである)のID整数得ました。

bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.int64)
segment_a = bert_tokenizer.tokenize(examples["text_a"])
segment_a
<tf.RaggedTensor [[[22, 9], [24], [23, 11, 10], [28], [14], [15, 13, 7]], [[18, 12], [15, 13, 8]]]>
segment_b = bert_tokenizer.tokenize(examples["text_b"])
segment_b
<tf.RaggedTensor [[[16, 5], [19, 6], [28], [30], [21], [0]], [[21], [28], [30], [27], [29]]]>

text.BertTokenizer返しRaggedTensor形状と[batch, num_tokens, num_wordpieces]私たちは余分な必要はありませんのでnum_tokens当社の現在のユースケースのための寸法を、我々は得ることが最後の二つの次元をマージすることができRaggedTensor形状で[batch, num_wordpieces]

segment_a = segment_a.merge_dims(-2, -1)
segment_a
<tf.RaggedTensor [[22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7], [18, 12, 15, 13, 8]]>
segment_b = segment_b.merge_dims(-2, -1)
segment_b
<tf.RaggedTensor [[16, 5, 19, 6, 28, 30, 21, 0], [21, 28, 30, 27, 29]]>

コンテンツのトリミング

BERTへの主な入力は、2つの文の連結です。ただし、BERTでは入力を固定サイズと形状にする必要があり、予算を超えるコンテンツが含まれる場合があります。

私たちは、使用してこれに取り組むことができtext.Trimmer (最後の軸に沿って連結後)所定の大きさに私たちのコンテンツダウンをトリミングします。異なるがあるtext.Trimmer異なるアルゴリズムを使用して保存するコンテンツを選択種類が。 text.RoundRobinTrimmer例えば、各セグメントに対して均等クォータを割り当てますが、文章の端部をトリミングしてもよいです。 text.WaterfallTrimmer最後の文の終わりから始まるトリムします。

私たちの例では、使用するRoundRobinTrimmer左から右への方法で各セグメントから項目を選択します。

trimmer = text.RoundRobinTrimmer(max_seq_length=[_MAX_SEQ_LEN])
trimmed = trimmer.trim([segment_a, segment_b])
trimmed
[<tf.RaggedTensor [[22, 9, 24, 23], [18, 12, 15, 13]]>,
 <tf.RaggedTensor [[16, 5, 19, 6], [21, 28, 30, 27]]>]

trimmed今バッチを横断要素の数は(軸= -1に沿って連結)8つの要素であるセグメントを含みます。

セグメントの組み合わせ

今、私たちは、セグメントがトリミングされていることを、我々は、単一取得するためにそれらを一緒に組み合わせることができRaggedTensor 。 BERTは、開始(示すために、特殊なトークンを使用して[CLS]とセグメント(の終わり[SEP] )。我々はまた、必要RaggedTensor組み合わせでどの項目を表すTensorどのセグメントに属します。我々は使用することができますtext.combine_segments()これらの両方を取得するためにTensor挿入し、特殊なトークンで。

segments_combined, segments_ids = text.combine_segments(
  [segment_a, segment_b],
  start_of_sequence_id=_START_TOKEN, end_of_segment_id=_END_TOKEN)
segments_combined, segments_ids
(<tf.RaggedTensor [[3, 22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 8, 4, 21, 28, 30, 27, 29, 4]]>,
 <tf.RaggedTensor [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]]>)

マスクされた言語モデルタスク

今、私たちは私たちの基本的な入力を持っていることを、私たちは「仮面LMとマスキング手順」のために必要な入力を抽出するために始めることができるタスクはで説明BERT:言語理解のためのディープ双方向変圧器の事前研修

マスクされた言語モデルタスクには、2つのサブ問題があります。(1)マスキング用に選択する項目と(2)それらに割り当てられている値です。

アイテムの選択

私たちは、マスキングのためのランダムにアイテムを選択することになるので、我々は、使用するtext.RandomItemSelectorRandomItemSelectorランダムに(所定の制限にバッチ対象の項目を選択max_selections_per_batchselection_rate及びunselectable_ids )と項目が選択されたかを示すブール値マスクを返します。

random_selector = text.RandomItemSelector(
    max_selections_per_batch=_MAX_PREDICTIONS_PER_BATCH,
    selection_rate=0.2,
    unselectable_ids=[_START_TOKEN, _END_TOKEN, _UNK_TOKEN]
)
selected = random_selector.get_selection_mask(
    segments_combined, axis=1)
selected
<tf.RaggedTensor [[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, True, True, True, False, False], [False, False, False, False, False, True, False, False, False, False, False, True, False]]>

マスクされた値の選択

マスキングの値を選択するための元のBERTペーパーを説明した方法論は、次のとおりです。

mask_token_rate時間の、とアイテムを交換[MASK]トークン:

"my dog is hairy" -> "my dog is [MASK]"

random_token_rate時間の、ランダムな単語とアイテムを交換します:

"my dog is hairy" -> "my dog is apple"

1 - mask_token_rate - random_token_rate時間の、変わらない項目を保ちます:

"my dog is hairy" -> "my dog is hairy."

text.MaskedValuesChooserこのロジックをカプセル化し、私たちの前処理機能のために使用することができます。ここでは何の例ですMaskValuesChooserリターンが与えられたmask_token_rate 80%のは、デフォルトrandom_token_rate

input_ids = tf.ragged.constant([[19, 7, 21, 20, 9, 8], [13, 4, 16, 5], [15, 10, 12, 11, 6]])
mask_values_chooser = text.MaskValuesChooser(_VOCAB_SIZE, _MASK_TOKEN, 0.8)
mask_values_chooser.get_mask_values(input_ids)
<tf.RaggedTensor [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1], [1, 10, 1, 1, 6]]>

が供給されるとRaggedTensor入力、 text.MaskValuesChooser返しRaggedTensorのいずれかと同じ形状の_MASK_VALUE (0)、ランダムID、または同一の不変IDを。

マスクされた言語モデルタスクの入力の生成

我々が持っていることを今RandomItemSelectorマスキングしてのための項目を選択する私たちを助けるためにtext.MaskValuesChooser値を割り当てるために、我々は使用することができますtext.mask_language_model()当社BERTモデルのために、このタスクのすべての入力を組み立てること。

masked_token_ids, masked_pos, masked_lm_ids = text.mask_language_model(
  segments_combined,
  item_selector=random_selector, mask_values_chooser=mask_values_chooser)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/dispatch.py:206: batch_gather (from tensorflow.python.ops.array_ops) is deprecated and will be removed after 2017-10-25.
Instructions for updating:
`tf.batch_gather` is deprecated, please use `tf.gather` with `batch_dims=-1` instead.

深いレッツ・ダイビングとの出力を調べるmask_language_model() 。出力masked_token_ids次のとおりです。

masked_token_ids
<tf.RaggedTensor [[3, 22, 1, 24, 23, 1, 10, 28, 1, 15, 1, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 1, 4, 21, 28, 30, 27, 1, 4]]>

入力は語彙を使用してエンコードされていることを忘れないでください。私たちが解読した場合masked_token_ids私たちの語彙を使用して、我々が得ます:

tf.gather(_VOCAB, masked_token_ids)
<tf.RaggedTensor [[b'[CLS]', b'Sp', b'[MASK]', b'bob', b'Sq', b'[MASK]', b'##pants', b'is', b'[MASK]', b'A', b'[MASK]', b'##ger', b'[SEP]', b'Bar', b'##ack', b'Ob', b'##ama', b'is', b'the', b'President', b'[UNK]', b'[SEP]'], [b'[CLS]', b'Mar', b'##vel', b'A', b'##ven', b'[MASK]', b'[SEP]', b'President', b'is', b'the', b'highest', b'[MASK]', b'[SEP]']]>

いくつかのwordpieceトークンは、いずれかで置換されていることをお知らせ[MASK] [RANDOM]または異なるID値。 masked_pos出力は、私たちに交換されたトークンの(それぞれのバッチ内)のインデックスを提供します。

masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>

masked_lm_ids私たちにトークンの元の値を与えます。

masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>

ここでもIDをデコードして、人間が読める値を取得できます。

tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>

パディングモデル入力

今、私たちは私たちのモデルのすべての入力を持っていることを、私たちの前の最後のステップは、固定された2次元にそれらをパッケージ化することであるTensorパディングとSともマスク生成Tensorパッド値である値を示しています。我々は使用することができますtext.pad_model_inputs()このタスクで私たちを助けるために。

# Prepare and pad combined segment inputs
input_word_ids, input_mask = text.pad_model_inputs(
  masked_token_ids, max_seq_length=_MAX_SEQ_LEN)
input_type_ids, _ = text.pad_model_inputs(
  masked_token_ids, max_seq_length=_MAX_SEQ_LEN)

# Prepare and pad masking task inputs
masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
  masked_token_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
masked_lm_ids, _ = text.pad_model_inputs(
  masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)

model_inputs = {
    "input_word_ids": input_word_ids,
    "input_mask": input_mask,
    "input_type_ids": input_type_ids,
    "masked_lm_ids": masked_lm_ids,
    "masked_lm_positions": masked_lm_positions,
    "masked_lm_weights": masked_lm_weights,
}
model_inputs
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  1, 24, 23,  1, 10, 28],
        [ 3, 18, 12, 15, 13,  1,  4, 21]])>,
 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1]])>,
 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  1, 24, 23,  1, 10, 28],
        [ 3, 18, 12, 15, 13,  1,  4, 21]])>,
 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[ 9, 11, 14, 13,  0],
        [ 8, 29,  0,  0,  0]])>,
 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[ 3, 22,  1, 24, 23],
        [ 3, 18, 12, 15, 13]])>,
 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]])>}

レビュー

これまでの内容を確認して、前処理関数を組み立てましょう。これが私たちが持っているものです:

def bert_pretrain_preprocess(vocab_table, features):
  # Input is a string Tensor of documents, shape [batch, 1].
  text_a = features["text_a"]
  text_b = features["text_b"]

  # Tokenize segments to shape [num_sentences, (num_words)] each.
  tokenizer = text.BertTokenizer(
      vocab_table,
      token_out_type=tf.int64)
  segments = [tokenizer.tokenize(text).merge_dims(
      1, -1) for text in (text_a, text_b)]

  # Truncate inputs to a maximum length.
  trimmer = text.RoundRobinTrimmer(max_seq_length=6)
  trimmed_segments = trimmer.trim(segments)

  # Combine segments, get segment ids and add special tokens.
  segments_combined, segment_ids = text.combine_segments(
      trimmed_segments,
      start_of_sequence_id=_START_TOKEN,
      end_of_segment_id=_END_TOKEN)

  # Apply dynamic masking task.
  masked_input_ids, masked_lm_positions, masked_lm_ids = (
      text.mask_language_model(
        segments_combined,
        random_selector,
        mask_values_chooser,
      )
  )

  # Prepare and pad combined segment inputs
  input_word_ids, input_mask = text.pad_model_inputs(
    masked_input_ids, max_seq_length=_MAX_SEQ_LEN)
  input_type_ids, _ = text.pad_model_inputs(
    masked_input_ids, max_seq_length=_MAX_SEQ_LEN)

  # Prepare and pad masking task inputs
  masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
    masked_input_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
  masked_lm_ids, _ = text.pad_model_inputs(
    masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)

  model_inputs = {
      "input_word_ids": input_word_ids,
      "input_mask": input_mask,
      "input_type_ids": input_type_ids,
      "masked_lm_ids": masked_lm_ids,
      "masked_lm_positions": masked_lm_positions,
      "masked_lm_weights": masked_lm_weights,
  }
  return model_inputs

我々は以前に構築tf.data.Dataset 、私たちは今、私達の、組み立て前処理機能を使用することができますbert_pretrain_preprocess()Dataset.map()これにより、生の文字列データを整数入力に変換し、モデルに直接フィードするための入力パイプラインを作成できます。

dataset = tf.data.Dataset.from_tensors(examples)
dataset = dataset.map(functools.partial(
    bert_pretrain_preprocess, lookup_table))

next(iter(dataset))
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  9,  1,  4, 16,  5, 19],
        [ 3, 18,  1, 15,  4,  1, 28, 30]])>,
 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1]])>,
 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  9,  1,  4, 16,  5, 19],
        [ 3, 18,  1, 15,  4,  1, 28, 30]])>,
 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[24, 19,  0,  0,  0],
        [12, 21,  0,  0,  0]])>,
 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[ 3, 22,  9,  1,  4],
        [ 3, 18,  1, 15,  4]])>,
 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]])>}
  • BERTと分類テキスト-分類テキストにpretrained BERTモデルを使用する方法についてのチュートリアル。これは、BERTモデルで使用される入力を前処理する方法に精通しているので、すばらしいフォローアップです。

  • TFテキストとトークン化-チュートリアルTF.Textに存在するトークナイザの異なる種類を詳述します。

  • テキストの取り扱いRaggedTensor - 、使用を作成および操作する方法についての詳細なガイドRaggedTensor秒。