Bootcamp
A 60-minute ML bootcamp that takes one tabular dataset from raw CSV to a deployable, evaluated model. Run it on a real dataset in one sitting; the goal is to ship a baseline AND a real model that beats it.
A 60-minute end-to-end ML bootcamp
EXAMPLE
# ===== Objectives =====
# 1. Load + clean a tabular dataset
# 2. Ship a dumb baseline
# 3. Train a pipeline (preprocessing + model)
# 4. Evaluate with the right metric for the problem
# 5. Save the artifact for serving
# ===== 0-5 min: pick a target =====
# Use a small CSV (1k-100k rows). Choose a clear label: churn, fraud, price.
# Decide the metric NOW (not after seeing results):
# - balanced binary -> ROC-AUC
# - imbalanced binary -> PR-AUC + threshold-tuned precision/recall
# - multiclass -> macro-F1
# - regression -> MAE (outliers matter) or RMSE
# - ranking -> NDCG@k
# ===== 5-15 min: load + inspect =====
import pandas as pd, numpy as np
df = pd.read_csv('churn.csv',
dtype={'status': 'category'},
parse_dates=['signup_at'])
df.info(memory_usage='deep')
df.describe(include='all').T
df.isna().sum().sort_values(ascending=False).head(15)
df['churned'].value_counts(normalize=True) # imbalance check
# ===== 15-20 min: baseline =====
from sklearn.metrics import roc_auc_score, average_precision_score, classification_report
y = df.pop('churned')
X = df.copy()
# Always-majority baseline
maj = y.mode()[0]
print(classification_report(y, [maj] * len(y)))
print('baseline PR-AUC:', average_precision_score(y, [y.mean()] * len(y)))
# ===== 20-40 min: train/test split + pipeline =====
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
num = X.select_dtypes(include='number').columns.tolist()
cat = X.select_dtypes(exclude='number').columns.tolist()
pre = ColumnTransformer([
('num', Pipeline([('imp', SimpleImputer(strategy='median')),
('sc', StandardScaler())]), num),
('cat', Pipeline([('imp', SimpleImputer(strategy='most_frequent')),
('ohe', OneHotEncoder(handle_unknown='ignore'))]), cat),
])
X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, test_size=.2, random_state=42)
# Start with logistic regression — fast, interpretable, hard to overfit
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([('prep', pre), ('clf', LogisticRegression(max_iter=1000, class_weight='balanced'))])
# Cross-validation on training data
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
auc = cross_val_score(pipe, X_tr, y_tr, cv=cv, scoring='roc_auc', n_jobs=-1)
print(f'cv ROC-AUC {auc.mean():.3f} +/- {auc.std():.3f}')
pipe.fit(X_tr, y_tr)
print('test ROC-AUC:', roc_auc_score(y_te, pipe.predict_proba(X_te)[:, 1]))
# ===== 40-50 min: try a stronger model =====
from xgboost import XGBClassifier
pipe_xgb = Pipeline([
('prep', pre),
('clf', XGBClassifier(n_estimators=400, max_depth=6, learning_rate=0.05,
eval_metric='auc', n_jobs=-1)),
])
auc_xgb = cross_val_score(pipe_xgb, X_tr, y_tr, cv=cv, scoring='roc_auc', n_jobs=-1)
print(f'XGB cv ROC-AUC {auc_xgb.mean():.3f}')
pipe_xgb.fit(X_tr, y_tr)
print('XGB test ROC-AUC:', roc_auc_score(y_te, pipe_xgb.predict_proba(X_te)[:, 1]))
# Pick the model that BEATS the baseline AND beats logistic + XGB tradeoffs
# (XGB usually wins on tabular; logistic wins on interpretability + tiny data).
# ===== 50-60 min: serve =====
import joblib
joblib.dump(pipe_xgb, 'churn_model.joblib')
# Tiny prediction service
# from fastapi import FastAPI; from pydantic import BaseModel
# app = FastAPI()
# model = joblib.load('churn_model.joblib')
# class Req(BaseModel): pass
# @app.post('/predict')
# def predict(req: dict):
# X = pd.DataFrame([req]); return { 'churn_prob': float(model.predict_proba(X)[0, 1]) }
# ===== Bonus — leakage + drift checks =====
# - Pretty score? Look for time leakage (feature derived AFTER the label)
# - Plot feature distributions of train vs test vs prod (KS test on each numeric column)
# - Save a SAMPLE of training inputs as a JSONL fixture; serving must produce
# identical predictions given the same inputs
# ===== Bookmark =====
# - Hands-On Machine Learning (Geron) — workflow
# - Forecasting Principles & Practice (Hyndman) — time series
# - Designing ML Systems (Huyen) — production ML
Why it matters
Always ship a dumb baseline next to the real model. It is free, it sets the floor for "is the model worth shipping?", and it makes leakage obvious: when a five-minute LogisticRegression beats your sophisticated model, the feature pipeline almost certainly leaks information from the future.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…