Encontrar os hiperparâmetros certos para o XGBoost pode parecer como procurar uma agulha em um palheiro.

Acredite, eu já passei por isso.

O XGBoost foi um modelo crucial para vencer pelo menos duas das competições do Kaggle das quais participei.

Ao final deste tutorial, você estará equipado com as mesmas técnicas que usei para otimizar meus modelos e alcançar essas vitórias.

Vamos começar!

Instalando XGBoost e Optuna

Instalar o XGBoost é fácil, basta executar:

pip install xgboost

Ou, se você estiver usando Anaconda, execute:

conda install -c conda-forge py-xgboost

Instalar o Optuna também é fácil, basta executar:

pip install optuna

O Optuna usa uma técnica inteligente chamada otimização bayesiana para encontrar os melhores hiperparâmetros para o seu modelo.

A otimização bayesiana é como um caçador de tesouros usando um detector de metais avançado para encontrar ouro escondido, em vez de apenas cavar buracos aleatórios (random search) ou percorrer toda a área com uma pá (grid search).

A melhor parte? Você só precisa de algumas linhas de código para fazê-lo funcionar!

Usar a otimização bayesiana é uma escolha óbvia.

Geralmente é tão boa ou melhor que as alternativas quando você tem mais de 2 ou 3 hiperparâmetros para ajustar.

Vou usar o conjunto de dados Red Wine Quality da UCI.

É um conjunto de dados real com propriedades químicas de vinhos tintos onde o objetivo é prever a qualidade do vinho.

import pandas as pd
from sklearn.model_selection import train_test_split

url = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
data = pd.read_csv(url, delimiter=";")

X = data.drop("quality", axis=1)
y = data["quality"]

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
7.4 0.7 0 1.9 0.076 11 34 0.9978 3.51 0.56 9.4 5
7.8 0.88 0 2.6 0.098 25 67 0.9968 3.2 0.68 9.8 5
7.8 0.76 0.04 2.3 0.092 15 54 0.997 3.26 0.65 9.8 5
11.2 0.28 0.56 1.9 0.075 17 60 0.998 3.16 0.58 9.8 6
7.4 0.7 0 1.9 0.076 11 34 0.9978 3.51 0.56 9.4 5

Podemos tratar este problema como regressão ou classificação, mas eu vou tratá-lo como uma regressão.

O processo de tuning para o XGBoost é o mesmo para ambos os casos.

Quais Hiperparâmetros do XGBoost Devo Tunar?

Existem apenas 6 hiperparâmetros nos quais você realmente precisa se concentrar ao tunar o XGBoost.

Primeiro, temos o número de árvores que você vai treinar, também conhecido como n_estimators.

Quanto mais árvores você tiver, mais confiáveis serão suas previsões.

Então, quantas árvores você deve escolher?

Isso depende do propósito do seu modelo.

Se o seu modelo precisa entregar resultados rapidamente (por exemplo: trading de alta frequência, previsão de cliques em anúncios), você deve limitar o número de árvores para cerca de 200.

No entanto, se o seu modelo é executado uma vez por semana (por exemplo: previsão de vendas) e tem mais tempo para fazer as previsões, você pode considerar usar até 5.000 árvores.

Como diretriz geral, comece fixando o número de árvores e depois concentre-se em ajustar a learning_rate.

Ela regula quanto cada árvore contribui para a previsão final.

Quanto mais árvores você tiver, menor deve ser a learning rate.

O intervalo recomendado para a learning rate é entre 0,001 e 0,1.

Em seguida, vamos falar sobre max_depth.

Este hiperparâmetro decide a complexidade de cada árvore no seu modelo.

Mais precisamente, determina a profundidade máxima que uma árvore pode ter.

Uma árvore mais profunda significa mais caminhos de decisão e potencialmente capturar padrões mais complexos nos dados.

No entanto, aumentar a profundidade também pode fazer com que o modelo sofra com overfitting.

Eu gosto de usar um intervalo de 1 a 10.

O hiperparâmetro subsample desempenha um papel no controle da quantidade de dados usados para construir cada árvore em seu modelo.

É uma fração que varia de 0 a 1, representando a proporção do conjunto de dados a ser selecionada aleatoriamente para treinar cada árvore.

Ao usar apenas uma parte dos dados para cada árvore, o modelo pode se beneficiar da diversidade e reduzir a correlação entre as árvores, o que pode ajudar a combater o overfitting.

O intervalo que eu uso para subsample é entre 0,05 e 1.

O hiperparâmetro colsample_bytree é outro hiperparâmetro a ser considerado ao ajustar seu modelo XGBoost.

Ele determina a proporção de features a serem consideradas para cada árvore.

Este valor varia de 0 a 1, onde um valor de 1 significa que todas as features serão consideradas para cada árvore, e um valor menor indica que apenas um subconjunto de features será escolhido aleatoriamente antes de construir cada árvore.

Este método também é conhecido como Random Subspace.

Os hiperparâmetros subsample e colsample_bytree no XGBoost o tornam um pouco semelhante às Random Forests, já que este último amostra linhas (com reposição) e colunas para cada árvore.

Apesar desses hiperparâmetros fazerem o XGBoost e as Random Forests parecerem semelhantes, eles são algoritmos diferentes.

XGBoost é um método de gradient boosting, enquanto Random Forests é um método de bagging, o que significa que eles aprendem com os dados de maneiras diferentes.

Por último, o hiperparâmetro min_child_weight define a soma mínima de pesos de instância que deve estar presente em um nó filho em cada árvore.

Na regressão, isso significa simplesmente o número de observações que devem estar presentes em cada nó.

Este parâmetro ajuda a controlar a complexidade do modelo e previne o overfitting.

Isso não significa que você deva definir min_child_weight para um valor alto, pois isso tornará seu modelo menos flexível e mais propenso ao underfitting.

Eu gosto de mantê-lo na faixa de 1 a 20.

Código para Tunar Hiperparâmetros do XGBoost com Optuna

Agora que você está familiarizado com os hiperparâmetros essenciais, vamos explorar o código para otimizá-los usando o Optuna.

Primeiro, definiremos a função objetivo, que o Optuna buscará otimizar.

import xgboost as xgb
from sklearn.metrics import mean_squared_error
import optuna

def objective(trial):
    params = {
        "objective": "reg:squarederror",
        "n_estimators": 1000,
        "verbosity": 0,
        "learning_rate": trial.suggest_float("learning_rate", 1e-3, 0.1, log=True),
        "max_depth": trial.suggest_int("max_depth", 1, 10),
        "subsample": trial.suggest_float("subsample", 0.05, 1.0),
        "colsample_bytree": trial.suggest_float("colsample_bytree", 0.05, 1.0),
        "min_child_weight": trial.suggest_int("min_child_weight", 1, 20),
    }

    model = xgb.XGBRegressor(**params)
    model.fit(X_train, y_train, verbose=False)
    predictions = model.predict(X_val)
    rmse = mean_squared_error(y_val, predictions, squared=False)
    return rmse

Nosso objetivo principal é minimizar a raiz do erro quadrático médio (RMSE) no conjunto de validação.

O dicionário params dentro da função objetivo contém os hiperparâmetros do XGBoost a serem ajustados pelo Optuna.

Os métodos trial.suggest_* definem o espaço de busca para cada hiperparâmetro de acordo com os valores que sugeri acima.

Por exemplo, learning_rate é selecionada em uma escala logarítmica de 1e-3 a 0,1, e max_depth é pesquisado em um intervalo inteiro de 1 a 10.

A escala logarítmica é aplicada ao learning rate para testar mais valores próximos a 0,001, já que taxas de aprendizado menores combinadas com um alto número de árvores geralmente produzem modelos mais estáveis.

Uma vez que o modelo é treinado, ele gera previsões no conjunto de validação e calcula o RMSE.

Para executar a otimização, criamos um objeto de estudo e passamos a função objetivo para o método optimize.

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=30)

O parâmetro direction especifica se queremos minimizar ou maximizar a função objetivo.

Como um RMSE menor indica um modelo melhor, nosso objetivo é minimizá-lo.

Se você estiver tunando um modelo de classificação com precisão ou AUROC, defina a direction para maximizar.

O parâmetro n_trials define o número de vezes que o modelo será treinado com diferentes valores de hiperparâmetros.

Na prática, cerca de 30 tentativas geralmente são suficientes para encontrar um conjunto sólido de hiperparâmetros.

Durante a otimização, o Optuna exibirá os melhores hiperparâmetros descobertos até o momento, juntamente com a pontuação RMSE.

[I 2023-04-09 10:37:16,565] A new study created in memory with name: no-name-8560b24e-3a91-48de-8449-c32977dd4c2b
[I 2023-04-09 10:37:49,060] Trial 0 finished with value: 0.5532276610157627 and parameters: {'learning_rate': 0.010958651631216162, 'max_depth': 7, 'subsample': 0.5354179500853077, 'colsample_bytree': 0.7165765694026183, 'min_child_weight': 12}. Best is trial 0 with value: 0.5532276610157627.
[I 2023-04-09 10:37:53,790] Trial 1 finished with value: 0.6019606330122452 and parameters: {'learning_rate': 0.017141615266207846, 'max_depth': 2, 'subsample': 0.5707056227081352, 'colsample_bytree': 0.31105599394671785, 'min_child_weight': 6}. Best is trial 0 with value: 0.5532276610157627.
[I 2023-04-09 10:37:58,257] Trial 2 finished with value: 0.5745420558557753 and parameters: {'learning_rate': 0.05475472895519491, 'max_depth': 3, 'subsample': 0.8723964423668571, 'colsample_bytree': 0.5917021002963235, 'min_child_weight': 18}. Best is trial 0 with value: 0.5532276610157627.

Depois que a otimização estiver concluída, podemos exibir os melhores hiperparâmetros e a pontuação RMSE.

print('Melhores hiperparâmetros:', study.best_params)
print('Melhor RMSE:', study.best_value)

Melhores hiperparâmetros: {'learning_rate': 0.01742253012219986, 'max_depth': 9, 'subsample': 0.6685330381926933, 'colsample_bytree': 0.669833311520857, 'min_child_weight': 14}
Melhor RMSE: 0.5404657574376048

Agora você pode retreinar o modelo com os melhores hiperparâmetros e usá-lo para fazer previsões em novos dados.

Uma dica adicional: se a maioria das melhores combinações de hiperparâmetros estiver concentrada em uma determinada região do espaço de busca, você pode ajustar o intervalo de busca nesta região.

Isso indica que seus limites originais podem não ser os melhores para cobrir o espaço de hiperparâmetros que vão gerar o melhor modelo.

Por exemplo, se a maioria das melhores tentativas usar learning_rate próximo a 0,001, você provavelmente deve reiniciar a otimização com o espaço de busca trial.suggest_float("learning_rate", 1e-4, 1e-2, log=True).

Usando este método, você obterá um excelente conjunto de hiperparâmetros, permitindo que você se concentre em outras tarefas com um maior impacto no desempenho do modelo, como engenharia de features.

Esta abordagem tem se mostrado consistentemente bem-sucedida em minhas competições do Kaggle e no trabalho diário!

Seja o primeiro a saber das novidades em Machine Learning. Me siga no LinkedIn.