本頁說明用於文字相關任務的 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")
...
範例
- Colab 教學課程:使用電影評論進行文字分類。
透過預先處理的輸入取得文字嵌入
透過預先處理的輸入取得文字嵌入是由兩個不同的 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 上。
範例
- Colab 教學課程:使用 BERT 對文字進行分類。
具有 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 中的規則相同 (請參閱上文)。
範例
- Colab 教學課程:在 TPU 上使用 BERT 解決 GLUE 任務。