文本任务的通用 SavedModel API

此页面介绍了文本相关任务的 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")
    ...

示例

具有预处理输入的文本嵌入

具有预处理输入的文本嵌入由两个单独的 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 上。

示例

使用 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 中的规则相同(见上文)。

示例