The Credit Card Fraud dataset from Kaggle has 284,807 transactions. Only 492 are fraud โ€” that is 0.17%. This class imbalance is the first real challenge, and how you handle it defines the quality of everything that follows.

The Data

Features V1-V28 are PCA-anonymised (privacy protection). Only Amount, Time, and Class are raw. I added three engineered features: Hour (from Time), Log_Amount (log transform to reduce skew), and Amount_Bucket.

df['Hour'] = (df['Time'] / 3600).astype(int) % 24
df['Log_Amount'] = np.log1p(df['Amount'])
df['Amount_Bucket'] = pd.cut(
    df['Amount'],
    bins=[0, 10, 50, 200, 1000, 10000],
    labels=[0, 1, 2, 3, 4]
)

Handling 0.17% Fraud โ€” SMOTE

With 394 fraud cases in training and 49,606 legitimate, a naive model just predicts "not fraud" every time and gets 99.8% accuracy. That is useless. SMOTE (Synthetic Minority Oversampling Technique) creates synthetic fraud examples to balance the classes.

smote = SMOTE(random_state=42, k_neighbors=5)
X_train_resampled, y_train_resampled = smote.fit_resample(
    X_train_scaled, y_train
)
# Result: 50/50 class balance in training data
Important: Only apply SMOTE to training data. Never to test data โ€” that is data leakage and will give you falsely optimistic metrics.

The Ensemble โ€” Two Models

XGBoost (supervised) โ€” trained on labelled fraud/legit examples. Fast, accurate, handles imbalance with scale_pos_weight.

Isolation Forest (unsupervised) โ€” detects anomalies without needing labels. Catches novel fraud patterns XGBoost has never seen.

# Ensemble: 30% Isolation Forest + 70% XGBoost
ensemble_proba = 0.3 * iso_score + 0.7 * xgb_proba

Final result: AUC-ROC 0.98+. The ensemble consistently outperforms either model alone.

SHAP โ€” Making the Model Explainable

A fraud score of 87/100 is not enough. A risk analyst needs to know WHY. SHAP (SHapley Additive exPlanations) assigns a contribution value to each feature for each prediction.

explainer = shap.TreeExplainer(xgb_model)
shap_values = explainer.shap_values(transaction)
# Result: [V14: +0.43, V12: +0.31, Amount: -0.12, ...]
# Positive = pushed toward fraud, Negative = pushed toward safe

GPT-4o-mini Narration

The final layer translates ML output into plain English for a fraud analyst. The SHAP values and risk score go in as context, a risk report streams back.

prompt = f"""
Transaction: ${amount}, {hour}:00
Risk Score: {score}/100 ({risk_level})
Key signals: {shap_summary}

Write a 3-sentence risk report for a fraud analyst.
"""

This is the ML + LLM pipeline pattern โ€” ML for the prediction, LLM for the explanation. The combination is more trustworthy than either alone.

๐ŸŽ“ Learn this with me

I teach React, AI/ML engineering, and Node.js from beginner to advanced. Real POC projects, real code. Book a free 30-minute session.

๐Ÿ“… Book Free Session
โ† GitHub Actions Publishing to npm โ†’