Como criar um Pipeline no scikit-learn: passo a passo
Criar um Pipeline no scikit-learn junta o pré-processamento e o modelo num único objeto: o mesmo tratamento aplicado ao treino é aplicado, automaticamente, aos dados novos. É a forma mais simples de evitar data leakage e de garantir que o que vai para produção é exatamente aquilo que foi validado.
Pré-requisitos
- Python 3.10 ou superior com scikit-learn e pandas instalados (
pip install scikit-learn pandas). - Um ficheiro CSV com dados tabulares e uma coluna alvo (aqui usamos
clientes.csve a colunachurn). - Noções básicas de treino e teste de modelos.
Passo 1: Separar treino e teste antes de tudo
A regra de ouro: o conjunto de teste não pode influenciar nada, nem sequer a média usada para normalizar. Por isso, divide os dados antes de qualquer transformação.
import pandas as pd
from sklearn.model_selection import train_test_split
df = pd.read_csv("clientes.csv")
X = df.drop(columns=["churn"])
y = df["churn"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
Passo 2: Criar o Pipeline mais simples
Um Pipeline é uma lista de passos com nome. Todos os passos, menos o último, têm de transformar os dados; o último é normalmente o modelo.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline(steps=[
("scaler", StandardScaler()),
("modelo", LogisticRegression(max_iter=1000)),
])
Os nomes ("scaler", "modelo") são escolhidos por ti e vão dar jeito mais à frente.
Passo 3: Treinar e prever com uma só linha
Ao chamar fit, o Pipeline aprende a escala com o treino e treina o modelo. Ao chamar predict, aplica a mesma escala aos dados novos, sem tu teres de te lembrar disso.
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
Passo 4: Tratar colunas numéricas e categóricas com ColumnTransformer
Na vida real as colunas não são todas iguais. O ColumnTransformer aplica um caminho diferente a cada grupo de colunas e encaixa dentro do Pipeline como se fosse um passo qualquer.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
numericas = ["idade", "mensalidade", "meses_cliente"]
categoricas = ["plano", "regiao"]
pre = ColumnTransformer(transformers=[
("num", Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
]), numericas),
("cat", OneHotEncoder(handle_unknown="ignore"), categoricas),
])
pipe = Pipeline([
("pre", pre),
("modelo", LogisticRegression(max_iter=1000)),
])
pipe.fit(X_train, y_train)
O handle_unknown="ignore" evita o erro clássico de aparecer, em produção, uma categoria que não existia no treino.
Passo 5: Validar e afinar o Pipeline inteiro
Como o Pipeline é um estimador normal, podes passá-lo diretamente à validação cruzada. Em cada dobra, o pré-processamento é reaprendido só com a parte de treino — é exatamente isto que elimina o leakage.
from sklearn.model_selection import cross_val_score, GridSearchCV
scores = cross_val_score(pipe, X_train, y_train, cv=5, scoring="f1")
print(scores.mean())
grelha = {"modelo__C": [0.1, 1, 10]}
busca = GridSearchCV(pipe, grelha, cv=5, scoring="f1")
busca.fit(X_train, y_train)
print(busca.best_params_)
Repara no duplo underscore: modelo__C significa "o parâmetro C do passo chamado modelo". É assim que se afinam hiperparâmetros dentro de um Pipeline.
Passo 6: Guardar o Pipeline completo
Guarda o Pipeline, não só o modelo. Assim, o ficheiro já contém o pré-processamento e recebe dados em bruto.
import joblib
joblib.dump(busca.best_estimator_, "modelo_churn.joblib")
modelo = joblib.load("modelo_churn.joblib")
print(modelo.predict(X_test.head()))
Verificar o resultado
Três verificações rápidas:
pipe.named_stepsmostra os passos pela ordem certa.pipe.predict(X_test.head())funciona com o DataFrame em bruto, sem escalar nem codificar nada à mão. Se funcionar, o Pipeline está a fazer o trabalho todo.- Se aparecer o erro could not convert string to float, ficou uma coluna de texto fora do ColumnTransformer; se aparecer columns are missing, os dados novos não têm as mesmas colunas do treino.
Compara o resultado da validação cruzada com e sem Pipeline. Se o valor "melhorar" quando normalizas tudo antes de dividir, isso não é um modelo melhor — é leakage.
Conclusão
Com meia dúzia de linhas passaste de um conjunto de passos soltos para um objeto único, validável e pronto a servir. O passo seguinte natural é acrescentar ao Pipeline uma seleção de variáveis ou um TargetEncoder, e registar o modelo numa plataforma de MLOps. Uma pergunta para levares contigo: se amanhã chegar uma linha nova com uma categoria nunca vista, o teu Pipeline aguenta — ou rebenta?