Otimizando o Ajuste de Modelos de Aprendizado de Máquina: Adotando RandomizedSearchCV em vez de GridSearchCV
No mundo dinâmico do aprendizado de máquina, o ajuste de modelos é fundamental para alcançar um desempenho ideal. Tradicionalmente, o GridSearchCV tem sido o método preferido para a otimização de hiperparâmetros. No entanto, à medida que os conjuntos de dados aumentam em tamanho e complexidade, o GridSearchCV pode se tornar um gargalo intensivo em recursos. Surge então o RandomizedSearchCV — uma alternativa mais eficiente que oferece resultados comparáveis com uma sobrecarga computacional significativamente reduzida. Este artigo explora as complexidades de ambos os métodos, destacando as vantagens de adotar o RandomizedSearchCV em projetos de dados de grande escala.
Sumário
- Entendendo o GridSearchCV e Suas Limitações
- Introduzindo o RandomizedSearchCV
- Análise Comparativa: GridSearchCV vs. RandomizedSearchCV
- Preparação e Pré-processamento de Dados
- Construção de Modelos e Ajuste de Hiperparâmetros
- Resultados e Avaliação de Desempenho
- Conclusão: Quando Escolher o RandomizedSearchCV
- Recursos e Leituras Adicionais
Entendendo o GridSearchCV e Suas Limitações
GridSearchCV é uma ferramenta poderosa do scikit-learn usada para ajuste de hiperparâmetros. Ele busca exaustivamente por um conjunto predefinido de hiperparâmetros para identificar a combinação que oferece o melhor desempenho do modelo com base em uma métrica especificada.
Características Principais:
- Busca Exaustiva: Avalia todas as combinações possíveis na grade de parâmetros.
- Integração com Validação Cruzada: Utiliza validação cruzada para garantir a robustez do modelo.
- Seleção do Melhor Estimador: Retorna o melhor modelo com base nas métricas de desempenho.
Limitações:
- Intensivo em Computação: À medida que a grade de parâmetros cresce, o número de combinações aumenta exponencialmente, levando a tempos de computação mais longos.
- Consumo de Memória: Lidar com grandes conjuntos de dados com inúmeras combinações de parâmetros pode sobrecarregar os recursos do sistema.
- Retornos Decrescentes: Nem todas as combinações de parâmetros contribuem significativamente para o desempenho do modelo, tornando a busca exaustiva ineficiente.
Exemplo Concreto: Processar um conjunto de dados com mais de 129.000 registros usando GridSearchCV levou aproximadamente 12 horas, mesmo com hardware robusto. Isso demonstra sua impraticabilidade para aplicações de grande escala.
Introduzindo o RandomizedSearchCV
RandomizedSearchCV oferece uma alternativa pragmática ao GridSearchCV ao amostrar um número fixo de combinações de hiperparâmetros a partir das distribuições especificadas, em vez de avaliar todas as combinações possíveis.
Vantagens:
- Eficiência: Reduz significativamente o tempo de computação ao limitar o número de avaliações.
- Flexibilidade: Permite especificar distribuições para cada hiperparâmetro, possibilitando uma amostragem mais diversificada.
- Escalabilidade: Melhor adaptado para grandes conjuntos de dados e modelos complexos.
Como Funciona:
O RandomizedSearchCV seleciona aleatoriamente um subconjunto de combinações de hiperparâmetros, as avalia usando validação cruzada e identifica a combinação com melhor desempenho com base na métrica escolhida.
Análise Comparativa: GridSearchCV vs. RandomizedSearchCV
Aspecto | GridSearchCV | RandomizedSearchCV |
---|---|---|
Método de Busca | Exaustiva | Amostragem Aleatória |
Tempo de Computação | Alto | Baixo a Médio |
Uso de Recursos | Alto | Moderado a Baixo |
Desempenho | Potencialmente Melhor | Comparável com Menos Esforço |
Flexibilidade | Combinações Fixas | Amostragem Baseada em Probabilidade |
Visualização: Na prática, o RandomizedSearchCV pode reduzir o tempo de ajuste de modelos de horas para meros minutos sem uma queda significativa no desempenho.
Preparação e Pré-processamento de Dados
Um pré-processamento de dados eficaz estabelece a base para um treinamento de modelo bem-sucedido. Aqui está um guia passo a passo baseado no Jupyter Notebook fornecido.
Carregando o Conjunto de Dados
O conjunto de dados utilizado é o Satisfação de Passageiros de Aeronova do Kaggle. Ele contém 5.000 registros com 23 características relacionadas às experiências dos passageiros e níveis de satisfação.
1 2 3 4 5 6 |
import pandas as pd import seaborn as sns # Loading the small dataset data = pd.read_csv('Airline2_tiny.csv') print(data.shape) # Output: (4999, 23) |
Tratamento de Dados Faltantes
Dados Numéricos
Valores numéricos ausentes são imputados usando a estratégia da média.
1 2 3 4 5 6 7 |
import numpy as np from sklearn.impute import SimpleImputer imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean') numerical_cols = list(np.where((X.dtypes == np.int64) | (X.dtypes == np.float64))[0]) imp_mean.fit(X.iloc[:, numerical_cols]) X.iloc[:, numerical_cols] = imp_mean.transform(X.iloc[:, numerical_cols]) |
Dados Categóricos
Valores categóricos ausentes são imputados usando a estratégia do mais frequente.
1 2 3 4 |
imp_mode = SimpleImputer(missing_values=np.nan, strategy='most_frequent') string_cols = list(np.where((X.dtypes == 'object'))[0]) imp_mode.fit(X.iloc[:, string_cols]) X.iloc[:, string_cols] = imp_mode.transform(X.iloc[:, string_cols]) |
Codificação de Variáveis Categóricas
Características categóricas são codificadas usando uma combinação de One-Hot Encoding e Label Encoding com base no número de categorias únicas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, LabelEncoder def OneHotEncoderMethod(indices, data): columnTransformer = ColumnTransformer([('encoder', OneHotEncoder(), indices )], remainder='passthrough') return columnTransformer.fit_transform(data) def LabelEncoderMethod(series): le = LabelEncoder() le.fit(series) return le.transform(series) def EncodingSelection(X, threshold=10): string_cols = list(np.where((X.dtypes == 'object'))[0]) one_hot_encoding_indices = [] for col in string_cols: length = len(pd.unique(X[X.columns[col]])) if length == 2 or length > threshold: X[X.columns[col]] = LabelEncoderMethod(X[X.columns[col]]) else: one_hot_encoding_indices.append(col) X = OneHotEncoderMethod(one_hot_encoding_indices, X) return X # Apply encoding X = EncodingSelection(X) print(X.shape) # Output: (4999, 24) |
Seleção de Características
Selecionar as características mais relevantes melhora o desempenho do modelo e reduz a complexidade.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from sklearn.feature_selection import SelectKBest, chi2 from sklearn.preprocessing import MinMaxScaler kbest = SelectKBest(score_func=chi2, k='all') MMS = MinMaxScaler() K_features = 10 x_temp = MMS.fit_transform(X) x_temp = kbest.fit(x_temp, y) best_features = np.argsort(x_temp.scores_)[-K_features:] features_to_delete = np.argsort(x_temp.scores_)[:-K_features] X = np.delete(X, features_to_delete, axis=1) print(X.shape) # Output: (4999, 10) |
Divisão entre Treino e Teste
Dividir o conjunto de dados garante que o modelo seja avaliado em dados não vistos, facilitando métricas de desempenho imparciais.
1 2 3 4 5 |
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=1) print(X_train.shape) # Output: (3999, 10) print(X_test.shape) # Output: (1000, 10) |
Escalonamento de Características
Escalonar as características garante que todas as características contribuam igualmente para o desempenho do modelo.
1 2 3 4 5 6 |
from sklearn.preprocessing import StandardScaler sc = StandardScaler(with_mean=False) sc.fit(X_train) X_train = sc.transform(X_train) X_test = sc.transform(X_test) |
Construção de Modelos e Ajuste de Hiperparâmetros
Com os dados pré-processados, é hora de construir e otimizar vários modelos de aprendizado de máquina usando o RandomizedSearchCV.
K-Nearest Neighbors (KNN)
O KNN é um algoritmo de aprendizado simples e baseado em instâncias.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import RandomizedSearchCV, StratifiedKFold model = KNeighborsClassifier() params = { 'n_neighbors': [4, 5, 6, 7], 'leaf_size': [1, 3, 5], 'algorithm': ['auto', 'kd_tree'], 'weights': ['uniform', 'distance'] } cv = StratifiedKFold(n_splits=2) random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: KNeighborsClassifier(leaf_size=1) print("Best score", random_search_cv.best_score_) # Output: 0.8774673417446253 |
Regressão Logística
Um modelo probabilístico usado para tarefas de classificação binária.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from sklearn.linear_model import LogisticRegression model = LogisticRegression() params = { 'solver': ['newton-cg', 'lbfgs', 'liblinear'], 'penalty': ['l1', 'l2'], 'C': [100, 10, 1.0, 0.1, 0.01] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: LogisticRegression(C=0.01) print("Best score", random_search_cv.best_score_) # Output: 0.8295203666687819 |
Gaussian Naive Bayes (GaussianNB)
Um classificador probabilístico simples, mas eficaz, baseado no teorema de Bayes.
1 2 3 4 5 6 7 8 |
from sklearn.naive_bayes import GaussianNB from sklearn.metrics import accuracy_score, classification_report model_GNB = GaussianNB() model_GNB.fit(X_train, y_train) y_pred = model_GNB.predict(X_test) print(accuracy_score(y_pred, y_test)) # Output: 0.84 print(classification_report(y_pred, y_test)) |
Saída:
1 2 3 4 5 6 7 8 |
precision recall f1-score support 0 0.86 0.86 0.86 564 1 0.82 0.81 0.82 436 accuracy 0.84 1000 macro avg 0.84 0.84 0.84 1000 weighted avg 0.84 0.84 0.84 1000 |
Support Vector Machine (SVM)
Um classificador robusto eficaz em espaços de alta dimensionalidade.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from sklearn.svm import SVC model = SVC() params = { 'kernel': ['linear', 'poly', 'rbf', 'sigmoid'], 'C': [1, 5, 10], 'degree': [3, 8], 'coef0': [0.01, 10, 0.5], 'gamma': ['auto', 'scale'] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: SVC(C=10, coef0=0.5, degree=8) print("Best score", random_search_cv.best_score_) # Output: 0.9165979221213969 |
Árvore de Decisão
Um modelo hierárquico que toma decisões com base em divisões de características.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from sklearn.tree import DecisionTreeClassifier model = DecisionTreeClassifier() params = { 'max_leaf_nodes': list(range(2, 100)), 'min_samples_split': [2, 3, 4] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: DecisionTreeClassifier(max_leaf_nodes=30, min_samples_split=4) print("Best score", random_search_cv.best_score_) # Output: 0.9069240944070234 |
Random Forest
Um método de ensemble que utiliza múltiplas árvores de decisão para melhorar o desempenho preditivo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from sklearn.ensemble import RandomForestClassifier model = RandomForestClassifier() params = { 'bootstrap': [True], 'max_depth': [80, 90, 100, 110], 'max_features': [2, 3], 'min_samples_leaf': [3, 4, 5], 'min_samples_split': [8, 10, 12], 'n_estimators': [100, 200, 300, 1000] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: RandomForestClassifier(max_leaf_nodes=96, min_samples_split=3) print("Best score", random_search_cv.best_score_) # Output: 0.9227615146702333 |
AdaBoost
Um método de ensemble de boosting que combina múltiplos classificadores fracos para formar um classificador forte.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from sklearn.ensemble import AdaBoostClassifier model = AdaBoostClassifier() params = { 'n_estimators': np.arange(10, 300, 10), 'learning_rate': [0.01, 0.05, 0.1, 1] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: AdaBoostClassifier(learning_rate=0.1, n_estimators=200) print("Best score", random_search_cv.best_score_) # Output: 0.8906331862757826 |
XGBoost
Um framework otimizado de gradient boosting conhecido por seu desempenho e velocidade.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import xgboost as xgb from sklearn.metrics import accuracy_score, classification_report model = xgb.XGBClassifier() params = { 'min_child_weight': [1, 5, 10], 'gamma': [0.5, 1, 1.5, 2, 5], 'subsample': [0.6, 0.8, 1.0], 'colsample_bytree': [0.6, 0.8, 1.0], 'max_depth': [3, 4, 5], 'n_estimators': [100, 500, 1000], 'learning_rate': [0.01, 0.3, 0.5, 0.1], 'reg_lambda': [1, 2] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # Output: XGBClassifier with best parameters print("Best score", random_search_cv.best_score_) # Output: 0.922052180776655 # Model Evaluation model_best = random_search_cv.best_estimator_ model_best.fit(X_train, y_train) y_pred = model_best.predict(X_test) print(accuracy_score(y_pred, y_test)) # Output: 0.937 print(classification_report(y_pred, y_test)) |
Saída:
1 2 3 4 5 6 7 8 |
precision recall f1-score support 0 0.96 0.93 0.95 583 1 0.91 0.94 0.93 417 accuracy 0.94 1000 macro avg 0.93 0.94 0.94 1000 weighted avg 0.94 0.94 0.94 1000 |
Resultados e Avaliação de Desempenho
A eficácia do RandomizedSearchCV é evidente a partir do desempenho dos modelos:
- KNN alcançou um F1-score de ~0.877.
- Regressão Logística entregou um F1-score de ~0.830.
- GaussianNB manteve uma acurácia de 84%.
- SVM destacou-se com um impressionante F1-score de ~0.917.
- Árvore de Decisão obteve um F1-score de ~0.907.
- Random Forest liderou com um F1-score de ~0.923.
- AdaBoost alcançou um F1-score de ~0.891.
- XGBoost excelou com um F1-score de ~0.922 e uma acurácia de 93,7%.
Principais Observações:
- RandomForestClassifier e XGBoost demonstraram desempenho superior.
- RandomizedSearchCV reduziu efetivamente o tempo de computação de mais de 12 horas (GridSearchCV) para meros minutos sem comprometer a acurácia do modelo.
Conclusão: Quando Escolher o RandomizedSearchCV
Enquanto o GridSearchCV oferece um ajuste exaustivo de hiperparâmetros, suas demandas computacionais podem ser proibitivas para conjuntos de dados grandes. O RandomizedSearchCV surge como uma solução pragmática, equilibrando eficiência e desempenho. É particularmente vantajoso quando:
- Tempo é uma Restrição: Ajuste rápido do modelo é essencial.
- Recursos Computacionais Limitados: Reduz a carga sobre os recursos do sistema.
- Espaços de Hiperparâmetros de Alta Dimensionalidade: Simplifica o processo de busca.
A adoção do RandomizedSearchCV pode agilizar o fluxo de trabalho de aprendizado de máquina, permitindo que os profissionais se concentrem na interpretação e implantação do modelo em vez de procedimentos de ajuste demorados.
Recursos e Leituras Adicionais
- Documentação Scikit-learn: RandomizedSearchCV
- Kaggle: Conjunto de Dados de Satisfação de Passageiros de Aeronova
- Documentação Oficial do XGBoost
- Um Guia Completo para Ajuste de Hiperparâmetros
Ao aproveitar o RandomizedSearchCV, os profissionais de aprendizado de máquina podem alcançar um ajuste de modelo eficiente e eficaz, garantindo soluções escaláveis e de alto desempenho em aplicações orientadas por dados.