Sans suivi d’expériences, on entraîne dix variantes d’un modèle, on les compare à la main dans un tableur, et trois jours plus tard on n’arrive plus à reproduire la meilleure. Tous les data scientists rencontrent cette frustration au moins une fois — et la promesse de MLflow est précisément de l’éviter. Ce tutoriel installe MLflow en local, instrumente un script d’entraînement, compare des runs dans l’interface web, sauvegarde les modèles versionnés, puis bascule sur un backend PostgreSQL pour un usage en équipe.
Pour le contexte global, voir le guide principal sur la stack data 2026. Ce tutoriel suppose qu’on dispose déjà d’un pipeline scikit-learn — voir le tutoriel régression et classification.
Prérequis
- Python 3.10+, scikit-learn 1.8+, mlflow 3.x
- Connaissance de base : run, métrique, artefact
- Pour la partie équipe : PostgreSQL 15+ accessible et 4 Go d’espace disque
- Temps estimé : 2 heures
Étape 1 — Installer MLflow
MLflow s’installe via pip et fournit deux choses en une commande : la bibliothèque cliente Python qu’on appelle depuis le code d’entraînement, et le serveur web qu’on lance pour consulter les expériences. Pour démarrer, l’installation par défaut suffit ; les extras (PostgreSQL, S3, base de modèles) s’ajoutent ensuite selon les besoins.
pip install mlflow scikit-learn pandas
mlflow --version
La sortie de la commande version confirme que tout est en place. Si mlflow n’est pas trouvé après installation, c’est presque toujours que l’environnement virtuel n’est pas activé ou que le PATH n’inclut pas le dossier des scripts. La commande which mlflow sur Linux ou where mlflow sur Windows lève le doute en affichant le chemin du binaire.
Étape 2 — Lancer le serveur MLflow local
En local, MLflow stocke ses runs dans un dossier mlruns/ sur disque et expose une interface web sur le port 5000. C’est suffisant pour un usage individuel ou pour découvrir le produit. Le serveur tourne en arrière-plan dans un terminal dédié pendant qu’on entraîne dans un autre.
mlflow ui --host 127.0.0.1 --port 5000
Ouvrir http://127.0.0.1:5000 dans le navigateur affiche l’interface vide pour l’instant. Le terminal qui héberge le serveur affiche les requêtes au fur et à mesure — utile pour déboguer si quelque chose ne se loggue pas. Pour arrêter, Ctrl-C ferme le serveur proprement. En production on lancerait MLflow comme service systemd, derrière un reverse proxy Nginx.
Étape 3 — Instrumenter un script d’entraînement
L’instrumentation tient en trois fonctions : mlflow.start_run() ouvre un run, log_param et log_metric enregistrent les valeurs, log_artifact sauvegarde un fichier (modèle, graphique, rapport). Tout ce qui n’est pas explicitement loggué reste hors-radar — d’où l’intérêt d’instrumenter dès le début, pas après coup.
import mlflow
import mlflow.sklearn
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import f1_score, roc_auc_score
mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("classification_paiements")
with mlflow.start_run(run_name="hgb_baseline"):
params = {"max_iter": 200, "learning_rate": 0.1, "max_depth": None}
mlflow.log_params(params)
clf = HistGradientBoostingClassifier(**params, random_state=42)
clf.fit(X_train_prep, y_train)
y_pred = clf.predict(X_test_prep)
y_proba = clf.predict_proba(X_test_prep)[:, 1]
mlflow.log_metric("f1", f1_score(y_test, y_pred))
mlflow.log_metric("auc_roc", roc_auc_score(y_test, y_proba))
mlflow.sklearn.log_model(clf, name="model")
print("Run terminé, visible dans MLflow UI")
Après exécution, le run apparaît immédiatement dans l’interface web sous l’expérience classification_paiements. Cliquer dessus ouvre une page détaillée : paramètres, métriques, artefacts, code Git, environnement. Le bloc with mlflow.start_run(...) garantit que le run est fermé proprement même en cas d’exception, ce qui évite les runs fantômes dans l’interface.
Étape 4 — Lancer plusieurs runs et comparer
L’intérêt principal de MLflow apparaît quand on accumule des runs. Une boucle simple sur des hyperparamètres remplit l’interface en quelques minutes, et la comparaison se fait visuellement plutôt qu’à la main dans un fichier Excel. Chaque run garde la trace exacte de ses paramètres et métriques, indépendamment de l’ordre d’exécution.
for lr in [0.01, 0.05, 0.1, 0.3]:
for depth in [None, 4, 8, 16]:
with mlflow.start_run(run_name=f"hgb_lr{lr}_d{depth}"):
mlflow.log_params({"learning_rate": lr, "max_depth": depth})
clf = HistGradientBoostingClassifier(
learning_rate=lr, max_depth=depth, random_state=42, max_iter=200
)
clf.fit(X_train_prep, y_train)
y_pred = clf.predict(X_test_prep)
y_proba = clf.predict_proba(X_test_prep)[:, 1]
mlflow.log_metric("f1", f1_score(y_test, y_pred))
mlflow.log_metric("auc_roc", roc_auc_score(y_test, y_proba))
Seize runs apparaissent dans l’interface. Les colonnes affichent paramètres et métriques côte à côte, on les trie d’un clic, on coche plusieurs runs et on lance « Compare » pour voir un graphique parallèle des paramètres et des métriques. C’est cette vue qui fait gagner le plus de temps quand on cherche le bon réglage : un coup d’œil suffit pour repérer que learning_rate=0,05 avec max_depth=8 domine les autres.
Étape 5 — Sauvegarder le modèle au bon format
MLflow ne stocke pas seulement le fichier joblib du modèle : il sauvegarde aussi sa signature (types des entrées et sorties), la liste des dépendances Python, et un script de chargement. Cette enveloppe permet de récupérer un modèle dans plusieurs mois et de le réutiliser sans connaître les versions exactes utilisées à l’origine — MLflow régénère un environnement compatible.
from mlflow.models.signature import infer_signature
with mlflow.start_run(run_name="hgb_final"):
clf = HistGradientBoostingClassifier(learning_rate=0.05, max_depth=8, random_state=42, max_iter=200)
clf.fit(X_train_prep, y_train)
sig = infer_signature(X_train_prep, clf.predict(X_train_prep))
mlflow.sklearn.log_model(
clf, name="model",
signature=sig,
input_example=X_train_prep.iloc[:5],
)
mlflow.log_metric("f1", f1_score(y_test, clf.predict(X_test_prep)))
L’input_example est une bonne pratique : il documente concrètement la forme attendue en entrée, ce qui aide énormément le développeur qui consommera le modèle plus tard. La signature explicite déclenche une erreur claire si quelqu’un appelle le modèle avec un mauvais type — bien plus utile qu’un crash obscur en production.
Étape 6 — Charger un modèle depuis MLflow
Une fois loggué, un modèle se recharge en deux lignes via son URI unique runs:/<run_id>/model ou via le registre de modèles si on l’a publié. C’est ce mécanisme qui rend la production reproductible : le code de service charge la version du modèle qu’on a choisie, indépendamment du fait qu’on ait entraîné cinquante autres versions depuis.
run_id = "abc123def456..." # à copier depuis l'UI MLflow
model_uri = f"runs:/{run_id}/model"
modele_charge = mlflow.sklearn.load_model(model_uri)
predictions = modele_charge.predict(X_test_prep.head(5))
print(predictions)
Le run_id est l’identifiant unique du run, visible dans l’interface MLflow ou récupérable par programmation via mlflow.search_runs(). Cette dernière méthode est très utile pour automatiser : par exemple, retrouver le meilleur run d’une expérience donnée et le promouvoir en production via un script.
Étape 7 — Logguer un graphique d’évaluation
Au-delà des métriques, on peut attacher des artefacts arbitraires à un run : graphique d’évaluation, rapport texte, fichier CSV de prédictions. Cette possibilité transforme MLflow en archive complète des expérimentations — utile pour expliquer six mois plus tard pourquoi une décision a été prise.
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
with mlflow.start_run(run_name="hgb_avec_artefacts"):
clf = HistGradientBoostingClassifier(learning_rate=0.05, random_state=42)
clf.fit(X_train_prep, y_train)
y_pred = clf.predict(X_test_prep)
mlflow.log_metric("f1", f1_score(y_test, y_pred))
fig, ax = plt.subplots(figsize=(5, 5))
ConfusionMatrixDisplay.from_predictions(y_test, y_pred, ax=ax, cmap="Blues")
fig.savefig("confusion.png", dpi=120, bbox_inches="tight")
mlflow.log_artifact("confusion.png", artifact_path="figures")
Le graphique est stocké aux côtés du modèle et accessible depuis l’interface. artifact_path permet d’organiser les fichiers en sous-dossiers — utile quand on stocke plusieurs graphiques par run. Pour de très gros artefacts (jeux de données par exemple), on configurera un stockage S3 plutôt que le disque local.
Étape 8 — Le registre de modèles
Le registre est l’endroit où l’on promeut un modèle d’expérimentation à production. Chaque modèle enregistré reçoit un nom et un numéro de version. Plusieurs aliases (Production, Staging, Archived) permettent de pointer vers la version active sans changer le code de service. C’est la fonctionnalité qui transforme MLflow d’un outil de tracking en un outil MLOps complet.
from mlflow.tracking import MlflowClient
client = MlflowClient()
# Enregistrer le modèle d'un run dans le registre
result = mlflow.register_model(
model_uri=f"runs:/{run_id}/model",
name="modele_paiement",
)
print(f"Version enregistrée : {result.version}")
# Promouvoir cette version en alias "production"
client.set_registered_model_alias("modele_paiement", "production", result.version)
Le code de service charge alors le modèle via l’URI models:/modele_paiement@production. Promouvoir une nouvelle version se fait sans toucher au code applicatif — un simple appel à set_registered_model_alias bascule l’alias. C’est ce qui rend les rollbacks rapides en cas de problème : il suffit de réaliasser une version antérieure.
Étape 9 — Backend PostgreSQL pour un usage en équipe
Le backend par défaut (SQLite + dossier local) ne tient pas à plusieurs utilisateurs concurrents. Pour une équipe, on bascule sur PostgreSQL pour les métadonnées et un stockage S3-compatible (Minio en self-hosted, ou un bucket cloud) pour les artefacts. La configuration tient en variables d’environnement et un démarrage de serveur adapté.
export MLFLOW_TRACKING_URI="postgresql://mlflow:secret@db.exemple.local:5432/mlflow"
export MLFLOW_ARTIFACT_ROOT="s3://mlflow-artifacts/"
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
mlflow server \
--backend-store-uri "$MLFLOW_TRACKING_URI" \
--default-artifact-root "$MLFLOW_ARTIFACT_ROOT" \
--host 0.0.0.0 --port 5000
Côté client, pointer vers ce serveur se fait avec mlflow.set_tracking_uri("http://serveur.exemple.local:5000"). Le serveur peut être déployé derrière un Nginx avec authentification basique, ou intégré à un fournisseur d’identité OIDC pour des environnements plus sensibles. La séparation métadonnées/artefacts permet de scaler les deux indépendamment.
Étape 10 — Auto-logging
MLflow propose une fonctionnalité particulièrement pratique : l’auto-logging. Une seule ligne mlflow.autolog() avant le code d’entraînement, et MLflow loggue automatiquement tous les paramètres scikit-learn, métriques d’évaluation, et le modèle final. C’est parfait pour démarrer ou pour tester rapidement une nouvelle approche sans écrire le code de logging à la main.
mlflow.autolog()
with mlflow.start_run(run_name="autologging_demo"):
clf = HistGradientBoostingClassifier(learning_rate=0.05, max_depth=8, random_state=42)
clf.fit(X_train_prep, y_train)
# Tout est loggué automatiquement
L’auto-logging détecte le framework (scikit-learn, PyTorch, XGBoost, LightGBM, etc.) et applique le bon logger. C’est un excellent point d’entrée mais on perd le contrôle fin : pour une production sérieuse, on revient au logging manuel pour choisir précisément ce qui est gardé. Le bon usage est : auto-logging pour explorer, logging manuel pour figer la version finale.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| Le run n’apparaît pas dans l’UI | Tracking URI mal configuré | Vérifier mlflow.get_tracking_uri() |
| Run « RUNNING » qui ne se ferme jamais | Exception non gérée pendant le run | Utiliser le bloc with pour fermeture auto |
| Modèle chargé donne des prédictions différentes | Versions de scikit-learn divergentes | Conda env auto-régénéré par MLflow ou pip lockfile |
| Artefacts énormes saturent le disque | log_artifact sur de très gros fichiers | Backend S3 plutôt que filesystem local |
| Permission denied sur mlruns/ | UI lancée par un autre utilisateur | Aligner le user ou mettre des droits 777 sur le dossier |
| Auto-logging ignore certains paramètres | Framework non supporté | Compléter avec log_params manuel |
| UI lente avec des milliers de runs | Backend SQLite saturé | Migrer vers PostgreSQL |
Tutoriels associés
- Régression et classification avec scikit-learn
- Servir un modèle ML en API avec FastAPI
- 🔝 Retour au guide principal : Sciences de données : la stack pratique 2026
Ressources officielles
FAQ
MLflow ou Weights & Biases ?
MLflow est gratuit, self-hosted, open source. Weights & Biases est SaaS payant avec une UI très soignée et des fonctionnalités collaboratives plus poussées. Pour un usage individuel ou en équipe restreinte, MLflow couvre 90 % des besoins.
MLflow gère-t-il les modèles non scikit-learn ?
Oui. PyTorch, TensorFlow, XGBoost, LightGBM, statsmodels, Prophet et bien d’autres ont des intégrations natives. Pour des modèles maison, on utilise mlflow.pyfunc qui accepte n’importe quel objet Python sérialisable.
Faut-il logger chaque expérience ?
Oui dès que l’expérience pourrait être utile à comparer. Les runs « jetables » saturent l’interface mais ne coûtent rien — les supprimer en masse plus tard est trivial. À l’inverse, un run non loggué est définitivement perdu.
Combien d’espace disque pour 1000 runs ?
Avec des modèles scikit-learn classiques de quelques Mo, on est aux alentours de 5 à 10 Go. Avec des réseaux profonds qui pèsent 500 Mo par snapshot, on monte vite à plusieurs centaines de Go — d’où l’intérêt du backend S3 et d’une politique de purge.