Machine Learning

ML In Production And Capstone

Capstone Project: End-to-End ML System

A telecom company wants to predict customer churn — which customers are likely to cancel their subscription in the next 30 days — so retention offers can be tar

JrCodex·6 min read

Jr Codex ML Notes

Level: Advanced Prerequisites: All of Modules 1–9 (Chapters 1-3) Time to complete: ~50 minutes


Table of Contents

  1. The Scenario
  2. Phase 1 — Problem Definition
  3. Phase 2 — Data Preparation
  4. Phase 3 — Baseline & Model Comparison
  5. Phase 4 — Ensemble & Tuning
  6. Phase 5 — Final Evaluation
  7. Phase 6 — Building the Deployable Pipeline
  8. Phase 7 — Serving & Monitoring Plan
  9. Where to Go From Here

1. The Scenario

A telecom company wants to predict customer churn — which customers are likely to cancel their subscription in the next 30 days — so retention offers can be targeted proactively. This single project pulls in a technique from nearly every module in this curriculum.

customer_data.csv — columns
─────────────────────────────────────────
  customer_id, tenure_months, monthly_charges, total_charges,
  contract_type, payment_method, internet_service, tech_support,
  num_support_calls, churned (0/1 — the TARGET)
─────────────────────────────────────────

2. Phase 1 — Problem Definition

Applying Module 1, Chapter 3's workflow discipline before touching data:

Precise Problem Framing
─────────────────────────────────────────
  Paradigm (Module 1, Ch.2):        SUPERVISED — CLASSIFICATION
                                       (churned: yes/no)

  Performance measure:                 RECALL matters more than
                                          precision — missing a
                                          true churner (losing
                                          them silently) is more
                                          costly than a wasted
                                          retention offer to a
                                          happy customer (Module
                                          4, Ch.6's precision/
                                          recall trade-off)

  Success threshold:                      Catch at least 70% of
                                             actual churners
                                             (recall >= 0.70),
                                             while keeping
                                             precision reasonably
                                             usable (>= 0.40, so
                                             retention offers
                                             aren't wasted on too
                                             many false alarms)
─────────────────────────────────────────

3. Phase 2 — Data Preparation

Applying Module 2's complete toolkit:

import pandas as pd
from sklearn.model_selection import train_test_split
 
df = pd.read_csv("customer_data.csv")
 
# Split FIRST (Module 1, Ch.5) — before any preprocessing decisions
X = df.drop(["customer_id", "churned"], axis=1)
y = df["churned"]
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42      # stratify — Module 2, Ch.5's imbalance concern
)
 
print(f"Churn rate in training data: {y_train.mean():.2%}")      # e.g. 15% — meaningfully imbalanced
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
 
numeric_features = ["tenure_months", "monthly_charges", "total_charges", "num_support_calls"]
categorical_features = ["contract_type", "payment_method", "internet_service", "tech_support"]
 
preprocessor = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),      # Module 2, Ch.1
        ("scaler", StandardScaler()),                           # Module 2, Ch.3
    ]), numeric_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("encoder", OneHotEncoder(handle_unknown="ignore")),      # Module 2, Ch.2
    ]), categorical_features),
])

4. Phase 3 — Baseline & Model Comparison

Applying Module 7, Chapter 4's discipline — start simple, compare rigorously:

from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, StratifiedKFold
 
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)      # Module 7, Ch.1
 
candidates = {
    "Dummy (baseline)": DummyClassifier(strategy="stratified"),
    "Logistic Regression": LogisticRegression(class_weight="balanced"),      # Module 2, Ch.5's class weights
    "Decision Tree": DecisionTreeClassifier(class_weight="balanced", max_depth=8),
    "Random Forest": RandomForestClassifier(class_weight="balanced", n_estimators=100, random_state=42),
}
 
results = {}
for name, model in candidates.items():
    pipeline = Pipeline([("preprocessor", preprocessor), ("classifier", model)])
    scores = cross_val_score(pipeline, X_train, y_train, cv=cv, scoring="recall")      # our chosen metric
    results[name] = scores.mean()
    print(f"{name}: recall={scores.mean():.3f} (+/- {scores.std():.3f})")

5. Phase 4 — Ensemble & Tuning

Applying Module 5's ensembles and Module 7's systematic tuning to the most promising candidate:

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
import xgboost as xgb
 
xgb_pipeline = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", xgb.XGBClassifier(random_state=42, scale_pos_weight=5)),      # Module 2, Ch.5's imbalance fix
])
 
param_distributions = {
    "classifier__n_estimators": randint(50, 300),
    "classifier__max_depth": randint(3, 10),
    "classifier__learning_rate": uniform(0.01, 0.3),
    "classifier__subsample": uniform(0.6, 0.4),
}
 
search = RandomizedSearchCV(
    xgb_pipeline, param_distributions, n_iter=50, cv=cv,
    scoring="recall", random_state=42, n_jobs=-1,
)
search.fit(X_train, y_train)
 
print(f"Best parameters: {search.best_params_}")
print(f"Best cross-validation recall: {search.best_score_:.3f}")

6. Phase 5 — Final Evaluation

Applying Module 4, Chapter 6's full evaluation toolkit, and Module 7, Chapter 4's statistical comparison, on the untouched test set:

from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score
from scipy import stats
 
best_pipeline = search.best_estimator_
 
test_predictions = best_pipeline.predict(X_test)
test_probabilities = best_pipeline.predict_proba(X_test)[:, 1]
 
print(classification_report(y_test, test_predictions))
print(f"Confusion matrix:\n{confusion_matrix(y_test, test_predictions)}")
print(f"ROC-AUC: {roc_auc_score(y_test, test_probabilities):.3f}")
 
# Threshold tuning (Module 4, Ch.1) — recall matters more, adjust the cutoff
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_test, test_probabilities)
 
# Find the LOWEST threshold that still achieves our 70% recall requirement (Phase 1)
for p, r, t in zip(precisions, recalls, thresholds):
    if r >= 0.70:
        print(f"Threshold={t:.3f}: precision={p:.3f}, recall={r:.3f}")
        break

7. Phase 6 — Building the Deployable Pipeline

Applying Chapter 1's pipeline and serialization practices:

import joblib
from datetime import datetime
 
# The FULL pipeline — preprocessing AND the tuned model, as ONE object
final_pipeline = best_pipeline
 
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
joblib.dump(final_pipeline, f"churn_model_v{timestamp}.joblib")
 
metadata = {
    "version": timestamp,
    "test_recall": recalls[recalls >= 0.70][0] if any(recalls >= 0.70) else None,
    "chosen_threshold": t,
    "training_date": timestamp,
    "notes": "XGBoost, tuned via RandomizedSearchCV, threshold adjusted for 70% recall target",
}
print(metadata)

8. Phase 7 — Serving & Monitoring Plan

Applying Chapters 2-3's deployment and monitoring practices, as a concrete plan (not fully implemented here, but scoped):

Deployment Plan
─────────────────────────────────────────
  Serving style (Ch.2, Sec.2):    BATCH — churn risk scores
                                     computed NIGHTLY, fed into
                                     the retention team's
                                     dashboard each morning
                                     (no need for real-time
                                     serving for this use case)

  Monitoring (Ch.3):                 - Track INPUT feature drift
                                        (PSI, Ch.3, Sec.5) on
                                        tenure_months, monthly_charges
                                      - Track PREDICTION distribution
                                        (Ch.3, Sec.6) — % of
                                        customers flagged as
                                        at-risk, week over week
                                      - Track ACTUAL churn rate
                                        among flagged customers,
                                        once outcomes are known
                                        (concept drift check, Ch.3,
                                        Sec.4)

  Retraining trigger (Ch.3, Sec.7):     Retrain MONTHLY on a
                                           schedule, PLUS
                                           immediately if PSI
                                           exceeds 0.25 on any
                                           key feature
─────────────────────────────────────────

9. Where to Go From Here

Module → Technique Used in This Capstone
─────────────────────────────────────────
  Module 1 (Foundations)         → problem framing, bias-variance
                                      awareness, train/test discipline
  Module 2 (Data Preparation)       → imputation, encoding, scaling,
                                        class weights for imbalance
  Module 3-4 (Regression/                logistic regression, decision
  Classification)                           trees as comparison baselines
  Module 5 (Ensemble Learning)                XGBoost as the final,
                                                 tuned model
  Module 6 (Unsupervised)                        (not directly used here
                                                    — but would apply to
                                                    a follow-up customer
                                                    SEGMENTATION project)
  Module 7 (Model Selection)                        cross-validation,
                                                        randomized search,
                                                        statistical
                                                        comparison
  Module 8 (Reinforcement                              (not directly used
  Learning)                                              here — would
                                                           apply to
                                                           optimizing WHICH
                                                           retention OFFER
                                                           to show each
                                                           customer, a
                                                           genuine RL/
                                                           bandit problem)
  Module 9 (This module)                                    pipelines,
                                                                deployment,
                                                                monitoring
─────────────────────────────────────────

You've now completed a full, realistic ML project end-to-end, using nearly every technique across this entire curriculum. Chapter 5 closes out this module — and the whole Machine Learning curriculum — by mapping out exactly where to go next.


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index