Guía de Pipelines en Scikit‑Learn: Cómo construir flujos de ML listos para producción (Parte 2)


Scikit-Learn (Parte 2)

Flujo completo de un Pipeline: cómo automatizar tu ML como un profesional

En la Parte 1 vimos los tres pilares de Scikit-Learn:

  • Estimators
  • Transformers
  • Predictors

Funcionan, sí. Pero escribirlos a mano es frágil, repetitivo y propenso a errores. Y en cuanto tu pipeline crece a 5, 8 o 12 pasos… se vuelve directamente inmanejable.

Diagrama de un pipeline de Scikit‑Learn con pasos de preprocesamiento y modelo.

Por eso Scikit-Learn tiene una de las ideas más elegantes de toda su API:

El Pipeline.

Un objeto que:

  • encadena pasos
  • evita leakage
  • mantiene la consistencia entre train y test
  • facilita la validación
  • permite automatizar la búsqueda de hiperparámetros
  • y te deja un sistema listo para producción

Hoy vas a aprender a construir uno de verdad. No el típico ejemplo de dos líneas que se ve en cualquier tutorial.

Vamos paso a paso.


¿Qué problema resuelve un Pipeline?

Imagina este flujo manual:

python
scaler = StandardScaler()
pca = PCA(n_components=5)
model = LogisticRegression()

scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

pca.fit(X_train_scaled)
X_train_pca = pca.transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)

model.fit(X_train_pca, y_train)
y_pred = model.predict(X_test_pca)

Funciona, sí. Pero tiene problemas serios:

  • Si cambias un paso, tienes que tocar 6 líneas.
  • Si añades un paso, tienes que acordarte de aplicarlo en train y en test.
  • Si quieres cross-validation, tienes que reescribir medio script.
  • Si quieres GridSearch, tienes que anidar funciones a mano.
  • Y un solo.fit_transform()mal puesto → data leakage silencioso.

Un Pipeline elimina todo eso de un plumazo.


¿Qué es un Pipeline?

Un Pipeline es un Estimator compuesto.

Es decir:

  • tiene.fit()
  • tiene.predict()
  • y por dentro ejecuta todos los pasos en el orden correcto
python
from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('pca', PCA(n_components=5)),
    ('model', LogisticRegression())
])

Ahora todo tu flujo es un único objeto:

python
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

Y Scikit-Learn se encarga de:

  • aplicar.fit()solo donde corresponde
  • aplicar.transform()en cada paso intermedio
  • pasar los datos de un paso al siguiente
  • mantener el orden
  • evitar leakage

Tú no escribes nada de eso. Lo gestiona la librería.


La regla de oro del Pipeline

En un Pipeline:

  • Todos los pasos menos el último deben ser Transformers.
  • El último paso debe ser un Predictor.

Ejemplo válido:

Scaler → PCA → LogisticRegression

Ejemplo inválido:

Scaler → LogisticRegression → PCA

¿Por qué? Porque un Predictor no tiene.transform(), así que no puede pasar datos al siguiente paso. El último eslabón siempre es el que decide.


Cómo funciona internamente

Cuando llamas a:

python
pipeline.fit(X_train, y_train)

Scikit-Learn ejecuta por dentro:

  1. scaler.fit(X_train)
  2. X1 = scaler.transform(X_train)
  3. pca.fit(X1)
  4. X2 = pca.transform(X1)
  5. model.fit(X2, y_train)

Y cuando llamas a:

python
pipeline.predict(X_test)

Hace:

  1. X1 = scaler.transform(X_test)
  2. X2 = pca.transform(X1)
  3. model.predict(X2)

Sin que tú escribas una sola línea extra. Y sin posibilidad de equivocarte de orden.


ColumnTransformer: el arma secreta

En proyectos reales, no todas las columnas se procesan igual:

  • unas necesitan escalado
  • otras one-hot encoding
  • otras imputación de nulos
  • y otras se quedan tal cual

Para eso existeColumnTransformer.

python
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder

preprocess = ColumnTransformer([
    ('num', StandardScaler(), ['age', 'salary']),
    ('cat', OneHotEncoder(), ['city', 'gender'])
])

Y lo metes dentro del Pipeline como un paso más:

python
pipeline = Pipeline([
    ('prep', preprocess),
    ('model', LogisticRegression())
])

Ahora tu pipeline, en un único objeto:

  • escala las numéricas
  • codifica las categóricas
  • entrena el modelo
  • y se queda listo para predecir sobre datos nuevos con el mismo tratamiento

Esto es lo que separa un notebook de juguete de un pipeline real.


Cross-validation con Pipelines (la forma correcta)

Antes, cuando hacías:

python
from sklearn.model_selection import cross_val_score
cross_val_score(model, X, y, cv=5)

estabas validando el modelo, pero no el preprocesamiento. Y si escalaste antes confit_transformsobre todoX, ya hubo leakage en el momento que lo hiciste.

Con un Pipeline el problema desaparece:

python
cross_val_score(pipeline, X, y, cv=5)

Ahora, en cada fold, Scikit-Learn:

  • reentrena el scaler solo con el train del fold
  • reentrena el PCA solo con el train del fold
  • reentrena el modelo solo con el train del fold
  • evalúa sobre el validation del fold

Validación honesta. Sin leakage. Sin dolor.


GridSearchCV con Pipelines

Aquí es donde los Pipelines brillan de verdad.

python
from sklearn.model_selection import GridSearchCV

params = {
    'prep__num__with_mean': [True, False],
    'model__C': [0.1, 1, 10]
}

grid = GridSearchCV(pipeline, params, cv=5)
grid.fit(X, y)

La sintaxisprep__num__with_meanse lee así:

pipeline → paso 'prep' → bloque 'num' → parámetro 'with_mean'

Es decir: dobles guiones bajos (__) para bajar de nivel. Con esto puedes tunear cualquier parámetro de cualquier paso, incluido el preprocesamiento, todo en una sola llamada.

Y lo mejor: cada combinación se evalúa con cross-validation honesto, porque el reentrenamiento ocurre dentro de cada fold.


Pipeline listo para producción

Una vez entrenado, lo guardas:

python
import joblib
joblib.dump(pipeline, 'model.pkl')

Y en producción solo necesitas:

python
pipeline = joblib.load('model.pkl')
pipeline.predict(new_data)

Sin preocuparte por:

  • escalado
  • encoding
  • orden de pasos
  • consistencia entre train y producción

Todo está dentro del Pipeline. Esa es justamente la idea: lo que despliegas no es un modelo, es el flujo completo.


Errores comunes (y cómo evitarlos)

❌ Error 1: Hacer.fit()fuera del Pipeline

python
scaler.fit(X_train)

En el momento que haces esto por tu cuenta, ya rompiste la idea del Pipeline. Deja que el objeto se encargue.

❌ Error 2: Mezclar.fit_transform()con Pipelines

Nunca mezcles flujos manuales con Pipelines. O todo dentro, o todo fuera. Lo demás es pedir leakage a gritos.

❌ Error 3: Procesar columnas a mano en lugar de usarColumnTransformer

Si separas numéricas y categóricas con slicing manual, pierdes modularidad y reproducibilidad. Y un día lo pagarás caro.

❌ Error 4: Hacer GridSearch sin Pipeline

Si el preprocesamiento queda fuera del objeto que validas, casi seguro que hay leakage. La regla es simple: todo lo que se aprende de los datos, dentro del Pipeline.


Ejemplo final: un Pipeline completo y real

python
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression

preprocess = ColumnTransformer([
    ('num', StandardScaler(), ['age', 'salary']),
    ('cat', OneHotEncoder(handle_unknown='ignore'), ['city', 'gender'])
])

pipeline = Pipeline([
    ('prep', preprocess),
    ('model', LogisticRegression())
])

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

Esto ya es nivel producción. Modular, validable, serializable y sin leakage. Justamente lo que veníamos buscando desde la Parte 1.


TL;DR

  • Un Pipeline encadena pasos y evita leakage automáticamente.
  • Todos los pasos menos el último deben ser Transformers; el último, un Predictor.
  • ColumnTransformerte permite aplicar transformaciones distintas a columnas distintas.
  • Cross-validation y GridSearch funcionan correctamente solo cuando el preprocesamiento vive dentro del Pipeline.
  • Un Pipeline es el objeto que se guarda conjobliby se despliega en producción.
  • Si entiendes bien el Pipeline, dejas de tener «scripts de ML» y empiezas a tener sistemas de ML.