W3docs

Cross-Validation in Python: K-Fold, Stratified, LOOCV

Cross-Validation in Python: K-Fold, Stratified K-Fold, Leave-One-Out, Nested CV und Pipelines mit scikit-learn — mit ausführbaren Beispielen.

Cross-Validation ist die Standardmethode, um abzuschätzen, wie gut ein Machine-Learning-Modell auf unbekannten Daten abschneiden wird. Statt sich auf einen einzelnen Train/Test-Split zu verlassen — der je nachdem, welche Stichproben wo landen, einen übermäßig optimistischen oder pessimistischen Score liefern kann — trainiert und bewertet Cross-Validation das Modell mehrfach auf verschiedenen Partitionen der Daten und mittelt die Ergebnisse.

Diese Seite behandelt:

  • Warum ein einzelner Train/Test-Split nicht ausreicht
  • K-Fold Cross-Validation und die Wahl von k
  • Stratified K-Fold für unausgewogene Klassenverteilungen
  • Leave-One-Out (LOO) Cross-Validation für kleine Datensätze
  • Auswertung mehrerer Metriken mit cross_validate
  • Verwendung einer Pipeline innerhalb der Cross-Validation zur Vermeidung von Data Leakage
  • Verschachtelte Cross-Validation für unverfälschtes Hyperparameter-Tuning

Alle Beispiele verwenden scikit-learn und den eingebauten Iris-Datensatz, sodass Sie sie sofort ohne Downloads ausführen können.

Warum Cross-Validation wichtig ist

Ein naiver Auswertungsworkflow teilt die Daten einmalig auf, trainiert auf einem Teil und testet auf dem anderen. Der erhaltene Score hängt stark davon ab, welche Stichproben in welchem Teil gelandet sind — ein günstiger Split kann ein schwaches Modell gut aussehen lassen; ein ungünstiger kann ein starkes Modell schlecht aussehen lassen.

Cross-Validation löst dieses Problem, indem der Train/Test-Prozess k-mal wiederholt wird, wobei jedes Mal ein anderer Teil der Daten als Testmenge dient. Der Endscore ist der Durchschnitt über alle Folds, der weitaus stabiler als eine einzelne Messung ist.

Cross-Validation nutzt außerdem begrenzte Daten optimal: Jede Stichprobe wird über das gesamte Experiment sowohl für Training als auch für die Auswertung verwendet.

Unter train/test split finden Sie die einfachere Basistechnik, die Cross-Validation verbessert.

K-Fold Cross-Validation

K-Fold ist die am häufigsten verwendete Cross-Validation-Strategie. Die Daten werden in k gleichgroße Folds aufgeteilt. In jeder der k Iterationen:

  1. Ein Fold wird als Testmenge zurückgehalten.
  2. Die verbleibenden k - 1 Folds bilden die Trainingsmenge.
  3. Das Modell wird von Grund auf neu trainiert und auf dem Test-Fold bewertet.

Nach k Iterationen liegen k Scores vor. Ihr Mittelwert ist die kreuzvalidierte Leistungsschätzung; ihre Standardabweichung zeigt, wie konsistent diese Leistung über verschiedene Datensegmente ist.

Für k = 5 und einen Datensatz mit 150 Stichproben enthält jeder Fold 30 Stichproben (20 %) zum Testen und 120 Stichproben (80 %) zum Trainieren.

Einfaches K-Fold-Beispiel

from sklearn.model_selection import KFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

# Load the built-in Iris dataset (150 samples, 4 features, 3 classes)
iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
kfold = KFold(n_splits=5, shuffle=True, random_state=42)

scores = cross_val_score(model, X, y, cv=kfold, scoring='accuracy')

print("Fold scores:", scores.round(4))
# Fold scores: [1.     1.     0.9333 0.9667 0.9667]

print("Mean accuracy: %.4f  Std: %.4f" % (scores.mean(), scores.std()))
# Mean accuracy: 0.9733  Std: 0.0249

Die Funktion cross_val_score verwaltet alle Iterationen intern. Wichtige Parameter:

ParameterZweck
estimatorJedes scikit-learn-Modell (oder Pipeline)
X, yFeature-Matrix und Zielvektor
cvCross-Validator-Objekt oder Integer (z. B. cv=5)
scoringMetrik-String — 'accuracy', 'f1_macro', 'roc_auc' usw.

Wahl von k

  • k = 5 oder k = 10 wird für die meisten Datensätze empfohlen. Diese Werte bieten einen guten Bias-Varianz-Kompromiss bei der Schätzung.
  • Größeres k (z. B. 10) liefert geringere Verzerrung, aber höhere Varianz in der Schätzung und ist rechenintensiver.
  • Kleineres k (z. B. 3) ist schneller, aber die Schätzung reagiert empfindlicher auf die Art der Datenaufteilung.
  • Bei sehr kleinen Datensätzen (weniger als ~100 Stichproben) sollten Sie stattdessen Leave-One-Out in Betracht ziehen.

Manuelle Inspektion der Folds

Sie können die Folds selbst iterieren, wenn Sie prüfen möchten, was in jeden Split einfließt, oder wenn Sie benutzerdefinierte Logik pro Fold ausführen möchten:

from sklearn.model_selection import KFold
from sklearn.datasets import load_iris
import numpy as np

iris = load_iris()
X, y = iris.data, iris.target

kfold = KFold(n_splits=5, shuffle=True, random_state=42)

for fold, (train_idx, test_idx) in enumerate(kfold.split(X), start=1):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    print(f"Fold {fold}: train={len(train_idx)} samples, test={len(test_idx)} samples")

Ausgabe:

Fold 1: train=120 samples, test=30 samples
Fold 2: train=120 samples, test=30 samples
Fold 3: train=120 samples, test=30 samples
Fold 4: train=120 samples, test=30 samples
Fold 5: train=120 samples, test=30 samples

Stratified K-Fold Cross-Validation

Normales K-Fold teilt die Daten nach Indexreihenfolge auf. Bei unausgewogenen Klassenverteilungen kann dies dazu führen, dass einige Folds sehr wenige Beispiele einer Minderheitsklasse enthalten, was den Score unzuverlässig macht.

Stratified K-Fold stellt sicher, dass jeder Fold ungefähr denselben Anteil jeder Klasse wie der gesamte Datensatz enthält. Verwenden Sie StratifiedKFold, wenn Ihr Zielwert kategorialer Natur ist:

from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
skfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

scores = cross_val_score(model, X, y, cv=skfold, scoring='accuracy')

print("Fold scores:", scores.round(4))
# Fold scores: [1.     0.9667 0.9333 1.     0.9333]

print("Mean accuracy: %.4f  Std: %.4f" % (scores.mean(), scores.std()))
# Mean accuracy: 0.9667  Std: 0.0298

StratifiedKFold ist der Standard-Cross-Validator, der innerhalb von GridSearchCV und RandomizedSearchCV für Klassifikationsprobleme verwendet wird — in diesen Kontexten erhalten Sie die Stratifizierung automatisch.

Leave-One-Out Cross-Validation

Leave-One-Out (LOO) Cross-Validation ist der extreme Fall: k entspricht der Anzahl der Stichproben. In jeder Iteration ist eine Stichprobe die Testmenge und alle verbleibenden Stichproben bilden die Trainingsmenge. Bei einem Datensatz mit 150 Stichproben bedeutet das 150 Trainings- und Auswertungszyklen.

from sklearn.model_selection import LeaveOneOut, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
loocv = LeaveOneOut()

scores = cross_val_score(model, X, y, cv=loocv, scoring='accuracy')

print(f"Number of folds: {len(scores)}")        # 150
print(f"Mean accuracy: {scores.mean():.4f}")     # 0.9667
print(f"Std deviation: {scores.std():.4f}")      # 0.1795

Wann LOO verwenden:

  • Ihr Datensatz hat weniger als ~100 Stichproben und Sie können es sich nicht leisten, Daten für Tests zurückzuhalten.
  • Sie möchten die verzerrungsärmste Schätzung der Modellleistung.

Nachteile von LOO:

  • Sehr hohe Rechenkosten — das Modell wird n-mal neu trainiert.
  • Hohe Varianz in der Schätzung: Der Test-Score jedes Folds ist 0 oder 1 (binäre Klassifikation) oder ein einzelner Punkt, sodass die Standardabweichung für einzelne Folds nicht aussagekräftig ist.

Für die meisten Datensätze ist K-Fold mit k=5 oder k=10 ein besserer Kompromiss.

Mehrere Metriken gleichzeitig auswerten

cross_val_score kann pro Aufruf nur eine Metrik berechnen. Verwenden Sie cross_validate, um mehrere Metriken gleichzeitig zu berechnen und auch Trainings-Scores abzurufen, um Overfitting zu erkennen:

from sklearn.model_selection import KFold, cross_validate
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
import numpy as np

iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
kfold = KFold(n_splits=5, shuffle=True, random_state=42)

cv_results = cross_validate(
    model, X, y,
    cv=kfold,
    scoring=['accuracy', 'f1_macro'],
    return_train_score=True,
)

print("Test accuracy: ", cv_results['test_accuracy'].round(4))
# Test accuracy:  [1.     1.     0.9333 0.9667 0.9667]

print("Train accuracy:", cv_results['train_accuracy'].round(4))
# Train accuracy: [0.975  0.9583 0.9833 0.975  0.9833]

print("Test F1-macro: ", cv_results['test_f1_macro'].round(4))
# Test F1-macro:  [1.     1.     0.9259 0.9691 0.971 ]

Der Vergleich von Trainings- und Test-Scores über Folds hinweg ist eine schnelle Möglichkeit, Overfitting zu erkennen: Wenn die Trainings-Accuracy durchgehend deutlich höher als die Test-Accuracy ist, hat das Modell die Trainingsdaten auswendig gelernt. Weitere Hintergrundinformationen finden Sie unter Bias und Varianz.

Eine Pipeline innerhalb der Cross-Validation verwenden

Ein häufiger Fehler besteht darin, Vorverarbeitungsschritte (wie Feature-Skalierung oder Imputation) auf dem gesamten Datensatz vor der Cross-Validation anzupassen. Dadurch gelangen Informationen aus dem Test-Fold in den Trainingsprozess, was zu einem übermäßig optimistischen Score führt.

Das korrekte Muster besteht darin, die Vorverarbeitung und das Modell in einer Pipeline zu kapseln und die Pipeline an cross_val_score zu übergeben. scikit-learn passt die gesamte Pipeline — einschließlich des Scalers — unabhängig innerhalb jedes Folds neu an:

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

iris = load_iris()
X, y = iris.data, iris.target

# Correct: preprocessing is fitted only on training folds
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('clf', LogisticRegression(max_iter=200)),
])

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X, y, cv=skf, scoring='accuracy')

print("Fold scores:", scores.round(4))
# Fold scores: [1.     0.9667 0.9    1.     0.9   ]

print("Mean accuracy: %.4f  Std: %.4f" % (scores.mean(), scores.std()))
# Mean accuracy: 0.9533  Std: 0.0452

Verwenden Sie immer eine Pipeline, wenn Ihr Workflow einen Schritt enthält, der aus den Daten lernt (Skalierung, PCA, Encoding, Imputation).

Verschachtelte Cross-Validation

Wenn Sie Cross-Validation verwenden, um sowohl Hyperparameter zu tunen als auch die Modellleistung auf denselben Daten zu bewerten, riskieren Sie ein Overfitting gegenüber den Validierungs-Folds — die gewählten Hyperparameter sind diejenigen, die auf diesen bestimmten Partitionen die besten Scores erzielt haben, sodass der gemeldete Score optimistisch ist.

Verschachtelte Cross-Validation trennt die beiden Anliegen:

  • Innere Schleife: Hyperparameter per Grid-Search auf Trainings-Folds auswählen.
  • Äußere Schleife: Das beste von der inneren Schleife gefundene Modell auf einem zurückgehaltenen Test-Fold bewerten.
from sklearn.model_selection import GridSearchCV, StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

# Inner CV: hyperparameter selection
inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=2)
param_grid = {'C': [0.01, 0.1, 1, 10]}
gs = GridSearchCV(
    LogisticRegression(max_iter=300),
    param_grid,
    cv=inner_cv,
    scoring='accuracy',
)

# Outer CV: unbiased performance estimation
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
nested_scores = cross_val_score(gs, X, y, cv=outer_cv, scoring='accuracy')

print("Nested CV fold scores:", nested_scores.round(4))
# Nested CV fold scores: [0.9667 1.     0.9333 1.     0.9   ]

print("Mean accuracy: %.4f" % nested_scores.mean())
# Mean accuracy: 0.9600

Der verschachtelte Ansatz liefert eine unverfälschte Schätzung der Leistung des endgültig eingesetzten Modells. Verwenden Sie ihn, wenn Sie Ergebnisse in einem Forschungskontext berichten oder Algorithmen vergleichen. Für einen einfachen Deployment-Workflow, bei dem Sie ohnehin auf allen verfügbaren Daten neu trainieren, ist eine einzelne äußere Schleife mit GridSearchCV in der Regel ausreichend. Unter grid search finden Sie eine detaillierte Erläuterung des Hyperparameter-Tunings.

Häufige Fallstricke

Vorverarbeitung außerhalb des Folds

Das Anpassen eines Scalers auf dem gesamten Datensatz vor dem Aufruf von cross_val_score — statt innerhalb einer Pipeline — lässt Test-Fold-Statistiken in das Training einfließen. Die Lösung ist immer, eine Pipeline zu verwenden.

Fehlerhafte Verwendung von random_state

Wenn Sie shuffle=True ohne random_state setzen, erzeugt jeder Lauf einen anderen Split und Ihre Ergebnisse sind nicht reproduzierbar. Setzen Sie random_state immer auf einen festen Integer, wenn Sie Zahlen berichten.

Interpretation der Standardabweichung

Eine hohe Standardabweichung über Folds ist nicht immer schlecht — sie kann echte Variabilität im Datensatz widerspiegeln (z. B. sind manche Folds einfacher als andere). Betrachten Sie die einzelnen Fold-Scores, bevor Sie Schlussfolgerungen ziehen.

Cross-Validation bei Zeitreihendaten

K-Fold mischt Daten zufällig, was bei Zeitreihenproblemen zukünftige Informationen in vergangene Trainingsfenster einmischen würde. Verwenden Sie stattdessen TimeSeriesSplit aus scikit-learn, das die zeitliche Reihenfolge respektiert.

Kurzübersicht

TechnikWann verwendenscikit-learn-Klasse
K-FoldStandardwahl für die meisten Regressions-/KlassifikationsaufgabenKFold
Stratified K-FoldKlassifikation mit unausgewogenen KlassenStratifiedKFold
Leave-One-OutSehr kleine Datensätze (< ~100 Stichproben)LeaveOneOut
Nested CVBericht unverfälschter Scores mit Hyperparameter-TuningGridSearchCV inside cross_val_score
Time-series CVDaten mit zeitlicher OrdnungTimeSeriesSplit

Verwandte Themen

  • Train/Test Split — die einfachere Basistechnik, die Cross-Validation verbessert
  • Grid Search — Hyperparameter-Tuning, häufig in Kombination mit Cross-Validation
  • Logistic Regression — einer der Klassifikatoren, die in den obigen Beispielen verwendet werden
  • Linear Regression — Regressions-Pendant, ebenfalls mit Cross-Validation ausgewertet
  • Confusion Matrix — klassenspezifische Leistungsaufschlüsselung als Ergänzung zu Accuracy-Scores
  • AUC-ROC Curve — eine weitere Auswertungsmetrik, die Sie an scoring in cross_val_score übergeben können
Was this page helpful?