Marcel0123 commited on
Commit
4d73445
·
verified ·
1 Parent(s): 8406363

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +139 -126
  2. requirements.txt +1 -0
app.py CHANGED
@@ -1,142 +1,155 @@
1
 
2
  import gradio as gr
3
  import numpy as np
 
4
  import matplotlib.pyplot as plt
5
  from sklearn import datasets
6
  from sklearn.preprocessing import StandardScaler
7
  from sklearn.decomposition import PCA
8
- from sklearn.cluster import MiniBatchKMeans
9
- from sklearn.metrics import silhouette_score
10
-
11
- INTRO_MD = r"""
12
- ### Wat gebeurt hier?
13
- We laten **unsupervised learning** zien: het algoritme zoekt **vanzelf groepjes** in de data — zónder dat we van tevoren labels geven.
14
- We gebruiken een bekende dataset (sklearn *diabetes*) met meerdere metingen per persoon (features).
15
-
16
- - We **schalen** de data (zodat alle metingen vergelijkbaar meewegen).
17
- - We projecteren alles naar **2D met PCA** om het zichtbaar te maken.
18
- - We voeren **k-means clustering** uit en **updaten** de centers stap voor stap (mini-batches).
19
- - Je ziet live:
20
- - de **punten** (elk een persoon) ingekleurd per **cluster**,
21
- - de **clustercentra** (kruisjes) die **opschuiven**,
22
- - en de **inertia-curve** die meestal **daalt** (lager = strakkere clusters).
23
-
24
- > Educatief voorbeeld. Dit is géén medisch advies en geen diagnose.
25
- """
26
 
27
- def load_diabetes_features():
 
 
 
28
  d = datasets.load_diabetes()
29
- X = d.data # 10 features
30
- return X
31
-
32
- def kmeans_live_generator(k, iters, batch_size, seed):
33
- # Data voorbereiden
34
- X = load_diabetes_features()
35
- scaler = StandardScaler(with_mean=True, with_std=True)
36
- Xs = scaler.fit_transform(X)
37
-
38
- pca = PCA(n_components=2, random_state=int(seed))
39
- Z = pca.fit_transform(Xs) # 2D projectie voor visualisatie
40
-
41
- # MiniBatchKMeans voor stapsgewijze updates
42
- kmeans = MiniBatchKMeans(
43
- n_clusters=int(k),
44
- random_state=int(seed),
45
- n_init=1,
46
- init="k-means++",
47
- batch_size=int(batch_size),
48
- reassignment_ratio=0.01,
49
- )
50
-
51
- # Maak batches
52
- n = Xs.shape[0]
53
- rng = np.random.RandomState(int(seed))
54
- idx = np.arange(n)
55
- rng.shuffle(idx)
56
-
57
- inertias = []
58
- for t in range(1, int(iters) + 1):
59
- # Pak een batch (roterend door de data)
60
- start = ((t - 1) * batch_size) % n
61
- end = min(start + int(batch_size), n)
62
- batch_idx = idx[start:end]
63
- Xb = Xs[batch_idx]
64
-
65
- # Eén update-stap
66
- kmeans.partial_fit(Xb)
67
-
68
- # Labels en inertia op volledige set
69
- labels = kmeans.predict(Xs)
70
- inertia = float(kmeans.inertia_)
71
- inertias.append(inertia)
72
-
73
- # Projecteer centers naar 2D
74
- centers_2d = pca.transform(kmeans.cluster_centers_)
75
-
76
- # Plot 1: 2D scatter met clusters + centers
77
- fig_main = plt.figure(figsize=(7, 4))
78
- ax1 = fig_main.add_subplot(111)
79
- ax1.scatter(Z[:, 0], Z[:, 1], c=labels, s=22, alpha=0.85)
80
- ax1.scatter(centers_2d[:, 0], centers_2d[:, 1], marker="x", s=120, linewidths=2)
81
- ax1.set_title(f"K-means live — iteratie {t}/{iters} (k={k})")
82
- ax1.set_xlabel("PCA component 1")
83
- ax1.set_ylabel("PCA component 2")
84
- ax1.grid(True, linestyle=":", linewidth=0.6)
85
- plt.tight_layout()
86
-
87
- # Plot 2: inertia-curve
88
- fig_inertia = plt.figure(figsize=(7, 3.2))
89
- ax2 = fig_inertia.add_subplot(111)
90
- ax2.plot(range(1, len(inertias)+1), inertias, marker="o")
91
- ax2.set_title("Inertia (doelfunctie) per iteratie — lager is beter")
92
- ax2.set_xlabel("Iteratie")
93
- ax2.set_ylabel("Inertia")
94
- ax2.grid(True, linestyle=":", linewidth=0.6)
95
- plt.tight_layout()
96
-
97
- # Metrics: op laatste stap ook silhouette en cluster-groottes
98
- metrics_lines = [f"**Iteratie:** {t}/{iters} — **Inertia:** {inertia:.2f}"]
99
- if t == int(iters):
100
- try:
101
- sil = float(silhouette_score(Xs, labels))
102
- metrics_lines.append(f"**Silhouette score:** {sil:.3f}")
103
- except Exception:
104
- metrics_lines.append("**Silhouette score:** (n.v.t.)")
105
- # cluster groottes
106
- sizes = np.bincount(labels, minlength=int(k))
107
- size_str = ", ".join([f"cluster {i}: {sizes[i]}" for i in range(int(k))])
108
- metrics_lines.append(f"**Cluster-groottes:** {size_str}")
109
- metrics_lines.append("> Tip: probeer een andere *k* en vergelijk de inertia/silhouette.")
110
-
111
- yield fig_main, fig_inertia, "\n".join(metrics_lines)
112
-
113
- with gr.Blocks(title="Unsupervised Learning — Live Clustering (K-means + PCA)") as demo:
114
- gr.Markdown("# Unsupervised Learning — Live Clustering (K-means + PCA)")
115
- gr.Markdown(INTRO_MD)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  with gr.Row():
118
  with gr.Column(scale=1):
119
- k = gr.Slider(2, 10, value=3, step=1, label="Aantal clusters (k)")
120
- iters = gr.Slider(5, 200, value=40, step=1, label="Iteraties")
121
- batch_size = gr.Slider(16, 256, value=128, step=1, label="Batchgrootte")
122
- seed = gr.Slider(0, 9999, value=42, step=1, label="Random seed")
123
- run_btn = gr.Button("Cluster live")
 
 
 
 
 
 
124
  with gr.Column(scale=2):
125
- plot_main = gr.Plot(label="2D-projectie (PCA) met clusters en centers (live)")
126
- plot_inertia = gr.Plot(label="Inertia per iteratie")
127
- metrics = gr.Markdown()
128
-
129
- run_btn.click(
130
- fn=kmeans_live_generator,
131
- inputs=[k, iters, batch_size, seed],
132
- outputs=[plot_main, plot_inertia, metrics]
133
- )
134
-
135
- demo.load(
136
- fn=kmeans_live_generator,
137
- inputs=[k, iters, batch_size, seed],
138
- outputs=[plot_main, plot_inertia, metrics]
139
- )
140
 
141
  if __name__ == "__main__":
142
  demo.launch()
 
1
 
2
  import gradio as gr
3
  import numpy as np
4
+ import pandas as pd
5
  import matplotlib.pyplot as plt
6
  from sklearn import datasets
7
  from sklearn.preprocessing import StandardScaler
8
  from sklearn.decomposition import PCA
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # ------------------------------
11
+ # Data loading
12
+ # ------------------------------
13
+ def load_diabetes_df():
14
  d = datasets.load_diabetes()
15
+ X = pd.DataFrame(d.data, columns=d.feature_names)
16
+ y = pd.Series(d.target, name="target")
17
+ # Voeg target erbij voor mogelijke kleurselecties, al is default BMI
18
+ df = X.copy()
19
+ df["target"] = y
20
+ return df
21
+
22
+ # ------------------------------
23
+ # PCA computation + visuals
24
+ # ------------------------------
25
+ def pca_biplot(color_feature="bmi", arrow_scale=2.0, point_size=32, alpha=0.85, n_components=10, standardize=True):
26
+ df = load_diabetes_df()
27
+ feats = [c for c in df.columns if c != "target"]
28
+ X = df[feats].values
29
+
30
+ # Standardize (diabetes is al ongeveer gestandaardiseerd, maar we doen dit expliciet voor duidelijkheid)
31
+ if standardize:
32
+ scaler = StandardScaler(with_mean=True, with_std=True)
33
+ Xs = scaler.fit_transform(X)
34
+ else:
35
+ Xs = X
36
+
37
+ # PCA
38
+ pca = PCA(n_components=min(n_components, Xs.shape[1]))
39
+ Z = pca.fit_transform(Xs) # scores
40
+ loadings = pca.components_.T # shape (features, components)
41
+ expl = pca.explained_variance_ratio_
42
+
43
+ # Kleur op geselecteerde feature
44
+ if color_feature not in df.columns:
45
+ color_feature = "bmi"
46
+ cvals = df[color_feature].values
47
+
48
+ # ---------------- Plot 1: PCA biplot (scores + feature vectors) ----------------
49
+ fig1 = plt.figure(figsize=(7.5, 5.5))
50
+ ax = fig1.add_subplot(111)
51
+ sc = ax.scatter(Z[:, 0], Z[:, 1], c=cvals, s=point_size, alpha=alpha)
52
+ cbar = plt.colorbar(sc, ax=ax, pad=0.02)
53
+ cbar.set_label(f"Kleur: {color_feature}")
54
+ ax.set_xlabel("PC1")
55
+ ax.set_ylabel("PC2")
56
+ ax.set_title("PCA biplot — punten (projectie) + pijlen (feature-bijdragen)")
57
+
58
+ # pijlen voor feature loadings (alleen PC1/PC2)
59
+ for i, feat in enumerate(feats):
60
+ x_arrow = loadings[i, 0] * arrow_scale
61
+ y_arrow = loadings[i, 1] * arrow_scale
62
+ ax.arrow(0, 0, x_arrow, y_arrow, head_width=0.05, head_length=0.08, fc="k", ec="k", length_includes_head=True)
63
+ ax.text(x_arrow * 1.08, y_arrow * 1.08, feat, fontsize=9, ha="center", va="center")
64
+
65
+ ax.axhline(0, color="grey", linewidth=0.6, linestyle=":")
66
+ ax.axvline(0, color="grey", linewidth=0.6, linestyle=":")
67
+ ax.grid(True, linestyle=":", linewidth=0.6)
68
+ plt.tight_layout()
69
+
70
+ # ---------------- Plot 2: Explained variance (bar + cumulative line) ----------------
71
+ fig2 = plt.figure(figsize=(7.5, 3.8))
72
+ ax2 = fig2.add_subplot(111)
73
+ xs = np.arange(1, len(expl) + 1)
74
+ ax2.bar(xs, expl, width=0.8, align="center")
75
+ ax2.plot(xs, np.cumsum(expl), marker="o")
76
+ ax2.set_xticks(xs)
77
+ ax2.set_xlabel("Principal Component")
78
+ ax2.set_ylabel("Explained variance ratio")
79
+ ax2.set_title("Uitlegvariantie per component (balken) + cumulatief (lijn)")
80
+ ax2.grid(True, linestyle=":", linewidth=0.6)
81
+ plt.tight_layout()
82
+
83
+ # ---------------- Tabel: top-features per PC1 en PC2 ----------------
84
+ load_df = pd.DataFrame({
85
+ "feature": feats,
86
+ "PC1_loading": loadings[:, 0],
87
+ "PC2_loading": loadings[:, 1],
88
+ "PC1_abs": np.abs(loadings[:, 0]),
89
+ "PC2_abs": np.abs(loadings[:, 1]),
90
+ })
91
+ # sorteer per component en merge een compacte weergave
92
+ top_pc1 = load_df.sort_values("PC1_abs", ascending=False)[["feature", "PC1_loading"]].head(6).reset_index(drop=True)
93
+ top_pc2 = load_df.sort_values("PC2_abs", ascending=False)[["feature", "PC2_loading"]].head(6).reset_index(drop=True)
94
+
95
+ top_pc1.rename(columns={"feature": "Feature (PC1)", "PC1_loading": "Loading PC1"}, inplace=True)
96
+ top_pc2.rename(columns={"feature": "Feature (PC2)", "PC2_loading": "Loading PC2"}, inplace=True)
97
+
98
+ # Combineer netjes naast elkaar
99
+ max_len = max(len(top_pc1), len(top_pc2))
100
+ top_pc1 = top_pc1.reindex(range(max_len))
101
+ top_pc2 = top_pc2.reindex(range(max_len))
102
+ table = pd.concat([top_pc1, top_pc2], axis=1)
103
+
104
+ # Beschrijving in gewone taal
105
+ summary_md = f"""### Wat zie je hier?
106
+ - **Punten (personen)** geprojecteerd in 2D met **PCA**. Dicht bij elkaar = **lijkt op elkaar** over meerdere metingen.
107
+ - **Kleur** = waarde van **{color_feature}** (bijv. BMI). Zo zie je meteen of die eigenschap een **gradiënt** vormt.
108
+ - **Pijlen** = bijdrage van **features** aan de richting van **PC1/PC2**. Lengte ≈ hoe sterk die feature die richting beïnvloedt.
109
+ - **Balkgrafiek** = per component hoeveel variatie hij uitlegt; **lijn** = cumulatief.
110
+
111
+ ### Hoe lees je de biplot?
112
+ - Staat een pijl **rechts/boven**, dan drukt die feature de data die kant op in PC1/PC2.
113
+ - Punten in de richting van een pijl hebben vaak **hogere waarden** voor die feature.
114
+ - Kleurgradiënt (bijv. BMI): als kleuren geleidelijk veranderen langs een as, is dat **consistentie** met die component.
115
+
116
+ > Tip: verander **pijl-schaal**, **puntgrootte** en **transparantie** om het patroon beter te zien.
117
+ """
118
+
119
+ return fig1, fig2, table, summary_md
120
+
121
+ # ------------------------------
122
+ # UI
123
+ # ------------------------------
124
+ with gr.Blocks(title="PCA Biplot — Diabetes (kleur: BMI)") as demo:
125
+ gr.Markdown("# PCA Biplot — Diabetes (kleur: BMI)")
126
+ gr.Markdown("""In deze demo zie je **live** hoe PCA de data samenvat. De punten zijn personen; pijlen laten zien welke features
127
+ (zoals **bmi**, **bp**, **s1..s6**) de richting van de componenten bepalen. De **kleur** toont standaard **bmi**.
128
+ """)
129
 
130
  with gr.Row():
131
  with gr.Column(scale=1):
132
+ color_feat = gr.Dropdown(
133
+ choices=["bmi", "bp", "s1", "s2", "s3", "s4", "s5", "s6", "age", "sex", "target"],
134
+ value="bmi",
135
+ label="Kleur op feature"
136
+ )
137
+ arrow_scale = gr.Slider(0.5, 5.0, value=2.0, step=0.1, label="Pijl-schaal (loadings)")
138
+ point_size = gr.Slider(8, 80, value=32, step=2, label="Puntgrootte")
139
+ alpha = gr.Slider(0.2, 1.0, value=0.85, step=0.05, label="Transparantie (punten)")
140
+ n_components = gr.Slider(2, 10, value=10, step=1, label="Aantal PCA-componenten (voor variatieplot)")
141
+ standardize = gr.Checkbox(value=True, label="Standaardiseer features (aanbevolen)")
142
+ run_btn = gr.Button("Update visualisaties")
143
  with gr.Column(scale=2):
144
+ plot_biplot = gr.Plot(label="PCA biplot punten + pijlen")
145
+ plot_expl = gr.Plot(label="Uitlegvariantie per component")
146
+ table = gr.Dataframe(headers=["Feature (PC1)", "Loading PC1", "Feature (PC2)", "Loading PC2"], row_count=6)
147
+ summary = gr.Markdown()
148
+
149
+ inputs = [color_feat, arrow_scale, point_size, alpha, n_components, standardize]
150
+
151
+ run_btn.click(fn=pca_biplot, inputs=inputs, outputs=[plot_biplot, plot_expl, table, summary])
152
+ demo.load(fn=pca_biplot, inputs=inputs, outputs=[plot_biplot, plot_expl, table, summary])
 
 
 
 
 
 
153
 
154
  if __name__ == "__main__":
155
  demo.launch()
requirements.txt CHANGED
@@ -2,3 +2,4 @@ gradio>=4.36.0
2
  matplotlib>=3.7.0
3
  numpy>=1.23.0
4
  scikit-learn>=1.2.0
 
 
2
  matplotlib>=3.7.0
3
  numpy>=1.23.0
4
  scikit-learn>=1.2.0
5
+ pandas>=1.5.0