Advanced
18 min read
#MLOps#Deployment#FastAPI#Docker#Model Serving#Joblib#Model Drift#CI/CD

MLOps & Production Deployment Basics

Bridge the gap from notebook to production: Pipeline serialization with Joblib, high-performance REST API serving with FastAPI and Pydantic, Docker containerization, and data/concept drift monitoring.

MLOps & Production Deployment Basics

Focus: From Jupyter Notebook to REST API, Containerization, and Drift Monitoring Tools: Scikit-Learn, FastAPI, Pydantic, Joblib, Uvicorn, Docker Level: Advanced / Production-Ready


Table of Contents#

  1. Introduction: The Production ML Lifecycle
  2. Step 1: Pipeline Serialization (Saving & Loading)
  3. Step 2: Building High-Throughput REST APIs with FastAPI
  1. Step 3: Containerization with Docker
  1. Step 4: MLOps Monitoring & Model Drift
  1. Production Readiness Checklist
  2. Interview Preparation Cheat Sheet
  3. Conclusion & Key Takeaways

1. Introduction: The Production ML Lifecycle#

Training a high-accuracy model in a notebook represents only a fraction of the enterprise machine learning lifecycle.

Architecture & Data Flow
 [ Data Ingestion & Validation ]
 |
 [ Feature Engineering ]
 |
 [ Model Training & CV ]
 |
 [ Pipeline Serialization ] <--- Joblib
 |
 [ REST API Model Serving ] <--- FastAPI
 |
 [ Containerization (Docker) ]
 |
 [ Cloud Orchestration & CI/CD ]
 |
 [ Monitoring (Data/Concept Drift) ]

MLOps (Machine Learning Operations) merges ML engineering, software engineering, and DevOps practices to deliver reliable, scalable, and automated model lifecycles.


2. Step 1: Pipeline Serialization (Saving & Loading)#

Serialization Best Practice: Never serialize only the raw estimator. Always serialize the entire Scikit-Learn Pipeline (including scalers, encoders, and imputers) to prevent training-serving skew.

🐍 Python
import joblib from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score # 1. Train Preprocessing + Estimator Pipeline data = load_breast_cancer() X, y = data.data, data.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) pipeline = Pipeline([ ('scaler', StandardScaler()), ('clf', RandomForestClassifier(n_estimators=100, random_state=42)) ]) pipeline.fit(X_train, y_train) acc = accuracy_score(y_test, pipeline.predict(X_test)) print(f"Model Trained Successfully. Test Accuracy: {acc:.4f}") # 2. Serialize Pipeline to Disk model_filename = 'production_model.pkl' joblib.dump(pipeline, model_filename) print(f"Serialized pipeline saved to: {model_filename}") # 3. Deserialize and Validate loaded_pipeline = joblib.load(model_filename) sample_input = X_test[:1] prediction = loaded_pipeline.predict(sample_input) print(f"Deserialized Inference Test: Class {prediction[0]}")

3. Step 2: Building High-Throughput REST APIs with FastAPI#

FastAPI provides an asynchronous, type-safe framework with automatic OpenAPI/Swagger documentation generation and request validation via Pydantic.

3.1 API Implementation Architecture#

  • Load the serialized pipeline once at startup into application memory.
  • Define strict input and output schemas via Pydantic BaseModel.
  • Handle batched or single inference requests with structured exception handling.

3.2 Production FastAPI Script (main.py)#

🐍 Python
""" File: main.py Execution: uvicorn main:app --host 0.0.0.0 --port 8000 --reload """ from fastapi import FastAPI, HTTPException, status from pydantic import BaseModel, Field from typing import List import joblib import numpy as np # Initialize FastAPI Application app = FastAPI( title="Breast Cancer Diagnostic API", description="Production REST API for real-time inference using serialized Scikit-Learn pipelines.", version="1.0.0" ) # Global Model Container MODEL_PATH = "production_model.pkl" try: model_pipeline = joblib.load(MODEL_PATH) print(f"Loaded production model from {MODEL_PATH}") except Exception as exc: print(f"Error loading model from {MODEL_PATH}: {exc}") model_pipeline = None # Pydantic Input Schema class InferenceInput(BaseModel): features: List[float] = Field( ..., description="30 numerical features matching breast cancer diagnostic measurements." ) class Config: schema_extra = { "example": { "features": [ 17.99, 10.38, 122.8, 1001.0, 0.1184, 0.2776, 0.3001, 0.1471, 0.2419, 0.07871, 0.5663, 0.9749, 0.2461, 0.1089, 0.181, 0.05667, 0.5435, 0.1587, 0.304, 0.07115, 24.99, 17.89, 158.7, 1956.0, 0.1238, 0.1866, 0.2416, 0.186, 0.275, 0.08902 ] } } # Pydantic Output Schema class InferenceOutput(BaseModel): prediction: int label: str probability: float @app.get("/health", status_code=status.HTTP_200_OK) def health_check(): if model_pipeline is None: raise HTTPException(status_code=503, detail="Model pipeline is unavailable.") return {"status": "healthy", "model_loaded": True} @app.post("/predict", response_model=InferenceOutput) async def predict(payload: InferenceInput): if model_pipeline is None: raise HTTPException(status_code=503, detail="Model not loaded.") if len(payload.features) != 30: raise HTTPException( status_code=422, detail=f"Expected exactly 30 features, received {len(payload.features)}." ) try: data_arr = np.array([payload.features]) pred_class = int(model_pipeline.predict(data_arr)[0]) pred_prob = float(model_pipeline.predict_proba(data_arr)[0][pred_class]) label_str = "Benign" if pred_class == 1 else "Malignant" return InferenceOutput( prediction=pred_class, label=label_str, probability=round(pred_prob, 4) ) except Exception as e: raise HTTPException(status_code=500, detail=str(e))

4. Step 3: Containerization with Docker#

Docker encapsulates application code, the Python runtime, system libraries, and serialized model files into an immutable image.

4.1 Writing the Dockerfile#

dockerfile
# Use lightweight multi-arch Python runtime FROM python:3.11-slim # Prevent Python from writing .pyc and enable unbuffered logging ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 WORKDIR /app # Install dependencies first for Docker caching COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application artifacts COPY main.py production_model.pkl ./ # Expose FastAPI listening port EXPOSE 8000 # Execute Uvicorn server CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

4.2 Build and Deployment Commands#

bash
# 1. Build Docker image docker build -t ml-inference-api:1.0.0 . # 2. Run container locally mapping port 8000 docker run -d -p 8000:8000 --name ml-api-container ml-inference-api:1.0.0 # 3. Test health check endpoint curl -X GET http://localhost:8000/health

5. Step 4: MLOps Monitoring & Model Drift#

5.1 Data Drift vs. Concept Drift#

DimensionData Drift (Covariate Shift)Concept Drift
DefinitionChange in the distribution of input features P(X)P(X)Change in the mapping relationship P(yX)P(y \mid X)
ExampleReal estate model encounters higher square footage distributions in a newly expanded city districtEconomic inflation alters the price per square foot across all property tiers
Detection MethodKolmogorov-Smirnov (KS) test, Population Stability Index (PSI), Wasserstein DistanceDegradation in live ground-truth metrics (MAE, RMSE, ROC-AUC) over time
RemediationRetrain on recent input data, adjust scaling pipelinesRe-architect feature representations, retrain model with recency weighting

5.2 Continuous Retraining Loops#

Architecture & Data Flow
[ Production Request Traffic ]
 |
 [ Live Logging Stream ]
 |
 [ Statistical Drift Monitor (PSI / KS) ]
 |
 Trigger Retraining Pipeline (Airflow / Kubeflow)
 |
 [ Automated Validation vs Baseline Model ]
 |
 [ Canary Deployment via Model Registry ]

6. Production Readiness Checklist#

  • Data Pipeline Encapsulation: Preprocessors, imputers, and scalers are enclosed inside a single serialized Pipeline object.
  • Schema Validation: Strict Pydantic models validate input dimensions, types, and range boundaries.
  • Health & Readiness Probes: Dedicated /health and /ready endpoints configured for Kubernetes/ECS container probes.
  • Observability: Structured JSON logging for request latencies, predictions, and input distributions.
  • Model Registry & Versioning: Models versioned via MLflow or Cloud Storage with rollback capabilities.
  • Automated CI/CD: Unit tests for inference contracts and integration tests run on every pull request.

7. Interview Preparation Cheat Sheet#

Q1: Why should you serialize an entire Pipeline instead of only the trained model object?#

Answer: Serializing only the estimator creates Training-Serving Skew. If raw inference data is fed to the model without the exact identical imputation statistics, scaling factors (μ,σ\mu, \sigma), or one-hot encodings learned during training, predictions will silently fail or produce degraded accuracy.

Q2: How do you detect Data Drift in production when ground-truth labels are delayed?#

Answer: Since ground-truth labels may take weeks or months to arrive (e.g., loan defaults), we monitor Input Feature Distributions (P(X)P(X)) using statistical distance metrics:

  • Population Stability Index (PSI): Quantifies distributional divergence (PSI >0.2> 0.2 indicates significant drift).
  • Kolmogorov-Smirnov (KS) Test: Non-parametric test comparing continuous feature cumulative distributions.
  • Evidently AI / Great Expectations: Production monitoring frameworks tracking feature summary metrics.

Q3: What is the purpose of multi-worker concurrency (--workers 4) in Uvicorn?#

Answer: CPython contains a Global Interpreter Lock (GIL) that constrains CPU-bound execution to one thread per process. Running Uvicorn with multiple worker processes spawns isolated Python instances across CPU cores behind an internal load balancer, scaling request throughput.

Q4: What is the difference between Canary and Blue/Green deployment for ML models?#

Answer:

  • Blue/Green: Two identical production environments exist. The new model (Green) is deployed and tested, and 100%100\% of traffic is instantly switched from Blue to Green.
  • Canary: Traffic is routed progressively (e.g., 5%25%100%5\% \rightarrow 25\% \rightarrow 100\%) to the new model candidate while monitoring error rates, latency, and drift metrics before full cutover.

8. Conclusion & Key Takeaways#

  1. Holistic Lifecycle: True ML engineering extends beyond model fitting to API wrapping, containerization, and post-deployment monitoring.
  2. Standardized Serialization: Always use Joblib with end-to-end pipelines to eliminate preprocessing discrepancies.
  3. Continuous Maintenance: Deploy automated statistical drift detectors to monitor live inference data and trigger scheduled retraining pipelines.
Knowledge Checkpoint

MLOps & Deployment Checkpoint

Q1.What is the difference between Data Drift and Concept Drift in production ML systems?
AData Drift occurs when input feature distributions change $P(X)$, whereas Concept Drift occurs when the underlying statistical relationship between features and target changes $P(Y|X)$.
BData Drift is for structured data, Concept Drift is for images.
CData Drift causes GPU crashes, Concept Drift causes CPU overheating.
DThere is no difference; they are synonymous.
Q2.What is a Model Registry in production MLOps (e.g. MLflow, Weights & Biases)?
AA centralized repository managing versioning, lineage, hyperparameters, evaluation metrics, and staging transitions (Staging -> Production -> Archived).
BA hardware server rack holding physical GPU chips.
CA DNS server routing HTTP requests.
DA Python package manager.
Q3.What deployment strategy routes a small fraction (e.g. 5%) of live traffic to a new model version before full rollout?
ACanary Deployment
BShadow Deployment
CBig Bang Deployment
DCold Standby
Track Your Learning

Finished studying this notebook?

Mark this guide as completed to update your course progress roadmap.