Machine Learning Academy · Les

Conceptdrift: wanneer de relatie tussen X en Y verandert

Cursisten onderscheiden datadrift van conceptdrift aan de hand van een tijdreeksvoorbeeld en begrijpen waarom datadrift niet altijd leidt tot slechtere modelprestaties.

Les 2 van 413 stappen

Conceptdrift: wanneer de relatie tussen X en Y verandert is een gratis Machine Learning Academy-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Machine Learning Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Machine Learning Academy bevat in totaal 4 lessen.

Wat is conceptdrift?

Conceptdrift treedt op wanneer de relatie tussen invoerkenmerken X en het doel-label Y in de loop van de tijd verandert, zelfs als de verdelingen van de kenmerken zelf stabiel blijven. Bij fraudedetectie leren fraudeurs welke transacties worden gemarkeerd en veranderen ze hun patronen. Transacties die er in 2022 frauduleus uitzagen, lijken daardoor in 2024 onschuldig. Het concept dat wordt geleerd — hoe fraude eruitziet — is verschoven, maar de verdelingen van de kenmerken kunnen ongewijzigd lijken. Daardoor is conceptdrift moeilijker te detecteren dan datadrift.

Datadrift versus conceptdrift: het belangrijkste verschil

Het cruciale onderscheid is als volgt: datadrift is een verandering in P(X), de verdeling van de invoerkenmerken. Conceptdrift is een verandering in P(Y|X), de voorwaardelijke relatie tussen kenmerken en labels. Datadrift verslechtert de modelprestaties niet altijd, als de nieuwe verdeling bijvoorbeeld nog binnen de geleerde beslissingsgrens valt. Conceptdrift verslechtert de prestaties altijd, omdat de door het model geleerde omzetting van X naar Y niet langer klopt, ongeacht of X er hetzelfde uitziet.

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).')

Typen conceptdrift

Conceptdrift komt in vier vormen voor. Plotselinge drift: de relatie verandert abrupt, bijvoorbeeld doordat COVID-19 het consumentenaankoopgedrag van de ene op de andere dag veranderde. Geleidelijke drift: het oude concept vervaagt langzaam en een nieuw concept ontstaat, bijvoorbeeld door een veranderende definitie van spam gedurende meerdere maanden. Incrementele drift: een voortdurende, langzame drift zonder duidelijk omslagpunt. Terugkerende drift: seizoenspatronen — de relatie rond Kerstmis verschilt van die in juli, maar in juli keert de eerdere situatie uiteindelijk terug.

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}')

Conceptdrift detecteren via prestatiemonitoring

Het betrouwbaarste signaal voor conceptdrift is afnemende modelprestatie op gelabelde productiegegevens. Houd de gekozen maat (nauwkeurigheid, F1, AUC) bij over een voortschrijdend venster met recente voorspellingen waarvoor de werkelijke labels beschikbaar zijn gekomen. Een dalende prest trend die niet door datadrift wordt verklaard — de kenmerken zien er hetzelfde uit — is sterk bewijs voor conceptdrift. Hiervoor is een feedbacklus nodig: je moet de werkelijke labels van productievoorspellingen verzamelen, wat afhankelijk van de taak dagen tot maanden kan duren.

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())

Conceptdrift detecteren zonder labels

Wachten op werkelijke labels duurt lang. Wanneer labels met veel vertraging binnenkomen, kun je monitoring van de voorspellingsverdeling gebruiken als vroegtijdig waarschuwingssignaal. Als P(X) stabiel is (geen datadrift), maar de verdeling van de door het model voorspelde labels aanzienlijk verschuift, wijst dit erop dat het onderliggende concept is veranderd. Als bijvoorbeeld het percentage positieve voorspellingen van een binaire classificator daalt van 30% naar 5% zonder dat de verdelingen van de kenmerken veranderen, ziet het model waarschijnlijk een andere relatie tussen de kenmerken en de positieve klasse.

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!')

Op vensters gebaseerd opnieuw trainen: aanpassen aan conceptdrift

De meestgebruikte strategie om met conceptdrift om te gaan is op vensters gebaseerd opnieuw trainen: train het model periodiek opnieuw met alleen de meest recente N voorbeelden en verwijder oude gegevens die verouderde patronen weerspiegelen. Een schuivend venster bevat de meest recente N voorbeelden; een uitbreidend venster gebruikt alle gegevens met een weging op basis van recentheid. De optimale venstergrootte hangt af van de snelheid waarmee het concept verandert — snel veranderende domeinen, zoals financiële markten, hebben kleinere vensters nodig dan langzaam veranderende domeinen, zoals weersvoorspellingen.

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}')

Voorbeelden wegen: oude gegevens minder zwaar laten meetellen

In plaats van een harde grens voor het venster kun je exponentieel afnemende gewichten toekennen aan trainingsvoorbeelden, zodat recente voorbeelden zwaarder meetellen voor het model. Voorbeelden van vorige week krijgen gewicht 1,0; voorbeelden van vorige maand krijgen gewicht 0,5; voorbeelden van zes maanden geleden krijgen gewicht 0,1. Veel scikit-learn-schatters accepteren een parameter sample_weight in hun fit-methode, waardoor je dit eenvoudig kunt implementeren zonder gegevens weg te gooien.

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))

CUSUM- en ADWIN-driftdetectoren

Speciale algoritmen voor het detecteren van conceptdrift leveren beter onderbouwde waarschuwingen. CUSUM (Cumulative Sum) detecteert veranderingen in het gemiddelde van een reeks door afwijkingen van een referentiewaarde op te tellen. ADWIN (Adaptive Windowing) houdt een venster met variabele lengte bij en maakt dit kleiner wanneer een statistische toets een veranderde verdeling binnen het venster detecteert. De bibliotheek river (voorheen scikit-multiflow) implementeert beide voor gegevensstromen.

# 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))

Conceptdrift versus seizoensinvloeden

Seizoensinvloeden zijn voorspelbare periodieke variaties, zoals pieken in de kerstinkopen en zomerreizen, terwijl conceptdrift een onvoorspelbare verschuiving in één richting is. Een goed monitoringsysteem maakt onderscheid tussen beide door historische seizoenspatronen te controleren. Als de huidige verdeling overeenkomt met de verdeling in dezelfde periode van vorig jaar, gaat het waarschijnlijk om seizoensinvloeden en niet om drift. Voeg kenmerken voor de tijd van het jaar toe aan je model om voorspelbare seizoensinvloeden te verwerken in plaats van onnodig opnieuw trainen te starten.

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')

Een reactieplan voor conceptdrift opstellen

Reageer op conceptdrift met een plan in verschillende niveaus. Niveau 1 (vroegtijdige waarschuwing): waarschuw bij verschuivingen in de voorspellingsverdeling zonder op labels te wachten. Niveau 2 (bevestiging): bevestig zodra de labels binnenkomen dat de prestatie relatief met meer dan 5% is afgenomen. Niveau 3 (actie): start automatisch opnieuw trainen op basis van een venster, met het tijdstip waarop drift werd gedetecteerd als grens voor het venster. Niveau 4 (escalatie): escaleer naar een datawetenschapper voor een controle van het ontwerpen van kenmerken als opnieuw trainen de prestaties niet herstelt.

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

Ensemblemodellen en veerkracht tegen conceptdrift

Ensemblemethode zoals random forests en gradient boosting kunnen conceptdrift langer verhullen dan afzonderlijke modellen, omdat hun diversiteit ze minder gevoelig maakt voor een verschuiving in de verdeling van één kenmerk. Deze veerkracht heeft echter ook een keerzijde: het model wordt langzamer en ongemerkter slechter. Houd in een forest de mate van onenigheid tussen afzonderlijke bomen bij — wanneer bomen het steeds minder vaak eens zijn over voorspellingen, is dat een vroeg signaal dat het concept verschuift en het ensemble moeite heeft om tot overeenstemming te komen.

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))

Snelle controle

Toets uw begrip van de concepten rond machinaal leren met Python uit deze les.

Samenvatting van de les

In deze les heb je geleerd dat conceptdrift een verandering is in P(Y|X) — de omzetting van kenmerken naar labels — die kan optreden zelfs wanneer de verdelingen van kenmerken stabiel lijken, dat afnemende modelprestaties op gelabelde productiegegevens het definitieve signaal van conceptdrift vormen en dat op vensters gebaseerd opnieuw trainen en het wegen van voorbeelden de belangrijkste aanpassingsstrategieën zijn om modellen actueel te houden terwijl concepten veranderen. Hierna bekijken we de monitoring van voorspellingsverdelingen en betrouwbaarheidsscores als vroegtijdig waarschuwingssysteem wanneer de werkelijke labels nog niet beschikbaar zijn.

Gratis beginnen

Leer Python met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
30
Lessen
120

Veelgestelde vragen

Is de les “Conceptdrift: wanneer de relatie tussen X en Y verandert” gratis?

Ja — de volledige tekst van “Conceptdrift: wanneer de relatie tussen X en Y verandert” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Machine Learning Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Machine Learning Academy bevat in totaal 4 lessen.

Wat leer ik in “Conceptdrift: wanneer de relatie tussen X en Y verandert”?

Cursisten onderscheiden datadrift van conceptdrift aan de hand van een tijdreeksvoorbeeld en begrijpen waarom datadrift niet altijd leidt tot slechtere modelprestaties. Je oefent met Machine Learning Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Machine Learning Academy te beginnen?

Ervaring vooraf is niet nodig. Machine Learning Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 4.

Hoe lang duurt de les “Conceptdrift: wanneer de relatie tussen X en Y verandert”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Machine Learning Academy?

Ja. Elke les over Machine Learning Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Datadrift: verschuivingen in featureverdelingen door de tijd
  2. Conceptdrift: wanneer de relatie tussen X en Y verandert
  3. Voorspellingsverdelingen en betrouwbaarheidsscores monitoren
  4. Een driftwaarschuwingspipeline bouwen met Evidently AI
← Terug naar Machine Learning Academy