Un modèle qui reste dans un notebook n’apporte aucune valeur. Déployer, c’est le rendre utilisable par d’autres systèmes : une application, un tableau de bord, un traitement nocturne. Ce cours suit un fil rouge : un modèle de prédiction de churn (départ client), que nous allons sauvegarder, exposer en API, conteneuriser, tester et surveiller.
Un seul objet : le Pipeline
L’erreur la plus fréquente en production est d’appliquer un prétraitement différent de celui de l’entraînement (une normalisation oubliée, des colonnes dans le mauvais ordre). On l’évite en sauvegardant le prétraitement et le modèle ensemble :
import joblib
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
modele = make_pipeline(StandardScaler(), LogisticRegression())
modele.fit(X_train, y_train) # X_train : DataFrame avec colonnes nommées
joblib.dump(modele, "modele.joblib") # sérialisation
modele_charge = joblib.load("modele.joblib")
assert (modele_charge.predict(X_test) == modele.predict(X_test)).all()
Pour PyTorch, on sauvegarde le state_dict (torch.save(modele.state_dict(), "poids.pt")) avec le code de la classe du modèle. Pour servir un modèle depuis un autre langage ou accélérer l’inférence, le format ONNX (skl2onnx, torch.onnx.export) est une option portable.
Attention à la sécurité
pickle et joblib peuvent exécuter du code arbitraire au chargement. Ne chargez jamais un fichier dont vous ne maîtrisez pas l’origine (téléchargé, reçu par e-mail). Pour partager des modèles scikit-learn, la bibliothèque skops propose un format qui vérifie les types avant chargement ; pour les poids de réseaux, le format safetensors est la norme.
Les versions comptent
Un modèle sérialisé avec scikit-learn 1.9 peut refuser de se charger, ou se comporter différemment, avec une autre version. Figez donc vos dépendances :
# requirements.txt
scikit-learn==1.9.1
pandas==3.1.1
numpy==2.5.4
joblib==1.6.0
Fixez aussi les graines aléatoires (random_state) pour pouvoir ré-entraîner à l’identique.
Documenter l’artefact
Un fichier .joblib seul ne dit rien. Accompagnez-le d’une fiche (au format JSON) :
import hashlib, json, datetime, io
import sklearn
from sklearn.metrics import roc_auc_score
tampon = io.BytesIO()
joblib.dump(modele, tampon)
fiche = {
"nom": "churn",
"version": "1.0.0",
"features": list(X_train.columns), # ordre attendu en entrée
"metriques": {"auc_test": round(roc_auc_score(y_test, modele.predict_proba(X_test)[:, 1]), 4)},
"sha256": hashlib.sha256(tampon.getvalue()).hexdigest(), # empreinte de l'artefact
"sklearn": sklearn.__version__,
"entraine_le": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"donnees": "clients_2026-09.parquet",
}
with open("modele.json", "w") as f:
json.dump(fiche, f, indent=2)
L’empreinte SHA-256 permet de vérifier que le fichier déployé est bien celui qui a été validé. La version suit souvent le versionnement sémantique : majeure si le contrat d’entrée change, mineure pour un ré-entraînement compatible, correctif pour une correction mineure.
Des outils comme MLflow automatisent ce suivi (paramètres, métriques, artefacts, registre de modèles) ; nous les verrons en leçon 4.
À retenir
Sauvegardez un Pipeline complet, figez les versions, ne chargez que des fichiers de confiance et documentez chaque artefact avec ses features, ses métriques et son empreinte.
Exercices
Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.
Sauvegarder et recharger en mémoire
Avec le modèle fourni, écrivez
serialiser(modele)qui renvoie les octets du modèle sérialisé avecjoblib.dumpdans unio.BytesIO, etdeserialiser(octets)qui reconstruit le modèle avecjoblib.load.import numpy as np import pandas as pd from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression rng = np.random.default_rng(0) n = 500 X = pd.DataFrame({ "anciennete_mois": rng.integers(1, 120, n), "montant_mensuel": rng.uniform(10, 120, n).round(2), "nb_reclamations": rng.poisson(1.0, n), }) logit = -1.0 - 0.03 * X["anciennete_mois"] + 0.02 * X["montant_mensuel"] + 0.8 * X["nb_reclamations"] y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int) modele = make_pipeline(StandardScaler(), LogisticRegression()).fit(X, y) FEATURES = list(X.columns) import io import joblib def serialiser(modele): # TODO pass def deserialiser(octets): # TODO passFiche modèle
Écrivez
fiche_modele(modele, X_test, y_test, version)qui renvoie un dictionnaire sérialisable en JSON contenant :version,features(liste des colonnes de X_test, dans l’ordre),accuracy(float arrondi à 4 décimales),sha256(empreinte hexadécimale des octets joblib du modèle),sklearn(version de scikit-learn) etcree_le(date ISO 8601 en UTC).import numpy as np import pandas as pd from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression rng = np.random.default_rng(0) n = 500 X = pd.DataFrame({ "anciennete_mois": rng.integers(1, 120, n), "montant_mensuel": rng.uniform(10, 120, n).round(2), "nb_reclamations": rng.poisson(1.0, n), }) logit = -1.0 - 0.03 * X["anciennete_mois"] + 0.02 * X["montant_mensuel"] + 0.8 * X["nb_reclamations"] y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int) modele = make_pipeline(StandardScaler(), LogisticRegression()).fit(X, y) FEATURES = list(X.columns) import io, json, hashlib, datetime import joblib import sklearn from sklearn.metrics import accuracy_score def fiche_modele(modele, X_test, y_test, version): # TODO passCharger un modèle reçu
Un partenaire vous envoie
modele.pklpar e-mail. Que faites-vous ?- Je le charge avec pickle.load pour tester
- Je ne le charge pas tel quel : le chargement d'un pickle peut exécuter du code ; je demande un format sûr ou le code d'entraînement
- Je le renomme en .joblib
- Je l'ouvre dans un éditeur de texte puis je le charge
Quiz de fin de leçon
Pourquoi sauvegarder un Pipeline plutôt que le seul classifieur ?
- Le fichier est plus petit
- Pour que le prétraitement appliqué en production soit exactement celui de l'entraînement
- Parce que joblib ne sait pas sauvegarder un classifieur seul
- Pour accélérer l'entraînement
À quoi sert l’empreinte SHA-256 d’un artefact ?
- À chiffrer le modèle
- À vérifier que le fichier déployé est identique à celui qui a été validé
- À compresser le fichier
- À mesurer la performance
Le modèle a été entraîné avec scikit-learn 1.9.1. En production, quelle version installer ?
- La dernière disponible
- La même version exacte, figée dans requirements.txt
- N'importe quelle version 1.x
- Aucune, joblib suffit
La suite du cours
- Servir un modèle avec une API FastAPI20 min
- Conteneuriser le service avec Docker18 min
- Tests, intégration continue et registre de modèles20 min
- Surveiller un modèle en production : logs, dérive et ré-entraînement20 min