keras 训练好模型后,我们一般可以在本地直接调用,但是大部分场景是需要将模型功能服务化,提供接口供第三方调用。
如果直接用flask 等web框架封装成服务,往往成本较高,且性能需要其他方案去优化。tensorflow 提供一套模型服务化的解决方案:利用tensorflow servering + docker 将模型直接部署到docker容器上,同时提供模型热更新的功能。
本小节用具体的case来讲述 keras模型如何服务化,分成五个小节:
模型是一个基于InceptionV3 fintuning的 图像分类模型,模型具体定义如下
def cls_model(self):
"""定义模型结构
:return:
"""
base_model = InceptionV3(weights=self.weight_path, include_top=False)
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)
predictions = Dense(self.cls_num, activation='softmax')(x)
model = Model(inputs=base_model.input, outputs=predictions)
if self.lock_fine_tuning:
for layer in base_model.layers:
layer.trainable = False
model.summary()
model.compile(optimizer=SGD(lr=0.0001, momentum=0.9), loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
本case 直接采用ImageDataGenerator 来读取数据,分成训练集合与验证集合
def train_model(self):
"""模型训练
:return:
"""
train_datagen = ImageDataGenerator(
rescale=1. / 255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
val_datagen = ImageDataGenerator(
rescale=1. / 255)
train_generator = train_datagen.flow_from_directory(
self.train_data_path,
target_size=(160, 90),
batch_size=self.batch_size,
class_mode='binary')
validation_generator = val_datagen.flow_from_directory(
self.val_data_path,
target_size=(160, 90),
batch_size=self.batch_size,
class_mode='binary')
model = self.cls_model()
checkpoint = ModelCheckpoint(self.__save_model_name, monitor='val_accuracy', mode='max',
save_best_only=True, verbose=1)
model.fit_generator(
train_generator,
steps_per_epoch=train_generator.samples // self.batch_size,
epochs=self.epoch,
validation_data=validation_generator,
validation_steps=validation_generator.samples // self.batch_size,
callbacks=[checkpoint]
)
训练后,模型就保存到了 self.__save_model_name 定义的这个文件,一般keras里模型文件是 .h5文件
注意 这个既保存了模型结构,也保存了模型参数
如1小节里所述,训练好的keras 模型被保存成了 xxx.h5 。
这里需要将keras模型转成tensorflow 模型, 也就是带有pd文件的模型文件
转换方式如下:
@staticmethod
def export_model(save_model_path=None, export_model_dir=None, model_version=None):
"""
:param save_model_path: 保存好的keras模型
:param export_model_dir: 转换后的保存目录
:param model_version:
:return:
"""
model = tf.keras.models.load_model(save_model_path)
model.save(export_model_dir, save_format='tf')
⚠️注意: export_model_dir 需要是带有数字的目录(tensroflow serving 需要) 例如 pbmodel/0, pbmodel/1 等
转换后的模型目录如下
pbmodel
└── 0
├── assets
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index
由于作者是mac 环境,下载安装比较简单,这块自行参考docker官方的文档或者其他博主的blog
本case 没用用到gpu 所以直接下载了普通版本,具体版本之间的差异可以参考官方文档,根据需求pull不同的镜像
docker pull tensorflow/serving:latest-devel
直接docker run tensorflow serving, 涉及较多的参数,具体参数含义可以参考官方文档或者该blog(感谢大佬) TensorFlow Serving + Docker + Tornado机器学习模型生产级快速部署
具体的命令如下
docker run -p 9500:8500 -p:9501:8501 --mount type=bind,source=/Users/xxx/xxx/pdmodel,target=/models/xxx_model -e MODEL_NAME=xxx_model -t tensorflow/serving
⚠️注意: 一定需要注意端口的问题,如上的命令意味着我的 grpc接口与rest接口的访问的是9500, 9501
启动ok后就可以看到如下log
2020-07-29 08:19:09.632106: I tensorflow_serving/model_servers/server.cc:355] Running gRPC ModelServer at 0.0.0.0:8500 ...
[warn] getaddrinfo: address family for nodename not supported
2020-07-29 08:19:09.634360: I tensorflow_serving/model_servers/server.cc:375] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 238] NET_LOG: Entering the event loop .
本case只演示了如何调用rest http接口,以及需要特别注意的地方
调用python 脚本如下
def docker_server_predict():
""" 访问docker搭建好的预测服务
:return:
"""
server_url = "http://localhost:9501/v1/models/xxx_model:predict"
image_path = './dataset/xxx_dataset/train/1/screen_18_0.3330320987654321.png'
time_1 = time.time()
test_img = load_img(image_path, target_size=(160, 90, 3)) # 此处得到的是pillow图像Image实例
test_img = img_to_array(test_img) # 将Image实例转化为多维数组
test_img = test_img / 255 # 此处还需要将0-255转化为0-1
test_img = test_img.tolist()
print(time.time() - time_1)
headers = {"content-type": "application/json"}
body = {"instances": [{"input_1": test_img}]}
response = requests.post(server_url, data=json.dumps(body), headers=headers)
response.raise_for_status()
prediction = response.json()['predictions'][0]
print('label:', np.argmax(prediction))
print(time.time() - time_1)
预测调用的接口就是 ip :port/v1/models/<启动服务时候定义的模型名字 --name>: predict
⚠️ 注意1 可能会出现如下错误
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: http://localhost:9501/v1/models/player_first_frame_time_model:predict
造成该错误的原因是数据的参数问题。检查思路如下:
saved_model_cli show --dir pbmodel/0 --all
结果如下:
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['__saved_model_init_op']:
The given SavedModel SignatureDef contains the following input(s):
The given SavedModel SignatureDef contains the following output(s):
outputs['__saved_model_init_op'] tensor_info:
dtype: DT_INVALID
shape: unknown_rank
name: NoOp
Method name is:
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['input_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, -1, -1, 3)
name: serving_default_input_1:0
The given SavedModel SignatureDef contains the following output(s):
outputs['dense_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 4)
name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict
WARNING:tensorflow:From
⚠️ 注意2 图片数据输入方式错误,rest接口输入的是json,所以需要将numpy数据转换成json, 且一定要注意输入的数据维度,这里是一张一张图片输入的 不是一个batch 所以维度只有3
本文根据作者自己使用时候的思路来描述的,如果遇到问题或者不对的地方非常欢迎留言讨论纠正,非常感谢
一定要看⚠️注意事项,这是作者自己使用使用踩到的坑,也欢迎补充其他
使用tensorflow serving部署keras模型(tensorflow 2.0.0)
[译]TensorFlow Serving RESTful API