"use client"; import { motion } from "framer-motion"; import { useEffect, useState } from "react"; import { COLORS, VizFrame } from "./common"; import { useReducedMotion } from "@/lib/hooks/useReducedMotion"; /** * 1D gradient descent on a clean convex parabola, with the tangent line drawn * at the current point and the update Δθ = -η ∇L visualised as a horizontal * arrow. Auto-iterates and stops near the minimum. */ export function GradDescentExplainer({ width = 760, height = 420, }: { width?: number; height?: number; }) { const padX = 60; const padY = 50; const xMin = -4; const xMax = 4; const yMin = 0; const yMax = 10; const sx = (x: number) => padX + ((x - xMin) / (xMax - xMin)) * (width - padX * 2); const sy = (y: number) => height - padY - ((y - yMin) / (yMax - yMin)) * (height - padY * 2); // L(θ) = (θ - 1)² + 1 const L = (t: number) => (t - 1) ** 2 + 1; const dL = (t: number) => 2 * (t - 1); const [theta, setTheta] = useState(-3); const [lr, setLr] = useState(0.18); const [paused, setPaused] = useState(false); const reduce = useReducedMotion(); useEffect(() => { if (paused || reduce) return; const id = setInterval(() => { setTheta((t) => { if (Math.abs(t - 1) < 0.05) return -3; // restart return t - lr * dL(t); }); }, 220); return () => clearInterval(id); }, [lr, paused, reduce]); const xs = Array.from({ length: 200 }, (_, i) => xMin + (i / 199) * (xMax - xMin)); const path = `M ${xs.map((x) => `${sx(x)},${sy(L(x))}`).join(" L ")}`; // Tangent const slope = dL(theta); const tangentX1 = theta - 1.0; const tangentX2 = theta + 1.0; const tangentY1 = L(theta) + slope * (tangentX1 - theta); const tangentY2 = L(theta) + slope * (tangentX2 - theta); return (
{/* Axes */} θ L(θ) {/* Loss curve */} {/* Tangent line at θ */} {/* Slope label */} ∇L = {slope.toFixed(2)} {/* Δθ horizontal arrow */} {/* Marker */} {/* Minimum */} θ*
); }