Spaces:
Running
Running
| """ | |
| Overfitting Explorer β Before/After Comparison with Gradio | |
| ========================================================== | |
| Train a baseline (overfitting) model and a regularized model on Fashion-MNIST | |
| side by side. Choose which regularization techniques to apply and see the | |
| difference in accuracy/loss curves. | |
| """ | |
| # ββ 1. Import Libraries βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| import os | |
| os.environ["KERAS_BACKEND"] = "torch" | |
| import keras | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| import gradio as gr | |
| print(f"Keras version: {keras.__version__}") | |
| print(f"Backend: {keras.backend.backend()}") | |
| # ββ 2. Download and Prepare Fashion-MNIST ββββββββββββββββββββββββββββββββββββ | |
| (x_train_raw, y_train_raw), (x_test_raw, y_test_raw) = keras.datasets.fashion_mnist.load_data() | |
| CLASS_NAMES = [ | |
| "T-shirt/top", "Trouser", "Pullover", "Dress", "Coat", | |
| "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot", | |
| ] | |
| # Normalize to [0, 1] | |
| x_train_all = x_train_raw.astype("float32") / 255.0 | |
| x_test_all = x_test_raw.astype("float32") / 255.0 | |
| # Train/val split | |
| y_train = y_train_raw[:50000] | |
| y_val = y_train_raw[50000:] | |
| y_test = y_test_raw | |
| # Flat versions for dense models | |
| x_train_flat = x_train_all[:50000].reshape(-1, 784) | |
| x_val_flat = x_train_all[50000:].reshape(-1, 784) | |
| x_test_flat = x_test_all.reshape(-1, 784) | |
| # Image versions for CNN/augmentation models | |
| x_train_img = x_train_all[:50000].reshape(-1, 28, 28, 1) | |
| x_val_img = x_train_all[50000:].reshape(-1, 28, 28, 1) | |
| x_test_img = x_test_all.reshape(-1, 28, 28, 1) | |
| print(f"Training set: {x_train_flat.shape} (flat), {x_train_img.shape} (image)") | |
| print(f"Validation set: {x_val_flat.shape}") | |
| print(f"Test set: {x_test_flat.shape}") | |
| # ββ 3. Gradio Progress Callback βββββββββββββββββββββββββββββββββββββββββββββ | |
| class ProgressCallback(keras.callbacks.Callback): | |
| """Keras callback that updates a Gradio progress bar.""" | |
| def __init__(self, progress, total_epochs, label=""): | |
| super().__init__() | |
| self.progress = progress | |
| self.total_epochs = total_epochs | |
| self.label = label | |
| def on_epoch_end(self, epoch, logs=None): | |
| logs = logs or {} | |
| self.progress( | |
| (epoch + 1) / self.total_epochs, | |
| desc=( | |
| f"{self.label} β Epoch {epoch + 1}/{self.total_epochs} | " | |
| f"loss: {logs.get('loss', 0):.4f} | " | |
| f"val_loss: {logs.get('val_loss', 0):.4f} | " | |
| f"val_acc: {logs.get('val_accuracy', 0):.4f}" | |
| ), | |
| ) | |
| # ββ 4. Model Builders βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| LAYER_UNITS = [512, 512, 256, 256, 128] | |
| def build_baseline(): | |
| """Large dense network with NO regularization β will overfit.""" | |
| model = keras.Sequential( | |
| [keras.layers.Input(shape=(784,))] | |
| + [keras.layers.Dense(u, activation="relu") for u in LAYER_UNITS] | |
| + [keras.layers.Dense(10, activation="softmax")] | |
| ) | |
| model.compile( | |
| optimizer="adam", | |
| loss="sparse_categorical_crossentropy", | |
| metrics=["accuracy"], | |
| ) | |
| return model | |
| def build_regularized(use_dropout, use_batchnorm, use_augmentation, | |
| use_l1, use_l2, dropout_rate, l1_factor, l2_factor): | |
| """Build a model with selected regularization techniques.""" | |
| if use_l1 and use_l2: | |
| reg = keras.regularizers.l1_l2(l1=l1_factor, l2=l2_factor) | |
| elif use_l1: | |
| reg = keras.regularizers.l1(l1_factor) | |
| elif use_l2: | |
| reg = keras.regularizers.l2(l2_factor) | |
| else: | |
| reg = None | |
| if use_augmentation: | |
| # CNN model to support spatial data augmentation | |
| layers = [keras.layers.Input(shape=(28, 28, 1))] | |
| # Data augmentation (only active during training) | |
| layers.append(keras.layers.RandomFlip("horizontal")) | |
| layers.append(keras.layers.RandomRotation(0.1)) | |
| # Conv block 1 | |
| layers.append(keras.layers.Conv2D(32, (3, 3), padding="same", | |
| kernel_regularizer=reg)) | |
| if use_batchnorm: | |
| layers.append(keras.layers.BatchNormalization()) | |
| layers.append(keras.layers.Activation("relu")) | |
| layers.append(keras.layers.MaxPooling2D((2, 2))) | |
| if use_dropout: | |
| layers.append(keras.layers.Dropout(dropout_rate * 0.5)) | |
| # Conv block 2 | |
| layers.append(keras.layers.Conv2D(64, (3, 3), padding="same", | |
| kernel_regularizer=reg)) | |
| if use_batchnorm: | |
| layers.append(keras.layers.BatchNormalization()) | |
| layers.append(keras.layers.Activation("relu")) | |
| layers.append(keras.layers.MaxPooling2D((2, 2))) | |
| if use_dropout: | |
| layers.append(keras.layers.Dropout(dropout_rate * 0.5)) | |
| # Dense head | |
| layers.append(keras.layers.Flatten()) | |
| layers.append(keras.layers.Dense(256, kernel_regularizer=reg)) | |
| if use_batchnorm: | |
| layers.append(keras.layers.BatchNormalization()) | |
| layers.append(keras.layers.Activation("relu")) | |
| if use_dropout: | |
| layers.append(keras.layers.Dropout(dropout_rate)) | |
| layers.append(keras.layers.Dense(128, kernel_regularizer=reg)) | |
| if use_batchnorm: | |
| layers.append(keras.layers.BatchNormalization()) | |
| layers.append(keras.layers.Activation("relu")) | |
| if use_dropout: | |
| layers.append(keras.layers.Dropout(dropout_rate * 0.8)) | |
| layers.append(keras.layers.Dense(10, activation="softmax")) | |
| else: | |
| # Dense model (same architecture as baseline) | |
| layers = [keras.layers.Input(shape=(784,))] | |
| for units in LAYER_UNITS: | |
| layers.append(keras.layers.Dense(units, kernel_regularizer=reg)) | |
| if use_batchnorm: | |
| layers.append(keras.layers.BatchNormalization()) | |
| layers.append(keras.layers.Activation("relu")) | |
| if use_dropout: | |
| layers.append(keras.layers.Dropout(dropout_rate)) | |
| layers.append(keras.layers.Dense(10, activation="softmax")) | |
| model = keras.Sequential(layers) | |
| model.compile( | |
| optimizer="adam", | |
| loss="sparse_categorical_crossentropy", | |
| metrics=["accuracy"], | |
| ) | |
| return model | |
| # ββ 5. Training and Comparison ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def train_and_compare(use_dropout, use_batchnorm, use_augmentation, | |
| use_l1, use_l2, use_early_stopping, | |
| dropout_rate, l1_factor, l2_factor, | |
| epochs, progress=gr.Progress()): | |
| """Train baseline and regularized models, return comparison plots.""" | |
| epochs = int(epochs) | |
| # ββ Train baseline (always dense, no regularization) ββββββββββββββββ | |
| progress(0, desc="Training baseline model (no regularization)...") | |
| baseline_model = build_baseline() | |
| baseline_cb = ProgressCallback(progress, epochs, label="Baseline") | |
| baseline_history = baseline_model.fit( | |
| x_train_flat, y_train, | |
| epochs=epochs, batch_size=128, | |
| validation_data=(x_val_flat, y_val), | |
| callbacks=[baseline_cb], | |
| verbose=0, | |
| ) | |
| # ββ Train regularized βββββββββββββββββββββββββββββββββββββββββββββββ | |
| progress(0, desc="Training regularized model...") | |
| reg_model = build_regularized( | |
| use_dropout, use_batchnorm, use_augmentation, | |
| use_l1, use_l2, dropout_rate, l1_factor, l2_factor, | |
| ) | |
| # Choose data format based on augmentation | |
| rx_train = x_train_img if use_augmentation else x_train_flat | |
| rx_val = x_val_img if use_augmentation else x_val_flat | |
| rx_test = x_test_img if use_augmentation else x_test_flat | |
| reg_callbacks = [ProgressCallback(progress, epochs, label="Regularized")] | |
| reg_epochs = epochs | |
| if use_early_stopping: | |
| reg_callbacks.append(keras.callbacks.EarlyStopping( | |
| monitor="val_loss", patience=5, | |
| restore_best_weights=True, verbose=0, | |
| )) | |
| reg_epochs = epochs + 20 # allow extra room for early stopping | |
| reg_history = reg_model.fit( | |
| rx_train, y_train, | |
| epochs=reg_epochs, batch_size=128, | |
| validation_data=(rx_val, y_val), | |
| callbacks=reg_callbacks, | |
| verbose=0, | |
| ) | |
| # ββ Evaluate on test set ββββββββββββββββββββββββββββββββββββββββββββ | |
| progress(1.0, desc="Evaluating on test set...") | |
| b_loss, b_acc = baseline_model.evaluate(x_test_flat, y_test, verbose=0) | |
| r_loss, r_acc = reg_model.evaluate(rx_test, y_test, verbose=0) | |
| bh = baseline_history.history | |
| rh = reg_history.history | |
| # ββ Plot 1: Accuracy and loss comparison ββββββββββββββββββββββββββββ | |
| fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) | |
| b_ep = range(1, len(bh["loss"]) + 1) | |
| r_ep = range(1, len(rh["loss"]) + 1) | |
| ax1.plot(b_ep, bh["accuracy"], "b--", label="Baseline Train", linewidth=1.5) | |
| ax1.plot(b_ep, bh["val_accuracy"], "r--", label="Baseline Val", linewidth=1.5) | |
| ax1.plot(r_ep, rh["accuracy"], "b-", label="Regularized Train", linewidth=2) | |
| ax1.plot(r_ep, rh["val_accuracy"], "r-", label="Regularized Val", linewidth=2) | |
| ax1.set_title("Accuracy: Baseline vs Regularized", fontsize=13) | |
| ax1.set_xlabel("Epoch") | |
| ax1.set_ylabel("Accuracy") | |
| ax1.legend() | |
| ax1.grid(True, alpha=0.3) | |
| ax1.set_ylim([0.7, 1.0]) | |
| ax2.plot(b_ep, bh["loss"], "b--", label="Baseline Train", linewidth=1.5) | |
| ax2.plot(b_ep, bh["val_loss"], "r--", label="Baseline Val", linewidth=1.5) | |
| ax2.plot(r_ep, rh["loss"], "b-", label="Regularized Train", linewidth=2) | |
| ax2.plot(r_ep, rh["val_loss"], "r-", label="Regularized Val", linewidth=2) | |
| ax2.set_title("Loss: Baseline vs Regularized", fontsize=13) | |
| ax2.set_xlabel("Epoch") | |
| ax2.set_ylabel("Loss") | |
| ax2.legend() | |
| ax2.grid(True, alpha=0.3) | |
| fig1.suptitle("Before/After Overfitting Comparison", fontsize=15, fontweight="bold") | |
| plt.tight_layout() | |
| # ββ Plot 2: Individual curves side by side ββββββββββββββββββββββββββ | |
| fig2, axes = plt.subplots(1, 2, figsize=(14, 5)) | |
| # Baseline | |
| ax = axes[0] | |
| ax.plot(b_ep, bh["accuracy"], "b-", label="Train Acc", linewidth=2) | |
| ax.plot(b_ep, bh["val_accuracy"], "r-", label="Val Acc", linewidth=2) | |
| b_gap = bh["accuracy"][-1] - bh["val_accuracy"][-1] | |
| ax.set_title(f"Baseline (No Regularization)\nGap: {b_gap:.4f}", fontsize=12, fontweight="bold") | |
| ax.set_xlabel("Epoch") | |
| ax.set_ylabel("Accuracy") | |
| ax.legend() | |
| ax.grid(True, alpha=0.3) | |
| ax.set_ylim([0.7, 1.0]) | |
| # Regularized | |
| ax = axes[1] | |
| ax.plot(r_ep, rh["accuracy"], "b-", label="Train Acc", linewidth=2) | |
| ax.plot(r_ep, rh["val_accuracy"], "r-", label="Val Acc", linewidth=2) | |
| r_gap = rh["accuracy"][-1] - rh["val_accuracy"][-1] | |
| ax.set_title(f"Regularized\nGap: {r_gap:.4f}", fontsize=12, fontweight="bold") | |
| ax.set_xlabel("Epoch") | |
| ax.set_ylabel("Accuracy") | |
| ax.legend() | |
| ax.grid(True, alpha=0.3) | |
| ax.set_ylim([0.7, 1.0]) | |
| plt.tight_layout() | |
| # ββ Build techniques list βββββββββββββββββββββββββββββββββββββββββββ | |
| techniques = [] | |
| if use_dropout: | |
| techniques.append(f"Dropout (rate={dropout_rate})") | |
| if use_batchnorm: | |
| techniques.append("Batch Normalization") | |
| if use_augmentation: | |
| techniques.append("Data Augmentation (RandomFlip + RandomRotation)") | |
| if use_l1: | |
| techniques.append(f"L1 Regularization (factor={l1_factor})") | |
| if use_l2: | |
| techniques.append(f"L2 Regularization (factor={l2_factor})") | |
| if use_early_stopping: | |
| techniques.append(f"Early Stopping (stopped at epoch {len(rh['loss'])})") | |
| if not techniques: | |
| techniques.append("None selected β regularized model is same as baseline") | |
| # ββ Architecture info βββββββββββββββββββββββββββββββββββββββββββββββ | |
| if use_augmentation: | |
| arch_info = " Baseline: Dense NN (784 β 512 β 512 β 256 β 256 β 128 β 10)\n Regularized: CNN + Dense (28x28x1 β Conv32 β Conv64 β 256 β 128 β 10)" | |
| else: | |
| arch_info = f" Both models: Dense NN (784 β {' β '.join(str(u) for u in LAYER_UNITS)} β 10)" | |
| # ββ Summary text ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| summary = ( | |
| f"MODEL ARCHITECTURE\n" | |
| f"{'β' * 45}\n" | |
| f"{arch_info}\n" | |
| f"\n" | |
| f"TECHNIQUES APPLIED\n" | |
| f"{'β' * 45}\n" | |
| + "\n".join(f" - {t}" for t in techniques) + "\n" | |
| f"\n" | |
| f"BASELINE (Before)\n" | |
| f"{'β' * 45}\n" | |
| f" Test Accuracy: {b_acc:.4f} ({b_acc * 100:.2f}%)\n" | |
| f" Test Loss: {b_loss:.4f}\n" | |
| f" Train Accuracy: {bh['accuracy'][-1]:.4f}\n" | |
| f" Val Accuracy: {bh['val_accuracy'][-1]:.4f}\n" | |
| f" Overfit Gap: {b_gap:.4f}\n" | |
| f"\n" | |
| f"REGULARIZED (After)\n" | |
| f"{'β' * 45}\n" | |
| f" Test Accuracy: {r_acc:.4f} ({r_acc * 100:.2f}%)\n" | |
| f" Test Loss: {r_loss:.4f}\n" | |
| f" Train Accuracy: {rh['accuracy'][-1]:.4f}\n" | |
| f" Val Accuracy: {rh['val_accuracy'][-1]:.4f}\n" | |
| f" Overfit Gap: {r_gap:.4f}\n" | |
| f"\n" | |
| f"IMPROVEMENT\n" | |
| f"{'β' * 45}\n" | |
| f" Test Acc Change: {(r_acc - b_acc) * 100:+.2f}%\n" | |
| f" Gap Reduction: {(b_gap - r_gap) * 100:+.2f}%\n" | |
| ) | |
| return fig1, fig2, summary | |
| # ββ 6. Gradio Interface βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| demo = gr.Interface( | |
| fn=train_and_compare, | |
| inputs=[ | |
| gr.Checkbox(value=True, label="Dropout"), | |
| gr.Checkbox(value=True, label="Batch Normalization"), | |
| gr.Checkbox(value=False, label="Data Augmentation (switches to CNN)"), | |
| gr.Checkbox(value=False, label="L1 Regularization (sparsity)"), | |
| gr.Checkbox(value=True, label="L2 Regularization (weight decay)"), | |
| gr.Checkbox(value=True, label="Early Stopping"), | |
| gr.Slider(minimum=0.1, maximum=0.7, value=0.5, step=0.05, | |
| label="Dropout Rate"), | |
| gr.Slider(minimum=0.000001, maximum=0.001, value=0.00001, step=0.000001, | |
| label="L1 Factor"), | |
| gr.Slider(minimum=0.00001, maximum=0.01, value=0.0001, step=0.00001, | |
| label="L2 Factor"), | |
| gr.Slider(minimum=5, maximum=40, value=20, step=5, | |
| label="Epochs (Baseline)"), | |
| ], | |
| outputs=[ | |
| gr.Plot(label="Before/After Comparison"), | |
| gr.Plot(label="Individual Accuracy Curves"), | |
| gr.Textbox(label="Results Summary", lines=28), | |
| ], | |
| flagging_mode="never", | |
| title="Overfitting Explorer β Fashion-MNIST", | |
| description=( | |
| "Compare a baseline dense NN (no regularization) with a regularized model " | |
| "on Fashion-MNIST. Toggle regularization techniques on/off to see how " | |
| "each one affects overfitting. Enabling Data Augmentation switches the " | |
| "regularized model to a CNN architecture to support spatial transforms." | |
| ), | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |