Concept Drift
Your fraud detection model was perfect when you trained it. Then fraudsters adapted. The same transactions that were safe last year are fraud this year, and vice versa. The inputs didn't change — the world did. That's concept drift.
Why Does This Exist?
Data drift detection catches when the inputs shift. Concept drift is harder: it's when the relationship between inputs and outputs shifts, even while the inputs look the same.
A credit scoring model trained in 2022 assigned high risk to applicants with short credit history. By 2024, a generation of people adopted buy-now-pay-later services that don't appear in traditional credit reports — short credit history no longer predicts default the same way. The features haven't drifted. The world has.
Think of It Like This
Think of It Like This
Imagine a music recommendation model trained when "lo-fi hip hop" meant niche bedroom producers. Three years later, lo-fi is a global mainstream genre with tens of millions of listeners. The same genre tag means something completely different in the training data and in production. The input (genre=lo-fi) is identical; the output the model should recommend has completely changed.
Gradual vs Sudden Drift
Gradual drift accumulates over months or years. Consumer behaviour, language patterns, and market conditions all shift slowly. The model degrades incrementally, which makes it hard to attribute the decline to a specific cause. The fix is scheduled retraining on a sliding window of recent data.
Sudden drift happens overnight. A regulation changes, a competitor launches, a global event alters behaviour — and the model's training distribution is immediately obsolete. Fraud patterns after a data breach are a classic example. The fix is emergency retraining triggered by an abrupt accuracy drop.
Recurring drift is periodic. A retail demand model trained on non-holiday data performs well except every November, when shopping patterns spike in ways the model never saw. The fix is time-aware training: use holiday-period data as a required component of every training cycle, not an afterthought.
Detecting Concept Drift Without Ground Truth
This is the hard part. With data drift you compare feature distributions — no labels needed. With concept drift, you need to know whether the model's predictions are now wrong, which requires ground truth labels you often don't have in real time.
Three approaches when labels are delayed:
Proxy metrics. Track model confidence distributions. If a fraud model starts assigning 0.5 probability to almost everything, its decision boundary has collapsed — it's no longer confident in anything. This is a symptom of concept drift even without knowing which predictions were wrong.
Human annotation sampling. Route 1–2% of predictions to human reviewers. Use their labels as a sample-based ground truth to estimate accuracy. Expensive but reliable for high-stakes applications.
Delayed feedback loop. Some labels arrive naturally with delay: a loan default label arrives when the borrower misses payments, a churn label arrives when the subscription lapses. Instrument your system to connect those delayed labels back to the original prediction, then compute accuracy on a rolling window.
Show Me the Code
import numpy as npfrom scipy.stats import chi2_contingency
def detect_prediction_drift( train_preds: np.ndarray, prod_preds: np.ndarray, n_bins: int = 5, alpha: float = 0.05,) -> dict: """ Detect drift in prediction distributions using chi-squared test. Works without ground truth labels. """ bins = np.linspace(0, 1, n_bins + 1) train_counts, _ = np.histogram(train_preds, bins=bins) prod_counts, _ = np.histogram(prod_preds, bins=bins)
# chi-squared test: are these from the same distribution? contingency = np.array([train_counts, prod_counts]) chi2, p_value, dof, expected = chi2_contingency(contingency)
return { "chi2": round(chi2, 3), "p_value": round(p_value, 4), "drift_detected": p_value < alpha, "message": "Prediction distribution has drifted" if p_value < alpha else "Stable", }
# Example usagetrain_scores = np.load("training_fraud_scores.npy") # historical model predictionsprod_scores = np.load("production_fraud_scores_30d.npy") # recent predictions
result = detect_prediction_drift(train_scores, prod_scores)print(result)# {'chi2': 18.4, 'p_value': 0.001, 'drift_detected': True, 'message': 'Prediction distribution has drifted'}Watch Out For
Watch Out For
Retraining on drifted data without curation. When concept drift is detected, the instinct is to retrain immediately on recent data. But recent data might be noisy, adversarial (in the case of fraud where attackers probe your model), or simply small. Retraining too eagerly on a bad recent window can make things worse than the drifted model. Curate the retraining window: combine recent data with a historically representative sample, and always run your full eval suite before promoting the retrained model.
The Quick Version
- Concept drift means changed — the relationship between features and labels, not just the features.
- Three types: gradual (slow accumulation), sudden (overnight event), recurring (seasonal).
- Without ground truth labels, detect it via proxy metrics (confidence collapse), human annotation sampling, or delayed feedback loops.
- When retraining to fix drift, curate the training window — don't blindly retrain on recent data without running evals first.
What to Read Next
shadow-and-canary-deployments— How to safely test a retrained model on live traffic before fully promoting it.data-drift-detection— The simpler related problem: detecting that the inputs have shifted, even when the relationship hasn't.