keras +TensorFlow Serving +docker 模型服务化

文章目录

    • 一 项目背景
    • 二 keras 模型上线流程
      • 1 keras 模型定义与训练
        • a 模型定义
        • b 模型训练
      • 2 keras模型转换成tensorflow模型
      • 3 docker环境配置
      • 4 启动容器服务并加载模型文件
        • 1 获取 tensorflow serving 镜像
        • 2 起镜像服务
      • 5 服务调用
    • 三 其他说明
    • 四 参考

一 项目背景

keras 训练好模型后,我们一般可以在本地直接调用,但是大部分场景是需要将模型功能服务化,提供接口供第三方调用。

如果直接用flask 等web框架封装成服务,往往成本较高,且性能需要其他方案去优化。tensorflow 提供一套模型服务化的解决方案:利用tensorflow servering + docker 将模型直接部署到docker容器上,同时提供模型热更新的功能。

二 keras 模型上线流程

本小节用具体的case来讲述 keras模型如何服务化,分成五个小节:

  • keras模型定义与训练
  • keras模型转换成tensorflow模型
  • docker环境配置
  • 启动容器并加载模型文件
  • 服务调用

1 keras 模型定义与训练

a 模型定义

模型是一个基于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

b 模型训练

本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文件
注意 这个既保存了模型结构,也保存了模型参数

2 keras模型转换成tensorflow模型

如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

3 docker环境配置

由于作者是mac 环境,下载安装比较简单,这块自行参考docker官方的文档或者其他博主的blog

4 启动容器服务并加载模型文件

1 获取 tensorflow serving 镜像

本case 没用用到gpu 所以直接下载了普通版本,具体版本之间的差异可以参考官方文档,根据需求pull不同的镜像

docker pull tensorflow/serving:latest-devel

2 起镜像服务

直接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 .

5 服务调用

本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

造成该错误的原因是数据的参数问题。检查思路如下:

  • 看看定义的模型的输入层是不是叫 input_1, 查看命令如下
 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

你可能感兴趣的:(深度学习)