Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Serveur MLflow : retrouver le run qui a produit un modèle

Par mis à jour le

Améliorer cette page

MLflow est une plateforme open source qui enregistre chaque entraînement d'un modèle de machine learning : paramètres, métriques, fichiers produits. Branché sur un serveur partagé, il répond à la question qu'on se pose une semaine après : quel code, quelles données et quels paramètres ont produit le modèle qui tourne ?

Cette leçon est la quatrième du module Reproduire de la formation MLOps. Le pipeline DVC sait rejouer un entraînement ; ici, on garde la trace de chaque rejeu. Public : intermédiaire, à l'aise avec Python et scikit-learn. La leçon montre le suivi local en quelques lignes, puis le serveur, ce qu'il faut journaliser pour remonter d'un modèle à son run, et les deux pièges rencontrés en lab.

  • Définir MLflow et les composants qu'on utilise.
  • Tracer un entraînement en local, puis sur un serveur adossé à PostgreSQL et à un bucket S3.
  • Choisir ce qu'on journalise pour relier un modèle à son code et à ses données.
  • Retrouver le run d'un modèle à partir d'un commit Git.
  • Contourner l'échec de log_model sur une forêt aléatoire.

La leçon suppose les trois précédentes du module : un projet verrouillé par uv.lock avec sa graine dans params.yaml, les données du fil rouge (le jeu synthétique de métriques de services généré par genere-metriques.py) et un dépôt Git. Docker doit être installé : le serveur du lab tourne dans des conteneurs. Aucune connaissance préalable de MLflow n'est requise. Versions mesurées : MLflow 3.17.1 (client et serveur) et scikit-learn 1.9.1.

MLflow standardise la façon dont on suit et partage les travaux de machine learning. Sans lui, les résultats d'expériences finissent dispersés dans des carnets, des noms de fichiers et des tableurs : impossible de reproduire un bon modèle quelques semaines après. MLflow enregistre le contexte de chaque exécution et le rend consultable.

Développé à l'origine par Databricks, il est open source, léger et modulaire : on adopte seulement les composants utiles. Il s'intègre nativement avec scikit-learn, PyTorch, TensorFlow et d'autres.

MLflow s'organise autour de briques indépendantes, dont voici les principales :

ComposantRôle
TrackingEnregistrer paramètres, métriques et artefacts (les fichiers produits, comme le modèle) de chaque exécution
ModelsEmpaqueter un modèle dans un format portable, quel que soit le framework
Model RegistryVersionner les modèles et gérer leur cycle de vie
ProjectsDécrire un projet reproductible (dépendances, points d'entrée)

Cette leçon porte sur le Tracking et le serveur qui le stocke ; le Model Registry fait l'objet de la leçon sur le registre.

Le plus court chemin pour voir MLflow à l'œuvre est le suivi local. Ajoutez MLflow à un projet uv :

Fenêtre de terminal
uv add mlflow==3.17.1 scikit-learn==1.9.1

MLflow range le travail en deux niveaux : une expérience (ici demo) regroupe des runs, chaque run étant une exécution de l'entraînement. Le code ci-dessous entraîne un modèle de classification et enregistre dans un run son paramètre C (la force de régularisation), sa métrique et le modèle lui-même :

import mlflow, mlflow.sklearn
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = make_classification(n_samples=500, random_state=42)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42)
mlflow.set_experiment("demo")
with mlflow.start_run() as run:
C = 1.0
model = LogisticRegression(C=C, max_iter=200).fit(Xtr, ytr)
acc = accuracy_score(yte, model.predict(Xte))
mlflow.log_param("C", C) # un paramètre
mlflow.log_metric("accuracy", acc) # une métrique
info = mlflow.sklearn.log_model(model, name="model") # le modèle
print("run_id:", run.info.run_id)
print("accuracy:", round(acc, 3))

La sortie confirme l'enregistrement (votre run_id sera différent). Avec MLflow 3.17.1, un dossier mlruns/ (les artefacts) et un fichier mlflow.db (les métadonnées) apparaissent dans le dossier courant :

run_id: f58e2fd23a924ba7a9bed2b31773d590
accuracy: 0.9

Chaque appel à start_run crée un nouveau run dans l'expérience active. En relançant le script avec un autre C, vous accumulez des runs comparables.

MLflow fournit une interface web pour explorer et comparer les runs. Depuis le dossier contenant mlflow.db et mlruns/ :

Fenêtre de terminal
mlflow ui --port 5055

L'interface s'ouvre sur http://localhost:5055. Le port par défaut, 5000, est celui du serveur du socle de labs plus bas : si ce serveur tourne, mlflow ui sans option échoue sur un port déjà pris, d'où --port 5055. Vous y visualisez les expériences, triez les runs par métrique et comparez les paramètres côte à côte.

Le suivi local reste sur un poste : mlflow.db et mlruns/ ne se partagent pas, et le modèle d'un collègue est invisible pour vous. Un serveur MLflow centralise les runs de toute l'équipe, derrière une seule adresse.

Lisez le schéma de gauche à droite. Le script d'entraînement ne parle qu'au serveur. C'est lui qui range les métadonnées (paramètres, métriques, tags) dans PostgreSQL et les fichiers (le modèle) dans un bucket S3. Le client n'a donc besoin ni d'accès direct à la base ni de clés S3.

Le script train.py, client MLflow, envoie paramètres, métriques, tags et fichiers du modèle au serveur MLflow sur le port 5000. Le serveur écrit les métadonnées des runs dans PostgreSQL et les artefacts dans le bucket S3 mlflow-artefacts.paramètres, métriques, tagsfichiers du modèlemétadonnées des runsartefactstrain.pyclient MLflowserveur MLflow:5000PostgreSQLbucket S3mlflow-artefacts
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le catalogue de labs fournit cette pile : PostgreSQL, le stockage S3 Garage déjà vu avec DVC, et le serveur MLflow. Un script la démarre et attend que le serveur réponde sur /health (une dizaine de secondes) :

Fenêtre de terminal
bash scripts/socle-demarrer.sh mlflow

Le conteneur du serveur lance une commande de cette forme, dont les options disent tout le montage :

Fenêtre de terminal
mlflow server --host 0.0.0.0 --port 5000 \
--backend-store-uri postgresql://mlflow@postgres:5432/mlflow \
--artifacts-destination s3://mlflow-artefacts/ \
--serve-artifacts \
--allowed-hosts "*"

--backend-store-uri désigne la base des métadonnées, --artifacts-destination le bucket des fichiers, et --serve-artifacts fait passer les fichiers par le serveur : le client n'a besoin d'aucune clé S3. Pour les gros fichiers, le serveur peut aussi annoncer au client un accès direct à S3 par URL présignée (une adresse temporaire, signée par le serveur, qui donne accès à un fichier sans clé). Ce raccourci pose un piège au téléchargement, traité dans la leçon sur le registre. --allowed-hosts "*" désactive le contrôle de l'en-tête Host des requêtes, qui protège contre le DNS rebinding (une page web qui piège le navigateur pour atteindre un service interne) : par défaut, MLflow 3.17.1 n'accepte que localhost et les adresses IP privées, et l'aide de la commande déconseille * en production. Ce joker n'est tolérable que parce que le port reste lié à 127.0.0.1 sur le poste : sur un serveur joignable du réseau, listez les noms d'hôtes autorisés. Côté script, une seule ligne change, l'adresse du serveur :

mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("latence")

On peut aussi fixer la variable d'environnement MLFLOW_TRACKING_URI, ce qui évite d'écrire l'adresse dans le code.

Que journaliser pour retrouver le run d'un modèle ?

Section intitulée « Que journaliser pour retrouver le run d'un modèle ? »

Pour remonter d'un modèle à ce qui l'a produit, chaque run porte quatre informations : les paramètres, les métriques, le commit Git du code et l'empreinte des données. Les deux premières sont des appels de base. Les deux dernières, vous les posez en tags (des étiquettes texte attachées au run), parce que MLflow ne sait pas à lui seul quelles données vous avez lues.

import hashlib, subprocess
commit = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True,
text=True, check=True).stdout.strip()
empreinte = hashlib.sha256(open("data/metriques.csv", "rb").read()).hexdigest()
with mlflow.start_run() as run:
mlflow.set_tags({"git_commit": commit, "data_sha256": empreinte})
mlflow.log_params(params)
mlflow.log_metrics(metriques)
mlflow.sklearn.log_model(modele, name="modele",
skops_trusted_types=["sklearn.tree._tree.Tree"])

Ici params est la section entrainement de params.yaml, lue par params = yaml.safe_load(open("params.yaml", encoding="utf-8"))["entrainement"] (sans cette section, n_arbres ne serait pas journalisé), et metriques le dictionnaire des mesures calculées sur la part de test, comme dans les leçons précédentes. Le tag git_commit pointe vers le code exact, data_sha256 vers les données : avec le dvc.lock de la leçon précédente, ces deux repères ferment la boucle. MLflow pose de son côté un tag mlflow.source.git.commit (et la branche) quand le script est lancé depuis un dépôt Git et que l'exécutable git est disponible, mais pas l'empreinte des données : ce tag-là, c'est à vous de l'écrire.

Le paramètre skops_trusted_types n'est pas décoratif : il évite l'échec de log_model sur une forêt aléatoire, expliqué ci-dessous.

Pourquoi log_model refuse-t-il une forêt aléatoire ?

Section intitulée « Pourquoi log_model refuse-t-il une forêt aléatoire ? »

Avec MLflow 3.17.1, log_model d'un RandomForestRegressor échoue tant qu'on ne déclare pas les types que skops doit accepter. MLflow écrit le modèle dans un fichier : c'est la sérialisation. Hors Databricks (où le format est cloudpickle), le format par défaut est skops, qui refuse les objets qu'il ne connaît pas comme sûrs, par prudence : un fichier modèle est un code potentiel. Le message est de la forme :

The saved sklearn model references untrusted types ... ['sklearn.tree._tree.Tree']

La parade est le paramètre déjà vu, skops_trusted_types=["sklearn.tree._tree.Tree"]. Retenez un effet de bord : l'échec laisse un run en statut FAILED, avec ses paramètres et ses métriques. Quand vous cherchez « le run du modèle », ne retenez donc que les runs FINISHED, sinon vous risquez de désigner un essai raté qui n'a produit aucun modèle.

Autre surprise de la version 3 : le modèle n'est plus rangé sous les artefacts du run, mais sous <expérience>/models/<model_id>/artifacts/, avec les fichiers MLmodel, model.skops, conda.yaml, python_env.yaml et requirements.txt. list_artifacts sur le run rend donc une liste vide. Le lien entre le run et son modèle est le champ outputs.model_outputs[].model_id du run.

On interroge le serveur par tag : le run dont git_commit vaut le commit du modèle est celui qu'on cherche. Partez du tag Git (par exemple v1), résolvez-le en commit, puis filtrez les runs terminés :

commit = subprocess.run(["git", "rev-parse", "v1^{commit}"],
capture_output=True, text=True, check=True).stdout.strip()
runs = mlflow.search_runs(
experiment_names=["latence"],
filter_string=f"tags.git_commit = '{commit}' and attributes.status = 'FINISHED'",
output_format="list",
)
print(runs[0].info.run_id)

Le ^{commit} suit un tag annoté jusqu'au commit qu'il désigne. L'identifiant affiché est celui à noter pour retrouver le modèle. Le serveur garde les métadonnées dans PostgreSQL et les fichiers dans le bucket : c'est le serveur qui fait foi, pas un fichier du dépôt.

Une fois un run satisfaisant, on ne désigne pas son modèle par un chemin ni par son model_id opaque (m-…) : on l'enregistre avec mlflow.register_model dans le Model Registry, qui lui donne un nom, des versions numérotées et des alias. C'est ce qui permet à un service de charger « le modèle validé » sans connaître le run qui l'a produit. Le geste, avec ses pièges, a sa leçon : Registre de modèles : versions, alias, lignée.

Les deux servent le MLOps mais à des échelles différentes. MLflow se concentre sur le suivi des expériences et la gestion des modèles, s'installe en une commande et convient à un usage individuel comme à une équipe. Kubeflow orchestre des pipelines ML complets sur Kubernetes : plus puissant, mais bien plus lourd à opérer. On les combine parfois, MLflow pour le tracking à l'intérieur de pipelines Kubeflow.

SymptômeCause probableCorrection
untrusted types ... sklearn.tree._tree.Tree à log_modelFormat skops par défaut, forêt aléatoire non déclarée sûrePasser skops_trusted_types=["sklearn.tree._tree.Tree"]
list_artifacts(run) rend []En MLflow 3, le modèle n'est plus sous les artefacts du runLire outputs.model_outputs[].model_id du run, le modèle est sous models/<model_id>
La recherche par commit renvoie un run sans modèleLe run FAILED d'un essai raté porte les mêmes tagsFiltrer attributes.status = 'FINISHED'
Le script ne joint pas le serveurSocle non démarré ou mauvaise adressebash scripts/socle-demarrer.sh mlflow, puis curl http://127.0.0.1:5000/health
Les runs atterrissent dans mlruns/ et pas sur le serveurset_tracking_uri oubliéAppeler set_tracking_uri avant set_experiment, ou fixer MLFLOW_TRACKING_URI

Le lab vous remet un train.py qui n'enregistre rien. Dans un dépôt Git à vous, avec le jeu généré et mlflow==3.17.1 installé (la version du serveur), vous le branchez sur le serveur MLflow du socle, vous l'exécutez deux fois depuis deux commits (50 puis 100 arbres) en journalisant paramètres, métriques, modèle, commit et empreinte des données, vous taguez v1 le commit du premier entraînement, puis vous écrivez dans run-v1.txt l'identifiant du run de v1. La validation interroge le serveur, PostgreSQL et le bucket : elle ne regarde jamais les commandes tapées. Démarrez d'abord le socle avec bash scripts/socle-demarrer.sh mlflow.

  • Un serveur MLflow centralise les runs : métadonnées dans PostgreSQL, fichiers dans un bucket S3, une seule adresse pour le script.
  • Un run porte ses paramètres, ses métriques et son modèle ; le commit Git et l'empreinte des données s'ajoutent en tags.
  • Avec scikit-learn et MLflow 3.17.1, log_model d'une forêt aléatoire demande skops_trusted_types=["sklearn.tree._tree.Tree"].
  • Le modèle est rangé sous models/<model_id> et non sous les artefacts du run ; le lien est outputs.model_outputs[].model_id.
  • Un essai raté laisse un run FAILED : on ne cherche que parmi les runs FINISHED.
  • mlflow ui et mlflow server n'ont aucune authentification par défaut : jamais au-delà de 127.0.0.1 sans protection.

Ces questions reprennent des situations rencontrées en exploitation : à vous de choisir le geste qui convient, la correction explique pourquoi.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn