XGBoost para classificação binária: escolhendo o limite certo


8

Estou trabalhando em um conjunto de dados com rótulos binários altamente desequilibrado, em que o número de rótulos verdadeiros fica a apenas 7% de todo o conjunto de dados. Mas alguma combinação de recursos pode gerar um número acima da média de um em um subconjunto.

Por exemplo, temos o seguinte conjunto de dados com um único recurso (cor):

180 amostras vermelhas - 0

20 amostras vermelhas - 1

300 amostras verdes - 0

100 amostras verdes - 1

Podemos construir uma árvore de decisão simples:

                      (color)

                red /       \ green

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

P (1) = 0,2 para o conjunto de dados geral

Se eu executar o XGBoost neste conjunto de dados, ele poderá prever probabilidades não maiores que 0,25. O que significa que, se eu tomar uma decisão no limite de 0,5:

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

Então sempre receberei todas as amostras rotuladas como zeros . Espero que eu tenha descrito claramente o problema.

Agora, no conjunto de dados inicial, estou obtendo o seguinte gráfico (limite no eixo x):

insira a descrição da imagem aqui

Ter o máximo de f1_score no limite = 0,1. Agora eu tenho duas perguntas:

  • devo usar f1_score para um conjunto de dados dessa estrutura?
  • é sempre razoável usar o limite de 0,5 para mapear probabilidades para rótulos ao usar o XGBoost para classificação binária?

Atualizar. Vejo que esse tópico atrai algum interesse. Abaixo está o código Python para reproduzir o experimento em vermelho / verde usando o XGBoost. Na verdade, gera as 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))))

Resultado:

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

Respostas:


5

Você tem que decidir o que deseja maximizar.

A classificação comparando a probabilidade a 0,5 é apropriada se você deseja maximizar a precisão. Não é apropriado se você deseja maximizar a métrica F1.

Se você deseja maximizar a precisão, sempre prever zero é o classificador ideal.

Como alternativa, dada uma pontuação de probabilidade , outra opção é lançar aleatoriamente uma moeda tendenciosa; com probabilidade , classificação de saída 1, caso contrário, classificação de saída 0. Isso nem sempre prevê zero. No entanto, provavelmente não é realmente melhor de nenhuma maneira útil.pp

Se você deseja maximizar a métrica f1, uma abordagem é treinar seu classificador para prever uma probabilidade e, em seguida, escolha um limite que maximize a pontuação f1. O limite provavelmente não será 0,5.

Outra opção é entender o custo dos erros do tipo I versus erros do tipo II e atribuir pesos de classe de acordo.


1
Eu só quero mencionar mais duas coisas: (a) em vez da pontuação F1, o OP também pode usar precisão ponderada ou até maximizar uma métrica de classificação como AUC ROC (b) xgboostsuporta pesos de classe, o OP deve jogar com aqueles se ele está insatisfeito com qualquer métrica que ele queira maximizar.
Ricardo Cruz

@RicardoCruz, obrigado - boas sugestões! (Mencionei brevemente os pesos das classes - último parágrafo da resposta -, mas estou feliz que você tenha destacado isso.) #
317 DW

Aliás, um class_weights comuns utilizadas são frequências inversos: n_samples / (n_classes * np.bincount(y)). Isso evita que o classificador dê mais peso a classes mais populosas.
Ricardo Cruz
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.