此页面介绍了文本相关任务的 TF2 SavedModel 应如何实现 可重复使用的 SavedModel API。(这取代并扩展了 文本通用签名,适用于现已弃用的 TF1 Hub 格式。)
概览
有多个 API 可用于计算文本嵌入(也称为文本的密集表示或文本特征向量)。
文本输入文本嵌入的 API 由 SavedModel 实现,该 SavedModel 将一批字符串映射到一批嵌入向量。这非常易于使用,并且 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()
überhaupt 具有 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", ...)
,resp.,必须在内部发生
with strategy.scope():
...
以便以分布式方式重新创建编码器变量。
同样,如果预处理器是训练模型的一部分(如上面简单的示例中),则还需要在分布策略范围内加载它。但是,如果预处理器在输入管道中使用(例如,在可调用项中传递给 tf.data.Dataset.map()
),则必须在分布策略范围之外加载它,以便将其变量(如果有)放置在主机 CPU 上。
示例
- Colab 教程 使用 BERT 对文本进行分类。
使用 Transformer 编码器的文本嵌入
用于文本的 Transformer 编码器在输入序列批处理上运行,每个序列包含 n ≥ 1 个标记化文本段,在 n 的特定于模型的范围内。对于 BERT 及其许多扩展,该界限为 2,因此它们接受单个段和段对。
具有 Transformer 编码器的文本嵌入的 API 将具有预处理输入的文本嵌入的 API 扩展到此设置。
预处理器
具有 Transformer 编码器的文本嵌入的预处理器 SavedModel 实施了具有预处理输入的文本嵌入的预处理器 SavedModel 的 API(见上文),该 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 限制。标记器输出中的额外维度可以在这里提供帮助(例如,尊重单词边界),但在下一步中变得毫无意义。
就可重用 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 的别名。)
此 API 定义严格来说不要求 encoder_inputs
的内容。但是,对于使用 BERT 样式输入的编码器,建议使用以下名称(来自 TensorFlow Model Garden 的 NLP 建模工具包),以最大程度地减少在交换编码器和重复使用预处理器模型时的摩擦
"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 任务。