XGBoost per la classificazione binaria: scegliere la soglia giusta


8

Sto lavorando a un set di dati con etichetta binaria altamente squilibrato, in cui il numero di etichette vere è solo il 7% dall'intero set di dati. Ma una combinazione di funzionalità potrebbe produrre un numero di unità superiore alla media in un sottoinsieme.

Ad esempio, abbiamo il seguente set di dati con una singola funzione (colore):

180 campioni rossi - 0

20 campioni rossi - 1

300 campioni verdi - 0

100 campioni verdi - 1

Possiamo costruire un semplice albero decisionale:

                      (color)

                red /       \ green

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

P (1) = 0,2 per il set di dati complessivo

Se eseguo XGBoost su questo set di dati, è possibile prevedere probabilità non superiori a 0,25. Ciò significa che se prendo una decisione alla soglia 0,5:

  • 0 - P <0,5
  • 1 - P> = 0,5

Quindi otterrò sempre tutti i campioni etichettati come zero . Spero di aver chiaramente descritto il problema.

Ora, nel set di dati iniziale sto ottenendo il seguente diagramma (soglia sull'asse x):

inserisci qui la descrizione dell'immagine

Avere un massimo di f1_score alla soglia = 0,1. Ora ho due domande:

  • dovrei anche usare f1_score per un set di dati di tale struttura?
  • è sempre ragionevole utilizzare la soglia 0,5 per mappare le probabilità sulle etichette quando si utilizza XGBoost per la classificazione binaria?

Aggiornare. Vedo che l'argomento attira un certo interesse. Di seguito è riportato il codice Python per riprodurre l'esperimento rosso / verde utilizzando XGBoost. Produce effettivamente le probabilità previste:

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))))

Produzione:

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

Risposte:


5

Devi decidere cosa vuoi massimizzare.

La classificazione confrontando la probabilità con 0,5 è appropriata se si desidera massimizzare la precisione. Non è appropriato se si desidera massimizzare la metrica f1.

Se si desidera massimizzare l'accuratezza, prevedere sempre zero è il classificatore ottimale.

In alternativa, dato un punteggio di probabilità p, un'altra opzione è lanciare casualmente una moneta distorta; con probabilitàp, classificazione di output 1, altrimenti classificazione di output 0. Questo non prevede sempre zero. Tuttavia, probabilmente non è affatto meglio in alcun modo utile.

Se vuoi massimizzare la metrica f1, un approccio è quello di addestrare il tuo classificatore per prevedere una probabilità, quindi scegliere una soglia che massimizzi il punteggio f1. La soglia probabilmente non sarà 0,5.

Un'altra opzione è comprendere il costo degli errori di tipo I rispetto agli errori di tipo II e quindi assegnare i pesi di classe di conseguenza.


1
Voglio solo menzionare altre due cose: (a) invece del punteggio di F1, l'OP può anche usare l'accuratezza ponderata, o addirittura massimizzare una metrica di classifica come AUC ROC (b) xgboostsupporta pesi di classe, l'OP dovrebbe giocare con quelli se lui non è soddisfatto della metrica che vuole massimizzare.
Ricardo Cruz,

@RicardoCruz, grazie - buoni suggerimenti! (Ho menzionato brevemente i pesi delle lezioni - ultimo paragrafo della risposta - ma sono contento che tu l'abbia evidenziato.)
DW

Btw, un class_weights comuni utilizzati sono frequenze inverse: n_samples / (n_classes * np.bincount(y)). Questo evita che il classificatore dia più peso alle classi più popolose.
Ricardo Cruz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.