Un pipeline CI/CD pour du code applicatif se résume souvent à : lint, test, build, deploy. Simple et efficace. Mais quand on l’applique naïvement aux modèles de Machine Learning, on découvre rapidement que les hypothèses qui sous-tendent ce modèle ne tiennent plus. Les données changent, les métriques ne sont pas binaires, et un modèle peut “passer” tous ses tests tout en étant silencieusement dégradé.
La différence fondamentale : les tests ne sont pas déterministes
En développement logiciel, un test qui passe aujourd’hui passe demain, à condition que le code n’ait pas changé. En ML, un modèle entraîné sur les données de la semaine dernière peut avoir une précision très différente de celui entraîné cette semaine sans que le code ait bougé d’une ligne.
Cela implique deux niveaux de CI/CD en ML :
- CI/CD du code Tester les scripts d’entraînement, les transformations, les APIs de serving (logique classique)
- CI/CD du modèle Valider les métriques, détecter les dérives, approuver les promotions en production (logique ML-spécifique)
Les étapes d’un pipeline ML mature
Étape 1 : Validation des données
Avant même d’entraîner un modèle, il faut vérifier que les données d’entrée respectent les contrats attendus. Un schéma invalide, des valeurs manquantes anormales, ou une distribution qui dévie trop de la baseline peuvent corrompre l’entraînement.
# Avec Great Expectations ou Pandera
import pandera as pa
schema = pa.DataFrameSchema({
"age": pa.Column(int, pa.Check.between(0, 120)),
"montant": pa.Column(float, pa.Check.gt(0)),
"categorie": pa.Column(str, pa.Check.isin(["A", "B", "C"])),
})
# Raise une erreur si les données ne respectent pas le schéma
validated_df = schema.validate(df)
Si cette étape échoue, le pipeline s’arrête immédiatement pas d’entraînement sur des données corrompues.
Étape 2 : Tests du code d’entraînement
Le code qui prépare les données et entraîne le modèle doit être testé comme n’importe quel code applicatif :
# test_pipeline.py
def test_feature_engineering_output_shape():
sample_data = create_sample_dataset(n=100)
features = build_features(sample_data)
assert features.shape[1] == EXPECTED_FEATURE_COUNT
def test_model_training_completes():
small_data = create_sample_dataset(n=50)
model = train_model(small_data, epochs=1)
assert model is not None
def test_prediction_returns_probabilities():
model = load_test_model()
preds = model.predict_proba([[0.5, 1.2, 0.3]])
assert 0 <= preds[0][1] <= 1
Ces tests tournent sur de petits jeux de données synthétiques et doivent être rapides (< 2 minutes).
Étape 3 : Entraînement et évaluation
Le vrai entraînement sur les données complètes peut prendre des heures. Dans le pipeline CI/CD, on déclenche cet entraînement et on évalue les métriques sur un jeu de validation :
# .github/workflows/train.yml
- name: Train and evaluate
run: |
python train.py \
--data-version ${{ env.DATA_VERSION }} \
--output-path ./artifacts/model \
--mlflow-uri ${{ secrets.MLFLOW_URI }}
- name: Check metrics gate
run: |
python check_metrics.py \
--min-f1-score 0.82 \
--min-precision 0.80 \
--model-path ./artifacts/model
Si les métriques n’atteignent pas les seuils définis, le pipeline échoue. Pas de déploiement automatique d’un modèle dégradé.
Étape 4 : Comparaison avec le modèle en production
Ce point est souvent négligé : avant de promouvoir un nouveau modèle, il faut s’assurer qu’il est meilleur que l’actuel sur les mêmes données d’évaluation.
production_metrics = load_production_model_metrics()
candidate_metrics = load_candidate_metrics()
if candidate_metrics["f1"] < production_metrics["f1"] - REGRESSION_TOLERANCE:
raise ValueError(
f"Candidate model is worse than production: "
f"{candidate_metrics['f1']:.3f} vs {production_metrics['f1']:.3f}"
)
Étape 5 : Shadow deployment
Avant un déploiement complet, le mode “shadow” consiste à envoyer les mêmes requêtes aux deux versions (ancienne et nouvelle) sans affecter les réponses réelles aux utilisateurs. On compare les outputs pour détecter des comportements inattendus.
# Istio VirtualService pour le shadow traffic
spec:
http:
- mirror:
host: fraud-detection-v2
port:
number: 8080
mirrorPercentage:
value: 100
route:
- destination:
host: fraud-detection-v1
Étape 6 : Déploiement progressif
Si le shadow deployment est concluant, on passe à un déploiement canary (5% → 20% → 50% → 100%) avec des métriques business automatiquement surveillées.
Les pièges classiques
Piège 1 : Tester uniquement sur les métriques globales. Un modèle peut avoir une précision globale de 90% tout en étant catastrophique sur un sous-groupe (clients seniors, transactions nocturnes). Ajoutez des métriques segmentées dans votre gate.
Piège 2 : Ne pas versionner les données. Si vous ne savez pas sur quelles données a été entraîné le modèle en production, vous ne pouvez pas reproduire une expérience. Utilisez DVC ou un registre de données.
Piège 3 : Un seul environment de staging. Les modèles ML ont besoin de staging, de canary, et de production chacun avec ses propres données de monitoring.
Piège 4 : Ignorer la latence. Un modèle 2% plus précis qui met 3× plus de temps à répondre peut dégrader l’expérience utilisateur. Ajoutez des tests de performance dans votre pipeline.
Conclusion
Un CI/CD ML mature est un investissement. Mais chaque heure passée à construire ces gardes-fous vous en économise des dizaines lors de l’investigation d’un incident en production. La clé est de traiter les données, le code et le modèle comme trois artefacts de première classe, chacun avec son propre cycle de validation.
