Machine Learning Academy · Leçon

Dérive conceptuelle : quand la relation entre X et Y change

Vous distinguerez la dérive des données de la dérive conceptuelle à l’aide d’un exemple de série temporelle et comprendrez pourquoi la dérive des données n’implique pas toujours une dégradation des performances du modèle.

Leçon 2 sur 413 étapes

Dérive conceptuelle : quand la relation entre X et Y change est une leçon Machine Learning Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Machine Learning Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Machine Learning Academy comprend 4 leçons au total.

Qu'est-ce que la dérive de concept ?

Le changement de concept survient lorsque la relation entre les caractéristiques d’entrée X et l’étiquette cible Y évolue au fil du temps, même si les distributions des caractéristiques restent elles-mêmes stables. En détection de fraude, les fraudeurs apprennent quelles transactions sont signalées et modifient leurs habitudes ; ainsi, des transactions qui semblaient frauduleuses en 2022 paraissent légitimes en 2024. Le « concept » appris (ce à quoi ressemble une fraude) a changé, mais les distributions des caractéristiques peuvent sembler inchangées, ce qui rend le changement de concept plus difficile à détecter que la dérive des données.

Dérive des données ou changement de concept : différence essentielle

La distinction essentielle est la suivante : la dérive des données correspond à une modification de P(X), c’est-à-dire de la distribution des caractéristiques d’entrée. Le changement de concept correspond à une modification de P(Y|X), c’est-à-dire de la relation conditionnelle entre les caractéristiques et les étiquettes. La dérive des données ne dégrade pas toujours les performances du modèle (si la nouvelle distribution reste à l’intérieur de la frontière de décision apprise). Le changement de concept dégrade toujours les performances, car la correspondance apprise par le modèle entre X et Y est désormais incorrecte, que X semble ou non identique.

import numpy as np
import pandas as pd

# Scenario: credit scoring model
# Feature: income. Label: 1 = defaults, 0 = repays

# Training period: incomes 30-50k correlate with defaults
np.random.seed(42)
train_income = np.random.normal(40000, 8000, 1000)
train_default = (train_income < 35000).astype(int)  # low income -> default

# Concept drift: inflation shifts threshold; now defaults start at 50k
prod_income = np.random.normal(40000, 8000, 500)  # SAME distribution (no data drift)
prod_default = (prod_income < 50000).astype(int)  # new relationship

print('Train default rate:', train_default.mean().round(3))
print('Prod default rate:', prod_default.mean().round(3))
print('Feature distribution same (no data drift).',
      'But Y|X relationship has changed (concept drift).')

Types de changements de concept

Le changement de concept se présente sous quatre formes. Changement soudain : la relation change brutalement (par exemple, le COVID modifie du jour au lendemain les habitudes de consommation). Changement progressif : l’ancien concept disparaît lentement tandis qu’un nouveau concept émerge (par exemple, la définition du courrier indésirable évolue sur plusieurs mois). Changement incrémental : une évolution continue et lente, sans point de rupture clairement identifiable. Changement récurrent : des régularités saisonnières ; la relation à Noël diffère de celle de juillet, mais la situation de juillet finit par revenir.

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(0, 100, 1000)

# Sudden drift: step function at t=50
sudden = np.where(t < 50, 0.8, 0.3)  # accuracy drops at t=50

# Gradual drift: linear transition
gradual = np.where(t < 40, 0.8,
          np.where(t > 60, 0.4,
          0.8 - (t - 40) * 0.02))

# Recurring drift: seasonal pattern
recurring = 0.6 + 0.2 * np.sin(2 * np.pi * t / 20)

for name, series in [('Sudden', sudden), ('Gradual', gradual), ('Recurring', recurring)]:
    print(f'{name} drift -- min acc: {series.min():.2f}, max: {series.max():.2f}')

Détecter les changements de concept grâce au suivi des performances

Le signal le plus fiable d’un changement de concept est la baisse des performances du modèle sur des données de production étiquetées. Suivez la métrique choisie (exactitude, F1, AUC) sur une fenêtre glissante de prédictions récentes pour lesquelles les étiquettes de référence sont devenues disponibles. Une tendance à la baisse des performances qui ne s’explique pas par une dérive des données (les caractéristiques semblent identiques) constitue un indice fort de changement de concept. Cela nécessite une boucle de rétroaction : collecter les véritables étiquettes des prédictions de production, ce qui peut prendre de quelques jours à plusieurs mois selon la tâche.

import numpy as np
import pandas as pd

np.random.seed(42)

# Simulate weekly model accuracy tracking
weekly_accuracy = [
    0.92, 0.91, 0.90, 0.89, 0.88,  # slight decline
    0.86, 0.83, 0.79, 0.74, 0.68,  # accelerating decline = concept drift
    0.65, 0.61
]

df = pd.DataFrame({'week': range(1, 13), 'accuracy': weekly_accuracy})
df['rolling_mean'] = df['accuracy'].rolling(3).mean()
df['alert'] = df['accuracy'] < 0.75  # threshold

print(df.to_string(index=False))
print('Weeks with alerts:', df[df['alert']]['week'].tolist())

Détecter les changements de concept sans étiquettes

Attendre les étiquettes de référence est lent. Lorsque les étiquettes arrivent avec un délai important, utilisez le suivi de la distribution des prédictions comme signal d’alerte précoce. Si P(X) reste stable (aucune dérive des données), mais que la distribution des étiquettes prédites par le modèle change fortement, cela suggère que le concept sous-jacent a évolué. Par exemple, si le taux de prédictions positives d’un classifieur binaire passe de 30 % à 5 % sans modification des distributions des caractéristiques, le modèle est probablement confronté à une relation différente entre les caractéristiques et la classe positive.

import numpy as np

# Monitoring prediction distributions as a proxy for concept drift
# Period 1 (training distribution): ~30% positive predictions
period1_probs = np.random.beta(2, 5, 1000)  # right-skewed, ~30% above 0.5
period1_pos_rate = (period1_probs > 0.5).mean()

# Period 2 (concept drifted): only ~5% positive predictions (model confused)
period2_probs = np.random.beta(1, 15, 500)  # very right-skewed
period2_pos_rate = (period2_probs > 0.5).mean()

print(f'Period 1 positive rate: {period1_pos_rate:.2%}')
print(f'Period 2 positive rate: {period2_pos_rate:.2%}')
print(f'Rate drop: {(period1_pos_rate - period2_pos_rate):.2%}')
print('Possible concept drift detected!')

Réentraînement par fenêtre : s’adapter aux changements de concept

La stratégie la plus courante pour gérer les changements de concept est le réentraînement par fenêtre : réentraîner périodiquement le modèle en utilisant uniquement les N exemples les plus récents et en supprimant les anciennes données qui reflètent des tendances dépassées. Une fenêtre glissante conserve les N exemples les plus récents ; une fenêtre extensible utilise toutes les données en pondérant leur récence. La taille optimale de la fenêtre dépend de la vitesse d’évolution du concept : les domaines qui changent rapidement (les marchés financiers) nécessitent des fenêtres plus petites que ceux qui évoluent lentement (les prévisions météorologiques).

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

np.random.seed(42)

def simulate_concept_drift(n, t):
    X = np.random.normal(0, 1, (n, 2))
    # Decision boundary rotates over time
    y = ((X[:, 0] * np.cos(t) - X[:, 1] * np.sin(t)) > 0).astype(int)
    return X, y

# Full retrain vs sliding window retrain
for window_size in [100, 500, None]:  # None = all data
    all_X, all_y = [], []
    accs = []
    for t in np.linspace(0, np.pi, 10):
        X, y = simulate_concept_drift(200, t)
        all_X.append(X)
        all_y.append(y)
        if window_size:
            train_X = np.vstack(all_X[-window_size//200:])
            train_y = np.hstack(all_y[-window_size//200:])
        else:
            train_X = np.vstack(all_X)
            train_y = np.hstack(all_y)
        clf = LogisticRegression().fit(train_X, train_y)
        accs.append(clf.score(X, y))
    print(f'Window={window_size}: avg accuracy={np.mean(accs):.3f}')

Pondération des échantillons : réduire le poids des anciennes données

Au lieu d’appliquer une coupure stricte à la fenêtre, attribuez des poids décroissant de manière exponentielle aux exemples d’entraînement afin que les exemples récents contribuent davantage au modèle. Les exemples de la semaine dernière reçoivent un poids de 1,0 ; ceux du mois dernier, un poids de 0,5 ; ceux d’il y a six mois, un poids de 0,1. De nombreux estimateurs de scikit-learn acceptent un paramètre sample_weight dans leur méthode fit, ce qui facilite la mise en œuvre sans supprimer de données.

import numpy as np
from sklearn.ensemble import GradientBoostingClassifier

np.random.seed(42)
# Simulate 1000 training examples collected over 10 weeks
X = np.random.normal(0, 1, (1000, 5))
y = (X[:, 0] + np.random.normal(0, 0.5, 1000) > 0).astype(int)
weeks_ago = np.linspace(10, 0, 1000)  # example 0 is oldest, 999 is newest

# Exponential decay: half-life of 3 weeks
half_life = 3.0
sample_weights = np.exp(-weeks_ago * np.log(2) / half_life)
sample_weights /= sample_weights.sum()

clf = GradientBoostingClassifier(n_estimators=100, random_state=42)
clf.fit(X, y, sample_weight=sample_weights * len(y))  # scale up
print('Model trained with recency-weighted samples.')
print('Weight ratio new/old:', round(sample_weights[-1] / sample_weights[0], 2))

Détecteurs de dérive CUSUM et ADWIN

Les algorithmes dédiés à la détection des changements de concept fournissent des alertes plus rigoureuses. CUSUM (somme cumulative) détecte les changements de moyenne d’une séquence en cumulant les écarts par rapport à une valeur de référence. ADWIN (fenêtrage adaptatif) gère une fenêtre de longueur variable et la réduit lorsqu’un test statistique détecte une modification de la distribution au sein de la fenêtre. La bibliothèque river (anciennement scikit-multiflow) implémente ces deux méthodes pour les données diffusées en continu.

# Using the river library for streaming drift detection
# pip install river

# from river.drift import ADWIN

# adwin = ADWIN(delta=0.002)  # delta controls sensitivity
# error_sequence = [0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1]  # errors over time
#
# for i, error in enumerate(error_sequence):
#     adwin.update(error)
#     if adwin.drift_detected:
#         print(f'ADWIN detected drift at step {i}')
#         adwin = ADWIN(delta=0.002)  # reset detector

# Manual CUSUM example:
def cusum(errors, threshold=5, drift=0.01):
    cum_sum, alerts = 0, []
    for i, e in enumerate(errors):
        cum_sum = max(0, cum_sum + e - drift)
        if cum_sum > threshold:
            alerts.append(i)
            cum_sum = 0
    return alerts

errors = [0]*10 + [1]*15  # errors spike after step 10
print('CUSUM drift alerts at steps:', cusum(errors))

Changement de concept ou saisonnalité

La saisonnalité est une variation périodique prévisible (pics d’achats à Noël, habitudes de voyage estivales), tandis que le changement de concept est une évolution imprévisible et unidirectionnelle. Un bon système de suivi les distingue en vérifiant les régularités saisonnières historiques. Si la distribution actuelle correspond à celle de la même période l’année dernière, il s’agit probablement de saisonnalité plutôt que de dérive. Intégrez des caractéristiques liées à la période de l’année dans votre modèle afin de gérer la saisonnalité prévisible au lieu de déclencher des réentraînements inutiles.

import numpy as np
from scipy.stats import ks_2samp

np.random.seed(42)

# Training: July 2023 data
july_2023 = np.random.normal(100, 20, 1000)  # low activity (summer)

# Monitoring: July 2024 (seasonal -- same month last year)
july_2024 = np.random.normal(102, 21, 500)   # similar to last July

# Monitoring: December 2024 (seasonal spike -- same as Dec 2023)
dec_2024 = np.random.normal(180, 30, 500)    # high activity (Christmas)

stat_july, p_july = ks_2samp(july_2023, july_2024)
stat_dec, p_dec = ks_2samp(july_2023, dec_2024)

print(f'July 2024 vs July 2023: KS={stat_july:.3f} p={p_july:.3f} -> {"drift" if p_july < 0.05 else "seasonal OK"}')
print(f'Dec 2024 vs July 2023:  KS={stat_dec:.3f} p={p_dec:.3f} -> Expected seasonal shift')

Élaborer un plan de réponse aux changements de concept

Répondez aux changements de concept au moyen d’un plan à plusieurs niveaux. Niveau 1 (alerte précoce) : déclencher une alerte en cas de modification de la distribution des prédictions sans attendre les étiquettes. Niveau 2 (confirmation) : lorsque les étiquettes arrivent, confirmer que la dégradation des performances dépasse 5 % en valeur relative. Niveau 3 (action) : déclencher automatiquement un réentraînement par fenêtre en utilisant l’horodatage de détection de la dérive comme limite de la fenêtre. Niveau 4 (escalade) : si le réentraînement ne rétablit pas les performances, transmettre le problème à un scientifique des données pour examiner l’ingénierie des caractéristiques.

def concept_drift_response(current_accuracy, baseline_accuracy,
                            data_drift_detected, prediction_rate_shift):
    relative_drop = (baseline_accuracy - current_accuracy) / baseline_accuracy

    if relative_drop > 0.10:
        return ('TIER 4 ESCALATION: >10% accuracy drop. '
                'Manual feature engineering review required.')
    elif relative_drop > 0.05:
        return ('TIER 3 ACTION: 5-10% accuracy drop. '
                'Trigger windowed retraining immediately.')
    elif data_drift_detected or prediction_rate_shift > 0.15:
        return ('TIER 2 MONITOR: Early warning signals present. '
                'Increase monitoring to daily. Queue retraining.')
    else:
        return 'TIER 1 OK: No significant concept drift detected.'

print(concept_drift_response(0.84, 0.92, False, 0.05))   # TIER 3
print(concept_drift_response(0.91, 0.92, True, 0.18))    # TIER 2
print(concept_drift_response(0.91, 0.92, False, 0.03))   # TIER 1

Modèles d’ensemble et résistance aux changements de concept

Les méthodes d’ensemble, comme les forêts aléatoires et le gradient boosting, peuvent masquer un changement de concept plus longtemps que les modèles individuels, car leur diversité les rend moins sensibles à la modification d’une seule distribution de caractéristiques. Toutefois, cette résistance est à double tranchant : le modèle se dégrade plus lentement et de manière plus silencieuse. Suivez le taux de désaccord entre les arbres individuels d’une forêt : lorsque les arbres sont de plus en plus en désaccord sur les prédictions, cela indique précocement que le concept évolue et que l’ensemble peine à parvenir à un consensus.

import numpy as np
from sklearn.ensemble import RandomForestClassifier

# Measure inter-tree disagreement on current data as drift signal
def inter_tree_disagreement(forest, X):
    predictions = np.array([tree.predict(X) for tree in forest.estimators_])
    # Fraction of pairs of trees that disagree per sample, averaged over samples
    n_trees = len(forest.estimators_)
    disagreement_per_sample = []
    for i in range(X.shape[0]):
        preds = predictions[:, i]
        disagree = (preds != preds[0]).sum() / n_trees
        disagreement_per_sample.append(disagree)
    return np.mean(disagreement_per_sample)

# Higher disagreement = more concept drift
# print(inter_tree_disagreement(model, X_current))

Vérification rapide

Vérifiez votre compréhension des concepts de l’apprentissage automatique avec Python présentés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que le changement de concept est une modification de P(Y|X), c’est-à-dire de la correspondance entre les caractéristiques et les étiquettes, qui peut survenir même lorsque les distributions des caractéristiques semblent stables, que la baisse des performances du modèle sur des données de production étiquetées est le signal définitif d’un changement de concept, et que le réentraînement par fenêtre et la pondération des échantillons sont les principales stratégies d’adaptation pour maintenir les modèles à jour lorsque les concepts évoluent. Nous allons maintenant étudier le suivi des distributions de prédictions et des scores de confiance comme système d’alerte précoce lorsque les étiquettes de référence ne sont pas encore disponibles.

Gratuit pour commencer

Apprends Python avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Dérive conceptuelle : quand la relation entre X et Y change » est-elle gratuite ?

Oui — le texte complet de « Dérive conceptuelle : quand la relation entre X et Y change » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Machine Learning Academy, passe à CoddyKit PRO. Le cours Machine Learning Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Dérive conceptuelle : quand la relation entre X et Y change » ?

Vous distinguerez la dérive des données de la dérive conceptuelle à l’aide d’un exemple de série temporelle et comprendrez pourquoi la dérive des données n’implique pas toujours une dégradation des p… Tu pratiques Machine Learning Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Machine Learning Academy ?

Aucune expérience préalable n'est requise. Machine Learning Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Dérive conceptuelle : quand la relation entre X et Y change » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Machine Learning Academy ?

Oui. Chaque leçon Machine Learning Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Dérive des données : évolution des distributions de caractéristiques au fil du temps
  2. Dérive conceptuelle : quand la relation entre X et Y change
  3. Surveiller les distributions des prédictions et les scores de confiance
  4. Construire un pipeline d’alerte de dérive avec Evidently AI
← Retour à Machine Learning Academy