逑识

吾生也有涯,而知也无涯,以无涯奉有涯,其易欤?

0%

TensorFlow 2.x 模型 Serving 服务

导语」在模型训练完成后,我们需要使用保存后的模型进行线上预测,即模型 Serving 服务。 TensorFlow 团队提供了专门用于模型预测的服务系统 TensorFlow Serving,它专为生产环境设计,具备高性能且具有很强大的灵活性,本文将从服务搭建,服务配置,远程访问等多个方面对 TensorFlow Serving 进行详细地介绍。

Serving 服务搭建

官方推荐使用 Docker 来完成 TensorFlow Serving 服务的搭建,这是最容易也是最直接的搭建方式。为便于操作,本文也采取了这种搭建方式。

在搭建 Serving 服务时,首先要确保系统已经安装了 Docker 程序,然后通过命令 docker pull tensorflow/serving:version 来获取指定版本的 TensorFlow Serving 镜像,接着就可以使用该镜像来启动 Serving 服务了。

需要注意的是 TensorFlow Serving 服务的版本最好与训练时用到的 TensorFlow 版本保持一致,以减少不必要的兼容性问题。可以从 dockerhub 网站查询该镜像的所有可用版本,比如稳定版 (latest) ,指定版本 (2.2.0) 或者每天更新版 (nightly) 等。

如果要使用 GPU 来支持模型的预测服务,则需要获取 TensorFlow Serving 镜像相应的 GPU 版本 (docker pull tensorflow/serving:version-gpu) 并且要配置好本地的 GPU 环境。

单模型 Serving 服务

当我们训练完成并保存好一个模型后,可以很方便地启动一个 Serving 服务来进行测试与验证。

首先将保存的 SavedModel 格式的模型文件放置于本地的 /path/to/tfs/models/model/version 目录下,该目录的结构如下所示:

model/
└── 0
├── assets
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index

其中 model 表示将要加载的模型名称, 0 表示该模型的可用版本,这里只有一个版本,也可以存在多个版本。版本目录下的文件即为 SavedModel 格式的文件,在模型保存一文中已经对这些文件存储的内容做过介绍,这里就不再赘述。

然后使用以下命令即可启动一个 Serving 服务:

docker run -d -p 8501:8501 \
-v /path/to/tfs/models/model:/models/model \
tensorflow/serving:2.2.0

Serving 服务仅加载了一个模型,模型的名称为 model ,版本号为 0 。搭载该 Serving 服务的容器暴露了 8501 端口,用于接收 HTTP 接口的访问请求。

我们可以在容器启动时通过设置容器内的环境变量 MODEL_BASE_PATH 来指定模型的存储路径,该环境变量的默认值为 /models ,表示从容器内的 /models 路径加载模型。同样地,可以通过 MODEL_NAME 环境变量来指定要加载的模型名称,该环境变量的默认值为 model 。基于环境变量的 Serving 服务启动命令如下所示:

docker run -d -p 8501:8501 \
-v /path/to/tfs/models/model:/models/model \
-e MODEL_BASE_PATH=/models \
-e MODEL_NAME=model \
tensorflow/serving:2.2.0

除了设置容器内的环境变量,我们还可以使用 Serving 服务的启动参数 --model_base_path--model_name 来分别指定模型的存储路径和模型名称。启动命令如下所示:

docker run -d -p 8501:8501 \
-v /path/to/tfs/models/model:/models/model \
tensorflow/serving:2.2.0 \
--model_base_path=/models \
--model_name=model

Serving 服务成功启动后,可以通过访问特定的 HTTP 接口来查看模型是否加载成功。其请求和响应的示例如下所示:

$ curl http://localhost:8501/v1/models/model
{
"model_version_status": [
{
"version": "0",
"state": "AVAILABLE",
"status": {
"error_code": "OK",
"error_message": ""
}
}
]
}

当模型状态为 AVAILABLE 时表示该模型的相应版本已经成功加载,并且已经准备好提供 Serving 服务了。如果调用该接口返回错误或没有返回,则说明模型加载出现问题,此时可以根据返回的错误信息或者使用 docker logs containerid 查看容器的日志以定位问题。

需要注意的是,如果模型的存储路径下包含多个模型版本,则 Serving 服务默认会加载最大版本号对应的模型,也就是说一个模型同时只能有一个版本提供 Serving 服务。

多模型 Serving 服务

一般而言,线上的 Serving 服务中会同时部署多个模型或者模型的多个版本,以供 A/B Test 测试使用,进而区分不同模型的实际运行效果。而上一节介绍的单模型 Serving 服务由于其模型名称和加载的模型版本都是固定的,因此并不能满足这一需求,此时可以使用带有配置文件 (models.config) 的 Serving 服务来实现上述目标。

模型配置文件的内容是基于 ModelServerConfig 结构生成的, ModelServerConfigmodel_server_config.proto 文件中定义的一种数据结构 (message) ,具体定义信息可参见其源码。该配置文件的基础示例如下所示:

model_config_list {
config {
name: 'first_model'
base_path: '/models/first_model'
model_platform: 'tensorflow'
}
config {
name: 'second_model'
base_path: '/models/second_model'
model_platform: 'tensorflow'
}
}

配置文件中的每一个 config 都对应于一个将要被加载的模型,其中 base_path 对应于该模型的加载路径, name 对应于模型的名称,如果该路径中存在多个版本的模型文件,那么 TensorFlow Serving 还是会选择最大版本号的模型来提供 Serving 服务。

将上述配置文件放置于 /path/to/tfs/config 目录并使用如下命令即可启动一个多模型的 TensorFlow Serving 服务:

docker run -d -p 8501:8501 \
-v /path/to/tfs/models:/models \
-v /path/to/tfs/config:/config \
tensorflow/serving:2.2.0 \
--model_config_file=/config/models.config

同理在 Serving 服务启动后可以使用 HTTP 接口来查看各个模型的加载信息:

$ curl http://localhost:8501/v1/models/first_model
$ curl http://localhost:8501/v1/models/second_model
{
"model_version_status": [
{
"version": "0",
"state": "AVAILABLE",
"status": {
"error_code": "OK",
"error_message": ""
}
}
]
}

此时我们就可以使用多个模型来同时进行线上 Serving 服务了。

Serving 服务配置

模型配置

models.config 配置文件除了可以指定多个模型的名称和加载路径以外,还可以指定模型的版本策略 (version policy)、版本的标签 (labels) 以及请求日志的记录 (logging) 方式等,为我们提供了极大的便利。

在同一模型路径下, Serving 服务会默认加载版本号最大的那个模型,而通过修改配置文件中的 model_version_policy 配置项我们可以指定要加载的模型版本。假如我们要使用版本号为 N 的模型提供 Serving 服务,首先需要将 model_version_policy 策略设置为 specific ,然后在 specific 中提供相应的版本号 N 即可。指定版本号的模型配置如下所示:

config {
name: 'first_model'
base_path: '/models/first_model'
model_platform: 'tensorflow'
model_version_policy {
specific {
versions: 0
}
}
}

这种配置选项在最新版本的模型 Serving 出现问题需要回滚到一个已知的较好版本时会比较有用。

如果需要同时使用多个版本的模型来提供 Serving 服务,也是同样地设置方式,只不过需要在 specific 中提供多个版本号。模型配置如下所示:

config {
name: 'first_model'
base_path: '/models/first_model'
model_platform: 'tensorflow'
model_version_policy {
specific {
versions: 0
versions: 1
}
}
}

有时为模型的版本号指定一个别名或标签会很有帮助,这样客户端在调用时就不必知道具体的版本号,直接访问对应的标签即可。比如我们可以设置一个稳定版 (stable) 标签,它可以指向任一版本号的模型,客户端只需访问 stable 标签即可获取最新 Serving 服务。如果有新的稳定版本,则只需要修改 stable 标签指向的版本号,而客户端对这次改变是无感知的,因此也无需做任何修改。注意只有通过 gRPC 接口访问时才可以使用标签功能,HTTP 接口则不支持。

带有标签的模型配置如下所示:

config {
name: 'first_model'
base_path: '/models/first_model'
model_platform: 'tensorflow'
model_version_policy {
specific {
versions: 0
versions: 1
}
}
version_labels {
key: 'stable'
value: 1
}
version_labels {
key: 'canary'
value: 0
}
}

如果模型需要回滚,则只需要修改配置文件中相应标签对应的版本号即可。

注意只有模型版本已被加载的且该模型版本的状态为 AVAILABLE 时,才能给该模型版本指定一个标签。如果需要给一个尚未加载的模型版本指定标签(比如在 Serving 启动时,配置文件中同时提供了模型版本和标签),则需要将 --allow_version_labels_for_unavailable_models 启动参数设置为 true

而上述启动参数,只适用于为新标签指定版本号。如果要给一个已在使用的标签重新分配版本,则必须将其分配给一个已加载的模型版本,这样可以避免在版本切换时发送到该标签的请求失效而造成线上故障。因此,如果要将标签 stable 从版本 N 重新分配到版本 N + 1,则必须首先提交同时包含版本 N 和版本 N + 1 的配置,待版本 N + 1 加载成功后,再提交将标签 stable 指向版本 N + 1 的配置以完成标签的更新操作。

监控配置

在线上 Serving 时,我们需要了解 Serving 服务的各项指标,以便于我们及时发现问题并作出调整。

我们可以使用 --monitoring_config_file 参数来启用 Serving 服务的监控指标获取功能。该参数需要指定一个配置文件,其内容对应于 monitoring_config.proto 文件中定义的 MonitoringConfig 数据结构 (message) ,具体定义可参见源码。配置文件的内容如下所示:

prometheus_config {
enable: true,
path: "/metrics"
}

其中 enable 表示是否开启监控, path 表示获取监控指标的 uri 。将上述配置保存到 monitor.config 文件中并将该文件放置于 /path/to/tfs/config 目录下,然后使用如下命令即可启动带有监控指标的 Serving 服务:

docker run -d -p 8501:8501 \
-v /path/to/tfs/models:/models \
-v /path/to/tfs/config:/config \
tensorflow/serving:2.2.0 \
--model_config_file=/config/models.config \
--monitoring_config_file=/config/monitor.config

访问 http://localhost:8501/metrics 即可看到 TensorFlow Serving 服务的实时监控指标信息。部分监控指标如下所示:

# TYPE :tensorflow:api:op:using_fake_quantization gauge
# TYPE :tensorflow:cc:saved_model:load_attempt_count counter
:tensorflow:cc:saved_model:load_attempt_count{model_path="/models/first_model/0",status="success"} 1
:tensorflow:cc:saved_model:load_attempt_count{model_path="/models/first_model/1",status="success"} 1
:tensorflow:cc:saved_model:load_attempt_count{model_path="/models/second_model/1",status="success"} 1
# TYPE :tensorflow:cc:saved_model:load_latency counter
:tensorflow:cc:saved_model:load_latency{model_path="/models/first_model/0"} 61678
:tensorflow:cc:saved_model:load_latency{model_path="/models/first_model/1"} 59848
:tensorflow:cc:saved_model:load_latency{model_path="/models/second_model/1"} 95868
# TYPE :tensorflow:cc:saved_model:load_latency_by_stage histogram
:tensorflow:cc:saved_model:load_latency_by_stage_bucket{model_path="/models/first_model/0",stage="init_graph",le="10"} 0

为了更直观的监控这些指标,可以将所有的指标都采集到 Prometheus 并使用 Grafana 进行可视化展示。部分指标可视化的结果如下图所示:

监控指标可视化

批处理配置

Serving 服务可以对客户端的请求进行批处理,以实现更好的吞吐量。对于 Serving 服务加载的所有模型和模型版本,批处理操作将会在全局范围内进行统一调度,以确保可以最大程度的利用基础计算资源。可以使用 --enable_batching 参数来启用批处理操作,并使用 --batching_parameters_file 启动参数指定批处理参数配置文件 (batching.config)来对与批处理有关的参数进行配置。

批处理参数配置文件的内容如下所示:

max_batch_size { value: 128 }
batch_timeout_micros { value: 0 }
num_batch_threads { value: 8 }
max_enqueued_batches { value: 8 }

其中 max_batch_size 参数用来控制吞吐量和延迟之间的权衡,同时它也可以防止因 batch 过大超出了资源限制而导致请求失败。 batch_timeout_micros 参数表示执行一次批处理操作之前所要等待的最长时间(即使可能尚未达到 max_batch_size 也会执行),主要用于控制尾部延迟。 num_batch_threads 表示并行执行批处理操作的最大数量。 max_enqueued_batches 表示可以进入调度程序队列的批处理任务的数量,用来对排队带来的延迟进行限制。

batching.config 文件并放置于 /path/to/tfs/config 目录下,然后使用以下命令即可启动一个带有批处理操作的 Serving 服务:

docker run -d -p 8501:8501 \
-v /path/to/tfs/models:/models \
-v /path/to/tfs/config:/config \
tensorflow/serving:2.2.0 \
--model_config_file=/config/models.config \
--enable_batching=true \
--batching_parameters_file=/config/batching.config

注意在调整 max_batch_sizemax_enqueued_batches 参数时,不能设置的过大,避免因内存占用过高而导致内存溢出,从而影响线上的 Serving 服务。

服务启动参数

除了上面介绍过的一些 Serving 服务启动参数外,还有其它一些启动参数需要了解,使用这些参数可以对部署的 Serving 服务进行微调,以达到想要的效果。下面对其中一些比较重要的启动参数做简要介绍:

  1. --portgRPC 服务的监听端口,默认为 8500 端口。
  2. --rest_api_portHTTP 服务的监听端口,默认为 8501 端口。
  3. --rest_api_timeout_in_msHTTP 请求的超时限制(毫秒),默认为 30000 毫秒。
  4. --model_config_file:模型配置文件路径。
  5. --model_config_file_poll_wait_seconds:定期加载模型配置文件的时间间隔(秒),默认为 0 ,表示 Serving 服务启动后就不再定期加载。
  6. --file_system_poll_wait_seconds:定期加载新模型版本的时间间隔(秒),默认为 1 秒。
  7. --enable_batchingbool,是否开启请求批处理操作,默认为 false
  8. --batching_parameters_file:批处理操作的参数配置文件路径。
  9. --monitoring_config_file:用于监控的配置文件路径。
  10. --allow_version_labels_for_unavailable_modelsbool,是否可以对尚未加载的模型分配标签,默认为 false
  11. --model_name:模型名称,在单模型 Serving 时使用。
  12. --model_base_path:模型加载路径,在单模型 Serving 时使用。
  13. --enable_model_warmupbool,是否使用数据对模型进行预热,以减少第一次请求的响应延迟,默认为 false 。因为 TensorFlow 运行时具有延迟初始化的组件,这可能导致在模型加载后发送给模型的第一次请求的响应时间比较长,而且这个延迟的时间可能比单个请求所需的时间高好几个数量级。为了减小延迟初始化对请求响应的影响,可以在模型加载时提供一组请求样本来触发组件的初始化,这个过程被称为模型预热
  14. 更多的启动参数可以参见源码。

完整示例

一个配置齐全的多模型 Serving 服务的启动方式如下所示:

docker run -d -p 8500:8500 -p 8501:8501 \
-v /path/to/tfs/models:/models \
-v /path/to/tfs/config:/config \
tensorflow/serving:2.2.0 \
--model_config_file=/config/models.config \
--monitoring_config_file=/config/monitor.config \
--enable_batching=true \
--batching_parameters_file=/config/batching.config \
--model_config_file_poll_wait_seconds=10 \
--file_system_poll_wait_seconds=10 \
--allow_version_labels_for_unavailable_models=true

HTTP 接口访问

我们可以通过访问 Serving 服务的 HTTP 接口来查询模型信息,包括模型的加载状态,模型的元数据以及模型的预测结果等。

所有的请求和响应的 Body 都是 json 格式的,对于错误的请求, Serving 服务会返回如下格式的错误信息:

{
"error": "error message string"
}

模型状态

访问 http://host:port/v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}] 接口可以获取模型的指定版本的状态信息,其中 /versions/${MODEL_VERSION} 是可选部分,如果不指定则会返回该模型所有版本的状态信息。请求和响应示例如下所示:

$ curl http://localhost:8501/v1/models/first_model/versions/0
{
"model_version_status": [
{
"version": "0",
"state": "AVAILABLE",
"status": {
"error_code": "OK",
"error_message": ""
}
}
]
}

模型 Metadata

访问 http://host:port/v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}]/metadata 接口可以获取模型的指定版本的元数据信息。其中 /versions/${MODEL_VERSION} 是可选部分,如果不指定则会返回该模型最新版本的元数据。请求和响应示例如下所示:

$ curl http://localhost:8501/v1/models/first_model/versions/0/metadata
{
"model_spec": {
"name": "first_model",
"signature_name": "",
"version": "0"
},
"metadata": {
"signature_def": {
"signature_def": {
"serving_default": {
"inputs": {
"input_1": {
"dtype": "DT_INT64",
"tensor_shape": {
"dim": [
{
"size": "-1",
"name": ""
},
{
"size": "31",
"name": ""
}
],
"unknown_rank": false
},
"name": "serving_default_input_1:0"
}
},
"outputs": {
"output_1": {
"dtype": "DT_FLOAT",
"tensor_shape": {
"dim": [
{
"size": "-1",
"name": ""
},
{
"size": "1",
"name": ""
}
],
"unknown_rank": false
},
"name": "StatefulPartitionedCall:0"
}
},
"method_name": "tensorflow/serving/predict"
},
"__saved_model_init_op": {
"inputs": {},
"outputs": {
"__saved_model_init_op": {
"dtype": "DT_INVALID",
"tensor_shape": {
"dim": [],
"unknown_rank": true
},
"name": "NoOp"
}
},
"method_name": ""
}
}
}
}
}

元数据信息中包含了该模型输入张量的数据类型和维度信息,还包含了输入张量的名称 (key) ,这里为 input_1 ,这对于后面使用数据进行模型预测十分重要,因为数据的 input_key 以及输入格式必须要与 metadata 中的定义保持一致。

模型预测

访问 http://host:port/v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}]:predict 接口可以获取指定输入数据的预测值,即给定模型的数据输入,获取模型的预测输出。 其中 /versions/${MODEL_VERSION} 是可选部分,如果不指定则会默认使用最新版本的模型进行预测。

访问该接口需要提供一个 json 格式的请求 Body ,其格式如下所示:

{
// If unspecifed default serving signature is used.
"signature_name": "string",

// Input Tensors in row ("instances") or columnar ("inputs") format.
// A request can have either of them but NOT both.
"instances": <value>|<(nested)list>|<list-of-objects>
"inputs": <value>|<(nested)list>|<object>
}

其中 signature_name 表示模型的签名,如果不指定,则默认为 serving_defaultinstancesinputs 表示模型的输入,在请求的 Body 中必须指定其中的一个,它们后接指定格式的输入数据。

instances 表示以行的形式提供模型的输入数据,其 json 格式的请求如下所示:

{
"instances": [
{
"input_1": [1, 1]
}
]
}

当模型仅有一个带名称的输入时,也可以省略 input_key ,其 json 格式的请求如下所示:

{
"instances": [[1, 1]]
}

利用 curl 并使用上述方式进行预测请求的示例如下所示:

$ curl -d '{"instances": [{"input_1": [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]}]}' -X POST http://localhost:8501/v1/models/first_model:predict
$ curl -d '{"instances": [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]}' -X POST http://localhost:8501/v1/models/first_model:predict
{
"predictions": [
[
0.778520346
]
]
}

这里响应结果中 jsonkeypredictionsvalue 值为一个列表,对应于每一个样本的输出值。由于模型只有一个输出张量,所以这里省略了输出的 keyoutput_1

inputs 表示以列的形式提供模型的输入数据,其 json 格式的请求如下所示:

{
"inputs": {
"input_1": [[1, 1]]
}
}

同样地,当模型仅有一个带名称的输入时,也可以省略 input_key ,其 json 格式的请求如下所示:

{
"inputs": [[1, 1]]
}

利用 curl 并使用上述方式进行预测请求的示例如下所示:

$ curl -d '{"inputs": {"input_1": [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]}}' -X POST http://localhost:8501/v1/models/first_model:predict
$ curl -d '{"inputs": [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]}' -X POST http://localhost:8501/v1/models/first_model:predict
{
"outputs": [
[
0.778520346
]
]
}

这里响应 jsonkeyoutputsvalue 为一个列表,对应于每一个样本的输出值。由于模型只有一个输出张量,所以这里也省略了输出的 keyoutput_1

对于有多个输入张量和多个输出张量的模型, instances 输入方式的请求和响应示例如下所示:

{
"instances": [
{
"input_1": [1, 1],
"input_2": [1, 1, 1]
},
{
"input_1": [1, 1],
"input_2": [1, 1, 1]
}
]
}
{
"predictions": [
{
"output_1": [0.431975186],
"output_2": [0.382744759, 0.32798624, 0.289268941]
},
{
"output_1": [0.431975186],
"output_2": [0.382744759, 0.32798624, 0.289268941]
}
]
}

inputs 输入方式的请求和响应示例如下所示:

{
"inputs": {
"input_1": [
[1, 1],
[1, 1]
],
"input_2": [
[1, 1, 1],
[1, 1, 1]
]
}
}
{
"outputs": {
"output_2": [[0.431975186], [0.431975186]],
"output_2": [
[0.382744759, 0.32798624, 0.289268941],
[0.382744759, 0.32798624, 0.289268941]
]
}
}

总结一下这两种输入方式, instances 的输入数据可以理解为是字典 (dict) 组成的列表 (list) ,列表中的每一个字典元素都是一个输入样本,字典的 key 为模型输入张量的名称, value 为张量对应的数值,这也是我们容易理解的输入方式;而 inputs 的输入数据可以理解为是纯字典,字典的 key 为模型输入张量的名称,而 value 是一个列表,列表中的每一个元素都是一个数据样本对应的值。另外,对于每种输入方式,其响应输出的格式也是同理。

gRPC 远程调用

除了可以使用 HTTP 接口进行模型访问外, TensorFlow Serving 还提供了 gRPC 接口来更高效地完成模型的访问请求。

目前官方仅提供了 Python 语言对应的 gRPC API ,如需使用,首先要安装与 TensorFlow Serving 版本相对应的 pythontensorflow-serving-apipip install tensorflow-serving-api==2.2.0 ,然后使用它提供的 API 接口来进行 gRPC 请求。

使用 gRPC 请求 Serving 服务的 Python 版示例代码如下所示:

import grpc
import tensorflow as tf
from absl import app, flags
import numpy as np

from tensorflow.core.framework import types_pb2
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc

flags.DEFINE_string(
'server',
'127.0.0.1:8500',
'PredictionService host:port',
)
FLAGS = flags.FLAGS

def main(_):
channel = grpc.insecure_channel(FLAGS.server)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)

request = predict_pb2.PredictRequest()
request.model_spec.name = 'first_model'
request.model_spec.signature_name = 'serving_default'
request.model_spec.version_label = "stable"
# request.model_spec.version.value = 0
data = np.ones((2, 31))
request.inputs['input_1'].CopyFrom(
tf.make_tensor_proto(data, dtype=types_pb2.DT_INT64))

request.output_filter.append('output_1')
result = stub.Predict(request, 10.0) # 10 secs timeout

print(result)
print(result.outputs["output_1"].float_val)

if __name__ == '__main__':
app.run(main)

上述代码执行流程如下:

  1. 首先要创建一个 gRPC 通道 (channel) ,并使用该通道初始化一个客户端存根 (stub) ,用于调用远程的 Predict 函数。
  2. 然后要初始化一个请求对象 request ,并且需要指定该请求对象的一些属性值,比如请求的模型名称,模型的版本以及输入张量的数据等等。
  3. 最后使用 stub 将该请求发送给 Serving 服务,并返回结果。
  4. 如果模型的 outputskey 值有多个,可以通过 output_filter 来筛选指定的输出。另外可以通过 outputfloat_val 属性来获取返回结果的数值。

执行上述代码,打印的结果如下所示:

outputs {
key: "output_1"
value {
dtype: DT_FLOAT
tensor_shape {
dim {
size: 2
}
dim {
size: 1
}
}
float_val: 0.7785203456878662
}
}
model_spec {
name: "first_model"
version {
value: 1
}
signature_name: "serving_default"
}

[0.7785203456878662, 0.7785203456878662]

在真实的线上环境中,数据预测往往是一个独立的服务,它从客户端接收数据请求,然后将数据转发给 Serving 服务以获取预测值,最后将预测的结果返回。而这项服务会涉及到客户端的高并发请求,因此我们可能不会使用 Python 框架来完成这项任务,而往往会选择一些更加成熟的,具有高性能的后端框架来做请求转发,比如 Go 语言的 ginweb 框架,它们高并发的场景下会有比较优异的表现。

为了使用其它语言的高性能框架,我们需要安装该语言的 TensorFlow Serving gRPC API 库来进行 Serving 请求,而官方并没有提供除 Python 以外的其它语言的 gPRC API ,此时就需要我们基于源码中的 .proto 文件来生成相应的 API 代码文件。

API 代码文件的生成工作可以交由 protoc 工具来完成,需要注意的是, TensorFlow Serving 源码中的部分 .proto 文件会依赖于 TensorFlow 源码中的一些 .proto 文件,所以在使用 protoc 编译代码文件时,需要将 TensorFlowTensorFlow Serving 两者的代码都拉取到本地进行统一生成。

生成了相应语言版本的 API 代码文件后,即可使用与 Python 相似的 gRPC 接口来访问 Serving 服务了。

模型本地测试

在保存了 SavedModel 格式的模型之后,除了使用 TensorFlow Serving 服务加载模型来进行验证外,还可以使用 SavedModel 命令行工具 (CLI) 来直接检查保存的模型。 CLI 可以使你快速地确认 SavedModel 中输入及输出张量的数据类型以及它们的维度是否与模型定义中的张量相匹配。另外,还可以使用 CLI 进行简单的数据测试,通过向模型传递样本数据并获取模型的输出以验证该模型的可用性。

CLI 工具一般在安装 TensorFlow 时就已经随之一起安装在系统中了,它是一个可执行程序,位于 TensorFlow 安装目录的 bin 目录下,名为 saved_model_cli 。可以直接使用 saved_model_cli -h 来查看该 CLI 工具的使用方法。

saved_model_cli 有两种常用的操作,分别为 showrun

Show 模型信息

show 操作表示查看 SavedModel 的基本信息,与访问 HTTPmetadata 接口获取元数据信息类似。它的使用方法如下所示:

$ saved_model_cli show [-h] --dir DIR [--all] [--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]
optional arguments:
-h, --help show this help message and exit
--dir DIR directory containing the SavedModel to inspect
--all if set, will output all information in given SavedModel
--tag_set TAG_SET tag-set of graph in SavedModel to show, separated by ','
--signature_def SIGNATURE_DEF_KEY key of SignatureDef to display input(s) and output(s) for

比如要查看版本号为 0first_model 模型的基本信息,可以通过如下命令获取:

$ saved_model_cli show --dir /models/first_model/0 --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
inputs['input_1'] tensor_info:
dtype: DT_INT64
shape: (-1, 31)
name: serving_default_input_1:0
The given SavedModel SignatureDef contains the following output(s):
outputs['output_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict

Run 模型运算

run 操作表示执行一次模型的计算操作,给定输入数据然后返回输出结果。它的使用方法如下所示:

saved_model_cli run [-h] --dir DIR --tag_set TAG_SET --signature_def SIGNATURE_DEF_KEY
[--inputs INPUTS]|[--input_exprs INPUT_EXPRS]|[--input_examples INPUT_EXAMPLES]
[--outdir OUTDIR]
[--overwrite]
[--tf_debug]

run 操作提供了 3 种数据输入的方式,它们分别为 --inputs--input_exprs 以及 --input_examples ,在执行 run 操作时,必须提供这 3 种输入方式中的 1 种。

  1. --inputs INPUTS 表示从文件中读取 numpy 数组作为输入数据, INPUTS 可以是 input_key=filenameinput_key=filename[variable_name] 中的任意 1 种格式, filename 的文件格式可以是 .npy.npzpickle 中的一种, saved_model_cli 会使用 numpy.load 方法去加载该文件。

    当文件格式为 .npy 时,文件中的数组会直接作为 input_key 的输入数据。

    当文件格式为 .npz 时,如果指定了 variable_name 则会加载 .npz 文件中名为的 variable_name.npy 文件作为 input_key 的输入数据,如果没有指定 variable_name 则会加载 .npz 的任意一个 .npy 文件作为 input_key 的输入数据。

    当文件格式为 pickle 时,如果指定了 variable_name 那么 saved_model_cli 会假设 pickle 文件中存储的数据为一个字典,然后读取 variable_name 对应的 value 值作为 input_key 的输入数据,如果没有指定 variable_name , 那么会将 pickle 文件中的所有内容作为 input_key 的输入数据。

  2. --input_exprs INPUT_EXPRS 表示使用 Python 的表达式作为输入数据,在需要使用一些简单的样本数据对 SavedModel 模型做测试时比较有用。其中 INPUT_EXPRS 既可以是简单的 listinput_key=[1, 1, 1] 也可以是 numpy 函数,如 input_key=np.ones((1, 3))

  3. --input_examples INPUT_EXAMPLES 表示使用 tf.train.Example 作为输入数据。其中 INPUT_EXAMPLES 的格式为 input_key=[{"age":[22,24],"education":["BS","MS"]}]input_key 的值是一个字典 (dict) 的列表 (list) ,字典的 key 为模型输入特征的名称, value 为每一个特征的数值列表 (list) 。能否使用该种数据输入方式,需要根据 SavedModel 模型的基本信息来决定。

一般而言,使用 --input_exprs INPUTS 指定输入数据是最快速、最便捷的校验 SavedModel 模型可用性的方式,其使用方法如下所示:

$ saved_model_cli run --dir /models/first_model/0 --tag_set serve --signature_def serving_default --input_expr "input_1=np.ones((1,31))"
INFO:tensorflow:Restoring parameters from /models/first_model/0/variables/variables
Result for output key output_1:
[[0.77852035]]

参考资料

  1. TensorFlow Serving with Docker
  2. TensorFlow Serving Configuration
  3. TensorFlow Serving RESTful API
  4. SavedModel Command Line Interface
  5. Python GRPC Client Example

欢迎关注我的其它发布渠道