先写中文吧。

Tensorflow Serving是google tensorflow开源项目下的一个子项目,官方的对Tensorflow Serving的介绍如下:

TensorFlow Serving is a flexible, high-performance serving system for machine learning models, designed for production environments.

可以看到,TF serving设计宗旨就是高性能,高灵活性,为生产环境而生。所以当你已经有了训练好的模型,想要和应用系统进行集成的时候,可以考虑使用TF serving。对tensorflow模型,TF serving基本做到开箱即用,使用它可以帮你省去一些API开发成本。所以如果你正在使用tensorflow,那我强烈介意你了解一下TF serving。

要学习使用TF serving,最好的材料当然还是官方文档,但是在实践过程中我或多或少遇到一些问题,这里把他们整理下来作为对官方文档做一个补充。本教程不是使用官方教程中MNIST手写数字识别模型,而是使用基于Facenet的人脸特征提取模型为例,但是TF serving部分基本一致。

当你已经有了一个可用的模型一般只需要做下面两件事情即可将它部署到TF serving上,不过一般模型部署好之后还需要用一个客户端程序能够访问调用模型服务,所以本教程分三步:

  1. 将模型导出成SavedModel,一种TF serving使用的模型格式。
  2. 安装TF serving,用正确的参数将它启动。
  3. 开发一个简易客户端来调用模型服务

1. 将模型导出成SavedModel

官方文档中的示例代码给出了一个完整的模型创建和导出过程,但是如果你已经有了一个模型,你需要的只是以下几行代码(这段代码摘取自我的人脸识别项目中的convert_pretrained_model_to_savedModel.py,他将一个预先训练好的模型转换成TF serving可用的模型):

  # export model to savedmodel

  # This is an path string like './model/1'
  export_model_path = os.path.join(EXPORT_PATH, str(MODEL_VERSION))  
  print('Model saved to:', export_model_path)
  builder = tf.saved_model.builder.SavedModelBuilder(export_model_path)

  # SavedModel need tensor info like dtype and shape, this method build tensor_info protobuf for us.
  # For we need build tensor_info protobuf for all the input and output tensor.
  tensor_info_x = tf.saved_model.utils.build_tensor_info(images_placeholder)
  tensor_info_train = tf.saved_model.utils.build_tensor_info(phase_train_placeholder)
  tensor_info_y = tf.saved_model.utils.build_tensor_info(embeddings)

  # a signature here is like a method signature used for gRPC call.
  # we need specify the input, out, put and method name.
  prediction_signature = (
      tf.saved_model.signature_def_utils.build_signature_def(
          inputs={'images': tensor_info_x, 'is_training': tensor_info_train},
          outputs={'scores': tensor_info_y},
          method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))
  

  # build the model with previous signatrue, I only have one signature, so I make it the default one.               
  # the legacy_init_op is used for compatibility with model generated by tf<1.2
  legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
  builder.add_meta_graph_and_variables(
      sess, [tf.saved_model.tag_constants.SERVING],
      signature_def_map={
          tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
              prediction_signature,
      },
      legacy_init_op=legacy_init_op)

  builder.save()

当模型导出成功之后,你的模型目录应该如下图所示:

导出模型目录

2. 安装,启动TF serving

这一部分比较复杂,我记得我去年尝试使用TF serving时就卡在了这一步,因为当时我的电脑没有足够的内存来编译它。然后去年Google中国开发者大会时我跟他们提了没有预编译的TF serving这个事,他们的主管很震惊还表示一定会把事情提上优先级。不过这些都是过去,现在是2018了,这些问题也都已经得到解决。

如果你的电脑配置不错,可以直接按照官方文档,下载Bazel和各种依赖,然后自己编译TF serving。但是如果你和我一样使用的是笔记本,那么docker+安装预编译包会是一个很好的选择。这一部分教程需要你有一点点Docker知识,教程基于我在Mac OS 上的操作,其他系统可能会有些许不同。

  1. 首先下载基础Docker文件,理论上我们可以使用任何基于Ubuntu系统的docker,这里我使用Google提供的docker是为了确保不会有依赖缺失问题。
  2. 因为下载的知识一个docker file,我们需要在本地build docker image。在Dockerfile.devel所在文件路径下运行(不要丢掉命令最后的.

     docker build --pull -t $USER/tensorflow-serving-devel -f Dockerfile.devel .
    
  3. 用交互模式运行编译好的docker镜像:

     docker run -it -p 9000:9000 $USER/tensorflow-serving-devel
    
  4. 在启动的docker中,使用apt-get安装预编译的TF serving:

     echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | tee /etc/apt/sources.list.d/tensorflow-serving.list
          
     curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | apt-key add -
          
     apt-get update && apt-get install tensorflow-model-server
    
  5. 在docker外将刚刚生成的SavedModel复制到docker中,在terminal中运行如下代码:

     docker cp ~/PROJECTS/python/Face-recognize-system/fake_facenet/tf_serving_model/ {name_of_your_container}:/temp
    
    
  6. 最后一步,启动TF serving,并将生成的模型指定为提供服务的模型,这里的facenet是模型的名称,可以任意指定,只要和客户端调用时使用相同字符串即可。

     tensorflow_model_server --port=9000 --model_name='facefeature' --model_base_path=/temp/
    

到这里我们已经有了一个可以使用的TF serving 服务运行在本机的9000端口上。

3. 构建一个可以调用TF serving的python客户端程序

TF serving使用gRPC进行远程调用,客户端可以使用TF serving中定义的proto文件在任何支持gRPC的语言下对其进行调用。不过在python中我们不需要自己导入proto,因为TF serving为我们提供了python的API供我们调用,我们需要做的只是安装tensorflow-serving-api库。 目前,tensorflow-serving-api官方只支持python2,但是社区开发者已经证明当前1.0.0版本的库也可以在python3下面使用,不过在python3下不能直接使用pip安装它,而是要手动将软件包下载下来解压后拷贝到python的site-package,目录下。关于tensorflow-serving-api安装,可以参考这里

安装完成后,我们可以用下面这段程序来调用TF serving库,由于代码涉及到gRPC调用比较复杂,特加入详细中文注释(这段代码摘取自我的人脸识别项目中的tf_serving_client_demo.py

```python
# 服务器IP和端口号
host, port = '127.0.0.1', '9000'

# 创建一个stub用来代表要调用的远程服务器,调用stub命令时相当于调用TF serving中的方法
channel = implementations.insecure_channel(host, int(port))
stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)

# 根据ProtoBuf 创建请求对象,这里通过 TF servingAPI 快速创建一个Predict的请求
request = predict_pb2.PredictRequest()

# 指定模型名称,需要和我们启动TF serving 服务器时指定的名称相同
request.model_spec.name = 'facenet'


# 这里快速构建一个需要预测的图像,为了方面,直接截图图像的左上角
input_size = 160 #inception-resnet v1 模型的输入图像尺寸
picture = misc.imread('./resources/full_body_zh1.jpg')[:input_size, :input_size, :]
picture = picture.reshape([1, input_size, input_size, 3])
picture = picture.astype(np.float32)

# 下面代码为指定调用方法名(由于我们的模型只有一个默认方法,这里不需要指定方法名,所以注释掉)
# request.model_spec.signature_name = "serving_default"  

# 初始化输入参数,所有参数都要转换成对应的protobuf以在网络间传输,这里tf.contrib.util帮我们自动完成这些转换
request.inputs['images'].CopyFrom(
    tf.contrib.util.make_tensor_proto(picture, shape=[1, input_size, input_size, 3]))
request.inputs['is_training'].CopyFrom(tf.contrib.util.make_tensor_proto(False))
# 调用predict方法,该方法会请求TF serving服务器
result = stub.Predict(request, 10.0)  # 10 为超时时间,单位为妙

# 得到返回结果,可以根据需求做进一步处理
print(np.array(result.outputs['scores'].float_val).shape)
```

到这里我们就有了一个功能相对完备的TF serving服务和客户端,之后模型升级,只需要把对应版本的模型文件拷贝进来即可。其实TF serving更像是一个Google提供给我们的模型服务器,我们只需生成相应的模型,然后使用它即可。

TF serving是一个为生产环境和高性能而开发的,需要为不同硬件进行针对性(如CPU指令集,图形加速卡等)性能优化,所以采用编译安装本身是一件合乎清理的事情。 不过对于我来说,使用TF serving的难点一直都在于编译安装。记得去年初次尝试时,TF serving要求最小16g的内存才能编译,这直接将我这种用比起本启虚拟机的开发者挡在门外。 现在通过提供预编译包和docker镜像,安装过程变得简单了许多。 所以如果你也对TF serving感兴趣,现在正式时候。