Serialisation et versionnement de modeles
Introduction
L'entrainement d'un modele peut prendre des minutes, des heures ou meme des jours. Une fois que vous avez un modele performant, vous devez le sauvegarder pour pouvoir le reutiliser plus tard — pour la prediction, le deploiement ou le partage avec les collegues. Ce processus s'appelle la serialisation.
La serialisation est comme mettre un plat cuisine au congelateur. Vous avez passe des heures a preparer un ragout complexe (entrainer le modele). Au lieu de recommencer a chaque fois, vous le congelez (serialisez) et le rechauffez (deserialisez) quand vous en avez besoin. Le plat conserve toutes ses saveurs sans avoir a cuisiner de nouveau.
1. Pourquoi serialiser les modeles ?
| Raison | Detail |
|---|---|
| Deploiement | Un modele en memoire Python ne peut pas servir une API. Il doit etre sauvegarde sur disque. |
| Reproductibilite | Pouvoir recreer exactement les memes predictions 6 mois plus tard. |
| Collaboration | Partager un modele avec un collegue sans lui demander de le reentrainer. |
| Versionnement | Conserver plusieurs versions et pouvoir revenir en arriere. |
| Efficacite | Eviter de reentrainer un modele couteux a chaque redemarrage du serveur. |
2. Pickle — Le standard Python
pickle est le module de serialisation natif de Python. Il convertit un objet Python en une sequence d'octets et vice versa.
Sauvegarder un modele avec Pickle
import pickle
from sklearn.ensemble import RandomForestClassifier
# Train model
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# Serialize (save)
with open('model_rf.pkl', 'wb') as f:
pickle.dump(model, f)
print("Model saved successfully!")
Charger un modele avec Pickle
# Deserialize (load)
with open('model_rf.pkl', 'rb') as f:
loaded_model = pickle.load(f)
# Verify it works
predictions = loaded_model.predict(X_test)
print(f"Loaded model accuracy: {loaded_model.score(X_test, y_test):.4f}")
Sauvegarder un pipeline complet
En production, vous devez sauvegarder le pipeline complet (pretraitement + modele), pas seulement le modele. Sinon, vous devrez reproduire manuellement les etapes de pretraitement, ce qui est source d'erreurs.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
pipeline = Pipeline([
('scaler', StandardScaler()),
('clf', RandomForestClassifier(n_estimators=100, random_state=42))
])
pipeline.fit(X_train, y_train)
# Save the ENTIRE pipeline
with open('pipeline_rf.pkl', 'wb') as f:
pickle.dump(pipeline, f)
# Load and predict — no manual preprocessing needed!
with open('pipeline_rf.pkl', 'rb') as f:
loaded_pipeline = pickle.load(f)
predictions = loaded_pipeline.predict(X_test) # scaler + model applied
Risques de securite de Pickle
Ne chargez JAMAIS un fichier pickle provenant d'une source non fiable. pickle.load() peut executer du code arbitraire. Un fichier pickle malveillant peut supprimer des fichiers, installer des logiciels malveillants ou voler des donnees.
# ⚠️ EXAMPLE OF MALICIOUS PICKLE — DO NOT ACTUALLY USE
import pickle
import os
class MaliciousModel:
def __reduce__(self):
return (os.system, ('rm -rf /',)) # Deletes everything!
# If someone gives you this pickle file and you load it...
# pickle.load(malicious_file) → YOUR SYSTEM IS COMPROMISED
Regles de securite :
- Ne jamais charger un pickle d'une source inconnue
- Valider l'integrite du fichier (checksum SHA256)
- Utiliser des environnements isoles (conteneurs Docker)
- Considerer des alternatives plus sures (ONNX, joblib avec precaution)
3. Joblib — Optimise pour les donnees scientifiques
joblib est une alternative a pickle specifiquement optimisee pour les objets contenant de grands tableaux NumPy (ce qui est le cas pour la plupart des modeles ML).
Avantages de Joblib par rapport a Pickle
| Fonctionnalite | Pickle | Joblib |
|---|---|---|
| Vitesse (grands tableaux) | Standard | ⚡ 2-10x plus rapide |
| Taille de fichier | Standard | 📦 Compression integree |
| Grands objets NumPy | Performance moyenne | Optimise |
| Securite | ⚠️ Risque | ⚠️ Similaire a pickle |
| Compatibilite | Tout objet Python | Tout objet Python |
Utiliser Joblib
import joblib
# Save model (no compression)
joblib.dump(model, 'model_rf.joblib')
# Save with compression (smaller file, slightly slower)
joblib.dump(model, 'model_rf_compressed.joblib', compress=3)
# Load model
loaded_model = joblib.load('model_rf.joblib')
# Verify
print(f"Loaded model accuracy: {loaded_model.score(X_test, y_test):.4f}")
Comparaison des tailles de fichiers
import os
import pickle
import joblib
# Save with different methods
with open('model_pickle.pkl', 'wb') as f:
pickle.dump(model, f)
joblib.dump(model, 'model_joblib.joblib')
joblib.dump(model, 'model_joblib_c3.joblib', compress=3)
joblib.dump(model, 'model_joblib_c9.joblib', compress=9)
# Compare sizes
files = ['model_pickle.pkl', 'model_joblib.joblib',
'model_joblib_c3.joblib', 'model_joblib_c9.joblib']
for f in files:
size_kb = os.path.getsize(f) / 1024
print(f"{f:30s} → {size_kb:8.1f} KB")
Preferez joblib pour les modeles scikit-learn en general. L'avantage est particulierement significatif pour les modeles avec de grands tableaux internes (Random Forest avec beaucoup d'arbres, grandes matrices de poids, etc.).
4. ONNX — Interoperabilite multi-plateforme
ONNX (Open Neural Network Exchange) est un format ouvert concu pour la portabilite des modeles entre les frameworks et les langages de programmation.
Convertir un modele scikit-learn en ONNX
# pip install skl2onnx onnxruntime
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
# Define input shape
initial_type = [('float_input', FloatTensorType([None, X_train.shape[1]]))]
# Convert model to ONNX
onnx_model = convert_sklearn(model, initial_types=initial_type)
# Save ONNX model
with open('model_rf.onnx', 'wb') as f:
f.write(onnx_model.SerializeToString())
print("ONNX model saved successfully!")
Utiliser un modele ONNX pour l'inference
import onnxruntime as ort
import numpy as np
# Load ONNX model
session = ort.InferenceSession('model_rf.onnx')
# Get input name
input_name = session.get_inputs()[0].name
# Run inference
X_test_float = X_test.astype(np.float32)
onnx_predictions = session.run(None, {input_name: X_test_float})
predicted_labels = onnx_predictions[0]
print(f"ONNX predictions (first 5): {predicted_labels[:5]}")
Avantages et limites de ONNX
| Avantage | Detail |
|---|---|
| Portabilite | Meme modele en Python, C++, JavaScript, mobile |
| Performance | ONNX Runtime est souvent plus rapide que scikit-learn natif |
| Securite | Pas d'execution de code arbitraire (contrairement a pickle) |
| Standardisation | Format ouvert supporte par Microsoft, Meta, AWS |
| Limite | Detail |
|---|---|
| Conversion | Tous les modeles/operations ne sont pas supportes |
| Complexite | Configuration plus complexe que pickle/joblib |
| Debogage | Plus difficile d'inspecter le modele |
| Pipeline | Les pipelines complexes peuvent ne pas se convertir facilement |
5. Tableau comparatif — Formats de serialisation
| Critere | Pickle | Joblib | ONNX |
|---|---|---|---|
| Facilite d'utilisation | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| Performance (grands modeles) | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Securite | ⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
| Portabilite | ⭐⭐ (Python uniquement) | ⭐⭐ (Python uniquement) | ⭐⭐⭐⭐⭐ |
| Compression | ❌ Manuelle | ✅ Integree | N/A |
| Support des pipelines | ✅ Complet | ✅ Complet | ⚠️ Partiel |
| Inference rapide | Standard | Standard | ⚡ Optimisee |
| Ecosysteme | Standard Python | scikit-learn | Multi-framework |
| Cas d'usage | Prototypage rapide | Modeles sklearn en production | Deploiement multi-plateforme |
- Developpement / Prototypage →
joblib(simple et efficace) - Production Python →
joblibavec compression - Production multi-langage →
ONNX - A eviter en production →
picklebrut (preferez joblib)
6. Strategies de versionnement de modeles
Pourquoi versionner les modeles ?
Voir l'historique des versions de modeles
| Situation | Sans versionnement | Avec versionnement |
|---|---|---|
| Le nouveau modele est pire que l'ancien | 😱 Panique, impossible de revenir | 😌 Retour arriere en 1 minute |
| Audit reglementaire | ❌ Impossible de prouver quel modele etait actif | ✅ Historique complet |
| Test A/B | ❌ Un seul modele possible | ✅ Comparer v1 vs v2 en production |
| Bug en production | 😰 Quel modele cause le bug ? | 🔍 Trace de version exacte |
Convention de nommage
models/
├── iris_classifier_v1.0.0_2025-01-15.joblib
├── iris_classifier_v1.1.0_2025-02-01.joblib
├── iris_classifier_v2.0.0_2025-03-10.joblib
├── metadata/
│ ├── iris_classifier_v1.0.0_metadata.json
│ ├── iris_classifier_v1.1.0_metadata.json
│ └── iris_classifier_v2.0.0_metadata.json
Sauvegarder des modeles avec des metadonnees
import json
import joblib
from datetime import datetime
from sklearn.metrics import accuracy_score, f1_score
def save_model_with_metadata(model, X_test, y_test, version, model_name):
"""Save a model alongside its metadata for tracking."""
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"{model_name}_v{version}_{timestamp}.joblib"
# Save model
joblib.dump(model, filename)
# Generate and save metadata
y_pred = model.predict(X_test)
metadata = {
"model_name": model_name,
"version": version,
"timestamp": timestamp,
"filename": filename,
"metrics": {
"accuracy": round(accuracy_score(y_test, y_pred), 4),
"f1_score": round(f1_score(y_test, y_pred, average='weighted'), 4),
},
"hyperparameters": model.get_params() if hasattr(model, 'get_params') else {},
"training_samples": len(X_test),
"python_version": "3.10",
"sklearn_version": "1.3.0",
}
metadata_file = f"{model_name}_v{version}_metadata.json"
with open(metadata_file, 'w') as f:
json.dump(metadata, f, indent=2, default=str)
print(f"✅ Model saved: {filename}")
print(f"📋 Metadata saved: {metadata_file}")
return filename, metadata
# Usage
save_model_with_metadata(
model=best_model,
X_test=X_test, y_test=y_test,
version="1.0.0",
model_name="iris_classifier"
)
7. MLflow — Registre et suivi de modeles
MLflow est une plateforme open source pour gerer le cycle de vie du ML : suivi des experiences, versionnement des modeles et deploiement.
Suivi basique avec MLflow
# pip install mlflow
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score
mlflow.set_experiment("iris-classification")
with mlflow.start_run(run_name="random_forest_v1"):
# Log hyperparameters
mlflow.log_param("n_estimators", 100)
mlflow.log_param("max_depth", 5)
mlflow.log_param("random_state", 42)
# Train model
model = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
model.fit(X_train, y_train)
# Evaluate
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred, average='weighted')
# Log metrics
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("f1_score", f1)
# Log model
mlflow.sklearn.log_model(model, "model")
print(f"Run ID: {mlflow.active_run().info.run_id}")
print(f"Accuracy: {accuracy:.4f}, F1: {f1:.4f}")
Charger un modele depuis MLflow
# Load by run ID
run_id = "abc123def456"
model_uri = f"runs:/{run_id}/model"
loaded_model = mlflow.sklearn.load_model(model_uri)
# Load from model registry (production stage)
model_uri = "models:/iris-classifier/Production"
production_model = mlflow.sklearn.load_model(model_uri)
predictions = production_model.predict(X_new)
Cycle de vie des modeles MLflow
| Etape | Description | Qui y accede |
|---|---|---|
| None | Modele enregistre mais pas encore evalue | Developpeur |
| Staging | En cours de validation, tests d'integration | Equipe QA |
| Production | Sert des predictions en temps reel | API / Utilisateurs |
| Archive | Retire, conserve pour audit et historique | Archive |
8. Sauvegarder des pipelines complets
La plus grande source de bugs en production ML provient de l'incoherence entre le pretraitement d'entrainement et le pretraitement de production. Sauvegarder le pipeline complet elimine ce risque.
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
import joblib
# Define preprocessing for different column types
numeric_features = ['age', 'income', 'credit_score']
categorical_features = ['gender', 'education', 'employment']
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numeric_features),
('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features)
]
)
# Build complete pipeline
full_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(n_estimators=100, random_state=42))
])
# Train on raw data
full_pipeline.fit(X_train_raw, y_train)
# Save EVERYTHING in one file
joblib.dump(full_pipeline, 'full_pipeline_v1.0.0.joblib')
# In production: load and predict on raw input
pipeline = joblib.load('full_pipeline_v1.0.0.joblib')
predictions = pipeline.predict(new_raw_data) # preprocessing handled automatically
9. Considerations sur la taille des fichiers
| Modele | Taille typique | Format recommande |
|---|---|---|
| Regression Logistique | 1-10 Ko | pickle / joblib |
| SVM (petit jeu de donnees) | 10-100 Ko | joblib |
| Random Forest (100 arbres) | 1-50 Mo | joblib compress=3 |
| Random Forest (1000 arbres) | 50-500 Mo | joblib compress=9 |
| Deep Learning (ResNet50) | 100-300 Mo | ONNX |
| LLM (type GPT) | 1-100 Go | Formats specialises |
import os
import joblib
# Check file size before deployment
model_file = 'full_pipeline_v1.0.0.joblib'
size_mb = os.path.getsize(model_file) / (1024 * 1024)
print(f"Model file size: {size_mb:.1f} MB")
if size_mb > 100:
print("⚠️ Model is large. Consider:")
print(" - Reducing n_estimators")
print(" - Using compression: joblib.dump(model, file, compress=9)")
print(" - Converting to ONNX for optimized runtime")
elif size_mb > 500:
print("🚨 Model too large for most API deployments")
print(" - Consider model distillation or pruning")
Resume
🔑 Points cles a retenir
- Toujours serialiser le pipeline complet (pretraitement + modele), pas seulement le modele.
- Pickle : Simple mais dangereux. Ne jamais charger d'une source non fiable.
- Joblib : Prefere pour scikit-learn. Compression integree, optimise pour les grands tableaux.
- ONNX : Pour le deploiement multi-plateforme et l'inference rapide. Plus securise.
- Versionnez vos modeles avec des conventions de nommage et des metadonnees (metriques, hyperparametres, date).
- MLflow : L'outil de reference pour le suivi d'experiences et le registre de modeles.
- Taille de fichier : Verifiez avant le deploiement. Compressez si necessaire.
- Metadonnees : Sauvegardez toujours les versions des dependances (Python, sklearn) avec le modele.
Lectures complementaires
| Ressource | Lien |
|---|---|
| Documentation Python pickle | docs.python.org/3/library/pickle |
| Documentation Joblib | joblib.readthedocs.io |
| ONNX Runtime | onnxruntime.ai |
| Documentation skl2onnx | onnx.ai/sklearn-onnx |
| Documentation MLflow | mlflow.org/docs/latest |