Framework professionnel de validation de modèles de détection de fraude et de criminalité financière (FinCrime), conforme aux standards réglementaires SR 11-7, ECB TRIM Guide, AMLD6 et Bâle III/IV.
- Vue d'ensemble
- Contenu du dépôt
- Installation
- Le jeu de données
- Architecture du validateur
- Modules de validation
- Démarrage rapide
- Interprétation des résultats
- Référentiel réglementaire
- Dépendances
- FAQ
La validation de modèle est une exigence non négociable dans le secteur financier. Un modèle de détection de fraude avec un excellent AUC sur le jeu de test peut néanmoins échouer en production pour plusieurs raisons :
- Les distributions de scores dérivent dans le temps (PSI élevé)
- Les probabilités prédites ne sont pas calibrées (ECE > 0.05)
- Le modèle discrimine différemment selon les groupes protégés (biais)
- Les données d'entraînement ont été manipulées (Benford non-conforme)
- La performance s'effondre sous certains scénarios de stress (Bâle III)
Ce framework adresse tous ces risques de façon systématique et traçable.
┌─────────────────────────────────────────────────────────────────┐
│ FRAUD MODEL VALIDATOR v2.0 — FLUX │
│ │
│ Données ──► Modèle ──► FraudModelValidatorV2 │
│ (CSV/DF) (sklearn) │ │
│ ├── Performance (AUC, KS…) │
│ ├── Calibration (ECE, BSS) │
│ ├── Lift & Gains │
│ ├── Stabilité (PSI/CSI) │
│ ├── Benford's Law │
│ ├── Graph Anomaly │
│ ├── Fairness │
│ ├── Threshold Optim. │
│ ├── Stress Tests │
│ ├── Walk-Forward BT │
│ └── Rapport HTML ◄─────────┤
└─────────────────────────────────────────────────────────────────┘
fraud-model-validator/
│
├── Fraud_Model_Validator_v2.py # Validateur principal (2 000+ lignes)
├── fraud_dataset.csv # Dataset complet — 50 000 transactions
├── fraud_dataset.xlsx # Dataset Excel — 4 feuilles annotées
└── README.md # Ce fichier
| Fichier | Taille | Description |
|---|---|---|
Fraud_Model_Validator_v2.py |
~2 100 lignes | Classe FraudModelValidatorV2 + démo complète |
fraud_dataset.csv |
7,7 Mo | 50 000 transactions, 30 colonnes, 4 typologies de fraude |
fraud_dataset.xlsx |
1,6 Mo | Version Excel : Transactions (10k) + Stats + Dictionnaire + Guide |
- Python 3.9+
- pip
pip install pandas numpy scikit-learn matplotlib scipy# Explainabilité (SHAP values)
pip install shap
# Analyse du réseau de transactions (Graph Anomaly Detection)
pip install networkx
# Export Excel du dataset
pip install openpyxlpip install pandas numpy scikit-learn matplotlib scipy shap networkx openpyxlfrom Fraud_Model_Validator_v2 import FraudModelValidatorV2
validator = FraudModelValidatorV2("test")
print("✅ Installation OK")Le fichier fraud_dataset.csv contient 50 000 transactions bancaires simulées avec des propriétés statistiques calquées sur des données réelles de détection de fraude.
| Classe | Effectif | % |
|---|---|---|
| Légitimes | 48 000 | 96,0% |
| Fraudes | 2 000 | 4,0% |
| Total | 50 000 | 100% |
Note sur le déséquilibre de classes : Un taux de fraude de 4% est volontairement réaliste (les banques retail observent généralement 0,5% à 5%). Il implique d'utiliser des métriques adaptées au déséquilibre : AUCPR, F1, MCC — et non l'accuracy brute qui serait trompeuse.
Fraude la plus courante dans le commerce en ligne. Le fraudeur utilise des coordonnées bancaires volées pour des achats à distance.
Signature dans les données :
transaction_amount: élevé (lognormal µ=5.5)hour_of_day: nocturne (0h–6h, 22h–24h)auth_method:no_authdans 75% des casip_risk_score: élevé (Beta(7,2))is_international: 65% de transactions transfrontalières
Technique de blanchiment d'argent : fragmenter de grosses sommes en multiples petites transactions pour passer sous les seuils de déclaration réglementaires (1 000€ en France, 10 000$ aux États-Unis).
Signature dans les données :
transaction_amount: systématiquement entre 490€ et 999€ (juste sous le seuil de 1 000€)transaction_velocity_1h: très élevée (Poisson λ=12)receiver_id: concentré sur un petit nombre de destinatairesnb_cards_last_30d: 3 à 7 cartes différentes- Signal fort pour la Loi de Benford (les montants ne suivent pas la distribution naturelle)
Compromission de compte légitime : le fraudeur prend le contrôle d'un compte existant (phishing, credential stuffing, SIM swapping) et effectue des transactions.
Signature dans les données :
is_new_device: 1 dans presque 100% des cas (le fraudeur utilise son propre appareil)failed_auth_last_24h: très élevé (3 à 8 échecs avant de réussir)hour_of_day: tôt le matin (1h–7h)distance_from_home_km: très élevée (le fraudeur est ailleurs)device_risk_scoreetip_risk_score: très élevés
Fraude commise par un employé ou un complice interne. La plus difficile à détecter car le profil ressemble à un utilisateur légitime.
Signature dans les données :
hour_of_day: horaires de bureau (9h–18h) — se fond dans la masseday_of_week: jours ouvrés (lundi–vendredi)distance_from_home_km: faible (transaction depuis le bureau)is_new_device: faible (appareil connu)device_risk_scoreetip_risk_score: modérés — signal discret
| Variable | Type | Description | Signal Fraude |
|---|---|---|---|
transaction_id |
str | Identifiant unique (TXNxxxxxx) | — |
customer_id |
str | Identifiant client (CUSTxxxx) | — |
transaction_date |
datetime | Horodatage YYYY-MM-DD HH:MM | ★★★ |
sender_id |
int | ID émetteur pour analyse graphe [1–8000] | ★★★ |
receiver_id |
int | ID destinataire pour analyse graphe [1–5000] | ★★★★ |
transaction_amount |
float | Montant en € (lognormal) | ★★★★ |
avg_amount_30d |
float | Montant moyen des 30 derniers jours | ★★★ |
amount_vs_avg_ratio |
float | Ratio montant / moyenne 30j | ★★★★ |
transaction_velocity_1h |
int | Nb de transactions dans la dernière heure | ★★★★★ |
transaction_velocity_24h |
int | Nb de transactions dans les dernières 24h | ★★★★ |
merchant_name |
str | Nom du marchand (25 valeurs) | ★★ |
merchant_risk_score |
float | Score de risque marchand [0,1] | ★★★ |
customer_age_days |
int | Ancienneté du compte en jours | ★★★★ |
is_international |
int | Transaction internationale 0/1 | ★★★ |
is_new_device |
int | Appareil non enregistré 0/1 | ★★★★ |
is_new_merchant |
int | Marchand jamais utilisé 0/1 | ★★★ |
hour_of_day |
int | Heure de la transaction [0–23] | ★★★ |
day_of_week |
int | Jour de la semaine [0=Lun…6=Dim] | ★★ |
device_type |
str | mobile_android / mobile_ios / desktop / tablet | ★★ |
auth_method |
str | 3DS / PIN / biometric / signature / no_auth | ★★★★ |
device_risk_score |
float | Score de risque appareil [0,1] | ★★★★ |
ip_risk_score |
float | Score de risque adresse IP [0,1] | ★★★★ |
distance_from_home_km |
float | Distance depuis le domicile en km | ★★★ |
nb_cards_last_30d |
int | Nb de cartes différentes utilisées (30j) | ★★★ |
failed_auth_last_24h |
int | Nb d'échecs d'authentification (24h) | ★★★★★ |
chargeback_rate_6m |
float | Taux de rétrofacturation sur 6 mois [0,1] | ★★★★ |
country_origin |
str | Pays d'émission (ISO alpha-2) | ★★★ |
currency |
str | EUR / USD / GBP / CHF | ★★ |
fraud_type |
str | CNP / smurfing / account_takeover / insider / none | INFO |
is_fraud |
int | 🎯 Variable cible — 0=légitime, 1=fraude | TARGET |
| Feature | Moy. Légitime | Moy. Fraude | Ratio F/L |
|---|---|---|---|
transaction_amount (€) |
~65 | ~490 | ×7.5 |
transaction_velocity_1h |
~1.5 | ~8.0 | ×5.3 |
failed_auth_last_24h |
~0.1 | ~3.5 | ×35 |
device_risk_score |
~0.12 | ~0.68 | ×5.7 |
ip_risk_score |
~0.11 | ~0.72 | ×6.5 |
distance_from_home_km |
~15 | ~320 | ×21 |
chargeback_rate_6m |
~0.05 | ~0.45 | ×9.0 |
from Fraud_Model_Validator_v2 import FraudModelValidatorV2
validator = FraudModelValidatorV2(
model_name = "MonModèle_GBM_v1", # Nom affiché dans les rapports
cost_fn = 5000, # Coût d'un faux négatif (fraude manquée) en €
cost_fp = 100, # Coût d'un faux positif (fausse alarme) en €
currency = "€" # Symbole monétaire pour l'affichage
)Pourquoi paramétrer les coûts ? En détection de fraude, les coûts sont très asymétriques : manquer une fraude (FN) coûte souvent 10 à 100 fois plus cher qu'une fausse alerte (FP). Paramétrer ces coûts permet d'optimiser le seuil de décision de façon économiquement rationnelle plutôt qu'en maximisant naïvement le F1-score.
metrics = validator.calculate_performance_metrics(
y_true = y_test,
y_pred_proba = y_proba,
threshold = 0.5,
bootstrap = True, # Activer les intervalles de confiance
n_bootstrap = 1000 # Nb d'itérations (1000 recommandé en prod)
)Métriques calculées :
| Métrique | Description | Bon seuil (fraude) |
|---|---|---|
| AUC-ROC | Aire sous la courbe ROC. Mesure la capacité à séparer les classes. | ≥ 0.80 |
| GINI | 2 × AUC - 1. Version normalisée entre 0 et 1. |
≥ 0.60 |
| KS-Statistic | Écart maximum entre les CDF des deux classes. | ≥ 0.40 |
| H-measure | Alternative à l'AUC (Hand 2009) qui intègre l'asymétrie des coûts. | ≥ 0.30 |
| Average Precision | AUC de la courbe Precision-Recall, plus adaptée aux classes déséquilibrées. | ≥ 0.60 |
| MCC | Matthews Correlation Coefficient. Seule métrique fiable sur toutes les configurations de matrice de confusion. | ≥ 0.50 |
| Brier Score | Erreur quadratique moyenne sur les probabilités. 0 = parfait. | ≤ 0.05 |
Bootstrap CI 95% : pour chaque métrique clé, l'IC bootstrap donne une idée de la variance d'estimation et de la robustesse du modèle. Un IC large signale un modèle instable ou un jeu de test trop petit.
cal_results = validator.calibration_analysis(
y_true = y_test,
y_pred_proba = y_proba,
n_bins = 10
)Pourquoi la calibration est critique ?
Un modèle peut avoir un excellent AUC tout en étant mal calibré : si le modèle prédit P(fraude) = 0.8 mais que le taux réel de fraude dans ce groupe n'est que de 40%, les équipes risque prendront de mauvaises décisions sur les seuils d'alerte.
Métriques calculées :
| Métrique | Description | Interprétation |
|---|---|---|
| Brier Score | Erreur quadratique moyenne | < 0.05 = excellent |
| Brier Skill Score | BSS normalisé par la climatologie (taux de base). 1 = parfait, 0 = pas mieux qu'un modèle naïf. | > 0.50 |
| ECE | Expected Calibration Error : erreur de calibration moyenne pondérée par la taille des bins. | < 0.02 |
| MCE | Maximum Calibration Error : pire erreur de calibration observée dans un bin. | < 0.05 |
Reliability Diagram : graphique clé qui compare les probabilités prédites aux fréquences observées. Une droite parfaite à 45° indique une calibration parfaite.
lift_df = validator.lift_gain_analysis(
y_true = y_test,
y_pred_proba = y_proba,
deciles = 10
)Interprétation du Lift :
Le lift mesure combien de fois le modèle est meilleur qu'un ciblage aléatoire dans chaque décile. Un lift de 8.0 dans le décile 1 signifie que les 10% de transactions avec les scores les plus élevés contiennent 8 fois plus de fraudes que la moyenne.
Décile 1 (top 10% scores) → Lift = 8.0 → 80% des fraudes dans 10% du volume
Décile 2 → Lift = 4.0
Décile 3 → Lift = 2.0
...
Décile 10 (scores faibles) → Lift ≈ 0.1 (quasi aucune fraude)
Utilité opérationnelle : permet de dimensionner les équipes de revue manuelle — si les analystes ne peuvent traiter que 5% des transactions, combien de fraudes vont-ils capturer ?
# PSI temporel sur le score
stability_df = validator.stability_analysis(
df = df_with_scores,
score_col = 'score',
date_col = 'transaction_date',
period = 'M' # 'M' = mensuel, 'W' = hebdo, 'Q' = trimestriel
)
# CSI par feature
csi_df = validator.feature_stability_index(
df_ref = df_reference, # Période de référence (ex: train set)
df_cur = df_current, # Période courante (ex: production)
feature_cols = feature_cols,
bins = 10
)PSI — Population Stability Index :
Le PSI mesure si la distribution des scores a changé entre deux périodes. C'est l'indicateur standard de dérive de modèle.
| PSI | Interprétation | Action |
|---|---|---|
| < 0.10 | ✅ Stable — Pas d'action nécessaire | Surveillance normale |
| 0.10 – 0.25 | Analyse des causes | |
| > 0.25 | 🚨 Dérive critique — Retraining probable | Alerte réglementaire |
CSI — Characteristic Stability Index : le PSI appliqué à chaque feature individuellement. Permet d'identifier quelle variable est responsable de la dérive du score.
benford_results = validator.benford_law_analysis(
df = df,
amount_col = 'transaction_amount'
)Qu'est-ce que la loi de Benford ?
Dans tout jeu de données naturel (montants, populations, prix de marché…), le premier chiffre significatif suit une distribution logarithmique prévisible :
| Chiffre | Fréquence attendue |
|---|---|
| 1 | 30,1% |
| 2 | 17,6% |
| 3 | 12,5% |
| 4 | 9,7% |
| 5 | 7,9% |
| ... | ... |
| 9 | 4,6% |
Pourquoi c'est un signal de fraude ?
- Le smurfing crée des montants artificiels juste sous les seuils → sur-représentation des chiffres 4, 9 (
490€,990€) - Le round-tripping produit des montants ronds → sur-représentation du chiffre 1 (
1000€,10000€) - La falsification comptable génère des patterns non-naturels détectables statistiquement
Métriques :
- MAD (Mean Absolute Deviation) :
< 0.006= conforme,> 0.015= non-conforme - χ² p-value : si
p < 0.05, la déviation est statistiquement significative - Z-scores par chiffre : identifie quels chiffres sont sur ou sous-représentés
graph_df = validator.graph_anomaly_detection(
df = df.sample(10000), # Échantillon suffisant
source_col = 'sender_id',
target_col = 'receiver_id',
amount_col = 'transaction_amount',
fraud_col = 'is_fraud' # Optionnel, pour évaluation
)Pourquoi une approche graphe ?
Les modèles tabulaires traitent chaque transaction indépendamment. Ils ne voient pas que :
CUST_123envoie de l'argent à 50 receivers différents en 24h (hub de smurfing)- Un ensemble de comptes forme un cycle de transactions (round-tripping)
- Certains receivers concentrent une part anormale des transactions frauduleuses
Métriques calculées par nœud (entité) :
| Métrique | Description |
|---|---|
| Degree (in/out) | Nb de connexions entrantes/sortantes |
| PageRank | Influence systémique dans le réseau (comme Google pour les pages web) |
| Betweenness centrality | Le nœud est-il un "pont" structurel dans le réseau ? Les mules financières ont souvent une betweenness élevée. |
| Score d'anomalie composite | Moyenne des Z-scores standardisés de toutes les métriques |
Interprétation : les nœuds avec un score d'anomalie composite élevé méritent une investigation manuelle, indépendamment de leurs scores individuels de transaction.
imp_df = validator.permutation_feature_importance(
model = trained_model,
X = X_test,
y = y_test,
n_repeats = 10, # Stabilité de l'estimation
feature_names = feature_cols
)Pourquoi préférer la permutation importance à l'importance impurity-based (MDI) ?
L'importance par impureté des forêts et GBM est biaisée en faveur des features à haute cardinalité et peut masquer des features inutiles ou corrélées. La permutation importance mesure directement l'impact sur la métrique de validation (AUC-ROC) en permutant aléatoirement les valeurs d'une feature et en observant la chute de performance. C'est la mesure la plus fiable et la plus interprétable.
Sortie : DataFrame avec importance_mean ± 1.96σ (IC 95%) pour chaque feature, trié par importance décroissante. Une feature avec un IC incluant 0 n'apporte pas de valeur significative.
fairness_df, summary = validator.fairness_analysis(
df = df_eval,
score_col = 'score',
target_col = 'is_fraud',
protected_attr = 'is_international', # Attribut protégé
threshold = 0.5
)Métriques d'équité calculées :
| Métrique | Description | Seuil réglementaire |
|---|---|---|
| TPR gap (Equalized Odds) | Différence de taux de détection entre groupes | < 0.10 |
| FPR gap | Différence de taux de fausse alarme entre groupes | < 0.10 |
| Disparate Impact Ratio | Ratio des taux de classification positive. DIR < 0.80 = discrimination prima facie (règle des 4/5, EEOC). |
≥ 0.80 |
| Test KS inter-groupes | Les distributions de scores sont-elles significativement différentes ? | p > 0.05 |
| Test χ² | Indépendance entre l'attribut protégé et la décision | p > 0.05 |
best_threshold, results_df, optimal_thresholds = validator.optimize_threshold(
y_true = y_test,
y_pred_proba = y_proba,
metric = 'cost' # 'f1', 'mcc', 'cost', 'gmean', 'youden_j'
)Stratégies disponibles :
| Stratégie | Optimise | Quand l'utiliser |
|---|---|---|
cost |
Coût total FN×cost_fn + FP×cost_fp |
Recommandé en production : aligne la décision sur l'économie réelle |
f1 |
F1-Score | Équilibre précision/recall sans information de coût |
mcc |
Matthews Correlation Coefficient | Robuste au déséquilibre de classes |
gmean |
Moyenne géométrique TPR×TNR | Maximise simultanément sensibilité et spécificité |
youden_j |
Statistique J de Youden (TPR − FPR) | Équivalent au KS-statistic pour le choix de seuil |
Conseil : en conditions réelles, utiliser toujours metric='cost' avec des coûts reflétant l'économie du produit (coût d'une fraude manquée vs coût d'un faux positif en termes d'expérience client et de revue manuelle).
stress_df = validator.stress_test(
X = X_test,
y_true = y_test,
model_predict_fn = lambda X: model.predict_proba(X)[:,1],
perturbation_factors = [0.5, 0.8, 1.2, 1.5, 2.0, 3.0]
)Scénarios testés :
| Catégorie | Scénario | Exigence réglementaire |
|---|---|---|
| Choc Montants | Montants ×0.5 à ×3.0 | Bâle III — résistance aux pics de marché |
| Robustesse Bruit | Bruit gaussien σ=5%, 10%, 20%, 50% | SR 11-7 — robustesse aux données dégradées |
| Data Quality | Suppression de 1, 2, 3 features | Continuité d'activité — données manquantes |
| Concept Drift | Taux fraude ×1.5, ×2.0 | Bâle III — scénario adverse |
| Black Swan | Taux fraude ×5.0 | AMLD6 — scénario de crise systémique |
Lecture des résultats :
auc_drop_pct < 5%→ ✅ Modèle robusteauc_drop_pct 5–10%→⚠️ Plan de mitigation requisauc_drop_pct > 10%→ 🚨 Modèle fragile — retraining ou garde-fou nécessaire
wf_df = validator.walk_forward_backtest(
df = df,
model = GradientBoostingClassifier(n_estimators=150),
feature_cols = feature_cols,
target_col = 'is_fraud',
date_col = 'transaction_date',
n_splits = 5,
min_train_size = 0.5 # 50% minimum pour l'entraînement initial
)Pourquoi le walk-forward et pas la cross-validation standard ?
La CV standard avec shuffle=True permet au modèle de s'entraîner sur des données futures par rapport au jeu de test. C'est un look-ahead bias qui produit des estimations de performance trop optimistes.
CV Standard (incorrect pour les séries temporelles) :
Train: [Jan, Mar, May, Jul] Test: [Feb, Apr, Jun, Aug] ← Le futur prédit le passé!
Walk-Forward (correct) :
Fold 1: Train [Jan–Jun] → Test [Jul–Aug]
Fold 2: Train [Jan–Aug] → Test [Sep–Oct]
Fold 3: Train [Jan–Oct] → Test [Nov–Dec]
... ← Toujours le passé prédit le futur
Signal d'alerte : un écart-type d'AUC > 0.03 entre les folds indique une instabilité temporelle — le modèle pourrait dériver rapidement en production.
validator.generate_html_report(
output_path = 'rapport_validation.html'
)Génère un rapport HTML standalone (pas de dépendance externe, tout est encodé en base64) incluant :
- Score global pondéré sur 100
- Badges de conformité réglementaire (vert/orange/rouge)
- Toutes les figures en haute résolution
- Tableau de KPIs clés
- Matrice d'alertes automatiques
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from Fraud_Model_Validator_v2 import FraudModelValidatorV2
# ── 1. Chargement des données ──────────────────────────────────────────────
df = pd.read_csv('fraud_dataset.csv', parse_dates=['transaction_date'])
feature_cols = [
'transaction_amount', 'avg_amount_30d', 'amount_vs_avg_ratio',
'transaction_velocity_1h', 'transaction_velocity_24h',
'merchant_risk_score', 'customer_age_days', 'is_international',
'is_new_device', 'is_new_merchant', 'hour_of_day', 'day_of_week',
'device_risk_score', 'ip_risk_score', 'distance_from_home_km',
'nb_cards_last_30d', 'failed_auth_last_24h', 'chargeback_rate_6m'
]
X = df[feature_cols]
y = df['is_fraud']
# ── 2. Entraînement du modèle ──────────────────────────────────────────────
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
scaler = StandardScaler()
model = GradientBoostingClassifier(n_estimators=150, learning_rate=0.08,
max_depth=5, random_state=42)
model.fit(scaler.fit_transform(X_train), y_train)
y_proba = model.predict_proba(scaler.transform(X_test))[:, 1]
# ── 3. Validation ──────────────────────────────────────────────────────────
validator = FraudModelValidatorV2("GBM_v1", cost_fn=5000, cost_fp=100)
metrics = validator.calculate_performance_metrics(y_test.values, y_proba, bootstrap=True)
_ = validator.plot_advanced_curves(y_test.values, y_proba)
lift_df = validator.lift_gain_analysis(y_test.values, y_proba)
cal = validator.calibration_analysis(y_test.values, y_proba)
benford = validator.benford_law_analysis(df)
# ── 4. Rapport HTML ────────────────────────────────────────────────────────
validator.generate_html_report('rapport_validation.html')
print("✅ Rapport généré !")import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from Fraud_Model_Validator_v2 import FraudModelValidatorV2
df = pd.read_csv('fraud_dataset.csv', parse_dates=['transaction_date'])
feature_cols = [
'transaction_amount', 'avg_amount_30d', 'amount_vs_avg_ratio',
'transaction_velocity_1h', 'transaction_velocity_24h',
'merchant_risk_score', 'customer_age_days', 'is_international',
'is_new_device', 'is_new_merchant', 'hour_of_day', 'day_of_week',
'device_risk_score', 'ip_risk_score', 'distance_from_home_km',
'nb_cards_last_30d', 'failed_auth_last_24h', 'chargeback_rate_6m'
]
X, y = df[feature_cols], df['is_fraud']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
scaler = StandardScaler()
model = GradientBoostingClassifier(n_estimators=150, random_state=42)
model.fit(scaler.fit_transform(X_train), y_train)
y_proba = model.predict_proba(scaler.transform(X_test))[:, 1]
predict_fn = lambda X_in: model.predict_proba(scaler.transform(X_in))[:, 1]
validator = FraudModelValidatorV2("GBM_CompletDemo", cost_fn=5000, cost_fp=100)
# Performance + Bootstrap CI
metrics = validator.calculate_performance_metrics(y_test.values, y_proba, bootstrap=True)
# Courbes diagnostiques (ROC, PR, DET, KS)
validator.plot_advanced_curves(y_test.values, y_proba)
# Lift & Gains
lift_df = validator.lift_gain_analysis(y_test.values, y_proba)
# Calibration (ECE, MCE, BSS, Reliability Diagram)
cal = validator.calibration_analysis(y_test.values, y_proba)
# Stabilité temporelle (PSI + KS drift)
df_s = X_test.copy()
df_s['score'] = y_proba
df_s['transaction_date'] = df['transaction_date'].iloc[X_test.index].values
stability = validator.stability_analysis(df_s)
# CSI par feature
half = len(df) // 2
csi_df = validator.feature_stability_index(
df.iloc[:half][feature_cols], df.iloc[half:][feature_cols], feature_cols
)
# Benford's Law
benford = validator.benford_law_analysis(df, amount_col='transaction_amount')
# Graph Anomaly Detection
graph_df = validator.graph_anomaly_detection(
df.sample(8000, random_state=42),
source_col='sender_id', target_col='receiver_id',
amount_col='transaction_amount', fraud_col='is_fraud'
)
# Permutation Feature Importance
imp_df = validator.permutation_feature_importance(
model, scaler.transform(X_test), y_test.values,
feature_names=feature_cols, n_repeats=10
)
# Mahalanobis + Isolation Forest
anomaly_scores = validator.mahalanobis_outlier_detection(X_test, contamination=0.05)
# Fairness Analysis
df_eval = X_test.copy()
df_eval['score'], df_eval['is_fraud'] = y_proba, y_test.values
fairness_df, summary = validator.fairness_analysis(
df_eval, 'score', 'is_fraud', 'is_international'
)
# Threshold Optimization multi-critères
best_t, thresh_df, optimal = validator.optimize_threshold(
y_test.values, y_proba, metric='cost'
)
# Stress Testing réglementaire (Bâle III + AMLD6)
stress_df = validator.stress_test(X_test, y_test.values, predict_fn)
# Cross-Validation stratifiée
cv_df = validator.cross_validate(
X, y, GradientBoostingClassifier(n_estimators=80), n_splits=5
)
# Walk-Forward Backtesting
wf_df = validator.walk_forward_backtest(
df, GradientBoostingClassifier(n_estimators=80),
feature_cols, 'is_fraud', 'transaction_date', n_splits=5
)
# Rapport texte
validator.generate_validation_report(
metrics=metrics, stability_df=stability,
stress_df=stress_df, cv_df=cv_df,
output_path='rapport_validation.txt'
)
# Rapport HTML interactif
validator.generate_html_report('rapport_validation.html')Avec un GradientBoostingClassifier(n_estimators=150) et StandardScaler :
| Métrique | Valeur typique | Interprétation |
|---|---|---|
| AUC-ROC | 0.96 – 0.98 | Excellent — séparation quasi-parfaite |
| GINI | 0.92 – 0.96 | Excellent |
| KS-Statistic | 0.80 – 0.90 | Excellent — forte séparation des CDF |
| Average Precision | 0.85 – 0.95 | Très bon malgré le déséquilibre |
| F1-Score (@0.5) | 0.80 – 0.90 | Bon équilibre précision/recall |
| Brier Score | 0.01 – 0.03 | Bonne calibration |
| Lift (décile 1) | 6× – 9× | Cibler le top 10% capture ~70-80% des fraudes |
Ces performances élevées reflètent des features synthétiques fortement discriminantes. En conditions réelles, avec du bruit de mesure, des données manquantes et des fraudeurs adaptatifs, les AUC se situent plutôt entre 0.80 et 0.92.
| Critère | Seuil minimal | Seuil satisfaisant | Excellent |
|---|---|---|---|
| AUC-ROC | 0.70 | 0.80 | ≥ 0.90 |
| KS-Statistic | 0.30 | 0.40 | ≥ 0.60 |
| PSI (stabilité) | < 0.25 | < 0.10 | < 0.05 |
| ECE (calibration) | < 0.05 | < 0.02 | < 0.01 |
| Disparate Impact | ≥ 0.80 | ≥ 0.90 | ≈ 1.00 |
| AUC drop (stress) | < 15% | < 10% | < 5% |
Ce framework est conçu pour répondre aux exigences de :
Directive phare sur la gestion des risques liés aux modèles. Exige une validation indépendante couvrant : la solidité conceptuelle, les données d'entraînement, les performances out-of-sample, et la surveillance continue.
Guide de la BCE pour la revue des modèles internes bancaires. Exige notamment la validation de la stabilité, la backtesting, et la documentation des choix de modélisation.
Renforce les obligations de surveillance des transactions suspectes. Introduit la notion de responsabilité pénale pour les entités ne disposant pas de contrôles adéquats. Le scénario "Black Swan" (fraude ×5) dans le stress testing répond à cette exigence.
Accords internationaux imposant aux banques de maintenir des exigences en capital calculées à partir de modèles de risque validés et résistants aux stress scenarios.
| Package | Version testée | Usage |
|---|---|---|
pandas |
≥ 1.5 | Manipulation des données |
numpy |
≥ 1.23 | Calculs numériques |
scikit-learn |
≥ 1.2 | Modèles, métriques, CV |
matplotlib |
≥ 3.6 | Visualisations |
scipy |
≥ 1.9 | Tests statistiques (KS, chi², etc.) |
| Package | Version testée | Usage |
|---|---|---|
shap |
≥ 0.41 | SHAP values pour l'explainabilité |
networkx |
≥ 3.0 | Graph Anomaly Detection |
openpyxl |
≥ 3.1 | Génération du dataset Excel |
Q : Puis-je utiliser un modèle non-sklearn (XGBoost, LightGBM, CatBoost, PyTorch) ?
Oui. La plupart des méthodes n'ont besoin que de y_proba (un array numpy de probabilités). Pour les méthodes qui requièrent un objet modèle (permutation_feature_importance, cross_validate, walk_forward_backtest), votre modèle doit exposer les méthodes .fit(X, y) et .predict_proba(X), ce que font nativement XGBoost, LightGBM et CatBoost avec leur API sklearn.
Q : Le framework fonctionne-t-il pour la détection d'anomalies non-supervisée ?
Partiellement. Les métriques basées sur les labels (y_true) ne sont pas applicables. En revanche, les modules Benford, Graph Anomaly Detection, et Mahalanobis/Isolation Forest fonctionnent sans labels et peuvent être utilisés standalone.
Q : Comment adapter les seuils réglementaires à mon contexte ?
Les seuils PSI (0.10/0.25), ECE (0.05), disparate impact (0.80), etc. sont des valeurs standard de l'industrie. Ils peuvent être ajustés selon les exigences spécifiques de votre régulateur. Documentez tout ajustement dans votre rapport de validation.
Q : Le rapport HTML est-il partageable directement ?
Oui. Le HTML généré est entièrement autonome : toutes les images sont encodées en base64 et intégrées dans le fichier. Il peut être envoyé par email, partagé sur un drive, ou publié comme page web statique sans dépendance externe.
Q : Quelle taille de jeu de test est recommandée ?
Pour des IC bootstrap fiables, un minimum de 5 000 observations dans le jeu de test est recommandé, dont au moins 200 fraudes. En dessous, les IC seront très larges et les métriques peu fiables.
Q : Comment interpréter un H-measure très différent de l'AUC ?
L'AUC suppose implicitement une distribution uniforme des coûts de mauvaise classification. Le H-measure utilise une distribution Beta(2,2) sur les coûts, ce qui est plus réaliste. Si H-measure << AUC, votre modèle performe bien en moyenne mais mal aux seuils de décision où les coûts sont les plus asymétriques — c'est souvent le cas des modèles surajustés sur les cas faciles.
MIT License — libre d'utilisation, modification et distribution avec attribution.
FraudModelValidatorV2 — Validation rigoureuse pour des décisions de confiance.