API SavedModel Umum untuk Tugas Teks

Halaman ini menjelaskan bagaimana TF2 SavedModels untuk tugas terkait teks harus mengimplementasikan Reusable SavedModel API . (Ini menggantikan dan memperluas Tanda Tangan Umum untuk Teks untuk format Hub TF1 yang sekarang sudah tidak digunakan lagi.)

Ringkasan

Ada beberapa API untuk menghitung penyematan teks (juga dikenal sebagai representasi teks padat, atau vektor fitur teks).

  • API untuk penyematan teks dari input teks diimplementasikan oleh SavedModel yang memetakan kumpulan string ke kumpulan vektor penyematan. Ini sangat mudah digunakan, dan banyak model di TF Hub yang telah menerapkannya. Namun, hal ini tidak memungkinkan penyesuaian model pada TPU.

  • API untuk penyematan teks dengan masukan yang telah diproses sebelumnya menyelesaikan tugas yang sama, tetapi diimplementasikan oleh dua SavedModel terpisah:

    • sebuah praprosesor yang dapat berjalan di dalam saluran masukan tf.data dan mengubah string dan data panjang variabel lainnya menjadi Tensor numerik,
    • sebuah pembuat enkode yang menerima hasil praprosesor dan melakukan bagian komputasi penyematan yang dapat dilatih.

    Pemisahan ini memungkinkan input diproses terlebih dahulu secara asinkron sebelum dimasukkan ke dalam loop pelatihan. Secara khusus, ini memungkinkan pembuatan encoder yang dapat dijalankan dan disempurnakan di TPU .

  • API untuk penyematan teks dengan pembuat enkode Transformer memperluas API untuk penyematan teks dari masukan yang telah diproses sebelumnya ke kasus khusus BERT dan pembuat enkode Transformer lainnya.

    • Praprosesor diperluas untuk membuat masukan encoder dari lebih dari satu segmen teks masukan.
    • Encoder Transformer memperlihatkan penyematan token individual yang peka konteks.

Dalam setiap kasus, input teks adalah string berkode UTF-8, biasanya berupa teks biasa, kecuali dokumentasi model menentukan lain.

Terlepas dari API-nya, berbagai model telah dilatih sebelumnya tentang teks dari berbagai bahasa dan domain, dan dengan tugas yang berbeda pula. Oleh karena itu, tidak semua model penyematan teks cocok untuk setiap masalah.

Penyematan Teks dari Input Teks

SavedModel untuk penyematan teks dari masukan teks menerima kumpulan masukan dalam string Tensor berbentuk [batch_size] dan memetakannya ke Tensor bentuk [batch_size, dim] float32 dengan representasi padat (vektor fitur) dari masukan.

Sinopsis penggunaan

obj = hub.load("path/to/model")
text_input = ["A long sentence.",
              "single-word",
              "http://example.com"]
embeddings = obj(text_input)

Ingat dari Reusable SavedModel API bahwa menjalankan model dalam mode pelatihan (misalnya, untuk dropout) mungkin memerlukan argumen kata kunci obj(..., training=True) , dan obj menyediakan atribut .variables , .trainable_variables dan .regularization_losses sebagaimana berlaku .

Di Keras, semua ini diurus oleh

embeddings = hub.KerasLayer("path/to/model", trainable=...)(text_input)

Pelatihan terdistribusi

Jika penyematan teks digunakan sebagai bagian dari model yang dilatih dengan strategi distribusi, panggilan ke hub.load("path/to/model") atau hub.KerasLayer("path/to/model", ...) , resp., harus terjadi di dalam lingkup DistributionStrategy untuk membuat variabel model dengan cara terdistribusi. Misalnya

  with strategy.scope():
    ...
    model = hub.load("path/to/model")
    ...

Contoh

Penyematan Teks dengan Input yang Telah Diproses Sebelumnya

Penyematan teks dengan input yang telah diproses sebelumnya diimplementasikan oleh dua SavedModel terpisah:

  • sebuah praprosesor yang memetakan string Tensor berbentuk [batch_size] ke dict Tensor numerik,
  • sebuah encoder yang menerima dict Tensor seperti yang dikembalikan oleh praprosesor, melakukan bagian komputasi penyematan yang dapat dilatih, dan mengembalikan dict output. Output di bawah kunci "default" adalah Tensor float32 berbentuk [batch_size, dim] .

Hal ini memungkinkan untuk menjalankan praprosesor dalam saluran masukan tetapi menyempurnakan penyematan yang dihitung oleh pembuat enkode sebagai bagian dari model yang lebih besar. Secara khusus, ini memungkinkan pembuatan pembuat enkode yang dapat dijalankan dan disempurnakan di TPU .

Ini adalah detail implementasi Tensor mana yang terdapat dalam keluaran praprosesor, dan Tensor tambahan mana (jika ada) selain "default" yang terdapat dalam keluaran pembuat enkode.

Dokumentasi pembuat enkode harus menentukan praprosesor mana yang akan digunakan. Biasanya, hanya ada satu pilihan yang benar.

Sinopsis penggunaan

text_input = tf.constant(["A long sentence.",
                          "single-word",
                          "http://example.com"])
preprocessor = hub.load("path/to/preprocessor")  # Must match `encoder`.
encoder_inputs = preprocessor(text_input)

encoder = hub.load("path/to/encoder")
encoder_outputs = encoder(encoder_inputs)
embeddings = encoder_outputs["default"]

Ingat dari Reusable SavedModel API bahwa menjalankan encoder dalam mode pelatihan (misalnya, untuk dropout) mungkin memerlukan argumen kata kunci encoder(..., training=True) , dan encoder tersebut menyediakan atribut .variables , .trainable_variables dan .regularization_losses sebagaimana berlaku .

Model preprocessor mungkin memiliki .variables tetapi tidak dimaksudkan untuk dilatih lebih lanjut. Pemrosesan awal tidak bergantung pada mode: jika preprocessor() memiliki argumen training=... sama sekali, hal ini tidak berpengaruh.

Di Keras, semua ini diurus oleh

encoder_inputs = hub.KerasLayer("path/to/preprocessor")(text_input)
encoder_outputs = hub.KerasLayer("path/to/encoder", trainable=True)(encoder_inputs)
embeddings = encoder_outputs["default"]

Pelatihan terdistribusi

Jika encoder digunakan sebagai bagian dari model yang dilatih dengan strategi distribusi, panggilan ke hub.load("path/to/encoder") atau hub.KerasLayer("path/to/encoder", ...) , resp., harus terjadi di dalam

  with strategy.scope():
    ...

untuk membuat ulang variabel encoder dengan cara terdistribusi.

Demikian pula, jika praprosesor adalah bagian dari model yang dilatih (seperti pada contoh sederhana di atas), praprosesor juga perlu dimuat dalam cakupan strategi distribusi. Namun, jika praprosesor digunakan dalam pipeline masukan (misalnya, dalam callable yang diteruskan ke tf.data.Dataset.map() ), pemuatannya harus terjadi di luar cakupan strategi distribusi, untuk menempatkan variabelnya (jika ada ) pada CPU tuan rumah.

Contoh

Penyematan teks dengan Transformer Encoder

Pembuat enkode transformator untuk teks beroperasi pada sekumpulan rangkaian masukan, setiap rangkaian terdiri dari n ≥ 1 segmen teks yang diberi token, dalam beberapa batasan khusus model pada n . Untuk BERT dan banyak ekstensinya, batasannya adalah 2, sehingga mereka menerima segmen tunggal dan pasangan segmen.

API untuk penyematan teks dengan pembuat enkode Transformer memperluas API untuk penyematan teks dengan masukan yang telah diproses sebelumnya ke pengaturan ini.

Praprosesor

SavedModel praprosesor untuk penyematan teks dengan pembuat enkode Transformer mengimplementasikan API dari SavedModel praprosesor untuk penyematan teks dengan masukan yang telah diproses sebelumnya (lihat di atas), yang menyediakan cara untuk memetakan masukan teks segmen tunggal langsung ke masukan pembuat enkode.

Selain itu, praprosesor SavedModel menyediakan tokenize subobjek yang dapat dipanggil untuk tokenisasi (secara terpisah per segmen) dan bert_pack_inputs untuk mengemas n segmen yang diberi token ke dalam satu urutan masukan untuk pembuat enkode. Setiap subobjek mengikuti Reusable SavedModel API .

Sinopsis penggunaan

Sebagai contoh nyata untuk dua segmen teks, mari kita lihat tugas pengikatan kalimat yang menanyakan apakah premis (segmen pertama) menyiratkan hipotesis atau tidak (segmen kedua).

preprocessor = hub.load("path/to/preprocessor")

# Tokenize batches of both text inputs.
text_premises = tf.constant(["The quick brown fox jumped over the lazy dog.",
                             "Good day."])
tokenized_premises = preprocessor.tokenize(text_premises)
text_hypotheses = tf.constant(["The dog was lazy.",  # Implied.
                               "Axe handle!"])       # Not implied.
tokenized_hypotheses = preprocessor.tokenize(text_hypotheses)

# Pack input sequences for the Transformer encoder.
seq_length = 128
encoder_inputs = preprocessor.bert_pack_inputs(
    [tokenized_premises, tokenized_hypotheses],
    seq_length=seq_length)  # Optional argument.

Di Keras, perhitungan ini dapat dinyatakan sebagai

tokenize = hub.KerasLayer(preprocessor.tokenize)
tokenized_hypotheses = tokenize(text_hypotheses)
tokenized_premises = tokenize(text_premises)

bert_pack_inputs = hub.KerasLayer(
    preprocessor.bert_pack_inputs,
    arguments=dict(seq_length=seq_length))  # Optional argument.
encoder_inputs = bert_pack_inputs([tokenized_premises, tokenized_hypotheses])

Detail tokenize

Panggilan ke preprocessor.tokenize() menerima string Tensor berbentuk [batch_size] dan mengembalikan RaggedTensor berbentuk [batch_size, ...] yang nilainya berupa id token int32 yang mewakili string masukan. Mungkin ada r ≥ 1 dimensi acak-acakan setelah batch_size tetapi tidak ada dimensi seragam lainnya.

  • Jika r =1, bentuknya adalah [batch_size, (tokens)] , dan setiap input diberi token menjadi rangkaian token datar.
  • Jika r >1, terdapat r -1 tingkat pengelompokan tambahan. Misalnya, tensorflow_text.BertTokenizer menggunakan r =2 untuk mengelompokkan token berdasarkan kata dan menghasilkan bentuk [batch_size, (words), (tokens_per_word)] . Tergantung pada model yang ada, berapa banyak level tambahan yang ada, jika ada, dan pengelompokan apa yang diwakilinya.

Pengguna dapat (tetapi tidak perlu) memodifikasi input yang diberi token, misalnya, untuk mengakomodasi batas seq_length yang akan diterapkan dalam mengemas input encoder. Dimensi ekstra dalam keluaran tokenizer dapat membantu di sini (misalnya, untuk menghormati batasan kata) tetapi menjadi tidak berarti pada langkah berikutnya.

Dalam kaitannya dengan Reusable SavedModel API , objek preprocessor.tokenize mungkin memiliki .variables tetapi tidak dimaksudkan untuk dilatih lebih lanjut. Tokenisasi tidak bergantung pada mode: jika preprocessor.tokenize() memiliki argumen training=... sama sekali, hal ini tidak berpengaruh.

Detail bert_pack_inputs

Panggilan ke preprocessor.bert_pack_inputs() menerima daftar input tokenized Python (dikumpulkan secara terpisah untuk setiap segmen input) dan mengembalikan dict Tensor yang mewakili kumpulan urutan input dengan panjang tetap untuk model encoder Transformer.

Setiap masukan yang diberi token adalah int32 RaggedTensor berbentuk [batch_size, ...] , dengan jumlah r dimensi kasar setelah batch_size adalah 1 atau sama dengan keluaran preprocessor.tokenize(). (Yang terakhir ini hanya untuk kenyamanan; dimensi ekstra diratakan sebelum dikemas.)

Pengepakan menambahkan token khusus di sekitar segmen masukan seperti yang diharapkan oleh pembuat enkode. Panggilan bert_pack_inputs() mengimplementasikan persis skema pengepakan yang digunakan oleh model BERT asli dan banyak ekstensinya: urutan yang dikemas dimulai dengan satu token awal urutan, diikuti oleh segmen yang diberi token, masing-masing diakhiri oleh satu ujung segmen. token. Posisi yang tersisa hingga seq_length, jika ada, diisi dengan token padding.

Jika urutan yang dikemas melebihi seq_length, bert_pack_inputs() memotong segmennya menjadi prefiks dengan ukuran yang kira-kira sama sehingga urutan yang dikemas pas dengan seq_length.

Pengepakan tidak bergantung pada mode: jika preprocessor.bert_pack_inputs() memiliki argumen training=... sama sekali, maka hal tersebut tidak berpengaruh. Selain itu, preprocessor.bert_pack_inputs tidak diharapkan memiliki variabel, atau mendukung penyesuaian.

Pembuat enkode

Encoder dipanggil berdasarkan dict encoder_inputs dengan cara yang sama seperti di API untuk penyematan teks dengan input yang telah diproses sebelumnya (lihat di atas), termasuk ketentuan dari Reusable SavedModel API .

Sinopsis penggunaan

encoder = hub.load("path/to/encoder")
encoder_outputs = encoder(encoder_inputs)

atau setara di Keras:

encoder = hub.KerasLayer("path/to/encoder", trainable=True)
encoder_outputs = encoder(encoder_inputs)

Detail

encoder_outputs adalah dict Tensor dengan kunci berikut.

  • "sequence_output" : Tensor bentuk float32 [batch_size, seq_length, dim] dengan penyematan kontekstual dari setiap token dari setiap urutan input yang dikemas.
  • "pooled_output" : Tensor float32 berbentuk [batch_size, dim] dengan penyematan setiap urutan masukan secara keseluruhan, berasal dari sequence_output dengan cara yang dapat dilatih.
  • "default" , seperti yang disyaratkan oleh API untuk penyematan teks dengan masukan yang telah diproses sebelumnya: Tensor bentuk float32 [batch_size, dim] dengan penyematan setiap urutan masukan. (Ini mungkin hanya alias dari pooled_output.)

Konten encoder_inputs tidak sepenuhnya diwajibkan oleh definisi API ini. Namun, untuk encoder yang menggunakan input gaya BERT, disarankan untuk menggunakan nama berikut (dari NLP Modeling Toolkit TensorFlow Model Garden ) untuk meminimalkan hambatan dalam menukar encoder dan menggunakan kembali model praprosesor:

  • "input_word_ids" : Tensor int32 berbentuk [batch_size, seq_length] dengan id token dari urutan masukan yang dikemas (yaitu, termasuk token awal urutan, token akhir segmen, dan padding).
  • "input_mask" : Tensor int32 berbentuk [batch_size, seq_length] dengan nilai 1 pada posisi semua token masukan yang ada sebelum padding dan nilai 0 untuk token padding.
  • "input_type_ids" : Tensor int32 berbentuk [batch_size, seq_length] dengan indeks segmen masukan yang memunculkan token masukan pada posisi masing-masing. Segmen masukan pertama (indeks 0) mencakup token awal urutan dan token akhir segmen. Segmen kedua dan selanjutnya (jika ada) menyertakan token akhir segmennya masing-masing. Token padding mendapatkan indeks 0 lagi.

Pelatihan terdistribusi

Untuk memuat objek praprosesor dan encoder di dalam atau di luar cakupan strategi distribusi, aturan yang sama berlaku seperti di API untuk penyematan teks dengan input yang telah diproses sebelumnya (lihat di atas).

Contoh