模型推理代码编写说明-九游平台
本章节介绍了在modelarts中模型推理代码编写的通用方法及说明,针对常用ai引擎的自定义脚本代码示例(包含推理代码示例),请参见自定义脚本代码示例。本文在编写说明下方提供了一个tensorflow引擎的推理代码示例以及一个在推理脚本中自定义推理逻辑的示例。
modelarts推理因api网关(apig)的限制,模型单次预测的时间不能超过40s,模型推理代码编写需逻辑清晰,代码简洁,以此达到更好的推理效果。
推理代码编写指导
- 在模型代码推理文件“customize_service.py”中,需要添加一个子类,该子类继承对应模型类型的父类,各模型类型的父类名称和导入语句如表1所示。导入语句所涉及的python包在modelarts环境中已配置,用户无需自行安装。
表1 各模型类型的父类名称和导入语句 模型类型
父类
导入语句
tensorflow
tfservingbaseservice
from model_service.tfserving_model_service import tfservingbaseservice
pytorch
ptservingbaseservice
from model_service.pytorch_model_service import ptservingbaseservice
mindspore
singlenodeservice
from model_service.model_service import singlenodeservice
- 可以重写的方法有以下几种。
表2 重写方法 方法名
说明
__init__(self, model_name, model_path)
初始化方法,适用于深度学习框架模型。该方法内加载模型及标签等(pytorch和caffe类型模型必须重写,实现模型加载逻辑)。
__init__(self, model_path)
初始化方法,适用于机器学习框架模型。该方法内初始化模型的路径(self.model_path)。在spark_mllib中,该方法还会初始化sparksession(self.spark)。
_preprocess(self, data)
预处理方法,在推理请求前调用,用于将api接口输入的用户原始请求数据转换为模型期望输入数据。
_inference(self, data)
实际推理请求方法(不建议重写,重写后会覆盖modelarts内置的推理过程,运行自定义的推理逻辑)。
_postprocess(self, data)
后处理方法,在推理请求完成后调用,用于将模型输出转换为api接口输出。
- 用户可以选择重写preprocess和postprocess方法,以实现api输入数据的预处理和推理输出结果的后处理。
- 重写模型父类的初始化方法init可能导致模型“运行异常”。
- 可以使用的属性为模型所在的本地路径,属性名为“self.model_path”。另外pyspark模型在“customize_service.py”中可以使用“self.spark”获取sparksession对象。
推理代码中,需要通过绝对路径读取文件。模型所在的本地路径可以通过self.model_path属性获得。
- 当使用tensorflow、caffe、mxnet时,self.model_path为模型文件目录路径,读取文件示例如下:
# model目录下放置label.json文件,此处读取 with open(os.path.join(self.model_path, 'label.json')) as f: self.label = json.load(f)
- 当使用pytorch、scikit_learn、pyspark时,self.model_path为模型文件路径,读取文件示例如下:
# model目录下放置label.json文件,此处读取 dir_path = os.path.dirname(os.path.realpath(self.model_path)) with open(os.path.join(dir_path, 'label.json')) as f: self.label = json.load(f)
- 当使用tensorflow、caffe、mxnet时,self.model_path为模型文件目录路径,读取文件示例如下:
- 预处理方法、实际推理请求方法和后处理方法中的接口传入“data”当前支持两种content-type,即“multipart/form-data”和“application/json”。
- “multipart/form-data”请求
curl -x post \
\ -f image1=@cat.jpg \ -f image2=@horse.jpg 对应的传入data为
[ { "image1":{ "cat.jpg":"
" } }, { "image2":{ "horse.jpg":" " } } ] - “application/json”请求
curl -x post \
\ -d '{ "images":"base64 encode image" }' 对应的传入data为python dict
{ "images":"base64 encode image" }
- “multipart/form-data”请求
tensorflow的推理脚本示例
- 推理代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
from pil import image import numpy as np from model_service.tfserving_model_service import tfservingbaseservice class mnistservice(tfservingbaseservice): def _preprocess(self, data): preprocessed_data = {} for k, v in data.items(): for file_name, file_content in v.items(): image1 = image.open(file_content) image1 = np.array(image1, dtype=np.float32) image1.resize((1, 784)) preprocessed_data[k] = image1 return preprocessed_data def _postprocess(self, data): infer_output = {} for output_name, result in data.items(): infer_output["mnist_result"] = result[0].index(max(result[0])) return infer_output
- 请求
curl -x post \ 在线服务地址 \ -f images=@test.jpg
- 返回
{"mnist_result": 7}
在上面的代码示例中,完成了将用户表单输入的图片的大小调整,转换为可以适配模型输入的shape。首先通过pillow库读取“32×32”的图片,调整图片大小为“1×784”以匹配模型输入。在后续处理中,转换模型输出为列表,用于restful接口输出展示。
自定义推理逻辑的推理脚本示例
首先,需要在配置文件中,定义自己的依赖包,详细示例请参见使用自定义依赖包的模型配置文件示例。然后通过如下示例代码,实现了“saved_model”格式模型的加载推理。

当前推理基础镜像使用的python的logging模块,采用的是默认的日志级别warning,即当前只有warning级别的日志可以默认查询出来。如果想要指定info等级的日志能够查询出来,需要在代码中指定logging的输出日志等级为info级别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# -*- coding: utf-8 -*- import json import os import threading import numpy as np import tensorflow as tf from pil import image from model_service.tfserving_model_service import tfservingbaseservice import logging logging.basicconfig(level=logging.info, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getlogger(__name__) class mnistservice(tfservingbaseservice): def __init__(self, model_name, model_path): self.model_name = model_name self.model_path = model_path self.model_inputs = {} self.model_outputs = {} # label文件可以在这里加载,在后处理函数里使用 # label.txt放在obs和模型包的目录 # with open(os.path.join(self.model_path, 'label.txt')) as f: # self.label = json.load(f) # 非阻塞方式加载saved_model模型,防止阻塞超时 thread = threading.thread(target=self.get_tf_sess) thread.start() def get_tf_sess(self): # 加载saved_model格式的模型 # session要重用,建议不要用with语句 sess = tf.session(graph=tf.graph()) meta_graph_def = tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.serving], self.model_path) signature_defs = meta_graph_def.signature_def self.sess = sess signature = [] # only one signature allowed for signature_def in signature_defs: signature.append(signature_def) if len(signature) == 1: model_signature = signature[0] else: logger.warning("signatures more than one, use serving_default signature") model_signature = tf.saved_model.signature_constants.default_serving_signature_def_key logger.info("model signature: %s", model_signature) for signature_name in meta_graph_def.signature_def[model_signature].inputs: tensorinfo = meta_graph_def.signature_def[model_signature].inputs[signature_name] name = tensorinfo.name op = self.sess.graph.get_tensor_by_name(name) self.model_inputs[signature_name] = op logger.info("model inputs: %s", self.model_inputs) for signature_name in meta_graph_def.signature_def[model_signature].outputs: tensorinfo = meta_graph_def.signature_def[model_signature].outputs[signature_name] name = tensorinfo.name op = self.sess.graph.get_tensor_by_name(name) self.model_outputs[signature_name] = op logger.info("model outputs: %s", self.model_outputs) def _preprocess(self, data): # https两种请求形式 # 1. form-data文件格式的请求对应:data = {"请求key值":{"文件名":<文件io>}} # 2. json格式对应:data = json.loads("接口传入的json体") preprocessed_data = {} for k, v in data.items(): for file_name, file_content in v.items(): image1 = image.open(file_content) image1 = np.array(image1, dtype=np.float32) image1.resize((1, 28, 28)) preprocessed_data[k] = image1 return preprocessed_data def _inference(self, data): feed_dict = {} for k, v in data.items(): if k not in self.model_inputs.keys(): logger.error("input key %s is not in model inputs %s", k, list(self.model_inputs.keys())) raise exception("input key %s is not in model inputs %s" % (k, list(self.model_inputs.keys()))) feed_dict[self.model_inputs[k]] = v result = self.sess.run(self.model_outputs, feed_dict=feed_dict) logger.info('predict result : ' str(result)) return result def _postprocess(self, data): infer_output = {"mnist_result": []} for output_name, results in data.items(): for result in results: infer_output["mnist_result"].append(np.argmax(result)) return infer_output def __del__(self): self.sess.close() |

对于modelarts不支持的结构模型或者多模型加载,需要__init__方法中自己指定模型加载的路径。示例代码如下:
# -*- coding: utf-8 -*- import os from model_service.tfserving_model_service import tfservingbaseservice class mnistservice(tfservingbaseservice): def __init__(self, model_name, model_path): # 获取程序当前运行路径,即model文件夹所在的路径 root = os.path.dirname(os.path.abspath(__file__)) # test.onnx为待加载模型文件的名称,需要放在model文件夹下 self.model_path = os.path.join(root, test.onnx) # 多模型加载,例如:test2.onnx # self.model_path2 = os.path.join(root, test2.onnx)
mindspore的推理脚本示例
-
snt3芯片目前只有北京四提工单申请权限后才可以使用,支持模型格式为.om,推理脚本如下:
from __future__ import absolute_import from __future__ import division from __future__ import print_function import json import os import numpy as np from pil import image from hiai.nn_tensor_lib import nntensor from hiai.nntensor_list import nntensorlist from model_service.hiai_model_service import hiaibaseservice class demoservice(hiaibaseservice): def __init__(self, *args, **kwargs): # 默认加载模型包目录下的om文件 super(demoservice, self).__init__(*args, **kwargs) self.labels_list = none self.is_multilabel = false def _preprocess(self, data): preprocessed_data = {} images = [] for k, v in data.items(): for file_name, file_content in v.items(): image = image.open(file_content) image = np.array(image) # nhwc # aipp should use rgb format. # mean reg is applied in aipp. # transpose is applied in aipp tensor = nntensor(image) images.append(tensor) tensor_list = nntensorlist(images) preprocessed_data['images'] = tensor_list return preprocessed_data def _inference(self, data, image_info=none): result = {} for k, v in data.items(): result[k] = self.model.proc(v) return result def _postprocess(self, data): # 这里增加自己的后处理 return str(data)
相关文档
意见反馈
文档内容是否对您有帮助?
如您有其它疑问,您也可以通过华为云社区问答频道来与我们联系探讨