feature: add excercise project structure

This commit is contained in:
2026-06-18 14:43:51 +02:00
parent 12c271281a
commit 90bcd04e4e
14 changed files with 61930 additions and 9 deletions
+68
View File
@@ -0,0 +1,68 @@
# K-Means: Elbow-Methode & Gütemasse
> Thema: Optimale Clusterzahl $k$ bestimmen und Clusterqualität bewerten
> Skript: Unsupervised Learning (CAS PML)
## Ziel
Auf synthetischen 2D-Daten (drei Gauss-Blobs) K-Means für verschiedene $k$ laufen lassen und die "richtige" Clusterzahl systematisch über Gütemasse bestimmen — statt nur visuell zu schätzen.
## Projektstruktur
```
.
├── data/ # leer: Daten werden synthetisch generiert
├── src/
│ └── k_means2_elbow_measures.py
├── devenv.nix
└── README.md
```
Ausführen (in der devenv-Shell):
```
python src/k_means2_elbow_measures.py
```
> Hinweis: Das erhaltene Textfile hat kaputte Umlaute (Windows-1252 statt UTF-8). Beim Übernehmen nach `src/` als UTF-8 neu speichern, sonst sind die Kommentare unleserlich.
## Ablauf des Skripts
1. Daten generieren: je 100 Punkte um die Zentren $(0,0)$, $(6,6)$, $(0,6)$ via `np.random.randn` (Seed fix → reproduzierbar)
2. Für $k = 1 \dots 8$ K-Means fitten und drei Masse sammeln:
- Inertia (`kmeans.inertia_`) — immer
- Silhouette-Score und Davies-Bouldin-Index — erst ab $k \geq 2$ (für $k=1$ nicht definiert → `np.nan`)
3. Bestes $k$ wählen (vereinfacht: maximaler Silhouette-Score via `np.nanargmax`)
4. Finales Modell mit `best_k` fitten → Labels + Zentren
5. Visualisierung in vier Panels: Rohdaten | Elbow (Inertia) | Silhouette | finales Clustering
## Gütemasse (Kurzreferenz)
- Inertia / WCSS — Summe der quadrierten Abstände zum jeweiligen Zentroid; sinkt monoton mit $k$, hat also kein Optimum, nur einen "Knick" (Elbow)
$$ \text{WCSS} = \sum_{i=1}^{k} \sum_{x \in P_i} \lVert x - \mu_i \rVert^2 $$
- Silhouette-Score — $a_i$ = mittlere Intra-Cluster-Distanz, $b_i$ = mittlere Distanz zum nächsten Cluster; Bereich $[-1, 1]$, höher = besser
$$ s_i = \frac{b_i - a_i}{\max(a_i, b_i)} $$
- Davies-Bouldin-Index — mittleres "Worst-Case"-Verhältnis von Cluster-Streuung zu Cluster-Abstand; niedriger = besser (Achtung: umgekehrt zur Silhouette!)
$$ DB = \frac{1}{k} \sum_{i=1}^{k} \max_{j \neq i} \frac{\sigma_i + \sigma_j}{d(c_i, c_j)} $$
- mit $\sigma_i$ = mittlere Distanz der Punkte in Cluster $i$ zu ihrem Zentroid $c_i$
## Verständnisfragen (im Code eingebettet)
- [ ] Warum nimmt man zur $k$-Wahl die Silhouette und nicht die Inertia?
- [ ] Warum sind die Cluster im Ergebnis-Plot farblich klar getrennt?
- [ ] Warum können verschiedene Runs unterschiedliche Ergebnisse liefern?
- [ ] Ist das gefundene $k$ immer "korrekt"?
## Experimente (zum Ausprobieren)
- [ ] Zentren näher zusammenlegen (Daten stärker überlappen) → wie verändern sich Elbow und Scores?
- [ ] Davies-Bouldin gegen die Silhouette als $k$-Kriterium tauschen → wählt es dasselbe $k$?
- [ ] `n_init` reduzieren / Seed variieren → wie stabil ist das Ergebnis?
> Zu allen Verständnisfragen stehen im Skript kurze Musterantworten als Kommentar.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
AAPL,738.5,tech,USA
XOM,369.5,nrg,USA
GOOGL,368.2,tech,USA
MSFT,346.7,tech,USA
BRK-A,343.5,fin,USA
WFC,282.4,fin,USA
CHL,282.1,tel,CN
JNJ,279.7,cons,USA
WMT,257.2,cons,USA
VZ,205.2,tel,USA
ORCL,192.1,tech,USA
RDS-A,195.7,nrg,NL
1 AAPL 738.5 tech USA
2 XOM 369.5 nrg USA
3 GOOGL 368.2 tech USA
4 MSFT 346.7 tech USA
5 BRK-A 343.5 fin USA
6 WFC 282.4 fin USA
7 CHL 282.1 tel CN
8 JNJ 279.7 cons USA
9 WMT 257.2 cons USA
10 VZ 205.2 tel USA
11 ORCL 192.1 tech USA
12 RDS-A 195.7 nrg NL
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1781627264,
"narHash": "sha256-TPj5d5MUyvuQZjsfDAAoYJ0SB+tNNNBrdCpD8XL0WeU=",
"owner": "cachix",
"repo": "devenv",
"rev": "0fe5629a2955141c336b95e384e2c793c01214fb",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"nixpkgs": {
"inputs": {
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1781620901,
"narHash": "sha256-UF6scQlG+6lRkZBUpn/3KNavhOo5G8kDWhjVHcno8uc=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "2df109b343d3c68efd752e32a444a1d9b9f89afa",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-python": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1781070343,
"narHash": "sha256-wXAybU+2LlbXm9cfRDHRPASakq60rLvZBXVT2Ahkj1U=",
"owner": "cachix",
"repo": "nixpkgs-python",
"rev": "23629493653be6df0472a46b1be1d65cbd6df55b",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "nixpkgs-python",
"type": "github"
}
},
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1781454065,
"narHash": "sha256-d2xfDjnfRuf/xYGdu9VVRHiav/2w5hDL/5cw2TuVAXw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9eac87a12312b8f60dd52e1c6e1a265f6fc7f5fc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"nixpkgs-python": "nixpkgs-python"
}
}
},
"root": "root",
"version": 7
}
+1
View File
@@ -8,6 +8,7 @@
venv.requirements = ''
ipython
jupyter
jupyterlab
matplotlib
numpy
scikit-learn
+6
View File
@@ -0,0 +1,6 @@
inputs:
nixpkgs-python:
url: github:cachix/nixpkgs-python
inputs:
nixpkgs:
follows: nixpkgs
@@ -272,9 +272,7 @@
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"scrolled": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -715,9 +713,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
"version": "3.14.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
@@ -88,20 +88,19 @@ ax[0].set_title("Rohdaten (unclustered)")
# ? Ja, visuell oft erkennbar, aber nicht immer eindeutig
# ---- Elbow ----
ax[1].plot(k_values, inertia_values, marker='o')
ax[1].plot(k_values, inertia_values, marker="o")
ax[1].set_title("Elbow (Inertia)")
ax[1].set_xlabel("k")
ax[1].set_ylabel("Fehler")
# ---- Silhouette ----
ax[2].plot(k_values, silhouette_values, marker='o')
ax[2].plot(k_values, silhouette_values, marker="o")
ax[2].set_title("Silhouette Score")
ax[2].set_xlabel("k")
# ---- Finales Clustering ----
scatter = ax[3].scatter(X[:, 0], X[:, 1], c=labels_final)
ax[3].scatter(centers_final[:, 0], centers_final[:, 1],
c='red', s=200, marker='X')
ax[3].scatter(centers_final[:, 0], centers_final[:, 1], c="red", s=200, marker="X")
ax[3].set_title(f"K-Means Ergebnis (k={best_k})")
# Frage: