File size: 6,696 Bytes
71c85a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# Fraud Detection on PaySim β€” End-to-End ML Pipeline

A production-style machine learning pipeline that detects fraudulent mobile-money
transactions in the [PaySim synthetic dataset](https://www.kaggle.com/datasets/ealaxi/paysim1)
(~6.3M transactions). The project covers the full lifecycle: data validation,
feature engineering, model training with experiment tracking, and model serving
through both a REST API and a web UI.

> **Focus:** this repo is less about squeezing out the last decimal of accuracy and
> more about doing the *engineering* around a model correctly β€” no data leakage,
> reproducible training, tracked experiments, saved artifacts, and a real serving layer.

---

## Results

Evaluated on a **chronologically held-out test set** (later time steps the model never
saw during training), so the numbers reflect genuine generalization rather than
memorization.

| Metric | @ threshold 0.50 | @ tuned threshold (0.64) |
|---|---|---|
| **PR-AUC** | **0.90** | 0.90 |
| Precision | 0.83 | 0.95 |
| Recall | 0.84 | 0.79 |
| F1 | 0.83 | 0.87 |

*PR-AUC (area under the precision–recall curve) is the headline metric β€” accuracy is
misleading here because only ~0.2–4% of transactions are fraudulent.*

**Known failure mode:** the model catches `TRANSFER` fraud almost perfectly (>99%
confidence) but is weaker on `CASH_OUT` fraud. In PaySim, fraud is typically a
`TRANSFER` of stolen funds *followed by* a `CASH_OUT` of the same money β€” the `TRANSFER`
leg carries the obvious "account drained to zero" signature, while the `CASH_OUT` leg
looks much like a legitimate withdrawal. Improving `CASH_OUT` recall (e.g. features
that link a cash-out back to a preceding transfer) is the clearest next modelling step.

> ⚠️ **Honesty note:** the tuned threshold (0.64) was selected on the test set, so its
> precision is mildly optimistic. The @0.50 numbers are the trustworthy ones. The test
> window is also more fraud-dense than the training window, so real-world precision at a
> lower fraud base rate would be lower.

---

## Approach

**Avoiding data leakage** β€” the most common mistake on this dataset:
- **Chronological split** by transaction `step` (train on earlier steps, test on later),
  mimicking how a fraud model is used in production: trained on the past, scoring the future.
- The feature scaler is **fit on the training split only** and reused at inference.
- The class-imbalance weight (`pos_weight`) is computed from the **training split only**.

**Feature engineering** (in `src/data/preprocess.py`):
- `errorBalanceOrig` / `errorBalanceDest` β€” accounting inconsistencies between stated
  balances and transaction amounts (the highest-signal engineered features).
- `orig_balance_missing` / `dest_balance_missing` β€” flags for placeholder-zero balances.
- One-hot encoded transaction type; only `TRANSFER` and `CASH_OUT` are kept, as these
  are the only types where fraud occurs in PaySim.

**Model:** a small PyTorch MLP (`FraudMLP`) with BatchNorm and Dropout, trained with
`BCEWithLogitsLoss` weighted for the severe class imbalance.

**Threshold tuning:** instead of a naive 0.5 cutoff, the decision threshold is selected
from the precision–recall curve to balance precision and recall (F1-optimal).

---

## Tech stack

| Area | Tools |
|---|---|
| Data / features | pandas, scikit-learn |
| Data validation | Great Expectations |
| Modelling | PyTorch |
| Experiment tracking | MLflow |
| Serving | FastAPI, Gradio, Uvicorn, Pydantic |

---

## Project structure

```
fraud-paysim/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”œβ”€β”€ load_data.py       # load the raw CSV
β”‚   β”‚   └── preprocess.py      # cleaning + feature engineering
β”‚   β”œβ”€β”€ features/
β”‚   β”‚   └── build_features.py  # chronological split, scaling, DataLoaders
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   └── validate_data.py   # Great Expectations checks (gate before training)
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ model.py           # FraudMLP architecture
β”‚   β”‚   β”œβ”€β”€ train.py           # training loop + MLflow logging + test metrics
β”‚   β”‚   └── evaluate.py        # evaluation + threshold tuning helpers
β”‚   └── app/
β”‚       β”œβ”€β”€ inference.py       # shared predict(): raw transaction -> probability
β”‚       └── main.py            # FastAPI API (/predict) + Gradio UI (/ui)
β”œβ”€β”€ scripts/
β”‚   └── run_pipeline.py        # end-to-end: load -> validate -> train -> save
β”œβ”€β”€ requirements.txt
└── Readme.md
```

---

## Setup

**1. Get the data.** The dataset is not included in the repo (it's ~470 MB). Download
"Synthetic Financial Datasets For Fraud Detection" from
[Kaggle](https://www.kaggle.com/datasets/ealaxi/paysim1) and place the CSV at:

```
fraud-paysim/data/Synthetic_Financial_datasets_log.csv
```

**2. Create a virtual environment and install dependencies:**

```bash
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -r requirements.txt
```

---

## Usage

### Train the model

Runs the full pipeline (load β†’ validate β†’ feature-build β†’ train β†’ evaluate β†’ save artifacts):

```bash
python scripts/run_pipeline.py
```

This logs metrics to MLflow and writes three artifacts to `artifacts/`:
`model.pt` (weights), `scaler.joblib` (fitted scaler), and `metadata.json`
(input dimension + exact feature column order). These three together are what the
serving layer needs to reproduce training-time preprocessing exactly.

Inspect experiments with:

```bash
mlflow ui
```

### Serve the model

```bash
uvicorn src.app.main:app --reload
```

Then open:
- **http://localhost:8000/docs** β€” interactive REST API (Swagger UI) for the `/predict` endpoint
- **http://localhost:8000/ui** β€” Gradio web form for manual predictions

**Example request** (a known fraud pattern β€” full balance transferred out, origin drained to zero):

```json
{
  "step": 1,
  "type": "TRANSFER",
  "amount": 181.0,
  "oldbalanceOrg": 181.0,
  "newbalanceOrig": 0.0,
  "oldbalanceDest": 0.0,
  "newbalanceDest": 0.0
}
```

Response:

```json
{ "fraud_probability": 0.9952, "is_fraud": 1 }
```

---

## Design notes

- **Shared inference path.** Both the API and the Gradio UI call the same
  `predict()` function, so there is a single source of truth for how a raw transaction
  is turned into a prediction β€” no risk of training/serving skew between the two faces.
- **Raw in, prediction out.** The serving layer accepts *raw* transaction fields and
  reconstructs the engineered features internally using the same `preprocess_data`
  used in training. Clients never compute features themselves.

---