Trabajo 2 · Redes neuronales y algoritmos bioinspirados · Universidad Nacional de Colombia, Facultad de Minas · 2026-02
Equipo: Tomás Gómez Cardona, Sebastián Ocampo Galvis, Juan Felipe Miranda Arciniegas, Ana Sofía Gómez Zapata y Juan José Baena García.
Entrada publicada en RPubs: https://rpubs.com/juan0911miranda/1472227
Repositorio: https://github.com/tomasgodev/RedesNeuronales_Trabajo2
Aplicación web: https://redesneuronalestrabajo2-y8uxecxjqz6frn9ex3ypta.streamlit.app/
Video promocional: https://canva.link/xrpw4q9vvf19v24
Videos de contribución individual: https://canva.link/9a2n3la84fpnyn0
Estimamos con perceptrones multicapa dos cantidades de créditos de consumo de Lending Club: la probabilidad de incumplimiento (PD) y la pérdida dado incumplimiento (LGD). Los comparamos con una regresión logística y una regresión lineal, con las mismas variables, la misma partición temporal y un presupuesto de búsqueda explícito.
Con 193,050 créditos originados entre 2012 y 2015, la red final de PD alcanza un AUC de 0.703 en prueba (media de 30 semillas), igual al de la regresión logística (0.703); la mejor configuración de una búsqueda de 31 llega a 0.704. Después de corregir el efecto del peso de clase, ambos modelos quedan bien calibrados. En LGD, la red tiene menor RMSE que la regresión lineal (0.081 frente a 0.093), pero esa ventaja viene de acertar el nivel de una LGD inflada por el recobro incompleto: la regresión lineal ordena mejor a los clientes.
Convertimos la PD en una scorecard en la que 600 puntos corresponden al cliente promedio y cada 20 puntos duplican las odds de pagar. La scorecard ordena el riesgo: el incumplimiento va de 49.8 % en el decil de menor puntaje a 5.6 % en el de mayor puntaje. Explicamos cuántos puntos aporta cada variable y estimamos una pérdida esperada de 18.7 % del monto en el periodo de prueba. Cada experimento se reproduce con un solo comando, y la aplicación web permite a cualquier persona calcular su puntaje y ver cómo cambiaría.
Quién lo usa. El área de riesgo de crédito de un prestamista que financia créditos de consumo originados en la plataforma Lending Club.
Para qué decisión. Decidir si se financia un crédito y estimar su pérdida esperada, \(E[L] = PD \cdot LGD \cdot EAD\), para provisionarlo. La probabilidad de incumplimiento (\(PD\)) también se expresa como un puntaje de scorecard para que el analista y el solicitante la puedan leer.
En qué momento. Después de que la plataforma
califica la solicitud y fija sus condiciones, y antes del desembolso. El
diccionario de variables define sub_grade como “LC
assigned loan subgrade”, y int_rate como “Interest
Rate on the loan”. Como ambas son entradas del modelo, el modelo
solo se puede aplicar cuando ya existen; no sirve en el instante en que
la persona empieza a llenar el formulario.
Con qué información. Solo con lo que se conoce en ese momento (Tabla 1).
Tabla 1. Información disponible al decidir y variables que usa el modelo.
| Grupo | Variables |
|---|---|
| Solicitud | loan_amnt, term, purpose |
| Solicitante | annual_inc, home_ownership,
verification_status, addr_state,
dti |
| Historial de crédito | delinq_2yrs, inq_last_6mths,
open_acc, revol_bal, revol_util,
total_acc, collections_12_mths_ex_med,
acc_now_delinq, tot_cur_bal |
| Condiciones asignadas por la plataforma | sub_grade, int_rate |
Fuente: elaboración propia a partir de
src/preprocesamiento.py y de
datasets/LCDataDictionary.xlsx.
Lo que no se puede usar. Todo lo que se observa
después del desembolso: pagos, saldos, recuperaciones y fechas de pago
(out_prncp, total_pymnt,
total_rec_prncp, total_rec_int,
total_rec_late_fee, recoveries,
collection_recovery_fee, last_pymnt_amnt,
last_pymnt_d, next_pymnt_d,
last_credit_pull_d, entre otras). Por ejemplo, el
diccionario define recoveries como “post charge off
gross recovery”: solo existe si el crédito ya se castigó. Estas
columnas se usan únicamente para construir las variables objetivo de la
Parte 2, nunca como entradas.
Qué implica para el modelamiento.
issue_d
(“The month which the loan was funded”), porque el modelo se
aplicará a créditos futuros.Limitación. El diccionario no dice en qué fecha se
midieron las variables de historial de crédito; por ejemplo,
tot_cur_bal es “Total current balance of all
accounts”. Suponemos que corresponden a la fecha de la solicitud.
Hay un indicio a favor. En los suplementos de prospecto que Lending Club
presenta ante la SEC por cada crédito listado, variables como las
cuentas en mora, las líneas abiertas y las moras de los últimos dos años
aparecen como lo que “a credit bureau reported” sobre el
solicitante en una fecha cercana a la emisión (LendingClub Corporation,
2020). Ese documento es de 2020, no del periodo de nuestros datos, así
que lo tomamos como un indicio y no como una confirmación.
Usamos el conjunto Credit Risk Analysis de Kaggle (ranadeep,
s. f.), versión 3, archivo loan/loan.csv: 887,379
registros y 74 variables, de créditos originados entre
junio de 2007 y diciembre de 2015
(issue_d). Lo descargamos el 25 de octubre de 2026, en su
versión 3, con kagglehub; el mensaje de descarga quedó
guardado en el notebook notebooks/limpieza.ipynb del commit
df4b14c. Para verificar la reproducibilidad lo descargamos
de nuevo el 27 de octubre de 2026, también en la versión 3. Con esa
descarga, python -m src.preprocesamiento produce un archivo
idéntico a datasets/creditos_procesados.csv.gz. Los
significados de las variables se citan del diccionario incluido en el
conjunto (datasets/LCDataDictionary.xlsx).
Un detalle del diccionario: no trae la variable
verification_status, pero sí is_inc_v,
“Indicates if income was verified by LC, not verified, or if the
income source was verified”. Sus tres valores coinciden con los de
verification_status en el archivo
(Not Verified, Source Verified,
Verified), así que tomamos esa definición.
Codificamos loan_status como lo indica el enunciado
(Tabla 2). De los 887,379 créditos, 268,530 tienen un
estado codificado como 0 o 1; los demás (NA) se excluyen. De ellos,
58,819 son incumplimientos.
Tabla 2. Codificación de
loan_status.
| Categoría original | Codificación | Créditos |
|---|---|---|
| Fully Paid | 0 | 207,723 |
| Does not meet the credit policy. Status:Fully Paid | 0 | 1,988 |
| Charged Off | 1 | 45,248 |
| Default | 1 | 1,219 |
| Late (31-120 days) | 1 | 11,591 |
| Does not meet the credit policy. Status:Charged Off | 1 | 761 |
| Current | NA (excluido) | 601,779 |
| Issued | NA (excluido) | 8,460 |
| In Grace Period | NA (excluido) | 6,253 |
| Late (16-30 days) | NA (excluido) | 2,357 |
Fuente: codificación del enunciado; conteos de
python scripts/parte1_descriptivo.py
(resultados/parte1/descriptivo_loan_status.csv).
El 68 % de los créditos del archivo está en estado
Current y queda fuera del entrenamiento. La Sección 3.1
muestra que esa exclusión no es pareja en el tiempo.
El procesamiento está en notebooks/procesamiento.ipynb y
la versión que usan todos los modelos, en
src/preprocesamiento.py. Se regenera con:
python -m src.preprocesamiento
id, member_id, policy_code),
texto libre o de alta cardinalidad (url,
emp_title, desc, title,
zip_code, earliest_cr_line) y las variables
posteriores al desembolso de la Sección 1.mths_since_last_delinq, con 55 %.
Después se eliminan las filas con nulos en
total_rev_hi_lim, tot_cur_bal,
tot_coll_amt, emp_length,
revol_util y collections_12_mths_ex_med. Las
tres primeras no existen para las cosechas anteriores a agosto de 2012,
por eso los datos procesados empiezan en 2012-08.pub_rec (\(F = 0.005\),
\(p = 0.94\)) y
tot_coll_amt (\(F =
0.33\), \(p = 0.57\)). El resto
tiene \(p < 0.001\).loan_amnt tiene correlación de 1.00 con
funded_amnt y funded_amnt_inv, y de 0.96 con
installment; revol_bal tiene 0.83 con
total_rev_hi_lim. Se conservan loan_amnt y
revol_bal (Figura 1).emp_length (0.03),
pymnt_plan, initial_list_status y
application_type (0.00 las tres). Se conservan
sub_grade (0.26), term (0.16),
verification_status (0.08), home_ownership
(0.07), purpose (0.06) y addr_state (0.06).
grade se descarta porque su V con sub_grade es
1.00 (Figura 2).sub_grade queda ordinal
(A1 = 0, …, G5 = 34) y verification_status también (0 a 2).
term, home_ownership, purpose y
addr_state pasan a variables indicadoras, eliminando una
categoría de cada una.El resultado son 193,050 créditos y 82 variables de entrada, originados entre agosto de 2012 y diciembre de 2015, con una tasa de incumplimiento de 23.3 %.
Figura 1. Correlación de Pearson entre las variables numéricas.
Fuente: elaboración propia,
notebooks/procesamiento.ipynb (celda 12).
Figura 2. V de Cramér entre las variables categóricas y el incumplimiento.
Fuente: elaboración propia,
notebooks/procesamiento.ipynb (celda 14).
Limitación y verificación. La selección por ANOVA, correlación y V de Cramér se hizo con todos los créditos, incluidos los de validación y prueba. Es una fuga menor: las variables se escogieron mirando también el periodo con el que después se evalúa.
Para medirla, repetimos las mismas pruebas solo con los créditos de
entrenamiento
(python scripts/parte1_seleccion_entrenamiento.py). Con
todos los datos, el script reproduce exactamente los valores del
notebook. Solo con entrenamiento cambia una variable:
collections_12_mths_ex_med pasa de \(p < 0.001\) a \(p = 0.24\) en el ANOVA, y con la misma
regla se habría descartado. Todas las demás decisiones se mantienen.
El efecto es nulo. En la importancia por permutación (Sección 3.7), desordenar esa variable no baja el AUC de ninguno de los dos modelos; en la red queda de última entre 20 grupos. Sin ella, el AUC en prueba de la regresión logística pasa de 0.70330 a 0.70332. Por eso conservamos los datos procesados tal como están.
Antes de entrenar los modelos, el equipo formuló cuatro hipótesis
sobre qué hace más riesgosa a una persona; están en el borrador
archivo/docs/blog_post_tecnico_(Borrador).md. Las
contrastamos solo con el periodo de entrenamiento, para no mirar los
créditos con los que después se evalúa (Tabla 3 y Figura 3). La tasa
promedio de incumplimiento en ese periodo es 21.7 %.
Tabla 3. Hipótesis y tasa de incumplimiento en el periodo de entrenamiento.
| Hipótesis | Menor riesgo | Mayor riesgo | ¿Se confirma? |
|---|---|---|---|
H1. A mayor dti, más incumplimiento |
1.er quintil (\(\le 10.07\)): 15.8 % | 5.º quintil (\(> 24.02\)): 28.8 % | Sí: sube en cada quintil |
H2. A mayor int_rate y peor sub_grade, más
incumplimiento |
1.er quintil de tasa (\(\le 10.99\) %): 9.5 %. Letra A: 7.4 % | 5.º quintil de tasa (\(> 18.25\) %): 36.3 %. Letra G: 47.1 % | Sí: sube en cada quintil y en cada letra |
| H3. A 60 meses se incumple más que a 36 | 36 meses: 18.0 % | 60 meses: 35.2 % | Sí |
| H4. Más años de empleo, menos incumplimiento | — | — | No: la tasa va de 20.8 % a 23.5 % sin tendencia; menos de un año: 22.5 %; diez o más: 21.2 % |
Fuente: elaboración propia,
python scripts/parte1_descriptivo.py
(resultados/parte1/descriptivo_*.csv).
Figura 3. Tasa de incumplimiento en entrenamiento según cada hipótesis. La línea punteada es la tasa promedio.
Fuente: elaboración propia,
python scripts/parte1_descriptivo.py.
Las tres primeras se confirman con claridad. La cuarta no: la antigüedad laboral casi no cambia la tasa, lo que coincide con su V de Cramér de 0.03 y con que se haya descartado en la selección (Sección 2.3). Que una variable separe por sí sola no dice cuánto aporta cuando ya están las demás; eso se analiza con los modelos en la Sección 3.7.
Ordenamos los créditos por fecha de originación
(issue_d) y cortamos en fechas fijas, definidas en
src/preprocesamiento.py. Validación empieza en mayo de 2014
y prueba en noviembre de 2014 (Tabla 4). Así se simula el uso real: el
modelo se entrena con créditos pasados y se evalúa con créditos
posteriores. La Parte 2 usa exactamente los mismos cortes.
Tabla 4. Partición temporal de la Parte 1.
| Conjunto | Desde | Hasta | Créditos | % del total | Tasa de incumplimiento |
|---|---|---|---|---|---|
| Entrenamiento | 2012-08 | 2014-04 | 118,372 | 61.3 % | 21.7 % |
| Validación | 2014-05 | 2014-10 | 38,300 | 19.8 % | 26.4 % |
| Prueba | 2014-11 | 2015-12 | 36,378 | 18.8 % | 25.2 % |
Fuente: elaboración propia,
notebooks/clasificacion.ipynb (celdas 3 y 4).
La tasa de incumplimiento sube en validación y prueba, y la Figura 4
muestra por qué. La proporción de créditos con un estado definido pasa
de más de 92 % en las cosechas de 2012 a 41 % a comienzos de 2014, 23 %
a finales de 2014 y 1.6 % en el último trimestre de 2015. Los demás
siguen Current y se excluyen.
En una cosecha reciente, los créditos que ya tienen estado final son los que terminaron pronto: los que incumplieron en los primeros meses o los que se pagaron por adelantado. Eso explicaría que la tasa de incumplimiento de los incluidos suba de 16 % en 2012 a 28 % a comienzos de 2015 y vuelva a caer a 9.7 % en el último trimestre de 2015.
La partición temporal es coherente con el caso de uso, pero el conjunto de prueba no representa a todos los créditos otorgados en su periodo, sino a los que ya terminaron. Hay que tenerlo presente al leer todas las métricas de prueba.
Figura 4. Proporción de créditos excluidos (estado NA) y tasa de incumplimiento de los incluidos, por trimestre de originación.
Fuente: elaboración propia,
python scripts/parte1_descriptivo.py.
Todas las variables se estandarizan con StandardScaler,
ajustado solo con entrenamiento.
Regresión logística de scikit-learn con penalización \(L_2\) (\(C =
1\), valor por defecto), class_weight='balanced' y
max_iter=5000. Tiene 83 parámetros (82
coeficientes y el intercepto) y se ajusta en 0.3 s. La configuración es
la de notebooks/regresion_logistica.ipynb.
scripts/parte1_evaluacion.py la reajusta igual, porque es
determinista, la guarda en
weights/regresion_logistica_pd.joblib y obtiene el mismo
AUC de validación del notebook (0.7100).
class_weight='balanced' le da a cada clase un peso
inversamente proporcional a su frecuencia. La razón entre el peso de los
incumplimientos y el de los buenos es \(N_0/N_1 = 3.61\), el mismo
pos_weight de la red. Por eso sus probabilidades se
corrigen igual que las de la red, con \(p =
\sigma(z - \ln 3.61)\) (Sección 7.3).
La red de notebooks/clasificacion.ipynb es un perceptrón
multicapa en PyTorch. Cada capa oculta aplica una transformación lineal,
normalización por lotes (batch normalization), ReLU y
dropout:
\[82 \;\to\; 64 \;\to\; 32 \;\to\; 16 \;\to\; 1\]
El dropout es de 0.2 en las dos primeras capas ocultas; la tercera no tiene. La salida es un logit, y la probabilidad se obtiene con la sigmoide. Tiene 8,161 parámetros entrenables. La llamamos la red base.
BCEWithLogitsLoss) con pos_weight \(= N_0/N_1 = 3.61\), calculado con
entrenamiento. Como el peso desplaza el logit, las probabilidades se
corrigen con \(p = \sigma(z - \ln
3.61)\) (derivación en la Sección 7.3).weight_decay) de \(10^{-4}\) y lotes de 512. La tasa se reduce
a la mitad tras 3 épocas sin mejorar el AUC de validación
(ReduceLROnPlateau).La Tabla 5 compara la regresión logística con la red base guardada por el notebook, en validación y prueba.
Tabla 5. Discriminación y calibración en la Parte 1.
| Modelo | Conjunto | AUC | Gini | KS | Brier | Brier de referencia |
|---|---|---|---|---|---|---|
| Regresión logística | Validación | 0.7100 | 0.4200 | 0.3086 | 0.1761 | 0.1944 |
| Regresión logística | Prueba | 0.7033 | 0.4066 | 0.3057 | 0.1722 | 0.1886 |
| Red neuronal (base) | Validación | 0.7131 | 0.4262 | 0.3122 | 0.1744 | 0.1944 |
| Red neuronal (base) | Prueba | 0.7033 | 0.4066 | 0.2997 | 0.1709 | 0.1886 |
| Logística sin corregir | Validación | 0.7100 | 0.4200 | 0.3086 | 0.2170 | 0.1944 |
| Logística sin corregir | Prueba | 0.7033 | 0.4066 | 0.3057 | 0.2209 | 0.1886 |
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py
(resultados/parte1/metricas_pd.csv). El Brier de referencia
es el de un modelo que predice siempre la tasa de incumplimiento del
conjunto.
Figura 5. Curvas de calibración por deciles de probabilidad predicha.
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py.
Las curvas ROC de los dos modelos en prueba prácticamente se superponen (Figura 6). La Figura 7 muestra el estadístico KS de la red: la distancia máxima entre las distribuciones acumuladas de la probabilidad predicha de buenos y malos pagadores.
Figura 6. Curvas ROC en prueba.
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py.
Figura 7. Estadístico KS de la red neuronal en prueba.
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py.
La red base se fijó a mano, así que después hicimos una búsqueda aleatoria (Bergstra y Bengio, 2012). Usamos el mismo espacio de la Parte 2 (Tabla 16), sin la opción de pérdida, porque aquí siempre es la entropía cruzada ponderada. Fueron 30 configuraciones al azar con semilla 56, más la red base como candidata. Cada una se entrenó con el mismo procedimiento, y el criterio de selección fue el AUC de validación.
Las 31 configuraciones tardaron 9.6 minutos y quedaron entre 0.7072 y 0.7148 de AUC de validación. La Tabla 6 muestra las cinco mejores.
Tabla 6. Las cinco mejores configuraciones de la búsqueda (AUC de validación, semilla 56).
| Capas ocultas | Activación | Dropout | weight_decay |
Tasa de aprendizaje | Lote | Parámetros | AUC validación |
|---|---|---|---|---|---|---|---|
| [32] | ReLU | 0.3 | \(10^{-4}\) | \(10^{-3}\) | 512 | 2,753 | 0.7148 |
| [64, 32] | ReLU | 0.3 | 0 | \(10^{-3}\) | 256 | 7,617 | 0.7142 |
| [64] | Leaky ReLU | 0.2 | 0 | \(3\cdot10^{-4}\) | 256 | 5,505 | 0.7141 |
| [64, 32] | Leaky ReLU | 0.3 | \(10^{-3}\) | \(3\cdot10^{-4}\) | 1024 | 7,617 | 0.7136 |
| [64, 32, 16] (red base) | ReLU | 0.2 (0 en la última) | \(10^{-4}\) | \(10^{-3}\) | 512 | 8,161 | 0.7133 |
Fuente: elaboración propia,
python scripts/parte1_busqueda.py
(resultados/parte1/busqueda_pd.csv y
resultados/parte1/config_final_pd.json).
La ganadora es la red más pequeña de la búsqueda: una sola capa oculta de 32 neuronas. La diferencia con la red base (0.0015 de AUC) es del mismo orden que la variabilidad entre semillas. Por eso entrenamos ambas con 30 semillas (0 a 29); los resultados están en la Tabla 7.
Tabla 7. Métricas en prueba de las dos redes con 30 semillas.
| Red | Métrica | Media | Desv. estándar | Mejor | Peor |
|---|---|---|---|---|---|
| Búsqueda: [32], 2,753 parámetros | AUC | 0.7043 | 0.0007 | 0.7060 | 0.7030 |
| Gini | 0.4086 | 0.0015 | 0.4120 | 0.4060 | |
| KS | 0.3043 | 0.0015 | 0.3074 | 0.3008 | |
| Brier | 0.1713 | 0.0002 | 0.1709 | 0.1720 | |
| Base: [64, 32, 16], 8,161 parámetros | AUC | 0.7031 | 0.0011 | 0.7050 | 0.7009 |
| Gini | 0.4062 | 0.0023 | 0.4100 | 0.4018 | |
| KS | 0.3004 | 0.0028 | 0.3073 | 0.2958 | |
| Brier | 0.1713 | 0.0004 | 0.1706 | 0.1722 |
Fuente: elaboración propia,
python scripts/parte1_busqueda.py
(resultados/parte1/semillas_pd.csv). Para el Brier, mejor
es menor.
La red de la búsqueda es un poco mejor y más estable que la base, con un tercio de los parámetros. Las dos quedan a menos de 0.002 de AUC de la regresión logística (Tabla 5).
Red final. Mantenemos como final la red base
(weights/modelo_clasificador.pth), que es la que usan la
scorecard, la aplicación y la pérdida esperada. Su diferencia con la
ganadora de la búsqueda es de 0.0012 de AUC en prueba, y el Brier es el
mismo (0.1713), así que no tiene importancia práctica para la decisión
de crédito. La búsqueda la reportamos como evidencia de que ajustar la
arquitectura no cambia el desempeño de forma relevante. La red de la
búsqueda queda guardada en
weights/modelo_pd_busqueda.pth.
El puntaje es una transformación afín del logaritmo de las odds de
ser buen pagador, \(\ln\frac{1-p}{p}\).
Se fija con tres parámetros: el puntaje base, las odds de referencia en
ese puntaje y los puntos para duplicar las odds (PDO). La derivación
completa está en la Sección 7.8 y el código, en
scripts/scorecard_engine.py:
\[\text{Factor} = \frac{\text{PDO}}{\ln 2}, \qquad \text{Offset} = \text{Puntaje}_0 - \text{Factor}\cdot\ln(\text{Odds}_0), \qquad \text{Puntaje}(p) = \text{Offset} - \text{Factor}\cdot\ln\frac{p}{1-p}.\]
Usamos un puntaje base de 600, PDO de 20 y, como odds de referencia, las de la cartera de entrenamiento: \(\text{Odds}_0 = N_0/N_1 = 92{,}678/25{,}694 = 3.607\). Así, 600 puntos corresponden a un cliente con la probabilidad de incumplimiento promedio de entrenamiento (21.7 %): por encima de 600 el riesgo es menor que el promedio, y por debajo, mayor. Cada 20 puntos más, las odds de pagar se duplican.
Con estos valores, Factor \(=
28.8539\) y Offset \(=
562.9841\) (Tabla 8). Los tres parámetros se cambian con
--puntaje_base, --odds_base y
--pdo, o en la aplicación. Ninguno cambia el AUC, el KS ni
el orden de los clientes, porque la transformación es monótona; solo
cambian la escala.
Esta elección tiene una consecuencia práctica. Las odds de referencia
son exactamente el pos_weight de la red, \(w = N_0/N_1\), así que \(\ln w = \ln \text{Odds}_0\). Entonces la
corrección del peso de clase se cancela con el Offset (Sección 7.8), y
el puntaje sale directamente del logit de la red, sin corregir:
\[\text{Puntaje}(\mathbf{x}) = 600 - 28.8539\cdot f_\theta(\mathbf{x}).\]
Lo comprobamos en los 36,378 créditos de prueba: la diferencia máxima con el puntaje calculado desde la probabilidad corregida es de \(1.5\times10^{-5}\) puntos.
Tabla 8. Puntaje según la probabilidad de incumplimiento (puntaje base 600 con las odds de la cartera de entrenamiento, PDO 20).
| Probabilidad de incumplimiento | Odds (buenos : malos) | Puntaje |
|---|---|---|
| 2 % | 49 : 1 | 675 |
| 5 % | 19 : 1 | 648 |
| 10 % | 9 : 1 | 626 |
| 21.7 % (tasa de entrenamiento) | 3.6 : 1 | 600 |
| 30 % | 2.3 : 1 | 587 |
| 50 % | 1 : 1 | 563 |
| 80 % | 0.25 : 1 | 523 |
Fuente: elaboración propia con las fórmulas de
scripts/scorecard_engine.py.
Distribución en prueba. Con la red base, los puntajes de prueba tienen media 602 y desviación estándar 25. El 5 % más bajo queda por debajo de 564, la mediana es 600 y el 5 % más alto supera 646. El rango va de 526 a 813 (Figura 8).
¿Ordena bien? Sí. Al dividir los créditos de prueba en deciles de puntaje, la tasa observada de incumplimiento baja en cada decil: va de 49.8 % en el de menor puntaje a 5.6 % en el de mayor puntaje (Tabla 9). La PD media de la red queda un poco por debajo de la tasa observada en casi todos los deciles, por lo mismo que se ve en la calibración.
Tabla 9. Incumplimiento por decil de puntaje en prueba (red base).
| Decil | Puntaje | PD media | Tasa observada |
|---|---|---|---|
| 1 | 526 – 570 | 50.4 % | 49.8 % |
| 2 | 570 – 580 | 39.4 % | 41.3 % |
| 3 | 580 – 587 | 33.1 % | 34.8 % |
| 4 | 587 – 593 | 28.4 % | 30.1 % |
| 5 | 593 – 600 | 23.9 % | 26.2 % |
| 6 | 600 – 608 | 19.5 % | 22.2 % |
| 7 | 608 – 615 | 15.8 % | 16.9 % |
| 8 | 615 – 624 | 12.6 % | 15.2 % |
| 9 | 624 – 636 | 9.2 % | 10.2 % |
| 10 | 636 – 813 | 5.2 % | 5.6 % |
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py
(resultados/parte1/deciles_puntaje_prueba.csv). Cada decil
tiene 3,637 o 3,638 créditos.
Figura 8. Distribución de puntajes en prueba por resultado, e incumplimiento por decil de puntaje.
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py.
Cómo salen los puntos del modelo. En las dos versiones, el puntaje de un crédito es \(\text{Offset} - \text{Factor}\,[f(\mathbf{x}) - \ln w]\), donde \(f(\mathbf{x})\) es el logit del modelo y \(w = 3.607\). Lo que cambia es cómo se reparte ese puntaje entre las variables.
Tabla 10. Puntos de la scorecard continua de la regresión logística.
| Variable | Puntos por unidad | Unidad |
|---|---|---|
int_rate |
−2.95 | cada punto porcentual de tasa |
dti |
−0.70 | cada punto de la relación deuda/ingreso |
annual_inc |
+1.21 | cada 10,000 USD de ingreso |
loan_amnt |
−0.45 | cada 1,000 USD de monto |
open_acc |
−0.80 | cada línea de crédito abierta |
total_acc |
+0.45 | cada línea de crédito en total |
inq_last_6mths |
−1.22 | cada consulta de crédito reciente |
revol_util |
−0.08 | cada punto porcentual de uso del cupo rotativo |
term_ 60 months |
−11.90 | plazo de 60 meses frente a 36 |
purpose_small_business |
−23.07 | propósito pequeño negocio frente a carro |
purpose_debt_consolidation |
−8.78 | propósito consolidar deudas frente a carro |
home_ownership_RENT |
−1.98 | vivienda arrendada frente a ANY |
sub_grade |
+0.06 | cada escalón de subcalificación (A1 = 0, …, G5 = 34) |
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py
(resultados/parte1/scorecard_logistica_puntos.csv, con las
82 variables).
Usamos dos métodos de interpretabilidad que no dependen de la forma del modelo.
int_rate y
sub_grade están muy asociadas, también las permutamos
juntas: con una sola desordenada, el modelo todavía puede sacar la
información de la otra. Usamos 5 repeticiones por variable.Tabla 11. Caída del AUC en prueba al permutar cada grupo de variables (promedio de 5 repeticiones).
| Grupo | Red neuronal | Regresión logística |
|---|---|---|
int_rate + sub_grade (juntas) |
0.069 | 0.078 |
sub_grade |
0.043 | 0.000 |
int_rate |
0.004 | 0.080 |
annual_inc |
0.019 | 0.006 |
dti |
0.013 | 0.017 |
loan_amnt |
0.014 | 0.005 |
total_acc |
0.011 | 0.016 |
addr_state |
0.007 | 0.005 |
revol_util |
0.006 | 0.005 |
home_ownership |
0.006 | 0.003 |
term |
0.006 | 0.004 |
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py
(resultados/parte1/importancia_permutacion_prueba.csv, con
todos los grupos).
Figura 9. Los 15 coeficientes más positivos y los 15 más negativos de la regresión logística.
Fuente: elaboración propia,
notebooks/regresion_logistica.ipynb (celda 11).
Figura 10. Importancia de las variables: caída del AUC al permutar (ambos modelos) y puntos absolutos promedio con gradientes integrados (red).
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py.
Figura 11. Puntos por variable de un crédito de prueba con PD mediana, frente al cliente promedio.
Fuente: elaboración propia,
python scripts/parte1_evaluacion.py.
Lo que hace más riesgosa a una persona, según ambos modelos (Tabla 11, Figura 9 y Figura 10):
int_rate y sub_grade, son lo más
importante. Al permutarlas juntas, el AUC cae 0.07 en la red y
0.08 en la logística. Pero cada modelo usa una distinta: la logística se
apoya en la tasa (su coeficiente de sub_grade es −0.013,
prácticamente cero y con signo contrario al esperado), y la red, en la
subcalificación. Llevan casi la misma información; por eso, con una sola
permutada, el modelo que se apoya en la otra apenas lo nota. Así se
explica el signo raro de sub_grade en la logística: es
colinealidad, no un efecto real.annual_inc, que reduce
el riesgo), la relación deuda/ingreso (dti, que lo
aumenta), el monto (loan_amnt, que lo aumenta) y el número
de líneas de crédito (total_acc reduce el riesgo y
open_acc lo aumenta).Esto es coherente con H1 a H3 (Sección 2.4), ahora con todas las variables juntas.
En los gradientes integrados, addr_state aparece con más
puntos que en la permutación (5.6 puntos absolutos promedio frente a una
caída de AUC de 0.007). Se debe a la línea base: el cliente promedio
tiene una fracción de cada uno de los 49 estados, así que cualquier
persona real se aleja de él en todas esas indicadoras a la vez. Para
leer los puntos por variable hay que tener en cuenta esa elección.
El detalle de esta parte está en docs/parte2_lgd.md y el
código, en notebooks/regresion.ipynb.
python scripts/parte2_lgd.py reproduce el notebook con un
solo comando y sin internet: mismas funciones, misma configuración y
mismos resultados, incluida la red reentrenada con la semilla 56.
Tabla 12. Créditos que incumplieron, según su estado.
loan_status |
Créditos | % con recoveries > 0 |
out_prncp promedio (USD) |
|---|---|---|---|
| Charged Off | 45,248 | 53.45 | 0 |
| Does not meet the credit policy. Status:Charged Off | 761 | 64.39 | 0 |
| Default | 1,219 | 0.00 | 11,595 |
| Late (31-120 days) | 11,591 | 0.00 | 11,690 |
Fuente: elaboración propia,
python scripts/parte2_lgd.py --descriptivo (celda 4 de
notebooks/regresion.ipynb).
Default y Late (31-120 days) todavía no se
han castigado (Tabla 12): no tienen recuperaciones y aún deben capital,
así que su pérdida final no es observable. Si se incluyeran, todos
saldrían con pérdida total. El modelo se entrena solo con los
46,009 castigados. Como estima la pérdida si
el crédito incumpliera, después se puede aplicar a cualquier
crédito.
Las definiciones vienen del diccionario (Tabla 13):
Tabla 13. Campos usados para construir la LGD.
| Campo | Definición en el diccionario | Rol |
|---|---|---|
funded_amnt |
The total amount committed to that loan at that point in time. | Monto desembolsado |
total_rec_prncp |
Principal received to date. | Capital pagado antes del castigo |
recoveries |
post charge off gross recovery | Recuperación bruta |
collection_recovery_fee |
post charge off collection fee | Costo de cobranza |
Fuente: datasets/LCDataDictionary.xlsx.
\[EAD_i = \text{funded\_amnt}_i - \text{total\_rec\_prncp}_i, \qquad R_i = \text{recoveries}_i - \text{collection\_recovery\_fee}_i, \qquad LGD_i = \min\!\left(1,\ \max\!\left(0,\ 1 - \frac{R_i}{EAD_i}\right)\right).\]
En todos los castigados se cumple \(EAD
> 0\) y out_prncp \(=
0\). Solo 38 créditos dan LGD negativa antes del recorte, porque
lo recuperado puede cubrir también intereses y cargos; ninguno da LGD
mayor que 1.
La LGD tiene media 0.930, mediana 0.981 y desviación estándar 0.098.
El 46.4 % de los castigados tiene LGD \(= 1\), es decir, no se recuperó nada
(Figura 12). Hay picos: la razón recoveries / EAD se
concentra cerca de 5.7 % y 14 %, y la razón
collection_recovery_fee / recoveries, cerca de 1 % y 18 %.
Esto sugiere que la LGD depende más de reglas del proceso de recobro que
del perfil del cliente. Para la comisión hay una explicación
documentada: según su centro de ayuda, Lending Club descuenta de lo
recuperado comisiones de cobranza que son porcentajes fijos, de hasta 30
% sin litigio y hasta 40 % con litigio, más las que cobran agencias
externas cuando el crédito se castiga (LendingClub, s. f.). Para los
picos de recoveries / EAD no encontramos una fuente; una
posibilidad es la venta de carteras castigadas a un precio fijo, pero no
la podemos verificar con estos datos. Las políticas actuales pueden
diferir de las del periodo 2007-2015.
Figura 12. Distribución de la LGD en los créditos castigados.
Fuente: elaboración propia,
python scripts/parte2_lgd.py --descriptivo (celda 6 de
notebooks/regresion.ipynb).
Tabla 14. LGD por año de originación.
| Año | Créditos | LGD media | % sin recuperación |
|---|---|---|---|
| 2007 | 158 | 0.921 | 36.7 |
| 2008 | 496 | 0.931 | 44.2 |
| 2009 | 723 | 0.926 | 34.0 |
| 2010 | 1,753 | 0.925 | 25.6 |
| 2011 | 3,205 | 0.922 | 22.7 |
| 2012 | 8,084 | 0.911 | 25.2 |
| 2013 | 14,836 | 0.918 | 42.9 |
| 2014 | 13,981 | 0.945 | 62.7 |
| 2015 | 2,773 | 0.981 | 89.0 |
Fuente: elaboración propia,
python scripts/parte2_lgd.py --descriptivo (celda 8 de
notebooks/regresion.ipynb).
Default y
Late. Se supone que, al castigarse, se comportarán
como los ya castigados.collection_recovery_fee solo cubre el costo de la agencia
de cobranza.Se usan las mismas 82 variables y los mismos cortes de la Parte 1. Al quitar las filas con nulos quedan 33,110 castigados, originados entre agosto de 2012 y septiembre de 2015 (Tabla 15). El escalado se ajusta solo con entrenamiento.
Tabla 15. Partición temporal de la Parte 2.
| Conjunto | Desde | Hasta | Créditos | LGD media |
|---|---|---|---|---|
| Entrenamiento | 2012-08 | 2014-04 | 22,372 | 0.919 |
| Validación | 2014-05 | 2014-10 | 7,112 | 0.951 |
| Prueba | 2014-11 | 2015-09 | 3,626 | 0.975 |
Fuente: elaboración propia,
python scripts/parte2_lgd.py (celda 10 de
notebooks/regresion.ipynb).
La LGD media sube en validación y prueba por el recobro incompleto (Sección 4.4). Esto penaliza a cualquier modelo entrenado con cosechas más antiguas.
Referencia. Regresión lineal por mínimos cuadrados, con 83 parámetros. En prueba ninguna predicción cae fuera de \([0, 1]\), pero el modelo no lo garantiza.
Red neuronal. La misma estructura por capa de la Parte 1 (lineal, normalización por lotes, activación y dropout). La salida pasa por una sigmoide, porque la LGD está en \([0, 1]\). Se compararon dos pérdidas, ambas derivadas en la Sección 7.3: la entropía cruzada con objetivo continuo (Papke y Wooldridge, 1996) y el error cuadrático medio. Las dos se minimizan en la media condicional \(E[LGD \mid \mathbf{x}]\), que es lo que necesita \(E[L]\). Regularización: dropout, penalización \(L_2\) y parada temprana con paciencia de 10 sobre la pérdida de validación, con un máximo de 100 épocas.
Búsqueda. Búsqueda aleatoria (Bergstra y Bengio, 2012) de 30 configuraciones, con semilla 56, sobre el espacio de la Tabla 16. El criterio fue el menor RMSE en validación, igual para todas; no se puede comparar por la pérdida porque cada configuración usa la suya.
Tabla 16. Espacio de búsqueda de la red de LGD.
| Hiperparámetro | Valores |
|---|---|
| Capas ocultas | [32], [64], [64, 32], [128, 64], [64, 32, 16], [128, 64, 32] |
| Activación | ReLU, tanh, Leaky ReLU |
| Dropout | 0, 0.1, 0.2, 0.3 |
weight_decay (\(L_2\)) |
0, \(10^{-5}\), \(10^{-4}\), \(10^{-3}\) |
| Tasa de aprendizaje | \(10^{-4}\), \(3\cdot10^{-4}\), \(10^{-3}\), \(3\cdot10^{-3}\) |
| Tamaño de lote | 256, 512, 1024 |
| Pérdida | Entropía cruzada, error cuadrático medio |
Fuente: python scripts/parte2_lgd.py --busqueda
(celda 2 de notebooks/regresion.ipynb); resultados en
resultados/busqueda_lgd.csv.
La configuración ganadora tiene capas [64, 32, 16], tanh,
dropout de 0.2, weight_decay de \(10^{-5}\), tasa de aprendizaje de \(3\cdot10^{-4}\), lotes de 512 y error
cuadrático medio. Tiene 8,161 parámetros y está
guardada en resultados/config_final_lgd.json. Ocho de las
diez mejores configuraciones usan el error cuadrático medio. La Figura
13 muestra sus curvas de entrenamiento. Que la pérdida de validación
quede por debajo de la de entrenamiento no es un error: la LGD de
validación está más concentrada cerca de 1, así que tiene menos
varianza.
Figura 13. Curvas de entrenamiento de la red de LGD (configuración final, semilla 56).
Fuente: elaboración propia,
python scripts/parte2_lgd.py (celda 25 de
notebooks/regresion.ipynb).
La configuración final se entrenó con 30 semillas. La variabilidad entre semillas es pequeña frente a las diferencias entre modelos (Tabla 17).
Tabla 17. Red de LGD con 30 semillas (0 a 29).
| MAE validación | RMSE validación | MAE prueba | RMSE prueba | |
|---|---|---|---|---|
| Media | 0.0648 | 0.0770 | 0.0594 | 0.0805 |
| Desviación estándar | 0.0006 | 0.0002 | 0.0014 | 0.0007 |
| Mejor | 0.0637 | 0.0766 | 0.0571 | 0.0794 |
| Peor | 0.0658 | 0.0776 | 0.0622 | 0.0820 |
Fuente: elaboración propia,
python scripts/parte2_lgd.py --busqueda (celda 22 de
notebooks/regresion.ipynb); detalle en
resultados/semillas_lgd.csv.
Tabla 18. Comparación de modelos de LGD (red con semilla 56).
| Modelo | Conjunto | MAE | RMSE | RMSE sin sesgo | Sesgo | Spearman |
|---|---|---|---|---|---|---|
| Media de entrenamiento | Validación | 0.0773 | 0.0837 | 0.0772 | −0.0324 | — |
| Prueba | 0.0827 | 0.0950 | 0.0764 | −0.0565 | — | |
| Regresión lineal | Entrenamiento | 0.0734 | 0.0909 | 0.0909 | 0.0000 | 0.194 |
| Validación | 0.0738 | 0.0815 | 0.0767 | −0.0275 | 0.148 | |
| Prueba | 0.0775 | 0.0926 | 0.0773 | −0.0510 | 0.133 | |
| Red neuronal | Entrenamiento | 0.0752 | 0.0965 | 0.0911 | +0.0317 | 0.145 |
| Validación | 0.0645 | 0.0765 | 0.0765 | −0.0004 | 0.136 | |
| Prueba | 0.0594 | 0.0802 | 0.0762 | −0.0251 | 0.117 |
Fuente: elaboración propia,
python scripts/parte2_lgd.py (celda 28 de
notebooks/regresion.ipynb). El sesgo es la media predicha
menos la media observada; el RMSE sin sesgo se calcula después de
restarle su media a cada serie; Spearman es la correlación de rangos
entre la predicción y la LGD observada.
En MAE y RMSE gana la red: en prueba su RMSE es 0.0802, frente a 0.0926 de la regresión lineal (Tabla 18). Pero hay cuatro matices:
Figura 14. LGD promedio por trimestre de originación: observada y predicha.
Fuente: elaboración propia,
python scripts/parte2_lgd.py (celda 29 de
notebooks/regresion.ipynb).
Ningún modelo reproduce la distribución observada (Figura 15). En prueba, el 86 % de los créditos tiene LGD \(= 1\) exacta. Las predicciones se concentran en un rango estrecho: entre 0.90 y 0.95 la lineal y entre 0.93 y 0.96 la red. Sus desviaciones estándar son 0.018 y 0.010, frente a 0.076 de la observada. Es lo esperable de un estimador de la media condicional cuando las variables explican poco.
Figura 15. Distribución acumulada de la LGD en prueba: observada y predicha.
Fuente: elaboración propia,
python scripts/parte2_lgd.py (celda 31 de
notebooks/regresion.ipynb).
Conclusión preliminar. Con variables de originación, la LGD casi no se puede predecir a nivel individual: la correlación de Spearman va de 0.12 a 0.19. Lo que un modelo gana en MAE o RMSE en prueba se debe sobre todo a acertar el nivel de una LGD inflada por el recobro incompleto, no a distinguir entre clientes.
Cómo lo tratamos. Mantenemos la misma partición temporal de la Parte 1 y reportamos el sesgo de nivel junto con métricas que no dependen de él: el RMSE sin sesgo y la correlación de Spearman (Tabla 18). Hay otras dos opciones: usar solo las cosechas con recobro maduro, o escoger los hiperparámetros con una métrica sin sesgo. La primera rompería la coherencia con la Parte 1 y con el caso de uso, porque el modelo se aplica a créditos nuevos. Quedan como trabajo futuro.
Al originar no se sabe cuándo incumplirá el cliente. Por eso la EAD se estima como el monto del crédito por la fracción que en promedio queda pendiente al castigo, según el plazo. Esa fracción se calcula con los castigados de entrenamiento:
\[\widehat{EAD}_i = \text{loan\_amnt}_i \cdot \bar{f}_{\text{plazo}(i)}, \qquad \bar{f}_{36} = 0.670, \qquad \bar{f}_{60} = 0.846.\]
Se usa loan_amnt porque es la variable que conserva el
modelo y la que el usuario ingresa en la aplicación. Los créditos a 60
meses amortizan más despacio, así que al incumplir deben una fracción
mayor.
Como prueba de coherencia, en el conjunto de prueba todos los créditos incumplieron, así que se usa \(PD = 1\). La pérdida observada suma 50,758,058 USD (13,998 por crédito) y la estimada con la red y \(\widehat{EAD}\), 40,612,518 USD (11,200 por crédito). La subestimación de 20 % se explica por la LGD de prueba inflada y por usar un factor de EAD promedio.
Pérdida esperada con la PD de la Parte 1.
python scripts/perdida_esperada.py combina la red base de
la Parte 1 (con la corrección del peso de clase), la red de LGD y la
\(\widehat{EAD}\) en los 36,378
créditos de prueba (Tabla 19).
Tabla 19. Pérdida esperada en el conjunto de prueba.
| Concepto | Valor |
|---|---|
| Créditos | 36,378 |
| Monto prestado | 537.8 millones de USD |
| PD media predicha / tasa observada | 23.7 % / 25.2 % |
| LGD media predicha | 95.1 % |
| EAD media estimada | 11,040 USD |
| Pérdida esperada total | 100.5 millones de USD (18.7 % del monto) |
| Pérdida observada en los castigados | 50.8 millones de USD (3,626 créditos) |
| Incumplidos cuya pérdida aún no se observa (Default o Late) | 5,549 créditos, con 88.0 millones de USD de monto |
Fuente: elaboración propia,
python scripts/perdida_esperada.py
(resultados/perdida_esperada/resumen_red.json).
La pérdida observada es una cota inferior, porque falta la de 5,549
créditos en Default o Late (31-120 days) cuyo
recobro no ha terminado. Por decil de PD (Figura 16), la pérdida
esperada sube de 438 a 7,399 USD por crédito y ordena igual que la
observada, que va de 230 a 4,840 USD sin contar esos créditos.
Figura 16. Pérdida esperada y observada por decil de probabilidad de incumplimiento en prueba.
Fuente: elaboración propia,
python scripts/perdida_esperada.py.
Todos los modelos de una misma tarea usan el mismo preprocesamiento
(src/preprocesamiento.py) y la misma partición. La Tabla 20
resume el presupuesto de cada familia. Los tiempos son en CPU (Apple
M1). Los de la Parte 1 se midieron mientras corrían otros procesos, así
que son aproximados.
Tabla 20. Presupuesto de búsqueda, cómputo y complejidad.
| Tarea | Modelo | Parámetros | Configuraciones | Tiempo de búsqueda | Tiempo de 30 semillas | Métrica en prueba |
|---|---|---|---|---|---|---|
| PD | Regresión logística | 83 | 1 | 0.3 s | — (determinista) | AUC = 0.7033 |
| PD | Red base (final) | 8,161 | 1 (fijada a mano) | — | 7.9 min | AUC = 0.7031 ± 0.0011 |
| PD | Red de la búsqueda | 2,753 | 31 | 9.6 min | 7.6 min | AUC = 0.7043 ± 0.0007 |
| LGD | Media de entrenamiento | 1 | 1 | 0 s | — | RMSE = 0.0950 |
| LGD | Regresión lineal | 83 | 1 | 0.08 s | — (determinista) | RMSE = 0.0926 |
| LGD | Red neuronal | 8,161 | 30 | 6.0 min | 11.8 min | RMSE = 0.0805 ± 0.0007 |
Fuente: elaboración propia,
python scripts/parte1_busqueda.py,
python scripts/parte1_evaluacion.py y
notebooks/regresion.ipynb (celdas 22 y 27). En las redes,
la métrica es la media ± desviación estándar de 30 semillas.
En la probabilidad de incumplimiento, nada medible.
Una explicación probable es que la mayor parte de la señal está en
int_rate y sub_grade (Tabla 11). Estas
variables ya resumen la evaluación de riesgo de la plataforma, y su
relación con el incumplimiento es casi monótona (Figura 3). Sobre esa
base no quedan interacciones fuertes que una red pueda aprovechar. Es
coherente con que la mejor configuración de la búsqueda haya sido la más
pequeña.
En la LGD, la red gana en error, pero no por entender mejor al cliente. Su RMSE en prueba es menor (0.0805 frente a 0.0926), pero sin el sesgo de nivel empata con la regresión lineal, y la lineal ordena mejor a los clientes (Sección 4.8). Con variables de originación, la LGD individual es casi impredecible.
H1 (dti), H2 (int_rate y
sub_grade) y H3 (plazo) se confirmaron, tanto en el
análisis descriptivo (Tabla 3) como en la importancia de las variables
de los modelos (Tabla 11). H4 (antigüedad laboral) no se confirmó: la
tasa de incumplimiento es casi igual para todas las antigüedades.
sub_grade sale con signo negativo
en la logística y casi sin importancia, solo porque
int_rate lleva la misma información (Sección 3.7). Por eso
hay que medir su importancia juntas.drop_first
eliminaba la categoría elegida. Lo descubrimos al construir la
aplicación: cambiar el plazo no movía el puntaje.Una neurona artificial recibe un vector \(\mathbf{x} \in \mathbb{R}^d\), calcula una combinación afín y le aplica una función de activación \(\phi\):
\[z = \mathbf{w}^\top \mathbf{x} + b, \qquad a = \phi(z).\]
Un perceptrón multicapa encadena capas de neuronas. En nuestras redes, cada capa oculta \(l = 1, \dots, L\) aplica una transformación lineal, normalización por lotes (BN, Sección 7.6), la activación y dropout:
\[\mathbf{h}^{(0)} = \mathbf{x}, \qquad \mathbf{z}^{(l)} = \mathbf{W}^{(l)}\mathbf{h}^{(l-1)} + \mathbf{b}^{(l)}, \qquad \mathbf{h}^{(l)} = \operatorname{Dropout}\!\left(\phi\!\left(\operatorname{BN}(\mathbf{z}^{(l)})\right)\right).\]
La salida es un logit, \(f_\theta(\mathbf{x}) = \mathbf{w}_{\text{sal}}^\top \mathbf{h}^{(L)} + b_{\text{sal}}\), y la predicción es \(\hat{p} = \sigma(f_\theta(\mathbf{x}))\).
Una capa de \(d_{\text{in}}\) a \(d_{\text{out}}\) neuronas tiene \(d_{\text{in}} d_{\text{out}} + d_{\text{out}}\) parámetros lineales y \(2d_{\text{out}}\) de la BN (\(\gamma\) y \(\beta\)). Para \(82 \to 64 \to 32 \to 16 \to 1\) eso da \(5{,}440 + 2{,}144 + 560 + 17 = 8{,}161\), que coincide con lo que reporta PyTorch.
ReLU: \(\phi(z) = \max(0, z)\), con \(\phi'(z) = 1\) si \(z > 0\) y \(0\) si \(z < 0\).
Leaky ReLU: \(\phi(z) = z\) si \(z \ge 0\) y \(\alpha z\) si \(z < 0\), con \(\alpha = 0.01\), el valor por defecto de PyTorch. Su derivada es \(1\) o \(\alpha\).
Tangente hiperbólica: \(\phi(z) = \tanh z = \dfrac{e^{z} - e^{-z}}{e^{z} + e^{-z}}\), con \(\phi'(z) = 1 - \tanh^2 z\). Su salida está en \((-1, 1)\).
Sigmoide, en la capa de salida de ambas redes: \(\sigma(z) = \dfrac{1}{1 + e^{-z}}\), con \(\sigma'(z) = \sigma(z)\,(1 - \sigma(z))\). Lleva \(\mathbb{R}\) a \((0, 1)\), que es el rango de una probabilidad y de la LGD.
Entropía cruzada binaria (Parte 1). Suponemos que, dado \(\mathbf{x}_i\), la variable \(Y_i \in \{0, 1\}\) es Bernoulli con parámetro \(p_i = \sigma(f_\theta(\mathbf{x}_i))\), y que las observaciones son independientes. La verosimilitud y su logaritmo son
\[\mathcal{L}(\theta) = \prod_{i=1}^{N} p_i^{\,y_i}(1 - p_i)^{1 - y_i}, \qquad \ln \mathcal{L}(\theta) = \sum_{i=1}^{N}\left[y_i \ln p_i + (1 - y_i)\ln(1 - p_i)\right].\]
Maximizar la verosimilitud equivale a minimizar el promedio de su negativo:
\[\ell_{\text{BCE}}(\theta) = -\frac{1}{N}\sum_{i=1}^{N}\left[y_i \ln p_i + (1 - y_i)\ln(1 - p_i)\right].\]
Versión ponderada y corrección de las
probabilidades. Con pos_weight \(= w\), la pérdida de cada crédito es \(-\left[w\,y\ln p + (1 - y)\ln(1 -
p)\right]\). Fijemos \(\mathbf{x}\) y llamemos \(\pi = P(Y = 1 \mid \mathbf{x})\). La
pérdida esperada de predecir \(q\)
es
\[R(q) = -\left[w\,\pi \ln q + (1 - \pi)\ln(1 - q)\right], \qquad R'(q) = -\frac{w\pi}{q} + \frac{1 - \pi}{1 - q} = 0 \;\Longrightarrow\; q^* = \frac{w\pi}{w\pi + 1 - \pi}.\]
En escala logit, \(\ln\dfrac{q^*}{1 - q^*} = \ln w + \ln\dfrac{\pi}{1 - \pi}\). Es decir, el peso desplaza el logit en \(\ln w\). Para recuperar la probabilidad se resta ese desplazamiento:
\[\pi = \sigma\!\left(f_\theta(\mathbf{x}) - \ln w\right).\]
Con \(w = 3.61\), el desplazamiento es \(\ln 3.61 = 1.28\). La corrección es exacta solo si la red alcanza el mínimo de la pérdida esperada; por eso se verifica con la curva de calibración (Figura 5).
Entropía cruzada con objetivo continuo (Parte 2). Para \(y_i \in [0, 1]\) se usa la misma expresión, ahora como cuasi-verosimilitud de Bernoulli, con \(\mu_i = \sigma(f_\theta(\mathbf{x}_i))\) (Papke y Wooldridge, 1996). Si \(m = E[Y \mid \mathbf{x}]\), la pérdida esperada es \(-\left[m\ln\mu + (1 - m)\ln(1 - \mu)\right]\), que se minimiza en \(\mu = m\). Por tanto estima la media condicional aunque \(y\) no sea binaria.
Error cuadrático medio (Parte 2). Suponemos \(y_i = \mu_\theta(\mathbf{x}_i) + \varepsilon_i\) con \(\varepsilon_i \sim \mathcal{N}(0, \sigma^2)\) independientes, donde \(\mu_\theta = \sigma(f_\theta)\). La log-verosimilitud es
\[\ln \mathcal{L}(\theta, \sigma^2) = -\frac{N}{2}\ln(2\pi\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^{N}\left(y_i - \mu_\theta(\mathbf{x}_i)\right)^2.\]
Con \(\sigma^2\) fijo, maximizarla en \(\theta\) equivale a minimizar \(\ell_{\text{MSE}}(\theta) = \frac{1}{N}\sum_i \left(y_i - \mu_\theta(\mathbf{x}_i)\right)^2\). La LGD no es gaussiana: está acotada y tiene mucha masa en 1. Aun así, \(E[(Y - \mu)^2 \mid \mathbf{x}]\) se minimiza en \(\mu = E[Y \mid \mathbf{x}]\), que es la cantidad que necesita \(E[L]\).
La retropropagación (Rumelhart et al., 1986) aplica la regla de la cadena desde la salida hacia la entrada. Sea \(\delta^{(l)} = \partial \ell / \partial \mathbf{z}^{(l)}\). En la salida, con \(z = f_\theta(\mathbf{x})\):
En las capas ocultas, sin contar BN ni dropout:
\[\delta^{(l)} = \left(\mathbf{W}^{(l+1)\top}\delta^{(l+1)}\right) \odot \phi'\!\left(\mathbf{z}^{(l)}\right), \qquad \frac{\partial \ell}{\partial \mathbf{W}^{(l)}} = \delta^{(l)}\,\mathbf{h}^{(l-1)\top}, \qquad \frac{\partial \ell}{\partial \mathbf{b}^{(l)}} = \delta^{(l)}.\]
En un lote se promedian los gradientes de sus créditos. El dropout multiplica \(\delta\) por la misma máscara de la propagación hacia adelante. Con BN, la regla de la cadena atraviesa también la normalización (Ioffe y Szegedy, 2015); PyTorch la calcula por diferenciación automática.
Sea \(\mathbf{g}_t\) el gradiente
del lote en el paso \(t\). En
torch.optim.Adam, el weight_decay \(\lambda\) se suma al gradiente antes de
actualizar los momentos (Kingma y Ba, 2015):
\[\mathbf{g}_t \leftarrow \nabla_\theta \ell(\theta_t) + \lambda\theta_t, \qquad \mathbf{m}_t = \beta_1\mathbf{m}_{t-1} + (1 - \beta_1)\mathbf{g}_t, \qquad \mathbf{v}_t = \beta_2\mathbf{v}_{t-1} + (1 - \beta_2)\,\mathbf{g}_t \odot \mathbf{g}_t,\]
\[\hat{\mathbf{m}}_t = \frac{\mathbf{m}_t}{1 - \beta_1^t}, \qquad \hat{\mathbf{v}}_t = \frac{\mathbf{v}_t}{1 - \beta_2^t}, \qquad \theta_{t+1} = \theta_t - \eta\,\frac{\hat{\mathbf{m}}_t}{\sqrt{\hat{\mathbf{v}}_t} + \epsilon},\]
con \(\beta_1 = 0.9\), \(\beta_2 = 0.999\) y \(\epsilon = 10^{-8}\), los valores por defecto. Dos detalles:
Además, ReduceLROnPlateau hace \(\eta \leftarrow 0.5\,\eta\) cuando la
métrica de validación no mejora en 3 épocas.
Penalización \(L_2\). \(J(\theta) = \ell(\theta) +
\frac{\lambda}{2}\lVert\theta\rVert_2^2\), cuyo gradiente es
\(\nabla\ell(\theta) + \lambda\theta\).
Es exactamente el término que agrega weight_decay (Sección
7.5) y castiga los pesos grandes.
Dropout (Srivastava et al., 2014). En entrenamiento, cada activación se apaga con probabilidad \(q\) y las que quedan se reescalan:
\[r_j \sim \text{Bernoulli}(1 - q), \qquad \tilde{h}_j = \frac{r_j\,h_j}{1 - q}, \qquad E[\tilde{h}_j] = h_j.\]
En evaluación no se apaga nada, y gracias al reescalamiento no hay que corregir los pesos.
Parada temprana. Al final de cada época se mide la validación: el AUC en la Parte 1 y la pérdida en la Parte 2. Se guardan los pesos de la mejor época \(e^*\) y el entrenamiento se detiene cuando pasan 10 épocas sin mejorar, es decir, cuando \(e - e^* \ge 10\). Al final se restauran los pesos de \(e^*\).
Normalización por lotes (Ioffe y Szegedy, 2015). Para cada neurona, con la media \(\mu_B\) y la varianza \(\sigma_B^2\) del lote:
\[\hat{z} = \frac{z - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}}, \qquad \operatorname{BN}(z) = \gamma\,\hat{z} + \beta.\]
En evaluación se usan promedios móviles de \(\mu_B\) y \(\sigma_B^2\) calculados en entrenamiento (con momentum 0.1 en PyTorch). Hay una consecuencia en nuestra arquitectura: como la BN resta la media, el sesgo \(\mathbf{b}^{(l)}\) de la capa lineal anterior no tiene efecto, y su gradiente es cero. Son 112 de los 8,161 parámetros.
AUC. La curva ROC grafica \(TPR(t) = P(\hat{p} > t \mid Y = 1)\) contra \(FPR(t) = P(\hat{p} > t \mid Y = 0)\) para todos los umbrales \(t\). El área bajo ella es la probabilidad de que un mal pagador escogido al azar reciba una probabilidad mayor que un buen pagador escogido al azar (Hanley y McNeil, 1982).
Índice de Gini. \(\text{Gini} = 2\,AUC - 1\), que vale 0 para un modelo al azar y 1 para uno perfecto.
Estadístico KS. Es la máxima distancia entre las
distribuciones acumuladas de la probabilidad predicha en malos (\(F_1\)) y buenos (\(F_0\)): \(KS =
\sup_s \lvert F_1(s) - F_0(s)\rvert\). Se calcula con
scipy.stats.ks_2samp sobre las probabilidades de cada
grupo.
Puntaje de Brier (Brier, 1950). \(BS = \frac{1}{N}\sum_i (\hat{p}_i - y_i)^2\). Como referencia se usa el de predecir siempre la tasa observada \(\bar{y}\): \(BS_{\text{ref}} = \bar{y}\,(1 - \bar{y})\).
Curva de calibración. Los créditos se agrupan en 10 grupos por cuantiles de \(\hat{p}\). En cada grupo se compara la probabilidad media predicha con la tasa observada de incumplimiento. Si el modelo está bien calibrado, los puntos caen sobre la diagonal.
MAE y RMSE. \(MAE = \frac{1}{N}\sum_i \lvert y_i - \hat{y}_i\rvert\) y \(RMSE = \sqrt{\frac{1}{N}\sum_i (y_i - \hat{y}_i)^2}\).
Diagnóstico de la Parte 2. El sesgo es \(\bar{\hat{y}} - \bar{y}\). El RMSE sin sesgo es \(\sqrt{\frac{1}{N}\sum_i \left[(y_i - \bar{y}) - (\hat{y}_i - \bar{\hat{y}})\right]^2}\). La correlación de Spearman es la correlación de Pearson entre los rangos de \(y\) y de \(\hat{y}\).
Las odds de ser buen pagador son \(\text{Odds} = \dfrac{1 - p}{p}\), y el puntaje es una función afín de su logaritmo (Siddiqi, 2006):
\[\text{Puntaje} = \text{Offset} + \text{Factor}\cdot\ln(\text{Odds}).\]
Se imponen dos condiciones. La primera es que, con las odds de referencia, el puntaje sea el puntaje base. La segunda es que, al duplicar las odds, el puntaje suba PDO puntos:
\[\text{Puntaje}_0 = \text{Offset} + \text{Factor}\cdot\ln(\text{Odds}_0), \qquad \text{Puntaje}_0 + \text{PDO} = \text{Offset} + \text{Factor}\cdot\ln(2\,\text{Odds}_0).\]
Restando la primera de la segunda: \(\text{PDO} = \text{Factor}\cdot\ln 2\). De ahí salen
\[\text{Factor} = \frac{\text{PDO}}{\ln 2}, \qquad \text{Offset} = \text{Puntaje}_0 - \text{Factor}\cdot\ln(\text{Odds}_0).\]
Como \(\ln(\text{Odds}) = -\ln\frac{p}{1-p}\) y el logit corregido del modelo es \(f_\theta(\mathbf{x}) - \ln w\) (Sección 7.3), el puntaje de un cliente es
\[\text{Puntaje}(\mathbf{x}) = \text{Offset} - \text{Factor}\cdot\left[f_\theta(\mathbf{x}) - \ln w\right].\]
A mayor probabilidad de incumplimiento, menor puntaje.
Si las odds de referencia son las de la cartera de entrenamiento, \(\text{Odds}_0 = N_0/N_1\), coinciden con el peso de clase, \(w = N_0/N_1\). Reemplazando \(\text{Offset} = \text{Puntaje}_0 - \text{Factor}\cdot\ln w\):
\[\text{Puntaje}(\mathbf{x}) = \text{Puntaje}_0 - \text{Factor}\cdot\ln w - \text{Factor}\cdot f_\theta(\mathbf{x}) + \text{Factor}\cdot\ln w = \text{Puntaje}_0 - \text{Factor}\cdot f_\theta(\mathbf{x}).\]
Es decir, el puntaje es una función lineal del logit sin corregir, y \(f_\theta(\mathbf{x}) = 0\) corresponde exactamente al puntaje base.
Importancia por permutación (Breiman, 2001; Fisher et al., 2019). Sea \(G\) un grupo de columnas y \(\mathbf{X}^{\pi_G}\) la matriz de prueba con las filas de esas columnas permutadas al azar (la misma permutación para todo el grupo). La importancia del grupo es la caída del AUC, promediada en \(R = 5\) permutaciones:
\[I_G = AUC(\mathbf{X}) - \frac{1}{R}\sum_{r=1}^{R} AUC\!\left(\mathbf{X}^{\pi_G^{(r)}}\right).\]
Gradientes integrados (Sundararajan et al., 2017). Para un crédito \(\mathbf{x}\) y una línea base \(\mathbf{x}_0\), la atribución de la variable \(j\) al logit \(f\) es
\[IG_j(\mathbf{x}) = (x_j - x_{0j})\int_0^1 \frac{\partial f\big(\mathbf{x}_0 + \alpha(\mathbf{x} - \mathbf{x}_0)\big)}{\partial x_j}\,d\alpha \;\approx\; (x_j - x_{0j})\,\frac{1}{m}\sum_{k=1}^{m}\frac{\partial f\big(\mathbf{x}_0 + \tfrac{k - 1/2}{m}(\mathbf{x} - \mathbf{x}_0)\big)}{\partial x_j},\]
con \(m = 256\) pasos (regla del punto medio). Por el teorema fundamental del cálculo para integrales de línea, \(\sum_j IG_j(\mathbf{x}) = f(\mathbf{x}) - f(\mathbf{x}_0)\) (completitud). Con \(\mathbf{x}_0 = \mathbf{0}\) en las variables estandarizadas, los puntos de la variable \(j\) en la scorecard son \(-\text{Factor}\cdot IG_j(\mathbf{x})\), y el puntaje es el de la línea base más la suma de esos puntos (Sección 3.6).
Durante el desarrollo del proyecto utilizamos herramientas de inteligencia artificial como apoyo para comprender conceptos, resolver dudas, revisar código y mejorar la presentación de los resultados. Entre las herramientas empleadas estuvieron Claude y Gemini. Las respuestas se tomaron como sugerencias y se contrastaron con los datos, el código y las fuentes consultadas.
La Tabla 21 resume los principales prompts de cada integrante.
Tabla 21. Principales prompts utilizados y su impacto.
| Integrante | Prompt | Impacto en el resultado final |
|---|---|---|
| Tomás Gómez Cardona | “Explícame cómo abordar esta parte del análisis y qué aspectos debo tener en cuenta.” | facilitar la comprensión de la metodología y la organización del análisis. |
| Sebastián Ocampo Galvis | “Revisa este código y sugiere cómo mejorarlo o solucionar posibles errores.” | apoyar la revisión y depuración del código del proyecto. |
| Juan Felipe Miranda Arciniegas | “Revisa el proyecto y compáralo con los requisitos para identificar qué hace falta mejorar.” | Permitió identificar pendientes y aspectos por corregir antes de finalizar la entrega. |
| Juan Felipe Miranda Arciniegas | “Ayúdame a mejorar la presentación y el diseño de la aplicación para que la información sea más clara.” | Apoyó los ajustes visuales y la legibilidad de los resultados mostrados en la aplicación. |
| Ana Sofía Gómez Zapata | “Explícame estos conceptos del modelo de riesgo y cómo interpretar sus resultados.” | Sirvió para examinar conceptos técnicos y detectar respuestas incorrectas que luego fueron verificadas y corregidas. |
| Ana Sofía Gómez Zapata | “Revisa si esta explicación es correcta y qué debo comprobar antes de utilizarla.” | Ayudó a contrastar explicaciones con los resultados obtenidos y las fuentes originales. |
| Juan José Baena García | “Ayúdame a interpretar estos resultados y a redactar una explicación clara para el informe.” | apoyar la interpretación y la comunicación de los resultados. |
Fuente: elaboración propia.
El uso de inteligencia artificial facilitó varias actividades del proyecto, especialmente la comprensión de conceptos, la revisión del código, la identificación de posibles errores y la organización de la información. También permitió explorar alternativas para presentar los resultados de manera más clara y comprensible.
Sin embargo, las herramientas no siempre ofrecieron respuestas correctas. En particular, algunas explicaciones relacionadas con los datos y los conceptos estadísticos contenían imprecisiones. Por eso fue necesario verificar las sugerencias con el código, las fuentes originales y los resultados obtenidos.
En conclusión, la IA fue una herramienta de apoyo que ayudó a agilizar el trabajo, pero no reemplazó el análisis ni las decisiones del equipo. Su aporte principal estuvo en facilitar la revisión y mejorar la claridad del proyecto, mientras que la validación de los resultados siguió siendo responsabilidad de los integrantes.
Documentamos seis hallazgos de cuatro conversaciones con Gemini del
26 de octubre de 2026, que cubren tres de las cinco categorías del
enunciado: código, datos del mundo real y conceptos (Tabla 22). No
encontramos errores demostrables en matemáticas ni en bibliografía, y no
quisimos inventarlos. Los hallazgos 1 a 4 habrían cambiado el resultado
si hubiéramos usado esas respuestas; el 5 y el 6 son imprecisiones en
respuestas cuya idea central es correcta. El documento completo, con el
prompt literal, la respuesta, cómo sospechamos, la evidencia, la
corrección y la lección de cada hallazgo, está en
docs/caceria_alucinaciones.md.
Tabla 22. Hallazgos de la cacería de la alucinación.
| # | Hallazgo | Categoría | Gravedad | Evidencia y corrección |
|---|---|---|---|---|
| 1 | El código codifica 1 = buen pagador, al revés de lo que pide el enunciado | Código | Alta | La PD, el peso de clase y la scorecard quedarían invertidos. Usamos 1 = incumplimiento (Sección 2.2). |
| 2 | El texto pide incluir las variantes “Does not meet the credit policy”, pero el código las descarta | Código, datos del mundo real | Media | Son 2,749 créditos, el 1.02 % de los etiquetados. Asignamos
explícitamente las diez categorías de loan_status y
verificamos los conteos. |
| 3 | Suma a la recuperación los cargos por mora, que se cobran antes del castigo | Datos del mundo real | Media | La LGD media casi no cambia (0.930 frente a 0.929), pero la
proporción de créditos sin recuperación baja de 46.4 % a 42.9 %. Usamos
solo recoveries − collection_recovery_fee (Sección
4.2). |
| 4 | Presenta out_prncp como el monto original del
crédito |
Datos del mundo real | Alta | En los castigados vale 0: usado como exposición, daría LGD = 1 para
todos, sin ningún error visible. La exposición es
funded_amnt − total_rec_prncp. |
| 5 | Calibración “exacta” obligatoria en IFRS 9 y Basilea, Brier “cercano a 0” y Hosmer-Lemeshow como criterio | Conceptos | Menor | El Brier se compara con su referencia (0.1886), y con 36,378 créditos de prueba la prueba de Hosmer-Lemeshow rechaza aun con errores pequeños (Paul et al., 2013). |
| 6 | Compara dos AUC por solapamiento de intervalos y propone validación cruzada sin advertir la estructura temporal | Conceptos | Menor | Con AUC calculados sobre los mismos créditos se prueba la diferencia (DeLong et al., 1988). Nuestra partición es temporal y repetimos la red con 30 semillas (Sección 3.5). |
Fuente: elaboración propia, a partir de
docs/caceria_alucinaciones.md.
El hallazgo más peligroso es el 4, porque no produce ningún error: el código corre, la LGD sale entre 0 y 1 y el modelo entrena, pero aprende una constante. Lo detectamos al revisar la distribución de la variable antes de usarla. Además, el 27 de octubre repetimos cuatro preguntas técnicas en las que sospechábamos que la IA podía fallar: la corrección de logits, las odds de referencia de la scorecard, el KS y los gradientes integrados. Acertó en lo esencial, y reprodujimos cada resultado con código antes de usarlo; el detalle está en la sección 3 del mismo documento.
Bergstra, J., & Bengio, Y. (2012). Random search for hyper-parameter optimization. Journal of Machine Learning Research, 13(10), 281–305. https://jmlr.org/papers/v13/bergstra12a.html
Breiman, L. (2001). Random forests. Machine Learning, 45(1), 5–32. https://doi.org/10.1023/A:1010933404324
Brier, G. W. (1950). Verification of forecasts expressed in terms of probability. Monthly Weather Review, 78(1), 1–3. https://doi.org/10.1175/1520-0493(1950)078<0001:VOFEIT>2.0.CO;2
DeLong, E. R., DeLong, D. M., & Clarke-Pearson, D. L. (1988). Comparing the areas under two or more correlated receiver operating characteristic curves: A nonparametric approach. Biometrics, 44(3), 837–845. https://doi.org/10.2307/2531595
Fisher, A., Rudin, C., & Dominici, F. (2019). All models are wrong, but many are useful: Learning a variable’s importance by studying an entire class of prediction models simultaneously. Journal of Machine Learning Research, 20(177), 1–81. https://jmlr.org/papers/v20/18-760.html
Hanley, J. A., & McNeil, B. J. (1982). The meaning and use of the area under a receiver operating characteristic (ROC) curve. Radiology, 143(1), 29–36. https://doi.org/10.1148/radiology.143.1.7063747
Ioffe, S., & Szegedy, C. (2015). Batch normalization: Accelerating deep network training by reducing internal covariate shift. En Proceedings of the 32nd International Conference on Machine Learning (Vol. 37, pp. 448–456). PMLR. https://proceedings.mlr.press/v37/ioffe15.html
Kingma, D. P., & Ba, J. (2015). Adam: A method for stochastic optimization. En 3rd International Conference on Learning Representations (ICLR 2015). https://arxiv.org/abs/1412.6980
LendingClub. (s. f.). What investor payments and fees are required? LendingClub Help Center. Recuperado el 27 de octubre de 2026 de https://help.lendingclub.com/hc/en-us/articles/215480768-What-fees-does-LendingClub-charge-investors-
LendingClub Corporation. (2020). Prospectus supplement No. 3320 (Formulario 424B3). U.S. Securities and Exchange Commission. https://www.sec.gov/Archives/edgar/data/1409970/000140997020001174/postsup_20200919-055201-0.htm
Loshchilov, I., & Hutter, F. (2019). Decoupled weight decay regularization. En 7th International Conference on Learning Representations (ICLR 2019). https://arxiv.org/abs/1711.05101
Papke, L. E., & Wooldridge, J. M. (1996). Econometric methods for fractional response variables with an application to 401(k) plan participation rates. Journal of Applied Econometrics, 11(6), 619–632. https://doi.org/10.1002/(SICI)1099-1255(199611)11:6<619::AID-JAE418>3.0.CO;2-1
Paul, P., Pennell, M. L., & Lemeshow, S. (2013). Standardizing the power of the Hosmer-Lemeshow goodness of fit test in large data sets. Statistics in Medicine, 32(1), 67–80. https://doi.org/10.1002/sim.5525
ranadeep. (s. f.). Credit risk analysis [Conjunto de datos]. Kaggle. Recuperado el 25 de octubre de 2026 de https://www.kaggle.com/datasets/ranadeep/credit-risk-dataset
Rumelhart, D. E., Hinton, G. E., & Williams, R. J. (1986). Learning representations by back-propagating errors. Nature, 323(6088), 533–536. https://doi.org/10.1038/323533a0
Siddiqi, N. (2006). Credit risk scorecards: Developing and implementing intelligent credit scoring. John Wiley & Sons.
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). Dropout: A simple way to prevent neural networks from overfitting. Journal of Machine Learning Research, 15(56), 1929–1958. https://jmlr.org/papers/v15/srivastava14a.html
Sundararajan, M., Taly, A., & Yan, Q. (2017). Axiomatic attribution for deep networks. En Proceedings of the 34th International Conference on Machine Learning (Vol. 70, pp. 3319–3328). PMLR. https://proceedings.mlr.press/v70/sundararajan17a.html