"""Pictures for the AI Lab. Built for you; this file is not part of the exercises.
python tools/viz.py example
Builds AI01's running example with YOUR engine, then writes and opens
m01_autograd/out/graph.html: an animation of the forward pass, then of
backprop, one node at a time, replaying your own _backward functions.
From your code, after lab step 3 passes:
import sys; sys.path.insert(0, "../tools"); import viz
viz.draw_graph(L, "out/graph.html")
train.py --viz calls plot_training() for the XOR loss curve and decision map.
Everything is one self-contained HTML file with no downloads. It uses only
the attributes your engine's contract promises: data, grad, _prev, _op,
label, and _backward.
"""
import html
import json
import sys
import webbrowser
from pathlib import Path
LAB_ROOT = Path(__file__).resolve().parents[1]
# ----------------------------------------------------------------------
# Graph walking (independent of your backward(), so it can show what yours does)
# ----------------------------------------------------------------------
def _children(v):
seen, out = set(), []
for c in getattr(v, "_prev", ()):
if id(c) not in seen:
seen.add(id(c))
out.append(c)
return out
def topo_order(root):
"""Every node reachable from root, each after all of its children.
Iterative, so deep graphs cannot hit Python's recursion limit."""
order, seen = [], set()
stack = [(root, False)]
while stack:
v, expanded = stack.pop()
if expanded:
order.append(v)
continue
if id(v) in seen:
continue
seen.add(id(v))
stack.append((v, True))
for c in _children(v):
if id(c) not in seen:
stack.append((c, False))
return order
def _name(v, index):
label = getattr(v, "label", "") or ""
if label:
return label
if not _children(v): # an unlabeled leaf is a constant
return f"{v.data:g}"
return f"n{index}"
def _fmt(x):
return f"{x:.4g}"
# ----------------------------------------------------------------------
# The backprop animation
# ----------------------------------------------------------------------
def replay_backward(root):
"""Zero every grad, seed the root, and call each node's _backward in reverse
topological order, recording how much every call added to each child.
Leaves the graph with the same grads a fresh backward() would give."""
order = topo_order(root)
for v in order:
v.grad = 0.0
root.grad = 1.0
index = {id(v): i for i, v in enumerate(order)}
steps = []
for v in reversed(order):
kids = _children(v)
if not kids:
continue # a leaf's _backward does nothing
before = [c.grad for c in kids]
v._backward()
deltas = [(index[id(c)], c.grad - b) for c, b in zip(kids, before)]
steps.append((index[id(v)], deltas, [n.grad for n in order]))
return order, steps
def _layout(order):
"""Columns by longest path from the root (root on the right), rows by the
average row of each node's parents, so edges mostly run straight."""
idx = {id(v): i for i, v in enumerate(order)}
rank = {id(order[-1]): 0}
parents = {id(v): [] for v in order}
for v in reversed(order):
for c in _children(v):
rank[id(c)] = max(rank.get(id(c), 0), rank[id(v)] + 1)
parents[id(c)].append(idx[id(v)])
max_rank = max(rank.values())
columns = {}
for v in order:
columns.setdefault(max_rank - rank[id(v)], []).append(v)
row = {}
for col in sorted(columns, reverse=True):
nodes = columns[col]
nodes.sort(key=lambda v: (sum(row[p] for p in parents[id(v)]) / len(parents[id(v)])
if parents[id(v)] else 0.0))
for r, v in enumerate(nodes):
row[idx[id(v)]] = r - (len(nodes) - 1) / 2
return {idx[id(v)]: (col, row[idx[id(v)]]) for col, nodes in columns.items() for v in nodes}
def draw_graph(root, path="out/graph.html", title="Backprop, one node at a time", open_browser=True):
"""Write an interactive HTML animation of the forward and backward pass
through the graph that ends at `root`. Returns the output path."""
order, steps = replay_backward(root)
pos = _layout(order)
at = {id(v): i for i, v in enumerate(order)} # identity, never ==
names = [_name(v, i) for i, v in enumerate(order)]
nodes = []
for i, v in enumerate(order):
col, r = pos[i]
nodes.append({"id": i, "name": names[i], "op": getattr(v, "_op", "") or "",
"data": v.data, "col": col, "row": r,
"kids": [at[id(c)] for c in _children(v)]})
frames = []
leaves = [i for i, v in enumerate(order) if not _children(v)]
frames.append({"phase": "forward", "shown": leaves, "active": None,
"caption": "Forward pass. Start with the leaves: "
+ ", ".join(f"{names[i]} = {_fmt(order[i].data)}" for i in leaves)})
shown = list(leaves)
for i, v in enumerate(order):
kids = _children(v)
if not kids:
continue
shown = shown + [i]
kid_names = [names[at[id(c)]] for c in kids]
op = getattr(v, "_op", "") or "?"
if len(kids) == 1:
expr = f"{op}({kid_names[0]})" if not op.startswith("**") else f"{kid_names[0]}{op}"
else:
expr = f" {op} ".join(kid_names)
frames.append({"phase": "forward", "shown": list(shown), "active": i,
"caption": f"Forward: {names[i]} = {expr} = {_fmt(v.data)}"})
n = len(order)
grads0 = [0.0] * n
grads0[n - 1] = 1.0
frames.append({"phase": "seed", "shown": list(range(n)), "active": n - 1, "grads": grads0,
"caption": f"Backward pass. Seed: the output's gradient with respect to itself is 1, "
f"so {names[n - 1]}.grad = 1."})
for active, deltas, grads in steps:
sent = ", ".join(f"{'+' if d >= 0 else ''}{_fmt(d)} to {names[c]}" for c, d in deltas)
frames.append({"phase": "backward", "shown": list(range(n)), "active": active,
"deltas": deltas, "grads": grads,
"caption": f"{names[active]}._backward() with {names[active]}.grad = "
f"{_fmt(grads[active])}: sends {sent}."})
frames[-1]["caption"] += " Done: every grad is final."
payload = {"title": title, "nodes": nodes, "frames": frames,
"final": [{"name": names[i], "op": nodes[i]["op"], "data": order[i].data,
"grad": order[i].grad} for i in range(n)]}
return _write(path, title, GRAPH_BODY, payload, open_browser)
# ----------------------------------------------------------------------
# Training pictures: loss curve and decision map
# ----------------------------------------------------------------------
def plot_training(losses, model, xs, ys, path="out/xor.html", open_browser=True, grid=41, lo=-0.5, hi=1.5):
"""Loss per step, plus the model's output over the input square with the
training points on top. Blue means the model says +1, red means -1."""
cells = []
for gy in range(grid):
x2 = lo + (hi - lo) * gy / (grid - 1)
for gx in range(grid):
x1 = lo + (hi - lo) * gx / (grid - 1)
cells.append(model([x1, x2]).data)
points = [{"x": x[0], "y": x[1], "target": t, "pred": model(x).data} for x, t in zip(xs, ys)]
payload = {"losses": list(losses), "grid": grid, "lo": lo, "hi": hi, "cells": cells, "points": points}
return _write(path, "XOR training", TRAIN_BODY, payload, open_browser)
# ----------------------------------------------------------------------
def _write(path, title, body, payload, open_browser):
out = Path(path)
if not out.is_absolute():
out = Path.cwd() / out
out.parent.mkdir(parents=True, exist_ok=True)
page = (PAGE.replace("__TITLE__", html.escape(title))
.replace("__BODY__", body)
.replace("__DATA__", json.dumps(payload).replace("</", "<\\/")))
out.write_text(page, encoding="utf-8")
if open_browser:
webbrowser.open(out.as_uri())
return out
PAGE = r"""<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>__TITLE__</title>
<style>
:root { color-scheme: light;
--surface: #fcfcfb; --panel: #ffffff; --line: #e6e6e3; --text: #0b0b0b; --text2: #52514e; --muted: #8a8a86;
--blue: #2a78d6; --orange: #eb6834; --red: #e34948; --neutral: #f0efec; --ring: #fcfcfb; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { color-scheme: dark;
--surface: #1a1a19; --panel: #232322; --line: #383835; --text: #ffffff; --text2: #c3c2b7; --muted: #8f8e86;
--blue: #3987e5; --orange: #d95926; --red: #e66767; --neutral: #383835; --ring: #1a1a19; } }
:root[data-theme="dark"] { color-scheme: dark;
--surface: #1a1a19; --panel: #232322; --line: #383835; --text: #ffffff; --text2: #c3c2b7; --muted: #8f8e86;
--blue: #3987e5; --orange: #d95926; --red: #e66767; --neutral: #383835; --ring: #1a1a19; }
body { margin: 0; background: var(--surface); color: var(--text); font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif; }
main { max-width: 1180px; margin: 0 auto; padding: 20px 16px 40px; }
h1 { font-size: 20px; margin: 0 0 4px; } p.sub { color: var(--text2); margin: 0 0 14px; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 12px; margin-bottom: 14px; overflow-x: auto; }
.bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
button { font: inherit; padding: 6px 12px; border-radius: 6px; border: 1px solid var(--line); background: var(--surface); color: var(--text); cursor: pointer; }
button:hover { border-color: var(--blue); }
.caption { min-height: 2.9em; padding: 8px 10px; border-left: 3px solid var(--blue); background: var(--surface); color: var(--text); }
.count { color: var(--muted); font-variant-numeric: tabular-nums; }
svg text { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: 13px; fill: var(--text); }
svg .dim { fill: var(--muted); } svg .axis { stroke: var(--line); } svg .tick { fill: var(--text2); font-size: 11px; }
table { border-collapse: collapse; font-variant-numeric: tabular-nums; }
td, th { padding: 4px 12px; border-bottom: 1px solid var(--line); text-align: right; } th { color: var(--text2); font-weight: 600; }
td:first-child, th:first-child { text-align: left; }
details summary { cursor: pointer; color: var(--text2); }
.tip { position: fixed; pointer-events: none; background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; font-size: 13px; display: none; }
.legend { display: flex; gap: 16px; flex-wrap: wrap; color: var(--text2); font-size: 13px; }
.sw { display: inline-block; width: 12px; height: 12px; border-radius: 3px; vertical-align: -1px; margin-right: 5px; }
</style></head>
<body><script>const DATA = __DATA__;</script>
<main>__BODY__</main>
</body></html>"""
GRAPH_BODY = r"""
<h1 id="title"></h1>
<p class="sub">Each box is one Value: its name, its data, and (during backprop) its grad. The circle is the operation that made it.
Arrows run from inputs to the value they helped compute. Use the buttons, or the left and right arrow keys.</p>
<div class="card">
<div class="bar"><button id="prev">◀ Back</button><button id="next">Step ▶</button>
<button id="play">Play</button><button id="reset">Reset</button><span class="count" id="count"></span></div>
<div class="caption" id="caption"></div>
</div>
<div class="card"><svg id="g"></svg></div>
<details class="card"><summary>Table view (final values)</summary><table id="tbl"></table></details>
"""
GRAPH_SCRIPT = r"""
const W = 150, H = 54, CW = 230, RH = 84, PAD = 50;
const svg = document.getElementById('g');
const N = DATA.nodes, F = DATA.frames;
document.getElementById('title').textContent = DATA.title;
const cols = Math.max(...N.map(n => n.col)) + 1;
const rows = N.map(n => n.row);
const rmin = Math.min(...rows), rmax = Math.max(...rows);
const width = cols * CW + PAD, height = (rmax - rmin) * RH + H + 2 * PAD;
svg.setAttribute('width', width); svg.setAttribute('height', height);
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
const X = n => PAD + n.col * CW, Y = n => PAD + (n.row - rmin) * RH;
const NS = 'http://www.w3.org/2000/svg';
const el = (tag, attrs, parent) => { const e = document.createElementNS(NS, tag);
for (const k in attrs) e.setAttribute(k, attrs[k]); (parent || svg).appendChild(e); return e; };
const fmt = x => { const a = Math.abs(x); return (a !== 0 && (a < 1e-3 || a >= 1e5)) ? x.toExponential(2) : (+x.toPrecision(4)).toString(); };
const edges = [];
for (const n of N) for (const k of n.kids) {
const c = N[k]; const x1 = X(c) + W, y1 = Y(c) + H / 2, x2 = X(n) - (n.op ? 32 : 0), y2 = Y(n) + H / 2;
const mx = (x1 + x2) / 2;
const p = el('path', {d: `M${x1},${y1} C${mx},${y1} ${mx},${y2} ${x2},${y2}`, fill: 'none', stroke: 'var(--line)', 'stroke-width': 2});
const t = el('text', {x: mx, y: (y1 + y2) / 2 - 6, 'text-anchor': 'middle', class: 'dim'}); t.style.display = 'none';
edges.push({from: k, to: n.id, path: p, label: t});
}
const box = N.map(n => {
const g = el('g', {});
if (n.op) { el('line', {x1: X(n) - 12, y1: Y(n) + H / 2, x2: X(n), y2: Y(n) + H / 2, stroke: 'var(--line)', 'stroke-width': 2}, g);
el('circle', {cx: X(n) - 22, cy: Y(n) + H / 2, r: 12, fill: 'var(--surface)', stroke: 'var(--muted)', 'stroke-width': 1.5}, g);
const o = el('text', {x: X(n) - 22, y: Y(n) + H / 2 + 4, 'text-anchor': 'middle'}, g); o.textContent = n.op.length > 4 ? n.op.slice(0, 4) : n.op; }
const r = el('rect', {x: X(n), y: Y(n), width: W, height: H, rx: 6, fill: 'var(--panel)', stroke: 'var(--muted)', 'stroke-width': 1.5}, g);
const nm = el('text', {x: X(n) + 10, y: Y(n) + 17, 'font-weight': 700}, g); nm.textContent = n.name;
const d = el('text', {x: X(n) + 10, y: Y(n) + 33}, g);
const gr = el('text', {x: X(n) + 10, y: Y(n) + 48}, g);
return {g, r, d, gr};
});
let k = 0, timer = null;
function render() {
const f = F[k]; const shown = new Set(f.shown);
document.getElementById('caption').textContent = f.caption;
document.getElementById('count').textContent = `frame ${k + 1} of ${F.length}`;
N.forEach((n, i) => { const b = box[i];
b.g.style.opacity = shown.has(i) ? 1 : 0.25;
b.d.textContent = shown.has(i) ? `data ${fmt(n.data)}` : 'data ?';
b.gr.textContent = f.grads ? `grad ${fmt(f.grads[i])}` : '';
b.gr.setAttribute('fill', f.grads && f.grads[i] !== 0 ? 'var(--orange)' : 'var(--text2)');
const act = f.active === i;
b.r.setAttribute('stroke', act ? 'var(--blue)' : 'var(--muted)');
b.r.setAttribute('stroke-width', act ? 3 : 1.5); });
const sending = new Map((f.deltas || []).map(([c, d]) => [c, d]));
for (const e of edges) {
const hot = f.phase === 'backward' && e.to === f.active && sending.has(e.from);
const fwd = f.phase === 'forward' && e.to === f.active;
e.path.setAttribute('stroke', hot ? 'var(--orange)' : fwd ? 'var(--blue)' : 'var(--line)');
e.path.setAttribute('stroke-width', hot || fwd ? 3 : 2);
e.path.setAttribute('stroke-dasharray', hot ? '6 4' : 'none');
e.label.style.display = hot ? '' : 'none';
if (hot) { const d = sending.get(e.from); e.label.textContent = (d >= 0 ? '+' : '') + fmt(d); }
}
}
function go(d) { k = Math.min(F.length - 1, Math.max(0, k + d)); render(); }
document.getElementById('next').onclick = () => go(1);
document.getElementById('prev').onclick = () => go(-1);
document.getElementById('reset').onclick = () => { k = 0; render(); };
document.getElementById('play').onclick = () => {
if (timer) { clearInterval(timer); timer = null; document.getElementById('play').textContent = 'Play'; return; }
document.getElementById('play').textContent = 'Pause';
timer = setInterval(() => { if (k >= F.length - 1) { clearInterval(timer); timer = null;
document.getElementById('play').textContent = 'Play'; } else go(1); }, 1300); };
document.addEventListener('keydown', e => { if (e.key === 'ArrowRight') go(1); if (e.key === 'ArrowLeft') go(-1); });
const tbl = document.getElementById('tbl');
tbl.innerHTML = '<tr><th>value</th><th>op</th><th>data</th><th>grad</th></tr>' +
DATA.final.map(r => `<tr><td>${r.name}</td><td>${r.op || 'leaf'}</td><td>${fmt(r.data)}</td><td>${fmt(r.grad)}</td></tr>`).join('');
render();
"""
TRAIN_BODY = r"""
<h1>XOR: loss and decision map</h1>
<p class="sub">Left: the loss after each gradient-descent step. Right: what the trained network outputs for every input in the square.
Blue means it says +1, red means -1, and gray is the boundary where it is undecided.</p>
<div style="display:flex; gap:14px; flex-wrap:wrap">
<div class="card" style="flex:1 1 420px"><div class="bar"><strong>Loss</strong>
<button id="scale">Linear scale</button><span class="count" id="lossnote"></span></div><svg id="loss"></svg></div>
<div class="card" style="flex:1 1 360px"><div class="bar"><strong>Decision map</strong></div><svg id="map"></svg>
<div class="legend"><span><span class="sw" style="background:var(--blue)"></span>output near +1</span>
<span><span class="sw" style="background:var(--neutral);border:1px solid var(--line)"></span>output near 0</span>
<span><span class="sw" style="background:var(--red)"></span>output near -1</span></div></div>
</div>
<details class="card"><summary>Table view</summary><table id="tbl"></table></details>
<div class="tip" id="tip"></div>
"""
TRAIN_SCRIPT = r"""
const NS = 'http://www.w3.org/2000/svg';
const el = (tag, attrs, parent) => { const e = document.createElementNS(NS, tag);
for (const k in attrs) e.setAttribute(k, attrs[k]); parent.appendChild(e); return e; };
const tip = document.getElementById('tip');
const showTip = (ev, text) => { tip.textContent = text; tip.style.display = 'block';
tip.style.left = (ev.clientX + 14) + 'px'; tip.style.top = (ev.clientY + 12) + 'px'; };
const hideTip = () => { tip.style.display = 'none'; };
const L = DATA.losses; let logScale = true;
function drawLoss() {
const svg = document.getElementById('loss'); svg.innerHTML = '';
const w = 460, h = 280, ml = 58, mr = 14, mt = 12, mb = 36;
svg.setAttribute('viewBox', `0 0 ${w} ${h}`); svg.setAttribute('width', '100%');
const pos = L.filter(v => v > 0);
const lo = logScale ? Math.log10(Math.min(...pos)) : 0, hi = logScale ? Math.log10(Math.max(...pos)) : Math.max(...L);
const tx = i => ml + (w - ml - mr) * i / Math.max(1, L.length - 1);
const ty = v => { const u = logScale ? Math.log10(Math.max(v, 1e-300)) : v; return mt + (h - mt - mb) * (1 - (u - lo) / ((hi - lo) || 1)); };
const ticks = logScale ? (() => { const t = [], a = Math.floor(lo), b = Math.ceil(hi);
const every = Math.max(1, Math.ceil((b - a) / 5)); // at most about 6 labels
for (let e = b; e >= a; e -= every) t.push(Math.pow(10, e)); return t; })()
: [0, 0.25, 0.5, 0.75, 1].map(f => f * hi);
for (const t of ticks) { const y = ty(t); if (y < mt - 1 || y > h - mb + 1) continue;
el('line', {x1: ml, x2: w - mr, y1: y, y2: y, class: 'axis'}, svg);
const tt = el('text', {x: ml - 6, y: y + 4, 'text-anchor': 'end', class: 'tick'}, svg);
tt.textContent = logScale ? (t >= 0.01 ? +t.toPrecision(2) : '1e' + Math.round(Math.log10(t))) : +t.toPrecision(2); }
for (const f of [0, 0.5, 1]) { const i = Math.round(f * (L.length - 1));
const tt = el('text', {x: tx(i), y: h - mb + 18, 'text-anchor': 'middle', class: 'tick'}, svg); tt.textContent = i; }
const xl = el('text', {x: (ml + w - mr) / 2, y: h - 4, 'text-anchor': 'middle', class: 'tick'}, svg); xl.textContent = 'step';
el('path', {d: L.map((v, i) => `${i ? 'L' : 'M'}${tx(i)},${ty(v)}`).join(''), fill: 'none', stroke: 'var(--blue)', 'stroke-width': 2, 'stroke-linejoin': 'round'}, svg);
const cross = el('line', {y1: mt, y2: h - mb, stroke: 'var(--muted)', 'stroke-width': 1, visibility: 'hidden'}, svg);
const dot = el('circle', {r: 5, fill: 'var(--blue)', stroke: 'var(--ring)', 'stroke-width': 2, visibility: 'hidden'}, svg);
const hit = el('rect', {x: ml, y: mt, width: w - ml - mr, height: h - mt - mb, fill: 'transparent'}, svg);
hit.addEventListener('mousemove', ev => { const r = svg.getBoundingClientRect();
const px = (ev.clientX - r.left) * w / r.width; const i = Math.round((px - ml) / (w - ml - mr) * (L.length - 1));
const j = Math.max(0, Math.min(L.length - 1, i));
cross.setAttribute('x1', tx(j)); cross.setAttribute('x2', tx(j)); cross.setAttribute('visibility', 'visible');
dot.setAttribute('cx', tx(j)); dot.setAttribute('cy', ty(L[j])); dot.setAttribute('visibility', 'visible');
showTip(ev, `step ${j} · loss ${L[j].toPrecision(4)}`); });
hit.addEventListener('mouseleave', () => { cross.setAttribute('visibility', 'hidden'); dot.setAttribute('visibility', 'hidden'); hideTip(); });
document.getElementById('scale').textContent = logScale ? 'Linear scale' : 'Log scale';
document.getElementById('lossnote').textContent = `${L[0].toPrecision(3)} → ${L[L.length - 1].toPrecision(3)}` + (logScale ? ' (log scale shows late progress)' : '');
}
document.getElementById('scale').onclick = () => { logScale = !logScale; drawLoss(); };
drawLoss();
function mix(a, b, t) { return a.map((v, i) => Math.round(v + (b[i] - v) * t)); }
function hex(c) { const s = getComputedStyle(document.documentElement).getPropertyValue(c).trim(); return [1, 3, 5].map(i => parseInt(s.slice(i, i + 2), 16)); }
function drawMap() {
const svg = document.getElementById('map'); svg.innerHTML = '';
const S = 360, m = 30, G = DATA.grid, lo = DATA.lo, hi = DATA.hi, cell = (S - 2 * m) / G;
svg.setAttribute('viewBox', `0 0 ${S} ${S}`); svg.setAttribute('width', '100%'); svg.style.maxWidth = S + 'px';
const neu = hex('--neutral'), pos = hex('--blue'), neg = hex('--red');
const px = x => m + (x - lo) / (hi - lo) * (S - 2 * m), py = y => S - m - (y - lo) / (hi - lo) * (S - 2 * m);
DATA.cells.forEach((v, n) => { const gx = n % G, gy = Math.floor(n / G);
const t = Math.max(-1, Math.min(1, v)); const c = t >= 0 ? mix(neu, pos, t) : mix(neu, neg, -t);
const r = el('rect', {x: m + gx * cell, y: S - m - (gy + 1) * cell, width: cell + 0.5, height: cell + 0.5, fill: `rgb(${c})`}, svg);
const x1 = lo + (hi - lo) * gx / (G - 1), x2 = lo + (hi - lo) * gy / (G - 1);
r.addEventListener('mousemove', ev => showTip(ev, `input (${x1.toFixed(2)}, ${x2.toFixed(2)}) → output ${v >= 0 ? '+' : ''}${v.toFixed(3)}`));
r.addEventListener('mouseleave', hideTip); });
for (const t of [0, 0.5, 1]) { const a = el('text', {x: px(t), y: S - 10, 'text-anchor': 'middle', class: 'tick'}, svg); a.textContent = t;
const b = el('text', {x: 14, y: py(t) + 4, 'text-anchor': 'middle', class: 'tick'}, svg); b.textContent = t; }
for (const p of DATA.points) {
el('circle', {cx: px(p.x), cy: py(p.y), r: 10, fill: 'var(--panel)'}, svg); // halo so the point stands off its region
const c = el('circle', {cx: px(p.x), cy: py(p.y), r: 7, fill: p.target > 0 ? 'var(--blue)' : 'var(--red)', stroke: 'var(--text)', 'stroke-width': 1.5}, svg);
c.addEventListener('mousemove', ev => showTip(ev, `(${p.x}, ${p.y}) target ${p.target > 0 ? '+1' : '-1'} prediction ${p.pred.toFixed(3)}`));
c.addEventListener('mouseleave', hideTip); }
}
drawMap();
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', drawMap);
document.getElementById('tbl').innerHTML = '<tr><th>input</th><th>target</th><th>prediction</th></tr>' +
DATA.points.map(p => `<tr><td>(${p.x}, ${p.y})</td><td>${p.target > 0 ? '+1' : '-1'}</td><td>${p.pred.toFixed(3)}</td></tr>`).join('');
"""
# Each page body carries its own script, after the elements it draws into.
GRAPH_BODY = GRAPH_BODY + "\n<script>" + GRAPH_SCRIPT + "</script>"
TRAIN_BODY = TRAIN_BODY + "\n<script>" + TRAIN_SCRIPT + "</script>"
# ----------------------------------------------------------------------
def _example():
"""AI01's running example, built with the learner's own engine."""
sys.path.insert(0, str(LAB_ROOT / "m01_autograd"))
from engine import Value
a, b, c = Value(3.0, label="a"), Value(-2.0, label="b"), Value(5.0, label="c")
e = a * b
e.label = "e"
d = e + b
d.label = "d"
L = d * c
L.label = "L"
L.backward()
return L
def main(argv):
if len(argv) >= 2 and argv[1] == "example":
path = draw_graph(_example(), LAB_ROOT / "m01_autograd" / "out" / "graph.html",
title="AI01 running example: L = (a*b + b) * c")
print("wrote", path)
else:
print(__doc__)
if __name__ == "__main__":
main(sys.argv)