décembre 21, 2025
15 min de lecture

Questions d'entretien pour un poste d'ingénieur Machine Learning Junior : Guide complet

interview
career-advice
job-search
entry-level
Questions d'entretien pour un poste d'ingénieur Machine Learning Junior : Guide complet
Milad Bonakdar

Milad Bonakdar

Auteur

Maîtrisez les fondamentaux de l'ingénierie ML avec des questions d'entretien essentielles couvrant Python, les algorithmes de ML, l'entraînement de modèles, les bases du déploiement et le MLOps pour les jeunes ingénieurs en Machine Learning.


Introduction

Les ingénieurs en apprentissage automatique (Machine Learning Engineers) construisent, déploient et maintiennent des systèmes d'apprentissage automatique en production. On attend des ingénieurs débutants en apprentissage automatique qu'ils aient de solides compétences en programmation, une compréhension des algorithmes d'apprentissage automatique, une expérience des frameworks d'apprentissage automatique et une connaissance des pratiques de déploiement.

Ce guide couvre les questions d'entretien essentielles pour les ingénieurs débutants en apprentissage automatique. Nous explorons la programmation Python, les algorithmes d'apprentissage automatique, la formation et l'évaluation des modèles, les bases du déploiement et les fondamentaux du MLOps pour vous aider à vous préparer à votre premier rôle d'ingénieur en apprentissage automatique.


Python et programmation (5 questions)

1. Comment gérez-vous les grands ensembles de données qui ne tiennent pas en mémoire ?

Réponse: Plusieurs techniques permettent de gérer les données plus volumineuses que la RAM disponible :

  • Traitement par lots (Batch Processing) : Traiter les données par morceaux
  • Générateurs : Fournir les données à la demande
  • Dask/Ray : Frameworks de calcul distribué
  • Requêtes de base de données : Charger uniquement les données nécessaires
  • Fichiers mappés en mémoire : Accéder au disque comme s'il était en mémoire
  • Streaming de données : Traiter les données à mesure qu'elles arrivent
import pandas as pd
import numpy as np

# Mauvais : Charger l'ensemble du dataset en mémoire
# df = pd.read_csv('large_file.csv')  # Peut planter

# Bien : Lire par morceaux
chunk_size = 10000
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    # Traiter chaque morceau
    processed = chunk[chunk['value'] > 0]
    # Sauvegarder ou agréger les résultats
    processed.to_csv('output.csv', mode='a', header=False)

# Utilisation de générateurs
def data_generator(filename, batch_size=32):
    while True:
        batch = []
        with open(filename, 'r') as f:
            for line in f:
                batch.append(process_line(line))
                if len(batch) == batch_size:
                    yield np.array(batch)
                    batch = []

# Dask pour le calcul distribué
import dask.dataframe as dd
ddf = dd.read_csv('large_file.csv')
result = ddf.groupby('category').mean().compute()

Rareté : Très courant Difficulté : Moyenne


2. Expliquez les décorateurs en Python et donnez un cas d'utilisation en apprentissage automatique.

Réponse : Les décorateurs modifient ou améliorent les fonctions sans changer leur code.

  • Cas d'utilisation en apprentissage automatique :
    • Mesurer le temps d'exécution d'une fonction
    • Enregistrer les prédictions (logging)
    • Mettre en cache les résultats
    • Valider les entrées
import time
import functools

# Décorateur de mesure du temps
def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} a pris {end - start:.2f} secondes")
        return result
    return wrapper

# Décorateur d'enregistrement des prédictions (logging)
def log_predictions(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        predictions = func(*args, **kwargs)
        print(f"Nombre de prédictions effectuées : {len(predictions)}")
        print(f"Distribution des prédictions : {np.bincount(predictions)}")
        return predictions
    return wrapper

# Utilisation
@timer
@log_predictions
def predict_batch(model, X):
    return model.predict(X)

# Décorateur de mise en cache (mémoïsation)
def cache_results(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@cache_results
def expensive_feature_engineering(data_id):
    # Calcul coûteux
    return processed_features

Rareté : Courant Difficulté : Moyenne


3. Quelle est la différence entre @staticmethod et @classmethod ?

Réponse : Les deux définissent des méthodes qui ne nécessitent pas d'instance.

  • @staticmethod : Aucun accès à la classe ou à l'instance
  • @classmethod : Reçoit la classe comme premier argument
class MLModel:
    model_type = "classifier"
    
    def __init__(self, name):
        self.name = name
    
    # Méthode régulière - nécessite une instance
    def predict(self, X):
        return self.model.predict(X)
    
    # Méthode statique - fonction utilitaire
    @staticmethod
    def preprocess_data(X):
        # Aucun accès à self ou cls
        return (X - X.mean()) / X.std()
    
    # Méthode de classe - modèle factory
    @classmethod
    def create_default(cls):
        # Accès à cls
        return cls(name=f"default_{cls.model_type}")
    
    @classmethod
    def from_config(cls, config):
        return cls(name=config['name'])

# Utilisation
# Méthode statique - aucune instance nécessaire
processed = MLModel.preprocess_data(X_train)

# Méthode de classe - crée une instance
model = MLModel.create_default()
model2 = MLModel.from_config({'name': 'my_model'})

Rareté : Moyenne Difficulté : Moyenne


4. Comment gérez-vous les exceptions dans les pipelines d'apprentissage automatique ?

Réponse : Une gestion appropriée des erreurs empêche les échecs de pipeline et facilite le débogage.

import logging
from typing import Optional

# Configuration du logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ModelTrainingError(Exception):
    """Exception personnalisée pour les échecs d'entraînement de modèle"""
    pass

def train_model(X, y, model_type='random_forest'):
    try:
        logger.info(f"Début de l'entraînement avec {model_type}")
        
        # Valider les entrées
        if X.shape[0] != y.shape[0]:
            raise ValueError("X et y doivent avoir le même nombre d'échantillons")
        
        if X.shape[0] < 100:
            raise ModelTrainingError("Données d'entraînement insuffisantes")
        
        # Entraîner le modèle
        if model_type == 'random_forest':
            from sklearn.ensemble import RandomForestClassifier
            model = RandomForestClassifier()
        else:
            raise ValueError(f"Type de modèle inconnu : {model_type}")
        
        model.fit(X, y)
        logger.info("Entraînement terminé avec succès")
        return model
        
    except ValueError as e:
        logger.error(f"Erreur de validation : {e}")
        raise
    except ModelTrainingError as e:
        logger.error(f"Erreur d'entraînement : {e}")
        # Pourrait revenir à un modèle plus simple
        return train_fallback_model(X, y)
    except Exception as e:
        logger.error(f"Erreur inattendue : {e}")
        raise
    finally:
        logger.info("Tentative d'entraînement terminée")

# Gestionnaire de contexte pour la gestion des ressources
class ModelLoader:
    def __init__(self, model_path):
        self.model_path = model_path
        self.model = None
    
    def __enter__(self):
        logger.info(f"Chargement du modèle depuis {self.model_path}")
        self.model = load_model(self.model_path)
        return self.model
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        logger.info("Nettoyage des ressources")
        if self.model:
            del self.model
        return False  # Ne pas supprimer les exceptions

# Utilisation
with ModelLoader('model.pkl') as model:
    predictions = model.predict(X_test)

Rareté : Courant Difficulté : Moyenne


5. Que sont les générateurs Python et pourquoi sont-ils utiles en apprentissage automatique ?

Réponse : Les générateurs fournissent des valeurs une par une, économisant ainsi de la mémoire.

  • Avantages :
    • Efficacité mémoire
    • Évaluation paresseuse (lazy evaluation)
    • Séquences infinies
  • Cas d'utilisation en apprentissage automatique :
    • Chargement des données
    • Traitement par lots
    • Augmentation des données
import numpy as np

# Générateur pour le traitement par lots
def batch_generator(X, y, batch_size=32):
    n_samples = len(X)
    indices = np.arange(n_samples)
    np.random.shuffle(indices)
    
    for start_idx in range(0, n_samples, batch_size):
        end_idx = min(start_idx + batch_size, n_samples)
        batch_indices = indices[start_idx:end_idx]
        yield X[batch_indices], y[batch_indices]

# Utilisation dans l'entraînement
for epoch in range(10):
    for X_batch, y_batch in batch_generator(X_train, y_train):
        model.train_on_batch(X_batch, y_batch)

# Générateur d'augmentation des données
def augment_images(images, labels):
    for img, label in zip(images, labels):
        # Original
        yield img, label
        # Inversée
        yield np.fliplr(img), label
        # Rotation
        yield np.rot90(img), label

# Générateur infini pour l'entraînement
def infinite_batch_generator(X, y, batch_size=32):
    while True:
        indices = np.random.choice(len(X), batch_size)
        yield X[indices], y[indices]

# Utilisation avec steps_per_epoch
gen = infinite_batch_generator(X_train, y_train)
# model.fit(gen, steps_per_epoch=100, epochs=10)

Rareté : Courant Difficulté : Moyenne


Algorithmes et théorie de l'apprentissage automatique (5 questions)

6. Expliquez la différence entre le bagging et le boosting.

Réponse : Les deux sont des méthodes d'ensemble, mais fonctionnent différemment :

  • Bagging (Bootstrap Aggregating) :
    • Entraînement parallèle sur des sous-ensembles aléatoires
    • Réduit la variance
    • Exemple : Random Forest
  • Boosting :
    • Entraînement séquentiel, chaque modèle corrige les erreurs précédentes
    • Réduit le biais
    • Exemples : AdaBoost, Gradient Boosting, XGBoost
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score

# Générer des données
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)

# Bagging - Random Forest
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf_scores = cross_val_score(rf, X, y, cv=5)
print(f"Validation croisée Random Forest : {rf_scores.mean():.3f} (+/- {rf_scores.std():.3f})")

# Boosting - Gradient Boosting
gb = GradientBoostingClassifier(n_estimators=100, random_state=42)
gb_scores = cross_val_score(gb, X, y, cv=5)
print(f"Validation croisée Gradient Boosting : {gb_scores.mean():.3f} (+/- {gb_scores.std():.3f})")

# XGBoost (boosting avancé)
import xgboost as xgb
xgb_model = xgb.XGBClassifier(n_estimators=100, random_state=42)
xgb_scores = cross_val_score(xgb_model, X, y, cv=5)
print(f"Validation croisée XGBoost : {xgb_scores.mean():.3f} (+/- {xgb_scores.std():.3f})")

Rareté : Très courant Difficulté : Moyenne


7. Comment gérez-vous les ensembles de données déséquilibrés ?

Réponse : Les données déséquilibrées peuvent biaiser les modèles vers la classe majoritaire.

  • Techniques :
    • Rééchantillonnage : SMOTE, sous-échantillonnage
    • Poids de classe : Pénaliser la mauvaise classification
    • Méthodes d'ensemble : Random Forest équilibré
    • Évaluation : Utiliser F1, précision, rappel (pas l'exactitude)
    • Ajustement du seuil : Optimiser le seuil de décision
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from collections import Counter

# Créer un ensemble de données déséquilibré
X, y = make_classification(
    n_samples=1000, n_features=20,
    weights=[0.95, 0.05],  # 95% classe 0, 5% classe 1
    random_state=42
)

print(f"Distribution originale : {Counter(y)}")

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 1. Poids de classe
model_weighted = RandomForestClassifier(class_weight='balanced', random_state=42)
model_weighted.fit(X_train, y_train)
print("\nAvec des poids de classe :")
print(classification_report(y_test, model_weighted.predict(X_test)))

# 2. SMOTE (suréchantillonnage de la classe minoritaire)
smote = SMOTE(random_state=42)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
print(f"Après SMOTE : {Counter(y_train_smote)}")

model_smote = RandomForestClassifier(random_state=42)
model_smote.fit(X_train_smote, y_train_smote)
print("\nAvec SMOTE :")
print(classification_report(y_test, model_smote.predict(X_test)))

# 3. Ajustement du seuil
y_proba = model_weighted.predict_proba(X_test)[:, 1]
threshold = 0.3  # Un seuil plus bas favorise la classe minoritaire
y_pred_adjusted = (y_proba >= threshold).astype(int)
print("\nAvec un seuil ajusté :")
print(classification_report(y_test, y_pred_adjusted))

Rareté : Très courant Difficulté : Moyenne


8. Qu'est-ce que la validation croisée et pourquoi est-elle importante ?

Réponse : La validation croisée évalue les performances du modèle de manière plus fiable qu'une simple division train-test.

  • Types :
    • K-Fold : Diviser en k plis
    • K-Fold stratifiée : Préserve la distribution des classes
    • Time Series Split : Respecte l'ordre temporel
  • Avantages :
    • Estimation des performances plus robuste
    • Utilise toutes les données pour l'entraînement et la validation
    • Détecte le surapprentissage
from sklearn.model_selection import (
    cross_val_score, KFold, StratifiedKFold,
    TimeSeriesSplit, cross_validate
)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# Charger les données
data = load_iris()
X, y = data.data, data.target

model = RandomForestClassifier(random_state=42)

# K-Fold standard
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kfold)
print(f"Scores K-Fold : {scores}")
print(f"Moyenne : {scores.mean():.3f} (+/- {scores.std():.3f})")

# K-Fold stratifiée (préserve la distribution des classes)
stratified_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
stratified_scores = cross_val_score(model, X, y, cv=stratified_kfold)
print(f"\nK-Fold stratifiée : {stratified_scores.mean():.3f}")

# Time Series Split (pour les données temporelles)
tscv = TimeSeriesSplit(n_splits=5)
ts_scores = cross_val_score(model, X, y, cv=tscv)
print(f"Validation croisée de séries temporelles : {ts_scores.mean():.3f}")

# Métriques multiples
cv_results = cross_validate(
    model, X, y, cv=5,
    scoring=['accuracy', 'precision_macro', 'recall_macro', 'f1_macro'],
    return_train_score=True
)

print(f"\nExactitude : {cv_results['test_accuracy'].mean():.3f}")
print(f"Précision : {cv_results['test_precision_macro'].mean():.3f}")
print(f"Rappel : {cv_results['test_recall_macro'].mean():.3f}")
print(f"F1 : {cv_results['test_f1_macro'].mean():.3f}")

Rareté : Très courant Difficulté : Facile


9. Expliquez la précision, le rappel et le score F1.

Réponse : Métriques de classification pour évaluer les performances du modèle :

  • Précision : Parmi les positifs prédits, combien sont corrects
    • Formule : TP / (TP + FP)
    • Utilisation : Les faux positifs sont coûteux
  • Rappel : Parmi les positifs réels, combien ont été trouvés
    • Formule : TP / (TP + FN)
    • Utilisation : Les faux négatifs sont coûteux
  • Score F1 : Moyenne harmonique de la précision et du rappel
    • Formule : 2 × (Précision × Rappel) / (Précision + Rappel)
    • Utilisation : Nécessite un équilibre entre la précision et le rappel
from sklearn.metrics import (
    precision_score, recall_score, f1_score,
    classification_report, confusion_matrix
)
import numpy as np

# Exemple de prédictions
y_true = np.array([0, 1, 1, 0, 1, 1, 0, 1, 0, 0])
y_pred = np.array([0, 1, 0, 0, 1, 1, 0, 1, 1, 0])

# Calculer les métriques
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)

print(f"Précision : {precision:.3f}")  # 0.800
print(f"Rappel : {recall:.3f}")        # 0.800
print(f"Score F1 : {f1:.3f}")          # 0.800

# Matrice de confusion
cm = confusion_matrix(y_true, y_pred)
print(f"\nMatrice de confusion :\n{cm}")
# [[4 1]
#  [1 4]]

# Rapport de classification (toutes les métriques)
print("\nRapport de classification :")
print(classification_report(y_true, y_pred))

# Exemple de compromis
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt

# Obtenir les prédictions de probabilité
y_proba = model.predict_proba(X_test)[:, 1]

# Calculer la précision-rappel à différents seuils
precisions, recalls, thresholds = precision_recall_curve(y_test, y_proba)

# Trouver le seuil optimal (maximiser F1)
f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-10)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]
print(f"Seuil optimal : {optimal_threshold:.3f}")

Rareté : Très courant Difficulté : Facile


10. Qu'est-ce que la régularisation et quand l'utiliseriez-vous ?

Réponse : La régularisation empêche le surapprentissage en pénalisant la complexité du modèle.

  • Types :
    • L1 (Lasso) : Ajoute la valeur absolue des coefficients
    • L2 (Ridge) : Ajoute les coefficients au carré
    • Elastic Net : Combine L1 et L2
  • Quand utiliser :
    • Variance élevée (surapprentissage)
    • Nombreuses caractéristiques
    • Multicolinéarité
from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# Générer des données avec de nombreuses caractéristiques
X, y = make_regression(
    n_samples=100, n_features=50,
    n_informative=10, noise=10, random_state=42
)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# Pas de régularisation
lr = LinearRegression()
lr.fit(X_train, y_train)
lr_score = r2_score(y_test, lr.predict(X_test))
print(f"R² de la régression linéaire : {lr_score:.3f}")

# Régularisation L2 (Ridge)
ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
ridge_score = r2_score(y_test, ridge.predict(X_test))
print(f"R² de Ridge : {ridge_score:.3f}")

# Régularisation L1 (Lasso)
lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)
lasso_score = r2_score(y_test, lasso.predict(X_test))
print(f"R² de Lasso : {lasso_score:.3f}")
print(f"Coefficients non nuls de Lasso : {np.sum(lasso.coef_ != 0)}")

# Elastic Net (L1 + L2)
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
elastic.fit(X_train, y_train)
elastic_score = r2_score(y_test, elastic.predict(X_test))
print(f"R² d'Elastic Net : {elastic_score:.3f}")

# Réglage des hyperparamètres pour alpha
from sklearn.model_selection import GridSearchCV

param_grid = {'alpha': [0.001, 0.01, 0.1, 1.0, 10.0]}
grid = GridSearchCV(Ridge(), param_grid, cv=5)
grid.fit(X_train, y_train)
print(f"\nMeilleur alpha : {grid.best_params_['alpha']}")
print(f"Meilleur score de validation croisée : {grid.best_score_:.3f}")

Rareté : Très courant Difficulté : Moyenne


Formation et déploiement du modèle (5 questions)

11. Comment enregistrez-vous et chargez-vous des modèles en production ?

Réponse : La sérialisation du modèle permet le déploiement et la réutilisation.

import joblib
import pickle
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# Entraîner le modèle
X, y = load_iris(return_X_y=True)
model = RandomForestClassifier()
model.fit(X, y)

# Méthode 1 : Joblib (recommandée pour scikit-learn)
joblib.dump(model, 'model.joblib')
loaded_model = joblib.load('model.joblib')

# Méthode 2 : Pickle
with open('model.pkl', 'wb') as f:
    pickle.dump(model, f)

with open('model.pkl', 'rb') as f:
    loaded_model = pickle.load(f)

# Pour TensorFlow/Keras
import tensorflow as tf

# Sauvegarder le modèle entier
# keras_model.save('model.h5')
# loaded_model = tf.keras.models.load_model('model.h5')

# Sauvegarder uniquement les poids
# keras_model.save_weights('model_weights.h5')
# new_model.load_weights('model_weights.h5')

# Pour PyTorch
import torch

# Sauvegarder le dictionnaire d'état du modèle
# torch.save(model.state_dict(), 'model.pth')
# model.load_state_dict(torch.load('model.pth'))

# Sauvegarder le modèle entier
# torch.save(model, 'model_complete.pth')
# model = torch.load('model_complete.pth')

# Contrôle de version pour les modèles
import datetime

model_version = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
model_path = f'models/model_{model_version}.joblib'
joblib.dump(model, model_path)
print(f"Modèle sauvegardé dans {model_path}")

Rareté : Très courant Difficulté : Facile


12. Comment créez-vous une API REST pour le service de modèles ?

Réponse : Les API REST rendent les modèles accessibles aux applications.

from flask import Flask, request, jsonify
import joblib
import numpy as np

app = Flask(__name__)

# Charger le modèle au démarrage
model = joblib.load('model.joblib')

@app.route('/predict', methods=['POST'])
def predict():
    try:
        # Obtenir les données de la requête
        data = request.get_json()
        features = np.array(data['features']).reshape(1, -1)
        
        # Faire la prédiction
        prediction = model.predict(features)
        probability = model.predict_proba(features)
        
        # Renvoyer la réponse
        return jsonify({
            'prediction': int(prediction[0]),
            'probability': probability[0].tolist(),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': str(e),
            'status': 'error'
        }), 400

@app.route('/health', methods=['GET'])
def health():
    return jsonify({'status': 'healthy'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

# Alternative FastAPI (moderne, plus rapide)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class PredictionRequest(BaseModel):
    features: list

class PredictionResponse(BaseModel):
    prediction: int
    probability: list
    status: str

@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
    try:
        features = np.array(request.features).reshape(1, -1)
        prediction = model.predict(features)
        probability = model.predict_proba(features)
        
        return PredictionResponse(
            prediction=int(prediction[0]),
            probability=probability[0].tolist(),
            status="success"
        )
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

# Utilisation :
# curl -X POST "http://localhost:5000/predict" \
#      -H "Content-Type: application/json" \
#      -d '{"features": [5.1, 3.5, 1.4, 0.2]}'

Rareté : Très courant Difficulté : Moyenne


13. Qu'est-ce que Docker et pourquoi est-il utile pour le déploiement de l'apprentissage automatique ?

Réponse : Les conteneurs Docker empaquettent les applications avec toutes les dépendances.

  • Avantages :
    • Reproductibilité
    • Cohérence entre les environnements
    • Déploiement facile
    • Isolation
# Dockerfile pour le modèle d'apprentissage automatique
FROM python:3.9-slim

WORKDIR /app

# Copier les exigences
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copier le modèle et le code
COPY model.joblib .
COPY app.py .

# Exposer le port
EXPOSE 5000

# Exécuter l'application
CMD ["python", "app.py"]
# Construire l'image Docker
docker build -t ml-model:v1 .

# Exécuter le conteneur
docker run -p 5000:5000 ml-model:v1

# Docker Compose pour une configuration multi-conteneurs
# docker-compose.yml
version: '3.8'
services:
  model:
    build: .
    ports:
      - "5000:5000"
    environment:
      - MODEL_PATH=/app/model.joblib
    volumes:
      - ./models:/app/models

Rareté : Courant Difficulté : Moyenne


14. Comment surveillez-vous les performances du modèle en production ?

Réponse : La surveillance détecte la dégradation du modèle et assure la fiabilité.

  • Ce qu'il faut surveiller :
    • Métriques de prédiction : Exactitude, latence
    • Dérive des données : Changements de distribution des entrées
    • Dérive du modèle : Dégradation des performances
    • Métriques système : CPU, mémoire, erreurs
import logging
from datetime import datetime
import numpy as np

class ModelMonitor:
    def __init__(self, model):
        self.model = model
        self.predictions = []
        self.actuals = []
        self.latencies = []
        self.input_stats = []
        
        # Configuration du logging
        logging.basicConfig(
            filename='model_monitor.log',
            level=logging.INFO,
            format='%(asctime)s - %(message)s'
        )
    
    def predict(self, X):
        import time
        
        # Suivre les statistiques d'entrée
        self.input_stats.append({
            'mean': X.mean(),
            'std': X.std(),
            'min': X.min(),
            'max': X.max()
        })
        
        # Mesurer la latence
        start = time.time()
        prediction = self.model.predict(X)
        latency = time.time() - start
        
        self.predictions.append(prediction)
        self.latencies.append(latency)
        
        # Enregistrer la prédiction
        logging.info(f"Prédiction : {prediction}, Latence : {latency:.3f}s")
        
        # Alerter si la latence est trop élevée
        if latency > 1.0:
            logging.warning(f"Latence élevée détectée : {latency:.3f}s")
        
        return prediction
    
    def log_actual(self, y_true):
        self.actuals.append(y_true)
        
        # Calculer l'exactitude si nous avons suffisamment de données
        if len(self.actuals) >= 100:
            accuracy = np.mean(
                np.array(self.predictions[-100:]) == np.array(self.actuals[-100:])
            )
            logging.info(f"Exactitude glissante (100 derniers) : {accuracy:.3f}")
            
            if accuracy < 0.8:
                logging.error(f"Performances du modèle dégradées : {accuracy:.3f}")
    
    def check_data_drift(self, reference_stats):
        if not self.input_stats:
            return
        
        current_stats = self.input_stats[-1]
        
        # Détection simple de la dérive (comparer les moyennes)
        mean_diff = abs(current_stats['mean'] - reference_stats['mean'])
        if mean_
Newsletter subscription

Conseils de carrière hebdomadaires qui fonctionnent vraiment

Recevez les dernières idées directement dans votre boîte de réception

Votre Prochain Entretien n'est qu'à un CV

Créez un CV professionnel et optimisé en quelques minutes. Aucune compétence en design nécessaire—juste des résultats prouvés.

Créer mon CV

Partager cet article

Faites Compter Vos 6 Secondes

Les recruteurs scannent les CV pendant seulement 6 à 7 secondes en moyenne. Nos modèles éprouvés sont conçus pour capter l'attention instantanément et les inciter à continuer la lecture.