Back to projects

Credit card fraud detection

Most fraud models are judged on accuracy. A bank cares about money. This project picks the alert threshold that costs the least once you count both the analysts reviewing alerts and the fraud that slips through.

Cost vs. no model
−75.0%
Fraud caught
84.7%
AUPRC
0.863
Fraud rate
0.17%

The problem

The dataset holds about 285,000 European card transactions, and only 0.17% are fraud. A model that calls everything legitimate is 99.8% accurate and completely useless, so accuracy can't be the yardstick.

The real trade-off is between two costs:

Flag too much and review costs explode. Flag too little and fraud losses pile up. The goal is the cheapest point between them.

Approach

  1. Features. The 28 anonymized card features plus the amount, scaled with a robust scaler that isn't thrown off by a few huge transactions.
  2. An anomaly signal. Trained a small PyTorch autoencoder to reconstruct normal transactions. How badly it reconstructs a new one becomes an extra feature: fraud tends to look "unusual" in ways no single column shows.
  3. Handling the imbalance. Oversampled fraud with SMOTE during training only, never on the data used for evaluation.
  4. The model. A stacking ensemble: XGBoost and LightGBM make predictions, and a logistic regression learns how much to trust each one.
  5. The threshold. Instead of the default 0.5 cut-off, I swept 200 thresholds and priced each one with the cost function above, using a validation split carved out of the training data. The cheapest landed at 0.232. The test set was scored once, at that threshold.
  6. Explaining decisions. SHAP attributions show which features pushed each transaction toward "fraud", so an analyst can see why it was flagged before acting on it.
Line chart of total cost on the test set against decision threshold on a log scale. Cost falls from about $4,500 at very low thresholds to about $2,500 near 0.2, stays near $2,650 up to 0.9, then jumps as fraud is missed. The do-nothing cost is $10,645.
Total cost on the test set at each threshold. The threshold picked on validation (0.232) lands close to the test-set minimum, so the choice carries over to unseen data.

Results

On a held-out set of 56,962 transactions containing 98 frauds:

StrategyTotal cost
Do nothing (let all fraud through)$10,645
Manually review every transaction$284,810
Model at the threshold chosen on validation$2,666

The model caught 83 of the 98 frauds and raised 35 false alarms out of nearly 57,000 legitimate transactions. That's $7,979 saved on this sample, a 75.0% cut in fraud cost.

Precision-recall curve for the stacking ensemble, with an area under the curve of 0.863.
Precision against recall. AUPRC is the right summary when positives are this rare.

What I'd do next