Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,10 @@ paper/tmp/

# v5.0 XXL VM verification artifacts (large binary blobs, regenerable)
docs/superpowers/verification-runs/

# GNN fraud-detection showcase — large training artefacts kept out of git
data/ml/
models/ml/
*.pt
*.pkl
.gnn-venv/
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ df = ds.to_pandas()

All datasets are Apache 2.0, entirely synthetic, no PII.

## Showcases — interactive Spaces + trained models

| | URL | What |
|---|---|---|
| 🔗 **Accounting Network Explorer** | [`VynFi/accounting-network-explorer`](https://huggingface.co/spaces/VynFi/accounting-network-explorer) | Streamlit Space — interactive ISO 21378 Level-2 account-class graph from `je_network.parquet`. Filter by business process · fraud · anomaly · min-amount · top-N; click a class to drill into Level-3 sub-classes. |
| 🛡️ **Fraud-GNN Demo** | [`VynFi/fraud-gnn-demo`](https://huggingface.co/spaces/VynFi/fraud-gnn-demo) | Gradio Space — three tabs: edge fraud predictor (curated samples + manual entry), node anomaly explorer, live check on sampled edges with confusion matrix + ROC. |
| 📊 **Process Mining Demo** | [`VynFi/process-mining-demo`](https://huggingface.co/spaces/VynFi/process-mining-demo) | Streamlit Space — pm4py DFG, variants, statistics on `vynfi-supply-chain-ocel`. |
| 🤖 **JE Fraud GNN** | [`VynFi/je-fraud-gnn`](https://huggingface.co/VynFi/je-fraud-gnn) | Trained model: GraphSAGE 2-layer fraud classifier (test AUC **0.914**, F1 0.78) + attribute-reconstruction GAE node anomaly scorer (per-edge AUC **0.654** *unsupervised*). Bundle includes weights, preprocessor, and full metrics. |

The GNN training pipeline is reproducible from this repo:

```bash
pip install -r requirements-ml.txt
python -m scripts.ml.build_je_pyg_dataset --output data/ml/je_pyg_v1.pt --seed 20260509
python -m scripts.ml.train_je_fraud_gnn --epochs 60
python -m scripts.ml.train_je_anomaly_gae --epochs 80
python -m scripts.ml.package_for_hf
```

See [`notebooks/gnn_fraud_demo.ipynb`](notebooks/gnn_fraud_demo.ipynb) for the end-to-end walkthrough and the [model card](https://huggingface.co/VynFi/je-fraud-gnn) for honest framing of where graph methods help vs the LR baseline.

---

## Quick Start
Expand Down
156 changes: 156 additions & 0 deletions notebooks/gnn_fraud_demo.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# VynFi GNN fraud-detection showcase — reproducible end-to-end\n",
"\n",
"This notebook reproduces the [`VynFi/je-fraud-gnn`](https://huggingface.co/VynFi/je-fraud-gnn) model bundle from the published [`VynFi/vynfi-journal-entries-1m`](https://huggingface.co/datasets/VynFi/vynfi-journal-entries-1m) dataset (DataSynth v5.9.0, Method-A edges).\n",
"\n",
"The two tasks:\n",
"1. **Edge fraud classification** (supervised) — GraphSAGE encoder + edge head MLP.\n",
"2. **Edge / node anomaly scoring** (unsupervised) — attribute-reconstruction GAE.\n",
"\n",
"## Reproduce the published bundle\n",
"\n",
"From a clean clone of [`mivertowski/SyntheticData`](https://github.com/mivertowski/SyntheticData):\n",
"\n",
"```bash\n",
"pip install -r requirements-ml.txt\n",
"python -m scripts.ml.build_je_pyg_dataset --output data/ml/je_pyg_v1.pt --seed 20260509\n",
"python -m scripts.ml.train_je_fraud_gnn --epochs 60\n",
"python -m scripts.ml.train_je_anomaly_gae --epochs 80\n",
"python -m scripts.ml.package_for_hf\n",
"```\n",
"\n",
"Determinism: ChaCha8-derived seed `20260509`, sklearn `random_state=0`, torch `manual_seed`. Bit-for-bit reproducible on CPU.\n",
"\n",
"## Inspect the published model bundle\n",
"\n",
"Pull the model from the Hub and run a few sample predictions."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from huggingface_hub import snapshot_download\n",
"from scripts.ml.inference import load_bundle\n",
"\n",
"local_dir = snapshot_download(repo_id='VynFi/je-fraud-gnn')\n",
"bundle = load_bundle(local_dir)\n",
"print(f'nodes: {bundle.metadata[\"n_nodes\"]}, edges: {bundle.metadata[\"n_edges\"]:,}')\n",
"print(f'fraud test AUC : {bundle.metadata[\"fraud_metrics\"][\"gnn\"][\"test\"][\"auc_roc\"]:.4f}')\n",
"print(f'fraud test F1 : {bundle.metadata[\"fraud_metrics\"][\"gnn\"][\"test\"][\"f1\"]:.4f}')\n",
"print(f'GAE edge AUC : {bundle.metadata[\"anomaly_metrics\"][\"edge_results\"][\"auc_roc\"]:.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Single-edge fraud prediction\n",
"\n",
"Try a clear-fraud signature (round amount + weekend) vs a clean non-fraud."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"samples = [\n",
" # Clear fraud — $25K + Saturday\n",
" dict(from_account='1000', to_account='2000', amount=25_000.00,\n",
" business_process='P2P', posting_date='2024-08-10'),\n",
" # Clean — $7,432.89 + Tuesday\n",
" dict(from_account='1000', to_account='2000', amount=7_432.89,\n",
" business_process='P2P', posting_date='2024-03-12'),\n",
" # Borderline — $10K + Wednesday\n",
" dict(from_account='1000', to_account='2000', amount=10_000.00,\n",
" business_process='P2P', posting_date='2024-05-15'),\n",
"]\n",
"\n",
"probs = bundle.predict_fraud(\n",
" from_account=[s['from_account'] for s in samples],\n",
" to_account=[s['to_account'] for s in samples],\n",
" amount=[s['amount'] for s in samples],\n",
" business_process=[s['business_process'] for s in samples],\n",
" posting_date=[s['posting_date'] for s in samples],\n",
")\n",
"for s, p in zip(samples, probs):\n",
" print(f'{s[\"from_account\"]} -> {s[\"to_account\"]} ${s[\"amount\"]:>10,.2f} {s[\"posting_date\"]} -> fraud_p={p:.4f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Why the GraphSAGE lift over LR is small\n",
"\n",
"DataSynth's `fraud_bias` mechanism injects very strong *local* signals into edge attributes:\n",
"\n",
"| Bias | Probability | Effect |\n",
"|---|---|---|\n",
"| `weekend_bias` | 30 % | Posting date shifted to Sat/Sun |\n",
"| `round_dollar_bias` | 40 % | Amount rescaled to `$1K/$5K/$10K/$25K/$50K/$100K` |\n",
"| `off_hours_bias` | 35 % | `created_at` shifted to 22:00–05:59 |\n",
"| `post_close_bias` | 25 % | `is_post_close = true` |\n",
"\n",
"The first two land directly on edge attributes that we encode in the feature vector. As a result a vanilla LogisticRegression already gets to AUC 0.91 on the supervised edge task — leaving only +0.13 AUC pts of lift for the GraphSAGE encoder.\n",
"\n",
"Where the graph signal *does* show up:\n",
"\n",
"- **Unsupervised anomaly scoring** — the attribute-reconstruction GAE reaches edge AUC 0.65 *with no labels at train time*.\n",
"- **Per-process specificity** — A2R (rare, high-bias) reaches 0.95 supervised AUC vs P2P (more diffuse) at 0.93.\n",
"- **Cold-start / multi-hop fraud** — not directly testable on this dataset because the labelling is single-edge-bound, but graph methods are the natural fit.\n",
"\n",
"Where the LR baseline catches up:\n",
"\n",
"- Pure single-transaction fraud detection where the signal is fully in attributes."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Try the live demos\n",
"\n",
"- 🔗 **Accounting Network Explorer** — interactive ISO 21378 account-class graph: <https://huggingface.co/spaces/VynFi/accounting-network-explorer>\n",
"- 🛡️ **Fraud-GNN Demo** — Gradio inference Space (the model from this notebook): <https://huggingface.co/spaces/VynFi/fraud-gnn-demo>\n",
"\n",
"## Citation\n",
"\n",
"```bibtex\n",
"@misc{ivertowski2026datasynth,\n",
" author = {Ivertowski, Michael},\n",
" title = {{DataSynth}: Reference Knowledge Graphs for Enterprise\n",
" Audit Analytics through Synthetic Data Generation\n",
" with Provable Statistical Properties},\n",
" year = {2026},\n",
" month = {April},\n",
" howpublished = {SSRN Working Paper},\n",
" url = {https://ssrn.com/abstract=6538639}\n",
"}\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
12 changes: 12 additions & 0 deletions requirements-ml.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# GNN fraud-detection showcase — Python ML stack
# (Rust workspace remains the primary build; this is for the
# scripts/ml/ training pipeline + Gradio demo Space only.)

torch==2.5.1
torch-geometric==2.6.1
huggingface_hub==0.26.2
pandas==2.2.3
pyarrow==17.0.0
scikit-learn==1.5.2
numpy==2.1.3
matplotlib==3.9.2
Empty file added scripts/ml/__init__.py
Empty file.
Loading
Loading