Chapter 40 — Monitoring a Supervised Model
Learning objectives
- Explain why a model that performed well at deployment can become less reliable over time.
- Design input-data checks for schema, missingness, ranges, categories, and distribution changes.
- Monitor prediction volume, class balance, confidence, extreme outputs, and rejection or review rates.
- Track classification or regression performance when delayed ground-truth labels become available.
- Define retraining triggers, review responsibilities, rollback criteria, and monitoring records.
- Produce a practical monitoring plan for a final supervised-learning model.
Chapter focus Deployment is not the end of the machine-learning workflow. Monitoring asks whether the incoming data, the model outputs, and eventually the measured outcomes still resemble the conditions under which the model was validated. |
40.1 Why model performance changes
A supervised model learns relationships from historical data. Those relationships can become less useful when the environment changes. Some changes are gradual, some are abrupt, and some are caused by software or data-pipeline failures rather than by the real world itself.
| Source of change | What may happen | Monitoring clue |
|---|---|---|
| Data distribution changes | Feature values or category proportions move away from the training distribution. | Range checks, missingness, category frequencies, drift statistics. |
| User behavior changes | People respond differently because preferences, incentives, or workflows change. | Prediction mix changes; later performance declines. |
| New products | New product families create feature combinations not represented during training. | New categories, unusual ranges, low-confidence predictions. |
| Economic changes | Prices, demand, risk, and customer behavior may shift together. | Broad feature drift and subgroup performance changes. |
| Sensor changes | Replacement hardware or calibration changes alter measurement distributions. | Step changes in means, ranges, or missing-value patterns. |
| Data pipeline errors | A join, parser, unit conversion, or column mapping fails. | Schema violations, impossible values, sudden missingness. |
| Concept drift | The relationship between X and y changes even if X itself looks familiar. | Input checks may look normal while labeled performance degrades. |
Important distinction Data drift means the distribution of inputs changes. Concept drift means the relationship between inputs and the target changes. Input monitoring can detect the first directly; the second usually requires labels or a reliable proxy for performance. |
Monitoring time horizons
A useful monitoring system compares several horizons rather than one fixed window. For example, the last day may reveal pipeline failures, the last week may reveal operational changes, and the last month may reveal slower behavioral drift.
| Window | Typical purpose | Example question |
|---|---|---|
| Near real time / hourly | Operational health | Did a source stop sending data? |
| Daily | Fast distribution shifts | Did missingness or prediction volume jump today? |
| Weekly | Behavior and quality trends | Is confidence falling across several days? |
| Monthly / quarterly | Longer-term stability | Is performance deteriorating enough to require retraining? |
40.2 Input monitoring
Input monitoring checks whether the model is receiving data in the form and range it expects. These checks are available before labels arrive, so they are often the first line of defense in production.
- Missing-value rate: compare current missingness with a training or validation reference profile.
- Feature ranges: detect impossible, extreme, or newly observed values.
- Category frequencies: detect new categories and large changes in category proportions.
- Data types: confirm that numeric, categorical, Boolean, and datetime fields have not changed representation.
- Schema changes: detect missing, extra, renamed, or reordered fields where ordering matters.
- Distribution changes: compare reference and current distributions using descriptive or statistical measures.
Reference profile
Monitoring requires a baseline. Save a reference profile from the approved training or validation data at model-release time. The profile should contain the expected schema and simple descriptive statistics that can be compared with future batches.
Python example — build a compact reference profile
| import pandas as pd numeric = ["age", "income", "transactions_30d"] categorical = ["region", "product_type"] reference = { "columns": X_train.columns.tolist(), "dtypes": X_train.dtypes.astype(str).to_dict(), "missing_rate": X_train.isna().mean().to_dict(), "numeric_mean": X_train[numeric].mean().to_dict(), "numeric_std": X_train[numeric].std().to_dict(), "numeric_min": X_train[numeric].min().to_dict(), "numeric_max": X_train[numeric].max().to_dict(), "category_frequency": { c: X_train[c].value_counts(normalize=True).to_dict() for c in categorical }, } |
Schema and missingness checks
Python example — validate columns, dtypes, and missing-value changes
| def input_health_report(frame, reference, missing_tolerance=0.05): expected = set(reference["columns"]) received = set(frame.columns) missing_columns = sorted(expected - received) extra_columns = sorted(received - expected) current_missing = frame.isna().mean() missing_alerts = {} for feature, base_rate in reference["missing_rate"].items(): if feature in frame: delta = current_missing[feature] - base_rate if delta > missing_tolerance: missing_alerts[feature] = round(float(delta), 4) return { "missing_columns": missing_columns, "extra_columns": extra_columns, "missing_rate_increase": missing_alerts, } |
Simple distribution monitoring
Not every monitoring system needs a complex drift library. A transparent first version can track standardized mean movement for numeric variables and absolute frequency changes for categorical variables. More advanced systems may add PSI, KS tests, Jensen-Shannon divergence, or model-based drift detectors.
Standardized mean shift = |current mean - reference mean| / reference standard deviation |
Python example — numeric mean-shift report
| def numeric_shift_report(frame, reference, features, alert_at=0.5): rows = [] for feature in features: current_mean = frame[feature].mean() base_mean = reference["numeric_mean"][feature] base_std = reference["numeric_std"][feature] shift = abs(current_mean - base_mean) / max(base_std, 1e-12) rows.append({ "feature": feature, "standardized_mean_shift": shift, "alert": shift >= alert_at, }) return pd.DataFrame(rows).sort_values( "standardized_mean_shift", ascending=False ) |
Caution A drift alert does not automatically mean the model is wrong. It means current data differ from the reference and deserve investigation. Thresholds should be calibrated from historical variation and operational risk, not copied blindly from a generic example. |
40.3 Prediction monitoring
Prediction monitoring observes the model outputs even when ground-truth labels are not yet available. Sudden changes can reveal upstream data problems, new populations, threshold effects, or a genuine change in demand.
| Signal | Classification example | Regression example |
|---|---|---|
| Prediction frequency | Number of scores per hour/day. | Number of forecasts per hour/day. |
| Output distribution | Fraction predicted positive/negative. | Mean, median, and spread of predictions. |
| Confidence distribution | Histogram of predicted probabilities or margins. | Uncertainty interval width if available. |
| Extreme predictions | Probabilities near 0 or 1. | Predictions near or beyond plausible limits. |
| Rejection / review rate | Fraction sent to human review or abstained. | Fraction outside safety checks or requiring review. |
Python example — monitor classification outputs
| import numpy as np import pandas as pd probability = final_pipeline.predict_proba(X_batch)[:, 1] prediction = (probability >= decision_threshold).astype(int) confidence = np.maximum(probability, 1 - probability) prediction_report = { "n_predictions": len(prediction), "positive_rate": float(prediction.mean()), "mean_probability": float(probability.mean()), "low_confidence_rate": float((confidence < 0.60).mean()), "very_high_probability_rate": float((probability > 0.95).mean()), } print(pd.Series(prediction_report)) |
Abstention and review monitoring
If the application can abstain or request human review, the review rate becomes a first-class operational metric. A rising review rate may indicate safer behavior under uncertainty, but it may also overwhelm the team responsible for manual decisions.
Python example — confidence-based review flag
| lower, upper = 0.40, 0.60 review = (probability >= lower) & (probability <= upper) review_rate = review.mean() auto_decision_rate = 1.0 - review_rate print(f"Review rate: {review_rate:.3f}") print(f"Automatic decision rate: {auto_decision_rate:.3f}") |
40.4 Performance monitoring
True model performance can be measured only when trustworthy outcomes arrive. Labels may be immediate, delayed by days or months, incomplete, or biased toward cases that receive follow-up. Monitoring design must document this label process before interpreting trends.
Classification performance over time
- Accuracy and F1 over time, using a fixed evaluation definition.
- Precision and recall when false-positive and false-negative costs differ.
- ROC AUC or precision-recall metrics when probability ranking matters.
- Performance by subgroup, source, region, device, or other operational slice.
- Calibration changes: whether predicted probabilities still match observed frequencies.
- Threshold effectiveness: whether the chosen operating point still satisfies operational objectives.
Python example — classification metrics by time period
| from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score def classification_metrics(group): y_true = group["actual"] y_pred = group["prediction"] return pd.Series({ "n": len(group), "accuracy": accuracy_score(y_true, y_pred), "precision": precision_score(y_true, y_pred, zero_division=0), "recall": recall_score(y_true, y_pred, zero_division=0), "f1": f1_score(y_true, y_pred, zero_division=0), }) weekly = scored_with_labels.groupby("week").apply( classification_metrics, include_groups=False ) print(weekly) |
Regression performance over time
Python example — MAE and RMSE by month
| from sklearn.metrics import mean_absolute_error, mean_squared_error def regression_metrics(group): y_true = group["actual"] y_pred = group["prediction"] return pd.Series({ "n": len(group), "mae": mean_absolute_error(y_true, y_pred), "rmse": mean_squared_error(y_true, y_pred) ** 0.5, "mean_error": (y_true - y_pred).mean(), }) monthly = scored_with_labels.groupby("month").apply( regression_metrics, include_groups=False ) print(monthly) |
Subgroup and calibration monitoring
Aggregate metrics can hide local deterioration. A monitoring report should therefore repeat the important error metrics by relevant operational slices. For probabilistic classifiers, calibration should also be monitored because a model can maintain a similar AUC while its probabilities become systematically too high or too low.
Python example — subgroup classification report
| def subgroup_report(frame, group_column): rows = [] for group_name, g in frame.groupby(group_column): y_true = g["actual"] y_pred = g["prediction"] rows.append({ "group": group_name, "n": len(g), "f1": f1_score(y_true, y_pred, zero_division=0), "recall": recall_score(y_true, y_pred, zero_division=0), }) return pd.DataFrame(rows).sort_values("f1") print(subgroup_report(scored_with_labels, "region")) |
Label delay matters A dashboard that shows yesterday's input drift next to performance labels that describe customers from three months ago can be misleading. Every monitored metric should state the event time, prediction time, and label-availability time it represents. |
40.5 Retraining strategy
Retraining should be a controlled model-change process, not an automatic reaction to every alert. A monitoring plan defines who investigates, what evidence is required, how a replacement model is validated, and how the organization can roll back if the new model fails.
| Strategy | Trigger | Advantages | Risks / cautions |
|---|---|---|---|
| Scheduled retraining | Fixed calendar interval. | Simple planning; predictable governance. | May retrain when unnecessary or react too slowly to abrupt change. |
| Performance-triggered | Metric crosses a predefined action threshold. | Directly tied to measured usefulness. | Requires reliable and sufficiently timely labels. |
| Data-volume-triggered | Enough new labeled observations accumulate. | Ensures a meaningful amount of new evidence. | Volume alone does not prove the environment changed. |
| Manual review | Analyst or domain team decides after investigation. | Allows context and root-cause analysis. | Slower and depends on clear ownership. |
| Rollback | New model violates safety/performance criteria. | Restores a previously approved artifact quickly. | Requires preserved artifacts, data contracts, and deployment compatibility. |
A controlled retraining workflow
- Detect an alert or scheduled review event.
- Diagnose whether the cause is data quality, drift, label problems, or genuine concept change.
- Create a refreshed candidate dataset using an explicit cutoff date and target definition.
- Retrain candidate models under the approved validation protocol.
- Compare the candidate with the current production model using the same metrics and slices.
- Approve, version, and deploy only if acceptance criteria are satisfied.
- Monitor the new model closely and retain a tested rollback path.
Do not retrain blindly If the root cause is a broken unit conversion, missing source, or changed label definition, retraining on corrupted data can make the problem worse. Fix the data-generating process first, then reassess whether retraining is still necessary. |
Monitoring ownership and records
| Item | What to record |
|---|---|
| Model identity | Model name, artifact version, deployment date, decision threshold. |
| Reference data | Dataset version and period used to create monitoring baselines. |
| Metric definition | Formula, grouping window, exclusions, and label delay. |
| Alert rule | Warning/action threshold, minimum sample size, and persistence rule. |
| Owner | Team or person responsible for investigation and response. |
| Action history | Alert date, diagnosis, mitigation, retraining decision, rollback if used. |
Practical activity — Design a monitoring plan for the final model
Objective: create a monitoring specification that could be handed to an engineering or operations team after deployment. The activity uses a classification example, but the same structure can be adapted to regression.
Scenario
Assume the final model predicts customer churn each day. The application receives customer behavior data, produces a churn probability, applies a fixed threshold selected before final test evaluation, and sends low-confidence cases to human review. Confirmed churn labels arrive approximately 30 days later.
Step 1 — Create a synthetic monitoring stream
Python lab — create reference and current batches
| import numpy as np import pandas as pd rng = np.random.default_rng(42) n = 1500 reference_batch = pd.DataFrame({ "tenure_months": rng.normal(32, 12, n).clip(1, 72), "monthly_spend": rng.normal(65, 18, n).clip(5, 180), "tickets_30d": rng.poisson(1.2, n), "region": rng.choice(["North", "South", "West"], n, p=[0.4, 0.35, 0.25]), }) current_batch = reference_batch.copy() current_batch["monthly_spend"] *= 1.12 current_batch.loc[rng.random(n) < 0.08, "tickets_30d"] = np.nan |
Step 2 — Build a compact reference profile
Python lab — reference means, standard deviations, and missingness
| numeric = ["tenure_months", "monthly_spend", "tickets_30d"] baseline = { "columns": reference_batch.columns.tolist(), "missing_rate": reference_batch.isna().mean().to_dict(), "numeric_mean": reference_batch[numeric].mean().to_dict(), "numeric_std": reference_batch[numeric].std().to_dict(), "region_frequency": reference_batch["region"].value_counts(normalize=True).to_dict(), } print(pd.Series(baseline["numeric_mean"])) |
Step 3 — Monitor input health
Python lab — compare current input with the baseline
| input_rows = [] for feature in numeric: base_mean = baseline["numeric_mean"][feature] base_std = baseline["numeric_std"][feature] current_mean = current_batch[feature].mean() shift = abs(current_mean - base_mean) / max(base_std, 1e-12) missing_change = ( current_batch[feature].isna().mean() - baseline["missing_rate"][feature] ) input_rows.append({ "feature": feature, "mean_shift_sd": shift, "missing_change": missing_change, }) print(pd.DataFrame(input_rows)) |
Step 4 — Monitor predictions and confidence
Use your final trained pipeline if available. If the class project is not yet deployed, simulate stored probabilities so that the monitoring logic can still be implemented and tested.
Python lab — prediction-distribution report
| # Replace this line with final_pipeline.predict_proba(...) in your project. probability = np.clip(rng.beta(2.2, 5.0, n), 0, 1) decision_threshold = 0.45 prediction = (probability >= decision_threshold).astype(int) confidence = np.maximum(probability, 1 - probability) review = confidence < 0.60 prediction_summary = pd.Series({ "n": n, "positive_rate": prediction.mean(), "mean_probability": probability.mean(), "low_confidence_rate": review.mean(), "p95_probability": np.quantile(probability, 0.95), }) print(prediction_summary) |
Step 5 — Add delayed performance monitoring
Python lab — weekly F1 and recall once labels arrive
| from sklearn.metrics import f1_score, recall_score monitor = pd.DataFrame({ "week": np.repeat(np.arange(1, 7), n // 6)[:n], "probability": probability[:n], }) monitor["prediction"] = (monitor["probability"] >= decision_threshold).astype(int) # In production, replace this simulated target with confirmed delayed labels. monitor["actual"] = rng.binomial(1, monitor["probability"].clip(0.05, 0.80)) weekly = monitor.groupby("week").apply( lambda g: pd.Series({ "n": len(g), "f1": f1_score(g["actual"], g["prediction"], zero_division=0), "recall": recall_score(g["actual"], g["prediction"], zero_division=0), }), include_groups=False, ) print(weekly) |
Step 6 — Write alert rules as an engineering specification
| Area | Metric | Baseline / comparison | Example action rule | Owner / action |
|---|---|---|---|---|
| Input | Missing rate | Release reference profile | Investigate if increase persists for 2 daily windows. | Data team validates source and parser. |
| Input | Numeric drift | Reference mean/std or full distribution | Review if standardized mean shift exceeds calibrated tolerance. | ML team checks product/behavior change. |
| Prediction | Positive rate | Recent approved operating range | Investigate abrupt or sustained change. | ML + business owner assess demand and threshold. |
| Prediction | Low-confidence rate | Release and recent history | Review capacity if rate rises materially. | Operations checks review queue. |
| Performance | F1 / recall | Final-test and recent production baseline | Escalate after sufficient labeled sample and persistent degradation. | Model owner opens retraining review. |
| Performance | Subgroup metric | Approved subgroup baseline | Investigate meaningful, persistent gap with adequate sample size. | Model owner + domain reviewer. |
Step 7 — Define the retraining and rollback policy
- State whether retraining is scheduled, trigger-based, data-volume-based, manual, or a combination.
- Specify the minimum amount and recency of labeled data required before retraining.
- Keep the final-test-equivalent acceptance criteria for any replacement model.
- Preserve the current approved artifact until the replacement is validated and deployable.
- Define rollback conditions and verify that the previous artifact remains compatible with the serving system.
Student deliverable — monitoring plan
| Section | Required content |
|---|---|
| Model and use case | Model/version, target, users, prediction frequency, decision threshold. |
| Data inputs | Expected schema, sources, ranges, categories, missingness, monitoring windows. |
| Prediction monitoring | Volume, output distribution, confidence, review/rejection rate, extremes. |
| Performance monitoring | Metrics, label delay, subgroup views, calibration or residual checks. |
| Alerts | Warning/action levels, minimum sample size, persistence rule, responsible owner. |
| Retraining | Trigger type, validation protocol, approval criteria, deployment process. |
| Rollback | Previous approved artifact, rollback trigger, restoration test. |
| Documentation | Dashboard/report frequency, incident record, change log, review cadence. |
Discussion questions
1. Which monitored signals can be computed immediately, and which require delayed labels?
2. What input change would represent a harmless business change rather than a model failure?
3. How will you choose alert thresholds without creating excessive false alarms?
4. How much labeled evidence is required before declaring that performance has degraded?
5. Which subgroup or operational slices are essential for your use case?
6. What conditions should cause retraining, and what conditions should cause rollback instead?
Chapter summary
| Concept | Key takeaway |
|---|---|
| Model change | Production environments evolve through data drift, behavior change, pipeline failures, and concept drift. |
| Input monitoring | Track schema, dtypes, missingness, ranges, categories, and distribution changes before labels arrive. |
| Prediction monitoring | Track volume, output mix, confidence, extremes, and review/rejection rates. |
| Performance monitoring | When labels arrive, track task metrics over time and by important slices. |
| Retraining / rollback | Use evidence-based retraining and keep a known-good artifact with a tested restoration path. |