feature: add excercise project structure
This commit is contained in:
@@ -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
@@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
venv.requirements = ''
|
||||
ipython
|
||||
jupyter
|
||||
jupyterlab
|
||||
matplotlib
|
||||
numpy
|
||||
scikit-learn
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+3
-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:
|
||||
Reference in New Issue
Block a user