Tensorflow Serving是为训练好的模型提供对外rpc调用的接口服务器,它能够检测模型的最新版本并自动加载,使得模型到生产环境的部署更为便利。不得不说这个是一个非常好的工具,既免去了模型服务化的开发工作,又保证了迭代训练的模型能够快速上线。记得本博主去年还专门做过一段时间caffe模型的服务化,封装人脸识别和物体识别的http接口,一开始写起来还是挺麻烦的。最近团队内部NLP的模型训练切换到了Tensorflow, 于是乎便简单体验了一下Tensorflow Serving,这里做个简单的总结。
安装Tensorflow Serving。目前,Tensorflow Serving只能通过编译方式来安装,具体方法和依赖在官方安装文档上说的很清楚,就不赘述了。这里只列一下本博主在编译时遇到的一个错误:stropts.h: No such file or directory。头文件stropts.h是POSIX XSR的一部分,因为linux不支持STREAMS,所以缺少这个文件。解决办法很简单,在/usr/include目录下创建一个空的stropts.h文件即可。不过为了说明情况,可以在这个空文件中加一些注释来说明为什么创建了这么一个空的头文件,以避免不必要的误会。
完成Tensorflow Serving的编译安装后,在bazel-bin/tensorflow_serving/model_servers/目录下可以看到一个名为tensorflow_model_server的二进制可执行文件,这个文件就是Tensorflow Serving接口服务器了。Tensorflow Serving接口服务器目前提供了两种工作方式:单模型方式和多模型方式。前者指的就是服务器启动时只加载一个模型,而后者则是加载多个模型。为了展示这两种不同的工作方式,我们不妨准备一些模型数据,来看看具体效果。
首先,我们为官方教程中的MNIST导出两个版本的模型,以验证Tensorflow Serving对于多版本模型的管理,其中版本1在训练时进行100次迭代,而版本2在训练时进行2000次迭代,所以 版本2的分类准确度应该好于版本1,具体导出方法如下:
# bazel-bin/tensorflow_serving/example/mnist_saved_model --training_iteration=100 --model_version=1 /tmp/mnist_model
# bazel-bin/tensorflow_serving/example/mnist_saved_model --training_iteration=2000 --model_version=2 /tmp/mnist_model
# ls /tmp/mnist_model
1 2
其次,我们再为官方教程中的Inception-v3导出一个模型,以验证Tensorflow Serving对于多个模型的管理。为了简单起见,这里直接基于一个已经训练好的checkpoint来导出模型,具体导出方法如下:
# curl -O http://download.tensorflow.org/models/image/imagenet/inception-v3-2016-03-01.tar.gz
# tar xzf inception-v3-2016-03-01.tar.gz
# ls inception-v3
README.txt checkpoint model.ckpt-157585
# bazel-bin/tensorflow_serving/example/inception_saved_model --checkpoint_dir=inception-v3 --output_dir=/tmp/inception-model
Successfully loaded model from inception-v3/model.ckpt-157585 at step=157585.
Successfully exported model to /tmp/inception-model
# ls /tmp/inception-model
1
准备好以上模型文件之后,我们就可以启动Tensorflow Serving接口服务器来验证效果。单模型方式的启动方法如下,通过日志可以看到接口服务器自动加载了最新版本的模型version2(因为--model_version_policy参数默认为LATEST_VERSION,即仅加载使用最新版本,该选项的另一个可选值为ALL_VERSIONS,即加载使用所有版本)。
# bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server --port=9000 --model_name=mnist --model_base_path=/tmp/mnist_model/
2017-04-05 14:44:55.196611: I tensorflow_serving/model_servers/main.cc:157] Building single TensorFlow model file config: model_name: mnist model_base_path: /tmp/mnist_model/ model_version_policy: 0
2017-04-05 14:44:55.197071: I tensorflow_serving/model_servers/server_core.cc:338] Adding/updating models.
...
2017-04-05 14:44:55.411398: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: mnist version: 2}
2017-04-05 14:44:55.470798: I tensorflow_serving/model_servers/main.cc:357] Running ModelServer at 0.0.0.0:9000 ...
执行MNIST的客户端脚本来调用接口服务器,验证接口功能是否正常,调用结果如下,该模型版本最终的分类错误率为4.0%。
# bazel-bin/tensorflow_serving/example/mnist_client --server localhost:9000
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
....................................................................................................
Inference error rate: 4.0%
现在我们把/tmp/mnist_model/目录下的版本2模型数据移走,此时通过Tensorflow Serving接口服务器的日志可知,版本1模型自动加载,而版本2模型自动卸载。再次执行MNIST的客户端脚本来调用接口服务器,分类错误率变成了10.0%,即目前使用的已经是版本1的模型了。如果我们再把版本2的模型数据拷贝回来的话,那么又会自动加载版本2并卸载版本1。由此可见Tensorflow Serving在管理多版本模型时非常方便,一旦模型有了新版本直接放到模型数据目录下,其余的就交给Tensorflow Serving好了。
# mv /tmp/mnist_model/2 /tmp/
2017-04-05 15:04:32.136596: I tensorflow_serving/core/basic_manager.cc:698] Successfully reserved resources to load servable {name: mnist version: 1}
...
2017-04-05 15:04:32.168375: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: mnist version: 1}
2017-04-05 15:04:32.236408: I tensorflow_serving/core/loader_harness.cc:137] Quiescing servable version {name: mnist version: 2}
...
2017-04-05 15:04:32.238976: I tensorflow_serving/core/loader_harness.cc:127] Done unloading servable version {name: mnist version: 2}
# bazel-bin/tensorflow_serving/example/mnist_client --server localhost:9000
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
....................................................................................................
Inference error rate: 10.0%
看完了单模型方式,我们再来看看多模型方式,针对这种方式Tensorflow Serving的官方文档还没有给出使用示例,但本博主在它的github issues中搜索到了。为了在启动时加载多个模型,需要一个配置文件,假定这里要同时加载MNIST和Inception-v3两个模型,那么配置文件内容如下所示:
# cat tfserv.conf
model_config_list: {
config: {
name: "mnist",
base_path: "/tmp/mnist_model",
model_platform: "tensorflow"
},
config: {
name: "inception",
base_path: "/tmp/inception_model",
model_platform: "tensorflow"
}
}
相应的,Tensorflow Serving的启动方式如下,即通过--model_config_file选项指定多个模型的配置文件。此时通过日志可以看到,接口服务器成功加载了两个模型,
# bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server --port=9000 --model_config_file=tfserv.conf
2017-04-05 15:26:42.144933: I tensorflow_serving/model_servers/server_core.cc:338] Adding/updating models.
2017-04-05 15:26:42.145066: I tensorflow_serving/model_servers/server_core.cc:384] (Re-)adding model: mnist
2017-04-05 15:26:42.145097: I tensorflow_serving/model_servers/server_core.cc:384] (Re-)adding model: inception
...
2017-04-05 15:26:42.528413: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:274] Loading SavedModel: success. Took 82460 microseconds.
2017-04-05 15:26:42.528478: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: mnist version: 1}
...
2017-04-05 15:26:43.915144: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:274] Loading SavedModel: success. Took 1368851 microseconds.
2017-04-05 15:26:43.915346: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: inception version: 1}
2017-04-05 15:26:45.184006: I tensorflow_serving/model_servers/main.cc:357] Running ModelServer at 0.0.0.0:9000 ...
分别执行MNIST的客户端脚本和Inception的客户端脚本来调用接口服务器,结果说明两个模型的接口均可以成功调用,所以Tensorflow Serving可以通过加载多个模型的方式对外提供多种模型接口,只要客户端标明自己要调用的模型名称即可。
# bazel-bin/tensorflow_serving/example/mnist_client --server localhost:9000
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
....................................................................................................
Inference error rate: 10.0%
# bazel-bin/tensorflow_serving/example/inception_client --server localhost:9000 --image dog.jpg
outputs {
key: "classes"
value {
dtype: DT_STRING
tensor_shape {
dim {
size: 1
}
dim {
size: 5
}
}
string_val: "Labrador retriever"
string_val: "German short-haired pointer"
string_val: "Rottweiler"
string_val: "Doberman, Doberman pinscher"
string_val: "Appenzeller"
}
}
outputs {
key: "scores"
value {
dtype: DT_FLOAT
tensor_shape {
dim {
size: 1
}
dim {
size: 5
}
}
float_val: 9.16056632996
float_val: 5.29548358917
float_val: 5.13606595993
float_val: 4.43354558945
float_val: 3.943359375
}
}
实际上,通过查看源码不难得知,上文所谓的单模型方式和多模型方式对于Tensorflow Serving来说都是一样的,以下为文件serving/tensorflow_serving/model_servers/main.cc中main函数中一段代码,对于只提供--model_name和--model_base_path两个参数的单模型方式,Tensorflow Serving会使用BuildSingleModelConfig函数构造出类似于配置文件中的内容,所以最终对于模型的管理都是一样的。
if (model_config_file.empty()) {
options.model_server_config = BuildSingleModelConfig(
model_name, model_base_path, parsed_version_policy);
} else {
options.model_server_config =
ReadProtoFromFile(model_config_file);
}
需要注意的是,无论是单模型方式还是多模型方式,其本质都是一种静态的模型管理方式,即需要加载哪些模型是在Tensorflow Serving启动时静态配置的。但是在Tensorflow Serving的高级教程中有如下一段描述,即Tensorflow Serving既可以通过model_config_list(也就是上文的 配置文件)静态方式配置要加载的模型,也可以通过dynamic_model_config方式在运行期间动态指定要加载的模型。毫无疑问,动态加载模式显然更加灵活,但目前本博主还没有找到任何资料介绍如何使用这种方式,对于这个问题感兴趣的看客们可以持续关注我在github上提的这个issue,如果知道如何使用dynamic_model_config的话,希望不吝赐教啊!
ModelServerConfig that specifies models to be loaded. Models are declared either through model_config_list, which declares a static list of models, or through dynamic_model_config, which declares a dynamic list of models that may get updated at runtime.
因为在我们的业务中确实有对Tensorflow Serving动态加载模型的需要,在对dynamic_model_config调研无果后,本博主修改了Tensorflow Serving的源码,为其添加了第三种工作方式。在这种工作方式下Tensorflow Serving会监听一个模型数据目录,该目录将模型名称 作为子目录来组织模型数据。在Tensorflow Serving运行过程中,一旦发现该目录下多了一个新的模型子目录,就对其进行加载操作,目前实测可行。
本文转载自:http://blog.sina.com.cn/s/blog_48c95a190102wr8o.html