tf2.0 模型保存与1.0_Tensorflow2.0模型部署(python)

Python编程三剑客:Python编程从入门到实践第2版+快速上手第2版+极客编程(套装共3册)
作者:[美] 埃里克·马瑟斯(Eric Matthes)
出版社:人民邮电出版社

好评:100.0%

销售量:20
¥149
更多

模型训练完后,往往需要将模型应用到生产环境中。最常见的就是通过TensorFlow Serving来将模型部署到服务器端,以便客户端进行请求访问。

  1. TensorFlow Serving 安装

TensorFlow Serving一般安装在服务器端,最为方便,推荐在生产环境中 使用 Docker 部署 TensorFlow Serving 。当然也可以通过apt-get 安装 。这里我主要使用的前者。

首先安装docker,然后拉取最新的Tensorflow Serving镜像。

docker pull tensorflow/serving
2. 模型部署

2.1 Keras Sequential 模式模型的部署(单输入,单输出)

2.1.1 模型构建

由于Keras Sequential 模式的输入输出形式比较固定单一,所以这里简单的构造一个Sequential 模型。

import tensorflow as tf
import os
model = tf.keras.Sequential([tf.keras.layers.Dense(1)])
2.1.2 模型导出

模型构造好后,开始进行模型的导出。 由于这里只是示例,不进行数据输入训练等操作,通过TF2.0 中eager的特性来初始化模型。

version = ‘1’ #版本号
model_name = ‘sequential_model’
saved_path = os.path.join(‘models’,model_name)
data = tf.ones((2, 10))
model(datas
model.save(os.path.join(saved_path,version)) # models/sequential_model/1
注意:tensorflow Serving支持热更新,每次默认选取版本 version最大的版本号进行部署。因此,我们在保存模型的路径上需要加上指定的 version。
保存后的文件目录结构如下:

└── sequential_model
├── 1
│ ├── assets
│ ├── saved_model.pb
│ └── variables
│ ├── variables.data-00000-of-00001
│ └── variables.index
└── 2
├── assets
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index
接着在终端中,通过以下命令,查看保存的模型结构

saved_model_cli show --dir models/sequential_model/1 --all
终端输出 重点关注下面信息:

signature_def[‘serving_default’]:
The given SavedModel SignatureDef contains the following input(s):
inputs[‘dense_input’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_dense_input:0
The given SavedModel SignatureDef contains the following output(s):
outputs[‘dense’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict
其中 inputs[‘dense_input’] 中的 dense_input 是请求 TensorFlow Serving服务时所需的 输入名 outputs[‘dense’] 中的dense是TensorFlow Serving服务 返回的输出名 ( 当outputs只有一个时默认 输出名为 outputs)

2.1.3 docker部署

接下来我们运行Docker中的tensorflow Serving 进行部署:

docker run -t --name sequential_model -p 8501:8501
–mount type=bind,source=/root/models,target=/models
-e MODEL_NAME=sequential_model tensorflow/serving &
解释: --name 定义容器的名字 -p 8051:8051 指的是 本地端口:容器内部端口 的映射。(注:tensorflow Serving 默认开启的是8501端口,如需修改则需进入容器中手动指定 --rest_api_port) --mount type=bind, source=/root/models ,target=/models 指的是将本地/root/models 目录 映射到 docker容器中/models目录 -e MODEL_NAME=sequential_model 这里指的环境名称MODEL_NAME,tensorflow Serving会自动索引容器中/models/MODEL_NAME 目录下的模型文件

上面的docker 命令 相当于在容器中 执行下面命令。

tensorflow_model_server
–rest_api_port=8501
–model_name=sequential_model
–model_base_path= /models/sequential_model &
因此,也可以仅启动容器,设置端口映射,目录映射后 ,进入容器 通过自己手动启动tensorflow_model_server 来进行更多的自定义。

2.1.4 请求服务

终端中可以通过curl进行请求

curl -d ‘{“inputs”: {“dense_input”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]]}}’
-X POST http://localhost:8501/v1/models/sequential_model:predict
这里的dense_input 就是前面saved_model_cli在显示的输入名。

返回 由于只是单输出,所以这里隐藏了输出名,默认为outputs, 与saved_model_cli中输出名有点区别。

{
“outputs”: [
[
0.736189902
]
]
}
2.2 Keras Model函数式模型的部署(多输入,多输出)

由于Sequential 模式模型的输入输出过于单一,在模型构建方面有天生的弱势。 这里介绍下Keras Model函数式模型多输入多输出 的部署。

2.2.1 模型构建

重点注意下,定义的 name 属性。
import tensorflow as tf
import os
input_1 = tf.keras.layers.Input(shape=(10,), dtype=tf.float32, name=‘a’)
input_2 = tf.keras.layers.Input(shape=(10,), dtype=tf.float32, name=‘b’)
output_1 = tf.keras.layers.Dense(1, name=‘dense_1’)(input_1 + input_2)
output_2 = tf.keras.layers.Dense(1, name=‘dense_2’)(input_1 - input_2)
model = tf.keras.Model(inputs=[input_1, input_2], outputs=[output_1, output_2])
2.2.2 模型导出

这里依然通过eager的特性进行模型的初始构建。

version = ‘1’ #版本号
model_name = ‘keras_functional_model’
saved_path = os.path.join(‘models’,model_name)
data1= tf.ones((2,10),dtype=tf.float32)
data2= tf.ones((2,10),dtype=tf.float32)
model((data1,data2)) #model({‘a’:data1,‘b’:data2}) 两种方法都可以
model.save(os.path.join(saved_path,version)) # models/keras_functional_model/1
接下来我们查看下保存模型的内部结构

saved_model_cli show --dir models/keras_functional_model/1 --all
signature_def[‘serving_default’]:
The given SavedModel SignatureDef contains the following input(s):
inputs[‘a’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_a:0
inputs[‘b’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_b:0
The given SavedModel SignatureDef contains the following output(s):
outputs[‘dense_1’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall:0
outputs[‘dense_2’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: StatefulPartitionedCall:1
Method name is: tensorflow/serving/predict
前面keras Sequential 模式中,输入名和输出名,是系统根据 变量名自动生成的。 在Keras Model函数式模型 中 我们可以通过定义name属性来设置输入名和输出名。

2.2.3 docker部署

由于前面sequential_model 占用了本地8501端口,这里使用8502端口作为本地端口映射,tensorflow serving默认开启8501 所以内部端口8501 不用修改。

docker run -t --name keras_functional_model -p 8502:8501
–mount type=bind,source=/root/models,target=/models
-e MODEL_NAME=keras_functional_model tensorflow/serving &
2.2.4 请求服务

终端中可以通过curl进行请求

curl -d ‘{“inputs”: {“a”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]],“b”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]]}}’ /
-X POST http://localhost:8502/v1/models/keras_functional_model:predict
返回

由于这里是多输出,返回的同时会加上输出名。与Keras Sequential模式有区别。

{
“outputs”: {
“dense_1”: [
[
0.251481533
]
],
“dense_2”: [
[
0.0
]
]
}
}%
2.3 自定义 Keras 模型的部署 (多输入+mode,多输出)

如果Keras Sequential 模式 和 Keras Model函数式 模式 无法满足复杂模型需求怎么办?别担心,最大杀器自定义 Keras 模式可以帮你解决一切。

设想下,我们如果需要通过 mode,来控制模型的运行流程,这样的模型我们该怎么导出部署呢? 当mode=‘train’ 时,输出a。 当mode='predict’时,输出b。

2.3.1 方法一

2.3.1.1 模型构建

import tensorflow as tf
import os
class MyModel(tf.keras.Model):
def init(self, **kwargs):
super(MyModel, self).init(**kwargs)
self.dense_1 = tf.keras.layers.Dense(10)
self.dense_2 = tf.keras.layers.Dense(1)
@tf.function
def call(self, inputs):
a, b, mode = inputs
hidden = self.dense_1(a + b)
logits = self.dense_2(hidden)
if mode == ‘predict’:
return hidden, mode
return logits, mode
2.3.1.2 模型导出

这里依然通过eager的特性进行模型的初始构建。

version = ‘1’ #版本号
model_name = ‘custom_model’
saved_path = os.path.join(‘models’,model_name)
model = MyModel()
a = tf.ones((2, 10))
b = tf.ones((2, 10))
mode = ‘train’
mode = tf.cast(mode, tf.string) #这里的strin 必须转换成tf.string类型。
print(model((a, b, mode)))
model.save(os.path.join(saved_path,version))
这里由于未显示的指定,输入的形状、类型、名字等。模型根据输入的数据进行自动推断的。因此输入的数据 必须是 tf的dtype类型。所以,在输入字符串时需要转换成tf.string。 虽然,在我们平时训练模型时没有什么问题,但在导出模型时,会出现

TypeError: Invalid input_signature ; input_signature must be a possibly nested sequence of TensorSpec objects.
通过saved_model_cli对模型进行查看。

saved_model_cli show --dir models/keras_functional_model/1 --all
由于,并没有指定输入的名字,这里都由系统根据输入顺序,输出顺序自动命名,输入就是input_n,输出就是output_n.

signature_def[‘serving_default’]:
The given SavedModel SignatureDef contains the following input(s):
inputs[‘input_1’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_input_1:0
inputs[‘input_2’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_input_2:0
inputs[‘input_3’] tensor_info:
dtype: DT_STRING
shape: ()
name: serving_default_input_3:0
The given SavedModel SignatureDef contains the following output(s):
outputs[‘output_1’] tensor_info:
dtype: DT_FLOAT
shape: (-1, -1)
name: StatefulPartitionedCall:0
outputs[‘output_2’] tensor_info:
dtype: DT_STRING
shape: ()
name: StatefulPartitionedCall:1
Method name is: tensorflow/serving/predict
下面介绍下比较规范的自定义 Keras 模型 导出

2.3.2 方法二

2.3.2.1 模型构建

import tensorflow as tf
import os
class MyModel(tf.keras.Model):
def init(self, **kwargs):
super(MyModel, self).init(**kwargs)
self.dense_1 = tf.keras.layers.Dense(10)
self.dense_2 = tf.keras.layers.Dense(1)

@tf.function(input_signature=[(tf.TensorSpec([None, 10], name='a', dtype=tf.float32),
                               tf.TensorSpec([None, 10], name='b', dtype=tf.float32),
                               tf.TensorSpec([], name='mode', dtype=tf.string))])
def call(self, inputs):
    a, b, mode = inputs
    hidden = self.dense_1(a + b)
    logits = self.dense_2(hidden)
    if mode == 'predict':
        return hidden, mode
    return logits, mode

与 方法一 不同的是,我们通过显示的定义了inputs 需要输入的类型,形状,类别,名字。这样一来,模型就知道我们要输入的是什么了。

注意:在构建类似上面面多分支输出模型时,需要保持各分支输出的 变量类型,变量数量 一致,否则模型导出会抛出异常。
如:上面分支模型,当 mode==‘train’ 和 mode=='predict’时,return返回的都是 tf.float32和 tf.string类型,且都是两个变量。若出现变量类型,变量数量 不一致,则会提示 TypeError python TypeError: ‘retval_’ must have the same nested structure in the main and else branches:… 注意:看到这里,细心的可能会发现,我在编写call()函数时,不管是单输入,还是多输入,我都是通过解包的方式进行输入,并没有改动 inputs 这个变量。我们在训练模型过程中,经常会直接显示的指明变量,比如 上面的call可以进行改写 def call(self,inputs): ----> def call(self, a,b,mode): 虽然,在train的过程中不会出错,但在部署导出模型时,会发生未知错误。 因此,推荐 解包的方式进行输入变量。
2.3.2.2 模型导出

version = ‘2’ #版本号
model_name = ‘custom_model’
saved_path = os.path.join(‘models’,model_name)
model = MyModel()
a = tf.ones((2, 10))
b = tf.ones((2, 10))
mode = ‘train’
print(model((a, b, mode)))
model.save(os.path.join(saved_path,version))
这里,我们也无需将mode手动转换成tf.string了,一切都由模型自动完成。 再通过saved_model_cli来查看下模型信息。

saved_model_cli show --dir models/keras_functional_model/2 --all
由于我们在tf.function中指定了,输入名,因此这里输入名都发生了改变。

signature_def[‘serving_default’]:
The given SavedModel SignatureDef contains the following input(s):
inputs[‘a’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_a:0
inputs[‘b’] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10)
name: serving_default_b:0
inputs[‘mode’] tensor_info:
dtype: DT_STRING
shape: ()
name: serving_default_mode:0
The given SavedModel SignatureDef contains the following output(s):
outputs[‘output_1’] tensor_info:
dtype: DT_FLOAT
shape: (-1, -1)
name: StatefulPartitionedCall:0
outputs[‘output_2’] tensor_info:
dtype: DT_STRING
shape: ()
name: StatefulPartitionedCall:1
Method name is: tensorflow/serving/predict
2.3.3 docker部署

由于前面8501 8502 端口被占用,这里我们启用8503端口进行映射。

docker run -t --name custom_model -p 8503:8501
–mount type=bind,source=/root/models,target=/models
-e MODEL_NAME=custom_model tensorflow/serving &
通过返回的信息,我们可以看见,模型成功的加载了 version=‘2’ 的模型文件。tensorflow serving 的热部署默认加载最大的版本号模型。

2020-08-23 14:13:13.477881: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:199] Restoring SavedModel bundle.
2020-08-23 14:13:13.491605: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:183] Running initialization op on SavedModel bundle at path: /models/custom_model/2
2020-08-23 14:13:13.495698: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:303] SavedModel load for tags { serve }; Status: success: OK. Took 41678 microseconds.
2020-08-23 14:13:13.496311: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/custom_model/2/assets.extra/tf_serving_warmup_requests
2020-08-23 14:13:13.496841: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: custom_model version: 2}
2020-08-23 14:13:13.498519: I tensorflow_serving/model_servers/server.cc:367] Running gRPC ModelServer at 0.0.0.0:8500 …
[warn] getaddrinfo: address family for nodename not supported
2020-08-23 14:13:13.499738: I tensorflow_serving/model_servers/server.cc:387] Exporting HTTP/REST API at:localhost:8501 …
[evhttp_server.cc : 238] NET_LOG: Entering the event loop …
2.3.4 请求服务

mode = ‘train’

curl -d ‘{“inputs”: {“a”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]],
“b”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]],“mode”:“train”}}’
-X POST http://localhost:8503/v1/models/custom_model:predict
返回

{
“outputs”: {
“output_1”: [
[
2.90094423
]
],
“output_2”: “train”
}
}

mode=‘predict’

curl -d ‘{“inputs”: {“a”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]],
“b”:[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]],“mode”:“predict”}}’
-X POST http://localhost:8503/v1/models/custom_model:predict
返回

{
“outputs”: {
“output_1”: [
[
0.699072063,
0.756825566,
1.35330391,
-2.21226835,
1.43654501,
1.50765,
-1.43798947,
-0.434436381,
-0.289675713,
2.53842211
]
],
“output_2”: “predict”
}
}
3. 客户端中程序调用

程序端调用,遵循基本的RESTful API即可。这里以上文custom_model v2 为例子。

准备数据

import numpy as np
import json

a = np.ones((1, 10))
b = np.ones((1, 10))
mode = “train”
data = {“inputs”: {“a”: a.tolist(), “b”: b.tolist(), “mode”: mode}}
data = json.dumps(data)
pycurl

import pycurl
url = “http://localhost:8503/v1/models/custom_model:predict”
c = pycurl.Curl()
c.setopt(c.URL, url)
c.setopt(c.POSTFIELDS, data)
rs = c.performb_rs()
rs = eval(rs)
outputs = rs[‘outputs’]
print(outputs)
c.close()
request

import requests
url = “http://localhost:8503/v1/models/custom_model:predict”
rs = requests.post(url,data)
outputs = rs.json()[‘outputs’]
print(outputs)
输出:

{‘output_1’: [[2.90094423]], ‘output_2’: ‘train’}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值