In den meisten Machine-Learning-Projekt kommt irgendwann der Moment, an dem die Datenaufbereitung unübersichtlich wird: Man hat mehrere Schritte zur Vorverarbeitung, unterschiedliche Feature-Typen und Modelle, die verschiedene Input-Formate erwarten. Spätestens dann lohnt es sich auf Pipelines zurückzugreifen um einem einen strukturierten Ansatz zu folgen. Sie haben den Vorteil, dass sie weniger fehleranfällig sind und reproduzierbar sind. Insbesondere wenn es darum geht Trainings- und Testdaten sauber zu trennen und trotzdem konsistent aufzubereiten. Zudem sind sie deutlich besser wartbar und helfen dadurch beispielsweise beim Retraining von Modellen in den Verarbeitungsschritte konsistent zu bleiben
Scikit-Learn Pipelines
Eine Pipeline in Scikit-Learn ist ein Objekt, das eine Abfolge von Transformationen und einem abschließenden Modell bündelt. Alle Schritte sind dabei vollständig kompatibel mit Scikit-Learn’s API – inklusive GridSearchCV, Cross-Validation und Model Export.
Beispiel:
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='mean')),
('scaler', StandardScaler()),
('model', LogisticRegression())
])
Wichtige Regeln
- Alle Schritte außer dem letzten müssen Transformer sein
- Der letzte Schritt ist das Modell
fit()wird von links nach rechts ausgeführtpredict()nutzt exakt dieselbe Transformationskette
Beispieldatensatz
Um die Funktionsweise von Pipelines besser zu zeigen, verwenden wir im folgenden den Beispieldatensatz „Adult Census Income“. Wir trainieren ein Model, welches vorhersagen soll, ob eine Person mehr als 50.000 USD pro Jahr verdient.
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
import pandas as pd
adult = fetch_openml(
name="adult",
version=2,
as_frame=True
)
X = adult.data
y = adult.target
# Train-Test Split
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=42,
stratify=y
)Feature Engineering mit ColumnTransformer
In realen Datensätzen hast du fast immer heterogene Feature-Typen:
- numerische Features
- kategoriale Features
- weitere Datentypen wie Datum, Text, etc.
ColumnTransformer hilft hier bei der konsistenten und übersichtlichen Verarbeitung.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
numeric_features = [
'age',
'hours-per-week',
'capital-gain',
'capital-loss'
]
categorical_features = [
'workclass',
'education',
'marital-status',
'occupation',
'relationship',
'race',
'sex',
'native-country'
]
# Numerische Pipeline
numeric_transformer = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# Kategoriale
categorical_transformer = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('encoder', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])
# Zusammenführen
preprocessor = ColumnTransformer([
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])Eigene Transformer
Wenn Standard-Transformer nicht reichen, kannst man eigene schreiben:
from sklearn.base import BaseEstimator, TransformerMixin
class LogTransformer(BaseEstimator, TransformerMixin):
def fit(self, X, y=None):
return self
def transform(self, X):
return np.log1p(X)
End-to-End Pipeline
Wenn nun Transformationen und Modell zusammen ausgeführt werden sollen, dann kann man diese in eine Pipeline zusammenführen. Dabei wird das „fitten“ der Pipeline nur auf den Trainingsdaten ausgeführt. Für die Vorhersage wird auf den Testdaten dann „predict()“ verwendet:
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
model_pipeline = Pipeline([
('preprocessing', preprocessor),
('classifier', LogisticRegression(max_iter=1000, class_weight='balanced'))
])
# Training
model_pipeline.fit(X_train, y_train)
# Vorhersage
y_pred = model_pipeline.predict(X_test)
y_proba = model_pipeline.predict_proba(X_test)
Cross-Validation ohne Dataleaks
Pipelines helfen vor allem auch dabei, Cross-Validation sauber zu implementieren, ohne das Dataleaks entstehen. Jeder Fold bekommt dabei ein eigenes Feature Engineering:
from sklearn.model_selection import cross_val_score
cv_scores = cross_val_score(
model_pipeline,
X_train,
y_train,
cv=5,
scoring='roc_auc',
n_jobs=-1
)
print(f"Mean ROC-AUC: {cv_scores.mean():.3f}")Hyperparameter-Tuning
Auch das Optimieren von Hyperparametern lässt sich elegant in Pipelines integrieren:
from sklearn.model_selection import GridSearchCV
param_grid = {
'classifier__C': [0.01, 0.1, 1, 10]
}
grid = GridSearchCV(
model_pipeline,
param_grid=param_grid,
cv=5,
scoring='roc_auc',
n_jobs=-1
)
grid.fit(X_train, y_train)
grid.best_params_
best_pipeline = grid.best_estimator_Retraining-Pipelines
Besonders hilfreich sind Pipelines, wenn es darum geht regelmäßiges Retraining von bestehenden Modellen durchzuführen.
Ein typisches Retraining-Szenario sieht so aus:
- Neue Daten kommen regelmäßig
- Feature Engineering bleibt gleich
- Modell wird auf neuen Daten neu trainiert
- Das Ergebnis muss reproduzierbar sein
Wenn man bereits eine bestehende Pipeline hat, kann man das so umsetzen:
import joblib
def retrain_model(X_new, y_new, pipeline):
pipeline.fit(X_new, y_new)
return pipeline
# Model persistieren
joblib.dump(best_pipeline, "income_classifier_pipeline.joblib")
# Model laden und ausführen
loaded_pipeline = joblib.load("income_classifier_pipeline.joblib")
loaded_pipeline.predict(new_data)
Fazit
Scikit-Learn Pipelines helfen nicht nur beim Aufräumen deines Codes, sondern ermöglichen modulares, nachvollziehbares und robustes Feature Engineering.
- Sauberer Code: Klare Trennung der Schritte, keine Redundanz.
- Automatische Cross-Validation: Alle Schritte werden in jedem Fold korrekt ausgeführt.
- Einfache Hyperparameter-Optimierung:
GridSearchCVoderRandomizedSearchCVfunktionieren problemlos mit Pipelines. - Produktionsbereit: Die gesamte Pipeline kann via
joblibgespeichert und geladen werden. - Weniger Daten-Leaks: Transformationen passieren innerhalb der Trainingsdaten beim Cross-Validation.