PMMDS: Production ML Monitoring & Drift Detection
An end-to-end ML lifecycle system: train → serve → monitor for drift → retrain automatically → promote through a model registry.
Repo: github.com/aksh-ay06/pmmds · Stack: PySpark MLlib, MLflow, FastAPI, Prefect, Prometheus, Docker Compose
Problem
Models degrade silently once deployed. I wanted to build the whole loop that catches this, not just a training notebook: data validation, real-time serving, drift detection, and a safe way to replace the production model.
Approach
- Data & model: NYC TLC yellow taxi trips (~500K rows after filtering), processed with PySpark. A
GBTRegressorpredictsfare_amountfrom 12 engineered features (time, distance, duration, borough, rate code, and others). - Validation: schema and range checks run on both training and inference inputs.
- Serving: FastAPI loads an MLflow pyfunc model and returns request IDs, model version, and latency with each prediction.
- Drift detection: per-feature Population Stability Index (20 bins). Any feature with PSI above 0.2 triggers an alert. KL and JS divergence are also available.
- Automated retraining: when drift is detected, a challenger model is trained and compared with the champion. It is promoted only if its RMSE is at least 0.5 lower, with MAE and R² as secondary checks.
- Operations: Prefect orchestrates every pipeline, Prometheus collects metrics (predictions, latency, retraining runs), and all logs are structured JSON.
flowchart LR C[Client] --> API[FastAPI /predict] API --> M[(MLflow Registry)] API --> L[(Inference log)] L --> D{PSI > threshold?} D -->|yes| R[Train challenger] R --> CMP{Beats champion?} CMP -->|yes| M
Takeaways
The modeling was the easy part. Most of the work went into the guardrails around it: what counts as drift, what evidence justifies a model swap, and how to make every decision auditable.