Meilleures pratiques pour les artefacts de modèle

Utilisez ces considérations supplémentaires pour la création et l'assemblage d'artefacts de modèle.

Écriture d'un fichier score.py

  • Assurez-vous toujours que les fichiers score.py et runtime.yaml se trouvent dans le répertoire supérieur d'un artefact de modèle.

    Tous les autres fichiers devant faire partie d'un artefact doivent être au même niveau que ces deux fichiers ou dans des répertoires sous ceux-ci :

    .
    |-- runtime.yaml
    |-- score.py
    |-- <your-serialized-models>
  • Le déploiement de modèle utilise les fonctions score.py pour charger un modèle en mémoire et effectuer des prévisions.
  • Les définitions de fonction, load_model() et predict(), ne sont pas modifiables. Seul le corps de ces fonctions est personnalisable.
  • Le chemin autorisé pour écrire des données sur le disque lors de l'utilisation du service de déploiement de modèle est /home/datascience.
  • Vous pouvez accéder au stockage d'objets à l'aide des principaux de ressources lorsque les autorisations d'identité OCI définies le permettent.

Assemblage de modules personnalisés

Tous les modules personnalisés dont score.py ou le modèle sérialisé dépend doivent être écrits en tant que scripts Python distincts dans le même répertoire supérieur que score.py ou moins. Par exemple, model.joblib dépend d'une classe DataFrameLabelEncoder personnalisée, définie dans le script dataframelabelencoder.py comme dans cet exemple :

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

Le module est ensuite importé par le fichier 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 }

Dans l'exemple précédent, la structure d'artefact doit être :


.
|-- score.py 
|-- dataframelabelencoder.py 
|-- model.joblib 
|-- runtime.yaml

Modification des signatures load_model() et Predict().

La fonction predict(data, model=load_model()) attend les données utiles et un objet de modèle. Elle est retournée par load_model() par défaut. Votre cas d'utilisation peut nécessiter la transmission d'un paramètre supplémentaire à predict(). Il peut s'agir d'un paramètre d'ajustement (scaler) ou d'une table de consultation. Vous pouvez ajouter des paramètres à cette fonction si une valeur par défaut est affectée à ceux-ci.

Dans l'exemple suivant, les prédictions reposent sur un modèle PCA et un objet d'ajustement. Il montre comment predict() peut accepter un paramètre supplémentaire nommé scaler. Par défaut, la fonction load_scaler() retourne une valeur au paramètre scaler. Nous vous recommandons de suivre ce modèle. Si predict() ou load_model() nécessitent des paramètres supplémentaires, ils doivent être réglés aux valeurs par défaut retournées par les fonctions définies dans score.py. Cet exemple ajoute des paramètres à 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]}

Test d'un artefact de modèle avant l'enregistrement

Avant d'enregistrer un modèle dans le catalogue, nous vous recommandons de tester l'artefact de manière approfondie. Cet extrait de code permet de tester un artefact de modèle avant de l'enregistrer dans le catalogue :

  • Modifie le chemin Python en l'insérant dans l'artefact du modèle.

  • Charge le modèle en mémoire à l'aide de load_model().

  • Enfin, appelle predict().

Avant d'exécuter l'extrait de code, créez un nouveau fichier de carnet dans une session de carnet, modifiez le noyau, puis sélectionnez l'environnement Conda à utiliser pour le déploiement de modèle (environnement Conda d'intégration). Copiez et collez l'extrait de code et exécutez le code.

Exemple de code de test d'artefact de modèle

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

La méthode predictions_test contient les prédictions réalisées par le modèle sur les données utiles de chaîne JSON de l'exemple data. Comparez predictions_test à un résultat de modèle connu pour un jeu de données particulier. Par exemple, data peut être un échantillon du jeu de données d'entraînement.

Exemple de prédictions d'image

La méthode score.py predict() gère le traitement à partir de la demande reçue comme dans cet exemple de fichier score.py :

"""
   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,
    }}