Tengo un proyecto de clasificación sobre un conjunto de datos desequilibrado (HomeCredit Kaggle dataset) y he elegido el clasificador Ridge (implementación de sklearn) como el más eficiente tanto en términos de tiempo como en términos de mi indicador de rendimiento (puntuación ROC AUC).
El conjunto de datos inicial está muy desequilibrado (0,92 TARGET = 0, 0,08 TARGET = 1), por lo que tuve que realizar un sobremuestreo SMOTE al conjunto de datos de entrenamiento para devolver la proporción al 50/50. Hay unas 450 características con 300k muestras.
En una entrada anterior, los comentaristas parecían dudar de los méritos del sobremuestreo. He probado diferentes proporciones (desde la proporción inicial hasta 1 o 50/50) y se ha producido una mejora lineal del AUC del ROC al aumentar la proporción y llevarla hasta 1. Así que creo que es necesario en este caso, posiblemente debido al elevado número de características.
Como se me pide que proporcione probabilidades para mis predicciones, he ampliado la clase RidgeClassifier con softmax como tal (a partir de la investigación en otro post) :
class RidgeClassifierWithProba(RidgeClassifier):
def predict_proba(self, X):
d = self.decision_function(X)
d_2d = np.c_[-d, d]
return softmax(d_2d)
Las puntuaciones finales que obtengo de mi modelo son relativamente buenas, con una puntuación ROC AUC final de 0,76 cuando se tienen en cuenta esas probabilidades (0,70 sólo con las predicciones). Los mejores Kagglers solo han sido capaces de alcanzar 0,805 durante la competición, así que creo que está bastante cerca.
El problema es que el histograma de probabilidades muestra que no hay separación entre las 2 clases, con una densidad distribuida casi normalmente:
He intentado implementar las siguientes funciones de CalibratedClassifierCV para mejorar esta distribución:
pipe_iso = CalibratedClassifierCV(final_pipe, cv=2, method="isotonic")
pipe_sig = CalibratedClassifierCV(final_pipe, cv=2, method="sigmoid")
Pero hace que la distribución de probabilidades sea aún peor, con todas ellas sesgadas hacia la clase mayoritaria :
¿Estoy haciendo algo mal? ¿Mi modelo no es lo suficientemente bueno como para tener un histograma de probabilidades separado? ¿Qué más puedo hacer para calibrar mis probabilidades?
Editar : A petición de @Ben Reiniger, he subido mi trabajo a Kaggle : Enlace a Kagggle y estoy publicando más código a continuación. Esta es mi tubería final:
#Defining our final pipeline
final_pipe = Pipeline([
('cat_encode', cat_encode),
('imputation', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
('var', boruta),
('os', ADASYN()),
('ridge',RidgeClassifierWithProba(alpha=6.967)),
], memory="./Cache/")
Si hay alguna diferencia, esta es una tubería Imbalearn y no una tubería Sklearn ya que implementa el sobremuestreo.
El codificador Cat es un transformador de columna que imputa los campos categóricos en función del número de categorías únicas (One Hot cuando nunique <= 5 y WOE cuando nunique >5). Boruta es un FunctionTransformer construido a partir de un Selector Boruta.
Aquí están mis resultados en el conjunto de pruebas (los resultados en el conjunto de entrenamiento son similares) :
Para aclarar mi afirmación sobre la puntuación ROC AUC, cuando introduzco en la función roc_auc_score sólo los resultados predichos del clasificador Ridge base, devuelve 0,7, mientras que cuando introduzco las probabilidades, devuelve 0,76 (porque suaviza la curva).
final_pipe.fit(X_train_reduced, y_train)
train_predictions = final_pipe.predict(X_train_reduced)
proba_train = final_pipe.predict_proba(X_train_reduced)[:, 1]
test_predictions = final_pipe.predict(X_test)
proba_test = final_pipe.predict_proba(X_test)[:, 1]
roc_auc_score(y_test, test_predictions) #Returns 0.7
roc_auc_score(y_test, proba_test) #Returns 0.76
Al calibrar las probabilidades con mi calibrador isotónico, obtengo el siguiente gráfico de barras "apilado", que se ve bien pero en realidad los colores son las probabilidades de cada clase. Así que, a menos que esté malinterpretando algo, significa que con un umbral de probabilidad de 0,5 esto predeciría que todos los resultados de las pruebas están en la clase mayoritaria (azul) .
test_prob = pipe_iso.predict_proba(X_test)
plt.hist(test_prob, stacked=True)
plt.show()
Para comparar, este es el mismo gráfico con mi tubería sin calibrar: