Pemrosesan BERT dengan Teks TF

Lihat di TensorFlow.org Jalankan di Google Colab Lihat di GitHub Unduh buku catatan

Ringkasan

Prapemrosesan teks adalah transformasi ujung-ke-ujung dari teks mentah menjadi input integer model. Model NLP sering disertai dengan beberapa ratus (jika bukan ribuan) baris kode Python untuk teks prapemrosesan. Pemrosesan awal teks sering kali menjadi tantangan bagi model karena:

  • Kemiringan penyajian pelatihan. Menjadi semakin sulit untuk memastikan bahwa logika pemrosesan awal dari input model konsisten di semua tahap pengembangan model (misalnya, prapelatihan, penyempurnaan, evaluasi, inferensi). Menggunakan hyperparameter yang berbeda, tokenisasi, algoritme prapemrosesan string, atau sekadar mengemas input model secara tidak konsisten pada tahapan yang berbeda dapat menghasilkan efek yang sulit di-debug dan membawa bencana pada model.

  • Efisiensi dan fleksibilitas. Sementara prapemrosesan dapat dilakukan secara offline (misalnya dengan menuliskan keluaran yang diproses ke file pada disk dan kemudian menggunakan kembali data praproses tersebut dalam saluran masukan), metode ini menimbulkan biaya baca dan tulis berkas tambahan. Preprocessing offline juga merepotkan jika ada keputusan preprocessing yang perlu terjadi secara dinamis. Bereksperimen dengan opsi yang berbeda akan membutuhkan regenerasi dataset lagi.

  • Antarmuka model yang kompleks. Model teks jauh lebih mudah dipahami jika inputnya berupa teks murni. Sulit untuk memahami model ketika inputnya memerlukan langkah penyandian tidak langsung tambahan. Mengurangi kompleksitas prapemrosesan sangat dihargai untuk debugging model, penyajian, dan evaluasi.

Selain itu, antarmuka model yang lebih sederhana juga membuatnya lebih nyaman untuk mencoba model (misalnya inferensi atau pelatihan) pada kumpulan data berbeda yang belum dijelajahi.

Pemrosesan teks dengan TF.Text

Dengan menggunakan API prapemrosesan teks TF.Text, kita dapat membuat fungsi prapemrosesan yang dapat mengubah kumpulan data teks pengguna menjadi input integer model. Pengguna dapat mengemas preprocessing secara langsung sebagai bagian dari model mereka untuk mengatasi masalah yang disebutkan di atas.

Tutorial ini akan menunjukkan bagaimana menggunakan TF.Text preprocessing ops untuk mengubah data teks ke masukan untuk model Bert dan masukan untuk bahasa masking sebelum pelatihan tugas yang dijelaskan dalam "Masked LM dan Masking Prosedur" dari Bert: Pre-pelatihan dari Deep dua arah Transformers untuk Bahasa pemahaman . Prosesnya melibatkan teks tokenizing ke dalam unit subword, menggabungkan kalimat, pemangkasan konten ke ukuran tetap dan mengekstrak label untuk tugas pemodelan bahasa bertopeng.

Mempersiapkan

Mari impor paket dan perpustakaan yang kita butuhkan terlebih dahulu.

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

Data kami berisi dua fitur teks dan kita bisa membuat contoh tf.data.Dataset . Tujuan kami adalah untuk menciptakan sebuah fungsi yang kami dapat menyediakan Dataset.map() dengan yang akan digunakan dalam pelatihan.

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.'>}

Tokenisasi

Langkah pertama kami adalah menjalankan preprocessing string dan tokenize dataset kami. Hal ini dapat dilakukan dengan menggunakan text.BertTokenizer , yang merupakan text.Splitter yang dapat tokenize kalimat ke subwords atau wordpieces untuk model yang Bert diberi kosakata yang dihasilkan dari algoritma Wordpiece . Anda dapat mempelajari lebih lanjut tentang tokenizers subword lain yang tersedia di TF.Text dari sini .

Kosakata dapat berasal dari pos pemeriksaan BERT yang dibuat sebelumnya, atau Anda dapat membuatnya sendiri pada data Anda sendiri. Untuk tujuan contoh ini, mari kita buat kosakata mainan:

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

Mari membangun sebuah text.BertTokenizer menggunakan kosakata di atas dan tokenize input teks ke dalam 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']]]>

Teks output dari text.BertTokenizer memungkinkan kita melihat bagaimana teks sedang tokenized, tetapi model tersebut membutuhkan ID integer. Kita dapat mengatur token_out_type param untuk tf.int64 untuk mendapatkan bilangan bulat ID (yang merupakan indeks ke kosa kata).

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 mengembalikan RaggedTensor dengan bentuk [batch, num_tokens, num_wordpieces] . Karena kita tidak perlu ekstra num_tokens dimensi untuk kasus penggunaan kami saat ini, kita dapat menggabungkan dua dimensi terakhir untuk mendapatkan RaggedTensor dengan bentuk [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]]>

Pemangkasan Konten

Input utama untuk BERT adalah gabungan dari dua kalimat. Namun, BERT membutuhkan input dalam ukuran dan bentuk yang tetap dan kami mungkin memiliki konten yang melebihi anggaran kami.

Kita bisa mengatasi ini dengan menggunakan text.Trimmer untuk memangkas konten kami untuk ukuran yang telah ditentukan (setelah digabungkan sepanjang sumbu terakhir). Ada yang berbeda text.Trimmer jenis yang memilih konten untuk melestarikan menggunakan algoritma yang berbeda. text.RoundRobinTrimmer misalnya akan mengalokasikan kuota sama untuk setiap segmen tetapi mungkin trim ujung-ujung kalimat. text.WaterfallTrimmer akan memangkas mulai dari akhir kalimat terakhir.

Sebagai contoh, kita akan menggunakan RoundRobinTrimmer yang menyeleksi item dari setiap segmen dengan cara kiri ke kanan.

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 sekarang berisi segmen di mana jumlah elemen di batch adalah 8 unsur (ketika digabungkan sepanjang sumbu = -1).

Menggabungkan segmen

Sekarang kita telah segmen dipangkas, kita bisa menggabungkan mereka bersama-sama untuk mendapatkan satu RaggedTensor . Bert menggunakan token khusus untuk menunjukkan awal ( [CLS] ) dan akhir segmen ( [SEP] ). Kami juga membutuhkan RaggedTensor yang menunjukkan item dalam gabungan Tensor milik yang segmen. Kita dapat menggunakan text.combine_segments() untuk mendapatkan kedua hal ini Tensor dengan token khusus dimasukkan.

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

Tugas Model Bahasa Bertopeng

Sekarang kita memiliki input dasar kita, kita bisa mulai untuk mengekstrak masukan yang dibutuhkan untuk "Masked LM dan Masking Prosedur" tugas dijelaskan dalam Bert: Pre-pelatihan dari Deep dua arah Transformers untuk Memahami Bahasa

Tugas model bahasa bertopeng memiliki dua sub-masalah untuk kita pikirkan: (1) item apa yang harus dipilih untuk penyembunyian dan (2) nilai apa yang diberikan?

Pemilihan Barang

Karena kita akan memilih untuk memilih item secara acak untuk masking, kita akan menggunakan text.RandomItemSelector . RandomItemSelector secara acak memilih item dalam sebuah topik batch untuk pembatasan yang diberikan ( max_selections_per_batch , selection_rate dan unselectable_ids ) dan kembali masker boolean yang menunjukkan item yang dipilih.

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]]>

Memilih Nilai Bertopeng

Metodologi yang dijelaskan kertas BERT asli untuk memilih nilai untuk masking adalah sebagai berikut:

Untuk mask_token_rate waktu, mengganti item dengan [MASK] tanda:

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

Untuk random_token_rate waktu, mengganti item dengan kata acak:

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

Untuk 1 - mask_token_rate - random_token_rate waktu, menjaga item tidak berubah:

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

text.MaskedValuesChooser merangkum logika ini dan dapat digunakan untuk fungsi preprocessing kami. Berikut ini adalah contoh dari apa yang MaskValuesChooser kembali diberi mask_token_rate dari 80% dan standar 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]]>

Ketika dilengkapi dengan RaggedTensor input, text.MaskValuesChooser mengembalikan RaggedTensor dari bentuk yang sama dengan baik _MASK_VALUE (0), ID acak, atau id tidak berubah sama.

Menghasilkan Input untuk Tugas Model Bahasa Bertopeng

Sekarang bahwa kita memiliki RandomItemSelector untuk membantu kita memilih item untuk masking dan text.MaskValuesChooser untuk menetapkan nilai-nilai, kita dapat menggunakan text.mask_language_model() untuk merakit semua masukan dari tugas ini untuk model Bert kami.

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.

Menyelam Mari lebih dalam dan memeriksa output dari mask_language_model() . Output dari masked_token_ids adalah:

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]]>

Ingat bahwa input kita dikodekan menggunakan kosakata. Jika kita decode masked_token_ids menggunakan kosa kata kita, kita mendapatkan:

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

Perhatikan bahwa beberapa token wordpiece telah diganti dengan baik [MASK] , [RANDOM] atau nilai ID yang berbeda. masked_pos keluaran memberi kita indeks (dalam batch masing-masing) dari token yang telah diganti.

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

masked_lm_ids memberi kita nilai asli dari token.

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

Kami kembali dapat memecahkan kode ID di sini untuk mendapatkan nilai yang dapat dibaca manusia.

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

Masukan Model Padding

Sekarang bahwa kita memiliki semua input untuk model kami, langkah terakhir dalam preprocessing kami adalah untuk paket mereka ke dalam fixed 2-dimensi Tensor s dengan padding dan juga menghasilkan topeng Tensor menunjukkan nilai-nilai yang nilai pad. Kita dapat menggunakan text.pad_model_inputs() untuk membantu kami dengan tugas ini.

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

Tinjauan

Mari kita tinjau apa yang kita miliki sejauh ini dan kumpulkan fungsi preprocessing kita. Inilah yang kami miliki:

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

Kami sebelumnya dibangun tf.data.Dataset dan kita sekarang dapat menggunakan kami berkumpul preprocessing fungsi bert_pretrain_preprocess() di Dataset.map() . Ini memungkinkan kita untuk membuat saluran input untuk mengubah data string mentah kita menjadi input integer dan mengumpankan langsung ke model kita.

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]])>}