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
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