zoukankan      html  css  js  c++  java
  • Tensorflow的部署2:TensorFlow Serving

      目录

      TensorFlow Serving 安装

      TensorFlow Serving 模型部署

      Keras Sequential 模式模型的部署

      自定义 Keras 模型的部署

      在客户端调用以 TensorFlow Serving 部署的模型

      Python 客户端示例

      Node.js 客户端示例

      当我们将模型训练完毕后,往往需要将模型在生产环境中部署。最常见的方式,是在服务器上提供一个 API,即客户机向服务器的某个 API 发送特定格式的请求,服务器收到请求数据后通过模型进行计算,并返回结果。如果仅仅是做一个 Demo,不考虑高并发和性能问题,其实配合 Flask 等 Python 下的 Web 框架就能非常轻松地实现服务器 API。不过,如果是在真的实际生产环境中部署,这样的方式就显得力不从心了。这时,TensorFlow 为我们提供了 TensorFlow Serving 这一组件,能够帮助我们在实际生产环境中灵活且高性能地部署机器学习模型。

      TensorFlow Serving 安装

      TensorFlow Serving 可以使用 apt-get 或 Docker 安装。在生产环境中,推荐 使用 Docker 部署 TensorFlow Serving 。不过此处出于教学目的,介绍依赖环境较少的 apt-get 安装 。

      首先设置安装源:

      # 添加Google的TensorFlow Serving源

      echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list

      # 添加gpg key

      curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -

      更新源后,即可使用 apt-get 安装 TensorFlow Serving

      sudo apt-get update

      sudo apt-get install tensorflow-model-server

      在运行 curl 和 apt-get 命令时,可能需要设置代理。

      curl 设置代理的方式为 -x 选项或设置 http_proxy 环境变量,即

      export http_proxy=http://代理服务器IP:端口

      或

      curl -x http://代理服务器IP:端口 URL

      apt-get 设置代理的方式为 -o 选项,即

      sudo apt-get -o Acquire::http::proxy="http://代理服务器IP:端口" ...

      Windows 10 下,可以在 Linux 子系统(WSL) 内使用相同的方式安装 TensorFlow Serving。

      TensorFlow Serving 模型部署

      TensorFlow Serving 可以直接读取 SavedModel 格式的模型进行部署(导出模型到 SavedModel 文件的方法见 前文 )。使用以下命令即可:

      tensorflow_model_server

      --rest_api_port=端口号(如8501)

      --model_name=模型名

      --model_base_path="SavedModel格式模型的文件夹绝对地址(不含版本号)"

      TensorFlow Serving 支持热更新模型,其典型的模型文件夹结构如下:

      /saved_model_files

      /1 # 版本号为1的模型文件

      /assets

      /variables

      saved_model.pb

      ...

      /N # 版本号为N的模型文件

      /assets

      /variables

      saved_model.pb

      上面 1~N 的子文件夹代表不同版本号的模型。当指定 --model_base_path 时,只需要指定根目录的 绝对地址 (不是相对地址)即可。例如,如果上述文件夹结构存放在 home/snowkylin 文件夹内,则 --model_base_path 应当设置为 home/snowkylin/saved_model_files (不附带模型版本号)。TensorFlow Serving 会自动选择版本号最大的模型进行载入。

      Keras Sequential 模式模型的部署

      由于 Sequential 模式的输入和输出都很固定,因此这种类型的模型很容易部署,无需其他额外操作。例如,要将 前文使用 SavedModel 导出的 MNIST 手写体识别模型 (使用 Keras Sequential 模式建立)以 MLP 的模型名在 8501 端口进行部署,可以直接使用以下命令:

      tensorflow_model_server

      --rest_api_port=8501

      --model_name=MLP

      --model_base_path="/home/.../.../saved" # 文件夹绝对地址根据自身情况填写,无需加入版本号

      然后就可以按照 后文的介绍 ,使用 gRPC 或者 RESTful API 在客户端调用模型了。

      自定义 Keras 模型的部署

      使用继承 tf.keras.Model 类建立的自定义 Keras 模型的自由度相对更高。因此当使用 TensorFlow Serving 部署模型时,对导出的 SavedModel 文件也有更多的要求:

      需要导出到 SavedModel 格式的方法(比如 call )不仅需要使用 @tf.function 修饰,还要在修饰时指定 input_signature 参数,以显式说明输入的形状。该参数传入一个由 tf.TensorSpec 组成的列表,指定每个输入张量的形状和类型。例如,对于 MNIST 手写体数字识别,我们的输入是一个 [None, 28, 28, 1] 的四维张量( None 表示第一维即 Batch Size 的大小不固定),此时我们可以将模型的call方法做以下修饰:

      class MLP(tf.keras.Model):

      ...

      @tf.function(input_signature=[tf.TensorSpec([None, 28, 28, 1], tf.float32)])

      def call(self, inputs):

      ...

      在将模型使用 tf.saved_model.save 导出时,需要通过 signature 参数提供待导出的函数的签名(Signature)。简单说来,由于自定义的模型类里可能有多个方法都需要导出,因此,需要告诉 TensorFlow Serving 每个方法在被客户端调用时分别叫做什么名字。例如,如果我们希望客户端在调用模型时使用 call 这一签名来调用 model.call 方法时,我们可以在导出时传入 signature 参数,以 dict 的键值对形式告知导出的方法对应的签名,代码如下:

      model = MLP()

      ...

      tf.saved_model.save(model, "saved_with_signature/1", signatures={"call": model.call})

      以上两步均完成后,即可使用以下命令部署:

      tensorflow_model_server

      --rest_api_port=8501

      --model_name=MLP

      --model_base_path="/home/.../.../saved_with_signature" # 修改为自己模型的绝对地址

      在客户端调用以 TensorFlow Serving 部署的模型

      TensorFlow Serving 支持以 gRPC 和 RESTful API 调用以 TensorFlow Serving 部署的模型。本手册主要介绍较为通用的 RESTful API 方法。

      RESTful API 以标准的 HTTP POST 方法进行交互,请求和回复均为 JSON 对象。为了调用服务器端的模型,我们在客户端向服务器发送以下格式的请求:

      服务器 URI: http://服务器地址:端口号/v1/models/模型名:predict

      请求内容:

      {

      "signature_name": "需要调用的函数签名(Sequential模式不需要)",

      "instances": 输入数据

      }

      回复为:

      {

      "predictions": 返回值

      }

      Python 客户端示例

      以下示例使用 Python 的 Requests 库 (你可能需要使用 pip install requests 安装该库)向本机的 TensorFlow Serving 服务器发送 MNIST 测试集的前 10 幅图像并返回预测结果,同时与测试集的真实标签进行比较。

      import json

      import numpy as np

      import requests

      from zh.model.utils import MNISTLoader

      data_loader = MNISTLoader()

      data = json.dumps({

      "instances": data_loader.test_data[0:3].tolist()

      })

      headers = {"content-type": "application/json"}

      json_response = requests.post(

      'http://localhost:8501/v1/models/MLP:predict',

      data=data, headers=headers)

      predictions = np.array(json.loads(json_response.text)['predictions'])

      print(np.argmax(predictions, axis=-1))

      print(data_loader.test_label[0:10])

      输出:

      [7 2 1 0 4 1 4 9 6 9]

      [7 2 1 0 4 1 4 9 5 9]

      可见预测结果与真实标签值非常接近。

      对于自定义的 Keras 模型,在发送的数据中加入 signature_name 键值即可,即将上面代码的 data 建立过程改为

      data = json.dumps({

      "signature_name": "call",

      "instances": data_loader.test_data[0:10].tolist()

      })

      Node.js 客户端示例

      以下示例使用 Node.js 将下图转换为 28*28 的灰度图,发送给本机的 TensorFlow Serving 服务器,并输出返回的预测值和概率。(其中使用了 图像处理库 jimp 和 HTTP 库 superagent ,可使用 npm install jimp 和 npm install superagent 安装)

      

    在这里插入图片描述

      const Jimp = require('jimp')

      const superagent = require('superagent')

      const url =

      const getPixelGrey = (pic, x, y) => {

      const pointColor = pic.getPixelColor(x, y)

      const { r, g, b } = Jimp.intToRGBA(pointColor)

      const gray = +(r * 0.299 + g * 0.587 + b * 0.114).toFixed(0)

      return [ gray / 255 ]

      }

      const getPicGreyArray = async (fileName) => {

      const pic = await Jimp.read(fileName)

      const resizedPic = pic.resize(28, 28)

      const greyArray = []

      for ( let i = 0; i< 28; i ++ ) {

      let line = []

      for (let j = 0; j < 28; j ++) {

      line.push(getPixelGrey(resizedPic, j, i))

      }大连做人流哪家好 http://mobile.dlrlyy.com/

      console.log(line.map(_ => _ > 0.3 ? ' ' : '1').join(' '))

      greyArray.push(line)

      }

      return greyArray

      }

      const evaluatePic = async (fileName) => {

      const arr = await getPicGreyArray(fileName)

      const result = await superagent.post(url)

      .send({

      instances: [arr]

      })

      result.body.predictions.map(res => {

      const sortedRes = res.map((_, i) => [_, i])

      .sort((a, b) => b[0] - a[0])

      console.log(`我们猜这个数字是${sortedRes[0][1]},概率是${sortedRes[0][0]}`)

      })

      }

      evaluatePic('test_pic_tag_5.png')

      运行结果为:

      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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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

      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 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 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

      我们猜这个数字是5,概率是0.846008837

      可见输出结果符合预期。

      如果你不熟悉 HTTP POST,可以参考 这里 。事实上,当你在用浏览器填写表单(比方说性格测试)并点击 “提交” 按钮,然后获得返回结果(比如说 “你的性格是 ISTJ”)时,就很有可能是在向服务器发送一个 HTTP POST 请求并获得了服务器的回复。

      RESTful API 是一个流行的 API 设计理论,可以参考 这里 获得简要介绍。

  • 相关阅读:
    c++ 容器学习 理论
    TCP和UDP发送数据包的大小问题
    key.go
    election.go
    watch.go
    txn.go
    sort.go
    retry.go
    op.go
    maintenance.go
  • 原文地址:https://www.cnblogs.com/djw12333/p/14518646.html
Copyright © 2011-2022 走看看