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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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_modelsur une forêt aléatoire.
Quels sont les prérequis ?
Section intitulée « Quels sont les prérequis ? »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.
Qu'est-ce que MLflow ?
Section intitulée « Qu'est-ce que MLflow ? »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.
Les composants de MLflow
Section intitulée « Les composants de MLflow »MLflow s'organise autour de briques indépendantes, dont voici les principales :
| Composant | Rôle |
|---|---|
| Tracking | Enregistrer paramètres, métriques et artefacts (les fichiers produits, comme le modèle) de chaque exécution |
| Models | Empaqueter un modèle dans un format portable, quel que soit le framework |
| Model Registry | Versionner les modèles et gérer leur cycle de vie |
| Projects | Dé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.
Tracer une première expérience
Section intitulée « Tracer une première expérience »Le plus court chemin pour voir MLflow à l'œuvre est le suivi local. Ajoutez
MLflow à un projet uv :
uv add mlflow==3.17.1 scikit-learn==1.9.1MLflow 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.sklearnfrom sklearn.datasets import make_classificationfrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import train_test_splitfrom 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: f58e2fd23a924ba7a9bed2b31773d590accuracy: 0.9Chaque 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.
Ouvrir l'interface MLflow
Section intitulée « Ouvrir l'interface MLflow »MLflow fournit une interface web pour explorer et comparer les runs. Depuis
le dossier contenant mlflow.db et mlruns/ :
mlflow ui --port 5055L'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.
Pourquoi passer à un serveur MLflow ?
Section intitulée « Pourquoi passer à un serveur MLflow ? »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 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) :
bash scripts/socle-demarrer.sh mlflowLe conteneur du serveur lance une commande de cette forme, dont les options disent tout le montage :
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.
Comment retrouver le run d'un commit ?
Section intitulée « Comment retrouver le run d'un commit ? »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.
Versionner un modèle avec le Model Registry
Section intitulée « Versionner un modèle avec le Model Registry »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.
MLflow ou Kubeflow ?
Section intitulée « MLflow ou Kubeflow ? »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.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Correction |
|---|---|---|
untrusted types ... sklearn.tree._tree.Tree à log_model | Format skops par défaut, forêt aléatoire non déclarée sûre | Passer skops_trusted_types=["sklearn.tree._tree.Tree"] |
list_artifacts(run) rend [] | En MLflow 3, le modèle n'est plus sous les artefacts du run | Lire outputs.model_outputs[].model_id du run, le modèle est sous models/<model_id> |
| La recherche par commit renvoie un run sans modèle | Le run FAILED d'un essai raté porte les mêmes tags | Filtrer attributes.status = 'FINISHED' |
| Le script ne joint pas le serveur | Socle non démarré ou mauvaise adresse | bash scripts/socle-demarrer.sh mlflow, puis curl http://127.0.0.1:5000/health |
Les runs atterrissent dans mlruns/ et pas sur le serveur | set_tracking_uri oublié | Appeler set_tracking_uri avant set_experiment, ou fixer MLFLOW_TRACKING_URI |
Mettre en pratique
Section intitulée « Mettre en pratique »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.
À retenir
Section intitulée « À retenir »- 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_modeld'une forêt aléatoire demandeskops_trusted_types=["sklearn.tree._tree.Tree"]. - Le modèle est rangé sous
models/<model_id>et non sous les artefacts du run ; le lien estoutputs.model_outputs[].model_id. - Un essai raté laisse un run FAILED : on ne cherche que parmi les runs FINISHED.
mlflow uietmlflow servern'ont aucune authentification par défaut : jamais au-delà de127.0.0.1sans protection.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »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
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
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Registre de modèles MLflow : versions, alias et lignée
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »mlflow.start_run(), puis on enregistre les paramètres (log_param), les métriques (log_metric) et le modèle (log_model). MLflow stocke le tout en local, dans mlflow.db (métadonnées) et mlruns/ (artefacts), ou sur un serveur.champion) sans connaître son run. Il rend le modèle servi traçable et permet de revenir à une version antérieure.pip.