Building Predictive Models
Pythonimport pandas as pd from sklearn.model_selection import train_test_split, StratifiedKFold from catboost import CatBoostClassifier import optuna from sklearn.metrics import roc_auc_score import shap # 1. Load & split df = pd.read_csv("clients.csv") X, y = df.drop(columns=["default"]), df["default"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.15, stratify=y, random_state=42 ) X_train, X_val, y_train, y_val = train_test_split( X_train, y_train, test_size=0.176, stratify=y_train, random_state=42 ) # → 70/15/15 overall # 2. Baseline model baseline = CatBoostClassifier(verbose=0, random_state=42) baseline.fit(X_train, y_train, cat_features=["category_col"]) baseline_auc = roc_auc_score(y_val, baseline.predict_proba(X_val)[:, 1]) print(f"Baseline AUC: {baseline_auc:.3f}") # 3. Hyperparameter tuning def objective(trial): params = { "depth": trial.suggest_int("depth", 4, 10), "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True), "l2_leaf_reg": trial.suggest_float("l2_leaf_reg", 1, 10), "iterations": trial.suggest_int("iterations", 200, 1000), } model = CatBoostClassifier(**params, verbose=0, random_state=42) model.fit(X_train, y_train, cat_features=["category_col"]) preds = model.predict_proba(X_val)[:, 1] return roc_auc_score(y_val, preds) study = optuna.create_study(direction="maximize") study.study_optimize = study.optimize(objective, n_trials=50) # 4. Final model + interpretation best_model = CatBoostClassifier(**study.best_params, verbose=0, random_state=42) best_model.fit(X_train, y_train, cat_features=["category_col"]) explainer = shap.TreeExplainer(best_model) shap_values = explainer.shap_values(X_test)
Progress:
- Step 1: EDA — distributions, missing values, class balance, correlations
- Step 2: Preprocessing — impute missing values, scale/normalize numeric features, encode categoricals
- Step 3: Split data 70/15/15 (train/val/test), stratify on target for classification
- Step 4: Train baseline model (no tuning) and pick primary metric
- Step 5: Hyperparameter search (Optuna/GridSearch) on train, score on validation
- Step 6: Cross-validate best config (stratified k-fold, k=5) to confirm stability
- Step 7: Evaluate on held-out test set once, final check for overfitting
- Step 8: Interpret via SHAP/LIME, analyze error cases
- Step 9: Log experiment (MLflow/W&B), package model for deployment
Metric selection by task:
- Imbalanced classification → AUC-ROC, PR-AUC, F1
- Balanced classification → Accuracy, F1
- Regression → RMSE, MAE, R²
- Business-critical → cost-weighted custom metric
Overfitting checks:
- Train metric >> validation metric → overfitting signal
- Learning curves: gap between train/val loss growing with model complexity
- Cross-validation std across folds too high → unstable model
- Use regularization (L2, early stopping, max_depth limits) when gap detected
Example 1: Input: Bank customer table, 50,000 rows (age, income, credit history, loan amount), binary target "default" Process: pandas preprocessing → OrdinalEncoder for categoricals → CatBoostClassifier with 5-fold stratified CV → Optuna tuning (50 trials) → SHAP feature importance Output: AUC-ROC = 0.87; top factors: credit history (0.34), debt-to-income ratio (0.22), number of late payments (0.18)
Example 2:
Input: E-commerce churn dataset, 10,000 users, heavy class imbalance (5% churn)
Process: SMOTE oversampling on train only → LightGBM with scale_pos_weight → stratified k-fold CV → threshold tuning on PR curve instead of default 0.5
Output: PR-AUC = 0.61 (vs 0.30 baseline), optimal threshold = 0.32 maximizes F1
- Always split off test set first and touch it only once, at the very end
- Fit preprocessing (scalers, encoders, imputers) on train only, then transform val/test — never fit on full dataset
- Use stratified splits/folds for classification, especially with imbalance
- Start with a simple baseline (logistic regression, mean predictor) before complex models — it defines the bar to beat
- Prefer CatBoost/LightGBM when categorical features are present (native handling beats one-hot encoding)
- Track every experiment (params, metrics, data version) in MLflow or W&B — don't rely on memory
- Report confidence intervals from cross-validation, not a single fold score
- Explain predictions with SHAP before shipping — business stakeholders need reasons, not just scores
- Data leakage: scaling/imputing/feature-selecting on the whole dataset before splitting — inflates validation scores unrealistically
- Tuning on test set: using test metric to pick hyperparameters turns test into a second validation set — scores become optimistic
- Ignoring class imbalance: using accuracy as the metric when target is 95/5 — a trivial "always predict majority" model scores 95%
- Overfitting via too many Optuna trials on small validation set: small val sets cause the search to fit noise — use CV instead for small data
- Skipping error analysis: deploying a model with good aggregate AUC but systematic failures on a specific subgroup
- No baseline comparison: complex model gains 1% AUC over logistic regression but costs 10x latency and interpretability