Pipelines (Pré-processamento de machine learning + Modelagem)

O que “pipelines” significam em aprendizado de máquina

Um pipeline de aprendizado de máquina (machine learning) é um objeto de ponta a ponta que encadeia etapas de pré-processamento (por exemplo, imputação, codificação, escalonamento, geração de atributos) com um modelo em um único fluxo de trabalho consistente, que você pode fit() uma vez e depois predict() (ou transform()) repetidamente.

A ideia central é que toda operação que aprende com dados deve ser aprendida apenas nos dados de treinamento — e então aplicada sem mudanças aos dados de validação, teste e produção. Pipelines tornam esse comportamento o padrão.

Em muitas bibliotecas de aprendizado de máquina (notadamente o scikit-learn), um pipeline é construído a partir de dois tipos de componentes:

  • Transformadores (transformers): implementam fit() + transform() (por exemplo, imputadores, escalonadores, codificadores).
  • Estimadores (estimators) (modelos): implementam fit() + predict() (e às vezes predict_proba(), decision_function(), etc.).

Um pipeline se comporta como um único estimador:

  • pipeline.fit(X_train, y_train) aprende todos os parâmetros de pré-processamento e o modelo.
  • pipeline.predict(X_new) aplica o mesmo pré-processamento e então prediz.

Este artigo foca em pipelines práticos, de nível de produção, para aprendizado de máquina tabular, ao mesmo tempo em que destaca conceitos que se aplicam amplamente a sistemas de aprendizado de máquina.

Por que pipelines importam

Prevenindo vazamento de dados

Vazamento de dados (data leakage) acontece quando informações de fora do fold de treinamento (validação/teste, dados futuros ou alvos) influenciam o processo de treinamento. Vazamento pode produzir pontuações de validação enganosamente altas que desabam em produção.

Padrões comuns de vazamento incluem:

  • Imputar valores ausentes usando a média calculada no conjunto de dados completo.
  • Escalonar atributos usando estatísticas calculadas no conjunto de dados completo.
  • Selecionar atributos usando correlação com o alvo calculada no conjunto de dados completo.
  • Codificações conscientes do alvo calculadas sem a separação adequada por folds.

Pipelines evitam isso garantindo que o fit() de cada transformador veja apenas a partição de treinamento dentro da validação cruzada.

Tópico relacionado: Vazamento e Imputação (Tratamento de Valores Ausentes).

Validação cruzada e ajuste de hiperparâmetros corretos

Se você faz pré-processamento “na mão” antes da validação cruzada, seu procedimento de CV deixa de simular a realidade: o fold de validação influenciou o pré-processamento, e você efetivamente treinou nele.

Com pipelines, a validação cruzada funciona corretamente:

  • Para cada fold: os pré-processadores são ajustados apenas na porção de treino do fold.
  • A porção de validação do fold é transformada usando parâmetros aprendidos com os dados de treino daquele fold.

Isso é especialmente importante ao fazer GridSearchCV/RandomizedSearchCV, porque caso contrário o ajuste de hiperparâmetros vai explorar vazamento e sobreajustar na CV.

Análises de interpretabilidade confiáveis (importância por permutação, etc.)

Muitos métodos de interpretabilidade (por exemplo, importância por permutação (permutation importance)) exigem avaliar repetidamente o modelo em dados com alguma perturbação. Se o pré-processamento não estiver incluído, você corre o risco de:

  • Permutar entradas brutas que não correspondem ao espaço de atributos transformado esperado pelo modelo.
  • Aplicar etapas de pré-processamento inconsistentes entre treinamento e avaliação.
  • Atribuir incorretamente efeitos de atributos devido a desalinhamento entre representações brutas e codificadas/escalonadas.

Com um pipeline, ferramentas de interpretabilidade podem tratar o pipeline como o modelo: perturbar colunas brutas e deixar o pipeline transformá-las de forma consistente.

Reprodutibilidade e consistência de implantação

Um modelo treinado não é apenas pesos/coefs — é o conjunto completo de decisões de pré-processamento e parâmetros aprendidos, incluindo:

  • Valores de preenchimento da imputação
  • Mapas de categorias da codificação
  • Médias/variâncias do escalonamento
  • Definições de geração de atributos e vocabulário aprendido (para texto)

Pipelines tornam possível:

  • Serializar um artefato (pipeline) e recarregá-lo para inferência.
  • Evitar desalinhamento treino-serving (o treino usa um script de pré-processamento, o serving usa outro).
  • Rastrear versões do modelo com transformações consistentes.

Fundamento teórico: “fit/transform” como funções aprendidas

Pré-processamento é frequentemente descrito como “limpeza de dados”, mas em aprendizado de máquina geralmente é um mapeamento aprendido:

  • transform(x) depende de parâmetros aprendidos a partir dos dados (por exemplo, média, variância, conjuntos de categorias).
  • Portanto, deve ser aprendido apenas nos dados de treinamento.

Formalmente, para um transformador (T):

  • fit(X_train) aprende parâmetros (\theta) a partir dos dados de treinamento.
  • transform(X) aplica (T_\theta(X)).

Um pipeline compõe transformações:

[ \hat{y} = M\left(T_k(\dots T_2(T_1(X)))\right) ]

onde cada (T_i) é ajustado apenas nos dados de treinamento e depois aplicado de forma consistente em todos os demais lugares.

Etapas comuns de pré-processamento para incluir em pipelines

Tratamento de valores ausentes (imputação)

  • Numéricos: imputação pela média/mediana, imputação baseada em modelo
  • Categóricos: mais frequente, token constante “missing”

Ver: Imputação (Tratamento de Valores Ausentes)

Codificação de variáveis categóricas

  • Codificação one-hot (one-hot encoding) (comum para modelos lineares e muitos modelos de árvore)
  • Codificação ordinal (ordinal encoding) (quando existe ordem)
  • Codificação pelo alvo (target encoding) (poderosa, mas com alto risco de vazamento; deve ser feita de forma segura por folds)

Ver: Codificação One-Hot

Escalonamento e normalização

  • Padronização (média zero, variância unitária)
  • Escalonamento min-max
  • Escalonamento robusto (mediana/IQR)

Escalonamento é especialmente importante para modelos que usam distâncias ou otimização baseada em gradiente (por exemplo, modelos lineares, SVMs, redes neurais). Muitos modelos baseados em árvores são menos sensíveis ao escalonamento, mas pipelines ainda ajudam a manter fluxos de trabalho consistentes.

Geração de atributos

  • Transformações log, binning, atributos polinomiais, interações
  • Atributos de data/hora (dia da semana, mês, feriados)
  • Vetorização de texto (bag-of-words, TF-IDF)

Geração de atributos pode ser stateful (por exemplo, TF-IDF aprende vocabulário e pesos de IDF), então deve fazer parte do pipeline.

Seleção de atributos (opcional, mas frequentemente adequada para pipeline)

Seleção de atributos deve ser feita dentro da validação cruzada, não antes, caso contrário vaza informação de validação.

Ver: Seleção de Atributos

Pipelines práticos no scikit-learn

O Pipeline e o ColumnTransformer do scikit-learn são amplamente usados em fluxos tabulares.

Exemplo 1: Pipeline numérico simples (imputar → escalar → modelo)

import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

numeric_pipeline = Pipeline(steps=[
    ("impute", SimpleImputer(strategy="median")),
    ("scale", StandardScaler()),
    ("model", LogisticRegression(max_iter=200))
])

numeric_pipeline.fit(X_train, y_train)
pred = numeric_pipeline.predict(X_test)

Por que isso é melhor do que pré-processamento “manual”:

  • O imputador e o escalonador aprendem parâmetros apenas de X_train.
  • X_test é transformado com os mesmos parâmetros aprendidos.
  • Você pode fazer validação cruzada do pipeline inteiro com segurança.

Exemplo 2: Tipos de dados mistos com `ColumnTransformer`

A maioria dos conjuntos de dados reais mistura colunas numéricas e categóricas. Normalmente você quer pré-processamento diferente por tipo de coluna.

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import roc_auc_score

num_cols = ["age", "income", "balance"]
cat_cols = ["state", "job_type"]

numeric = Pipeline(steps=[
    ("impute", SimpleImputer(strategy="median")),
    ("scale", StandardScaler())
])

categorical = Pipeline(steps=[
    ("impute", SimpleImputer(strategy="most_frequent")),
    ("onehot", OneHotEncoder(handle_unknown="ignore"))
])

preprocess = ColumnTransformer(
    transformers=[
        ("num", numeric, num_cols),
        ("cat", categorical, cat_cols)
    ],
    remainder="drop"  # or "passthrough"
)

clf = Pipeline(steps=[
    ("preprocess", preprocess),
    ("model", LogisticRegression(max_iter=500))
])

clf.fit(X_train, y_train)
proba = clf.predict_proba(X_valid)[:, 1]
print("AUC:", roc_auc_score(y_valid, proba))

Detalhes importantes:

  • handle_unknown="ignore" evita falhas em tempo de inferência para categorias não vistas.
  • O pipeline é um único objeto que você pode validar por CV, ajustar e implantar.

Exemplo 3: Ajuste de hiperparâmetros sem vazamento

Use GridSearchCV (ou RandomizedSearchCV) no pipeline.

from sklearn.model_selection import GridSearchCV

param_grid = {
    "model__C": [0.1, 1.0, 10.0],
    "preprocess__num__impute__strategy": ["mean", "median"]
}

search = GridSearchCV(
    clf,
    param_grid=param_grid,
    scoring="roc_auc",
    cv=5,
    n_jobs=-1
)

search.fit(X_train, y_train)
print("Best params:", search.best_params_)
print("Best CV score:", search.best_score_)
best_model = search.best_estimator_

Convenção de nomenclatura:

  • Parâmetros são referenciados com step__substep__param, o que torna possível e seguro fazer ajuste ponta a ponta complexo.

Pipelines e avaliação: validação cruzada do jeito certo

CV k-fold padrão

Com um pipeline, cada fold é isolado:

  • Ajustar pré-processamento no treino do fold
  • Transformar a validação do fold usando estatísticas do treino do fold
  • Pontuar o modelo na validação do fold

Isso espelha o que acontece em produção: você nunca pode “espiar” distribuições futuras/de validação ao ajustar transformações.

Divisão por grupo ou sensível ao tempo

Pipelines não resolvem estratégias ruins de divisão, mas integram-se bem com divisores adequados:

  • GroupKFold para vazamento via entidades repetidas (por exemplo, múltiplas linhas por usuário)
  • TimeSeriesSplit para configurações temporais do tipo previsão

Uma boa prática é: escolher uma estratégia de divisão que corresponda à sua realidade de implantação, e então envolver o fluxo inteiro em um pipeline.

Pipelines para interpretabilidade e depuração

Importância por permutação com um pipeline

Importância por permutação avalia quanto o desempenho do modelo cai quando um atributo é permutado aleatoriamente. Com pipelines, você pode permutar colunas brutas e deixar o pipeline cuidar de codificação/escalonamento.

from sklearn.inspection import permutation_importance
from sklearn.metrics import roc_auc_score, make_scorer

result = permutation_importance(
    best_model,           # pipeline
    X_valid, y_valid,
    n_repeats=10,
    random_state=0,
    scoring=make_scorer(roc_auc_score, needs_proba=True)
)

importances = result.importances_mean
for col, imp in sorted(zip(X_valid.columns, importances), key=lambda x: -x[1]):
    print(col, imp)

Ressalva: se uma coluna bruta se expande em muitas colunas one-hot, a importância por permutação no nível da coluna bruta ainda funciona (você permuta o atributo bruto e mede o impacto). Isso geralmente é o que você quer para explicações amigáveis para stakeholders.

Mapeando de volta para nomes de atributos transformados

Às vezes você precisa dos nomes finais dos atributos (por exemplo, para coeficientes de modelos lineares). Muitos transformadores do sklearn suportam get_feature_names_out().

ohe = best_model.named_steps["preprocess"].named_transformers_["cat"].named_steps["onehot"]
cat_feature_names = ohe.get_feature_names_out(cat_cols)

Você pode combinar nomes numéricos + nomes one-hot para alinhar com coeficientes do modelo, mas tenha em mente:

  • Coeficientes após codificação one-hot representam indicadores de categoria.
  • Atributos correlacionados e escolhas de codificação podem complicar a interpretação (ver Multicolinearidade).

Reprodutibilidade e implantação: tratando o pipeline como o artefato

Serializar o pipeline inteiro

No scikit-learn, normalmente você salva o objeto pipeline (não apenas a etapa do modelo):

import joblib

joblib.dump(best_model, "credit_risk_pipeline.joblib")
loaded = joblib.load("credit_risk_pipeline.joblib")
loaded.predict(X_new)

Isso garante que exatamente o mesmo pré-processamento seja aplicado em produção.

Versionamento e consistência entre treino e serving

Pipelines melhoram a consistência, mas aprendizado de máquina em produção ainda exige disciplina:

  • Fixar versões de bibliotecas (sklearn, numpy, pandas).
  • Rastrear o esquema de atributos (nomes de colunas, dtypes, categorias permitidas).
  • Monitorar drift de dados; um pipeline não corrige um feed de dados quebrado.

Muitas equipes combinam pipelines com rastreamento de experimentos e registries de modelos (por exemplo, MLflow), mas o princípio central é o mesmo: entregar uma unidade que define completamente a função de predição.

Lidando com mudanças de esquema

Problemas operacionais comuns:

  • Colunas faltando em inferência
  • Novas categorias
  • Unidades alteradas (por exemplo, renda em dólares vs milhares)

Mitigações:

  • Validar entradas antes de chamar predict().
  • Usar configurações robustas de codificação (handle_unknown="ignore").
  • Considerar adicionar checagens explícitas de esquema como uma pré-etapa.

Padrões avançados

Transformadores personalizados

Quando transformadores embutidos não são suficientes (geração de atributos específica do domínio), crie um transformador personalizado que siga a API do sklearn.

from sklearn.base import BaseEstimator, TransformerMixin
import numpy as np

class Log1pTransformer(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return np.log1p(X)

# Use inside a numeric pipeline step

Transformadores personalizados devem ser:

  • Determinísticos e bem testados
  • Cuidadosos com dtypes e valores ausentes
  • Compatíveis com cloning (importante para CV); evitar armazenar estado não serializável (non-picklable) a menos que necessário

Seleção de atributos dentro do pipeline

Se você fizer seleção de atributos, coloque-a antes do modelo no pipeline para que seja validada por CV corretamente:

from sklearn.feature_selection import SelectKBest, f_classif

clf_fs = Pipeline(steps=[
    ("preprocess", preprocess),
    ("select", SelectKBest(score_func=f_classif, k=50)),
    ("model", LogisticRegression(max_iter=500))
])

Isso evita viés de seleção e vazamento (ver Seleção de Atributos).

Codificação pelo alvo (aviso: propensa a vazamento)

Codificações que usam o alvo (média do alvo por categoria) podem ser muito eficazes, mas devem ser implementadas de forma consciente dos folds (por exemplo, via lógica de CV aninhada ou bibliotecas especializadas) para evitar vazar informação do alvo para o pré-processamento. Se você usar codificação pelo alvo, trate isso como uma área de alto risco e valide cuidadosamente usando divisões corretas (ver Vazamento).

Armadilhas comuns e como evitá-las

  • Pré-processamento fora da CV: Fazer imputer.fit_transform(X) antes de cross_val_score vaza informação. Coloque o imputador no pipeline.
  • Ajustar em treino+val “porque é só escalonamento”: escalonamento aprende estatísticas; ainda vaza informação de distribuição.
  • Colunas desalinhadas: ColumnTransformer seleciona por nome — ótimo. Mas sistemas downstream precisam preservar esses nomes e dtypes.
  • Problemas de esparso vs denso: codificação one-hot frequentemente retorna matrizes esparsas; alguns modelos/etapas exigem arrays densos. Planeje adequadamente (ou use modelos que aceitem entrada esparsa).
  • Interpretar coeficientes ingenuamente após codificação: expansão one-hot e multicolinearidade podem tornar coeficientes instáveis ou enganosos (ver Multicolinearidade).

Checklist prático

  • Coloque todo pré-processamento aprendido (imputação, escalonamento, codificação, seleção, vetorização) dentro de um pipeline.
  • Use ColumnTransformer para aplicar pré-processamento diferente a diferentes subconjuntos de colunas.
  • Ajuste hiperparâmetros com GridSearchCV/RandomizedSearchCV no pipeline.
  • Execute ferramentas de interpretabilidade (por exemplo, importância por permutação) no pipeline para manter transformações consistentes.
  • Salve e implante o pipeline inteiro como o artefato de predição.
  • Escolha divisores que correspondam ao seu cenário do mundo real (tempo, grupos) e combine-os com pipelines para avaliação resistente a vazamento.

Pipelines são uma das práticas de maior alavancagem em aprendizado de máquina aplicado: reduzem risco de vazamento, tornam a avaliação confiável e transformam código de pré-processamento confuso em uma definição de modelo reprodutível e implantável.