MLOps Fundamentals : les 9 étapes clés pour passer de zéro à opérationnel
MLOps Fundamentals : l'essentiel en un article — vrai code, schémas et étapes concrètes, extraits d'un cours de 72 leçons.
Tout le monde peut apprendre MLOps Fundamentals — à condition de suivre les étapes dans le bon ordre. On a condensé un cours complet de 72 leçons en un parcours clair, avec les extraits de code les plus utiles.
- Installer l'environnement MLOps
- Decouvrir le MLOps
- Versionnement des donnees et modeles
- Pipelines ML avec MLflow
- Containeriser les modeles ML
Guide Complet du Projet Final – Étape par Étape
Cours MLOps Fundamentals • Détection de Fraude par Carte de Crédit • Pipeline MLOps End-to-End
Ce guide détaillé vous accompagne pas à pas dans la réalisation du projet final. Chaque section correspond à une étape clé du pipeline MLOps. Suivez les étapes dans l’ordre. Tout le code est fourni et expliqué. Durée estimée : 8–12 heures au total.
① Étape 1 – Initialisation du projet et environnement
Créez la structure du projet et l’environnement Conda :
mkdir fraud-detection-mlops
cd fraud-detection-mlops
git init
git config user.email "vous@email.com"
git config user.name "Votre Nom"
conda create -n fraud-mlops python=3.10 -y
conda activate fraud-mlops
pip install scikit-learn xgboost pandas numpy mlflow dvc \
fastapi uvicorn pydantic evidently \
pytest pytest-cov httpx joblib matplotlib \
seaborn imbalanced-learn flake8 black
conda env export > environment.ymlCréez le fichier requirements.txt :
scikit-learn==1.4.0 xgboost==2.0.3 pandas==2.1.4 numpy==1.26.3 mlflow==2.10.0 dvc==3.38.1 fastapi==0.109.0 uvicorn==0.27.0 pydantic==2.5.3 evidently==0.4.16 pytest==7.4.4 pytest-cov==4.1.0 httpx==0.26.0 joblib==1.3.2 matplotlib==3.8.2 seaborn==0.13.1 imbalanced-learn==0.11.0
Créez la structure des dossiers :
mkdir -p data/raw data/processed src api monitoring/reports tests models .github/workflows
__pycache__/ *.pyc *.pyo .env models/ mlruns/ *.pkl data/raw/ data/processed/ monitoring/reports/*.html monitoring/reports/*.json .coverage htmlcov/
dvc init git add . git commit -m "feat: init project structure and DVC"
② Étape 2 – Génération et versionnement des données
Créez src/generate_synthetic_data.py (si vous n’avez pas accès au dataset Kaggle) :
"""Génère un dataset synthétique de fraude carte de crédit."""
import pandas as pd
import numpy as np
def generate_fraud_dataset(n_samples=50000, fraud_ratio=0.002, random_state=42):
np.random.seed(random_state)
n_fraud = int(n_samples * fraud_ratio)
n_legit = n_samples - n_fraud
legit = pd.DataFrame({
f'V{i}': np.random.normal(0, 1, n_legit) for i in range(1, 29)
})
legit['Amount'] = np.abs(np.random.exponential(scale=88, size=n_legit))
legit['Time'] = np.sort(np.random.uniform(0, 172800, n_legit))
legit['Class'] = 0
fraud = pd.DataFrame({
f'V{i}': np.random.normal(np.random.uniform(-3, 3), 2, n_fraud) for i in range(1, 29)
})
fraud['Amount'] = np.abs(np.random.exponential(scale=122, size=n_fraud))
fraud['Time'] = np.random.uniform(0, 172800, n_fraud)
fraud['Class'] = 1
df = pd.concat([legit, fraud], ignore_index=True)
df = df.sample(frac=1, random_state=random_state).reset_index(drop=True)
return df
if __name__ == "__main__":
df = generate_fraud_dataset()
df.to_csv("data/raw/creditcard.csv", index=False)
print(f"Dataset généré : {len(df)} lignes")
print(f"Fraudes : {df['Class'].sum()} ({df['Class'].mean()*100:.3f}%)")Dockerfile pour un modèle ML : build et run
Cours MLOps Fundamentals — Chapitre 04 — Containeriser les modèles ML
- Comprendre chaque instruction d'un Dockerfile pour un modèle ML
- Écrire un Dockerfile complet pour une API scikit-learn avec FastAPI
- Construire une image Docker avec
docker build - Exécuter et tester un conteneur ML avec
docker run - Déboguer les problèmes courants de conteneurs ML
1. Structure du projet ML à conteneuriser
Avant d'écrire le Dockerfile, voyons la structure du projet que nous allons conteneuriser. Il s'agit d'une API de classification de vins basée sur un modèle Random Forest entraîné avec scikit-learn, servi via FastAPI.
wine-classifier/
â└â─â─ Dockerfile # Notre fichier de configuration Docker
â└â─â─ .dockerignore # Fichiers à exclure de l'image
â└â─â─ requirements.txt # Dépendances Python
â└â─â─ src/
â├â─â─ app.py # Application FastAPI (point d'entrée)
â├â─â─ predict.py # Logique de prédiction
â├â─â─ preprocessing.py # Pré-traitement des features
â└â─â─ models/
â└â─â─ rf_classifier.pkl # Modèle Random Forest sérialisé
â└â─â─ scaler.pkl # StandardScaler sauvegardéLe fichier requirements.txt
Le requirements.txt liste toutes les dépendances Python avec leurs versions pinées. C'est crucial pour la reproductibilité : sans version fixée, pip install pourrait télécharger une version plus récente et incompatible.
# requirements.txt # Framework web fastapi==0.110.0 uvicorn[standard]==0.27.1 # Machine Learning scikit-learn==1.4.1 numpy==1.26.4 pandas==2.2.1 # Sérialisation du modèle joblib==1.3.2 # Validation des données pydantic==2.6.3
Éviter
scikit-learn>=1.0 ou scikit-learn sans version. Si scikit-learn publie la version 2.0 avec des changements d'API, votre conteneur cassera au prochain rebuild. Utilisez toujours scikit-learn==1.4.1.L'application FastAPI (src/app.py)
# src/app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import numpy as np
import os
app = FastAPI(
title="Wine Quality Classifier API",
description="API de classification de la qualité du vin basée sur Random Forest",
version="1.0.0"
)
# Chargement du modèle au démarrage (pas à chaque requête)
MODEL_PATH = os.getenv("MODEL_PATH", "/app/models/rf_classifier.pkl")
SCALER_PATH = os.getenv("SCALER_PATH", "/app/models/scaler.pkl")
model = joblib.load(MODEL_PATH)
scaler = joblib.load(SCALER_PATH)
class WineFeatures(BaseModel):
fixed_acidity: float
volatile_acidity: float
citric_acid: float
residual_sugar: float
chlorides: float
free_sulfur_dioxide: float
total_sulfur_dioxide: float
density: float
pH: float
sulphates: float
alcohol: float
@app.get("/health")
def health_check():
return {"status": "ok", "model": "rf_classifier", "version": "1.0.0"}
@app.post("/predict")
def predict(features: WineFeatures):
try:
X = np.array([[
features.fixed_acidity, features.volatile_acidity,
features.citric_acid, features.residual_sugar,
features.chlorides, features.free_sulfur_dioxide,
features.total_sulfur_dioxide, features.density,
features.pH, features.sulphates, features.alcohol
]])
X_scaled = scaler.transform(X)
prediction = model.predict(X_scaled)[0]
probability = model.predict_proba(X_scaled)[0].max()
return {
"quality": int(prediction),
"confidence": round(float(probability), 4),
"label": "bon vin" if prediction >= 6 else "vin moyen"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))2. Le Dockerfile complet et commenté
Voici le Dockerfile complet. Nous allons analyser chaque instruction en détail immédiatement après.
# ============================================================
# Dockerfile pour un modèle scikit-learn servi avec FastAPI
# ============================================================
# Étape 1 : Image de base
FROM python:3.11-slim
# Étape 2 : Métadonnées de l'image
LABEL maintainer="mlops-team@exemple.com"
LABEL version="1.0.0"
LABEL description="Wine Quality Classifier - Random Forest API"
# Étape 3 : Variables d'environnement système
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Étape 4 : Répertoire de travail dans le conteneur
WORKDIR /app
# Étape 5 : Dépendances système (si nécessaire)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Étape 6 : Copier ET installer les dépendances Python (couche cachée)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Étape 7 : Copier le code source
COPY src/ ./src/
# Étape 8 : Copier le modèle ML
COPY models/ ./models/
# Étape 9 : Exposer le port de l'API
EXPOSE 8000
# Étape 10 : Utilisateur non-root pour la sécurité
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
# Étape 11 : Commande de démarrage
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]3. Analyse détaillée de chaque instruction
3.1 FROM python:3.11-slim
FROM définit l'image de base. python:3.11-slim est la version allégée de l'image Python officielle :
Pour scikit-learn, slim est le bon compromis : assez petit, mais compatible avec les extensions C de numpy/scipy.
:latestFROM python:latest peut passer de Python 3.11 à 3.12 du jour au lendemain et casser votre code. Toujours spécifier la version exacte : python:3.11-slim ou même python:3.11.8-slim.Si votre modèle utilise le GPU (TensorFlow, PyTorch), utilisez :
FROM nvidia/cuda:12.1-cudnn8-runtime-ubuntu22.043.2 Variables d'environnement ENV
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1| Variable | Effet | Pourquoi l'activer ? |
|---|---|---|
PYTHONDONTWRITEBYTECODE=1 |
Pas de fichiers .pyc |
Réduit la taille de l'image, évite les fichiers inutiles |
PYTHONUNBUFFERED=1 |
Logs en temps réel | Indispensable pour voir les logs dans docker logs immédiatement |
PIP_NO_CACHE_DIR=1 |
Pas de cache pip dans l'image | Réduit la taille finale de l'image (peut sauver 100-200 Mo) |
PIP_DISABLE_PIP_VERSION_CHECK=1 |
Pas de vérification de mise à jour pip | Accélère le build, évite les avertissements parasites |
3.3 WORKDIR /app
WORKDIR définit le répertoire de travail dans le conteneur. Toutes les instructions suivantes (COPY, RUN, CMD) s'exécutent depuis ce répertoire. Si le répertoire n'existe pas, Docker le crée automatiquement.
/app comme répertoire de travail pour les applications Python. Éviter de travailler directement dans / ou /root.Optimisation des images Docker ML
Cours MLOps Fundamentals — Chapitre 04 — Containeriser les modèles ML
- Comprendre pourquoi la taille des images Docker ML est un enjeu critique en production
- Maîtriser les multi-stage builds pour séparer construction et exécution
- Configurer un
.dockerignoreefficace pour les projets ML - Exploiter le cache de couches Docker à son maximum
- Appliquer les meilleures pratiques pour des images ML de production
1. Pourquoi la taille des images ML est critique
Contrairement à une application web classique (quelques dizaines de Mo), une image Docker ML peut facilement atteindre 2 à 8 Go. Cette taille a des conséquences directes sur votre workflow MLOps :
🚬 Déploiement lent
Télécharger une image de 5 Go sur un cluster Kubernetes prend 10 à 20 minutes. En cas d'auto-scaling sous charge, c'est inacceptable.
💸 Coût de stockage
Stocker 50 versions d'une image de 4 Go dans AWS ECR = 200 Go × 0.10$/Go/mois = 20$/mois rien que pour le stockage.
🔐 Surface d'attaque
Chaque outil supplémentaire (compilateurs, utilitaires système) est une potentielle vulnérabilité de sécurité. Une image minimale est plus sécurisée.
2. Comparaison : images grasses vs images slim
| Approche | Image de base | Taille typique | Temps de pull (100 Mbit/s) | Vulnérabilités |
|---|---|---|---|---|
| 🔴 Image grasse (naïve) | python:3.11 + tous les outils |
~2.1 Go | ~170 s | Très nombreuses |
| 🟡 Image slim (bonne pratique) | python:3.11-slim |
~700 Mo | ~56 s | Modérées |
| 🟢 Multi-stage slim | Build : python:3.11-slimRun : python:3.11-slim |
~350 Mo | ~28 s | Peu nombreuses |
| 🆕 Distroless | gcr.io/distroless/python3 |
~180 Mo | ~14 s | Très peu |
| 🟢 Alpine (avec précautions) | python:3.11-alpine |
~100 Mo | ~8 s | Minimales |
Alpine utilise
musl libc au lieu de glibc. Numpy, scikit-learn, pandas et PyTorch sont compilés pour glibc. L'utilisation d'Alpine force la compilation depuis les sources, ce qui prend 30 à 60 minutes et peut échouer. Réserver Alpine aux microservices sans dépendances C. Pour le ML, préférer python:3.11-slim.3. Le .dockerignore : votre première ligne de défense
Avant même le multi-stage build, le .dockerignore est l'optimisation la plus simple et la plus immédiate. Il empêche Docker d'envoyer des fichiers inutiles (voire dangereux) au dáemon de build.
Quand vous exécutez
docker build ., Docker envoie tout le répertoire courant au déamon Docker (appelé « contexte de build »). Sans .dockerignore, cela inclut votre dataset de 10 Go, les notebooks Jupyter, les environnements virtuels Python, les fichiers de configuration secrets…# .dockerignore pour un projet ML # Contrôle de version .git/ .gitignore .github/ # Environnements Python virtuels (critique ! peut faire des centaines de Mo) .venv/ venv/ env/ ENV/ __pycache__/ *.py[cod] *.pyo .pytest_cache/ .mypy_cache/ # Données ML (ne jamais embarquer dans l'image !) data/ datasets/ *.csv *.parquet *.arrow *.feather # Notebooks Jupyter (pas nécessaires en production) *.ipynb .ipynb_checkpoints/ # Fichiers de configuration locale .env .env.local .env.development *.env # Documentation et tests (inutiles en production) docs/ tests/ README.md CHANGELOG.md # Artefacts de build dist/ build/ *.egg-info/ htmlcov/ .coverage # IDE et éditeurs .vscode/ .idea/ *.swp *.swo .DS_Store Thumbs.db # Logs et fichiers temporaires logs/ *.log tmp/ temp/ # Fichiers MLflow et expériences mlruns/ mlflow.db # Modèles de test (garder seulement le modèle de production) models/experiments/ models/checkpoints/
Mesurer l'impact du .dockerignore
# Vérifier la taille du contexte de build AVANT .dockerignore docker build -t test-before . 2>&1 | head -5 # Sending build context to Docker daemon 4.521GB <-- sans .dockerignore ! # Vérifier la taille du contexte de build APRES .dockerignore docker build -t test-after . 2>&1 | head -5 # Sending build context to Docker daemon 12.34MB <-- avec .dockerignore
4. Les multi-stage builds pour le ML
Un multi-stage build utilise plusieurs instructions FROM dans un seul Dockerfile. Chaque étape produit une couche intermédiaire, et seule la dernière étape est conservée dans l'image finale. Cela permet de :
4.1 Multi-stage build pour un modèle scikit-learn
# ============================================================
# Multi-stage Dockerfile pour Wine Classifier (production)
# ============================================================
# ---- ÉTAPE 1 : Builder ----
# Cette étape installe tout, y compris les outils de compilation
FROM python:3.11-slim AS builder
# Variables d'environnement pour le build
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /build
# Installer les outils de build système (ne seront PAS dans l'image finale)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
build-essential \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Copier et installer dans un répertoire local isolé (--prefix)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ---- ÉTAPE 2 : Image de production (finale) ----
# Image finale ultra-légère : ne contient PAS gcc, build-essential, etc.
FROM python:3.11-slim AS production
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# Copier uniquement les bibliothèques installées depuis le builder
COPY --from=builder /install /usr/local
# Dépendances système runtime (minimales)
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Copier le code source et le modèle
COPY src/ ./src/
COPY models/ ./models/
# Sécurité : utilisateur non-root
RUN useradd --create-home --shell /bin/bash appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]En éliminant
gcc, g++, build-essential et les fichiers intermédiaires de compilation de l'image finale, vous gagnez généralement entre 200 et 600 Mo selon les dépendances C/C++ utilisées.4.2 Multi-stage build avec étape de test
# ============================================================ # Multi-stage Dockerfile avec étape de test intégrée # ============================================================ FROM python:3.11-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # ---- Étape de test ---- FROM base AS test COPY requirements-dev.txt . RUN pip install --no-cache-dir -r requirements-dev.txt COPY src/ ./src/ COPY models/ ./models/ COPY tests/ ./tests/ RUN pytest tests/ -v --tb=short # ---- Étape de production ---- FROM base AS production COPY src/ ./src/ COPY models/ ./models/ RUN useradd --create-home appuser && chown -R appuser /app USER appuser EXPOSE 8000 CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]
Cet article couvre les extraits les plus utiles — le cours complet MLOps Fundamentals (13 chapitres, 72 leçons, exercices corrigés et projet final) t'emmène jusqu'au bout.
./acceder-au-cours-complet cours gratuit : Maîtriser Claude CodeFAQ
Combien de temps pour apprendre MLOps Fundamentals ?
Faut-il des prérequis ?
Par où commencer concrètement ?
📬 Tu veux recevoir ce type de guide chaque semaine ? Abonne-toi gratuitement — code réel, zéro blabla.