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.
251 lines
9.5 KiB
HTML
251 lines
9.5 KiB
HTML
<!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>— drift & 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 →</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 (§ 1) is perturbed by a
|
||
controlled amount at each of a short sequence of timesteps, the
|
||
selected reducer (§ 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 —
|
||
‖ y(t) − y(t−1) ‖ —
|
||
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> <kbd>→</kbd> or <kbd>1</kbd> … <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…</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 →</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 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 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…</span>
|
||
</div>
|
||
|
||
</form>
|
||
|
||
</section>
|
||
|
||
<!-- ==================== RIGHT: runs log ============================== -->
|
||
<section class="col-right">
|
||
|
||
<div class="section-label">
|
||
<span>§ 4 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">●</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>
|