Journalisation de modèles MLflow

L’article suivant explique comment commencer à journaliser vos modèles entraînés (ou artefacts) en tant que modèles MLflow. Il explore les différentes méthodes pour personnaliser la façon dont MLflow effectue le package de vos modèles et comment il les exécute.

Pourquoi journaliser des modèles au lieu d’artefacts ?

Si vous n’êtes pas familiarisé avec MLflow, vous ne connaissez peut-être pas la différence entre la journalisation d’artefacts ou de fichiers et la journalisation de modèles MLflow. Nous vous recommandons de lire l’article Des artefacts aux modèles dans MLflow pour une introduction sur ce sujet.

Un modèle dans MLflow est également un artefact, mais avec une structure spécifique qui sert de contrat entre la personne qui a créé le modèle et la personne qui a l’intention de l’utiliser. Ce contrat aide à construire le pont sur les artefacts eux-mêmes et ce qu’ils signifient.

Les modèles de journalisation présentent les avantages suivants :

  • Les modèles peuvent être chargés directement pour l’inférence à l’aide de mlflow.<flavor>.load_model et avec la fonction predict.
  • Des modèles peuvent être utilisés directement comme entrées de pipelines.
  • Les modèles peuvent être déployés sans indiquer de script de scoring ni d’environnement.
  • Swagger est activé automatiquement dans les points de terminaison déployés, et la fonctionnalité Test peut être utilisée dans Azure Machine Learning Studio.
  • Vous pouvez utiliser le tableau de bord d’IA responsable.

Il existe différentes façons de commencer à utiliser le concept du modèle dans Azure Machine Learning avec MLflow, comme expliqué dans les sections suivantes :

Journalisation des modèles à l’aide de la journalisation automatique

L’une des façons les plus simples de commencer à utiliser cette approche consiste à utiliser la fonctionnalité de journalisation automatique MLflow. La journalisation automatique permet à MLflow d’indiquer à l’infrastructure associée à l’infrastructure que vous utilisez de journaliser l’ensemble des métriques, paramètres, artefacts et modèles que l’infrastructure considère comme pertinents. Par défaut, la plupart des modèles seront journalisés si la journalisation automatique est activée. Certaines saveurs peuvent décider de ne pas le faire dans des situations spécifiques. Par exemple, la saveur PySpark ne journalise pas les modèles s’ils dépassent une certaine taille.

Vous pouvez activer la journalisation automatique à l’aide de mlflow.autolog() ou mlflow.<flavor>.autolog(). L’exemple suivant utilise autolog() pour journaliser un modèle classifieur entraîné avec XGBoost :

import mlflow
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score

mlflow.autolog()

model = XGBClassifier(use_label_encoder=False, eval_metric="logloss")
model.fit(X_train, y_train, eval_set=[(X_test, y_test)], verbose=False)

y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)

Conseil

Si vous utilisez des pipelines Machine Learning, comme par exemple des pipelines Scikit-Learn, utilisez les fonctionnalités autolog de cette saveur pour les modèles de journalisation. Les modèles sont journalisés automatiquement lorsque la méthode fit() est appelée sur l’objet de pipeline. Le notebook Entraînement et suivi d’un classifieur XGBoost avec MLflow montre comment journaliser un modèle avec prétraitement à l’aide de pipelines.

Journalisation des modèles avec une signature, un environnement ou des exemples personnalisés

Vous pouvez journaliser des modèles manuellement à l’aide de la méthode mlflow.<flavor>.log_model dans MLflow. Ce flux de travail présente les avantages de conserver le contrôle des différents aspects de la façon dont le modèle est journalisé.

Utilisez cette méthode dans les cas suivants :

  • Vous souhaitez indiquer des packages pip ou un environnement conda différent de ceux détectés automatiquement.
  • Vous souhaitez inclure des exemples d’entrée.
  • Vous souhaitez inclure des artefacts spécifiques dans le package qui seront nécessaires.
  • Votre signature n’est pas déduite correctement par autolog. Cela est particulièrement important lorsque vous traitez des entrées qui sont des tenseurs où la signature a besoin de formes spécifiques.
  • Par exemple, le comportement par défaut de la journalisation automatique ne remplit pas votre objectif.

L’exemple de code suivant enregistre un modèle pour un classifieur XGBoost :

import mlflow
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score
from mlflow.models import infer_signature
from mlflow.utils.environment import _mlflow_conda_env

mlflow.autolog(log_models=False)

model = XGBClassifier(use_label_encoder=False, eval_metric="logloss")
model.fit(X_train, y_train, eval_set=[(X_test, y_test)], verbose=False)
y_pred = model.predict(X_test)

accuracy = accuracy_score(y_test, y_pred)

# Signature
signature = infer_signature(X_test, y_test)

# Conda environment
custom_env =_mlflow_conda_env(
    additional_conda_deps=None,
    additional_pip_deps=["xgboost==1.5.2"],
    additional_conda_channels=None,
)

# Sample
input_example = X_train.sample(n=1)

# Log the model manually
mlflow.xgboost.log_model(model, 
                         artifact_path="classifier", 
                         conda_env=custom_env,
                         signature=signature,
                         input_example=input_example)

Notes

  • log_models=False est configuré dans autolog. Cela empêche MLflow de journaliser automatiquement le modèle, car cela est effectué manuellement ultérieurement.
  • infer_signature est une méthode pratique pour essayer de déduire directement la signature des entrées et des sorties.
  • mlflow.utils.environment._mlflow_conda_env est une méthode privée dans le SDK MLflow et pourra changer à l’avenir. Cet exemple l’utilise simplement pour des questions de simplicité, mais elle doit être utilisée avec prudence ou générer manuellement la définition YAML en tant que dictionnaire Python.

Journalisation des modèles avec un comportement différent dans la méthode predict

Lorsque vous journalisez un modèle à l’aide de mlflow.autolog ou mlflow.<flavor>.log_model, la saveur utilisée pour le modèle détermine comment l’inférence doit être exécutée et ce qui est retourné par le modèle. MLflow n’applique aucun comportement spécifique dans la façon dont predict génère des résultats. Il existe des scénarios où vous souhaitez probablement effectuer un prétraitement ou un post-traitement avant et après l’exécution de votre modèle.

Une solution à ce scénario consiste à implémenter des pipelines Machine Learning qui passent des entrées aux sorties directement. Bien que cela soit possible (et parfois encourageant pour des questions relatives aux performances), cela peut être difficile à mettre en place. Dans ces cas de figure, vous souhaitez probablement personnaliser la façon dont votre modèle effectue une inférence à l’aide d’un modèle personnalisé, comme expliqué dans la section suivante.

Journalisation des modèles personnalisés

MLflow offre une prise en charge de divers frameworks Machine Learning, notamment FastAI, MXNet Gluon, PyTorch, TensorFlow, XGBoost, CatBoost, h2o, Keras, LightGBM, MLeap, ONNX, Prophet, spaCy, Spark MLLib, Scikit-Learn et statsmodels. Toutefois, il peut y avoir des moments où vous devez modifier le fonctionnement d’une saveur, journaliser un modèle non pris en charge en mode natif par MLflow ou même journaliser un modèle qui utilise plusieurs éléments à partir de différents frameworks. Dans ces cas de figure, vous devrez peut-être créer une saveur de modèle personnalisée.

Pour ce type de modèles, MLflow introduit une saveur appelée pyfunc (à partir de la fonction Python). En fait, cette saveur vous permet de journaliser n’importe quel objet souhaité en tant que modèle, tant qu’il satisfait à deux conditions :

  • Vous implémentez la méthode predict (au moins).
  • L’objet Python hérite de mlflow.pyfunc.PythonModel.

Conseil

Les modèles sérialisables qui implémentent l’API Scikit-learn peuvent utiliser la saveur Scikit-learn pour journaliser le modèle, quel que soit le modèle créé avec Scikit-learn. Si votre modèle peut être conservé au format Pickle et que l’objet a des méthodes predict() et predict_proba() (au moins), vous pouvez l’utiliser mlflow.sklearn.log_model() pour le journaliser à l’intérieur d’une exécution MLflow.

La façon la plus simple de créer la saveur de votre modèle personnalisé consiste à créer un wrapper autour de votre objet modèle existant. MLflow le sérialise et le package pour vous. Les objets Python sont sérialisables lorsque l’objet peut être stocké dans le système de fichiers en tant que fichier (généralement au format Pickle). Pendant l’exécution, l’objet peut être matérialisé à partir de ce fichier et toutes les valeurs, propriétés et méthodes disponibles lors de son enregistrement seront restaurées.

Utilisez cette méthode dans les cas suivants :

  • Votre modèle peut être sérialisé au format Pickle.
  • Vous souhaitez conserver l’état des modèles comme il était juste après l’entraînement.
  • Vous souhaitez personnaliser le fonctionnement de la fonction predict.

L’exemple suivant encapsule un modèle créé avec XGBoost pour qu’il se comporte de manière différente de l’implémentation par défaut de la saveur XGBoost (elle retourne les probabilités au lieu des classes) :

from mlflow.pyfunc import PythonModel, PythonModelContext

class ModelWrapper(PythonModel):
    def __init__(self, model):
        self._model = model

    def predict(self, context: PythonModelContext, data):
        # You don't have to keep the semantic meaning of `predict`. You can use here model.recommend(), model.forecast(), etc
        return self._model.predict_proba(data)

    # You can even add extra functions if you need to. Since the model is serialized,
    # all of them will be available when you load your model back.
    def predict_batch(self, data):
        pass

Ensuite, un modèle personnalisé peut être connecté à l’exécution comme suit :

import mlflow
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score
from mlflow.models import infer_signature

mlflow.xgboost.autolog(log_models=False)

model = XGBClassifier(use_label_encoder=False, eval_metric="logloss")
model.fit(X_train, y_train, eval_set=[(X_test, y_test)], verbose=False)
y_probs = model.predict_proba(X_test)

accuracy = accuracy_score(y_test, y_probs.argmax(axis=1))
mlflow.log_metric("accuracy", accuracy)

signature = infer_signature(X_test, y_probs)
mlflow.pyfunc.log_model("classifier", 
                        python_model=ModelWrapper(model),
                        signature=signature)

Conseil

Notez comment la méthode infer_signature utilise y_probs maintenant pour déduire la signature. Notre colonne cible a la classe cible, mais notre modèle retourne maintenant les deux probabilités pour chaque classe.

Étapes suivantes