🎯 What you'll learn: Lesson 22's API works the day it ships — but shipping isn't the finish line. This lesson covers what happens AFTER deployment: why a model that scored well in Lesson 15's evaluation can quietly get worse in production without anyone touching the code, the difference between data drift and concept drift, simple practical ways to monitor a live model, and — the genuinely useful question — how to tell when it's actually time to retrain.
Section 1
Why a Deployed Model Can Degrade Over Time
A trained model is a snapshot. It learned patterns from a specific training dataset, collected at a specific point in time, and everything it does — every prediction from Lesson 22's /predict endpoint — is really just applying that frozen snapshot of patterns to new, incoming data. The problem is simple to state and easy to forget in practice: the real world keeps changing, and the model does not — not unless someone deliberately retrains it.
⚠️
A model can look "broken" while the code is working perfectly
This is the trap: the API from Lesson 22 can be running flawlessly — no errors, no crashes, fast responses — while STILL quietly producing worse and worse predictions, because the relationship between input features and the real-world outcome has shifted underneath it. Nothing in a typical error log would show that on its own.
Two related but distinct things can go wrong, and telling them apart is the core of this lesson: the DATA a model sees can change (data drift), or the underlying RELATIONSHIP between that data and the correct answer can change (concept drift).
Section 2
Data Drift vs. Concept Drift
Both describe a live model seeing something different from what it was trained on — the distinction is WHAT changed.
Data Drift (a.k.a. feature or input drift)
The distribution of the INPUT features shifts over time, even though the underlying relationship between those features and the correct answer stays the same. The model itself isn't wrong about the pattern — it's just being asked about a part of the "space" it never really saw much of during training.
📊
Plain example — data drift
Imagine the Lesson 24 capstone's score predictor was trained on students during a normal semester. Then exam season arrives, and study_hours values across the whole incoming population jump noticeably higher than anything seen in training. The RELATIONSHIP between study hours and final score hasn't changed — but the model is now seeing a range of input values it has little experience with, and its predictions become less reliable simply because of that shift.
Concept Drift
The relationship between the input features (X) and the correct answer (y) itself changes — the same input that used to mean one thing now means something different. This is a deeper problem than data drift, because no amount of the model just "seeing more of the same kind of data" fixes it; the pattern it learned is genuinely no longer accurate.
📉
Plain example — concept drift
A spam classifier learns that certain words and formatting patterns reliably signal "spam." Spammers adapt their tactics over time specifically to evade filters like this one — so the SAME email content patterns that used to be a strong spam signal stop being reliable. The relationship between "what the email looks like" and "is it actually spam" has shifted, not just the volume or kind of email being sent.
Comparing feature statistics over time (Section 3)
Performance metrics dropping once true outcomes are known
Section 3
Simple, Practical Monitoring Approaches
None of this requires sophisticated infrastructure to start. A few genuinely useful habits, roughly in order of how easy they are to set up:
1
Track the prediction distribution over time
Log the mean, standard deviation, and a rough histogram of what the model is PREDICTING, batched daily or weekly. A sudden shift — average predicted score jumping or collapsing — is a signal worth investigating, even before you know why.
2
Compare feature statistics: training data vs. live data
Periodically compute simple summary statistics (mean, std, min/max, missing-value rate) for each incoming feature, and compare them against the same statistics from the original training set. A feature whose live mean has drifted far from its training mean is exactly what data drift looks like numerically.
3
Log every prediction for later review
Store the input features, the prediction, and a timestamp for every request — even without an immediate ground-truth label. When the true outcome eventually becomes available (a student's actual final grade, a customer's actual churn), it can be joined back to the logged prediction to measure real accuracy after the fact.
4
Watch for basic API health signals too
Rising latency or a rising error rate on the /predict endpoint from Lesson 22 isn't drift exactly, but it's part of the same "is this thing healthy" question, and easy to track alongside the model-specific signals above.
compare_feature_stats.py
PYTHON
import pandas as pd
# The original training data, and a batch of recent live requests
train_df = pd.read_csv("student_scores_train.csv")
live_df = pd.read_csv("logged_predictions_last_7_days.csv")
features = ["StudyHours", "Attendance", "PrevScore", "AssignmentsDone"]
# Simple side-by-side comparison of summary statistics
comparison = pd.DataFrame({
"train_mean": train_df[features].mean(),
"live_mean": live_df[features].mean(),
"train_std": train_df[features].std(),
"live_std": live_df[features].std(),
})
comparison["mean_shift"] = (comparison["live_mean"] - comparison["train_mean"]).abs()
print(comparison.sort_values("mean_shift", ascending=False))
log_prediction.py — inside the FastAPI endpoint
PYTHON
import csv
from datetime import datetime
deflog_prediction(features: dict, predicted_score: float):
"""Append one row per prediction — features, output, and a timestamp."""
row = {**features, "predicted_score": predicted_score, "timestamp": datetime.now().isoformat()}
withopen("prediction_log.csv", "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=list(row.keys()))
if f.tell() == 0:
writer.writeheader()
writer.writerow(row)
Calling log_prediction(...) from inside the /predict endpoint (Lesson 22, Section 5) turns every real request into a row that Section 3's comparison script above can analyze later — a genuinely simple addition with real long-term value.
Section 4
When Is Retraining Actually Needed?
Retraining isn't free — it costs compute time, and every new model version needs to be re-evaluated (Lesson 15's metrics, Lesson 4's cross-validation) before it replaces the one currently in production. A few practical, honest signals for when it's actually worth doing:
📉
Measured performance has genuinely dropped
Once real outcomes are available for logged predictions (Section 3), a real drop in accuracy/R²/whatever metric matters is the clearest possible signal — it's not a proxy, it's the actual thing being cared about.
📊
Feature statistics have shifted substantially
A large, sustained gap between training and live feature statistics (Section 3's comparison) — especially before ground-truth outcomes are even available yet — is a reasonable early warning to investigate.
🗓️
A scheduled, periodic baseline
Many teams retrain on a fixed schedule (e.g. monthly, or each new academic term) simply as a baseline habit, regardless of whether drift has been explicitly detected yet — cheap insurance against slow, hard-to-notice decay.
📅
A known real-world change happened
A new grading policy, a new semester structure, a business process change — anything that plausibly alters the true relationship between features and outcome is worth treating as a retraining trigger on its own.
💡
Monitoring is what makes "when to retrain" an answerable question
Without any of Section 3's logging or comparison in place, "should we retrain?" has no evidence behind it either way — it becomes a guess. The entire value of monitoring is turning that guess into a data-backed decision.
Section 5
The Wider MLOps Landscape
Everything in this lesson can be built with plain pandas, a CSV log file, and a scheduled script — genuinely enough to start. As a project or team grows, dedicated tools exist to handle these same ideas at scale: experiment and model-registry tracking (MLflow), dedicated drift-detection libraries, and general-purpose dashboarding tools for visualizing metrics over time. None of them change the underlying concepts from Sections 2–4 — they automate and scale the same core ideas: compare distributions, log predictions, watch for change.
⚠️
Concepts first, tooling second
It's tempting to reach for a dedicated monitoring platform immediately — but understanding data drift vs. concept drift, and what "compare training stats to live stats" actually means, is what makes ANY tool useful. A dashboard showing numbers nobody understands the meaning of isn't monitoring; it's just a chart.
🧩 Knowledge Check — Lesson 23
4 questions on model monitoring and drift detection.
1. What's the key difference between data drift and concept drift?
2. Which of these is an example of data drift rather than concept drift?
3. Why is it useful to log every prediction (with its input features and a timestamp) even before ground-truth outcomes are available?
4. According to this lesson, what's a practical, honest trigger for considering retraining a deployed model?
💪
Try It Yourself — Lesson 23
Practice monitoring a model · Intermediate Level
Build on the log_prediction() function and Lesson 22's FastAPI app for each task below.
Task 1: Wire up prediction logging 📝
Add log_prediction(features, predicted_score) from Section 3 into Lesson 22's /predict endpoint, so every real request appends a row to prediction_log.csv. Make several test requests and confirm the file fills up correctly.
Task 2: Compare a "new" batch against training stats 📊
Create a small synthetic CSV representing a "new" batch of incoming data where StudyHours is deliberately shifted higher than the original training data. Run Section 3's comparison script against it and confirm the mean_shift column flags it.
Task 3: Design a monitoring dashboard (on paper) 🗂️
Without writing any dashboard code, list the 5 fields/charts you'd want on a simple monitoring dashboard for this API — pick from prediction distribution, feature comparison, latency, error rate, or anything else this lesson covered — and write one sentence justifying each choice.
💡 Show hints if you're stuck
Task 1: Call log_prediction() right before the return PredictionOutput(...) line in Lesson 22's endpoint.
Task 2: Try adding roughly 3-4 hours to every StudyHours value in a copied CSV to create an obvious, easy-to-verify shift.
Task 3: A reasonable starting five: predicted-score distribution, per-feature mean shift, request volume over time, error rate, and average response latency.
Finished this lesson?
Mark it complete — one lesson left before the course capstone.
🎉
Lesson 23 Complete!
You now know why deployed models degrade, the difference between data drift and concept drift, how to monitor a live model with simple pandas comparisons and prediction logging, and when retraining is actually justified. Lesson 24 is the course capstone — pulling every one of these pieces together into one full project.