# Workshop 05 — Decision Tree Classifier: `min_impurity_decrease` Tuning ## Aufgabe Untersuchen, wie verschiedene Werte von `min_impurity_decrease` beim `DecisionTreeClassifier` die erreichbare **Test-Accuracy** beeinflussen. - Wertebereich **schrittweise eingrenzen** (grob → fein). - Resultate darstellen: - grafisch als **Liniendiagramm** (Parameter vs. Accuracy), - in der **Konsole**: bester Score + zugehöriger Parameterwert. Hinweis aus den Folien: `range()` liefert Ganzzahlen, `np.arange()` liefert Gleitkommawerte. `min_impurity_decrease` ist ein Float → `np.arange`. ## Setup ```bash devenv shell # python + venv (pandas, numpy, sklearn, matplotlib, seaborn) ``` Datengrundlage = vorbereiteter Bank-Datensatz aus W4. Geladen über das kursinterne Modul (muss auf dem `PYTHONPATH` / im Projektordner liegen): ```python from bfh_cas_pml import prep_data X_train, X_test, y_train, y_test = prep_data('bank_data_prep.csv', 'y', seed=1234) ``` Baseline zum Abgleich (voll ausgewachsener Baum): ```python DecisionTreeClassifier(random_state=1234) # train=1.0, test≈0.8296 → overfit ``` > Falls `bfh_cas_pml` / `bank_data_prep.csv` nicht zur Hand sind: als Fallback > tut es jeder `train_test_split` auf einem sklearn-Datensatz — die Mechanik > des Sweeps bleibt identisch. ## Theorie — was `min_impurity_decrease` tut Ein Split wird **nur** ausgeführt, wenn er die (gewichtete, auf den ganzen Baum normierte) Impurity um mindestens diesen Wert senkt: ``` ΔI_norm = (N_node / N_total) * ( I_parent - (N_left/N_node) * I_left - (N_right/N_node) * I_right ) ``` Kernpunkte fürs Verständnis: - Es ist eine **Pre-Pruning**-Schwelle: schwache Splits werden gar nicht erst gemacht → der Baum bleibt kleiner → weniger Overfitting. - Der Wert ist mit dem **Anteil der Beobachtungen im Knoten** gewichtet (`N_node / N_total`). Tiefe Knoten betreffen wenige Samples → ihr ΔI_norm ist winzig. Darum liegen sinnvolle Schwellen im Bereich **~1e-4 bis ~1e-2**, nicht bei 0.1+. (Vgl. das Folien-Rechenbeispiel: ein *guter* Split nahe der Wurzel ergab 0.0421.) - `=0` → kein Pruning → Baseline-Baum (overfit). ## Vorgehen — Eingrenzung (der eigentliche Lerninhalt) 1. **Grob**: weiter Bereich, grobe Schritte, um die Region des Maximums zu lokalisieren — z. B. `np.arange(0, 0.02, 0.001)`. 2. **Fein**: um das gefundene Maximum herum zoomen — z. B. `np.arange(0, 0.004, 0.0002)`. 3. Wiederholen, bis Lage/Wert des Peaks stabil sind. Jede Iteration ist ein eigener Sweep (gleicher Loop, anderer `np.arange`). Im README/Notes die drei (o. ä.) Ranges + jeweils Peak dokumentieren — das *ist* die geforderte „schrittweise Eingrenzung“. ## Erwartetes Verhalten (Sanity-Check) - Bei `0` startest du auf der Baseline (~0.83). - Mit steigendem Wert zunächst Plateau / leichter Bump (Rausch-Splits werden entfernt), dann **Kante nach unten**, sobald nützliche Splits wegfallen. - Im Extrem degeneriert der Baum zum Stumpf → Accuracy = Mehrheitsklasse. Der Bank-Datensatz wurde in W4 ~balanciert resampled → Floor liegt nahe ~0.5. Wenn deine Kurve dort hin abstürzt, ist das korrekt, kein Bug. ## Deliverables - [ ] Loop über `np.arange`-Range, `set_params(min_impurity_decrease=p)`, `fit`, `score(X_test, y_test)`, sammeln. - [ ] `sns.lineplot(x=params, y=scores)` + Scatter-Marker auf `max(scores)`, Achsen beschriftet (`min_impurity_decrease` / `accuracy`). - [ ] Konsole: `best score` + zugehöriger Parameterwert. - [ ] Mind. 2 Eingrenzungs-Stufen (grob + fein) dokumentiert. ## Caveats / Vertiefung (optional) - **Optimistic bias** (wie in W4): hier wird `min_impurity_decrease` direkt gegen `X_test` getunt — derselbe Trap wie die manuelle Grid-Search ohne CV. Der gemeldete „beste“ Score ist dadurch optimistisch verzerrt. Die Folien machen es so; fürs Deliverable also bewusst übernehmen, aber als Deviation notieren. Sauber wäre Tuning auf einem Validation-Split bzw. `GridSearchCV`. - **Verwandter Faden, falls Zeit/Interesse**: `ccp_alpha` (Cost-Complexity / Minimal-Cost-Complexity-Pruning) ist sklearns „eigentlicher“ Pruning-Knopf und liefert via `cost_complexity_pruning_path()` direkt eine sinnvolle Kandidatenliste statt manueller `np.arange`-Rate-Erei — nur als Zeiger, nicht Teil der Aufgabe.