Como Usar Pipelines no Scikit-Learn

Melhorando a legibilidade e manuntenção dos seus códigos de machine learning

João Paulo Nogueira
Data Hackers
7 min readJun 11, 2019

--

Photo by JJ Ying on Unsplash

Motivação

A forma mais conhecida de escrever códigos de machine learning e a que é geralmente ensinada é usando paradigma de programação procedural, onde cada etapa do código é feita em sequência, seguindo uma ordem específica e usando funções provenientes de pacotes ou escritas pelo usuário ou por terceiros. Códigos escritos nesse estilo não são os melhores para serem postos em produção porque geralmente não permitem a criação de um padrão de escrita de código que seja de fácil entendimento e manuntenção.

A classe Pipeline é uma funcionalidade do Scikit-Learn que ajuda criar códigos que possuam um padrão que possa ser facilmente entendido e compartilhando entre times de cientista e engenheiro de dados. Em sua essência, esse tipo de escrever código usa o estilo de programação Orientada à Objetos, mas não precisaremos tocar nos conceitos de programação OO para aprender a usar o Pipeline.

O objetivo desde tutorial é iniciar você a utilizar pipelines. Códigos de machine-learning são melhores escritos através de pipelines, que em essência fazem o output de uma dada transformação nos dados se tornarem o input para uma outra transformação que será aplicada nos dados.

Introdução

Para mostrar o uso de pipelines no scikit-learn, irei utilizar o dataset do titanic, muito bem explorado nesse artigo do Paulo Vasconcellos. A forma como o código está escrito no artigo citado (recomendo ir no artigo do Paulo e reproduzir o código), é um exemplo de código escrito no estilo programação procedural. Sem mais delongas, já irei aqui mostrar como fica o código escrito usando pipelines, para que você já sinta o feeling do negócio:

A principal diferença do código escrito no estilo acima para o código escrito no artigo do Paulo está na linha 21, quando o modelo é criado. Aqui, usamos a classe Pipeline que nos permite embutir no modelo não apenas o algoritmo usado, mas também a forma como ele irá pré-processar os dados. Perceba que o código fica muito mais limpo e fácil de ler. Além de explicitamente evidenciar o conceito de que um modelo não é apenas o algoritmo e deve incluir também todo o pré-processamento dos dados.

Criando Pipelines

Para criar um pipeline, primeiro devemos chamar a classe Pipeline do módulo pipeline do scikit-learn:

O próximo passo é instanciar a classe Pipeline com cada passo do pré-processamento do modelo como um elemento de uma tupla. O primeiro elemento da tupla é um nome que identifique o transformador ou estimador utilizado e o segundo elemento é o transformador ou estimador que irá aplicar uma transformação nos dados ou treinar um algoritmo nos dados, respectivamente. As tuplas são postas dentro de uma lista e passadas para o parâmetro steps:

No Pipeline acima, primeiro aplicamos o OneHotEncoder do pacote category_encoders. Esse transformador irá pegar todas as variáveis categóricas dos dados e aplicar a transformação. O segundo passo do Pipeline é a imputação de valores faltantes em variáveis númericas pela substituição dos valores faltantes pela média. O terceiro e último passo é uma árvore de decisão que será treinada nos dados pré-processados nos passos anteriores.

O algoritmo deve ser o último elemento na lista de tuplas a ser passada para o parâmetro steps do Pipeline.

Com os pipelines, qualquer pré-processador ou transformador de váriaveis contido no scikit-learn ou pacotes que sigam templates do scikit-learn para criação de pré-processadores customizados, poderá ser incluído em um pipeline.

Toda vez que treinarmos o modelo, todos os passos definidos no Pipeline serão executados na ordem em que aparecem, com o output do passo atual servindo como input para o passo anterior. O próprio Pipeline também possui o método fit, que facilita a nossa vida:

Assim, quando chamamos o método fit, todos os passos definidos no Pipeline serão executados na ordem em que aparecem.

Cross-Validation com Pipelines

Photo taken from the Caret documentation: https://topepo.github.io/caret/index.html

Uma grande vantagem de usar o Pipeline é a possibilidades de incluí-los no esquema de cross-validation do seu modelo. Segue abaixo o código com uma validação 5-fold cross-validation:

Perceba como, uma vez que você encapsulou todo o seu modelo dentro de um Pipeline, fica muito mais fácil validá-lo de forma correta e confiável. De forma bastante explícita, com o Pipeline o seu modelo não consiste apenas do algoritmo, mas também de todo o pré-processamento usado antes de aplicar o algoritmo nos dados. Isso fica evidente quando passamos o modelo para a função cross_validate, na linha 25. Como não precisamos reescrever código, ficamos livres de erros de copy & paste que podem levar a leaks de informação, por exemplo.

Grid-Search com Pipelines

Uma vez que o pipeline está criado, podemos também facilmente realizar uma tunagem de hiperparâmetro utilizando o GridSearchCV. O pipeline basicamente se torna o nosso modelo, logo, basta apenas definirmos o tipo de validação desejada e os parâmetros que deverão ser tunados. Abaixo, por motivos de ilustração, incluo apenas o parâmetro max_depth no grid de hiperparâmetros e passamos o nosso modelo construído com Pipeline no instanciamento da classe GridSearchCV.

Pré-processando diferentes variáveis com ColumnTransfomer

Ok. Talvez você tenha percebido que no pipeline definido nos códigos acima para criar o modelo, eu não defini em quais variáveis realizar o imputação de valores faltantes pela mediana e em quais aplicar o one-hot encoder. No código do artigo do Paulo, pelo menos fica evidente em quais variáveis os pré-processamentos estão sendo feitos. Mas calma lá, é nesse momento que o ColumnTransformer chega para nos ajudar! Basta apenas você criar um pipeline de pré-processamento para cada variável ou conjunto de variáveis e no fim usar o ColumnTransformer para compor todos os pré-processamentos, indicando na construção do modelo em quais variáveis cada pré-processamento irá atuar. No código abaixo está o código reescrito evidenciando quais variáveis receberão quais tipos de tratamento:

No código acima definimos dois Pipelines extras: num_transformer (linha 19) e cat_transformer (linha 24). No primeiro, criamos um Pipeline para tratar valores faltantes por imputação do valor médio. No segundo, criamos um Pipeline para aplicar um one-hot encoder em variáveis categóricas. Na linha 29 instanciamos a classe ColumnTransformer e passamos os pipelines de transformação de dados. Na linha 35, criamos o nosso modelo com um Pipeline, como anteriormene.

O ColumnTransformer é extremamente útil, apesar do caso tratado aqui ser bastante simples, apenas para efeito de ilustração. Mas imagina um pré-processamento de diferentes tipos de variáveis, com algumas variáveis numéricas tendo valores faltantes imputados pela mediana, outras pela média, um conjunto de variáveis categóricas com alta cardinalidade que precisa de um pré-processamento diferente de variáveis categóricas que apresentam baixa cardinalidade. Nessas situações, usar o Pipeline com o ColumnTransformer evita o seu código de se tornar um pesadelo sem fim para quem vai dar manuntenção ou para o seu futuro Eu!

Conclusão

As classes Pipeline e ColumnTransformer são o que há de supra sumo no scikit-learn para te ajudar a escrever um código limpo e de fácil manuntenção, que não vai te fazer passar vergonha na hora de mandar aquele código pra ser colocado em produção pro time de engenharia. Além de tudo, ele estabelece um padrão que facilita a leitura por qualquer cientista de dados que tenha domínio dessas ferramentas.

O Pipeline permite que você passe pra ele até um pré-processador customizado, inteiramente construído para as suas necessidades. Você deve apenas seguir um template do scikit-learn, que herda alguns métodos e atributos de umas classes base do scikit-learn para que o seu transformador customizado também possua os métodos fit e transform.

Como sugestão de exercício sugiro construir um transformador customizado para as variáveis Age e Fare, que atribua uma nova coluna para cada uma dessas variáveis indicando se o valor é missing ou não. Como leitura de apoio seguem esse excelente kernel A Deep Dive Into Sklearn Pipelines e esse tutorial do blog Towards Data Science: Custom Transformers and ML Data Pipelines with Python.

Sintam-se a vontade para entrar em contato comigo através do Linkedin ou pela própria comunidade do DataHackers!

--

--