Januar 12, 2026

Cross Validation

Ziel beim Machine Learning ist es ein Modell zu trainieren, dass auch auf neuen, bisher ungesehenen Daten funktioniert. Cross Validation ist eine zuverlässige Methode der Modellvalidierung, um die Leistung des Modells realistisch einzuschätzen.

Grundidee

In den meisten Fällen wird der Datensatz zunächst nur in einen Trainings- und einen Testdatensatz geteilt. Das Modell wird auf dem Trainingsdatensatz entwickelt und anschließend mithilfe der Testdaten validiert.

Das Problem ist, dass ein einfacher Split häufig stark zufallsabhängig ist, je nachdem welche Datenpunkte in den Spit fallen. Darüber hinaus ist es ineffizient, da diese Daten fürs Training nicht mehr genutzt werden können.

Cross Validation evaluiert das Modell dagegen mehrfach auf unterschiedlichen Datenaufteilungen. Dadurch erhält man robustere Schätzer der Generalisierungsleistung des Modells und gleichzeitig eine Einschätzung für die Streuung der Ergebnisse.

Als Testmodell verwenden wir eine einfaches Klassifizierungsbeispiel auf Basis des Iris Datensatzes:

from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

# --- Klassifikationsbeispiel ---
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
clf = LogisticRegression(max_iter=200)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

K-Fold Cross Validation

Bei der sogenannten K-Fold Cross Validation wird der Datensatz in k gleich große Teile (Folds) geteilt. Man trainiert das Modell k-mal und jeder Fold dient einmal als Testmenge. Am Ende erhält man einen Mittelwert der Performance (aus allen k Testergebnissen) und die Standardabweichung als Maß für die Stabilität des Modells.

from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression

clf = LogisticRegression(max_iter=1000)

scores = cross_val_score(
    clf,
    X,
    y,
    cv=5,
    scoring="accuracy"
)

print("CV-Accuracy:", scores)
print("Mittelwert:", scores.mean())
print("Standardabweichung:", scores.std())

Häufig ist es dabei sinnvoll, nicht nur einen Score zu verwenden. Insbesondere Accuracy ist bei Klassenungleichgewicht (imbalanced data) häufig irreführend.

Mit return_train_score=True kann man zudem auch die Trainingsmetriken zurückgeben lassen. Das hilft beim Erkennen von Overfitting (Metriken auf Train sind deutlich höher als auf Test).

import pandas as pd
from sklearn.model_selection import cross_validate

scoring = ["accuracy", "precision_macro", "recall_macro", "f1_macro"]

cv_results = cross_validate(
    clf,
    X,
    y,
    cv=5,
    scoring=scoring,
    return_train_score=True
)

df = pd.DataFrame(cv_results)
print(df)
print("\nMittelwerte:\n", df.mean(numeric_only=True))

Stratified K-Fold

Bei sehr ungleich verteilen Klassen, kann eine rein zufällige Verteilung der Folds problematisch sein und zu verzerrten Ergebnissen führen. Hier eignet sich die Stratified K-Fold Cross Validation. Sie sichert gleiche Klassenverteilung in jedem Fold.

from sklearn.model_selection import StratifiedKFold, cross_val_score

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

scores = cross_val_score(
    clf,
    X,
    y,
    cv=skf,
    scoring="f1_macro"
)

print("F1_macro pro Fold:", scores)
print("Ø F1_macro:", scores.mean())

Cross Validation für Regression

Bei Regressionsmodellen funktioniert die Cross Validation im Prinzip genauso wie bei der Klassifikation. Allerdings werden andere Metriken verwendet (RMSE, MAE, …). Wir verwenden ein einfaches Beispiel für Regression:

# --- Regressionsbeispiel ---
Xr, yr = make_regression(n_samples=200, n_features=3, noise=10, random_state=42)
Xr_train, Xr_test, yr_train, yr_test = train_test_split(Xr, yr, test_size=0.3, random_state=42)
reg = LinearRegression()
reg.fit(Xr_train, yr_train)
yr_pred = reg.predict(Xr_test)

In scikit-learn sind viele Losses als “Score” implementiert und werden negativ zurückgegeben (weil “größer ist besser”). Hier macht es Sinn die Werte vorher zu „drehen“.

import numpy as np
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import Ridge

reg = Ridge()

scores = cross_val_score(
    reg,
    Xr,
    yr,
    cv=5,
    scoring="neg_root_mean_squared_error"
)

rmse_scores = -scores
print("RMSE pro Fold:", rmse_scores)
print("Ø RMSE:", rmse_scores.mean())
print("Std RMSE:", rmse_scores.std())

Pipelines zur Verhinderung von Data Leakage

Ein sehr häufiger Fehler bei der Cross Validation ist, dass das Preprocessing der Daten vor der Cross Validation ausgeführt wird. Allerdings erzeugen Skalierung oder Encodierung der Daten Data Leakage, da Informationen aus allen Folds genutzt werden. Indirekt erhält jeder Test-Fold dadurch Informationen aus den Trainingsdaten (beispielsweise zur Verteilung).

Sklearn Pipelines können dabei helfen alle Schritte sauber getrennt auszuführen.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, StratifiedKFold

pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("clf", LogisticRegression(max_iter=1000))
])

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

scores = cross_val_score(pipe, X, y, cv=cv, scoring="f1_macro")
print(scores.mean(), scores.std())

Weiterführende Themen zur Cross Validation

Nested Cross Validation zur Bewertung bei Hyperparameter-Tuning

Wenn du Hyperparameter optimierst (GridSearchCV/RandomizedSearchCV) und danach mit derselben Cross Validation “bewertest”, ist das oft zu optimistisch, da die Daten bereits mehrfach verwendet wurden. Nested Cross Validation trennt deshalb die innere CV (für die Hyperparameter Optimierung) von der äußeren CV (zur echten Modell-Bewertung).

from sklearn.model_selection import GridSearchCV, cross_val_score, StratifiedKFold

param_grid = {"clf__C": [0.1, 1, 10]}

inner_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)

search = GridSearchCV(pipe, param_grid=param_grid, cv=inner_cv, scoring="f1_macro")

nested_scores = cross_val_score(search, X, y, cv=outer_cv, scoring="f1_macro")
print(nested_scores.mean(), nested_scores.std())

Repeated K-Fold für mehr Stabilität

Wenn man noch mehr Stabilität erhalten möchte, dann wiederholt man die Cross Validation mehrfach mit unterschiedlichen Shuffles. Das reduziert die Zufälligkeit noch weiter, kostet aber mehr Rechenzeit.

from sklearn.model_selection import RepeatedStratifiedKFold, cross_val_score

rskf = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=42)
scores = cross_val_score(pipe, X, y, cv=rskf, scoring="f1_macro")
print(scores.mean(), scores.std())

Time Serie Cross Validation

Bei Zeitreihen kann man die Daten nicht einfach mischen, wie bei K-Fold, da sonst ein leakt der Daten aus der Vergangenheit in die Zukunft entsteht. Stattdessen arbeitet man mit TimeSeriesSplit, welche eine rollierende Aufteilung der Daten erzeugt.

from sklearn.model_selection import TimeSeriesSplit, cross_val_score

tscv = TimeSeriesSplit(n_splits=5)
scores = cross_val_score(reg, X_time, y_time, cv=tscv, scoring="neg_mean_absolute_error")
mae_scores = -scores
print(mae_scores.mean(), mae_scores.std())

GroupKFold

Ähnlich kann es auch sein, dass Gruppen in den Daten existieren, die zusammenbleiben müssen. Beispielsweise wenn mehrere Messungen pro Person vorgenommen werden oder mehrere Session pro Nutzer. Auch hier könnte Data Leakage entstehen, wenn die Daten einfach nur zufällig gesplittet werden. Stattdessen verwendet man GroupKFold, welches dafür sorgt, dass die Daten strikt nach Gruppen getrennt werden.

from sklearn.model_selection import GroupKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# Modell + Pipeline
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("clf", LogisticRegression(max_iter=1000))
])

gkf = GroupKFold(n_splits=5)

scores = cross_val_score(
    pipe,
    X,
    y,
    cv=gkf,
    groups=groups,
    scoring="f1_macro"
)

print("F1_macro pro Fold:", scores)
print("Ø F1_macro:", scores.mean())
print("Std:", scores.std())

Leave-One-Out (LOO)

Leave-One-Out sei nur ergänzend erwähnt. Das ist eine weitere Variante der Cross-Validation die bei sehr kleinen Datensätzen verwendet wird.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert