Spaces:
Sleeping
Sleeping
| import io | |
| import random | |
| import tempfile | |
| from dataclasses import dataclass | |
| import gradio as gr | |
| import matplotlib | |
| matplotlib.use("Agg") # headless-friendly for Hugging Face Spaces | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import pandas as pd | |
| import torch | |
| import torch.nn as nn | |
| from torch.utils.data import DataLoader, TensorDataset | |
| class DataSpec: | |
| n_samples: int = 1024 | |
| n_features: int = 10 | |
| noise_std: float = 0.3 | |
| train_frac: float = 0.8 | |
| def set_seed(seed: int) -> None: | |
| random.seed(seed) | |
| np.random.seed(seed) | |
| torch.manual_seed(seed) | |
| torch.cuda.manual_seed_all(seed) | |
| def make_synthetic_regression(spec: DataSpec, seed: int = 42): | |
| """ | |
| Create a simple linear regression dataset: | |
| y = X @ w_true + b_true + noise | |
| Shapes: | |
| X: (n_samples, n_features) | |
| y: (n_samples, 1) | |
| """ | |
| set_seed(seed) | |
| # True parameters students can compare against | |
| w_true = torch.randn(spec.n_features, 1) * 2.0 | |
| b_true = torch.randn(1) * 0.5 | |
| X = torch.randn(spec.n_samples, spec.n_features) | |
| noise = torch.randn(spec.n_samples, 1) * spec.noise_std | |
| y = X @ w_true + b_true + noise | |
| # Train/val split | |
| n_train = int(spec.n_samples * spec.train_frac) | |
| X_train, y_train = X[:n_train], y[:n_train] | |
| X_val, y_val = X[n_train:], y[n_train:] | |
| return (X_train, y_train, X_val, y_val, w_true, b_true) | |
| def fig_to_image(fig) -> np.ndarray: | |
| """Convert a matplotlib figure to a numpy RGB image.""" | |
| buf = io.BytesIO() | |
| fig.savefig(buf, format="png", bbox_inches="tight", dpi=160) | |
| plt.close(fig) | |
| buf.seek(0) | |
| image = plt.imread(buf) | |
| return image | |
| def build_full_dataset_df(X_train, y_train, X_val, y_val) -> pd.DataFrame: | |
| """Create a single DataFrame with a 'split' column so it’s easy to teach/train/export.""" | |
| cols = [f"x{i}" for i in range(10)] | |
| train_df = pd.DataFrame(X_train.cpu().numpy(), columns=cols) | |
| train_df["y"] = y_train.cpu().numpy().reshape(-1) | |
| train_df["split"] = "train" | |
| val_df = pd.DataFrame(X_val.cpu().numpy(), columns=cols) | |
| val_df["y"] = y_val.cpu().numpy().reshape(-1) | |
| val_df["split"] = "val" | |
| full_df = pd.concat([train_df, val_df], axis=0, ignore_index=True) | |
| return full_df | |
| def save_df_to_temp_csv(df: pd.DataFrame) -> str: | |
| """Save DataFrame to a temp CSV and return the file path for Gradio download.""" | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", prefix="synthetic_linear_regression_") | |
| df.to_csv(tmp.name, index=False) | |
| return tmp.name | |
| def train_raw_pytorch( | |
| n_samples: int, | |
| noise_std: float, | |
| lr: float, | |
| batch_size: int, | |
| epochs: int, | |
| seed: int, | |
| device_choice: str, | |
| ): | |
| # ---------------------------- | |
| # 1) Data | |
| # ---------------------------- | |
| spec = DataSpec(n_samples=n_samples, n_features=10, noise_std=noise_std, train_frac=0.8) | |
| X_train, y_train, X_val, y_val, w_true, b_true = make_synthetic_regression(spec, seed=seed) | |
| # Full dataset CSV (train + val with split column) | |
| full_df = build_full_dataset_df(X_train, y_train, X_val, y_val).round(4) | |
| csv_path = save_df_to_temp_csv(full_df) | |
| # Data preview (first 20 rows from training split) | |
| preview_n = min(20, X_train.shape[0]) | |
| df_preview = pd.DataFrame( | |
| X_train[:preview_n].cpu().numpy(), | |
| columns=[f"x{i}" for i in range(10)] | |
| ) | |
| df_preview["y"] = y_train[:preview_n].cpu().numpy().reshape(-1) | |
| df_preview = df_preview.round(4) | |
| train_loader = DataLoader( | |
| TensorDataset(X_train, y_train), | |
| batch_size=batch_size, | |
| shuffle=True, | |
| drop_last=False, | |
| ) | |
| val_loader = DataLoader( | |
| TensorDataset(X_val, y_val), | |
| batch_size=batch_size, | |
| shuffle=False, | |
| drop_last=False, | |
| ) | |
| # ---------------------------- | |
| # 2) Model, optimizer, loss | |
| # ---------------------------- | |
| model = nn.Linear(10, 1) | |
| loss_fn = nn.MSELoss() | |
| optimizer = torch.optim.SGD(model.parameters(), lr=lr) | |
| # Device handling (CPU by default; CUDA if available & selected) | |
| if device_choice == "cuda" and torch.cuda.is_available(): | |
| device = torch.device("cuda") | |
| else: | |
| device = torch.device("cpu") | |
| model.to(device) | |
| w_true = w_true.to(device) | |
| b_true = b_true.to(device) | |
| # ---------------------------- | |
| # 3) Raw PyTorch training loop | |
| # ---------------------------- | |
| train_losses = [] | |
| val_losses = [] | |
| for epoch in range(1, epochs + 1): | |
| # ---- training | |
| model.train() | |
| running = 0.0 | |
| seen = 0 | |
| for x, y in train_loader: | |
| x = x.to(device) | |
| y = y.to(device) | |
| optimizer.zero_grad() # (1) reset grads | |
| y_pred = model(x) # (2) forward | |
| loss = loss_fn(y_pred, y) # (3) compute loss | |
| loss.backward() # (4) backprop | |
| optimizer.step() # (5) update weights | |
| batch_size_actual = x.size(0) | |
| running += loss.item() * batch_size_actual | |
| seen += batch_size_actual | |
| avg_train = running / max(seen, 1) | |
| train_losses.append(avg_train) | |
| # ---- validation | |
| model.eval() | |
| running = 0.0 | |
| seen = 0 | |
| with torch.no_grad(): | |
| for x, y in val_loader: | |
| x = x.to(device) | |
| y = y.to(device) | |
| y_pred = model(x) | |
| loss = loss_fn(y_pred, y) | |
| batch_size_actual = x.size(0) | |
| running += loss.item() * batch_size_actual | |
| seen += batch_size_actual | |
| avg_val = running / max(seen, 1) | |
| val_losses.append(avg_val) | |
| # ---------------------------- | |
| # 4) Results for students | |
| # ---------------------------- | |
| # Loss curve plot | |
| fig = plt.figure() | |
| plt.plot(range(1, epochs + 1), train_losses, marker="o", label="train") | |
| plt.plot(range(1, epochs + 1), val_losses, marker="o", label="val") | |
| plt.xlabel("Epoch") | |
| plt.ylabel("MSE Loss") | |
| plt.title("Raw PyTorch Training Loop (Linear Regression)") | |
| plt.grid(True, alpha=0.3) | |
| plt.legend() | |
| loss_plot = fig_to_image(fig) | |
| # Learned parameters vs. true parameters | |
| with torch.no_grad(): | |
| w_learned = model.weight.detach().view(-1, 1) # shape (10,1) | |
| b_learned = model.bias.detach().view(1) | |
| rows = [] | |
| for i in range(10): | |
| rows.append( | |
| { | |
| "feature": f"x{i}", | |
| "w_true": float(w_true[i].item()), | |
| "w_learned": float(w_learned[i].item()), | |
| "abs_error": float(abs(w_true[i].item() - w_learned[i].item())), | |
| } | |
| ) | |
| df_weights = pd.DataFrame(rows) | |
| df_weights["abs_error"] = df_weights["abs_error"].map(lambda v: round(v, 4)) | |
| df_weights["w_true"] = df_weights["w_true"].map(lambda v: round(v, 4)) | |
| df_weights["w_learned"] = df_weights["w_learned"].map(lambda v: round(v, 4)) | |
| df_weights = df_weights.sort_values("abs_error", ascending=False).reset_index(drop=True) | |
| summary = ( | |
| f"Device: {device}\n" | |
| f"Final train loss: {train_losses[-1]:.6f}\n" | |
| f"Final val loss: {val_losses[-1]:.6f}\n\n" | |
| f"True bias (b_true): {float(b_true.item()):.4f}\n" | |
| f"Learned bias (b_learned): {float(b_learned.item()):.4f}\n\n" | |
| f"Dataset CSV includes columns: x0..x9, y, split(train/val)\n" | |
| ) | |
| raw_loop_snippet = """# Raw PyTorch: requires manual training loop | |
| import torch | |
| import torch.nn as nn | |
| model = nn.Linear(10, 1) | |
| optimizer = torch.optim.SGD(model.parameters(), lr=0.01) | |
| loss_fn = nn.MSELoss() | |
| for x, y in dataloader: | |
| optimizer.zero_grad() | |
| y_pred = model(x) | |
| loss = loss_fn(y_pred, y) | |
| loss.backward() | |
| optimizer.step() | |
| """ | |
| # Added csv_path as downloadable artifact | |
| return loss_plot, df_weights, summary, raw_loop_snippet, df_preview, csv_path | |
| with gr.Blocks(title="Raw PyTorch Training Loop Demo") as demo: | |
| gr.Markdown( | |
| """ | |
| # Raw PyTorch Training Loop (Linear Regression) | |
| This Space generates **synthetic data** each run: | |
| \[ | |
| y = Xw + b + \\text{noise} | |
| \] | |
| Go to **Data Preview** to see sample rows and **download the full dataset** as CSV. | |
| """ | |
| ) | |
| with gr.Row(): | |
| n_samples = gr.Slider(256, 8192, value=1024, step=256, label="Number of samples") | |
| noise_std = gr.Slider(0.0, 2.0, value=0.3, step=0.05, label="Noise (std dev)") | |
| with gr.Row(): | |
| lr = gr.Slider(1e-4, 1.0, value=0.01, step=1e-4, label="Learning rate (SGD)") | |
| batch_size = gr.Dropdown([16, 32, 64, 128, 256], value=64, label="Batch size") | |
| with gr.Row(): | |
| epochs = gr.Slider(1, 50, value=10, step=1, label="Epochs") | |
| seed = gr.Number(value=42, precision=0, label="Random seed") | |
| device_choice = gr.Radio(["cpu", "cuda"], value="cpu", label="Device (cuda only if available)") | |
| run_btn = gr.Button("Train Model", variant="primary") | |
| with gr.Tab("Outputs"): | |
| loss_img = gr.Image(label="Loss Curve", type="numpy") | |
| weights_df = gr.Dataframe(label="Weights: True vs Learned (sorted by abs error)", wrap=True) | |
| summary_txt = gr.Textbox(label="Summary", lines=10) | |
| with gr.Tab("Data Preview"): | |
| data_preview = gr.Dataframe(label="First 20 rows of generated TRAIN data (X features + y)", wrap=True) | |
| download_file = gr.File(label="Download full dataset CSV (train + val)") | |
| with gr.Tab("Raw Loop Snippet"): | |
| snippet = gr.Code(label="Your original loop (as runnable reference)", language="python") | |
| run_btn.click( | |
| fn=train_raw_pytorch, | |
| inputs=[n_samples, noise_std, lr, batch_size, epochs, seed, device_choice], | |
| outputs=[loss_img, weights_df, summary_txt, snippet, data_preview, download_file], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |