import gradio as gr import numpy as np import matplotlib.pyplot as plt from sklearn import datasets from sklearn.utils import shuffle EXPLAIN_MD = """ ### Wat testen we hier? We bekijken of er een **lineair verband** is tussen **BMI** en de **diabetes-progressiescore** in een bekende (openbare) dataset. Dat doen we met *supervised learning*: het model ziet voorbeelden `(BMI → score)` en leert een lijn \(y = w x + b\) die dit verband benadert. **Hoe meten we of dat gelukt is?** - We splitsen de data in **train (80%)** en **test (20%)**. - We **trainen** het model alleen op de **trainset**. - We **toetsen** het resultaat op de **testset** die het model niet gezien heeft. - We rapporteren **MSE** (gemiddelde kwadratische fout) en **R²** (uitlegvariantie) op de testset. > Let op: in deze sklearn-dataset is BMI **genormaliseerd** (geschaald). De helling `w` geeft wel de **richting en sterkte** aan (positief = hogere BMI hangt samen met hogere score). """ STORY_MD = r""" ### Waarom kijken we naar BMI en diabetes? Stel je voor dat je arts wilt begrijpen of **het gewicht van mensen** (uitgedrukt als *Body Mass Index*, BMI) iets zegt over hun **gezondheid**. Een van de dingen die onderzocht wordt is het verband tussen BMI en de **ernst van diabetes**. We gebruiken hier **echte gegevens** uit een medische dataset (dus **geen foto’s**, maar gemeten waarden van mensen die in een onderzoek hebben meegedaan). Elke deelnemer heeft: - een **BMI-waarde** (hoe zwaar of licht iemand is ten opzichte van zijn lengte), - en een **score** die aangeeft hoe ernstig de diabetes bij die persoon verloopt. Met lineaire regressie testen we: *kunnen we een lijn tekenen die laat zien of een hogere BMI vaak samenvalt met een hogere (of juist lagere) score?* **Waarom is dat belangrijk?** - Als er wél een duidelijk verband is, kan dit helpen om **risico’s eerder te signaleren**. - Als er géén verband is, leren we dat BMI misschien niet de juiste voorspeller is en moet er verder gekeken worden naar andere factoren. Kortom: dit experiment laat je zien hoe data ons kan helpen om **patronen in gezondheid** te ontdekken — en dat doen we hier stap voor stap, live op je scherm. """ CONCLUSION_MD = r""" # **Conclusie** Mensen met een hogere **BMI** hebben in dit onderzoek gemiddeld vaker een ernstiger verloop van **diabetes**. Maar **BMI is niet de enige factor** — leeftijd, erfelijkheid, leefstijl en andere medische waarden spelen ook mee. """ def load_bmi_diabetes(): d = datasets.load_diabetes() X = d.data[:, 2] # BMI feature (genormaliseerd) y = d.target # Progressiescore return X.astype(np.float64), y.astype(np.float64), "Diabetes: BMI vs. score" def train_test_split_1d(x, y, test_size=0.2, seed=42): rng = np.random.RandomState(seed) idx = np.arange(x.shape[0]) rng.shuffle(idx) n_test = int(len(idx) * test_size) test_idx = idx[:n_test] train_idx = idx[n_test:] return x[train_idx], y[train_idx], x[test_idx], y[test_idx] def sgd_train_generator(lr, epochs, batch_size, seed, split_seed): # Data & split x, y, label = load_bmi_diabetes() x_tr, y_tr, x_te, y_te = train_test_split_1d(x, y, test_size=0.2, seed=int(split_seed)) n = x_tr.shape[0] w, b = 0.0, 0.0 x_min, x_max = float(np.min(x)), float(np.max(x)) train_losses, test_losses = [], [] rng = np.random.RandomState(int(seed)) for epoch in range(1, int(epochs) + 1): # shuffle train set x_tr, y_tr = shuffle(x_tr, y_tr, random_state=rng) # SGD over mini-batches for start in range(0, n, int(batch_size)): end = min(start + int(batch_size), n) xb, yb = x_tr[start:end], y_tr[start:end] yhat = w * xb + b err = yb - yhat dw = -(2.0 / xb.size) * np.sum(xb * err) db = -(2.0 / xb.size) * np.sum(err) w -= lr * dw b -= lr * db # Metrics on train and test y_tr_pred = w * x_tr + b y_te_pred = w * x_te + b mse_tr = float(np.mean((y_tr - y_tr_pred)**2)) mse_te = float(np.mean((y_te - y_te_pred)**2)) # R^2 on test ss_res = float(np.sum((y_te - y_te_pred)**2)) ss_tot = float(np.sum((y_te - np.mean(y_te))**2)) r2_te = 1.0 - ss_res / ss_tot if ss_tot > 0 else float("nan") train_losses.append(mse_tr) test_losses.append(mse_te) # Plot 1: data (train vs test) + regressielijn fig_main = plt.figure(figsize=(7, 4)) ax1 = fig_main.add_subplot(111) ax1.scatter(x_tr, y_tr, alpha=0.6, s=18, label="train") ax1.scatter(x_te, y_te, alpha=0.8, s=22, marker="x", label="test") xs = np.linspace(x_min, x_max, 200) ax1.plot(xs, w * xs + b, linewidth=2, label="model") ax1.set_title(f"{label} — Epoch {epoch}/{epochs}") ax1.set_xlabel("BMI (genormaliseerd)") ax1.set_ylabel("Progressiescore") ax1.legend() ax1.grid(True, linestyle=":", linewidth=0.6) plt.tight_layout() # Plot 2: loss-curve (train & test) fig_loss = plt.figure(figsize=(7, 3.5)) ax2 = fig_loss.add_subplot(111) ax2.plot(range(1, len(train_losses)+1), train_losses, marker="o", label="Train MSE") ax2.plot(range(1, len(test_losses)+1), test_losses, marker="o", linestyle="--", label="Test MSE") ax2.set_title("Loss-curve (MSE per epoch) — lager is beter") ax2.set_xlabel("Epoch") ax2.set_ylabel("MSE") ax2.legend() ax2.grid(True, linestyle=":", linewidth=0.6) plt.tight_layout() # Resultaten + opvallende conclusie verdict = "positief" if w >= 0 else "negatief" summary = ( f"**Wat levert dit op?**\n" f"- Huidige regressielijn: `y = {w:.4f} * x + {b:.4f}`\n" f"- Train MSE: `{mse_tr:.2f}` — Test MSE: `{mse_te:.2f}` — Test R²: `{r2_te:.3f}`\n" f"- Interpretatie: het verband tussen BMI en progressiescore is **{verdict}** in deze dataset " f"(hogere BMI hangt samen met hogere score als `w > 0`).\n\n" f"{CONCLUSION_MD}" ) yield fig_main, fig_loss, summary with gr.Blocks(title="Diabetes: BMI → Progressiescore (Live Regressie)") as demo: gr.Markdown("# Diabetes: BMI → Progressiescore (Live Lineaire Regressie)") gr.Markdown(EXPLAIN_MD) with gr.Row(): with gr.Column(scale=1): lr = gr.Slider(1e-4, 1e-0, value=5e-3, step=1e-4, label="Learning rate") epochs = gr.Slider(5, 200, value=60, step=1, label="Epochs") batch = gr.Slider(8, 256, value=64, step=1, label="Batchgrootte") seed = gr.Slider(0, 9999, value=42, step=1, label="Training seed") split_seed = gr.Slider(0, 9999, value=7, step=1, label="Train/test split seed") train_btn = gr.Button("Train live") # Story direct onder de knop gr.Markdown(STORY_MD) with gr.Column(scale=2): plot_main = gr.Plot(label="Data (train/test) & regressielijn (live)") plot_loss = gr.Plot(label="Loss-curve (MSE per epoch) — train vs test") results = gr.Markdown() # Training starten via knop train_btn.click( fn=sgd_train_generator, inputs=[lr, epochs, batch, seed, split_seed], outputs=[plot_main, plot_loss, results] ) # Auto-train bij laden met default-waarden demo.load( fn=sgd_train_generator, inputs=[lr, epochs, batch, seed, split_seed], outputs=[plot_main, plot_loss, results] ) if __name__ == "__main__": demo.launch()