Especificaciones para escribir el código de inferencia de modelo
Esta sección describe el método general de edición de código de inferencia de modelo de ModelArts. Para obtener detalles sobre los ejemplos de script personalizados (incluidos ejemplos de código de inferencia) de los principales motores de IA, véase Ejemplos de scripts personalizados. Esta sección también proporciona un ejemplo de código de inferencia para el motor de TensorFlow y un ejemplo de personalización de la lógica de inferencia en el script de inferencia.
Debido a la limitación de API Gateway, la duración de una sola predicción en ModelArts no puede superar los 40 segundos. El código de inferencia del modelo debe ser lógicamente claro y conciso para un rendimiento de inferencia satisfactorio.
Especificaciones para compilar código de inferencia
- En el archivo de código de inferencia de modelo customize_service.py, agregue una clase de modelo hijo. Esta clase de modelo hijo hereda las propiedades de su clase de modelo padre. Para obtener más información sobre las instrucciones de importación de diferentes tipos de clases de modelo padre, consulte Tabla 1.
Tabla 1 Importar instrucciones de diferentes tipos de clases de modelo padre Tipo de modelo
Clase primaria
Instrucción de importación
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
- Se pueden reescribir los siguientes métodos:
Tabla 2 Métodos a reescribir Método
Descripción
__init__(self, model_name, model_path)
Método de inicialización, que es adecuado para modelos creados basados en marcos de aprendizaje profundo. Los modelos y las etiquetas se cargan con este método. Este método debe ser reescrito para modelos basados en PyTorch y Caffe para implementar la lógica de carga del modelo.
__init__(self, model_path)
Método de inicialización, que es adecuado para modelos creados basados en marcos de aprendizaje automático. La ruta del modelo (self.model_path) se inicializa usando este método. En Spark_MLlib, este método también inicializa SparkSession (self.spark).
_preprocess(self, data)
Método de preproceso, que se llama antes de una solicitud de inferencia y se usa para convertir los datos de solicitud originales de una API en los datos de entrada esperados de un modelo
_inference(self, data)
Método de solicitud de inferencia. No se recomienda volver a escribir el método porque una vez que se haya reescrito, el proceso de inferencia integrado de ModelArts se sobrescribirá y se ejecutará la lógica de inferencia personalizada.
_postprocess(self, data)
Método de posprocesamiento, que se llama después de completar una solicitud de inferencia y se utiliza para convertir la salida del modelo en la salida de la API
- Puede elegir reescribir los métodos de preproceso y postproceso para implementar el preprocesamiento de la entrada de API y el postprocesamiento de la salida de inferencia.
- La reescritura del método init de la clase de modelo padre puede hacer que una aplicación de IA se ejecute de forma anormal.
- El atributo que se puede utilizar es la ruta local donde reside el modelo. El nombre del atributo es self.model_path. Además, los modelos basados en PySpark pueden usar self.spark para obtener el objeto de SparkSession en customize_service.py.
Se requiere una ruta absoluta para leer archivos en el código de inferencia. Puede obtener la ruta local del modelo desde el atributo self.model_path.
- Cuando se utiliza TensorFlow, Caffe o MXNet, self.model_path indica la ruta del archivo de modelo. Vea el siguiente ejemplo:
# Store the label.json file in the model directory. The following information is read: with open(os.path.join(self.model_path, 'label.json')) as f: self.label = json.load(f)
- Cuando se utiliza PyTorch o PySpark, self.model_path indica la ruta del archivo de modelo. Vea el siguiente ejemplo:
# Store the label.json file in the model directory. The following information is read: 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)
- Cuando se utiliza TensorFlow, Caffe o MXNet, self.model_path indica la ruta del archivo de modelo. Vea el siguiente ejemplo:
- data importados a través de la API para el procesamiento previo, la solicitud de inferencia real y el procesamiento posterior pueden ser multipart/form-data o application/json.
- Solicitud de multipart/form-data
curl -X POST \ <modelarts-inference-endpoint> \ -F image1=@cat.jpg \ -F images2=@horse.jpg
Los datos de entrada correspondientes son los siguientes:
[ { "image1":{ "cat.jpg":"<cat.jpg file io>" } }, { "image2":{ "horse.jpg":"<horse.jpg file io>" } } ]
- Solicitud de application/json
curl -X POST \ <modelarts-inference-endpoint> \ -d '{ "images":"base64 encode image" }'
El dato de entrada correspondiente es python dict.
{ "images":"base64 encode image" }
- Solicitud de multipart/form-data
Ejemplo de script de inferencia de TensorFlow
- Código de inferencia
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
- Solicitud
curl -X POST \ Real-time service address \ -F images=@test.jpg
- Respuesta
{"mnist_result": 7}
El ejemplo de código anterior cambia el tamaño de las imágenes importadas al formulario del usuario para adaptarse a la forma de entrada del modelo. La imagen 32×32 se lee de la biblioteca de Pillow y se cambia de tamaño a 1×784 para que coincida con la entrada del modelo. En el procesamiento posterior, convierta la salida del modelo en una lista para mostrar la API RESTful.
Ejemplo de Script de Inferencia XGBoost
Para obtener más información sobre el código de inferencia de otros motores de aprendizaje automático, consulte PySpark y Aprendizaje de Scikit.
# coding:utf-8 import collections import json import xgboost as xgb from model_service.python_model_service import XgSklServingBaseService class UserService(XgSklServingBaseService): # request data preprocess def _preprocess(self, data): list_data = [] json_data = json.loads(data, object_pairs_hook=collections.OrderedDict) for element in json_data["data"]["req_data"]: array = [] for each in element: array.append(element[each]) list_data.append(array) return list_data # predict def _inference(self, data): xg_model = xgb.Booster(model_file=self.model_path) pre_data = xgb.DMatrix(data) pre_result = xg_model.predict(pre_data) pre_result = pre_result.tolist() return pre_result # predict result process def _postprocess(self, data): resp_data = [] for element in data: resp_data.append({"predict_result": element}) return resp_data
Ejemplo de script de inferencia de la lógica de inferencia personalizada
Consulte Ejemplo de un archivo de configuración de modelo que utiliza un paquete de dependencia personalizado para personalizar un paquete de dependencias en el archivo de configuración. A continuación, utilice el siguiente ejemplo de código para cargar el modelo en formato saved_model para la inferencia.
El módulo de log de Python utilizado por la imagen de inferencia base utiliza el nivel de log predeterminado Warning. Por defecto, solo se pueden consultar los logs de warning. Para consultar logs de INFO, configure el nivel de log como INFO en el código.
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 = {} # The label file can be loaded here and used in the post-processing function. # Directories for storing the label.txt file on OBS and in the model package # with open(os.path.join(self.model_path, 'label.txt')) as f: # self.label = json.load(f) # Load the model in saved_model format in non-blocking mode to prevent blocking timeout. thread = threading.Thread(target=self.get_tf_sess) thread.start() def get_tf_sess(self): # Load the model in saved_model format. # The session will be reused. Do not use the with statement. 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): # Two request modes using HTTPS # 1. The request in form-data file format is as follows: data = {"Request key value":{"File name":<File io>}} # 2. Request in JSON format is as follows: data = json.loads("JSON body transferred by the API") 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() |
Para cargar modelos que no son compatibles con ModelArts ni con varios modelos, especifique la ruta de carga mediante el método __init__. Código de ejemplo:
# -*- coding: utf-8 -*- import os from model_service.tfserving_model_service import TfServingBaseService class MnistService(TfServingBaseService): def __init__(self, model_name, model_path): # Obtain the path to the model folder. root = os.path.dirname(os.path.abspath(__file__)) # test.onnx is the name of the model file to be loaded and must be stored in the model folder. self.model_path = os.path.join(root, test.onnx) # Loading multiple models, for example, test2.onnx # self.model_path2 = os.path.join(root, test2.onnx)