Starting from Zero — A Physical Intuition
Before looking at deployment graphs, let's understand MLOps through a physical factory scanner analogy:
- The Conveyor Scanner (Serving): Imagine you have a physical scanner at a bottle factory checking for caps. Batch Serving is taking all bottles produced overnight, putting them in a separate room, and scanning them at 8:00 AM in one large bin. Online Serving is scanning each bottle individually as it rolls down the active conveyor belt, giving a pass/fail output in milliseconds.
- The Factory Blueprint Registry (Model Registry): If a scanner breaks, you don't guess how to fix it. You look up the model registry—a secure folder containing the exact blueprint of the scanner hardware, the software version, and the training calibrations. Reverting to a previous scanner version is a simple copy-paste of the blueprint.
- The Input Quality Alarm (Data Drift): Imagine the factory changes the color of the bottles from clear to green. The scanner continues to output predictions, but because the input bottles look different (data distribution shift), it starts failing silently. The Population Stability Index (PSI) is a sensor that alerts the manager: "The color distribution of bottles today is completely different from the training baseline!" This triggers a recalibration (retraining) before defective bottles leave the factory.
The half of ML that isn't modeling
Training a good model is maybe 10% of the work. The other 90% is getting it to users and keeping it healthy: serving it at low latency, versioning it, shipping new versions without breaking anything, and noticing when the world shifts out from under it. A model is the only kind of software that silently gets worse after you ship it — not because the code changed, but because reality did. MLOps is the discipline that handles that.
Serving: how the model reaches users
- Batch / offline — score a whole table on a schedule (nightly churn scores, recommendations). Simple, high-throughput, latency doesn't matter.
- Online / real-time — the model sits behind an API and answers per request (fraud check at checkout). Latency is a hard requirement; you cache, batch requests, and right-size hardware (GPU vs CPU).
- Streaming — score events as they arrive off a queue.
In every case the model is a versioned artifact (weights + preprocessing) behind an interface — treat it like a deployable, not a notebook.
The deployment pipeline
The model registry versions every model with its data, code and metrics, so any deploy is reproducible and any bad release is one rollback away. Reproducibility in ML means pinning data + code + model, not just code.
Shipping safely
- Shadow — run the new model alongside the old on real traffic but don't serve its output; compare predictions offline. Zero user risk.
- Canary / A/B — route a small % of traffic to the new model, watch the metrics, ramp up if healthy.
- Rollback — because the model is a versioned artifact, reverting is instant.
Monitoring & drift
You can watch latency and error rate like any service — but the model-specific risk is drift: the live data drifting away from what the model trained on.
- Data drift — the input distribution shifts (a feature's range moves). Detect it without labels by comparing live vs training distributions, e.g. with the Population Stability Index (PSI).
- Concept drift — the relationship between inputs and the target changes (fraud patterns evolve), so accuracy decays even if inputs look the same.
- The catch: labels are delayed (you learn the true outcome days later), so input-distribution monitoring is your early-warning system.
Here's PSI in action: a monitored feature quietly drifts right window by window; the score climbs through the warning band and trips the retrain threshold — the signal to act, long before you'd have ground-truth labels:
1/7Window t+0: the live distribution still matches training. PSI = 0.000 < 0.1 — model is healthy, no action.
Training-serving skew
The most common production ML bug: a feature is computed one way in training and a different way in serving (different code paths, time zones, a fix applied to one pipeline but not the other). The model then sees inputs at serving time it never really trained on, and accuracy quietly tanks. The fix is a feature store (or shared feature code) so the exact same transformation runs in both places.
Retraining
Decide what triggers a refresh:
- Scheduled — retrain nightly/weekly (simple, may retrain when unnecessary).
- Drift-triggered — retrain when PSI / a monitor crosses a threshold (efficient, needs solid monitoring).
- Performance-triggered — retrain when measured accuracy drops (best, but needs timely labels).
Experiment Tracking with MLflow
To prevent "it worked in my notebook" syndrome, production MLOps relies on experiment tracking systems (like MLflow or Weights & Biases) to log every run's hyperparameters, training metrics, and code states, and register the model artifact.
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
# Set experiment name
mlflow.set_experiment("fraud-detection-model")
# Start run tracking
with mlflow.start_run():
# Define hyperparameters
n_estimators = 100
max_depth = 5
# Log parameters
mlflow.log_param("n_estimators", n_estimators)
mlflow.log_param("max_depth", max_depth)
# Train model
# (Assuming X_train, y_train, X_test, y_test are loaded)
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth)
clf.fit(X_train, y_train)
# Log metrics
y_pred = clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
mlflow.log_metric("accuracy", acc)
# Log and register the model in the registry
mlflow.sklearn.log_model(
sk_model=clf,
artifact_path="model",
registered_model_name="RandomForestFraudModel"
)
Model Containerization (Docker + FastAPI)
To deploy models consistently across staging and production, we package the model artifact and serving code into a container. Below is a standard Dockerfile and serving API using FastAPI:
# app.py (Serving API)
from fastapi import FastAPI
import joblib
import numpy as np
app = FastAPI()
# Load model from registry / disk
model = joblib.load("model.joblib")
@app.post("/predict")
def predict(features: list[float]):
# Expects features as list of float values
prediction = model.predict([np.array(features)])
return {"prediction": int(prediction[0])}
# Dockerfile
FROM python:3.10-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy model artifact and serving script
COPY model.joblib .
COPY app.py .
# Expose API port
EXPOSE 8000
# Run FastAPI app with Uvicorn
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
CI/CD for ML (GitHub Actions)
Continuous Integration in ML ensures that changes to training data, config files, or preprocessing code do not degrade performance. Below is a GitHub Actions workflow that automatically triggers on pull requests to run unit tests and check model performance regression:
name: ML CI/CD Pipeline
on: [push, pull_request]
jobs:
test_model:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Validate dataset schemas
run: |
pytest tests/test_data_validation.py
- name: Evaluate model performance threshold
run: |
# Fails the build if accuracy falls below 85%
python scripts/evaluate_regression.py --threshold 0.85
Model Cards
A Model Card is a structured markdown document that serves as the "public label" of an ML model, providing transparency into its intent, performance, limitations, and biases.
# Model Card: Fraud Detection Random Forest (v1.2.0)
## Overview
- **Developer**: Risk Platform Team
- **Model Type**: Random Forest Classifier
- **Release Date**: June 2026
## Intended Use
- **Primary Use**: Predicting credit card transaction fraud in real-time.
- **Out of Scope**: Credit score evaluations or long-term lending risks.
## Training Data & Methodology
- Trained on 1.2M anonymized transactions (Q1 2026 data).
- Features include: transaction amount, category, user location frequency, distance from home.
## Metrics & Performance
- **Accuracy**: 94.2%
- **Precision (Fraud class)**: 91.5% (minimizes false positive blocks)
- **Recall (Fraud class)**: 88.0% (captures true fraud events)
## Ethical Considerations & Limitations
- **Limitations**: The model lacks context on global traveling events, leading to higher false-positives during holidays.
- **Biases**: Tends to flag transactions from IP addresses in newly registered server pools; regular checks are run to ensure geographic fairness.
Common Interview Questions
Drift Detection in Code
Here is a complete, library-free Python implementation of a Population Stability Index (PSI) calculator to detect feature distribution drift between training (baseline) and serving (live) datasets:
import math
from typing import List, Tuple
def calculate_psi(expected: List[float], actual: List[float], num_buckets: int = 10) -> Tuple[float, str]:
"""
Calculate the Population Stability Index (PSI) between two 1D datasets.
Args:
expected: Baseline/training feature values.
actual: Production/serving feature values.
num_buckets: Number of quantiles to split the data.
Returns:
A tuple of (psi_value, drift_level).
"""
# 1. Sort the baseline data to determine bucket boundaries (quantiles)
sorted_expected = sorted(expected)
n_expected = len(expected)
n_actual = len(actual)
if n_expected == 0 or n_actual == 0:
raise ValueError("Inputs cannot be empty.")
# Get split boundaries based on equal-frequency quantiles from expected distribution
boundaries = []
for i in range(1, num_buckets):
idx = int(i * n_expected / num_buckets)
boundaries.append(sorted_expected[idx])
# Helper to count frequencies in each bucket
def get_bucket_counts(data: List[float]) -> List[int]:
counts = [0] * num_buckets
for val in data:
placed = False
for bucket_idx, boundary in enumerate(boundaries):
if val <= boundary:
counts[bucket_idx] += 1
placed = True
break
if not placed:
counts[-1] += 1
return counts
expected_counts = get_bucket_counts(expected)
actual_counts = get_bucket_counts(actual)
# 2. Calculate PSI
psi_val = 0.0
for e_count, a_count in zip(expected_counts, actual_counts):
# Convert to percentages (probabilities)
e_pct = e_count / n_expected
a_pct = a_count / n_actual
# Handle zero counts using a tiny epsilon to prevent division-by-zero/log-zero errors
if e_pct == 0:
e_pct = 0.0001
if a_pct == 0:
a_pct = 0.0001
psi_val += (a_pct - e_pct) * math.log(a_pct / e_pct)
# 3. Classify drift level
# PSI < 0.1: No change / Stable
# 0.1 <= PSI < 0.25: Slight change / Warning
# PSI >= 0.25: Significant change / Action required
if psi_val < 0.1:
status = "Healthy (No significant distribution shift)"
elif psi_val < 0.25:
status = "Warning (Moderate distribution shift detected)"
else:
status = "Alert (Significant distribution shift detected - Action Required)"
return psi_val, status
# Example usage with mock data
if __name__ == "__main__":
# expected: centered around 0
# actual: shifted slightly to the right (simulating drift)
expected_data = [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0] * 10
actual_data = [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] * 10
psi, level = calculate_psi(expected_data, actual_data, num_buckets=5)
print(f"Calculated PSI: {psi:.4f}")
print(f"Drift Level: {level}")
Think it through like the interview
Don't just trigger retraining on a schedule — derive the correct diagnosis when system performance begins to degrade.
PROBLEMYour production credit-risk model's accuracy drops by 8% over six months. Design a diagnostic workflow to determine whether this is data drift or concept drift, and outline the proper fix.
- 1
Isolate the symptom without labels
“True loan default labels take months to realize, but you need to know *now* why predictions look different. What is the first statistic you check on the incoming production features?”
- 2
Distinguish between Data and Concept Drift
“Suppose you find that the PSI for 'yearly income' is 0.32, but the model's mapping of income to default rate remains accurate. How do you distinguish if this is a simple shift in candidate demographics vs a change in how creditworthy high-income earners are?”
unlocks after the stage above - 3
Formulate the remediation strategy
“You confirm Concept Drift is occurring due to changing macroeconomic conditions. What steps should you take to safely redeploy the model?”
unlocks after the stage above
Interactive Quiz
1.
2.
3.
Practice
- Drift monitor: Run the custom Python PSI drift detection code provided above. Modify the code to calculate the PSI for a feature with 10 buckets instead of 5 and check how the sensitivity changes.
- Rollout plan: write the shadow → canary → full rollout steps for a new fraud model, naming the metric you'd watch and the rollback trigger at each stage.
- Skew hunt: take a feature defined in a training query and re-implement it in serving code — list three ways they could silently diverge.
- Retraining policy: for a recommendation model whose labels (clicks) arrive within minutes vs a credit model whose labels (defaults) take months, design a different retraining trigger for each and justify it.
This completes Level 11 — AI & Machine Learning, end to end: from the math through modeling, transformers, LLMs, RAG, agents, evaluation, and operating it all in production.