Pipelines
Uma cadeia real de pré-processamento — imputar, codificar, escalonar e então modelar — é uma sequência de passos que deve ser ajustada apenas nos dados de treino e reproduzida de forma idêntica nos folds de validação, no conjunto de teste e no tráfego de produção. Fazer isso à mão convida a bugs. O Pipeline do scikit-learn empacota toda a cadeia como um único estimador.
O problema que os pipelines resolvem
Sem um pipeline, o fluxo honesto exige uma contabilidade cuidadosa:
# Frágil: cada passo precisa ser ajustado à mão no treino e aplicado ao teste
imputer.fit(X_train)
X_train_i = imputer.transform(X_train)
X_test_i = imputer.transform(X_test)
scaler.fit(X_train_i)
X_train_s = scaler.transform(X_train_i)
X_test_s = scaler.transform(X_test_i)
model.fit(X_train_s, y_train)
model.predict(X_test_s)
Um fit fora do lugar — ou um fit_transform no conjunto completo — e você tem vazamento de dados. Pior: durante a validação cruzada o pré-processamento precisa ser reajustado em cada fold de treino, o que é praticamente impossível de fazer corretamente à mão.
Pipeline: um estimador, vários passos
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
('model', LogisticRegression()),
])
pipe.fit(X_train, y_train) # ajusta imputer → scaler → model, em ordem, no treino
pipe.predict(X_test) # transforma X_test pelos MESMOS passos ajustados
Regras da composição:
- todo passo, exceto o último, deve ser um transformador (
fit+transform); - o último passo é tipicamente um estimador (
fit+predict); pipe.fit(X, y)chamafit_transformem cada transformador em sequência e depoisfitno estimador;pipe.predict(X)chama apenastransformem cada transformador — os parâmetros ficam congelados.
Como o pipeline é um estimador, ele se encaixa diretamente em cross_val_score e GridSearchCV — e o pré-processamento é automaticamente reajustado dentro de cada fold, eliminando o bug de vazamento por construção:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipe, X, y, cv=5) # livre de vazamento por design
Colunas heterogêneas: ColumnTransformer
Tabelas reais misturam colunas numéricas e categóricas que precisam de tratamentos diferentes. O ColumnTransformer roteia subconjuntos de colunas para ramos de pré-processamento paralelos e concatena os resultados:
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
numeric = ['age', 'income', 'tenure']
categorical = ['city', 'plan', 'device']
preprocess = ColumnTransformer([
('num', Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
]), numeric),
('cat', Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore')),
]), categorical),
])
pipe = Pipeline([
('preprocess', preprocess),
('model', LogisticRegression(max_iter=1000)),
])
flowchart LR
X[DataFrame bruto] --> CT{ColumnTransformer}
CT -->|colunas numéricas| N[imputar mediana → escalonar]
CT -->|colunas categóricas| C[imputar moda → one-hot]
N --> J[concatenar]
C --> J
J --> M[LogisticRegression] Ajuste através do pipeline
Os hiperparâmetros de qualquer passo são endereçados como nomedopasso__nomedoparam (duplo sublinhado) — assim, uma única busca em grade pode ajustar escolhas de pré-processamento e hiperparâmetros do modelo juntos, de forma honesta:
from sklearn.model_selection import GridSearchCV
param_grid = {
'preprocess__num__imputer__strategy': ['mean', 'median'],
'model__C': [0.01, 0.1, 1, 10],
}
search = GridSearchCV(pipe, param_grid, cv=5, scoring='f1')
search.fit(X_train, y_train)
Mais sobre isso em Seleção de Modelos.
Inspeção e persistência
pipe.named_steps['model'].coef_ # acessar qualquer passo ajustado
pipe[:-1].transform(X_train) # rodar apenas o pré-processamento
pipe.get_feature_names_out() # nomes após a expansão one-hot
import joblib
joblib.dump(pipe, 'churn-model.joblib') # entregar UM artefato: pré-processamento + modelo
Persistir o pipeline inteiro é a base de um MLOps confiável: o código de produção não pode "esquecer" um passo de pré-processamento, porque os passos viajam dentro do artefato.
Hábito de projeto
Comece todo projeto escrevendo o esqueleto do pipeline — antes mesmo de escolher o modelo. Isso impõe a disciplina treino/teste desde a primeira linha de código e torna cada experimento posterior uma mudança de uma linha.
Material de aula
Notebook da aula (em português)
Notebook prático usado em sala — Aula 04 — Pipelines: abrir no Colab