Como um Grandmaster do Kaggle, eu adoro trabalhar com o LightGBM, uma biblioteca fantástica de machine learning que se tornou uma das minhas ferramentas preferidas.

Eu sempre foco em tunar os hiperparâmetros do modelo antes de mergulhar na engenharia de features.

Ao ajustar seus hiperparâmetros primeiro, você vai espremer cada gota de desempenho do seu modelo com os dados que já tem.

Depois que você tiver os hiperparâmetros ideais, você passa para a engenharia de features.

Neste tutorial, vou ensinar tudo o que sei sobre tunar hiperparâmetros do LightGBM usando o Optuna.

Instalando LightGBM e Optuna

Instalar o LightGBM é fácil, basta executar:

pip install lightgbm

Se você tiver algum problema, verifique a documentação oficial.

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 (busca aleatória) ou passar por toda a área com uma pá (busca em grade).

A melhor parte? Você só precisa de algumas linhas de código para começar a tunar seus hiperparâmetros!

Esta otimização é tão boa quanto, se não melhor, do que a busca aleatória quando você tem mais de 2 ou 3 hiperparâmetros para ajustar.

Vou usar o conjunto de dados de Qualidade do Vinho Tinto 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

Estes dados podem ser modelados como um problema de regressão ou classificação, mas vou tratá-lo como regressão.

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

Quais Hiperparâmetros do LightGBM Devo Tunar?

Existem apenas 6 hiperparâmetros com os quais você realmente precisa se preocupar ao tunar o LightGBM.

A primeira coisa a considerar é o número de árvores que você vai treinar, também conhecido como num_iterations.

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

Então, quantas árvores você deve escolher? Depende do caso de uso do seu modelo.

Se o seu modelo precisa entregar resultados com baixa latência (por exemplo: negociação de alta frequência, previsão de cliques em anúncios), você pode 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 regra geral, comece fixando o número de árvores e depois foque em tunar a learning_rate.

Isso controla quanto cada árvore contribui para a previsão final.

Quanto mais árvores você tiver, menor deve ser a taxa de aprendizado.

O intervalo recomendado para a taxa de aprendizado é entre 0,001 e 0,1.

Em seguida, temos o num_leaves.

Isso determina a complexidade de cada árvore em seu modelo.

Você pode pensar nisso como o equivalente ao parâmetro max_depth em outros modelos baseados em árvores.

Refere-se ao número máximo de nós terminais, ou folhas, que cada árvore pode ter.

Em uma árvore de decisão, uma folha representa uma decisão ou um resultado.

Ao aumentar o num_leaves, você permite que a árvore cresça mais complexa, criando um número maior de caminhos de decisão distintos.

Isso pode levar a um modelo mais flexível que pode capturar padrões complexos nos dados.

No entanto, aumentar o número de folhas também pode fazer com que o modelo sofra overfitting dos dados de treinamento, pois terá uma quantidade menor de dados por folha.

Eu gosto de tuná-lo em potências de 2, começando de 2 e indo até 1024.

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 um subconjunto 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.

Lembre-se de definir bagging_freq como um valor positivo ou o LightGBM ignorará o subsample.

bagging_freq é a frequência com que os dados são amostrados.

Defini-lo como 1 significa reamostrar os dados antes da construção de cada árvore, que é o comportamento padrão de outros modelos baseados em árvores.

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

O hiperparâmetro colsample_bytree é outro aspecto importante a considerar ao tunar seu modelo LightGBM.

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

Esse 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 LightGBM o tornam semelhante às Random Forests, já que este último amostra linhas (com reposição) e colunas para cada árvore.

Mesmo que esses hiperparâmetros façam o LightGBM e as Random Forests parecerem semelhantes, eles são algoritmos diferentes.

LightGBM é 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.

Finalmente, o hiperparâmetro min_data_in_leaf define o número mínimo de pontos de dados que devem estar presentes em nós terminais de cada árvore.

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

Pense nisso, se você tem um nó terminal com apenas uma amostra, seu rótulo será o valor desse único ponto de dados.

Se você tem um nó terminal com 30 amostras, seu rótulo será a média desses 30 pontos de dados.

É estatisticamente melhor tomar decisões com base em mais pontos de dados.

Isso não significa que você deva definir min_data_in_leaf com 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 100.

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

Agora que você sabe quais hiperparâmetros tunar, vamos ver como fazê-lo com o Optuna.

Primeiro, definimos a função objetivo, que é a função que o Optuna tentará otimizar.

import lightgbm as lgb
from sklearn.metrics import mean_squared_error
import optuna

def objective(trial):
    params = {
        "objective": "regression",
        "metric": "rmse",
        "n_estimators": 1000,
        "verbosity": -1,
        "bagging_freq": 1,
        "learning_rate": trial.suggest_float("learning_rate", 1e-3, 0.1, log=True),
        "num_leaves": trial.suggest_int("num_leaves", 2, 2**10),
        "subsample": trial.suggest_float("subsample", 0.05, 1.0),
        "colsample_bytree": trial.suggest_float("colsample_bytree", 0.05, 1.0),
        "min_data_in_leaf": trial.suggest_int("min_data_in_leaf", 1, 100),
    }

    model = lgb.LGBMRegressor(**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

O 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 LightGBM que serão tunados pelo Optuna.

Os métodos trial.suggest_* são usados para especificar o espaço de busca para cada hiperparâmetro.

Por exemplo, a learning_rate é pesquisada dentro de uma escala logarítmica de 1e-3 a 0,1, e num_leaves é pesquisado dentro de um intervalo inteiro de 2 a 1024.

A escala logarítmica é usada para a taxa de aprendizado porque tentará mais valores próximos a 0,001, já que taxas de aprendizado pequenas com um grande número de árvores tendem a ser mais estáveis.

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

Algumas pessoas gostam de usar early stopping, que é uma técnica que interrompe o processo de treinamento quando o modelo atinge um número de árvores que não melhora a pontuação de validação.

Eu nunca tive bons resultados com esta técnica, pois ela tende a causar overfitting no conjunto de validação, então tenha cuidado com isso.

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.

Um RMSE menor significa um modelo melhor, então queremos minimizá-lo.

Se você estiver tunando um modelo de classificação com acurácia ou AUROC, você deve definir direction como maximize.

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

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

Durante a otimização, o Optuna mostrará os melhores hiperparâmetros encontrados até o momento, junto com a pontuação RMSE.

[I 2024-07-08 16:45:29,256] A new study created in memory with name: no-name-b29a9389-ff8b-4801-a5d9-e0465430c898
[I 2024-07-08 16:45:29,529] Trial 0 finished with value: 0.649160458396145 and parameters: {'learning_rate': 0.0013876062614123, 'num_leaves': 722, 'subsample': 0.46596051322789933, 'colsample_bytree': 0.9761458689722037, 'min_data_in_leaf': 71}. Best is trial 0 with value: 0.649160458396145.
[I 2024-07-08 16:45:29,837] Trial 1 finished with value: 0.5577986274436232 and parameters: {'learning_rate': 0.014455383882026855, 'num_leaves': 554, 'subsample': 0.8957311761224013, 'colsample_bytree': 0.5681963219501298, 'min_data_in_leaf': 52}. Best is trial 1 with value: 0.5577986274436232.
[I 2024-07-08 16:45:30,049] Trial 2 finished with value: 0.5773798809899955 and parameters: {'learning_rate': 0.012734650846957452, 'num_leaves': 208, 'subsample': 0.7232171778794998, 'colsample_bytree': 0.41853885945165237, 'min_data_in_leaf': 67}. Best is trial 1 with value: 0.5577986274436232.

Após o término da otimização, podemos visualizar 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.015247440377194395, 'num_leaves': 13, 'subsample': 0.13740858380047208, 'colsample_bytree': 0.4167953910212117, 'min_data_in_leaf': 15}
Melhor RMSE: 0.5582819486587627

Agora você pode pegar esses valores e retreinar seu modelo para usar em produção.

Uma dica adicional: se você perceber que a maioria das melhores tentativas está usando um hiperparâmetro específico próximo ao valor mínimo ou máximo, você deve aumentar o espaço de busca para esse hiperparâmetro.

Por exemplo, se a maioria das melhores tentativas está usando 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).

O método que você acaba de aprender permitirá que, em vez de gastar horas ajustando manualmente os hiperparâmetros, você possa focar em tarefas mais importantes, como engenharia de features.

Nunca falhou comigo em minhas competições do Kaggle ou no meu trabalho do dia a dia!

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