El scoring de crédito es el problema de machine learning más maduro y regulado de la banca. Un modelo que predice bien no basta: la normativa europea (EBA, AI Act) exige poder explicar cada decisión individual. Credit Risk Lab es mi respuesta completa a ese problema, construida en cinco fases que se pueden leer commit a commit.

Baseline honesto antes que modelo espectacular

Empecé con 30.000 clientes reales (UCI) y dos modelos: una regresión logística — el estándar interpretable del sector — y XGBoost. Las métricas son las de la casa: además del ROC AUC, el KS statistic (la métrica clásica de scorecards) y el Brier score para calibración.

Modelo ROC AUC KS
Regresión logística 0.708 0.364
XGBoost 0.780 0.429

Un KS por encima de 0.4 es territorio de scorecard fuerte. Pero el número no es lo interesante — lo interesante es lo que viene después.

Reason codes: explicar sin LLM, a propósito

Cada scoring se traduce a motivos legibles con SHAP values:

{
  "p_default": 0.87,
  "reason_codes": [
    "Estado de pago del último mes: 2 mes(es) de retraso",
    "Importe facturado el último mes con patrón de uso atípico"
  ]
}

Dos decisiones de diseño que defendería en cualquier comité:

  1. Los reason codes son deterministas — plantillas sobre los SHAP values, no texto generado por un LLM. Una explicación de denegación debe ser reproducible ante un regulador: mismo input, misma explicación, siempre.
  2. La dirección se calcula, no se asume. Mi primera versión decía "facturación elevada" a un cliente con 2.500 NT$ de facturación — un SHAP positivo puede venir tanto de facturación alta como de una cuenta casi inactiva. Ahora cada razón se contrasta con la mediana de la cartera.

Y un contrato de fairness convertido en test automatizado: los atributos protegidos o sensibles (sexo, edad, estado civil, educación) nunca aparecen como motivo de denegación. Hay un test en CI que falla si eso cambia.

Deep Learning, probado y descartado con datos

Entrené un MLP en PyTorch con el mismo split y las mismas métricas: AUC 0.771 contra el 0.780 de XGBoost, y bastante peor calibrado. Es el resultado que documenta la literatura — en datos tabulares de este tamaño, el gradient boosting sigue ganando. Elegir XGBoost no fue una preferencia: fue una medición.

La liga grande: Home Credit

La última fase migra el sistema al benchmark estándar del sector: 307.000 solicitudes reales en 7 tablas relacionales — buró externo, créditos previos, pago de cuotas, saldos mensuales. El trabajo duro es el feature engineering: ~80 features agregadas con lectura de negocio (ratios de capacidad de pago, tasa de aprobación histórica, retraso medio real en cuotas, utilización de tarjeta).

Resultado: AUC 0.783 con un modelo único e interpretable — los ganadores de la competición de Kaggle rondaron 0.80 con ensembles masivos. Y el sanity check más importante: las features dominantes (scores de buró, utilización de tarjeta, retrasos en cuotas) son exactamente las que un analista de riesgo predeciría. El modelo aprende lo que el negocio ya sabe — y lo cuantifica.

Cierre

Todo el sistema termina en una API FastAPI dockerizada con un endpoint /score que devuelve probabilidad, banda de riesgo y reason codes, más una demo interactiva en Streamlit. El código completo, commit a commit, está en github.com/JonDScode/credit-risk-lab.