In de vorige notebook fitten we de scaler manueel op de trainingsset en transformeerden we train en test apart. Bij complexere workflows โ meerdere stappen, verschillende transformaties per kolom โ wordt dit al snel foutgevoelig.
Pipeline en ColumnTransformer lossen dit op:
| Probleem | Oplossing |
|---|---|
| Manueel fit/transform herhalen | Pipeline koppelt stappen aan elkaar |
| Verschillende preprocessing per kolom | ColumnTransformer verdeelt kolommen |
| Data leakage risico | Pipeline past fit automatisch enkel op trainingsdata toe |
| Veel boilerplate code | make_pipeline / make_column_transformer |
import numpy as np
import pandas as pd
import seaborn as sns
import sklearn
from sklearn.model_selection import train_test_split
print(sklearn.__version__)
penguins = sns.load_dataset("penguins").dropna()
X = penguins[["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "species", "island"]]
y = penguins["body_mass_g"].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"Train: {X_train.shape}, Test: {X_test.shape}")1.7.2
Train: (266, 5), Test: (67, 5)
Pipelineยถ
Een Pipeline is een aaneenschakeling van stappen. Elke stap (behalve de laatste) moet een Transformer zijn; de laatste stap mag ook een Estimator zijn.
Wanneer je pipeline.fit(X_train, y_train) roept:
Elke Transformer-stap wordt
fit_transform-ed op de output van de vorige stapDe laatste stap (Estimator) wordt gefit op het eindresultaat
Wanneer je pipeline.predict(X_test) roept:
Elke Transformer-stap doet enkel
transform(geen nieuwe fit!)De Estimator maakt voorspellingen
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# Only numerical features for now
X_num_train = X_train[["bill_length_mm", "bill_depth_mm", "flipper_length_mm"]]
X_num_test = X_test[["bill_length_mm", "bill_depth_mm", "flipper_length_mm"]]
pipe = Pipeline(
steps=[
("scaler", StandardScaler()),
("model", LinearRegression()),
]
)
pipe.fit(X_num_train, y_train)
print(f"Rยฒ on test set: {pipe.score(X_num_test, y_test):.3f}")Rยฒ on test set: 0.798
# Access a step by name
learned_mean = pipe.named_steps["scaler"].mean_
print("Learned mean:", learned_mean.round(2))Learned mean: [ 44.1 17.16 201. ]
make_pipelineยถ
make_pipeline is een snellere schrijfwijze: namen worden automatisch afgeleid van de klassenaam.
from sklearn.pipeline import make_pipeline
pipe2 = make_pipeline(StandardScaler(), LinearRegression())
pipe2.fit(X_num_train, y_train)
print(f"Rยฒ: {pipe2.score(X_num_test, y_test):.3f}")
print("Steps:", list(pipe2.named_steps.keys()))Rยฒ: 0.798
Steps: ['standardscaler', 'linearregression']
ColumnTransformerยถ
In de praktijk heb je bijna altijd een mix van numerieke en categorische kolommen die elk een andere preprocessing nodig hebben. ColumnTransformer past per kolomgroep de juiste transformer toe en plakt de resultaten samen.
Elke transformer wordt opgegeven als een tuple (naam, transformer, kolommen).
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
num_features = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm"]
cat_features = ["species", "island"]
preprocessor = ColumnTransformer(
transformers=[
("num", StandardScaler(), num_features),
("cat", OneHotEncoder(sparse_output=False), cat_features),
]
)
X_processed = preprocessor.fit_transform(X_train)
print("Shape after preprocessing:", X_processed.shape)
print("Feature names:", preprocessor.get_feature_names_out())Shape after preprocessing: (266, 9)
Feature names: ['num__bill_length_mm' 'num__bill_depth_mm' 'num__flipper_length_mm'
'cat__species_Adelie' 'cat__species_Chinstrap' 'cat__species_Gentoo'
'cat__island_Biscoe' 'cat__island_Dream' 'cat__island_Torgersen']
Alles samenvoegen: ColumnTransformer in een Pipelineยถ
Het meest gebruikte patroon in scikit-learn: een ColumnTransformer als eerste stap in een Pipeline, gevolgd door een Estimator.
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
num_features = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm"]
cat_features = ["species", "island"]
preprocessor = ColumnTransformer(
transformers=[
("num", StandardScaler(), num_features),
("cat", OneHotEncoder(sparse_output=False), cat_features),
]
)
full_pipeline = Pipeline(
steps=[
("preprocessor", preprocessor),
("model", LinearRegression()),
]
)
full_pipeline.fit(X_train, y_train)
print(f"Rยฒ on test set (num + cat): {full_pipeline.score(X_test, y_test):.3f}")Rยฒ on test set (num + cat): 0.867
Merk op: we roepen gewoon fit(X_train, y_train) op de volledige pipeline โ alles wordt intern correct afgehandeld. X_test wordt enkel getransformeerd, nooit opnieuw gefit.
Pipeline inspecterenยถ
Je kan altijd in de pipeline duiken om geleerde parameters op te vragen.
# Access fitted transformer inside the pipeline
num_scaler = full_pipeline.named_steps["preprocessor"].named_transformers_["num"]
print("Means of numerical features:", num_scaler.mean_.round(2))
ohe = full_pipeline.named_steps["preprocessor"].named_transformers_["cat"]
print("Species categories:", ohe.categories_[0])Means of numerical features: [ 44.1 17.16 201. ]
Species categories: ['Adelie' 'Chinstrap' 'Gentoo']
# get_params() lists all hyperparameters โ useful for tuning later
params = full_pipeline.get_params()
# show only a few relevant ones
relevant = {k: v for k, v in params.items() if "scaler" in k or "model" in k}
for k, v in relevant.items():
print(f"{k}: {v}")model: LinearRegression()
model__copy_X: True
model__fit_intercept: True
model__n_jobs: None
model__positive: False
model__tol: 1e-06
Oefeningenยถ
Oefening 1 โ Bouw een eenvoudige Pipeline met twee stappen: een MinMaxScaler gevolgd door een LinearRegression. Train op de numerieke features (bill_length_mm, bill_depth_mm, flipper_length_mm) van de penguins-trainingsset en evalueer de Rยฒ op de testset.
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
# your solution hereOefening 2 โ Gebruik make_pipeline om dezelfde pipeline als oefening 1 te bouwen. Verifieer dat de stap-namen automatisch zijn ingevuld met named_steps.
from sklearn.pipeline import make_pipeline
# your solution hereOefening 3 โ Bouw een ColumnTransformer voor de penguins-dataset:
Numerieke kolommen (
bill_length_mm,bill_depth_mm,flipper_length_mm):StandardScalerCategorische kolom (
sex):OneHotEncoder
Fit op de trainingsset en transformeer. Druk de shape en de feature-namen af.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
# your solution hereOefening 4 โ Combineer de ColumnTransformer uit oefening 3 met een LinearRegression in รฉรฉn volledige Pipeline. Fit op de trainingsset (met bill_length_mm, bill_depth_mm, flipper_length_mm en sex als input en body_mass_g als target). Evalueer de Rยฒ op de testset.
# your solution hereOefening 5 โ Haal uit de getrainede pipeline de geleerde gemiddelden op van de numerieke scaler (via named_steps en named_transformers_). Vergelijk ze met de werkelijke gemiddelden in de trainingsset (met X_train.mean()).
# your solution here