Como usar o Pipeline

  • AH Uyekita
  • Friday, Mar 22, 2019
  • Estimated reading time: 7 min

blog-image

binder badge

O Pipeline assim como o GridSearchCV são módulos para automatizar processos repetitivos que muitas vezes permeia o processo de tuning de Machine Learning; Este post abordará exclusivamente como implementar o uso do Pipeline e quando ele deve ser usado ou evitado.



Pipeline

O Pipeline é um módulo do Scikit Learn (leia a documentação dele aqui) que é usado para automatizar etapas sequenciais. Note que o Pipeline sempre executará as etapas na mesma ordem e as executará sem exceção. Além disso, o Pipeline foi concebido para ser executado diversas vezes, pois o objetivo primário dele é testar/avaliar parâmetros e classifiers.

Portanto, ao definir um Pipeline tenha em mente a utilidade de cada etapa definida nela e se isso realmente faz sentido estar dentro do Pipeline. Caso você tenha que repetir a mesma etapa para todas as simulações sem ter que ajustar nenhum parâmetro, pode ser um indicativo que essa etapa pode ser alocada antes do Pipeline com o é o caso da mudança de escala.

A Figura 1 apresenta uma ilustração de diagramas de blocos do Pipeline, note que o Scaler será executado todas as vezes que o Pipeline também o for, logo se o Scaler não tiver nenhum parâmetro para ser ajustado ou testado, ele pode ser executado antes do Pipeline, conforme a Figura 2.

Figura 1 - Anaconda Navigator.

Figura 1 - Diagrama de Blocos do Pipeline com Scaler.

Figura 1 - Anaconda Navigator.

Figura 2 - Diagrama de Blocos do Pipeline sem Scaler.

Jupyter Notebook

Foi feito um Jupyter Notebook (e disponibilizado pelo Binder) de acompanhamento do post, nele há diversos exemplos e alguns detalhes que foram omitidos neste post.

Dataset

Para elucidação do Pipeline, será usado o toy dataset de câncer de mama que está presente no próprio Scikit Learn.

# 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 de classificação para esse exemplo o Logistic Regression.

# Importação do Logistic Regression.
from sklearn.linear_model import LogisticRegression

# Uso do constructor do AdaBoost para criar um classifier.
clf = LogisticRegression(random_state = 42,
                         solver = 'lbfgs',
                         multi_class = 'multinomial')

Etapas do Pipeline

Inicialmente será exposto etapa por etapa e posteriormente será criado o Pipeline para comparar os benefícios de usá-lo. Desta maneira, este item abordará:

  • Mudança de Escala (propositalmente deixado no Pipeline);
  • Diminuição de Dimensão, e;
  • Treino e Previsão.
Mudança de Escala

A mudança de escala é feita usando o módulo do MinMaxScaler que também é parte do Scikit Learn.

# Importação do MinMaxScaler.
from sklearn.preprocessing import MinMaxScaler

# Constructor para criar o objeto do MinMaxScaler.
scaler = MinMaxScaler()

# Treino.
scaler.fit(features)

# Transformação.
features_scaled = scaler.transform(features)
Redução de Variáveis

A diminuição de features é feita usando a Análise de Componentes Principais (PCA), o Scikit Learn também possui um módulo dedicado para isso.

# Importação do PCA.
from sklearn.decomposition import PCA

# Uso do constructor para criar o objeto do PCA.
pca = PCA(n_components = 10)

# Treino.
pca.fit(features_scaled)

# Tranformação.
features_scaled_pca = pca.transform(features_scaled)
Treino e Previsão

Baseado no classifer clf, no features dataset e em labels será calculado a acurácia do Logistic Regression. Será utilizado o train_test_split para criar os datasets de treino e teste.

# Importação do StratifiedShuffleSplit.
from sklearn.model_selection import train_test_split

# Divisão dos features e labels em treino e teste.
features_train, features_test, labels_train, labels_test = train_test_split(features_scaled_pca,
                                                                            labels,
                                                                            test_size=0.33,
                                                                            random_state=42)

Agora faremos o treino e a previsão.

# Treino usando o dataset de treino.
clf.fit(features_train, labels_train)

# Previsões usando o dataset de test.
pred = clf.predict(features_test)

# Importação do módulo para cálculo do Accuracy.
from sklearn.metrics import accuracy_score

# Avaliação.
accuracy_score(labels_test, pred)
0.9787234042553191

Note que todas essas três etapas anteriores podem ser condensadas numa só ao criar um Pipeline. Os requisitos necessários para o Pipeline é a criação de uma lista de etapas, conforme essa apresentada abaixo.

# Minha lista de etapas.
etapas_pipeline = [('scaler', scaler), # O scaler está definido na Etapa 5.1.1
                   ('pca'   , pca),    # O pca está definido na Etapa 5.1.2
                   ('clf'   , clf)]    # O clf está definido no item 4 e foi usado na Etapa 5.1.3

Note que estou usando os objetos criados nas etapas anteriores, mas eu posso usar o próprio constructor, logo atualizando a lista etapas_pipeline, tem-se:

# Minha lista de etapas.
etapas_pipeline = [('scaler', MinMaxScaler()),                                     # Etapa 5.1.1
                   ('pca'   , PCA(n_components = 10)),                             # Etapa 5.1.2
                   ('clf'   , LogisticRegression(random_state = 42,                # Etapa 5.1.3
                                                 solver = 'lbfgs',
                                                 multi_class = 'multinomial'))]

Após a definição da lista de etapas, pode-se criar a partir do constructor do Pipeline o objeto pipe.

# Importação do Pipeline
from sklearn.pipeline import Pipeline

# Uso do constructor para criação do objeto do Pipeline.
pipe = Pipeline(etapas_pipeline)

Analogamente ao processo feito passo-a-passo, utilizarei o mesmo train_test_split para dividir features e labels em treino e teste.

# Divisão dos features e labels em treino e teste.
features_train_2, features_test_2, labels_train_2, labels_test_2 = train_test_split(features,
                                                                                    labels,
                                                                                    test_size=0.33,
                                                                                    random_state=42)

Por fim, consideramos o pipe como um objetivo parecido com um classifier, será treinado e depois utilizado para fazer previsões.

# Treino do pipe.
pipe.fit(features_train_2, labels_train_2);

# Fazendo previsões.
pred_2 = pipe.predict(features_test_2)

# Calculando o accuracy.
accuracy_score(labels_test_2, pred_2)
0.9787234042553191

Note que o Pipeline aplica ocultamente os processos fit e transform das primeiras duas etapas, deixando apenas a parte de fit e predict do classifier. Logo, é um requisito necessário para que o Pipeline funcione que as etapas que antecedem o classifier possuam esses dois métodos.

Múltiplos Classifiers

A maneira de usar diversos classifiers no Pipeline é usando dois dicionários. O primeiro serve para armazenar os classfiers e o segundo os tuples requeridos pelo Pipeline funcionar.

Dessa maneira, define-se o dicionário de classifiers:

# Importação do Naïve Bayes.
from sklearn.naive_bayes import GaussianNB

# Dicionário de classifiers.
meus_classifiers = {'logit':('lr', LogisticRegression(random_state = 42,
                                                      solver = 'lbfgs',
                                                      multi_class = 'multinomial')),
                    'bayes':('gnb', GaussianNB())}

Observe que o dicionário meus_classifiers somente funcionará caso seja definido o key, caso contrário o Pipeline não conseguirá interpretá-lo.

Agora, vamos inserir o dicionário meus_classifiers como um dos elementos de etapas_pipeline. A chave escolhida foi logit.

# Minha lista de etapas com um dicionário aninhado.
etapas_pipeline_2 = [('scaler', MinMaxScaler()),
                     ('pca'   , PCA(n_components = 10)),
                      meus_classifiers['logit']]

Implicitamente, sabe-se que meus_classifiers['logit'] tem o mesmo significado de:

LogisticRegression(random_state = 42,
                   solver = 'lbfgs',
                   multi_class = 'multinomial')

Portanto, pode-se treinar e calcular as previsões.

# Uso do constructor para criação do objeto do Pipeline.
pipe_2 = Pipeline(etapas_pipeline_2)

# Treino do pipe.
pipe_2.fit(features_train_2, labels_train_2);

# Fazendo previsões.
pred_4 = pipe.predict(features_test_2)

# Calculando o accuracy.
accuracy_score(labels_test_2, pred_4)
0.9840425531914894

Caso seja necessário a simulação para o Naïve Bayes, pode-se substituir a chave para bayes e executar novamente o treino e a previsão.

# Uso do constructor para criação do objeto do Pipeline.
pipe_3 = Pipeline([('scaler', MinMaxScaler()),
                   ('pca'   , PCA(n_components = 10)),
                    meus_classifiers['bayes']])         # Estou usando o Naïve Bayes agora.

# Treino do pipe.
pipe_3.fit(features_train_2, labels_train_2);

# Fazendo previsões.
pred_5 = pipe_3.predict(features_test_2)

# Calculando o accuracy.
accuracy_score(labels_test_2, pred_5)
0.9202127659574468

Note que o uso do Pipeline simplifica o processo de tuning, pois a aplicação de fit e transform são “omitidos” ao serem aplicados “automaticamente”.