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
Pipelineinnerhalb 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:
- Ein Fold wird als Testmenge zurückgehalten.
- Die verbleibenden
k - 1Folds bilden die Trainingsmenge. - 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.0249Die Funktion cross_val_score verwaltet alle Iterationen intern. Wichtige Parameter:
| Parameter | Zweck |
|---|---|
estimator | Jedes scikit-learn-Modell (oder Pipeline) |
X, y | Feature-Matrix und Zielvektor |
cv | Cross-Validator-Objekt oder Integer (z. B. cv=5) |
scoring | Metrik-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 samplesStratified 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.0298StratifiedKFold 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.1795Wann 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.0452Verwenden 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.9600Der 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
| Technik | Wann verwenden | scikit-learn-Klasse |
|---|---|---|
| K-Fold | Standardwahl für die meisten Regressions-/Klassifikationsaufgaben | KFold |
| Stratified K-Fold | Klassifikation mit unausgewogenen Klassen | StratifiedKFold |
| Leave-One-Out | Sehr kleine Datensätze (< ~100 Stichproben) | LeaveOneOut |
| Nested CV | Bericht unverfälschter Scores mit Hyperparameter-Tuning | GridSearchCV inside cross_val_score |
| Time-series CV | Daten mit zeitlicher Ordnung | TimeSeriesSplit |
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
scoringincross_val_scoreübergeben können