Model Monitoring
Model monitoring tracks the health of deployed ML models by measuring prediction quality, data drift, and system performance — alerting when the real world has diverged from the training distribution enough to degrade value.
Why Does This Exist?
A deployed model degrades silently. Unlike software, which either works or crashes with an error, an ML model continues returning responses while its predictions quietly become less useful. The world changes — user behavior shifts, economic conditions evolve, new products launch, fraud patterns mutate — and the model trained on yesterday's data serves tomorrow's distribution.
Model monitoring creates the observable feedback loop that software monitoring provides for traditional systems: measure continuously, alert on anomalies, respond before customers notice.
Think of It Like This
A weather station with automated alerts
A weather station measures temperature, pressure, and humidity continuously. It doesn't wait for a hurricane to notice something has changed — it detects the pressure drop that precedes the storm and alerts meteorologists with enough time to respond. Model monitoring works the same way: it measures leading indicators of performance degradation (feature distributions, prediction distributions) and alerts before customers notice degraded service.
How It Actually Works
What to monitor
1. Input feature drift: Are the features arriving at inference time still distributed like the training data?
Statistical tests:
- Population Stability Index (PSI): where = training distribution, = current distribution. PSI < 0.1 = negligible; 0.1–0.2 = moderate; > 0.2 = significant drift.
- Kolmogorov-Smirnov (KS) test: Non-parametric test comparing CDFs. Reports a p-value.
- Jensen-Shannon divergence: Symmetric version of KL divergence, bounded in [0, 1].
2. Output distribution shift: Is the distribution of model predictions changing? A fraud model that was predicting fraud 2% of the time and now predicts 15% either found a real epidemic or has broken.
3. Model performance metrics: Ground-truth performance (accuracy, AUC) — the most direct measure of degradation, but labels arrive with a delay. For a loan default model, you wait months to know if a loan actually defaults.
4. Proxy metrics: Business KPIs that correlate with model quality: click-through rate, conversion rate, support ticket volume. Use these when ground truth labels are delayed.
5. System metrics: Latency, throughput, error rate, memory usage, GPU utilization. Infrastructure health ≠ model quality, but infrastructure failures manifest as bad predictions.
Monitoring cadence
| Data volume | Recommended approach |
|---|---|
| Low traffic | Batch reports (daily/weekly) |
| Medium traffic | Rolling window statistics (hourly) |
| High traffic | Streaming monitoring (real-time PSI) |
Code
import pandas as pdimport numpy as npfrom evidently import ColumnMappingfrom evidently.report import Reportfrom evidently.metric_preset import DataDriftPreset, DataQualityPreset, TargetDriftPreset
# ── Simulate reference (training) and current (production) data ───────────────np.random.seed(42)n_ref, n_curr = 5000, 1000
reference_data = pd.DataFrame({ "age": np.random.normal(35, 10, n_ref).clip(18, 80), "income": np.random.exponential(50000, n_ref), "days_since_login": np.random.exponential(7, n_ref), "country": np.random.choice(["US", "UK", "DE", "FR"], n_ref, p=[0.5, 0.2, 0.2, 0.1]), "label": np.random.binomial(1, 0.05, n_ref), "prediction": np.random.beta(1, 19, n_ref), # model output})
# Simulate drift: income distribution shifts, country mix changescurrent_data = pd.DataFrame({ "age": np.random.normal(38, 12, n_curr).clip(18, 80), # slight shift "income": np.random.exponential(40000, n_curr), # significant shift "days_since_login": np.random.exponential(7, n_curr), # stable "country": np.random.choice(["US", "UK", "DE", "FR"], n_curr, p=[0.3, 0.3, 0.3, 0.1]), "label": np.random.binomial(1, 0.08, n_curr), # label shift "prediction": np.random.beta(1.5, 17, n_curr), # output shift})
# ── Configure and run an Evidently report ─────────────────────────────────────column_mapping = ColumnMapping( target="label", prediction="prediction", numerical_features=["age", "income", "days_since_login"], categorical_features=["country"],)
report = Report(metrics=[ DataDriftPreset(), DataQualityPreset(), TargetDriftPreset(),])
report.run( reference_data=reference_data, current_data=current_data, column_mapping=column_mapping,)
# Save HTML reportreport.save_html("monitoring_report.html")
# Programmatic result accessresult = report.as_dict()dataset_drift = result["metrics"][0]["result"]["dataset_drift"]drift_share = result["metrics"][0]["result"]["share_of_drifted_columns"]print(f"Dataset drift detected: {dataset_drift}")print(f"Share of drifted columns: {drift_share:.1%}")# ── Manual PSI computation ────────────────────────────────────────────────────def compute_psi(reference, current, bins=10): """Compute Population Stability Index for a numerical feature.""" # Create bins from reference distribution min_val = min(reference.min(), current.min()) max_val = max(reference.max(), current.max()) breakpoints = np.linspace(min_val, max_val, bins + 1) ref_pct = np.histogram(reference, bins=breakpoints)[0] / len(reference) + 1e-6 curr_pct = np.histogram(current, bins=breakpoints)[0] / len(current) + 1e-6 psi = np.sum((curr_pct - ref_pct) * np.log(curr_pct / ref_pct)) return psi
psi = compute_psi(reference_data["income"], current_data["income"])level = "negligible" if psi < 0.1 else "moderate" if psi < 0.2 else "significant"print(f"Income PSI: {psi:.3f} → {level} drift")Watch Out For
Monitoring infrastructure but not model quality
A 200 OK response with 5ms latency says nothing about whether the model's predictions are useful. Always monitor at least one model-quality signal — prediction distribution, a proxy business metric, or actual label performance when labels are available. Infrastructure health and model health are orthogonal.
Alert fatigue from over-sensitive thresholds
Setting drift thresholds too low means constant alerts for normal statistical variation. Teams begin ignoring the monitoring dashboard — the worst outcome. Calibrate thresholds by measuring drift during known-good periods (not just applying rule-of-thumb PSI > 0.1). Use rolling baselines that adapt to gradual seasonal shifts rather than a fixed training-data snapshot.
The Quick Version
- Model monitoring detects when a deployed model's environment has diverged from its training distribution.
- Monitor: input feature drift (PSI, KS test), output distribution shift, model performance (with labels or proxies), and system metrics.
- PSI > 0.2 on a key feature is the standard threshold for investigation.
- Use proxy business metrics (click-through rate, conversion) when ground-truth labels are delayed.
- Calibrate alert thresholds from known-good periods, not arbitrary rules — avoid alert fatigue.