在请求说明之前,您必须提交说明元数据文件,以配置说明请求。这个元数据文件必须包含模型的输入和输出。此外,其中还有一些可选设置,例如输入基准和图像数据的可视化设置。
通过指定模型的输入和输出,您可以为说明请求选择特定的特征,而无需更改模型。您可以在构建新模型时使用 Explainable AI SDK 自动执行此操作。如果您未使用 Explainable AI SDK,则需要手动标识输入和输出。
本指南着重介绍如何手动标识输入和输出张量,以帮助您准备说明元数据文件。
说明元数据中的输入和输出
如需准备说明元数据,您必须在名为 explanation_metadata.json
的文件中为模型指定输入和输出:
{
"inputs": {
string <input feature key>: {
"input_tensor_name": string,
},
"outputs": {
string <output value key>: {
"output_tensor_name": string
},
},
"framework": "tensorflow"
}
在该文件的 inputs
和 outputs
对象中,您必须为说明请求提供输入和输出张量的名称。
- 借助每个输入和输出的键(以上示例中的“input feature key”和“output value key”),您可以为每个张量指定有意义的名称。在以下示例中,input feature key 为
degrees_celsius
,output value key 为probabilities
。 - 对于每个元数据
input
和output
中的值,您必须以input_tensor_name
或output_tensor_name
形式提供张量的实际名称。在以下示例中,input_tensor_name
为x:0
,output_tensor_name
为dense/Softmax:0
。
{
"inputs": {
"degrees_celsius": {
"input_tensor_name": "x:0",
}
},
"outputs": {
"probabilities": {
"output_tensor_name": "dense/Softmax:0"
}
},
"framework": "tensorflow"
}
实际的张量名称格式为 name:index
。
查找输入和输出张量
训练 TensorFlow 模型后,请将其导出为 SavedModel。TensorFlow SavedModel 包含经过训练的 TensorFlow 模型,以及运行图所需的序列化签名、变量和其他资源。每个 SignatureDef
标识图中接受张量输入并生成张量输出的函数。类似地,说明元数据文件为发送给 AI Explanations 的特征归因请求定义了图的输入和输出。
通常,您在说明元数据文件中指定的输入和输出张量会精确映射到您在保存模型时定义的签名。如果是这样的话,查找输入和输出张量名称的操作相对来说就比较简单。 但在某些情况下,您要说明的输入或输出可能与您在保存模型时定义的输入或输出有所不同。
在以下情况下,说明的输入和输出与服务 SignatureDef
中设置的输入和输出相同:
- 输入不是序列化形式
SignatureDef
的每个输入都直接包含特征的值(可以是数值或字符串)- 输出是数值(被视为数字数据)。这不包括作为分类数据的类别 ID。
对于这些情况,您可以在构建模型时获取输入和输出张量的名称。或者,您可以使用 SavedModel CLI 检查 SavedModel 的 SignatureDef
,以查找输入和输出张量的名称。
对于不符合以上条件的任何情况,您可以通过其他方法来查找正确的输入和输出张量。
在训练期间获取张量名称
在训练期间,最容易获取输入和输出张量名称。您可以将这些值保存到说明元数据文件中,而您的程序或环境仍然可以访问您在构建模型时设置的变量。在以下示例中,Keras 层的 name
字段会生成说明元数据所需的基础张量名称:
bow_inputs = tf.keras.layers.Input(shape=(2000,))
merged_layer = tf.keras.layers.Dense(256, activation="relu")(bow_inputs)
predictions = tf.keras.layers.Dense(10, activation="sigmoid")(merged_layer)
model = keras.Model(inputs=bow_inputs, outputs=predictions)
print('input_tensor_name:', bow_inputs.name)
print('output_tensor_name:', predictions.name)
input_tensor_name: input_1:0
output_tensor_name: dense_1/Sigmoid:0
如需查看完整的有效示例,请参阅示例笔记本。
从签名定义中获取张量名称
如果 SignatureDef
和说明元数据同时标识了张量输入和输出,则您可以使用 SignatureDef
准备说明元数据文件(前提是需要满足前面提到的条件)。
请思考以下示例 SignatureDef
:
The given SavedModel SignatureDef contains the following input(s):
inputs['my_numpy_input'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: x:0
The given SavedModel SignatureDef contains the following output(s):
outputs['probabilities'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: dense/Softmax:0
Method name is: tensorflow/serving/predict
图中有一个名为 x:0
的输入张量和一个名为 dense/Softmax:0
的输出张量。此外,这两个张量还分别具有有意义的名称:my_numpy_input
和 probabilities
。如需请求关于 my_numpy_input
的 probabilities
说明,您可以按照如下所示,创建说明元数据文件:
{
"inputs": {
"my_numpy_input": {
"input_tensor_name": "x:0",
}
},
"outputs": {
"probabilities": {
"output_tensor_name": "dense/Softmax:0"
}
},
"framework": "tensorflow"
}
如需检查 SavedModel 的 SignatureDef
,您可以使用 SavedModel CLI。详细了解如何使用 SavedModel CLI。
处理输入和输出差异
在一些常见的情况下,说明元数据中的输入和输出张量不应该与服务 SignatureDef
中的输入和输出张量相同:
- 输入已序列化
- 图中包含预处理操作
- 服务输出不属于概率、logits 或其他浮点类型的张量
在这些情况下,您应使用不同的方法来查找正确的输入和输出张量。总体目标是查找与您要为输入说明的特征值有关的张量,以及与 logits(激活前)、概率(激活后)或任何其他输出表示有关的张量。
输入差异
如果您使用序列化输入来向模型提供数据,或者图中包含预处理操作,则说明元数据中的输入与服务 SignatureDef
中的输入有所不同。
序列化输入
TensorFlow SavedModel 可以接受各种复杂的输入,包括:
- 序列化 tf.Example 消息
- JSON 字符串
- 编码的 Base64 字符串(表示图片数据)
如果您的模型接受类似这样的序列化输入,则直接使用这些张量作为说明的输入将不起作用,或者可能生成无意义的结果。相反,您需要查找输入模型内特征列的后续输入张量。
导出模型时,您可以通过在服务输入函数中调用解析函数来向 TensorFlow 图添加解析操作。tf.io 模块中列出了解析函数。这些解析函数通常会返回张量作为响应,而这些张量则更适合用于说明元数据。
例如,您可以在导出模型时使用 tf.parse_example()。它会接收序列化 tf.Example 消息,并输出提供给特征列的张量字典。您可以使用其输出来填充说明元数据。如果这些输出中的某些输出是 tf.SparseTensor(这是由 3 个张量组成的命名元组),则您应获取索引、值和 dense_shape 张量的名称,并填充元数据中的相应字段。
以下示例展示了如何在解码操作后获取输入张量的名称:
float_pixels = tf.map_fn(
lambda img_string: tf.io.decode_image(
img_string,
channels=color_depth,
dtype=tf.float32
),
features,
dtype=tf.float32,
name='input_convert'
)
print(float_pixels.name)
预处理输入
如果您的模型图包含一些预处理操作,则您可能希望在预处理步骤之后获取张量的说明。在这种情况下,您可以使用 tf.Tensor 的 name
属性来获取这些张量的名称,并将它们放入说明元数据中:
item_one_hot = tf.one_hot(item_indices, depth,
on_value=1.0, off_value=0.0,
axis=-1, name="one_hot_items:0")
print(item_one_hot.name)
解码后的张量名称会变为 input_pixels:0
。
输出差异
在大多数情况下,服务 SignatureDef
中的输出是概率或 logits。
如果您的模型归因于概率,而您想要说明 logit 值,则您必须查找与 logits 对应的适当输出张量名称。
如果服务 SignatureDef
的输出不是概率或 logits,则您应参考训练图中的概率操作。对于 Keras 模型,这种情况不太可能出现。如果出现这种情况,您可以使用 TensorBoard(或其他图表可视化工具)来帮助查找正确的输出张量名称。
积分梯度的其他注意事项
AI Explanations 提供了两种特征归因方法:采样 Shapley 和积分梯度。如果使用积分梯度方法,则您需要确保输入可针对输出进行微分;因此,在准备说明元数据时,您必须牢记这一点。如果使用采样 Shapley 特征归因方法,则您无需确保输入是可微分的。请详细了解 AI Explanations 中支持的特征归因方法。
说明元数据在逻辑上会区分模型的特征与其输入。 将积分梯度与输入张量(该输入张量不可针对输出张量进行微分)时,您还需要提供该特征的编码(且可微分的)版本。
如果您有不可微分的输入张量,或者图中存在不可微分的操作,请使用以下方法:
- 将不可微分的输入编码为可微分的输入。
- 将
input_tensor_name
设置为不可微分的原始输入张量的名称,将encoded_tensor_name
设置为可微分的编码输入张量的名称。
包含编码的说明元数据文件
例如,假设某个模型的分类特征具有名为 zip_codes:0
的输入张量。由于输入数据包含字符串形式的邮政编码,因此输入张量 zip_codes:0
是不可微分的。如果该模型还会预处理此数据以获取邮政编码的独热编码表示,则预处理之后的输入张量是可微分的。如需将其与原始输入张量进行区分,您可以将其命名为 zip_codes_embedding:0
。
如需在说明请求中使用这两个输入张量的数据,请按如下所示设置元数据 inputs
:
- 将 input feature key 设置为有意义的名称,例如
zip_codes
。 - 将
input_tensor_name
设置为原始张量的名称zip_codes:0
。 - 将
encoded_tensor_name
设置为独热编码后的张量名称zip_codes_embedding:0
。 - 将
encoding
设置为combined_embedding
。
{
"inputs": {
"zip_codes": {
"input_tensor_name": "zip_codes:0",
"encoded_tensor_name": "zip_codes_embedding:0",
"encoding": "combined_embedding"
}
},
"outputs": {
"probabilities": {
"output_tensor_name": "dense/Softmax:0"
}
},
"framework": "tensorflow"
}
或者,您可以将 input_tensor_name
设置为可微分的编码输入张量的名称,并省略不可微分的原始张量。同时提供这两种张量的好处在于,可以对各个邮政编码值(而不是其独热编码表示)进行归因。在此示例中,您要排除原始张量 (zip_codes:0
),并将 input_tensor_name
设置为 zip_codes_embedding:0
。我们不推荐此方法,因为生成的特征归因很难说明原因。
编码
如需在说明请求中启用编码,您可以按照以上示例所示指定编码设置。
编码特征有助于反转从编码数据到输入数据的归因过程,从而无需手动对返回的归因进行后处理。目前,AI Explanations 支持 combined_embedding
,其中变量长度特征已组合到嵌入中。与此 combined_embedding
匹配的示例操作为 tf.nn.embedding_lookup_sparse
。
对于 combined_embedding
:
输入张量被编码为一维数组。例如:
- 输入:
["This", "is", "a", "test"]
- 编码后:
[0.1, 0.2, 0.3, 0.4]
后续步骤
- 试用示例笔记本
- 了解如何部署具有说明的模型