dr-sandbox/app/web/templates/index.html
Michael Pilosov bb46e5a18d add §0 introduction (default collapsed)
Three short paragraphs framing what the notebook studies: stability of
2-D embeddings under controlled perturbation of the input over time,
the two metrics logged per run, and why the streaming/longitudinal
angle matters for both visualization and downstream classification.
2026-04-22 10:41:24 -06:00

251 lines
9.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>embedding notebook — web1</title>
<link rel="stylesheet" href="/static/style.css?v=7" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3' fill='%231f4e5f'/%3E%3C/svg%3E" />
<script>
(function(){try{
var t=localStorage.getItem('theme');
if(!t)t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';
document.documentElement.setAttribute('data-theme',t);
}catch(e){}})();
</script>
</head>
<body>
<header class="masthead">
<div>
<h1 class="title">embedding notebook <em>&mdash; drift &amp; projection</em></h1>
</div>
<div class="meta">
<span class="dot {% if not deployment_id %}bad{% endif %}"></span>
{% if deployment_id %}prefect · deployment {{ deployment_id[:8] }}{% else %}prefect · unreachable{% endif %}
<a href="/metrics" class="nav-link">metrics &rarr;</a>
<button type="button" class="theme-toggle" id="theme-toggle" aria-label="toggle theme"></button><br/>
<span style="color:var(--faint)">{{ prefect_api }}</span>
</div>
</header>
<details class="dataset-picker intro" id="intro">
<summary>
<span class="picker-meta">
<span class="section-number">§ 0</span>
<span class="picker-title">introduction</span>
<span class="picker-selection">
<span class="lbl">scope</span>
<code>stability of low-dim embeddings under input drift</code>
</span>
</span>
<span class="picker-toggle" aria-hidden="true"></span>
</summary>
<div class="picker-body">
<div class="intro-prose">
<p>
<strong>What this is.</strong> Dimensionality reduction is a workhorse
for both exploratory visualization and downstream prediction, yet the
stability of its output under small perturbations of the input is
rarely examined directly. This notebook takes a narrow, empirical
approach: a three-dimensional point cloud (§&nbsp;1) is perturbed by a
controlled amount at each of a short sequence of timesteps, the
selected reducer (§&nbsp;2) is applied independently to every snapshot,
and the resulting trajectory of two-dimensional embeddings is recorded.
</p>
<p>
<strong>What it measures.</strong> Two stability views are logged
alongside each run and plotted on the
<a href="/metrics">metrics page</a>. Per-timestep travel —
&thinsp;y(t)&thinsp;&minus;&thinsp;y(t&minus;1)&thinsp;‖ —
captures how much the 2-D layout moves between consecutive frames.
<em>k</em>NN retention captures how much of the input-space neighborhood
graph survives projection. Together they separate reducers that are
globally stable but locally noisy from those with the opposite failure
mode.
</p>
<p>
<strong>Why this matters.</strong> A reducer that looks well-behaved on
a single snapshot is not automatically the right tool for a streaming
or longitudinal setting. Used as the substrate for a visualization,
frame-to-frame motion will read as change the user did not request;
used as a feature-extraction step inside a classification pipeline,
drift between training and inference will quietly erode accuracy. The
aim here is to build intuition for those regimes before committing the
reducer to either role.
</p>
</div>
</div>
</details>
<details class="dataset-picker" id="picker" open>
<summary>
<span class="picker-meta">
<span class="section-number">§ 1</span>
<span class="picker-title">input dataset</span>
<span class="picker-selection">
<span class="lbl">generator</span>
<code id="picker-summary-path"></code>
</span>
</span>
<span class="picker-toggle" aria-hidden="true"></span>
</summary>
<div class="picker-body">
<p class="lede">
Six candidate generators for the embedding pipeline. Drag to rotate, scroll to zoom,
<kbd></kbd>&nbsp;<kbd></kbd> or <kbd>1</kbd>&thinsp;&hellip;&thinsp;<kbd>6</kbd> to select.
</p>
<div class="picker-controls">
<span class="ctl-label">n samples</span>
<div class="segmented count-5" role="radiogroup" aria-label="number of samples">
<label><input type="radio" name="n" value="100"><span>100</span></label>
<label><input type="radio" name="n" value="500" checked><span>500</span></label>
<label><input type="radio" name="n" value="1000"><span>1,000</span></label>
<label><input type="radio" name="n" value="2500"><span>2,500</span></label>
<label><input type="radio" name="n" value="5000"><span>5,000</span></label>
</div>
<span class="ctl-label">noise σ</span>
<div class="segmented count-3" role="radiogroup" aria-label="noise σ">
<label><input type="radio" name="j" value="0.001"><span>0.001</span></label>
<label><input type="radio" name="j" value="0.005" checked><span>0.005</span></label>
<label><input type="radio" name="j" value="0.01"><span>0.010</span></label>
</div>
<span class="ctl-label">timesteps</span>
<div class="segmented count-3" role="radiogroup" aria-label="number of timesteps">
<label><input type="radio" name="f" value="12"><span>12</span></label>
<label><input type="radio" name="f" value="24" checked><span>24</span></label>
<label><input type="radio" name="f" value="48"><span>48</span></label>
</div>
</div>
<div class="gallery" id="gallery">
<div class="picker-loading">loading samples&hellip;</div>
</div>
<div class="picker-footer">
<div class="selection">
<span class="lbl">generator</span>
<code id="selected-path"></code>
</div>
<button type="button" class="continue" id="continue-btn" disabled>Continue &rarr;</button>
</div>
</div>
</details>
<main>
<!-- ==================== LEFT: parameter notebook ==================== -->
<section class="col-left">
<form
id="run-form"
hx-post="/submit"
hx-target="#runs-slot"
hx-swap="innerHTML"
hx-indicator="#busy"
>
<!-- Picker-driven hidden fields. Values are written by dataset-picker.js. -->
<input type="hidden" name="dataset_id" id="dataset_id" value="" />
<input type="hidden" name="num_points" id="num_points" value="500" />
<input type="hidden" name="num_timesteps" id="num_timesteps" value="24" />
<input type="hidden" name="jitter_scale" id="jitter_scale" value="0.005" />
<input type="hidden" name="seed" id="seed" value="42" />
<!-- §2 reducer -->
<div class="section">
<div class="section-label">
<span>§ 2 &nbsp; reducer</span><span class="ordinal">method</span>
</div>
<p class="lead">Dimensionality reduction applied to each snapshot. Only reducers whose Python package is importable are shown.</p>
<ul class="reducer-list">
{% for r in reducers %}
<li>
<input type="radio" name="reducer" id="red-{{ loop.index }}" value="{{ r.key }}"
{% if r.key == default_reducer %}checked{% endif %}
hx-get="/reducer-form?name={{ r.key }}"
hx-target="#reducer-params"
hx-swap="innerHTML"
hx-trigger="change" />
<label for="red-{{ loop.index }}">
<span class="mark">{{ "%02d"|format(loop.index) }}</span>
<span>
<span class="name">{{ r.label }}</span>
<span class="blurb">{{ r.blurb }}</span>
</span>
<span class="pkg">{{ r.key }}</span>
</label>
</li>
{% endfor %}
</ul>
</div>
<!-- §3 reducer params -->
<div class="section">
<div class="section-label">
<span>§ 3 &nbsp; parameters</span><span class="ordinal">kwargs</span>
</div>
<div id="reducer-params">
{% include "_reducer_form.html" with context %}
</div>
</div>
<div class="actions">
<button type="submit" class="submit">submit run</button>
<span id="busy" class="htmx-indicator">dispatching&hellip;</span>
</div>
</form>
</section>
<!-- ==================== RIGHT: runs log ============================== -->
<section class="col-right">
<div class="section-label">
<span>§ 4 &nbsp; recent runs</span>
<span class="run-count">
<span id="runs-count">{{ runs|length }}</span> / 10 · refresh 3s
<span id="poll-ind" class="htmx-indicator" style="margin-left:6px">&#9679;</span>
</span>
</div>
<div
id="runs-slot"
hx-get="/runs"
hx-trigger="load delay:3s, every 3s"
hx-swap="innerHTML"
hx-indicator="#poll-ind"
>
{% include "_runs.html" with context %}
</div>
</section>
</main>
<footer class="colophon">
<span><span class="k">web</span> · scientific instrument · port 8001</span>
</footer>
<script src="/static/theme.js?v=7"></script>
<script type="module" src="/static/dataset-picker.js?v=7"></script>
</body>
</html>