Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

3. Modelado con scikit-learn

Este capítulo corresponde a las secciones 9.10.4.3 y 9.10.4.4 del proyecto. Se entrenan los seis modelos con GridSearchCV sin Pipeline, con estos parámetros:

  • cv=3, el mismo número de pliegues que el CrossValidator de PySpark;

  • scoring="roc_auc";

  • n_jobs=-1.

Los modelos se entrenan sobre la matriz de entrenamiento del capítulo 2 y se evalúan en el conjunto de prueba común. Las puntuaciones continuas del conjunto de prueba se guardan para la prueba de DeLong.

3.1 Hardware y datos

Para interpretar los tiempos, se declara el hardware en el que se ejecutan ambos entornos (registrado en el capítulo 2). scikit-learn paraleliza la búsqueda de hiperparámetros entre los núcleos lógicos del procesador con n_jobs=-1; Spark corre en modo local[*] sobre los mismos núcleos.

Loading...
X_train (1076248, 91), X_test (269062, 91), prevalencia de default en train = 0.1996

3.2 Modelos y espacios de búsqueda

ModeloClase de scikit-learnEspacio de búsqueda
Regresión logísticaLogisticRegression(solver="lbfgs"), penalización L2C=1/(regParam⋅n)C = 1/(\text{regParam} \cdot n), con regParam ∈{10−6,10−5,10−4}\in \{10^{-6}, 10^{-5}, 10^{-4}\} y n=1.076.248n = 1.076.248
Árbol de decisiónDecisionTreeClassifiermax_depth ∈{5,10,15}\in \{5, 10, 15\}
Bosque aleatorioRandomForestClassifiern_estimators ∈{10,50,100}\in \{10, 50, 100\} × max_depth ∈{5,10,15}\in \{5, 10, 15\}
Gradient boostingGradientBoostingClassifier(learning_rate=0.1)n_estimators ∈{50,100}\in \{50, 100\} × max_depth ∈{3,5}\in \{3, 5\}
SVM linealLinearSVC(loss="hinge")el mismo CC que la regresión logística
Naive BayesGaussianNBsin búsqueda (configuración por defecto)

Equivalencias con PySpark, que se discuten con detalle en el capítulo 4:

  • La regresión logística de scikit-learn minimiza C∑iℓi+12∥w∥2C\sum_i \ell_i + \tfrac12\lVert w\rVert^2. La de Spark minimiza 1n∑iℓi+regParam2∥w∥2\tfrac1n\sum_i \ell_i + \tfrac{\text{regParam}}{2}\lVert w\rVert^2. Las dos coinciden cuando C=1/(regParam⋅n)C = 1/(\text{regParam}\cdot n). Esa relación es aproximada durante la validación cruzada, porque cada pliegue de ajuste tiene 2n/32n/3 filas y no nn.

  • LinearSVC(loss="hinge") reproduce la pérdida de Spark. El valor por defecto de scikit-learn es la hinge al cuadrado.

  • Pliegues. StratifiedKFold(3, shuffle=True, random_state=42) asigna los pliegues al azar con semilla fija, como hace CrossValidator con seed. Los pliegues concretos de cada entorno no son idénticos, pero ambos se evalúan sobre el mismo conjunto de prueba.

  • Semillas. Los modelos estocásticos (árbol, bosque y boosting) usan random_state=42.

  • Paralelismo. En el bosque se usa n_jobs=1 dentro del estimador. El paralelismo lo aporta GridSearchCV(n_jobs=-1), que ejecuta combinaciones y pliegues simultáneamente sin sobresuscribir los núcleos.

Loading...

3.3 Entrenamiento con GridSearchCV

Para cada modelo se registran:

  • best_params_, best_score_ (AUC medio de validación cruzada) y best_estimator_;

  • el tiempo de entrenamiento, que incluye la validación cruzada completa y el reajuste final con todo el conjunto de entrenamiento;

  • el tiempo de predicción sobre el conjunto de prueba.

La puntuación continua es predict_proba(X)[:, 1]. En LinearSVC, que no produce probabilidades, se usa decision_function(X).

Umbral de decisión. Con 20 % de default, el umbral por defecto (0,5 para probabilidades, 0 para LinearSVC) hace que casi nunca se prediga la clase minoritaria. Por eso las métricas que dependen de un umbral (accuracy, precision, recall, F1 y la prueba de McNemar) usan un umbral fijado solo con el conjunto de entrenamiento: el cuantil 1−π1-\pi de las puntuaciones en train, donde π=0,1996\pi = 0{,}1996 es la prevalencia de default en train. Con ese umbral, la proporción de préstamos marcados como default en entrenamiento es igual a la tasa real. El conjunto de prueba no interviene en su elección. Al final se incluyen también las métricas con el umbral por defecto.

LogReg        entrenamiento    25.4 s | predicción  0.02 s | AUC CV 0.7132 | AUC test 0.7135 | {'C': 0.9291538753149833}
DecisionTree  entrenamiento   136.2 s | predicción  0.05 s | AUC CV 0.7009 | AUC test 0.7029 | {'max_depth': 10}
RandomForest  entrenamiento  1544.8 s | predicción  4.30 s | AUC CV 0.7149 | AUC test 0.7151 | {'max_depth': 15, 'n_estimators': 100}
GradBoost     entrenamiento  7506.0 s | predicción  2.63 s | AUC CV 0.7194 | AUC test 0.7198 | {'max_depth': 5, 'n_estimators': 100}
LinearSVC     entrenamiento  1412.7 s | predicción  0.21 s | AUC CV 0.4866 | AUC test 0.4611 | {'C': 0.9291538753149833} | avisos de convergencia: 1
NaiveBayes    entrenamiento    12.4 s | predicción  0.34 s | AUC CV 0.6393 | AUC test 0.6390 | {}

3.4 Resultados de la validación cruzada

Loading...
Loading...

3.5 Métricas en entrenamiento y prueba

Loading...
Métricas en prueba con el umbral por defecto (0,5 para probabilidades; 0 para LinearSVC):
Loading...
<Figure size 1600x950 with 6 Axes>
<Figure size 800x700 with 1 Axes>
<Figure size 1400x450 with 2 Axes>
Loading...

3.6 Diagnóstico de LinearSVC(loss="hinge")

scikit-learn solo resuelve la pérdida hinge mediante el problema dual (liblinear, descenso por coordenadas). Además, liblinear regulariza el intercepto, que trata como una variable más con valor intercept_scaling=1. Para entender el resultado de LinearSVC, se compara con dos referencias entrenadas en todo el conjunto de entrenamiento. No forman parte de los seis modelos:

  • SGDClassifier(loss="hinge", alpha=regParam): la misma pérdida hinge optimizada en el primal y sin regularizar el intercepto. Es la formulación más cercana a la de Spark.

  • LinearSVC(loss="squared_hinge", dual=False): la pérdida por defecto de scikit-learn, resuelta en el primal.

Loading...

3.7 Resumen del capítulo

El mejor desempeño en scikit-learn corresponde a GradientBoostingClassifier con 100 árboles de profundidad 5, que en el conjunto de prueba alcanza un AUC de 0,7198, un AUC-PR de 0,3885 y un F1 de 0,403. Le siguen el bosque aleatorio (0,7151) y la regresión logística (0,7135). La ventaja de los modelos no lineales sobre el modelo lineal es de apenas 0,006 puntos de AUC, un resultado coherente con el EDA: la señal predictiva se concentra en unas pocas variables (int_rate, term y el puntaje FICO) cuya relación con el default es esencialmente monótona y aditiva, y un modelo lineal ya la captura en su mayor parte. En todos los modelos, salvo LinearSVC, el AUC de validación cruzada difiere del de prueba en menos de 0,003, lo que confirma que la validación fue un estimador fiable del desempeño fuera de muestra y que la partición aleatoria estratificada produjo conjuntos con la misma distribución. El único sobreajuste apreciable es el del bosque de profundidad 15, cuyo AUC de entrenamiento (0,782) supera en 0,067 al de prueba. La validación cruzada lo selecciona igualmente porque su desempeño de validación sigue siendo el mejor de su espacio de búsqueda.

La elección del umbral resulta determinante para las métricas de clasificación. Con el umbral convencional de 0,5, el recall en prueba se sitúa entre el 3 % y el 9 %, porque los modelos rara vez asignan probabilidades superiores a 0,5 a un problema con 20 % de prevalencia. El umbral fijado en entrenamiento, que iguala la tasa de positivos predichos con la prevalencia, equilibra la precisión y el recall en torno a 0,40 en los mejores modelos. Naive Bayes requiere un umbral de 0,93, síntoma de probabilidades mal calibradas: el supuesto de independencia condicional se viola con variables tan correlacionadas como las de este problema.

LinearSVC con pérdida hinge obtiene un AUC de 0,461, inferior al de un clasificador aleatorio, y su función de decisión es negativa para todos los préstamos. El diagnóstico de la sección 3.6 permite separar dos causas. La primera es propia de la pérdida: con clases muy solapadas y desbalanceadas, el óptimo de la pérdida hinge tiende a una solución casi degenerada, con coeficientes cercanos a cero y un intercepto que clasifica a todos los casos como pagados. La segunda es propia del optimizador: la formulación dual de liblinear, que además regulariza el intercepto, no conserva la dirección de los coeficientes que ordena correctamente a los prestatarios. La misma pérdida optimizada en el primal (SGDClassifier) recupera un AUC de 0,627, y la pérdida hinge al cuadrado alcanza 0,7135, prácticamente igual que la regresión logística. En este caso, la validación cruzada solo pudo escoger el valor de CC menos desfavorable entre alternativas igualmente inadecuadas.

En términos de cómputo, el entrenamiento completo con validación cruzada tomó 2 h 57 min, de los cuales el 71 % (2 h 5 min) correspondió a gradient boosting. GradientBoostingClassifier construye cada árbol de forma secuencial y en un solo hilo, de modo que el único paralelismo disponible es el que ofrece GridSearchCV al ajustar simultáneamente combinaciones y pliegues. LinearSVC consumió 24 minutos porque el dual no converge con C=0,93C = 0{,}93, mientras que la regresión logística y Naive Bayes requirieron segundos. En la prueba preliminar, HistGradientBoostingClassifier entrenó 100 iteraciones en 22 segundos, con un AUC de validación de 0,718, frente a unos 11 minutos del ajuste equivalente con GradientBoostingClassifier. Aunque el enunciado admitía esa sustitución, se conservó el estimador original por fidelidad a la especificación. La diferencia de tiempos se retoma en la reflexión final.