Debk's picture
Upload README.md with huggingface_hub
8987c49 verified
|
Raw
History Blame Contribute Delete
7.9 kB
---
license: apache-2.0
tags:
- credit-risk
- default-prediction
- tabular
- msme
- lightgbm
library_name: joblib
---
# SAARTHI β€” MSME Default Prediction
Credit default-prediction models trained **entirely on public datasets** for IDBI Innovate 2026 (Track 04, Default Prediction Model). No bank data was used at any point.
The headline design choice: SAARTHI ships **pre-trained** and then fine-tunes on a lender's own book, rather than cold-starting on every upload.
## Methodology β€” why these numbers are trustworthy
Every metric below comes from a **strict three-way split**:
| Fold | Share | Used for |
|---|---|---|
| fit | 60% | training the boosters |
| calibrate | 15% | fitting the isotonic calibrator **only** |
| test | 25% | never seen by either β€” all reported metrics |
This matters: a common shortcut fits the probability calibrator and then measures calibration error on that *same* fold, which drives ECE artificially toward zero. Here the calibrator never sees the test fold, so the reported ECE is a real out-of-sample calibration estimate.
## Per-dataset specialist models
Full native feature set per corpus. Ensemble of LightGBM + XGBoost + CatBoost, isotonic-calibrated.
| Dataset | n | Default rate | Test AUC | PR-AUC | ECE | Published reference |
|---|---:|---:|---:|---:|---:|---|
| `sba` | 897,167 | 0.1756 | **0.9800** | 0.9126 | 0.0015 | ~0.95 (literature) |
| `lending_club` | 1,369,566 | 0.2124 | **0.7263** | 0.4104 | 0.0024 | ~0.70-0.73 (leakage-free) |
| `home_credit` | 307,511 | 0.0807 | **0.7632** | 0.2449 | 0.0011 | 0.805 (Kaggle winner) |
| `gmsc` | 150,000 | 0.0668 | **0.8522** | 0.3629 | 0.0041 | ~0.87 (Kaggle winner) |
| `taiwan` | 30,000 | 0.2212 | **0.7698** | 0.5243 | 0.0074 | ~0.78 (literature) |
| `german` | 1,000 | 0.3000 | **0.7668** | 0.5505 | 0.0680 | ~0.79 (literature) |
| `berka` | 682 | 0.1111 | **0.8558** | 0.5725 | 0.0295 | n/a |
| `amex` | 120,000 | 0.2598 | **0.9580** | 0.8858 | 0.0050 | ~0.96 AUC (winners; the 0.80 headline is a different metric) |
| hc2024 | β€” | β€” | _failed_ | β€” | β€” | ~0.86 (Kaggle winner) |
- `sba` β€” US Small Business Administration loans - closest public analogue to MSME lending
- `lending_club` β€” 2007-2018 consumer loans, charge-off label
- `home_credit` β€” Home Credit 2018 application + bureau aggregates
- `gmsc` β€” Give Me Some Credit, 90+ DPD within 2 years
- `taiwan` β€” Taiwan credit-card default, 6-month repayment panel
- `german` β€” UCI German Credit, 1000 rows
- `berka` β€” PKDD'99 Czech bank, pre-origination transaction aggregates
- `amex` β€” American Express monthly statement panel
- `hc2024` β€” Home Credit 2024 model-stability competition
## Pooled global model (ships with the app)
Trained across corpora in a shared 15-field canonical credit vocabulary so it can score any loan book that maps onto it.
- **Test AUC:** 0.8605
- **PR-AUC:** 0.5795
- **ECE:** 0.0018 Β· **Brier:** 0.0868
- **Test rows:** 270,421
- **Corpora pooled:** sba, lending_club, home_credit, gmsc, taiwan, german, berka
## Leave-one-dataset-out transfer β€” a negative result
Train on every corpus *except* one, then score the held-out corpus cold. This is the honest proxy for _"will it transfer to a book it has never seen?"_ β€” the question that actually matters for deployment.
**It does not transfer.** Most hold-outs land at or *below* chance, which means the pooled model is not merely uninformative on an unseen corpus, it is anti-predictive: the feature→outcome relationships invert across lending domains.
| Held-out corpus | n | Raw pooling | Rank-normalised |
|---|---:|---:|---:|
| `berka` | 682 | 0.4612 | 0.5155 |
| `german` | 1,000 | 0.4646 | 0.4022 |
| `gmsc` | 150,000 | 0.7786 | 0.7711 |
| `home_credit` | 300,000 | 0.4991 | 0.5785 |
| `lending_club` | 300,000 | 0.5228 | 0.5399 |
| `sba` | 300,000 | 0.4103 | 0.2574 |
| `taiwan` | 30,000 | 0.7038 | 0.6989 |
| **mean** | | **0.5486** | **0.5377** |
### Normalisation did NOT rescue it
The obvious hypothesis is scale mismatch β€” these corpora are denominated in USD, DM, NT$ and CZK, so a "loan amount" of 50,000 means different things in each. Converting every numeric feature to its within-corpus percentile rank tests that hypothesis directly, and **it fails**: mean transfer AUC moved 0.5486 β†’ 0.5377, i.e. no better. SBA in particular degrades from 0.4103 to 0.2574.
The mechanism is therefore **relational inversion, not scale**. SBA is small-business lending, where a longer term and a larger SBA-guaranteed principal typically indicate a better-vetted, collateral-backed loan β€” the opposite of the consumer-credit corpora that dominate the pooled training set. Rank-normalising preserves that inverted ordering perfectly, which is why it cannot help.
**Practical consequence:** a single pooled "foundation" model for credit risk is not supported by this evidence. Domain-matched training plus fine-tuning on the lender's own book is the defensible architecture, which is what SAARTHI does.
## Ablation β€” does transaction-as-language earn its place?
A CoLES-style contrastive encoder (GRU + InfoNCE over disjoint sub-sequence views) was pre-trained on unlabelled transaction streams, then its 256-d embedding was tested against the tabular features.
| Corpus | Tabular | Sequence only | Tabular + sequence | Lift |
|---|---:|---:|---:|---:|
| `berka` | 0.8575 | 0.8097 | 0.8842 | +0.0267 |
| `amex` | 0.9590 | 0.9156 | 0.9585 | -0.0006 |
**When the sequence encoder earns its place:** on Berka (16 tabular features, raw bank transactions) it adds **+0.027 AUC**. On Amex (941 hand-engineered aggregates over the *same* statement data) it adds nothing β€” the aggregates already capture what the encoder learns. The embedding substitutes for feature engineering rather than adding to it; it is most valuable exactly where a lender has raw transaction streams but few curated fields, which is the realistic MSME case.
Note also that on Amex the 256-d embedding **alone** reaches 0.9156 AUC with no hand-engineered features at all, versus 0.9590 for 941 engineered columns.
### Linear probe on frozen embeddings
Logistic regression on the frozen encoder output β€” measures how much default signal the *unsupervised* embedding captured on its own.
- `amex`: AUC 0.8717 (n=60,000)
- `berka`: AUC 0.7783 (n=682)
## Known limitations
- **Berka** aggregates only *pre-origination* transactions. Using the full history yields AUC 1.0000 because 71% of an account's transactions occur after the loan date and encode the repayment behaviour that defines the label. The leaked figure is not reported here.
- **Amex** and **Home Credit** are Kaggle-competition datasets; they are used for research validation. The shipped global model is trained on the permissively-licensed core corpora.
- The pooled model uses a deliberately small shared vocabulary, so its AUC is *lower* than the specialists by construction. Its purpose is transfer, not peak in-corpus accuracy.
- No Indian MSME data was available; GST-behavioural fields are simulated against the schema IDBI published, not learned from real filings.
## Files
| File | What it is |
|---|---|
| `global_canonical.joblib` | pooled global model + isotonic calibrator |
| `specialist_<corpus>.joblib` | per-corpus specialist ensembles |
| `coles.pt` | transaction-sequence encoder checkpoint |
| `metrics.json` | every number above, machine-readable |
| `ablation_sequence.json` | sequence-embedding ablation |
## Usage
```python
import joblib, pandas as pd
b = joblib.load('global_canonical.joblib')
X = df.reindex(columns=b['features']) # canonical vocabulary
raw = sum(m.predict_proba(X)[:, 1] for m in b['members'].values()) / len(b['members'])
pd_calibrated = b['calibrator'].predict(raw) # real probability of default
```
---
_Generated 2026-08-31 from metrics files β€” numbers are not hand-entered._