Best Practices für Modellartefakte
Verwenden Sie diese zusätzlichen Überlegungen für die Erstellung und das Packaging von Modellartefakten.
score.py-Datei schreiben
-
Stellen Sie immer sicher, dass sich die Dateien
score.py
undruntime.yaml
in der obersten Verzeichnisebene eines Modellartefakts befinden.Alle anderen Dateien, die Teil eines Artefakts sein sollen, müssen sich auf derselben Ebene wie die beiden Dateien oder in Verzeichnissen darunter befinden:
. |-- runtime.yaml |-- score.py |-- <your-serialized-models>
- Das Modell-Deployment verwendet die
score.py
-Funktionen, um ein Modell in den Arbeitsspeicher zu laden und Prognosen zu erstellen. - Die Funktionsdefinitionen
load_model()
undpredict()
können nicht bearbeitet werden. Nur der Body dieser Funktionen ist anpassbar. - Der zulässige Pfad zum Schreiben von Daten auf den Datenträger, wenn der Modell-Deployment-Service verwendet wird, lautet
/home/datascience
. - Sie können mit Resource Principals auf Object Storage zugreifen, wenn die OCI-Identitätsberechtigungen korrekt dafür definiert sind.
Benutzerdefinierte Module verpacken
Alle benutzerdefinierten Module, von denen score.py
oder das serialisierte Modell abhängig sind, müssen als separate Python-Skripte in demselben Verzeichnis auf oberster Ebene wie score.py
oder unter geschrieben werden. Beispiel: model.joblib
hängt von einer benutzerdefinierten DataFrameLabelEncoder
-Klasse ab, die im Skript dataframelabelencoder.py
wie in diesem Beispiel definiert ist:
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
Das Modul wird dann von der Datei score.py
importiert:
"""
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 }
Im vorherigen Beispiel muss die Artefaktstruktur folgendermaßen aussehen:
.
|-- score.py
|-- dataframelabelencoder.py
|-- model.joblib
|-- runtime.yaml
load_model()- und predict()-Signaturen ändern
Die Funktion predict(data, model=load_model())
erwartet die Payload-Daten und ein Modellobjekt und wird standardmäßig von load_model()
zurückgegeben. In Ihrem Anwendungsfall muss möglicherweise ein zusätzlicher Parameter an predict()
übergeben werden. Ein Beispiel ist ein Scaler oder eine Lookup-Tabelle. Sie können dieser Funktion Parameter hinzufügen, wenn dem hinzugefügten Parameter ein Standardwert zugewiesen ist.
Im folgenden Beispiel basieren die Vorhersagen auf einem PCA-Modell und einem Scaler-Objekt. Sie zeigt, wie predict()
einen zusätzlichen Parameter mit dem Namen scaler
annehmen kann. Standardmäßig gibt die Funktion load_scaler()
einen Wert an den Parameter scaler
zurück. Es wird empfohlen, dass Sie diesem Muster folgen. Wenn predict()
oder load_model()
zusätzliche Parameter erfordern, müssen diese auf Standardwerte gesetzt werden, die von Funktionen zurückgegeben werden, die in score.py
definiert sind. In diesem Beispiel werden Parameter zu predict()
hinzugefügt:
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]}
Modellartefakte vor dem Speichern testen
Bevor Sie ein Modell im Katalog speichern, sollten Sie das Artefakt gründlich testen. Dieses Code-Snippet zum Testen eines Modellartefakts, bevor es im Katalog gespeichert wird:
-
Der Python-Pfad wird geändert, indem der Pfad zum Modellartefakt eingefügt wird.
-
Es lädt das Modell mit
load_model()
in den Arbeitsspeicher. -
Es ruft schließlich
predict()
auf.
Bevor Sie das Code-Snippet ausführen, erstellen Sie in einer Notizbuchsession eine neue Notizbuchdatei, ändern Sie den Kernel, und wählen Sie dann dieselbe Conda-Umgebung aus, die Sie für das Modell-Deployment verwenden möchten (Inferenz-Conda-Umgebung). Kopieren Sie das Code-Snippet, fügen Sie es ein, und führen Sie den Code aus.
Beispiel für Modellartefakt-Testcode
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
Die Methode predictions_test
enthält die Vorhersagen des Modells für die Beispiel-Payload data
mit JSON-Zeichenfolgen. Vergleichen Sie predictions_test
mit einem bekannten Modellergebnis für ein bestimmtes Dataset. Beispiel: data
könnte ein Muster des Trainings-Datasets sein.
Beispiel für Bildvorhersagen
Die Methode score.py predict()
verarbeitet die empfangene Anforderung wie in dieser score.py
-Beispieldatei:
"""
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,
}}