Como usar o GridSearchCV

  • AH Uyekita
  • Thursday, Mar 21, 2019
  • Estimated reading time: 7 min

blog-image

binder badge

O GridSearchCV é uma ferramenta usada para automatizar o processo de ajuste dos parâmetros de um algoritmo, pois ele fará de maneira sistemática diversas combinações dos parâmetros e depois de avaliá-los os armazenará num único objeto.

Foi disponinilizado o Jupter Notebook com detalhes pormenorizados do uso do GridSearchCV, bem como exemplos didáticos.



GridSearchCV

O GridSearchCV é um módulo do Scikit Learn (Leia mais sobre ele aqui) e é amplamente usado para automatizar grande parte do processo de tuning. O objetivo primário do GridSearchCV é a criação de combinações de parâmetros para posteriormente avaliá-las.

Jupyter Notebook

Há um arquivo Jupyter Notebook para acompanhar esse post com todos os detalhes de codificação.

Dataset

Será usado um toy dataset presente no próprio Scikit Learn para elucidação do GridSearchCV.

# Importação do dataset usado como exemplo
from sklearn import datasets

# Dados de Câncer de mama.
cancer = datasets.load_breast_cancer()

# Criação do dataset features e vetor labels.
features = cancer.data
labels = cancer.target

Classifier

Adota-se como algoritmo para ajustar os parâmetros o AdaBoost.

# Importação do AdaBoost.
from sklearn.ensemble import AdaBoostClassifier

# Uso do constructor do AdaBoost para criar um classifier.
clf = AdaBoostClassifier() # Sem nada dentro, pois vamos "variar" os parâmetros.

Criação de Combinações de Parâmetros

Com base numa “lista”” de parâmetros o GridSearchCV criará as combinações e depois as avaliará, por exemplo, quero testar alguns valores de parâmetros de um AdaBoost. Os valores desta “lista” foram arbitrariamente escolhidos e sem nenhum critério técnico, o objetivo é encontrar valores otimizados.

# Exemplo dos parâmetros que quero testar.
parametros = {'n_estimators':[1, 5, 10],
              'learning_rate':[0.1, 1, 2]}

Note que foram dados 3 valores para n_estimators e 3 valores para learning_rate, logo haverá 9 combinações possíveis.

$$Combinacoes = 3 \cdot 3 = 9 \tag{1}$$

Possíveis combinações:

# Todas as combinações possíveis.
# combinacao_n = [n_estimators, learning_rate]
combinacao_1 = [1, 0.1]
combinacao_2 = [1, 1]
combinacao_3 = [1, 2]
combinacao_4 = [5, 0.1]
combinacao_5 = [5, 1]
combinacao_6 = [5, 2]
combinacao_7 = [10, 0.1]
combinacao_8 = [10, 1]
combinacao_9 = [10, 2]

Portanto, sabe-se que o resultado do GridSearchCV deverá ter 9 linhas e a maneira de acessar essas informações é a partir do atributo .cv_results_.

Um exemplo de uso do GridsearchCV é exposto abaixo.

# Criação do objeto do GridSearchCV.
grid = GridSearchCV(estimator = clf,          # É o nosso AdaBoost.
                    param_grid = parametros,  # É aquele dicionário com valores para serem testados.
                    scoring = 'f1',           # Arbitrariamente escolhi o f1, adiante explico com detalhes.
                    cv = 20)                  # Idem, arbitratiamente escolhi 20 e adiante será explanado.

Após a criação do objeto do GridSearchCV, pode-se treinar e imprimir os resultados.

# Treinando o grid.
grid.fit(features, labels)

# Imprimindo os resultados.
pd.DataFrame(grid.cv_results_)

Figura 1 - Anaconda Navigator.

Há muitas colunas nesse resultado, então vamos fazer um subset escolhendo as colunas de interesse.

# Imprime colunas de interesse.
pd.DataFrame(grid.cv_results_)[['params','rank_test_score','mean_test_score']]

Figura 1 - Anaconda Navigator.

Conforme a tabela acima, o melhor resultado de f1 é produzido pelos parâmetros {'learning_rate': 1, 'n_estimators': 10}. O atributo .best_params_ pode ser usado também para obter os melhores parâmetros.

# Imprime os parâmetros que produziram o ".best_score_".
grid.best_params_

Bem como é possível imprimir o valor de f1 usando o atributo .best_score_.

# Imprimindo o score.
grid.best_score_

Que no caso será 0.9622818059600856

Cross Validation

Uma pequena revisão.

Note que o objeto grid foi definido com cv = 20 e quando cv é um número inteiro o GridSearchCV executa um StratifiedKFolds, isso quer dizer que o dataset foi divido em 20 partes (ou folds) e cada parte foi usada como test em uma simulação.

# Exemplo para o uso do StratifiedKFolds com 5 folds.
grid_2 = GridSearchCV(estimator = clf,
                      param_grid = parametros,
                      cv = 5,
                      scoring = 'f1')

# Imprime o f1
grid_2.fit(features,labels).best_score_

O cv também aceita a atribuição de um objeto do StratifiedShuffleSplit, neste caso é necessário a definição da quantidade de n_splits e o tamanho do dataset de testes.

# Exemplo para o uso do StratifiedShuffleSplit.

# Importação do módulo do StratifiedShuffleSplit.
from sklearn.model_selection import StratifiedShuffleSplit

# Usando o constructor para criar o objeto sss
sss = StratifiedShuffleSplit(n_splits = 20,       # 20 simulações.
                             test_size = 0.2,     # 20% do dataset será de testes.
                             random_state = 42)   # Permitir a reprodutibilidade.

# Criando um objeto do GridSearchCV
grid_3 = GridSearchCV(estimator = clf,
                      param_grid = parametros,
                      cv = sss,
                      scoring = 'f1')

# Imprime o f1
grid_3.fit(features,labels).best_score_

Por fim, há a opção de não usar o cv do GridSearchCV, isto seria feito omitindo-o.

# Exemplo do GridSearchCV sem cv. Demanda um cv manual.

# Criando um objeto do GridSearchCV sem cv.
grid_4 = GridSearchCV(estimator = clf,
                      param_grid = parametros,
                      scoring = 'f1')

# Imprime o f1
grid_4.fit(features,labels).best_score_

Note que nessas alternativas de cross validation o objetivo é usar métricas para a escolha do modelo que não sejam superestimadas, evitando assim o problema de overfitting.

Scoring

Cada simulação terá como base de avaliação o scoring, e a configuração básica seria a definição de uma das métricas:

Logo, os resultados apresentados no atributo .cv_results_ cujas colunas possuam o sufixo _score referem-se ao scoring definido, para os exemplos anteriores usou-se o f1.

Contudo, há casos onde se necessita a avaliação de não só de um parâmetro, mas de vários. Dessa maneira, introduz-se o make_scorer (leia a documentação do make_scorer), com esse módulo é possível fazer com que o GridSearchCV calcule diversos parâmetros.

# Importando o Make Scorer
from sklearn.metrics import make_scorer

# Importando os módulos de cálculo de métricas
from sklearn.metrics import precision_score
from sklearn.metrics import accuracy_score
from sklearn.metrics import recall_score
from sklearn.metrics import fbeta_score

# Criando um dicionário com as métricas que desejo calcular.
meus_scores = {'accuracy' :make_scorer(accuracy_score),
               'recall'   :make_scorer(recall_score),
               'precision':make_scorer(precision_score),
               'f1'       :make_scorer(fbeta_score, beta = 1)}

# Exemplo para o uso scoring igual ao meus_scores.
grid_5 = GridSearchCV(estimator = clf,
                      param_grid = parametros,
                      cv = 5,
                      scoring = meus_scores,   # É o meus_scores
                      refit = 'f1')            # Observe que foi configurado para f1

Espera-se que o grid_5 possua novas colunas das quais tenha sufixos accuracy, recall, precision e f1. Vamos imprimir os nomes das colunas.

# Imprime os nomes das colunas que estão armazenadas no grid_5.
pd.DataFrame(grid_5.cv_results_).columns.tolist()
['mean_fit_time',
 'std_fit_time',
 'mean_score_time',
 'std_score_time',
 'param_learning_rate',
 'param_n_estimators',
 'params',
 'split0_test_accuracy',
 'split1_test_accuracy',
 'split2_test_accuracy',
 'split3_test_accuracy',
 'split4_test_accuracy',
 'mean_test_accuracy',
 'std_test_accuracy',
 'rank_test_accuracy',
 'split0_train_accuracy',
 'split1_train_accuracy',
 'split2_train_accuracy',
 'split3_train_accuracy',
 'split4_train_accuracy',
 'mean_train_accuracy',
 'std_train_accuracy',
 'split0_test_recall',
 'split1_test_recall',
 'split2_test_recall',
 'split3_test_recall',
 'split4_test_recall',
 'mean_test_recall',
 'std_test_recall',
 'rank_test_recall',
 'split0_train_recall',
 'split1_train_recall',
 'split2_train_recall',
 'split3_train_recall',
 'split4_train_recall',
 'mean_train_recall',
 'std_train_recall',
 'split0_test_precision',
 'split1_test_precision',
 'split2_test_precision',
 'split3_test_precision',
 'split4_test_precision',
 'mean_test_precision',
 'std_test_precision',
 'rank_test_precision',
 'split0_train_precision',
 'split1_train_precision',
 'split2_train_precision',
 'split3_train_precision',
 'split4_train_precision',
 'mean_train_precision',
 'std_train_precision',
 'split0_test_f1',
 'split1_test_f1',
 'split2_test_f1',
 'split3_test_f1',
 'split4_test_f1',
 'mean_test_f1',
 'std_test_f1',
 'rank_test_f1',
 'split0_train_f1',
 'split1_train_f1',
 'split2_train_f1',
 'split3_train_f1',
 'split4_train_f1',
 'mean_train_f1',
 'std_train_f1']

Conforme esperado houve o cálculo de todas as métricas definidas no dicionário meus_scores. Uma maneira de facilitar a análise é fazendo o subset dos resultados para apenas imprimirem as colunas de interesse.

# Fazendo uma subamostragem. 
pd.DataFrame(grid_5.cv_results_)[['params',
                                  'mean_test_recall',
                                  'mean_test_precision',
                                  'mean_test_f1']]

Figura 3 - Make Scorer.

Vamos visualizar quais são os melhores parâmetros com base no refit definido.

# Imprime os melhores parâmetros.
grid_5.fit(features,labels).best_params_
{'learning_rate': 0.5, 'n_estimators': 7}

Note que quando há apenas uma métrica é fácil encontrar a melhor solução, pois você escolherá aquele conjunto de parâmetros que gerou a melhor métrica. Contudo, quando há o uso de muitas métricas o GridSearchCV não terá condições de escolher automaticamente, pois existe um trade-off entre as métricas, dessa maneira é necessário a configuração do refit para que o GridSearchCV saiba qual métrica definir como melhor conjunto de parâmetros.

Porém, ao definir o refit e fazer a escolha dos parâmetros usando .best_params_ o resultado fornecido não leva em consideração as outras métricas que não seja aquela definida no refit.