File size: 7,691 Bytes
1d12e6c
5217123
 
 
 
 
 
337b68c
 
 
 
 
 
 
 
 
 
 
 
 
5217123
1d12e6c
 
 
 
 
 
 
 
 
9975da0
1d12e6c
 
 
 
 
 
9975da0
1d12e6c
 
 
 
 
 
 
9975da0
 
 
 
 
 
 
337b68c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15de49c
337b68c
5217123
9975da0
337b68c
 
5217123
 
337b68c
 
 
 
5217123
 
337b68c
5217123
 
 
 
 
 
 
337b68c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5217123
337b68c
9975da0
337b68c
 
 
5217123
 
 
337b68c
 
 
 
 
 
5217123
 
337b68c
5217123
 
 
9975da0
337b68c
 
 
9975da0
 
 
 
 
337b68c
 
460491a
337b68c
 
 
 
 
 
 
 
 
 
 
 
460491a
 
9975da0
337b68c
 
 
 
 
460491a
337b68c
 
 
460491a
337b68c
 
9975da0
337b68c
 
 
460491a
337b68c
5217123
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

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()