152 lines
6.1 KiB
Markdown
152 lines
6.1 KiB
Markdown
# 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`
|
||
(17–98) oder `duration` (Sekunden). `StandardScaler` zentriert jede Spalte auf
|
||
Mittelwert 0 / Standardabweichung 1 und stellt damit alle Features gleichwertig.
|
||
|
||
### Leakage vermeiden: `fit` nur auf Train
|
||
|
||
```python
|
||
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
|
||
|
||
```sh
|
||
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.
|