XGBoost para clasificación binaria: elegir el umbral correcto


8

Estoy trabajando en un conjunto de datos con etiquetas binarias altamente desequilibradas, donde el número de etiquetas verdaderas es solo del 7% de todo el conjunto de datos. Pero alguna combinación de características podría producir un número de unidades superior al promedio en un subconjunto.

Por ejemplo, tenemos el siguiente conjunto de datos con una sola característica (color):

180 muestras rojas - 0

20 muestras rojas - 1

300 muestras verdes - 0

100 muestras verdes - 1

Podemos construir un árbol de decisión simple:

                      (color)

                red /       \ green

 P(1 | red) = 0.1              P(1 | green) = 0.25

P (1) = 0.2 para el conjunto de datos general

Si ejecuto XGBoost en este conjunto de datos, puede predecir probabilidades no mayores que 0.25. Lo que significa que si tomo una decisión en el umbral de 0.5:

  • 0 - P <0.5
  • 1 - P> = 0.5

Entonces siempre obtendré todas las muestras etiquetadas como ceros . Espero haber descrito claramente el problema.

Ahora, en el conjunto de datos inicial obtengo el siguiente gráfico (umbral en el eje x):

ingrese la descripción de la imagen aquí

Tener un máximo de f1_score en el umbral = 0.1. Ahora tengo dos preguntas:

  • ¿Debería incluso usar f1_score para un conjunto de datos de tal estructura?
  • ¿Es siempre razonable usar un umbral de 0.5 para asignar probabilidades a las etiquetas cuando se usa XGBoost para la clasificación binaria?

Actualizar. Veo que el tema atrae algo de interés. A continuación se muestra el código de Python para reproducir el experimento rojo / verde usando XGBoost. Realmente genera las probabilidades esperadas:

from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
import numpy as np

X0_0 = np.zeros(180) # red - 0
Y0_0 = np.zeros(180)

X0_1 = np.zeros(20) # red - 1
Y0_1 = np.ones(20)

X1_0 = np.ones(300) # green - 0
Y1_0 = np.zeros(300)

X1_1 = np.ones(100) # green  - 1
Y1_1 = np.ones(100)

X = np.concatenate((X0_0, X0_1, X1_0, Y1_1))
Y = np.concatenate((Y0_0, Y0_1, Y1_0, Y1_1))

# reshaping into 2-dim array
X = X.reshape(-1, 1)

import xgboost as xgb

xgb_dmat = xgb.DMatrix(X_train, label=y_train)

param = {'max_depth': 1,
         'eta': 0.01,
         'objective': 'binary:logistic',
         'eval_metric': 'error',
         'nthread': 4}

model = xgb.train(param, xg_mat, 400)

X0_sample = np.array([[0]])
X1_sample = np.array([[1]])

print('P(1 | red), predicted: ' + str(model.predict(xgb.DMatrix(X0_sample))))
print('P(1 | green), predicted: ' + str(model.predict(xgb.DMatrix(X1_sample))))

Salida:

P(1 | red), predicted: [ 0.1073855]
P(1 | green), predicted: [ 0.24398108]

Respuestas:


5

Tienes que decidir qué quieres maximizar.

Clasificar comparando la probabilidad con 0.5 es apropiado si desea maximizar la precisión. No es apropiado si desea maximizar la métrica f1.

Si desea maximizar la precisión, siempre predecir cero es el clasificador óptimo.

Alternativamente, dado un puntaje de probabilidad p, otra opción es voltear aleatoriamente una moneda sesgada; con probabilidadp, clasificación de salida 1, de lo contrario clasificación de salida 0. Esto no siempre predice cero. Sin embargo, probablemente no sea realmente mejor de ninguna manera útil.

Si desea maximizar la métrica f1, un enfoque es entrenar a su clasificador para predecir una probabilidad, luego elija un umbral que maximice la puntuación f1. El umbral probablemente no será 0.5.

Otra opción es comprender el costo de los errores de tipo I frente a los errores de tipo II, y luego asignar los pesos de clase en consecuencia.


1
Solo quiero mencionar dos cosas más: (a) en lugar del puntaje F1, el OP también puede usar precisión ponderada, o incluso maximizar una métrica de clasificación como AUC ROC (b) xgboostadmite pesos de clase, el OP debería jugar con ellos si él no está satisfecho con la métrica que quiera maximizar.
Ricardo Cruz

@RicardoCruz, gracias, ¡buenas sugerencias! (Mencioné brevemente los pesos de la clase, el último párrafo de la respuesta, pero me alegra que lo haya resaltado).
DW

Por cierto, un class_weights comunes utilizadas son frecuencias inversas: n_samples / (n_classes * np.bincount(y)). Esto evita que el clasificador otorgue más peso a las clases más pobladas.
Ricardo Cruz
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.