Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import gradio as gr | |
| def plot_secant(h): | |
| x = np.linspace(-1, 2, 400) | |
| y = x**2 | |
| m = (h**2) / h | |
| fig, axs = plt.subplots(1, 2, figsize=(8, 4)) | |
| for ax in axs: | |
| ax.set_xlim(-1, 2) | |
| ax.set_ylim(-1, 4) | |
| ax.set_xticks(np.arange(-1, 3, 1)) | |
| ax.set_yticks(np.arange(-1, 5, 1)) | |
| ax.grid(True, linestyle='--', linewidth=0.5, color='lightgray') | |
| ax.spines['top'].set_visible(False) | |
| ax.spines['right'].set_visible(False) | |
| axs[0].plot(x, y, color='black') | |
| axs[0].plot([0, h], [0, h**2], color='red', linewidth=2) | |
| axs[0].scatter([0, h], [0, h**2], color='red', zorder=5) | |
| axs[1].plot(x, m * x, color='red', linewidth=2) | |
| plt.tight_layout() | |
| return fig | |
| def plot_tangent(x0): | |
| x = np.linspace(-1, 2, 400) | |
| y = x**2 | |
| m = 2 * x0 | |
| y0 = x0**2 | |
| fig, axs = plt.subplots(1, 2, figsize=(8, 4)) | |
| for ax in axs: | |
| ax.set_xlim(-1, 2) | |
| ax.set_ylim(-1, 4) | |
| ax.set_xticks(np.arange(-1, 3, 1)) | |
| ax.set_yticks(np.arange(-1, 5, 1)) | |
| ax.grid(True, linestyle='--', linewidth=0.5, color='lightgray') | |
| ax.spines['top'].set_visible(False) | |
| ax.spines['right'].set_visible(False) | |
| axs[0].plot(x, y, color='black') | |
| axs[0].plot(x, m * (x - x0) + y0, color='red', linewidth=2) | |
| axs[0].scatter([x0], [y0], color='red', zorder=5) | |
| axs[1].plot(x, 2 * x, color='black') | |
| axs[1].scatter([x0], [m], color='red', zorder=5) | |
| plt.tight_layout() | |
| return fig | |
| def plot_gradient_descent(lr, init_x, steps): | |
| n = int(steps) | |
| path = [init_x] | |
| for _ in range(n): | |
| path.append(path[-1] - lr * 2 * path[-1]) | |
| xv = np.array(path) | |
| fig, axs = plt.subplots(1, 2, figsize=(8, 4)) | |
| x_plot = np.linspace(-2, 2, 400) | |
| axs[0].plot(x_plot, x_plot**2, color='black') | |
| axs[0].plot(xv, xv**2, marker='o', color='red', linewidth=2) | |
| for i in range(n): | |
| axs[0].annotate('', xy=(xv[i+1], xv[i+1]**2), xytext=(xv[i], xv[i]**2), arrowprops=dict(arrowstyle='->', color='red')) | |
| axs[0].set_xlim(-2, 2) | |
| axs[0].set_ylim(-0.5, 5) | |
| axs[0].set_title('Gradient Descent Path') | |
| axs[0].grid(True, linestyle='--', linewidth=0.5, color='lightgray') | |
| axs[1].plot(range(n+1), xv, marker='o', color='red', linewidth=2) | |
| for i in range(n): | |
| axs[1].annotate('', xy=(i+1, xv[i+1]), xytext=(i, xv[i]), arrowprops=dict(arrowstyle='->', color='red')) | |
| axs[1].set_xlim(0, n) | |
| axs[1].set_ylim(xv.min() - 0.5, xv.max() + 0.5) | |
| axs[1].set_xticks(range(0, n+1, max(1, n//5))) | |
| axs[1].set_xlabel('Iteration') | |
| axs[1].set_title('x over Iterations') | |
| axs[1].grid(True, linestyle='--', linewidth=0.5, color='lightgray') | |
| plt.tight_layout() | |
| return fig | |
| def plot_chain_network(x): | |
| y = 2 * x | |
| z = 3 * y | |
| L = 4 * z | |
| fig, ax = plt.subplots(figsize=(6, 2)) | |
| ax.axis('off') | |
| pos = {'x': 0.1, 'y': 0.3, 'z': 0.5, 'L': 0.7} | |
| for name in pos: | |
| ax.add_patch(plt.Circle((pos[name], 0.5), 0.05, fill=False)) | |
| ax.text(pos[name], 0.5, name, ha='center', va='center') | |
| for src, dst, lbl in [ | |
| ('x', 'y', r'$\partial y/\partial x=2$'), | |
| ('y', 'z', r'$\partial z/\partial y=3$'), | |
| ('z', 'L', r'$\partial L/\partial z=4$') | |
| ]: | |
| sx, dx = pos[src], pos[dst] | |
| ax.annotate('', xy=(dx, 0.5), xytext=(sx, 0.5), arrowprops=dict(arrowstyle='->')) | |
| ax.text((sx + dx)/2, 0.6, lbl, ha='center', va='center') | |
| ax.text(0.02, 0.15, r'$\frac{\partial L}{\partial x}=4\times3\times2$', transform=ax.transAxes, ha='left') | |
| for name, val in [('x', x), ('y', y), ('z', z), ('L', L)]: | |
| ax.text(pos[name], 0.3, f"{name}={val:.2f}", ha='center') | |
| plt.tight_layout() | |
| return fig | |
| def plot_backprop_dnn(x, w1, w2, t): | |
| a = w1 * x | |
| y = w2 * a | |
| L = 0.5 * (y - t)**2 | |
| fig, ax = plt.subplots(figsize=(6, 2)) | |
| ax.axis('off') | |
| pos = {'x': 0.1, 'a': 0.3, 'y': 0.5, 'L': 0.7} | |
| for name in pos: | |
| ax.add_patch(plt.Circle((pos[name], 0.5), 0.05, fill=False)) | |
| ax.text(pos[name], 0.5, name, ha='center', va='center') | |
| for src, dst, lbl in [ | |
| ('x', 'a', '∂a/∂x = w₁'), | |
| ('a', 'y', '∂y/∂a = w₂'), | |
| ('y', 'L', '∂L/∂y = (y - t)') | |
| ]: | |
| sx, dx = pos[src], pos[dst] | |
| ax.annotate('', xy=(dx, 0.5), xytext=(sx, 0.5), arrowprops=dict(arrowstyle='->')) | |
| ax.text((sx + dx)/2, 0.6, lbl, ha='center', va='center') | |
| ax.text(0.02, 0.15, '∂L/∂w₂ = (y - t) · a', transform=ax.transAxes, ha='left') | |
| ax.text(0.02, 0.02, '∂L/∂w₁ = (y - t) · w₂ · x', transform=ax.transAxes, ha='left') | |
| plt.tight_layout() | |
| return fig | |
| def update_secant(h): return plot_secant(h), f'**Δx=h={h:.4f}**, slope={(h**2)/h:.4f}' | |
| def update_tangent(x0): return plot_tangent(x0), f'**x={x0:.2f}**, dy/dx={2*x0:.2f}' | |
| def update_gd(lr, init_x, steps): return plot_gradient_descent(lr, init_x, steps), f'lr={lr:.2f}, init={init_x:.2f}, steps={int(steps)}' | |
| def update_chain(x): return plot_chain_network(x), f"**Values:** y={2*x:.2f}, z={3*(2*x):.2f}, L={4*(3*(2*x)):.2f}\n**dL/dx=24**" | |
| def update_bp(x, w1, w2, t): return plot_backprop_dnn(x, w1, w2, t), '' | |
| def load_secant(): return plot_secant(0.01), '**Hint:** try moving the slider!' | |
| def load_tangent(): return plot_tangent(0.0), '**Hint:** try moving the slider!' | |
| def load_gd(): return plot_gradient_descent(0.1, 1.0, 10), '**Hint:** try moving the sliders!' | |
| def load_chain(): return plot_chain_network(1.0), '**Hint:** try moving the slider!' | |
| def load_bp(): return plot_backprop_dnn(0.5, 1.0, 1.0, 0.0), '**Hint:** try moving the sliders!' | |
| def reset_all(): return ( | |
| gr.update(value=0.01), gr.update(value=0.0), gr.update(value=0.1), | |
| gr.update(value=1.0), gr.update(value=10), | |
| gr.update(value=1.0), gr.update(value=0.5), gr.update(value=1.0), | |
| gr.update(value=1.0), gr.update(value=0.0) | |
| ) | |
| demo = gr.Blocks() | |
| with demo: | |
| gr.HTML('<div style="border:1px solid lightgray; padding:10px; border-radius:5px">') | |
| with gr.Tabs(): | |
| with gr.TabItem('Secant Approximation'): | |
| gr.Markdown(''' | |
| **Feature:** Explore secant line approximations via Δx/h | |
| - Draws a chord between (x, x²) and (x+h, (x+h)²) | |
| - As h → 0, the chord converges to the true tangent | |
| - Goal: See how (f(x+h)-f(x)) / h approaches the instantaneous derivative | |
| ''') | |
| h = gr.Slider(0.001, 1.0, value=0.01, step=0.001, label='h') | |
| p1 = gr.Plot() | |
| m1 = gr.Markdown() | |
| h.change(update_secant, [h], [p1, m1]) | |
| with gr.TabItem('Tangent Visualization'): | |
| gr.Markdown(''' | |
| **Feature:** Visualize tangent line and instantaneous slope | |
| - Draws the exact tangent at (x, x²) | |
| - Slide x to update both the red line and its numeric slope | |
| - Goal: Grasp the derivative as the “best‐fit” rate of change at one point | |
| ''') | |
| x0 = gr.Slider(-1.0, 2.0, value=0.0, step=0.1, label='x') | |
| p2 = gr.Plot() | |
| m2 = gr.Markdown() | |
| x0.change(update_tangent, [x0], [p2, m2]) | |
| with gr.TabItem('Gradient Descent'): | |
| gr.Markdown(''' | |
| **Feature:** Observe gradient descent on y=x² | |
| - Treats the curve as a valley; you take downhill steps | |
| - Left: path on the curve with arrows; Right: x vs. iteration | |
| - Goal: Feel how step size (learning rate) affects speed and stability toward the minimum | |
| ''') | |
| lr = gr.Slider(0.01, 0.5, value=0.1, step=0.01, label='Learning Rate') | |
| init = gr.Slider(-2.0, 2.0, value=1.0, step=0.1, label='Initial x') | |
| st = gr.Slider(1, 50, value=10, step=1, label='Iterations') | |
| pg = gr.Plot() | |
| mg = gr.Markdown() | |
| for inp in [lr, init, st]: inp.change(update_gd, [lr, init, st], [pg, mg]) | |
| with gr.TabItem('Chain Rule'): | |
| gr.Markdown(''' | |
| **Feature:** Demonstrate the chain rule via a computation graph | |
| - Nodes: x → y=2x → z=3y → L=4z with arrows showing ∂→ | |
| - Multiply local slopes 2 × 3 × 4 = 24 for overall dL/dx | |
| - Goal: Visualize why and how partial derivatives combine to yield the total derivative | |
| ''') | |
| xs = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label='x') | |
| cp = gr.Plot() | |
| cm = gr.Markdown() | |
| xs.change(update_chain, [xs], [cp, cm]) | |
| with gr.TabItem('Backpropagation'): | |
| gr.Markdown(''' | |
| **Feature:** Visualize backpropagation derivatives in a simple DNN | |
| - Network: x → a=w₁x → y=w₂a → L=½(y–t)² with local ∂ annotations | |
| - Tweak x, w₁, w₂, or t and watch error gradients flow backward | |
| - Goal: Demystify how output error propagates to compute each weight’s gradient | |
| ''') | |
| xb = gr.Slider(-2.0, 2.0, value=0.5, step=0.1, label='x') | |
| w1b = gr.Slider(-2.0, 2.0, value=1.0, step=0.1, label='w1') | |
| w2b = gr.Slider(-2.0, 2.0, value=1.0, step=0.1, label='w2') | |
| tb = gr.Slider(-2.0, 2.0, value=0.0, step=0.1, label='t') | |
| pb = gr.Plot() | |
| mb = gr.Markdown() | |
| for inp in [xb, w1b, w2b, tb]: inp.change(update_bp, [xb, w1b, w2b, tb], [pb, mb]) | |
| demo.load(load_secant, [], [p1, m1]) | |
| demo.load(load_tangent, [], [p2, m2]) | |
| demo.load(load_gd, [], [pg, mg]) | |
| demo.load(load_chain, [], [cp, cm]) | |
| demo.load(load_bp, [], [pb, mb]) | |
| with gr.Row(): | |
| reset_btn = gr.Button('Reset to default settings') | |
| gr.HTML("<span style='cursor:help' title='Reset all sliders to defaults.'></span>") | |
| reset_btn.click(reset_all, [], [h, x0, lr, init, st, xs, xb, w1b, w2b, tb]) | |
| gr.HTML('</div>') | |
| demo.launch() |