Questions d'entretien pour Ingénieur Plateforme ML et MLOps
15 questions d'entretien sélectionnées sur la plateforme ML et MLOps, regroupées par niveau de séniorité. Utilisez-les pour réviser les fondamentaux, les compromis pratiques et le raisonnement de production de niveau senior.
1Qu’est-ce qu’un contrat de données dans une plateforme d’apprentissage automatique (ML) en production, et pourquoi compte-t-il pour la fiabilité des modèles ?
Un contrat de données est un accord versionné entre producteurs et consommateurs. Il précise le schéma, le sens et les unités, les valeurs et catégories autorisées, les champs manquants, la fraîcheur, les volumes attendus et les responsabilités. Des données conformes aux types peuvent tout de même changer de sens et dégrader les prédictions sans provoquer d’exception. Vérifiez le contrat à l’ingestion et lors des changements, et définissez le signalement, le rejet ou le repli en cas d’écart. Cela réduit les ruptures et aide à repérer les différences entre entraînement et service.
2Expliquez les vérifications de qualité des données qui vont au-delà de la validation de schéma et comment vous décideriez quelles vérifications devraient bloquer un pipeline d'entraînement ou de service.
Vérifiez les valeurs manquantes, les plages et catégories permises, l’unicité, les jointures, le volume, la fraîcheur et la complétude des partitions. Comparez aussi les distributions et les segments à une référence saisonnière. Bloquez ou mettez en quarantaine les données lorsqu’une règle indispensable est violée, par exemple des cibles corrompues ou l’absence d’une clé requise. Un écart statistique faible ou attendu peut déclencher une alerte. Fixez les seuils à partir du risque et des données historiques. En service, prévoyez si possible des valeurs fiables de repli ou un refus maîtrisé.
3À quoi sert un magasin de caractéristiques et comment distinguer génération hors ligne et service en ligne ?
Cette plateforme centralise gestion, découverte, stockage et service des caractéristiques entre entraînement et inférence pour réutiliser, éviter les doublons et réduire les écarts. Le stockage hors ligne analytique, Snowflake, BigQuery, S3/Parquet ou Delta Lake, optimise historique, volumes et jointures temporelles. Pour éviter les fuites, il faut aussi clés correctes, historique d’événement et disponibilité, versions des transformations et dates limites des étiquettes cibles. Il ne calcule pas automatiquement les caractéristiques : des chaînes de traitement externes peuvent l’alimenter. Le magasin en ligne, Redis, DynamoDB ou Cassandra, sert des valeurs récentes par identifiant d’entité pour enrichir la prédiction, avec latence réelle dépendant du déploiement et de la charge. Registre et définitions compatibles relient les deux, tandis qu’ingestion et synchronisation peuvent appartenir à la plateforme ou à l’extérieur. Les fonctions varient selon le produit.
Feature Definition: `user_30d_transaction_count`
|
+---------------+---------------+
| |
v v
[Offline Store] [Online Store]
- Tech: BigQuery, Iceberg, S3 - Tech: Redis, DynamoDB
- Workload: High-throughput batch - Workload: Low-latency point lookups
- Retention: Multi-year history - Retention: Latest entity state
- Usage: Point-in-time training - Usage: Real-time inference scoring
4Quelle différence y a-t-il entre définition, valeur et vue de variables de modèle, et clé d’entité dans une plateforme de variables d’apprentissage automatique ?
Une clé d’entité identifie l’objet à joindre ou à retrouver, par exemple `user_id`. Une définition de variable décrit son nom, son type et son calcul, par exemple la somme des transactions sur trente jours. Une vue regroupe des variables liées à des entités et à une source, avec ses règles temporelles et de matérialisation. La valeur est le résultat concret pour une entité et un instant, par exemple `452.10` pour l’utilisateur `1042` à une date donnée.
5Expliquez le lignage des données dans une plateforme d'apprentissage automatique (ML) et pourquoi le lignage est important pour le débogage des régressions de qualité de modèle.
Le lignage décrit les sources et versions de données, les transformations, les variables calculées et les entraînements ou modèles qui les consomment. Si une métrique baisse, remontez du modèle vers le jeu d’entraînement, la transformation et le lot source pour chercher un changement. Si une partition est corrompue, suivez au contraire ses descendants pour déterminer quels jeux, variables ou modèles sont touchés. Ces liens aident à choisir les corrections, reprises et réentraînements nécessaires.
[Degraded Model v3.1]
└── Trained on: [Dataset: training_set_2025_04_01]
└── Built from: [Feature View: user_features_v2 @ git_sha: abc1234]
└── Source Table: [raw_user_events @ batch_2025_03_31]
└── Issue Found: Logging bug produced 40% zero-filled values
6Expliquez ce qu'un registre de modèles (model registry) offre au-delà du simple stockage d'artefacts de modèle sérialisés.
Un registre associe un nom de modèle à des versions immuables et à leurs artefacts. Il peut lier une version à l’entraînement, au code, aux données, aux paramètres et à l’environnement. Il conserve les signatures, évaluations, fiches et approbations utiles, et permet de gérer les droits et promotions. Intégré au déploiement, il indique quelles versions sont admissibles et fournit les références nécessaires au suivi et au retour arrière. Le service de déploiement doit encore enregistrer ce qui reçoit effectivement du trafic.
7Expliquez l'objectif d'un plan de retour arrière (rollback plan) pour les déploiements de modèles et l'état nécessaire pour effectuer un retour arrière en toute sécurité.
Le plan de retour arrière rétablit une version validée si la qualité, la latence ou la disponibilité se dégrade. Conservez ensemble les poids, le code et l’image d’inférence, les dépendances, les transformations et versions de variables compatibles et la configuration de routage. Testez que la version précédente peut démarrer et lire les données actuelles, puis vérifiez ses résultats et sa latence après bascule. Définissez un repli compatible si les deux versions échouent et vérifiez séparément le traitement des décisions déjà prises.
8Comparez la validation de schéma au moment de l'écriture et au moment de la lecture pour les pipelines de caractéristiques de ML (Machine Learning), et expliquez quand chaque approche est préférable.
À l’écriture, contrôlez le contrat commun et refusez ou mettez en quarantaine les enregistrements invalides avant le stockage partagé. Le producteur reçoit un signal tôt, et chaque consommateur bénéficie du même contrôle. À la lecture, vérifiez les contraintes propres à l’entraînement ou au modèle, par exemple une colonne obligatoire pour un usage précis. Cette approche convient aussi aux sources tierces ou anciennes, mais détecte l’erreur plus tard. Combinez les deux quand la plateforme partage des données entre plusieurs usages.
# 1. Write-Time Validation: Reject or quarantine bad records before landing in Feature Store
def write_to_feature_store(raw_records, schema_validator, feature_table, dlq_publisher):
valid_records = []
for record in raw_records:
if schema_validator.is_valid(record):
valid_records.append(record)
else:
dlq_publisher.publish(record, reason="write_time_validation_failed")
feature_table.append_batch(valid_records)
# 2. Read-Time Validation: Consumer pipeline applies defensive model-specific checks
def load_training_features(feature_table, model_schema):
df = feature_table.read_partition("2023-10-01")
# Model-specific consumer gate: drops non-conforming rows without halting upstream ingestion
clean_df = df[model_schema.validate_row_mask(df)]
return clean_df
9Comment diagnostiquer un pipeline qui se termine normalement tout en perdant des enregistrements ou en remplaçant des valeurs manquantes par des valeurs qui faussent les prédictions ?
Mesurez les volumes et la couverture par entité à l’entrée, après les filtres, jointures et agrégations. Identifiez les clés perdues avec une comparaison des ensembles. Vérifiez les jointures internes, les conditions et les rejets. Comptez les valeurs absentes à la source, celles devenues nulles à la conversion et celles imputées par une règle comme `COALESCE`. Un zéro réel et un zéro imputé peuvent avoir des sens différents. Comparez les variables et scores à la référence par segment, corrigez la transformation, ajoutez des contrôles et rejouez les partitions touchées de manière idempotente.
# Anti-Pattern: Succeeds green but corrupts feature data
# 1. Inner join silently drops users with missing profiles
# 2. errors='coerce' turns string typos into NaNs
# 3. fillna(0) injects artificial 0-values into feature distribution
df_corrupt = df_events.merge(df_profiles, on="user_id", how="inner")
df_corrupt["credit_score"] = pd.to_numeric(df_corrupt["raw_score"], errors="coerce").fillna(0)
# Robust Implementation: Explicit checks and observable failure
df_clean = df_events.merge(df_profiles, on="user_id", how="left")
join_match_rate = df_clean["user_id"].notna().mean()
if join_match_rate < 0.98:
raise RuntimeError(f"Severe join drop detected! Match rate: {join_match_rate:.2%}")
raw_nulls = df_clean["raw_score"].isna().mean()
if raw_nulls > 0.05:
raise ValueError(f"Anomalous raw_score missingness: {raw_nulls:.2%}")
10Comparez chaînes de traitement des caractéristiques par lots et en flux selon fraîcheur, coût et fiabilité de l’apprentissage automatique (ML) en production.
Les lots traitent un jeu borné sur calendrier ou déclenchement, le traitement en flux les événements arrivants de façon incrémentale. Choisissez selon fraîcheur totale, volume, état et reprise. Un lot peut tourner toutes les quelques minutes ou à la demande sans retard inhérent de plusieurs heures. Le flux peut être rapide mais dépend encore d’ingestion, traitement et destination. Le lot simplifie souvent les recalculs historiques, sans être toujours moins cher : un flux incrémental évite des parcours complets, et les services gérés ou sans serveur modifient les coûts. Mesurez la charge réelle. Les deux exigent schémas, déduplication et publication fiable ; le flux met aussi au centre temps d’événement, retards, reprise d’état et contre-pression. Un hybride combine activité récente en flux et historique par lots, avec définitions et disponibilité temporelle cohérentes entre entraînement et service.
11Analysez les événements tardifs ou désordonnés et leurs effets sur données d’entraînement, labels et caractéristiques en ligne.
Le temps d’événement est celui de l’origine, le temps de traitement celui du moteur. Réseau, pannes et nouvelles tentatives désordonnent les arrivées. Les repères de progression temporelle (watermarks) estiment la progression temporelle ; fenêtres et politique de retard déterminent mise à jour, redirection ou abandon sans garantir qu’aucun événement plus ancien n’arrivera. L’historique d’entraînement doit respecter date des événements et disponibilité réelle au moment de décision. Un événement ancien reçu après prédiction crée une fuite si la jointure as-of utilise seulement son horodatage. Gardez historique de disponibilité et versions des transformations ; traitement et temps d’événement ne sont pas interchangeables. Les labels de conversion ou fraude arrivent aussi tard : une fenêtre d’observation inadéquate crée des faux négatifs. En ligne, des écritures anciennes peuvent écraser l’état récent ; utilisez upserts sensibles au temps, contrôles de version ou agrégations commutatives.
import pandas as pd
# Prediction events (e.g., ad impressions at inference time)
observations = pd.DataFrame({
'user_id': [101, 102],
'pred_time': pd.to_datetime(['2023-10-01 10:00:00', '2023-10-01 10:30:00'])
})
# Feature updates with event-time timestamps
user_features = pd.DataFrame({
'user_id': [101, 101, 102],
'feature_time': pd.to_datetime([
'2023-10-01 09:30:00',
'2023-10-01 10:15:00', # Occurs after observation 1 pred_time
'2023-10-01 10:00:00'
]),
'click_count_1h': [3, 5, 1]
})
# Backward as-of join guarantees only feature state known at pred_time is joined
training_set = pd.merge_asof(
observations.sort_values('pred_time'),
user_features.sort_values('feature_time'),
left_on='pred_time',
right_on='feature_time',
by='user_id',
direction='backward'
)
print(training_set[['user_id', 'pred_time', 'feature_time', 'click_count_1h']])
12Expliquez le rôle des files de messages en échec, de l’idempotence et des points de reprise dans les chaînes de traitement de caractéristiques en temps réel.
Les points de reprise sauvegardent une position de lecture et un état cohérents des opérateurs pour reprendre le traitement et rejouer les entrées après une panne. Les garanties dépendent de sources rejouables, de la configuration des points de reprise et de la coordination avec la destination : la cohérence de l’état interne ne garantit pas à elle seule que les effets externes se produisent exactement une fois. Une destination idempotente préserve le résultat attendu lorsque le même événement logique est appliqué plusieurs fois. Un remplacement déterministe associé à une clé stable peut être idempotent, mais une insertion ou mise à jour atomique qui incrémente un compteur compte à nouveau chaque tentative. Utilisez des identifiants de déduplication et des modifications d’état atomiques, ou une destination transactionnelle prise en charge. La comparaison d’horodatages exige une règle déterministe pour départager les égalités et ne résout pas tous les cas d’agrégation d’événements arrivant dans le désordre. Les files de messages en échec isolent les enregistrements invalides avec leurs identifiants et les causes d’échec pour permettre une investigation et un retraitement contrôlé. Surveillez leur volume et les entités concernées, coordonnez leur alimentation avec la progression de la source et bloquez ou dégradez le service si les entrées omises rendent les caractéristiques trop incomplètes. Le fonctionnement de la chaîne principale ne suffit pas à prouver la validité des données.
def update_user_feature(redis_client, user_id: str, new_feature_val: float, event_timestamp: int):
lua_script = """
local current_ts = redis.call('HGET', KEYS[1], 'last_updated')
if not current_ts or tonumber(ARGV[1]) > tonumber(current_ts) then
redis.call('HSET', KEYS[1], 'feature_val', ARGV[2], 'last_updated', ARGV[1])
return 1
end
return 0
"""
# Atomic check-and-set: older replayed events are ignored
return bool(redis_client.eval(lua_script, 1, f"user:{user_id}", event_timestamp, new_feature_val))
13Comment organiser le recalcul historique lorsque des corrections en amont invalident des variables dérivées utilisées par des modèles en production ?
Remontez le lignage pour identifier les fenêtres de calcul, variables, jeux d’entraînement et modèles touchés. Recalculez les partitions dans une version isolée et vérifiez les clés, la fraîcheur, les valeurs manquantes et les résultats attendus. Comparez à la référence, en distinguant les écarts voulus des défauts. Synchronisez le stockage en ligne avec limitation de débit et contrôle de version pour éviter d’écraser une valeur plus récente. Déterminez si le modèle a appris sur les données erronées ou les consomme seulement en service. Réévaluez-le et réentraînez-le si nécessaire sur les données corrigées. Conservez les versions et une procédure de retour arrière.
[Upstream Data Correction Event]
|
v
[1. Lineage Traversal] --------> Identifies: FeatureView_A, TrainingDataset_B, Model_C
|
v
[2. Isolated Reprocessing] ----> Writes to isolated staging: `features_v2_backfill`
|
v
[3. Validation Gate] ----------> Validates: Schema match, Null checks, PSI < 0.05
|
v
[4. Cutover & Retrain Trigger]-> Online: Atomic alias swap (`feature_v1` -> `feature_v2`)
Offline: Retrain Model_C on corrected historical split
14Concevez une récupération de caractéristiques en ligne rapide et analysez stockage, cache, partitionnement et clés chaudes.
Pour des objectifs de latence au 99e percentile (p99) souvent inférieurs à un seuil de 5 à 20 ms, utilisez un stockage clé-valeur tel Redis, DynamoDB, Cassandra ou Aerospike et mesurez réseau, taille, cohérence, charge et pannes réels : aucun ne garantit cette latence universellement. Regroupez les caractéristiques par clé entity_id:feature_view_name en Protobuf, FlatBuffers ou MessagePack pour réduire allers-retours. Un cache local borné sert les plus demandées. Pour de nombreuses entités, comme cinq cents candidats, utilisez multi-get ou appels asynchrones par partition. Le hachage cohérent répartit les clés, sans supprimer les points chauds de célébrités, produits viraux ou entités globales utilisées par défaut. Utilisez réplicas et cache pour les lectures chaudes. Des copies versionnées sous plusieurs clés peuvent répartir les lectures, avec coût de mise à jour et obsolescence. Si les valeurs sont des agrégats partiels, lisez et combinez correctement ; un suffixe aléatoire seul ne préserve pas la valeur et ne résout pas sûrement le problème. Limitez les appels et fournissez des valeurs statiques par défaut ou des représentations vectorielles en cache lors de dégradation.
# Conceptual Online Feature Client with Local LRU + Batched KV Fetch
import functools
from typing import List, Dict, Any
class OnlineFeatureClient:
def __init__(self, remote_kv_store, local_cache):
self.remote_kv = remote_kv_store
self.local_cache = local_cache
def get_online_features(self, entity_keys: List[str], feature_names: List[str]) -> Dict[str, Dict[str, Any]]:
results = {}
missing_keys = []
# 1. Check local in-memory L1 cache (mitigates hot-keys)
for key in entity_keys:
cached = self.local_cache.get(key)
if cached:
results[key] = cached
else:
missing_keys.append(key)
# 2. Batched async retrieval for missing keys from distributed store (e.g., Redis/DynamoDB)
if missing_keys:
fetched_records = self.remote_kv.mget(missing_keys)
for key, serialized_val in zip(missing_keys, fetched_records):
parsed_val = self._deserialize(serialized_val) # Proto/Msgpack
results[key] = parsed_val
self.local_cache.set(key, parsed_val, ttl=30) # Short TTL L1 cache
return results
def _deserialize(self, data): ...
15Comparez une gestion centralisée des variables de modèle à une responsabilité par équipe métier dans une plateforme d’apprentissage automatique partagée.
Une équipe centrale qui définit et exploite les variables favorise les conventions communes, la réutilisation et des objectifs homogènes. Elle peut devenir un point d’attente et manquer de contexte métier. Avec une responsabilité par domaine, les équipes proches des données adaptent et maintiennent les calculs plus vite, mais les doublons et règles divergentes sont plus probables. Une approche fédérée confie le sens, la fraîcheur et la maintenance aux domaines, avec un catalogue, des contrats, des accès et des contrôles communs fournis par la plateforme. Attribuez une personne responsable et un engagement de service à chaque variable partagée.