CSBootcampHSG

Lektion 11

Machine Learning II — saubere Methodik

Train/Validation/Test-Split mit Stratifizierung und festem Seed, leak-freies Skalieren mit `RobustScaler`, mehrere Klassifikatoren auf Trainings- und Validierungsdaten vergleichen und das beste Modell auf dem Testset berichten. Der Schritt von *funktioniert* zu *funktioniert sauber*.

Dauer:
75 Min. geschätzt
Voraussetzungen:
Klassifikation und KNN (Lektion 10)

Theorie & Konzepte

Phase 1 / 4

Lektion 10 hat dir den Workflow beigebracht: Split, fit/predict, Metriken. Diese Lektion macht aus dem Workflow eine Methodik, die du auch außerhalb des Bootcamps sauber anwenden kannst.

Vier neue Disziplinen kommen dazu:

  1. Drei Sets statt zwei — Train und Validation und Test. Du wählst Hyperparameter auf Validation, berichtest auf Test.
  2. Skalieren — Features auf vergleichbare Größenordnungen bringen. Pflicht bei Distanz-basierten Modellen, schadet aber nie.
  3. Mehrere Klassifikatoren vergleichen — nicht den erstbesten nehmen, sondern systematisch.
  4. Reproduzierbarkeitrandom_state überall, sonst sind Vergleiche wertlos.

Die HSG-Klausur prüft genau diese Disziplinen — nicht nur ob du sklearn-Funktionsnamen kennst, sondern ob du verstehst, warum sie in dieser Reihenfolge mit diesen Parametern aufgerufen werden.

Drei Sets — Train, Validation, Test

In Lektion 10 hattest du zwei Sets:

  • Train zum Lernen
  • Test zum Berichten

Das Problem: sobald du mehrere Modelle oder Hyperparameter ausprobierst und das beste auswählst, hast du das Testset indirekt zum Tunen benutzt. Information leckt vom Test ins Modell — die berichtete Performance ist optimistisch.

Die Lösung: drei Sets.

SetAnteilWofür
Train80 %Modelle trainieren (fit)
Validation10 %Hyperparameter wählen, Modelle vergleichen
Test10 %Einmal am Ende Performance berichten

Goldene Regel: das Testset ist heilig. Du schaust es genau einmal an — wenn du das fertige, beste Modell ein letztes Mal bewerten willst. Keine zweite Runde, kein „lass mich nur noch mal mit anderem k probieren".

Stratifizierter 80/10/10-Split — zwei `train_test_split`-Aufrufe

sklearn.model_selection.train_test_split macht einen Split. Für drei Sets rufst du es zweimal auf:

from sklearn.model_selection import train_test_split

# 1. Schritt: 80% Train, 20% Rest
X_train, X_rest, y_train, y_rest = train_test_split(
    X, y,
    train_size=0.8,
    stratify=y,
    random_state=13,
)

# 2. Schritt: Den Rest 50/50 in Validation und Test
X_val, X_test, y_val, y_test = train_test_split(
    X_rest, y_rest,
    train_size=0.5,
    stratify=y_rest,         # NEUER Stratify-Vektor — vom Rest!
    random_state=13,
)

Drei Details, die du nicht vergessen darfst:

  • random_state an beiden Aufrufen — sonst ist der erste reproduzierbar, der zweite nicht. Im HSG-Assignment ist random_state=13 Pflicht.
  • stratify=y und stratify=y_rest — du stratifizierst beim zweiten Split mit dem Rest-Vektor, nicht mit dem Original y. Vergessen heißt: ein Set hat eventuell nur Klassen A und B, ein anderes nur B und C.
  • Reihenfolge der Rückgabewerte — immer X_train, X_test, y_train, y_test. Wer hier vertauscht, trainiert auf Labels und testet auf Features.

Stratifizierter Split — `random_state` und `stratify` ausprobieren

90 Beispiele · Train (durchgezogen) · Test (getupft)

Trainingsset · 72 Zeilen

  • setosa24(33%)
  • versicolor24(33%)
  • virginica24(33%)

Testset · 18 Zeilen

  • setosa6(33%)
  • versicolor6(33%)
  • virginica6(33%)
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.20,
    random_state=42,
    stratify=y,
)
Stell `test_size` und `random_state` ein, schalte `stratify` an/aus. Beobachte, wie die Klassenverteilung in Train und Test mit/ohne Stratifizierung schwankt — und wie ein anderer Seed denselben Datensatz anders zerlegt.

Skalierung — warum und wie

Ein Feature in [0, 1000] und eines in [0, 1] haben für viele Modelle sehr unterschiedliche Gewichte:

  • Distanz-basierte Modelle (KNN, SVM, k-Means): die große Spalte dominiert die Distanzberechnung komplett. Eine 1.0 Veränderung im kleinen Feature ist „weiter" als 1000 im großen Feature gewichtsmäßig irrelevant.
  • Gradient-basierte Modelle (Logistic Regression, neuronale Netze): die Lernrate ist für die große Spalte falsch dimensioniert, Konvergenz wird langsam oder bricht.
  • Baumbasierte Modelle (DecisionTree, RandomForest, GradientBoosting): immun gegen Skalierung. Sie sind nur auf Reihenfolge angewiesen, nicht auf Beträge.

Konsequenz: du skalierst eigentlich immer. Schadet bei Bäumen nicht, hilft den anderen massiv.

Drei Standard-Scaler aus sklearn.preprocessing:

ScalerFormelWann
StandardScaler(x - mean) / stdDefault. Mittelwert 0, Standardabweichung 1.
MinMaxScaler(x - min) / (max - min)Wertebereich [0, 1]. Wenn die Range bekannt sein soll.
RobustScaler(x - median) / IQRRobust gegen Ausreißer. Default im HSG-Assignment.

Der Unterschied wird wichtig, wenn deine Daten Ausreißer haben. StandardScaler benutzt mean und std, beide sehr empfindlich gegenüber Ausreißern. RobustScaler benutzt median und IQR (Interquartilsabstand) — beide unempfindlich.

Scaler-Explorer — was machen die drei Scaler mit deinen Daten?

Transformierte Werte (11 Punkte)

Formel: (x − median) / IQR

-3.00-1.500.001.503.0023.5Plot-Bereich: [−3, 3.5] fest (Werte ausserhalb mit Pfeil)

Originale Statistik

  • mean = 22.118
  • std = 23.145
  • min = 11.200, max = 95.000
  • median = 15.000, IQR = 3.400

Nach Transformation

  • mean ≈ 2.094
  • min = -1.118, max = 23.529
  • → Median ≈ 0, IQR ≈ 1

💡 Aktiviere „Ausreißer dazu“ und schalte zwischen StandardScaler und RobustScaler. Bei StandardScaler ziehen mean & std den Ausreißer stark nach oben — die normalen Werte werden gequetscht. RobustScaler benutzt median und IQR, beide unempfindlich gegen Ausreißer; die normalen Werte bleiben gut auseinandergezogen.

Schalte zwischen None, StandardScaler, MinMaxScaler und RobustScaler. Du siehst die transformierten Werte und einen kleinen Histogramm-Vergleich. Aktiviere den Ausreißer-Modus, um zu sehen, wann RobustScaler glänzt.

Datenleck — die zwei Klassiker

So sieht's aus, wenn jemand „nur kurz schnell" macht.

Sauber

scaler.fit(X_train)
X_train_s = scaler.transform(X_train)
X_val_s   = scaler.transform(X_val)
X_test_s  = scaler.transform(X_test)

Scaler kennt nur Train. Val und Test sind unberührt.

Datenleck

# ✗ Variante 1: fit auf ganzem Datensatz
scaler.fit(X)            # X enthält auch Val + Test
X_scaled = scaler.transform(X)
X_train_s, X_val_s, X_test_s = split(X_scaled, ...)

# ✗ Variante 2: fit_transform auf jedem einzeln
X_train_s = scaler.fit_transform(X_train)
X_val_s   = scaler.fit_transform(X_val)   # NEUER fit pro Set!
X_test_s  = scaler.fit_transform(X_test)

In beiden Fällen kennt der Scaler Information aus Val/Test. Variante 2 ist schlimmer — drei verschiedene Skalierungen, die Sets sind danach nicht mehr vergleichbar.

Mehrere Klassifikatoren systematisch vergleichen

Im HSG-Assignment trainierst du fünf oder mehr verschiedene Klassifikatoren, jeweils auf scaled und unscaled Daten, und bewertest sie mit vier Metriken — also ≥ 5 × 2 × 4 = 40+ Zahlen. Die saubere Lösung: eine Schleife + ein DataFrame.

from sklearn.neighbors      import KNeighborsClassifier
from sklearn.tree           import DecisionTreeClassifier
from sklearn.ensemble       import RandomForestClassifier
from sklearn.linear_model   import LogisticRegression
from sklearn.svm            import SVC
from sklearn.metrics        import (
    accuracy_score, precision_score, recall_score, f1_score,
)
import pandas as pd

MODELS = {
    'KNN':            KNeighborsClassifier(n_neighbors=5),
    'DecisionTree':   DecisionTreeClassifier(random_state=13),
    'RandomForest':   RandomForestClassifier(random_state=13),
    'LogReg':         LogisticRegression(max_iter=1000, random_state=13),
    'SVM':            SVC(random_state=13),
}

results = []
for name, model in MODELS.items():
    for scaled, X_tr, X_va in [
        ('unscaled', X_train,        X_val),
        ('scaled',   X_train_scaled, X_val_scaled),
    ]:
        model.fit(X_tr, y_train)
        y_pred = model.predict(X_va)
        results.append({
            'model':     name,
            'scaling':   scaled,
            'accuracy':  accuracy_score(y_val, y_pred),
            'precision': precision_score(y_val, y_pred, average='macro', zero_division=0),
            'recall':    recall_score(y_val, y_pred,    average='macro', zero_division=0),
            'f1':        f1_score(y_val, y_pred,        average='macro', zero_division=0),
        })

df = pd.DataFrame(results)

Am Ende hast du einen DataFrame mit ca. 10 Zeilen. Sortiere nach F1 — das Modell mit dem höchsten Wert auf Validation ist dein Gewinner.

Erwartbare Muster:

  • Bäume und Random Forest: wenige Punkte Unterschied zwischen scaled und unscaled (sie sind skalierungsinvariant).
  • KNN, SVM, LogReg: klare Verbesserung durch Skalierung — manchmal 10–20 Prozentpunkte.
  • Random Forest schneidet auf vielen kleinen Datensätzen am besten ab — der Standard-Allrounder.

Das beste Modell auf dem Testset berichten

Sobald du auf der Validation deinen Gewinner gefunden hast, kommt der letzte Schritt: eine Bewertung auf dem Testset.

best_name = df.sort_values('f1', ascending=False).iloc[0]['model']
best_scaling = df.sort_values('f1', ascending=False).iloc[0]['scaling']

best_model = MODELS[best_name]
best_X_test = X_test_scaled if best_scaling == 'scaled' else X_test

# Re-fit auf vollständigen Trainingsdaten (optional, je nach Aufgabe)
best_model.fit(X_train, y_train)
y_pred_test = best_model.predict(best_X_test)

print(classification_report(y_test, y_pred_test))

Was du jetzt nicht mehr tust:

  • Kein neues Modell ausprobieren, das auf Test besser aussieht.
  • Keine neuen Hyperparameter probieren.
  • Kein „aber wenn ich den Seed ändere…".

Die Test-Zahl ist deine eine ehrliche Schätzung. Sie wird wahrscheinlich etwas niedriger sein als die Validation-Zahl — das ist normal. Wenn sie deutlich niedriger ist (>10 Prozentpunkte), hast du auf der Validation overfittet (z. B. zu viele Modelle ausprobiert).

Confusion Matrix lesen — beste Klasse, leere Diagonale

Quiz 11 hat zwei Fragen, die direkt eine Confusion Matrix zeigen. Du musst sie lesen können, ohne sklearn aufzurufen.

Welche Klasse schneidet am besten ab?

Die beste Klasse ist diejenige, deren Diagonal-Zelle relativ zum Zeilen-Total am höchsten ist — also wo der Recall am höchsten ist. Beispiel:

pred Apred Bpred CΣ Zeile
A181120
B314320
C241420
  • Recall(A) = 18 / 20 = 0.90
  • Recall(B) = 14 / 20 = 0.70
  • Recall(C) = 14 / 20 = 0.70

A schneidet am besten ab. Wenn die Zeilen-Totals unterschiedlich sind, immer auf den Anteil schauen, nicht auf die absolute Zahl.

Was bedeutet eine leere Diagonale?

Diagonale = alle korrekten Vorhersagen. Wenn die Diagonale komplett 0 ist:

  • Nicht der Klassifikator lag immer falsch im naiven Sinn — das wäre trivial.
  • Nicht der Klassifikator lag immer richtig — Accuracy wäre dann 0%, nicht 100%.
  • Nicht unmöglich — passiert bei einem schlechten Modell auf neuen Daten.
  • Ja: mindestens eine Vorhersage pro Klasse muss in einer Off-Diagonale liegen, sonst gäb's gar keine Datenpunkte. Heißt: jede Klasse wurde mindestens einmal vorhergesagt, aber nie für die richtige Klasse.

Das wirkt akademisch, ist aber genau die Quiz-Frage: zero-diagonal heißt accuracy=0, und jede Klasse hat mindestens eine (falsche) Vorhersage.

Assignment-Walkthrough

Phase 2 / 4

Hier gehen wir typspezifisch durch die 4 Aufgabenformen von Assignment 11: was gefragt ist, wie man es angeht, und wo man sich typischerweise verrennt. Die konkreten Namen und Daten siehst du im HSG-Notebook — die Muster hier passen auf jede Variante.

Assignment-Aufgabe 1

Stratifizierter 80/10/10-Split mit festem Seed

Was ist gefragt

Aufgabentyp: Du zerlegst den Datensatz in drei disjunkte Teile — 80 % Train, 10 % Validation, 10 % Test. Stratifiziert (gleiche Klassenverteilung in allen drei Sets) und mit festem Seed (random_state=13), damit deine Ergebnisse reproduzierbar sind.

Die Test-Zelle prüft die Größen und (oft) die Klassenverteilung in jedem Set.

Strategie

Trick: train_test_split macht nur einen Split. Für drei Sets rufst du es zweimal auf.

  1. Erster Aufruf: train_size=0.8X_train + X_rest.
  2. Zweiter Aufruf auf den Rest: train_size=0.5X_val + X_test (jeweils 10 % vom Original).
  3. random_state=13 in beiden Aufrufen.
  4. stratify=y im ersten Aufruf, stratify=y_rest im zweiten — du stratifizierst beim zweiten Mal mit dem Rest-Vektor, nicht mit dem Original.

Lösungsskelett

from sklearn.model_selection import train_test_split

X_train, X_rest, y_train, y_rest = train_test_split(
    x, y,
    train_size=0.8,
    stratify=y,
    random_state=13,
)

X_val, X_test, y_val, y_test = train_test_split(
    X_rest, y_rest,
    train_size=0.5,
    stratify=y_rest,       # WICHTIG: stratify mit y_rest, nicht y
    random_state=13,
)

Typische Stolperstellen

  • random_state nur im ersten Aufruf — der zweite Split wird dann nicht reproduzierbar.
  • stratify=y im zweiten Aufruf — würde y als Stratify-Vektor erwarten, aber X_rest/y_rest haben andere Längen. ValueError. Lösung: stratify=y_rest.
  • Reihenfolge der Rückgabewerte vertauschttrain_test_split gibt immer zurück: X_train, X_test, y_train, y_test (in dieser Reihenfolge). Wer Y und X tauscht, trainiert auf den Labels.
  • test_size statt train_size — funktioniert auch, aber dann musst du die Werte umrechnen: train_size=0.8test_size=0.2.

Assignment-Aufgabe 2

RobustScaler — fit auf Train, transform überall

Was ist gefragt

Aufgabentyp: Du skalierst alle drei Sets mit RobustScaler. Der Scaler wird nur auf den Trainingsdaten angepasst und dann auf alle drei Sets angewandt. Die Resultate heißen X_train_scaled, X_val_scaled, X_test_scaled.

Strategie

Vier Zeilen, immer dasselbe Pattern:

from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
scaler.fit(X_train)                    # NUR Train!
X_train_scaled = scaler.transform(X_train)
X_val_scaled   = scaler.transform(X_val)
X_test_scaled  = scaler.transform(X_test)

Die Reihenfolge ist:

  1. Scaler-Objekt erzeugen.
  2. fit einmal, ausschließlich auf X_train.
  3. transform dreimal, einmal pro Set.

Lösungsskelett

from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
scaler.fit(X_train)

X_train_scaled = scaler.transform(X_train)
X_val_scaled   = scaler.transform(X_val)
X_test_scaled  = scaler.transform(X_test)

Typische Stolperstellen

  • scaler.fit(X) auf dem ganzen Datensatz — Datenleck. Der Scaler kennt dann Val/Test-Verteilung.
  • scaler.fit_transform(X_val) — re-fittet den Scaler auf Val, du hast einen anderen Scaler. Niemals.
  • StandardScaler statt RobustScaler — funktioniert, ist aber empfindlicher gegen Ausreißer. Die Aufgabe verlangt explizit RobustScaler.
  • scaler.transform(X_train) vor scaler.fit(X_train)NotFittedError. Reihenfolge: erst fit, dann transform.

Assignment-Aufgabe 3

Mindestens fünf Klassifikatoren auf scaled + unscaled vergleichen

Was ist gefragt

Aufgabentyp: Du trainierst mindestens fünf verschiedene Klassifikatoren, jeweils auf scaled und unscaled Daten, und bewertest sie mit vier Metriken (Accuracy, Precision, Recall, F1) auf Trainings- und Validierungsdaten. Ergebnisse landen in einem DataFrame.

Das Ergebnis ist eine Tabelle mit ca. 10 Zeilen (5 Modelle × 2 Skalierungen) und 8 Zahlen-Spalten (4 Metriken × 2 Sets).

Strategie

Dict + doppelte Schleife:

  1. Modelle als Dict {name: instance} mit random_state=13, wo möglich.
  2. Liste [('unscaled', X_train, X_val), ('scaled', X_train_scaled, X_val_scaled)].
  3. Doppelte Schleife: pro Modell, pro Skalierung → fit + predict.
  4. Pro Eintrag: Accuracy/Precision/Recall/F1 auf Train UND auf Val berechnen. average='macro' für die letzten drei.
  5. Resultate in eine Liste von Dicts sammeln, am Ende pd.DataFrame(results).

Lösungsskelett

import pandas as pd
from sklearn.neighbors      import KNeighborsClassifier
from sklearn.tree           import DecisionTreeClassifier
from sklearn.ensemble       import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model   import LogisticRegression
from sklearn.svm            import SVC
from sklearn.metrics        import accuracy_score, precision_score, recall_score, f1_score

MODELS = {
    'KNN':            KNeighborsClassifier(n_neighbors=5),
    'DecisionTree':   DecisionTreeClassifier(random_state=13),
    'RandomForest':   RandomForestClassifier(random_state=13),
    'LogReg':         LogisticRegression(max_iter=1000, random_state=13),
    'SVM':            SVC(random_state=13),
    # optional: 'GradientBoost': GradientBoostingClassifier(random_state=13),
}

def _row(name, scaling, model, X_tr, y_tr, X_va, y_va):
    model.fit(X_tr, y_tr)
    yp_tr = model.predict(X_tr)
    yp_va = model.predict(X_va)
    return {
        'model':         name,
        'scaling':       scaling,
        'train_acc':     accuracy_score(y_tr, yp_tr),
        'val_acc':       accuracy_score(y_va, yp_va),
        'train_prec':    precision_score(y_tr, yp_tr, average='macro', zero_division=0),
        'val_prec':      precision_score(y_va, yp_va, average='macro', zero_division=0),
        'train_rec':     recall_score(y_tr, yp_tr,    average='macro', zero_division=0),
        'val_rec':       recall_score(y_va, yp_va,    average='macro', zero_division=0),
        'train_f1':      f1_score(y_tr, yp_tr,        average='macro', zero_division=0),
        'val_f1':        f1_score(y_va, yp_va,        average='macro', zero_division=0),
    }

results = []
for name, model in MODELS.items():
    results.append(_row(name, 'unscaled', model, X_train,        y_train, X_val,        y_val))
    results.append(_row(name, 'scaled',   model, X_train_scaled, y_train, X_val_scaled, y_val))

df = pd.DataFrame(results)

Typische Stolperstellen

  • Modell wird zweimal trainiert (scaled + unscaled) im selben Objekt — funktioniert, weil fit das Objekt überschreibt. Wenn du sichergehen willst: clone(model) aus sklearn.base.
  • average='macro' vergessen — gibt dir Arrays statt Skalaren. Pandas-Cell wird ein Liste statt einer Zahl.
  • max_iter-Default bei LogisticRegression — 100 ist oft zu wenig, das gibt ConvergenceWarning. Setze max_iter=1000.
  • random_state bei Bäumen vergessen — Resultate schwanken zwischen Läufen, die Vergleichstabelle ist nicht reproduzierbar.
  • Nur auf Val bewerten, Train ignorieren — die Aufgabe verlangt explizit beides. Train-Performance hilft, Overfitting zu erkennen.

Assignment-Aufgabe 4

Bestes Modell auswählen und auf dem Testset berichten

Was ist gefragt

Aufgabentyp: Schau dir den Vergleichs-DataFrame an, wähle das beste Modell (typisch nach val_f1 sortieren), trainiere es einmal sauber und berichte seine Performance auf dem Testset. Das ist der einzige Moment, in dem du das Testset überhaupt anschaust.

Strategie

Drei Schritte:

  1. Im DataFrame nach val_f1 (oder val_acc) sortieren, den ersten Eintrag nehmen.
  2. Das passende Modell instanziieren — frisch, nicht das aus der Vergleichsschleife (das wurde zuletzt mit scaled oder unscaled gefittet). Mit random_state=13 wieder reproduzierbar.
  3. Auf den vollständigen Trainingsdaten (X_train oder X_train_scaled, je nach Gewinner) fitten und auf X_test (resp. X_test_scaled) predicten. Dann classification_report drucken.

Lösungsskelett

from sklearn.metrics import classification_report

best = df.sort_values('val_f1', ascending=False).iloc[0]
print(f"Bestes Modell: {best['model']} mit {best['scaling']} Daten (val_f1 = {best['val_f1']:.3f})")

# Frische Instanz, gleiche Hyperparameter wie im Vergleich
best_model = MODELS[best['model']]

if best['scaling'] == 'scaled':
    X_tr_final, X_te_final = X_train_scaled, X_test_scaled
else:
    X_tr_final, X_te_final = X_train, X_test

best_model.fit(X_tr_final, y_train)
y_pred_test = best_model.predict(X_te_final)

print(classification_report(y_test, y_pred_test))

Typische Stolperstellen

  • Auf dem Test mehrere Modelle ausprobieren — Datenleck. Das Test ist einmal. Wenn du erst SVM, dann RandomForest auf Test testest und das bessere nimmst, hast du Test indirekt zum Tunen benutzt.
  • Schon-trainiertes Modell wiederverwenden — das Modell wurde in der Schleife zuletzt auf einem bestimmten Set gefittet. Wenn du sichergehen willst: clone(MODELS[best_model_name]) aus sklearn.base.
  • Falsche X_test-Variante — wenn der Gewinner auf scaled trainiert war, musst du auch das gescalte X_test_scaled benutzen. Sonst sieht das Modell andere Größenordnungen.
  • Test-Performance deutlich schlechter als Val-Performance (>10 %) — du hast wahrscheinlich auf Val overfittet. Lösung: weniger Modelle ausprobieren oder Cross-Validation.

Aufgaben

Phase 3 / 4
Aufgabe

80/10/10 stratifizierter Split

2 Punkte

Schreibe split_three(X, y, random_state=13), die den Datensatz stratifiziert in 80 % Train, 10 % Validation und 10 % Test zerlegt. Die Rückgabe ist ein Tupel:

(X_train, X_val, X_test, y_train, y_val, y_test)

Du brauchst genau zwei Aufrufe von train_test_split — der zweite zerlegt den 20-%-Rest hälftig in Val und Test. random_state muss in beiden Aufrufen gesetzt sein, sonst sind die Splits nicht reproduzierbar. stratify ebenfalls — beim zweiten Aufruf mit dem Rest-Vektor, nicht dem ursprünglichen y.

Hinweise

    Aufgabe

    RobustScaler — fit auf Train, transform überall

    2 Punkte

    Schreibe scale_all(X_train, X_val, X_test), die einen RobustScaler auf den Trainingsdaten anpasst und ihn dann auf alle drei Sets anwendet. Rückgabe ist ein Tupel (X_train_s, X_val_s, X_test_s).

    Wichtig: fit darf nur auf X_train aufgerufen werden — niemals auf X_val oder X_test. Sonst leckst du Information aus den Validierungs-/Testdaten in deine Skalierung.

    Hinweise

      Aufgabe

      Drei Klassifikatoren vergleichen

      3 Punkte

      Schreibe compare_models(X_train, y_train, X_val, y_val), die drei Klassifikatoren — KNeighborsClassifier(n_neighbors=5), DecisionTreeClassifier(random_state=13) und RandomForestClassifier(random_state=13) — trainiert und auf den Validierungsdaten bewertet. Rückgabe ist ein pd.DataFrame mit folgender Struktur:

      modelaccuracyprecisionrecallf1
      KNN............
      DecisionTree............
      RandomForest............

      Für Precision/Recall/F1 nimmst du average='macro' und zero_division=0.

      Hinweise

        Aufgabe

        Bestes Modell auf dem Testset berichten

        3 Punkte

        Schreibe best_on_test(results_df, models, X_train, y_train, X_test, y_test):

        • results_df ist ein DataFrame wie aus Aufgabe 3 (Spalten model, accuracy, precision, recall, f1).
        • models ist das ursprüngliche Dict {name: ModelInstance} aus Aufgabe 3.
        • Wähle das Modell mit dem höchsten F1 auf Validation.
        • Trainiere es (frisch) auf X_train, y_train und gib die Test-Accuracy als float zurück.

        Damit dokumentierst du die saubere Methodik: Validation für Auswahl, Test einmal für den Bericht.

        Hinweise

          Übungsquiz

          Phase 4 / 4

          Teste dein Verständnis in 15 Minuten mit 6 Frage(n), direkt an den HSG-Quizstolperfallen ausgerichtet.

          Quiz starten