Mejores prácticas para artefactos del modelo
Utilice estas consideraciones adicionales para la creación y el empaquetado de artefactos de modelo.
Escritura de un archivo score.py
-
Asegúrese siempre de que los archivos
score.py
yruntime.yaml
estén en el directorio de nivel superior de un artefacto de modelo.Cualquier otro archivo que deba formar parte de un artefacto debe estar en el mismo nivel que esos dos archivos o en directorios situados debajo de ellos:
. |-- runtime.yaml |-- score.py |-- <your-serialized-models>
- El despliegue del modelo utiliza las funciones
score.py
para cargar un modelo en la memoria y realizar predicciones. - Las definiciones de función,
load_model()
ypredict()
no se pueden editar. Solo se puede personalizar el cuerpo de estas funciones. - La ruta permitida para escribir datos en el disco cuando se utiliza el servicio de despliegue de modelo es
/home/datascience
. - Puede acceder a Object Storage mediante principales de recursos si los permisos de identidad de OCI están definidos correctamente para activarlo.
Empaquetado de módulos personalizados
Cualquier módulo personalizado del que dependa score.py
o el modelo en serie se debe escribir como scripts de Python independientes en el mismo directorio de nivel superior que score.py
o uno inferior. Por ejemplo, model.joblib
depende de una clase DataFrameLabelEncoder
personalizada, que se define en el script dataframelabelencoder.py
, como en este ejemplo:
from category_encoders.ordinal import OrdinalEncoder
from collections import defaultdict
from sklearn.base import TransformerMixin
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import LabelEncoder
class DataFrameLabelEncoder(TransformerMixin):
def __init__(self):
self.label_encoders = defaultdict(LabelEncoder)
def fit(self, X):
for column in X.columns:
if X[column].dtype.name in ["object", "category"]:
self.label_encoders[column] = OrdinalEncoder()
self.label_encoders[column].fit(X[column])
return self
def transform(self, X):
for column, label_encoder in self.label_encoders.items():
X[column] = label_encoder.transform(X[column])
return X
El módulo se importa a continuación mediante el archivo score.py
:
"""
Inference script. This script is used for prediction by scoring server when schema is known.
"""
import json
import os
from joblib import load
import io
import pandas as pd
from dataframelabelencoder import DataFrameLabelEncoder
def load_model():
"""
Loads model from the serialized format
Returns
-------
model: a model instance on which predict API can be invoked
"""
model_dir = os.path.dirname(os.path.realpath(__file__))
contents = os.listdir(model_dir)
model_file_name = "model.joblib"
# TODO: Load the model from the model_dir using the appropriate loader
# Below is a sample code to load a model file using `cloudpickle` which was serialized using `cloudpickle`
# from cloudpickle import cloudpickle
if model_file_name in contents:
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), model_file_name), "rb") as file:
model = load(file) # Use the loader corresponding to your model file.
else:
raise Exception('{0} is not found in model directory {1}'.format(model_file_name, model_dir))
return model
def predict(data, model=load_model()) -> dict:
"""
Returns prediction given the model and data to predict
Parameters
----------
model: Model instance returned by load_model API
data: Data format as expected by the predict API of the core estimator. For eg. in case of sckit models it could be numpy array/List of list/Panda DataFrame
Returns
-------
predictions: Output from scoring server
Format: { 'prediction': output from `model.predict` method }
"""
assert model is not None, "Model is not loaded"
X = pd.read_json(io.StringIO(data)) if isinstance(data, str) else pd.DataFrame.from_dict(data)
preds = model.predict(X).tolist()
return { 'prediction': preds }
En el ejemplo anterior, la estructura de artefacto debe ser:
.
|-- score.py
|-- dataframelabelencoder.py
|-- model.joblib
|-- runtime.yaml
Cambio de las firmas load_model() y predict()
La función predict(data, model=load_model())
espera los datos de carga útil y un objeto de modelo, el cual devuelve load_model()
por defecto. Es posible que su caso de uso requiera que se transfiera un parámetro adicional a predict()
. Un ejemplo puede ser una tabla de consulta o un scaler. Puede agregar parámetros a esa función si el parámetro que agrega tiene asignado un valor por defecto.
En el siguiente ejemplo, las predicciones se basan en un modelo PCA y un objeto scaler. Muestra cómo predict()
puede tomar un parámetro adicional denominado scaler
. Por defecto, la función load_scaler()
devuelve un valor al parámetro scaler
. Se recomienda seguir ese patrón. Si predict()
o load_model()
requieren parámetros adicionales, se deben definir en valores por defecto devueltos por las funciones definidas en score.py
. En este ejemplo, se agregan parámetros a predict()
:
import json
import os
from cloudpickle import cloudpickle
model_pickle_name = 'pca.pkl'
scaler_pickle_name = 'scaler.pkl'
"""
Inference script. This script is used for prediction by scoring server when schema is known.
"""
def load_model(model_file_name=model_pickle_name):
"""
Loads model from the serialized format
Returns
-------
model: a model instance on which predict API can be invoked
"""
model_dir = os.path.dirname(os.path.realpath(__file__))
contents = os.listdir(model_dir)
if model_file_name in contents:
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), model_file_name), "rb") as file:
return cloudpickle.load(file)
else:
raise Exception('{0} is not found in model directory {1}'.format(model_file_name, model_dir))
def load_scaler(model_file_name=scaler_pickle_name):
"""
Loads model from the serialized format
Returns
-------
model: a model instance on which predict API can be invoked
"""
model_dir = os.path.dirname(os.path.realpath(__file__))
contents = os.listdir(model_dir)
if model_file_name in contents:
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), model_file_name), "rb") as file:
return cloudpickle.load(file)
else:
raise Exception('{0} is not found in model directory {1}'.format(model_file_name, model_dir))
def predict(data, model=load_model(), scaler=load_scaler()):
"""
Returns prediction given the model and data to predict
Parameters
----------
model: Model instance returned by load_model API
data: Data format as expected by the predict API of the core estimator. For eg. in case of sckit models it could be numpy array/List of list/Panda DataFrame
Returns
-------
predictions: Output from scoring server
Format: {'prediction':output from model.predict method}
"""
from pandas import read_json, DataFrame
from io import StringIO
X = read_json(StringIO(data)) if isinstance(data, str) else DataFrame.from_dict(data)
X_s = scaler.transform(X)
return {'prediction':model.transform(X_s).tolist()[0]}
Prueba de un artefacto de modelo antes de guardarlo
Antes de guardar un modelo en el catálogo, recomendamos que pruebe el artefacto minuciosamente. Este fragmento de código para probar un artefacto de modelo antes de guardarlo en el catálogo:
-
Cambia la ruta de acceso de Python insertando la ruta de acceso al artefacto de modelo.
-
Carga el modelo en la memoria mediante
load_model()
. -
Por último, llama a
predict()
.
Antes de ejecutar el fragmento de código, cree un nuevo archivo de portátil en una sesión de Notebook, cambie el núcleo y, a continuación, seleccione el mismo entorno conda que desea utilizar para el despliegue del modelo (entorno conda de inferencia). Copie y pegue el fragmento de código y ejecute el código.
Ejemplo de código de prueba de artefacto de modelo
import sys
from json import dumps
# The local path to your model artifact directory is added to the Python path.
# replace <your-model-artifact-path>
sys.path.insert(0, f"<your-model-artifact-path>")
# importing load_model() and predict() that are defined in score.py
from score import load_model, predict
# Loading the model to memory
_ = load_model()
# Making predictions on a JSON string object (dumps(data)). Here we assume
# that predict() is taking data in JSON format
predictions_test = predict(dumps(data), _)
predictions_test
El método predictions_test
contiene las predicciones realizadas por el modelo en la carga útil de la cadena JSON data
de ejemplo. Compare predictions_test
con un resultado de modelo conocido para un juego de datos concreto. Por ejemplo, data
podría ser un ejemplo de juego de datos de entrenamiento.
Ejemplo de predicciones de imagen
El método score.py predict()
gestiona el procesamiento de la solicitud recibida como en este archivo score.py
de ejemplo:
"""
Inference script. This script is used for prediction by scoring server when schema is known.
"""
import torch
import torchvision
import io
import numpy as np
from PIL import Image
import os
# COCO Labels
COCO_INSTANCE_CATEGORY_NAMES = [
'__background__', 'person', 'bicycle', 'car', 'motorcycle']
model_name = 'PyTorch_Retinanet.pth'
def load_model(model_file_name = model_name):
"""
Loads model from the serialized format
Returns
-------
model: Pytorch model instance
"""
model = torchvision.models.detection.retinanet_resnet50_fpn(pretrained=False, pretrained_backbone=False)
cur_dir = os.path.dirname(os.path.abspath(__file__))
model.load_state_dict(torch.load(os.path.join(cur_dir, model_file_name)))
model.eval()
return model
def predict(data, model=load_model()):
"""
Returns prediction given the model and data to predict
Parameters
----------
model: Model instance returned by load_model API
data: Data format in json
Returns
-------
predictions: Output from scoring server
Format: {'prediction':output from model.predict method}
"""
img_bytes = io.BytesIO(data)
image = Image.open(img_bytes)
image_np = np.asarray(image)
image_th = torch.from_numpy(image_np)
image_th = image_th.permute(2, 0, 1)
image_th = image_th.unsqueeze(0) / 255
with torch.no_grad():
pred = model(image_th)
object_index_list = np.argwhere(pred[0].get("scores") > 0.5)
label_index_list = pred[0].get("labels")
labels = [COCO_INSTANCE_CATEGORY_NAMES[label_index_list[i]] for i in object_index_list]
box_list = pred[0].get("boxes")
boxes = [box_list[i].numpy().tolist() for i in object_index_list][0]
return {'prediction': {
'labels': labels,
'boxes': boxes,
}}