File size: 4,673 Bytes
d32737a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9dde5d4
d32737a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b672a8
cbd6f7b
 
9b672a8
cbd6f7b
9b672a8
 
 
 
 
 
d32737a
 
 
 
9b672a8
d32737a
9b672a8
d32737a
 
 
 
 
 
 
 
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
/* Function plotter — pick an activation/curve, drag the input x, and watch z = w·x + b feed through
 * f(z); the curve draws itself and a tracer marks (x, f(x)). Mirrors the Manim `plot` animation. */
(function () {
  const k = MANIMO.kit;
  const FNS = {
    sigmoid: { f: (z) => 1 / (1 + Math.exp(-z)), tex: "σ(z) = 1 / (1 + e⁻ᶻ)", yr: [0, 1] },
    tanh: { f: (z) => Math.tanh(z), tex: "tanh(z)", yr: [-1, 1] },
    relu: { f: (z) => Math.max(0, z), tex: "ReLU(z) = max(0, z)", yr: [0, 4] },
    decay: { f: (z) => Math.exp(-Math.max(0, z)), tex: "e⁻ᶻ  (loss decay)", yr: [0, 1] },
    linear: { f: (z) => z, tex: "z", yr: [-4, 4] },
  };
  function mount(root, concept, theme) {
    const p = (concept && concept.params) || {};
    let name = FNS[p.shape] ? p.shape : (p.shape === "parabola" ? "relu" : "sigmoid");
    let w = p.w != null ? p.w : 1, b = p.b != null ? p.b : 0, x = 0;
    const cv = document.createElement("canvas"); cv.width = 460; cv.height = 240; cv.className = "plot";
    cv.setAttribute("role", "img"); cv.setAttribute("aria-label", "Activation function plot with a draggable input tracer");
    const readout = k.el("div", { class: "readout" });
    const sel = k.el("select", { class: "sel", onchange: (e) => { name = e.target.value; draw(); } },
      Object.keys(FNS).map((nm) => k.el("option", { value: nm }, [nm])));
    sel.value = name;
    const sx = slider("x", -4, 4, x, (v) => { x = v; draw(); });
    const sw = slider("w", -3, 3, w, (v) => { w = v; draw(); });
    const sb = slider("b", -3, 3, b, (v) => { b = v; draw(); });

    function draw() {
      const fn = FNS[name], W = cv.width, H = cv.height, ctx = cv.getContext("2d");
      const xr = [-4, 4], yr = fn.yr;
      const X = (xv) => (xv - xr[0]) / (xr[1] - xr[0]) * (W - 30) + 20;
      const Y = (yv) => H - 24 - (yv - yr[0]) / (yr[1] - yr[0]) * (H - 40);
      ctx.fillStyle = theme.bg; ctx.fillRect(0, 0, W, H);
      // axes
      ctx.strokeStyle = theme.muted; ctx.lineWidth = 1; ctx.beginPath();
      ctx.moveTo(20, Y(Math.max(yr[0], 0))); ctx.lineTo(W - 8, Y(Math.max(yr[0], 0)));
      ctx.moveTo(X(0), 16); ctx.lineTo(X(0), H - 18); ctx.stroke();
      // curve
      ctx.strokeStyle = theme.accent; ctx.lineWidth = 3; ctx.beginPath();
      for (let i = 0; i <= 200; i++) { const xv = xr[0] + (xr[1] - xr[0]) * i / 200; const z = w * xv + b; const yv = fn.f(z); (i ? ctx.lineTo : ctx.moveTo).call(ctx, X(xv), Y(yv)); }
      ctx.stroke();
      // tracer at x
      const z = w * x + b, y = fn.f(z);
      ctx.fillStyle = theme.accent2; ctx.beginPath(); ctx.arc(X(x), Y(y), 6, 0, 7); ctx.fill();
      ctx.strokeStyle = theme.accent2; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.moveTo(X(x), Y(yr[0])); ctx.lineTo(X(x), Y(y)); ctx.lineTo(X(0), Y(y)); ctx.stroke(); ctx.setLineDash([]);
      readout.innerHTML = "z = w·x + b = " + k.fmt(w) + "·" + k.fmt(x) + " + " + k.fmt(b) + " = <b>" + k.fmt(z) + "</b>  →  " + fn.tex + " = <b>" + k.fmt(y) + "</b>";
    }
    let raf = null;
    function stop() { if (raf) { cancelAnimationFrame(raf); raf = null; } }
    function play() { stop(); const t0 = performance.now(); (function tick(t) { x = -4 + 8 * (((t - t0) / 3400) % 1); sx.set(x); draw(); raf = requestAnimationFrame(tick); })(t0); }
    // one-shot sweep for "Play all": glide x left→right once (slowly, so the curve reads), then signal done.
    function playOnce(done) {
      stop(); const dur = 3400, t0 = performance.now();
      (function tick(t) {
        const u = (t - t0) / dur;
        if (u >= 1) { x = 4; sx.set(x); draw(); raf = null; if (done) done(); return; }
        x = -4 + 8 * u; sx.set(x); draw(); raf = requestAnimationFrame(tick);
      })(t0);
    }

    root.appendChild(k.row([k.el("label", { class: "fld" }, ["function ", sel]), readout], "ctrls"));
    root.appendChild(cv);
    root.appendChild(k.row([sx.node, sw.node, sb.node], "sliders"));
    root.appendChild(k.row([k.button("▶ Sweep x", play), k.button("⏹ Stop", stop, "ghost")], "ctrls"));
    draw();
    return { play: playOnce, stop, reset: () => { stop(); x = 0; sx.set(0); draw(); } };
  }
  function slider(label, lo, hi, val, on) {
    const out = MANIMO.kit.el("span", { class: "sval" }, [MANIMO.kit.fmt(val)]);
    const inp = MANIMO.kit.el("input", { type: "range", min: lo, max: hi, step: 0.05, value: val, oninput: (e) => { out.textContent = MANIMO.kit.fmt(+e.target.value); on(+e.target.value); } });
    return { node: MANIMO.kit.el("label", { class: "fld" }, [label + " ", inp, out]), set: (v) => { inp.value = v; out.textContent = MANIMO.kit.fmt(v); } };
  }
  MANIMO.registry["plot"] = { title: "Function Plot", mount };
})();