Die train_test_split Funktion: Modellvalidierung im Machine Learning

Im Herzen des modernen Machine Learnings liegt die Fähigkeit, Vorhersagemodelle zu entwickeln, die nicht nur auf Trainingsdaten gut funktionieren, sondern auch auf unbekannten, neuen Daten präzise sind. Dies ist eine zentrale Herausforderung in der Data Science und Ingenieurwissenschaften. Um die tatsächliche Leistungsfähigkeit eines Modells zu beurteilen und dessen Generalisierungsfähigkeit zu gewährleisten, ist eine sorgfältige Bewertung unerlässlich. Hier kommt die train_test_split Funktion ins Spiel, ein unverzichtbares Werkzeug aus der Scikit-Learn Bibliothek in Python, die es ermöglicht, einen Datensatz in Trainings- und Testsegmente zu unterteilen.

Dieser Blogbeitrag richtet sich an Entwickler, Studenten und Technologiebegeisterte, die tiefer in die Mechanismen der train_test_split Funktion eintauchen möchten. Wir werden die grundlegende Definition und den Zweck dieser Funktion erläutern, ihre vielfältigen Parameter im Detail beleuchten und aufzeigen, wie diese die reproduzierbare Datenaufteilung beeinflussen. Weiterhin demonstrieren wir die praktische Anwendung an einem realen Machine-Learning-Problem und diskutieren wichtige Überlegungen zur Modellbewertung, einschließlich der Erkennung von Overfitting und Underfitting. Abschließend betrachten wir Limitationen und fortgeschrittene Alternativen, um Ihnen ein umfassendes Verständnis für robuste Modellvalidierung zu vermitteln.

Grundlegendes zum Datensplitting mit train_test_split

Die Grundlage jedes erfolgreichen Machine-Learning-Projekts ist eine valide Bewertung der Modellleistung. Ein Modell, das auf den gleichen Daten bewertet wird, mit denen es trainiert wurde, kann ein trügerisch gutes Ergebnis liefern. Dies liegt daran, dass es die Trainingsdaten auswendig gelernt haben könnte, anstatt die zugrundeliegenden Muster zu erkennen. Genau hier setzt das Konzept der Datensatzaufteilung an. Indem der Datensatz in separate Trainings- und Testsegmente aufgeteilt wird, können wir das Modell auf den Trainingsdaten „fitten“ (trainieren) und seine Leistung anschließend auf völlig neuen, ungesehenen Testdaten bewerten. Dieses Vorgehen ist entscheidend, um die Fähigkeit des Modells zur Generalisierung auf reale, unbekannte Daten realistisch einzuschätzen.

Die Funktion train_test_split aus dem sklearn.model_selection Modul ist das Standardwerkzeug in Python für diese Aufgabe. Sie bietet eine einfache und effiziente Möglichkeit, Ihre Daten für das Training und die Validierung von Machine-Learning-Modellen vorzubereiten. Die korrekte Anwendung dieser Funktion ist ein grundlegender Schritt im Data Science Projektworkflow, um solide und vertrauenswürdige Vorhersagemodelle zu entwickeln.

Die Rolle von train_test_split im überwachten und unüberwachten Lernen

Die Art und Weise, wie train_test_split angewendet wird, hängt maßgeblich von der Art des Machine-Learning-Problems ab – genauer gesagt, ob es sich um überwachtes oder unüberwachtes Lernen handelt.

  • Überwachtes Lernen: Hierbei haben wir sowohl Eingabemerkmale (Features, typischerweise als X bezeichnet) als auch eine Zielvariable (Target, Labels, als y bezeichnet). Das Modell lernt, eine Funktion zu approximieren, die X auf y abbildet. Bei der Anwendung von train_test_split werden sowohl X als auch y aufgeteilt, sodass wir X_train, X_test, y_train, y_test erhalten. Es ist dabei entscheidend, dass X ein zweidimensionales Array (oder DataFrame) ist, wobei jede Zeile ein Datenpunkt und jede Spalte ein Merkmal darstellt. y hingegen ist ein eindimensionales Array, das die zugehörigen Labels für jeden Datenpunkt in X enthält. Ihre Zeilenanzahl muss übereinstimmen.
  • Unüberwachtes Lernen: In diesem Szenario gibt es keine explizite Zielvariable y. Das Modell versucht, Muster oder Strukturen direkt in den Eingabedaten X zu finden (z.B. Clustering, Dimensionsreduktion). Für unüberwachtes Lernen wird train_test_split daher nur auf das Eingabearray X angewendet, was zu X_train, X_test führt. Auch hier muss X ein zweidimensionales Array sein.

Ein häufiges Problem ist die Dimension der Eingabearrays. Für X wird oft ein 2D-Array erwartet. Wenn Sie beispielsweise nur ein einziges Merkmal haben, könnte es als 1D-Array vorliegen. In solchen Fällen müssen Sie es explizit in ein 2D-Array umformen.

import numpy as np
from sklearn.model_selection import train_test_split

# Beispiel für ein 1D-Array X (Einzelmerkmal)
X_1d = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
y = np.array([0, 0, 0, 1, 1, 1, 0, 0, 1, 1])

# FALSCH: X_1d ist 1D, train_test_split erwartet 2D für X
# X_train, X_test, y_train, y_test = train_test_split(X_1d, y, test_size=0.3, random_state=42) 

# RICHTIG: X_1d in 2D umformen (z.B. mit .reshape(-1, 1))
X_2d = X_1d.reshape(-1, 1) # -1 bedeutet "berechne die Größe automatisch", 1 bedeutet "eine Spalte"

print(f"Original X_1d Form: {X_1d.shape}")
print(f"Umgeformt X_2d Form: {X_2d.shape}")

# Jetzt kann train_test_split korrekt angewendet werden
X_train, X_test, y_train, y_test = train_test_split(X_2d, y, test_size=0.3, random_state=42)

print(f"X_train Form: {X_train.shape}")
print(f"X_test Form: {X_test.shape}")
print(f"y_train Form: {y_train.shape}")
print(f"y_test Form: {y_test.shape}")

Detaillierte Betrachtung der train_test_split Parameter

Die Funktion train_test_split bietet mehrere Parameter, die eine feingranulare Steuerung der Datenaufteilung ermöglichen. Das Verständnis dieser Parameter ist entscheidend für eine effektive Datenaufteilung für Machine-Learning-Modelle.

  • Arrays (arrays): Dies sind die Positionsargumente der Funktion, welche die zu teilenden Datensätze darstellen. In der Regel sind dies Ihr Feature-Set X und Ihre Zielvariable y. Sie können aber auch weitere Arrays übergeben, die synchron geteilt werden sollen, wie z.B. Sample-Weights.
  • test_size und train_size: Diese Parameter definieren den Anteil oder die absolute Anzahl der Samples, die dem Test- bzw. Trainingsset zugewiesen werden sollen.
    • Wenn ein Wert zwischen 0,0 und 1,0 (z.B. 0.2 für 20%) angegeben wird, repräsentiert dies den Anteil des Datensatzes.
    • Wenn eine ganze Zahl (z.B. 100 für 100 Samples) angegeben wird, repräsentiert dies die absolute Anzahl der Samples.
    • Es ist ausreichend, nur einen dieser Parameter zu setzen; der andere wird dann automatisch komplementär berechnet. Es ist üblich, test_size zu setzen, z.B. auf 0.2 oder 0.3.
  • random_state: Dieser Parameter ist für die Reproduzierbarkeit Ihrer Ergebnisse von größter Bedeutung. Er dient als Seed für den internen Pseudo-Zufallsgenerator. Wenn Sie einen festen Integer-Wert (z.B. 42) wählen, wird die Datenaufteilung bei jedem Aufruf der Funktion exakt gleich sein. Dies ist unerlässlich für das Debugging, die Ergebnisvalidierung und die Zusammenarbeit in Teams, da es sicherstellt, dass alle dasselbe Setup verwenden.
import numpy as np
from sklearn.model_selection import train_test_split

X = np.arange(20).reshape(-1, 1) # Einfaches 2D-Array von 0 bis 19
y = np.repeat([0, 1], 10)       # 10 Nullen, 10 Einsen

# Erster Split mit random_state=42
X_train_1, X_test_1, y_train_1, y_test_1 = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"Split 1 (random_state=42) X_test: {X_test_1.flatten()}")

# Zweiter Split mit random_state=42 (sollte identisch sein)
X_train_2, X_test_2, y_train_2, y_test_2 = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"Split 2 (random_state=42) X_test: {X_test_2.flatten()}")

# Dritter Split mit einem anderen random_state (wird anders sein)
X_train_3, X_test_3, y_train_3, y_test_3 = train_test_split(X, y, test_size=0.2, random_state=123)
print(f"Split 3 (random_state=123) X_test: {X_test_3.flatten()}")
  • shuffle: Dieser Boolean-Parameter (Standard ist True) bestimmt, ob die Daten vor der Aufteilung gemischt werden sollen. In den meisten Fällen ist ein Mischen der Daten wünschenswert, um sicherzustellen, dass Trainings- und Testset eine repräsentative Stichprobe des gesamten Datensatzes darstellen und keine unerwünschten Ordnungsabhängigkeiten im Datensatz die Modellleistung verfälschen. Bei Zeitreihendaten sollte shuffle jedoch auf False gesetzt werden, um die chronologische Reihenfolge der Daten zu erhalten.
  • stratify: Dieser fortgeschrittene Parameter ist besonders nützlich bei unbalancierten Datensätzen (d.h. wenn bestimmte Klassen in Ihrer Zielvariablen y deutlich seltener vorkommen als andere). Wenn Sie stratify=y setzen, sorgt die Funktion dafür, dass der proportionale Anteil der Klassen in y_train und y_test dem Anteil der Klassen im ursprünglichen y-Array entspricht. Dies verhindert, dass im Testset Klassen unterrepräsentiert oder ganz fehlen, was die Bewertung der Modellleistung erheblich verfälschen könnte.

Betrachten Sie beispielsweise ein Problem mit 90% Klasse A und 10% Klasse B. Ohne stratify könnte es passieren, dass Ihr Testset zufällig nur 5% Klasse B enthält, oder im Extremfall gar keine Klasse B, was eine valide Bewertung für diese Klasse unmöglich macht. Mit stratify=y wird sichergestellt, dass auch das Testset in etwa 90% Klasse A und 10% Klasse B aufweist.

Die präzise Kontrolle über die Datenaufteilung durch Parameter wie random_state und stratify ist das Fundament für die Entwicklung robuster und verallgemeinerungsfähiger Machine-Learning-Modelle.

Rückgabewerte und ihre Bedeutung

Je nachdem, ob Sie ein überwachtes oder unüberwachtes Lernproblem bearbeiten und welche Arrays Sie als Input übergeben, gibt train_test_split unterschiedliche Ausgaben zurück:

  • Für überwachtes Lernen (Input: X, y): Die Funktion gibt vier Arrays zurück:
    • X_train: Die Merkmale für das Training des Modells.
    • X_test: Die Merkmale für die Bewertung des Modells.
    • y_train: Die Zielvariablen, die zu X_train gehören.
    • y_test: Die Zielvariablen, die zu X_test gehören.
Diese vier Arrays sind entscheidend, um das Modell zu trainieren und anschließend seine Vorhersageleistung auf ungesehenen Daten zu überprüfen.
  • Für unüberwachtes Lernen (Input: X): Die Funktion gibt zwei Arrays zurück:
    • X_train: Die Merkmale für das Training des unüberwachten Modells (z.B. Clustering).
    • X_test: Die Merkmale für die Bewertung oder weitere Analyse des unüberwachten Modells.
Hier gibt es keine Zielvariable, da das Modell Muster in den Daten selbst finden soll.

Die klare Trennung in Trainings- und Testdaten ist die Grundlage für die objektive Bewertung der Modellleistung und das Erkennen potenzieller Probleme wie Überanpassung oder Unteranpassung.

from sklearn.model_selection import train_test_split
import numpy as np

# Beispiel-Daten
X = np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], [15, 16]])
y = np.array([0, 1, 0, 1, 0, 1, 0, 1])

# Aufteilung des Datensatzes
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)

print("X_train:n", X_train)
print("X_test:n", X_test)
print("y_train:n", y_train)
print("y_test:n", y_test)

# Überprüfung der Verhältnisse mit stratify
print(f"Original y Klassenverteilung: {np.bincount(y) / len(y)}")
print(f"y_train Klassenverteilung: {np.bincount(y_train) / len(y_train)}")
print(f"y_test Klassenverteilung: {np.bincount(y_test) / len(y_test)}")

Modellleistung bewerten mit train_test_split-Ergebnissen

Nachdem ein Machine-Learning-Modell auf dem Trainingsset trainiert wurde, besteht der nächste kritische Schritt darin, seine Leistung zu bewerten. Das Testset, das durch train_test_split bereitgestellt wird, dient hierfür als unabhängige Datengrundlage. Die Auswahl der richtigen Modellleistung Metriken ist entscheidend, da sie Aufschluss über verschiedene Aspekte der Modellgüte gibt und je nach Art des Problems variiert.

  • Für Regressionsprobleme: Wenn Sie kontinuierliche Werte vorhersagen, sind Metriken wie das Bestimmtheitsmaß (R-squared), der mittlere absolute Fehler (MAE) und der mittlere quadratische Fehler (RMSE) gängige Wahl.
    • R-squared (r2_score): Misst, wie gut die Varianz der abhängigen Variablen durch das Modell erklärt wird. Ein Wert nahe 1 ist ideal.
    • MAE (mean_absolute_error): Der Durchschnitt der absoluten Fehler zwischen vorhergesagten und tatsächlichen Werten. Weniger empfindlich gegenüber Ausreißern als RMSE.
    • RMSE (mean_squared_error, dann die Wurzel): Der Durchschnitt der quadratischen Fehlerwurzel. Bestraft größere Fehler stärker.
  • Für Klassifikationsprobleme: Wenn Sie diskrete Klassen vorhersagen, werden andere Metriken benötigt, die die Korrektheit der Klassifizierung bewerten.
    • Genauigkeit (Accuracy): Der Anteil der korrekt klassifizierten Samples an allen Samples. Eine einfache, aber manchmal irreführende Metrik bei unbalancierten Datensätzen.
    • Präzision (Precision): Der Anteil der richtig positiven Vorhersagen an allen positiven Vorhersagen. Wichtig, wenn falsch positive Ergebnisse teuer sind (z.B. Spam-Erkennung).
    • Recall (Empfindlichkeit): Der Anteil der richtig positiven Vorhersagen an allen tatsächlich positiven Fällen. Wichtig, wenn falsch negative Ergebnisse teuer sind (z.B. Krankheitserkennung).
    • F1-Score: Das harmonische Mittel aus Präzision und Recall. Eine gute Metrik, wenn ein Gleichgewicht zwischen Präzision und Recall wichtig ist, besonders bei unbalancierten Klassen.
    • ROC AUC: Area Under the Receiver Operating Characteristic Curve. Misst die Fähigkeit des Klassifikators, zwischen Klassen zu unterscheiden. Nützlich für unbalancierte Datensätze und wenn die Schwellenwertwahl flexibel sein soll.

Die Ergebnisse des Testsets liefern somit nicht nur eine Momentaufnahme der Modellleistung, sondern auch wertvolle Hinweise darauf, ob und wie das Modell verbessert werden sollte, bevor es in einem Produktionsumfeld eingesetzt wird.

MetrikBeschreibungAnwendungsbereichWünschenswert
Genauigkeit (Accuracy)Anteil korrekter Vorhersagen an allen VorhersagenAllgemeine Klassifikation, wenn Klassen balanciert sindNäher an 1
Präzision (Precision)Anteil echter Positiver an allen als Positiv klassifiziertenWichtig bei Vermeidung falsch Positiver (z.B. Spam)Näher an 1
Recall (Empfindlichkeit)Anteil echter Positiver an allen echten PositivenWichtig bei Vermeidung falsch Negativer (z.B. Krankheiten)Näher an 1
F1-ScoreHarmonisches Mittel aus Präzision und RecallGleichgewicht zwischen Präzision und Recall, unbalancierte KlassenNäher an 1
R-squaredErklärte Varianz durch das ModellRegressionNäher an 1
MAEDurchschnittlicher absoluter FehlerRegression, robust gegenüber AusreißernNäher an 0
RMSEWurzel des mittleren quadratischen FehlersRegression, bestraft große Fehler stärkerNäher an 0

Overfitting und Underfitting erkennen und adressieren

Die durch train_test_split erzeugten Trainings- und Testsets sind unverzichtbare Diagnosewerkzeuge zur Erkennung von Overfitting und Underfitting – zwei der häufigsten Probleme in der Modellentwicklung.

  • Overfitting (Überanpassung): Tritt auf, wenn das Modell die Trainingsdaten zu perfekt lernt, einschließlich des Rauschens und der spezifischen Muster, die nicht auf neue Daten übertragbar sind. Ein überangepasstes Modell zeigt eine exzellente Leistung auf dem Trainingsset, aber eine deutlich schlechtere Leistung auf dem Testset. Es hat die Trainingsdaten „auswendig gelernt“, statt die allgemeinen Prinzipien zu erfassen. Ursachen können ein zu komplexes Modell (zu viele Parameter, tiefe neuronale Netze), zu wenig Trainingsdaten oder ein hohes Verhältnis von Features zu Samples sein.
  • Underfitting (Unteranpassung): Passiert, wenn das Modell zu einfach ist, um die zugrundeliegenden Muster in den Daten zu erfassen. Es lernt sowohl auf dem Trainingsset als auch auf dem Testset schlecht. Das Modell ist nicht in der Lage, die Beziehungen zwischen den Eingaben und der Zielvariablen ausreichend zu modellieren. Ursachen können ein zu einfaches Modell (z.B. lineare Regression für nicht-lineare Daten), unzureichende Merkmale oder starke Regularisierung sein.

Die Diagnose erfolgt durch den Vergleich der Leistungsmetriken auf dem Trainings- und Testset:

  • Wenn die Leistung im Trainingsset gut ist, aber im Testset schlecht, deutet dies auf Overfitting hin.
  • Wenn die Leistung sowohl im Trainingsset als auch im Testset schlecht ist, deutet dies auf Underfitting hin.

Die Behebung dieser Probleme erfordert unterschiedliche Ansätze: Für Overfitting könnte man die Modellkomplexität reduzieren (z.B. weniger Features, Regularisierung, frühes Stoppen), mehr Trainingsdaten sammeln oder Feature Engineering betreiben. Bei Underfitting sollte man ein komplexeres Modell wählen, zusätzliche relevante Features hinzufügen oder die Regularisierung reduzieren.

Praktische Anwendung: Ein Machine-Learning-Problem lösen

Um die Funktionsweise von train_test_split zu veranschaulichen, setzen wir sie in einem vollständigen Machine-Learning-Workflow ein. Wir lösen ein überwachtes Lernproblem der binären Klassifizierung unter Verwendung des bekannten Brustkrebsdatensatzes.

Schritt 1: Problemverständnis

Unser Ziel ist es, ein Modell zu entwickeln, das basierend auf verschiedenen Körpermerkmalen vorhersagen kann, ob eine Person an Brustkrebs erkrankt ist (maligne) oder nicht (benigne). Dies ist ein klassisches Problem der binären Klassifizierung, da die Zielvariable nur zwei mögliche Ausprägungen hat. Die Bedeutung einer hohen Präzision und eines hohen Recalls in medizinischen Anwendungen ist hierbei besonders hervorzuheben.

Schritt 2: Daten laden und erkunden

Wir verwenden den load_breast_cancer Datensatz, der praktischerweise bereits in der Sklearn-Bibliothek enthalten ist. Dies erspart uns den Schritt des manuellen Datenimports und der Vorverarbeitung für dieses Beispiel.

import numpy as np
import pandas as pd # Optional, aber nützlich für Dateninspektion
from sklearn.datasets import load_breast_cancer

# Laden des Brustkrebs-Datensatzes
breast_cancer_data = load_breast_cancer()

# Ausgabe der Merkmalsnamen und Zielvariablen-Namen
print("Merkmalsnamen (Features):")
print(breast_cancer_data.feature_names)
print("nZielvariablen-Namen (Targets):")
print(breast_cancer_data.target_names)

# Optional: Erste Zeilen der Features als DataFrame anzeigen
df_features = pd.DataFrame(breast_cancer_data.data, columns=breast_cancer_data.feature_names)
print("nErste 5 Zeilen der Features:")
print(df_features.head())

# Optional: Kurze Beschreibung des Datensatzes
print("nBeschreibung des Datensatzes:")
print(breast_cancer_data.DESCR)

Wie die Ausgabe zeigt, gibt es zwei Zielklassen: „malignant“ (bösartig) und „benign“ (gutartig). Dies bestätigt, dass es sich um eine binäre Klassifizierungsaufgabe handelt. Der Datensatz enthält 30 numerische Features, die aus digitalisierten Bildern von Brustkrebszellen extrahiert wurden.

Schritt 3: X und y vorbereiten

Für die Anwendung von train_test_split müssen wir unser Feature-Set X und unsere Zielvariable y definieren. Sklearn bietet eine praktische Option, um diese direkt zu erhalten.

from sklearn.datasets import load_breast_cancer

# X als Features und y als Zielvariable extrahieren
X, y = load_breast_cancer(return_X_y=True)

# Überprüfung der Dimensionen
print(f"Dimensionen von X (Features): {X.shape}") # Erwartet: (n_samples, n_features)
print(f"Dimensionen von y (Labels): {y.shape}")   # Erwartet: (n_samples,)

# Anzeigen der ersten Zeilen von X und y zur Überprüfung
print("nErste 5 Zeilen von X:")
print(X[:5])
print("nErste 5 Zeilen von y:")
print(y[:5])

# Überprüfung der Klassenverteilung (wichtig für stratify)
unique, counts = np.unique(y, return_counts=True)
class_distribution = dict(zip(unique, counts))
print(f"nKlassenverteilung im originalen y: {class_distribution}")
print(f"Anteil der Klassen: {counts / len(y)}")

Wir sehen, dass X ein 2D-Array mit 569 Samples und 30 Features ist, während y ein 1D-Array mit 569 Labels ist. Die Dimensionen stimmen überein. Die binäre Kodierung (0 und 1) der Zielvariablen ist bereits von sklearn vorgenommen worden, wobei 0 für „malignant“ und 1 für „benign“ steht.

Schritt 4: Train- und Testsets erstellen

Nun teilen wir die Daten in Trainings- und Testsets auf. Wir verwenden einen Testsatz von 20% (test_size=0.2), setzen einen festen random_state für Reproduzierbarkeit und verwenden stratify=y, um die Klassenverteilung in beiden Sets zu erhalten, was bei medizinischen Datensätzen, wo eine Klasse seltener sein kann, besonders wichtig ist.

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

# Daten laden
X, y = load_breast_cancer(return_X_y=True)

# Daten aufteilen mit test_size, random_state und stratify
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# Überprüfung der Dimensionen der neuen Sets
print(f"X_train Dimensionen: {X_train.shape}")
print(f"X_test Dimensionen: {X_test.shape}")
print(f"y_train Dimensionen: {y_train.shape}")
print(f"y_test Dimensionen: {y_test.shape}")

# Überprüfung der Klassenverteilung in den neuen Sets
unique_train, counts_train = np.unique(y_train, return_counts=True)
unique_test, counts_test = np.unique(y_test, return_counts=True)

print(f"nKlassenverteilung in y_train: {dict(zip(unique_train, counts_train))} (Anteile: {counts_train / len(y_train)})")
print(f"Klassenverteilung in y_test: {dict(zip(unique_test, counts_test))} (Anteile: {counts_test / len(y_test)})")
# Man sieht, dass die Anteile durch stratify=y im Test- und Trainingsset gleich bleiben.

Schritt 5: Klassifikationsmodell trainieren

Für dieses Beispiel verwenden wir einen K-Nearest Neighbors (K-NN) Klassifikator, ein einfaches, aber effektives Modell für Klassifizierungsaufgaben. Wir initialisieren das Modell und trainieren es anschließend mit den Trainingsdaten (X_train, y_train).

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer

# Daten laden und aufteilen (wie in Schritt 4)
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# Initialisieren des K-NN Klassifikators
# Wir wählen hier k=5 als Beispiel
clf = KNeighborsClassifier(n_neighbors=5) 

# Modell auf dem Trainingsset trainieren (fitten)
print("Modell wird trainiert...")
clf.fit(X_train, y_train)
print("Modelltraining abgeschlossen.")

# Vorhersagen auf dem Testset treffen
prediction = clf.predict(X_test)
print("nErste 10 Vorhersagen auf dem Testset:", prediction[:10])
print("Erste 10 wahre Labels des Testsets:", y_test[:10])

Die .fit() Methode ist der Prozess, bei dem das Modell die Muster und Beziehungen aus den Trainingsdaten lernt. Anschließend können wir mit .predict() Vorhersagen auf neuen Daten, in diesem Fall dem Testset, treffen.

Schritt 6: Modell bewerten

Wir bewerten die Leistung unseres K-NN Modells mithilfe der Genauigkeit (Accuracy) für beide Sets. Darüber hinaus ist es in Klassifikationsproblemen, insbesondere bei medizinischen Daten, wichtig, auch Präzision, Recall und den F1-Score zu betrachten.

from sklearn.metrics import accuracy_score, classification_report
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer

# Daten laden und aufteilen (wie in Schritt 4)
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# Modell trainieren (wie in Schritt 5)
clf = KNeighborsClassifier(n_neighbors=5)
clf.fit(X_train, y_train)

# Genauigkeit auf dem Trainingsset berechnen
train_accuracy = clf.score(X_train, y_train)
print(f"Genauigkeit auf dem Trainingsset: {train_accuracy:.4f}")

# Genauigkeit auf dem Testset berechnen
test_accuracy = clf.score(X_test, y_test)
print(f"Genauigkeit auf dem Testset: {test_accuracy:.4f}")

# Detaillierter Klassifikationsbericht für das Testset
y_pred = clf.predict(X_test)
print("nKlassifikationsbericht für das Testset:")
# target_names hinzufügen für bessere Lesbarkeit
print(classification_report(y_test, y_pred, target_names=breast_cancer_data.target_names))

# Interpretation
if train_accuracy > test_accuracy + 0.05: # Ein Schwellenwert für deutliches Overfitting
    print("nAnzeichen von Overfitting: Die Trainingsgenauigkeit ist deutlich höher als die Testgenauigkeit.")
elif train_accuracy < test_accuracy - 0.05: # Ein seltenerer Fall
    print("nAnzeichen von Underfitting oder Testset-Glück: Die Testgenauigkeit ist höher als die Trainingsgenauigkeit.")
elif train_accuracy < 0.7 or test_accuracy < 0.7: # Beispiel für schlechte Leistung
    print("nAnzeichen von Underfitting: Das Modell lernt weder auf dem Trainings- noch auf dem Testset gut.")
else:
    print("nDas Modell zeigt eine gute Generalisierungsfähigkeit und kaum Anzeichen von Overfitting.")

Wir erhalten eine hohe Genauigkeit sowohl auf dem Trainings- als auch auf dem Testset, wobei die Werte nah beieinander liegen. Dies deutet darauf hin, dass das Modell gut gelernt hat und eine gute Generalisierungsfähigkeit besitzt. Es gibt keine deutlichen Anzeichen von Overfitting, da der Leistungsabfall vom Trainings- zum Testset minimal ist. Der Klassifikationsbericht gibt zudem detaillierte Einblicke in Präzision, Recall und F1-Score für jede Klasse, was eine umfassendere Beurteilung als nur die Genauigkeit ermöglicht.

Herausforderungen und fortgeschrittene Überlegungen zur Datenaufteilung

Obwohl train_test_split ein mächtiges und weit verbreitetes Werkzeug ist, birgt es bestimmte Herausforderungen und ist nicht immer die optimale Lösung für alle Szenarien. Ein tieferes Verständnis dieser Aspekte ist entscheidend für erfahrene Entwickler und angehende Data Scientists.

Die Problematik des random_state

Wie bereits erwähnt, sorgt random_state für Reproduzierbarkeit, indem es den Seed des Pseudo-Zufallsgenerators festlegt. Dies ist zwar für das Debugging und die Vergleichbarkeit wichtig, birgt aber auch eine Einschränkung: Die einmalige Auswahl eines random_state-Wertes erzeugt eine spezifische Aufteilung des Datensatzes. Es ist möglich, dass diese eine Aufteilung zufällig besonders günstig oder ungünstig für die Modellleistung ist. Das bedeutet, dass die Performance-Metriken, die Sie aus dieser einen Aufteilung erhalten, nicht unbedingt repräsentativ für die allgemeine Leistung des Modells sind, wenn es auf beliebigen neuen Daten angewendet wird. Verschiedene Seeds können zu unterschiedlichen Trainings- und Testsets und somit zu variierenden Testergebnissen führen, was die Robustheit der Modellbewertung in Frage stellen kann.

Cross-Validation als robustere Alternative

Um die Abhängigkeit von einer einzelnen Datenaufteilung zu überwinden und eine zuverlässigere Schätzung der Modellleistung zu erhalten, kommt die Cross-Validation (Kreuzvalidierung) ins Spiel. Eine gängige Methode ist die K-Fold Cross-Validation. Dabei wird der Datensatz in k gleich große „Folds“ (Teilmengen) unterteilt. Das Modell wird dann k-mal trainiert und bewertet: Jedes Mal wird ein anderer Fold als Testset verwendet, während die verbleibenden k-1 Folds als Trainingsset dienen. Die endgültige Modellleistung wird dann als Durchschnitt der k Bewertungsmetriken berechnet. Dies liefert eine wesentlich stabilere und repräsentativere Schätzung der Generalisierungsfähigkeit des Modells.

from sklearn.model_selection import KFold, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
import numpy as np

X, y = load_breast_cancer(return_X_y=True)

# Initialisieren des K-NN Klassifikators
clf = KNeighborsClassifier(n_neighbors=5)

# K-Fold Cross-Validation mit 5 Folds
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# Berechnung der Genauigkeit (Accuracy) für jeden Fold
cv_scores = cross_val_score(clf, X, y, cv=kf, scoring='accuracy')

print(f"Genauigkeiten für jeden Fold: {cv_scores}")
print(f"Durchschnittliche Genauigkeit (Cross-Validation): {np.mean(cv_scores):.4f}")
print(f"Standardabweichung der Genauigkeiten: {np.std(cv_scores):.4f}")

Die durchschnittliche Genauigkeit über mehrere Folds hinweg gibt ein besseres Bild von der tatsächlichen Robustheit des Modells.

Weitere Splitting-Strategien

Je nach Datentyp und Problemstellung gibt es weitere spezialisierte Splitting-Methoden:

  • StratifiedKFold: Eine Variante der K-Fold Cross-Validation, die die Klassenverteilung in jedem Fold erhält, ähnlich wie der stratify-Parameter in train_test_split. Ideal für unbalancierte Datensätze.
  • GroupKFold: Verhindert, dass Datenpunkte aus derselben Gruppe (z.B. Patienten-IDs in medizinischen Studien) sowohl im Trainings- als auch im Testset erscheinen, um Datenlecks zu vermeiden.
  • TimeSeriesSplit: Speziell für Zeitreihendaten entwickelt, bei denen die chronologische Reihenfolge der Daten erhalten bleiben muss. Das Testset besteht immer aus Daten, die nach den Trainingsdaten liegen.

Die Wahl der richtigen Datenaufteilungsstrategie ist ein entscheidender Faktor für die Güte und Zuverlässigkeit Ihrer Machine-Learning-Modelle.

Fazit: train_test_split als Fundament robuster ML-Modelle

Die train_test_split Funktion ist ein unverzichtbares Fundament in jedem Machine-Learning-Projekt. Sie ermöglicht die grundlegende Trennung von Daten in Trainings- und Testsets, was eine realistische Bewertung der Modellleistung und die frühzeitige Diagnose von Overfitting und Underfitting entscheidend unterstützt. Durch die präzise Steuerung mittels Parametern wie test_size, random_state und stratify können Entwickler eine kontrollierte und reproduzierbare Datenaufteilung gewährleisten. Obwohl die Funktion für viele Anwendungen ausreichend ist, zeigen fortgeschrittene Methoden wie die Cross-Validation auf, wie man eine noch robustere und zuverlässigere Modellvalidierung erreichen kann.

Wir hoffen, dieser detaillierte Einblick in die train_test_split Funktion hat Ihr Verständnis vertieft und Sie dazu angeregt, die besten Praktiken der Datenaufteilung in Ihren eigenen Machine Learning Entwicklungsprojekten anzuwenden. Experimentieren Sie mit den Parametern und erkunden Sie die verschiedenen Validierungsstrategien, um die bestmöglichen Modelle zu bauen. Haben Sie Fragen oder möchten Sie Ihre eigenen Erfahrungen teilen? Hinterlassen Sie gerne einen Kommentar oder stöbern Sie in unseren weiteren Fachartikeln, um Ihr Wissen im Bereich Data Science und KI zu erweitern.

Was ist der Hauptzweck von train_test_split?

Der Hauptzweck der train_test_split Funktion ist es, einen Datensatz in unabhängige Trainings- und Testsegmente aufzuteilen. Das Trainingsset wird verwendet, um ein Machine-Learning-Modell zu trainieren, während das Testset dazu dient, die Leistung dieses Modells auf ungesehenen Daten zu bewerten. Dies hilft, die Generalisierungsfähigkeit des Modells realistisch einzuschätzen und Probleme wie Overfitting zu erkennen.

Warum ist random_state wichtig?

random_state ist entscheidend für die Reproduzierbarkeit Ihrer Datenaufteilung. Wenn ein fester Integer-Wert für random_state gewählt wird, erzeugt die Funktion bei jedem Aufruf exakt dieselbe zufällige Aufteilung. Dies ist unerlässlich für Debugging, das Vergleichen von Modellen und die Zusammenarbeit in Teams, da es sicherstellt, dass alle dasselbe Trainings- und Testset verwenden und somit vergleichbare Ergebnisse erzielen können.

Wann sollte man stratify verwenden?

Der stratify-Parameter sollte verwendet werden, wenn Ihr Datensatz unbalancierte Klassen in der Zielvariablen (y) aufweist. Indem Sie stratify=y setzen, sorgt die Funktion dafür, dass die prozentuale Verteilung der Klassen im Trainings- und Testset dieselbe ist wie im ursprünglichen Datensatz. Dies ist entscheidend, um sicherzustellen, dass das Testset eine repräsentative Stichprobe aller Klassen enthält, was eine aussagekräftige Bewertung des Modells ermöglicht.

Wie unterscheidet sich train_test_split von Cross-Validation?

train_test_split teilt den Datensatz einmalig in ein Trainings- und ein Testset auf und liefert eine einzelne Leistungsbewertung. Cross-Validation (z.B. K-Fold) hingegen führt mehrere Aufteilungen durch und trainiert/testet das Modell mehrfach. Sie mittelt die Ergebnisse über diese verschiedenen Aufteilungen, um eine robustere und statistisch zuverlässigere Schätzung der Modellleistung zu erhalten, die weniger anfällig für die Besonderheiten einer einzelnen Aufteilung ist.