W3docs

Machine Learning: Training und Testing in Python

Daten in Trainings- und Testmengen aufteilen mit scikit-learn. Behandelt test_size, random_state, stratify und Auswertungsmetriken.

Die Train/Test-Aufteilung ist der grundlegendste Schritt beim Aufbau eines Machine-Learning-Modells. Die Idee ist einfach: Halte einen Teil deiner Daten während des Trainings vor dem Modell verborgen und miss dann, wie gut das Modell auf diesem verborgenen Anteil abschneidet. Ohne diese Trennung gibt es keine ehrliche Möglichkeit zu wissen, ob dein Modell ein Muster wirklich gelernt hat oder lediglich die Trainingsbeispiele auswendig gelernt hat.

Dieses Kapitel behandelt, wie train_test_split aus scikit-learn funktioniert, was jeder Parameter bewirkt und wie das resultierende Modell ausgewertet wird – sowohl für Regressions- als auch für Klassifikationsprobleme.

Warum Trainings- und Testdaten trennen?

Ein Modell, das auf denselben Daten trainiert und ausgewertet wird, wirkt weit genauer als es wirklich ist. Dies nennt man Data Leakage oder Overfitting: Das Modell hat die Trainingsbeispiele auswendig gelernt, anstatt ein verallgemeinerbares Muster zu erkennen.

Stell dir einen Schüler vor, der 100 Übungsaufgaben lernt und dann einen Test mit genau diesen 100 Aufgaben schreibt. Er erreicht vielleicht 100 % – aber dieser Wert sagt nichts darüber aus, ob er den Stoff wirklich versteht.

Die Train/Test-Aufteilung verhindert dies durch:

  • Training des Modells auf einem Teil der Daten, damit es Muster erlernen kann.
  • Testen des Modells auf einem separaten, ungesehenen Teil, um die reale Leistung zu messen.

Eine typische Aufteilung ist 80 % Training / 20 % Test, wobei das richtige Verhältnis davon abhängt, wie viele Daten vorhanden sind.

Die Funktion train_test_split

scikit-learn stellt train_test_split in seinem Modul model_selection bereit. Sie mischt den Datensatz zufällig durch und teilt ihn in zwei Teile auf:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

Die vier Rückgabewerte liegen immer in dieser Reihenfolge vor: Trainingsmerkmale, Testmerkmale, Trainingsbezeichnungen, Testbezeichnungen.

Wichtige Parameter

ParameterTypBeschreibung
test_sizefloat oder intAnteil (0–1) oder absolute Anzahl der Testsamples. Standard ist 0.25.
train_sizefloat oder intKomplement zu test_size. Normalerweise setzt man eines der beiden, nicht beide.
random_stateintSeed für den Zufallszahlengenerator. Verwende eine beliebige ganze Zahl, um die Aufteilung reproduzierbar zu machen.
stratifyarray-likeÜbergib hier y, um die Klassenanteile in beiden Splits beizubehalten. Unverzichtbar bei unausgewogenen Datensätzen.
shuffleboolOb vor der Aufteilung gemischt werden soll. Standard ist True. Auf False setzen für Zeitreihendaten.

Auswirkung von test_size

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

X, y = load_iris(return_X_y=True)   # 150 samples

for ts in [0.1, 0.2, 0.3]:
    X_tr, X_te, _, _ = train_test_split(X, y, test_size=ts, random_state=42)
    print(f"test_size={ts}: train={len(X_tr)}, test={len(X_te)}")

Ausgabe:

test_size=0.1: train=135, test=15
test_size=0.2: train=120, test=30
test_size=0.3: train=105, test=45

random_state und Reproduzierbarkeit

Ohne random_state ist die Aufteilung bei jedem Skriptlauf anders. Setze sie auf eine beliebige ganze Zahl, um reproduzierbare Ergebnisse zu erhalten:

import numpy as np
from sklearn.model_selection import train_test_split

X = np.arange(10).reshape(-1, 1)
y = np.arange(10)

_, X_te1, _, _ = train_test_split(X, y, test_size=0.3, random_state=42)
_, X_te2, _, _ = train_test_split(X, y, test_size=0.3, random_state=42)

print("Same random_state → same split:", list(X_te1.ravel()) == list(X_te2.ravel()))
# True

Die Wahl der ganzen Zahl (42, 0, 1 usw.) spielt keine Rolle – wichtig ist nur, dass stets derselbe Wert verwendet wird.

Regressionsbeispiel: Hauspreise vorhersagen

Das folgende Beispiel erzeugt einen synthetischen Datensatz aus Hausgrößen und -preisen, trainiert ein lineares Regressionsmodell und wertet es auf dem zurückgehaltenen Testset aus.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# Generate synthetic data: house size (sq ft) → price
np.random.seed(42)
n = 200
sqft = np.random.randint(500, 3500, n).astype(float)
price = 150 * sqft + np.random.randn(n) * 20000

X = sqft.reshape(-1, 1)
y = price

# Split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
print("Training samples:", len(X_train))   # 160
print("Testing samples:", len(X_test))     # 40

# Train
model = LinearRegression()
model.fit(X_train, y_train)

# Evaluate on the test set
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
r2  = r2_score(y_test, y_pred)

print(f"Mean Squared Error: {mse:,.0f}")
print(f"R² Score:           {r2:.4f}")
print(f"Coefficient:        {model.coef_[0]:.2f}")
print(f"Intercept:          {model.intercept_:.2f}")

Ausgabe:

Training samples: 160
Testing samples: 40
Mean Squared Error: 489,271,263
R² Score:           0.9651
Coefficient:        147.55
Intercept:          5237.02

Interpretation der Metriken:

  • MSE (Mean Squared Error) ist die durchschnittliche quadratische Abweichung zwischen Vorhersagen und tatsächlichen Werten. Kleiner ist besser, aber die Skala hängt von der Zielvariable ab (hier: Dollar).
  • liegt zwischen 0 und 1. Ein Wert von 0,965 bedeutet, dass das Modell etwa 96,5 % der Varianz in den Hauspreisen erklärt – eine starke Anpassung für diesen einfachen Datensatz.

Mehr zur linearen Regression findest du unter Lineare Regression in Python.

Klassifikationsbeispiel: Iris-Blumenarten

Bei einer Klassifikationsaufgabe sind Genauigkeit und ein Klassifikationsbericht aussagekräftiger als MSE.

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

X, y = load_iris(return_X_y=True)

# stratify=y ensures each class is proportionally represented in both splits
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

print("Train class distribution:", np.bincount(y_train))  # [40 40 40]
print("Test class distribution: ", np.bincount(y_test))   # [10 10 10]

model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)

y_pred = model.predict(X_test)
print("\nAccuracy:", round(accuracy_score(y_test, y_pred), 4))
print()
print(classification_report(y_test, y_pred,
      target_names=["setosa", "versicolor", "virginica"]))

Ausgabe:

Train class distribution: [40 40 40]
Test class distribution:  [10 10 10]

Accuracy: 0.9667

              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        10
  versicolor       1.00      0.90      0.95        10
   virginica       0.91      1.00      0.95        10

    accuracy                           0.97        30
   macro avg       0.97      0.97      0.97        30
weighted avg       0.97      0.97      0.97        30

Warum stratify=y wichtig ist: Ohne diese Option könnte eine zufällige Aufteilung eines unausgewogenen Datensatzes dazu führen, dass die meisten Samples einer seltenen Klasse ins Training wandern und keine mehr im Testset verbleiben. stratify=y stellt sicher, dass jede Klasse in beiden Splits in denselben Anteilen vertreten ist.

Mehr zur Klassifikation findest du unter Logistische Regression in Python und Konfusionsmatrix in Python.

Häufige Fallstricke

Vorverarbeitung vor oder nach der Aufteilung?

Teile die Daten stets vor dem Anpassen von Vorverarbeitungsschritten auf (Skalierung, Kodierung, Imputation). Wenn du alle Daten skalierst und erst dann aufteilst, hat das Testset den Skalierer beeinflusst – eine Form von Data Leakage.

Die richtige Reihenfolge:

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # fit ONLY on training data
X_test_scaled  = scaler.transform(X_test)       # transform test with same parameters

Siehe Feature Scaling in Python für eine ausführliche Anleitung.

Mischen und Zeitreihendaten

train_test_split mischt die Daten standardmäßig. Bei Zeitreihendaten ist das falsch – du würdest auf zukünftigen Daten trainieren, um die Vergangenheit vorherzusagen. Setze shuffle=False und stelle sicher, dass die Daten vor der Aufteilung chronologisch sortiert sind:

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, shuffle=False
)

Wenn eine einzelne Aufteilung nicht ausreicht

Eine einzelne 80/20-Aufteilung liefert nur eine Schätzung der Modellleistung, die davon abhängt, welche Samples zufällig ins Testset gelangt sind. Kreuzvalidierung wiederholt die Aufteilung mehrfach und mittelt die Ergebnisse, was eine wesentlich stabilere Schätzung ergibt – besonders bei kleinen Datensätzen.

Auswahl einer Auswertungsmetrik

Die richtige Metrik hängt von der Art des Problems ab:

ProblemGängige Metriken
RegressionMSE, RMSE, MAE, R²
Binäre KlassifikationAccuracy, Precision, Recall, F1, AUC-ROC
Mehrklassen-KlassifikationAccuracy, Makro-/gewichtetes F1

Für die binäre Klassifikation siehe AUC-ROC-Kurve in Python und Konfusionsmatrix in Python. Für die Hyperparameter-Optimierung nach einer funktionierenden Aufteilung siehe Grid Search in Python.

Zusammenfassung

  • Teile die Daten vor jeder Vorverarbeitung auf, um Data Leakage zu vermeiden.
  • Verwende test_size=0.2 als vernünftigen Standardwert; passe ihn je nach Datensatzgröße an.
  • Setze random_state auf eine beliebige ganze Zahl, um reproduzierbare Aufteilungen zu erhalten.
  • Verwende stratify=y bei Klassifikationsaufgaben, besonders bei unausgewogenen Daten.
  • Setze shuffle=False für Zeitreihendaten.
  • Eine einzelne Train/Test-Aufteilung ist ein schneller Ausgangspunkt; nutze Kreuzvalidierung für zuverlässigere Leistungsschätzungen.
Was this page helpful?