常用於文字任務的 SavedModel API

本頁說明用於文字相關任務的 TF2 SavedModel 應如何實作可重複使用的 SavedModel API。(這取代並擴充了現已淘汰的 TF1 Hub 格式文字通用簽名)。

總覽

有多種 API 可用於計算文字嵌入 (也稱為文字的密集表示法或文字特徵向量)。

  • 從文字輸入取得文字嵌入的 API 是由 SavedModel 實作,SavedModel 會將一批字串對應到一批嵌入向量。此 API 非常容易使用,且 TF Hub 上的許多模型都已實作。不過,這不允許在 TPU 上微調模型。

  • 透過預先處理的輸入取得文字嵌入的 API 解決了相同的任務,但由兩個不同的 SavedModel 實作

    • 預處理器可在 tf.data 輸入管線中執行,並將字串和其他變動長度的資料轉換為數值張量,
    • 編碼器接受預處理器的結果,並執行嵌入計算的可訓練部分。

    這種拆分方式允許在將輸入饋送到訓練迴圈之前,先非同步預先處理輸入。尤其是,它允許建構可在 TPU 上執行和微調的編碼器。

  • 具有 Transformer 編碼器的文字嵌入的 API 將透過預先處理的輸入取得文字嵌入的 API 擴充到 BERT 和其他 Transformer 編碼器的特殊情況。

    • 預處理器已擴充為從多個輸入文字區隔建構編碼器輸入。
    • Transformer 編碼器會公開個別符記的感知情境嵌入。

在每種情況下,文字輸入都是 UTF-8 編碼的字串,通常是純文字,除非模型文件另有規定。

無論 API 為何,不同的模型都已使用來自不同語言和網域的文字,並以不同的任務為目標進行預先訓練。因此,並非每個文字嵌入模型都適用於每個問題。

從文字輸入取得文字嵌入

從文字輸入取得文字嵌入的 SavedModel 接受形狀為 [batch_size] 的字串張量批次輸入,並將其對應到形狀為 [batch_size, dim] 的 float32 張量,其中包含輸入的密集表示法 (特徵向量)。

用法概要

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

可重複使用的 SavedModel API 回顧可知,在訓練模式下執行模型 (例如,用於 dropout) 可能需要關鍵字引數 obj(..., training=True),且 obj 會提供屬性 .variables.trainable_variables.regularization_losses (如適用)。

在 Keras 中,所有這些都由以下項目處理:

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

分散式訓練

如果文字嵌入用作使用分散策略訓練的模型的一部分,則對 hub.load("path/to/model")hub.KerasLayer("path/to/model", ...) 的呼叫必須在 DistributionStrategy 範圍內進行,才能以分散式方式建立模型的變數。例如

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

範例

透過預先處理的輸入取得文字嵌入

透過預先處理的輸入取得文字嵌入是由兩個不同的 SavedModel 實作

  • 預處理器,將形狀為 [batch_size] 的字串張量對應到數值張量的字典,
  • 編碼器,接受預處理器傳回的張量字典,執行嵌入計算的可訓練部分,並傳回輸出字典。金鑰 "default" 下的輸出是形狀為 [batch_size, dim] 的 float32 張量。

這允許在輸入管線中執行預處理器,但微調編碼器計算的嵌入,作為較大模型的一部分。尤其是,它允許建構可在 TPU 上執行和微調的編碼器。

預處理器的輸出中包含哪些張量,以及編碼器的輸出中除了 "default" 之外還包含哪些 (如果有的話) 其他張量,都是實作細節。

編碼器的文件必須指定要與其搭配使用的預處理器。通常,只有一個正確的選擇。

用法概要

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

可重複使用的 SavedModel API 回顧可知,在訓練模式下執行編碼器 (例如,用於 dropout) 可能需要關鍵字引數 encoder(..., training=True),且 encoder 會提供屬性 .variables.trainable_variables.regularization_losses (如適用)。

preprocessor 模型可能具有 .variables,但不適用於進一步訓練。預先處理與模式無關:如果 preprocessor() 根本沒有 training=... 引數,則不會產生任何效果。

在 Keras 中,所有這些都由以下項目處理:

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

分散式訓練

如果編碼器用作使用分散策略訓練的模型的一部分,則對 hub.load("path/to/encoder")hub.KerasLayer("path/to/encoder", ...) 的呼叫必須在以下範圍內發生:

  with strategy.scope():
    ...

才能以分散式方式重新建立編碼器變數。

同樣地,如果預處理器是已訓練模型的一部分 (如上面的簡單範例所示),則也需要在分散策略範圍內載入。但是,如果預處理器用於輸入管線中 (例如,在傳遞至 tf.data.Dataset.map() 的可呼叫物件中),則其載入必須在分散策略範圍之外發生,才能將其變數 (如果有的話) 放在主機 CPU 上。

範例

具有 Transformer 編碼器的文字嵌入

用於文字的 Transformer 編碼器對一批輸入序列進行運算,每個序列包含 n ≥ 1 個符記化文字區隔,在模型特定的 n 上限內。對於 BERT 及其許多擴充功能,該上限為 2,因此它們接受單個區隔和區隔對。

具有 Transformer 編碼器的文字嵌入的 API 將透過預先處理的輸入取得文字嵌入的 API 擴充到此設定。

預處理器

用於具有 Transformer 編碼器的文字嵌入的預處理器 SavedModel 實作了透過預先處理的輸入取得文字嵌入的預處理器 SavedModel 的 API (請參閱上文),其中提供了一種將單區隔文字輸入直接對應到編碼器輸入的方法。

此外,預處理器 SavedModel 還提供可呼叫的子物件 tokenize (用於符記化,每個區隔分別進行) 和 bert_pack_inputs (用於將 n 個符記化區隔封裝到編碼器的單個輸入序列中)。每個子物件都遵循可重複使用的 SavedModel API

用法概要

以兩個文字區隔的具體範例來說明,讓我們看看句子蘊涵任務,該任務詢問前提 (第一個區隔) 是否暗示假設 (第二個區隔)。

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.

在 Keras 中,此計算可以表示為

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

tokenize 的詳細資訊

呼叫 preprocessor.tokenize() 會接受形狀為 [batch_size] 的字串張量,並傳回形狀為 [batch_size, ...]RaggedTensor,其值是代表輸入字串的 int32 符記 ID。在 batch_size 之後,可能會有 r ≥ 1 個參差維度,但沒有其他統一維度。

  • 如果 r=1,則形狀為 [batch_size, (tokens)],且每個輸入都只是符記化為符記的平面序列。
  • 如果 r>1,則會有 r-1 個額外的分組層級。例如,tensorflow_text.BertTokenizer 使用 r=2,依字詞對符記進行分組,並產生形狀 [batch_size, (words), (tokens_per_word)]。模型本身決定存在多少額外層級 (如果有的話),以及它們代表哪些分組。

使用者可以 (但不需要) 修改符記化輸入,例如,為了適應將在封裝編碼器輸入中強制執行的 seq_length 限制。Tokenizer 輸出中的額外維度在這裡可以提供協助 (例如,為了尊重字詞邊界),但在下一步中會變得沒有意義。

可重複使用的 SavedModel API 而言,preprocessor.tokenize 物件可能具有 .variables,但不適用於進一步訓練。符記化與模式無關:如果 preprocessor.tokenize() 根本沒有 training=... 引數,則不會產生任何效果。

bert_pack_inputs 的詳細資訊

呼叫 preprocessor.bert_pack_inputs() 會接受符記化輸入的 Python 清單 (每個輸入區隔分別進行批次處理),並傳回張量字典,表示 Transformer 編碼器模型的固定長度輸入序列批次。

每個符記化輸入都是形狀為 [batch_size, ...] 的 int32 RaggedTensor,其中 batch_size 之後的參差維度 r 數量為 1 或與 preprocessor.tokenize() 的輸出相同。(後者僅為方便起見;額外維度在封裝前會展平)。

封裝會在輸入區隔周圍新增編碼器預期的特殊符記。bert_pack_inputs() 呼叫完全實作了原始 BERT 模型及其許多擴充功能使用的封裝方案:封裝序列以一個序列開始符記開始,後接符記化區隔,每個區隔以一個區隔結束符記終止。剩餘位置 (如果有的話,最多 seq_length) 會以填補符記填滿。

如果封裝序列會超過 seq_length,bert_pack_inputs() 會將其區隔截斷為大致相等的字首,以便封裝序列完全符合 seq_length 內。

封裝與模式無關:如果 preprocessor.bert_pack_inputs() 根本沒有 training=... 引數,則不會產生任何效果。此外,預期 preprocessor.bert_pack_inputs 沒有變數,或不支援微調。

編碼器

encoder_inputs 的字典上呼叫編碼器的方式與透過預先處理的輸入取得文字嵌入的 API 中的方式相同 (請參閱上文),包括來自可重複使用的 SavedModel API 的條款。

用法概要

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

或在 Keras 中等效地表示為

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

詳細資訊

encoder_outputs 是具有以下金鑰的張量字典。

  • "sequence_output":形狀為 [batch_size, seq_length, dim] 的 float32 張量,其中包含每個封裝輸入序列的每個符記的感知情境嵌入。
  • "pooled_output":形狀為 [batch_size, dim] 的 float32 張量,其中包含每個輸入序列的嵌入,整體而言,以某種可訓練的方式從 sequence_output 衍生而來。
  • "default",這是透過預先處理的輸入取得文字嵌入的 API 所要求的:形狀為 [batch_size, dim] 的 float32 張量,其中包含每個輸入序列的嵌入。(這可能只是 pooled_output 的別名。)

encoder_inputs 的內容並非此 API 定義嚴格要求。但是,對於使用 BERT 樣式輸入的編碼器,建議使用以下名稱 (來自 TensorFlow Model Garden 的 NLP Modeling Toolkit),以盡可能減少更換編碼器和重複使用預處理器模型時的摩擦

  • "input_word_ids":形狀為 [batch_size, seq_length] 的 int32 張量,其中包含封裝輸入序列的符記 ID (也就是說,包括序列開始符記、區隔結束符記和填補)。
  • "input_mask":形狀為 [batch_size, seq_length] 的 int32 張量,在填補之前存在的所有輸入符記的位置值為 1,填補符記的值為 0。
  • "input_type_ids":形狀為 [batch_size, seq_length] 的 int32 張量,其中包含產生各位置輸入符記的輸入區隔索引。第一個輸入區隔 (索引 0) 包含序列開始符記及其區隔結束符記。第二個和後續區隔 (如果存在) 包含其各自的區隔結束符記。填補符記再次取得索引 0。

分散式訓練

對於在分散策略範圍內或範圍外載入預處理器和編碼器物件,適用的規則與透過預先處理的輸入取得文字嵌入的 API 中的規則相同 (請參閱上文)。

範例