Files
cas-pml/SL/aufgaben/workshop4/README.md
T

6.1 KiB
Raw Blame History

Workshop 4 — kNN Hyperparametersuche

Hyperparameter-Tuning für KNeighborsClassifier auf bank_data_prep.csv (Klassifikation: Hat der Kunde abgeschlossen, ja/nein?), inklusive Vergleich mit/ohne Standardisierung.

  • Input: data/bank_data_prep.csv (Output aus Workshop 3 / Bank-Implementation)
  • Pipeline: src/hyperparametersearch.py

Aufgabenstellung

Drei Teile gemäss Folie:

  1. Features von Trainings- und Testdaten mit StandardScaler standardisieren.
  2. Beste Parameter für KNeighborsClassifier finden:
    • n_neighbors ∈ {1..10}
    • p ∈ {1, 2, 3}
  3. Ergebnisse mit vs. ohne Standardisieren vergleichen.

Konzepte kurz erklärt

Was ist p? (Minkowski-Distanz)

p steuert die Distanzmetrik, mit der kNN „Nachbarschaft" misst:

p Distanz Formel Verhalten
1 Manhattan Σ |x_i y_i| Strassen-Netz-Distanz, robuster gegen Ausreisser in Einzeldimensionen
2 Euklidisch √Σ(x_i y_i)² „Luftlinie", sklearn-Default
3+ Höhere Minkowski (Σ|x_i y_i|^p)^(1/p) Gewichtet grosse Einzelunterschiede zunehmend stärker

Bei diesem Datensatz gewinnt p=1 (Manhattan) konsistent über alle Varianten — plausibel, weil viele Dummy-Features (0/1) drin sind und Manhattan diese gleichmässiger gewichtet als Euklidisch.

Warum Standardisierung bei kNN nicht optional ist

kNN ist distanzbasiert. Ohne Skalierung dominiert die Variable mit dem grössten Wertebereich die Distanzberechnung — Dummy-Features (0/1) werden faktisch ignoriert, weil ihr Beitrag zur Distanz verschwindet gegen z.B. age (1798) oder duration (Sekunden). StandardScaler zentriert jede Spalte auf Mittelwert 0 / Standardabweichung 1 und stellt damit alle Features gleichwertig.

Leakage vermeiden: fit nur auf Train

scaler.fit(X_train)                    # mean/std NUR aus Train lernen
X_train = scaler.transform(X_train)
X_test  = scaler.transform(X_test)     # mit Train-Statistiken transformieren

Würde der Scaler auf X_test (oder dem gesamten Datensatz) gefittet, flössen Test-Statistiken in die Vorverarbeitung ein → Leakage. Das Test-Set soll so behandelt werden, als sähe man es zum ersten Mal — denn so wird es in Produktion (neue Daten) auch sein.

Hyperparametersuche: manuell vs. GridSearchCV

Folien-Methode: Doppelschleife über n_neighbors × p, Score auf Test-Set genommen, beste Kombi gewählt. Funktioniert, hat aber zwei Schwächen:

  1. Optimistic Bias: Tunt gegen das Test-Set. Die gewählte Kombi ist die, die zufällig auf genau diesem einen Split am besten lief.
  2. Einziger Split: Keine Robustheit gegen ungünstige Train/Test-Aufteilungen.

GridSearchCV löst beides: Cross-Validation auf den Trainingsdaten (k-Fold, hier 5), Test-Set wird erst am Ende einmal angefasst.

Drei Varianten

Variante Skalierung Hyperparameter-Suche Bestes k, p Accuracy
A nein manuell (Doppelschleife) k=9, p=1 76,6 % (Test)
B ja manuell (Doppelschleife) k=9, p=1 80,4 % (Test)
C ja GridSearchCV (5-fold) k=9, p=1 81,1 % (CV) / 80,4 % (Test)

Lesart:

  • A → B: +3,7 pp durch Skalierung. Belegt Aufgabenteil 3.
  • B → C: Accuracy bleibt gleich, aber der CV-Score ist der ehrliche Schätzer für die Generalisierung. Der Test-Score von B war leicht optimistisch verzerrt (gegen Test getunt); C bestätigt ihn unabhängig.

Klassifikationsqualität (Variante C)

[[1461  274]
 [ 371 1181]]

              precision    recall  f1-score   support
          no       0.80      0.84      0.82      1735
         yes       0.81      0.76      0.79      1552
    accuracy                           0.80      3287

Beide Klassen werden ausgewogen vorhergesagt (F1 ≈ 0,80 für beide). Das Modell ist leicht konservativer mit yes-Vorhersagen (Recall 0,76 vs. 0,84 für no).

Befund zum Datensatz

bank_data_prep.csv ist mit 53/47 (no/yes) annähernd balanciert. Das Original-Bank-Set hat eine ~88/12-Verteilung — der gelieferte Datensatz wurde also vorab resampled (vermutlich SMOTE oder Random Over/Undersampling via imbalanced-learn). Konsequenz: Accuracy ist hier eine vertretbare Metrik; wäre der Datensatz im Original-Verhältnis, wären 80 % Accuracy schlechter als die triviale „immer no"-Baseline (88 %), und man müsste mit Precision/ Recall/F1 arbeiten.

Projektstruktur

workshop4
├── data/
│   └── bank_data_prep.csv
├── src/
│   └── hyperparametersearch.py
├── devenv.nix
└── README.md

Ausführen

python src/hyperparametersearch.py

Implementierungs-Notizen

Bewusste Entscheidungen, die über die Folien-Vorlage hinausgehen:

  • fit nur auf X_train beim StandardScaler (Folie macht es so, viele Anleitungen nicht — daher hier explizit dokumentiert).
  • GridSearchCV zusätzlich zur Folien-Doppelschleife, um Optimistic Bias sichtbar zu machen und einen leakage-freien CV-Score zu erhalten.
  • classification_report + Confusion Matrix über die Folien-Anforderungen hinaus, weil reine Accuracy bei Klassifikation nie das ganze Bild zeigt.

Offene Punkte für ein echtes Projekt

  • Pipeline für Scaler + kNN. Ein Pipeline([('scaler', ...), ('knn', ...)]) macht aus den zwei Schritten ein Objekt — predict() skaliert dann automatisch mit dem trainierten Scaler. Verhindert strukturell die „Modell trainiert auf skaliert, Vorhersage auf unskaliert"-Klasse von Bugs. Auch GridSearchCV erhält dann die Pipeline statt nur den Classifier, was die Skalierung in jedem CV-Fold korrekt nur auf dem Fold-Train fittet.
  • Resampling-Schritt im Datenpfad sichtbar machen. Der Resampling-Eingriff passierte ausserhalb dieser Pipeline; in einem echten Projekt gehört er dokumentiert oder selbst (re-)produziert, sonst sind Ergebnisse nicht reproduzierbar.
  • Mehr Metriken bei der CV. GridSearchCV(scoring=...) kann auch auf F1, ROC-AUC u.a. optimieren — sinnvoller als Accuracy, sobald Klassen unbalanciert sind.