Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/course/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"id": "embedding",
"order": 2,
"title": "编号怎样变成向量?",
"outcome": "跟踪查表得到的向量,并比较方向、距离与其教学边界。",
"status": "planned",
"route": null,
"outcome": "用编号定位词表的一行,取出向量;再用坐标比较距离与方向,区分编号与几何关系。",
"status": "reviewed",
"route": "/learn/embedding/",
"published": false
},
{
Expand Down
43 changes: 43 additions & 0 deletions apps/site/learn/embedding/LESSON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 编号怎样变成向量?

Core question: Given a fixed embedding parameter table E, which vector does Token ID i retrieve?

## Teaching sequence

1. Identify the input Token and its integer address. The default car / ID 1 intentionally contrasts with cat / ID 0.
2. Select exactly one row of the four-row, two-column table, with zero-based indexing.
3. Copy both components unchanged into the output. The original table row remains present: lookup does not consume it or multiply by the index.
4. Construct the vector on equal-scale coordinates. The path first places x₁, then x₂. This is a drawing aid, not a model's computational execution trace.
5. Compare endpoints and directions with fixed cat [2,1]. Car is distance 4, cosine −0.6; dog is distance √0.08 ≈0.283, cosine ≈0.992. ID proximity is not vector proximity.
6. Keep the same table and switch only input ID. Predict E[2], then verify it without changing the teaching table.

## Review from the learner's perspective

The input ID and output vector are visible before starting; a single bubble points at the current addressed row, copied row or plotted endpoint. Rows/words retain their colors. No wheel or mechanical metaphor is reused. Pausing restores configuration; a control never silently changes the table. The source boundary is on the stage and explained in the inspectable model dialog.

Potential misconception: hand-authored cat/dog coordinates look like measured semantic knowledge. Mitigation: always-visible teaching-table notice, explicit untrained data, unnamed axes and statements that distance does not guarantee semantic truth. The scene explains static token lookup, not contextual embeddings or sentence embeddings. It does not assert measured learning gains. The prediction check is a teaching review aid, not a learner study.

## Sources checked 2026-10-27

- PyTorch nn.Embedding official documentation: https://docs.pytorch.org/docs/stable/generated/torch.nn.Embedding.html — fixed dictionary lookup indexed by integers; weight shape (num_embeddings, embedding_dim). No padding/max_norm/training behaviors are simulated here.
- PyTorch word embeddings tutorial: https://docs.pytorch.org/tutorials/beginner/nlp/word_embeddings_tutorial.html — dense vector representation and cosine comparison. Numeric fixtures are original authored teaching values, not quotations or real model output.

## Validation record

Numerical/model checks include independent golden rows, invalid index rejection, unchanged source table after output mutation, golden distances/cosines, symmetry/self identity/positive scaling, and every 0.05-second sample forward and backward. Actual browser and deployment evidence will be recorded after inspection. Do not read candidate status as a production release.


### Browser observations (2026-10-27)

Environment: managed Chrome, desktop 1363×936; same browser with a 390×844 iframe viewport (not a physical phone). Browser actions used actual controls, not app-state injection.

- Full default playback: progressed through copying and coordinates to 44/44, play state false, configuration restored; end state car distance 4.000, cosine −0.600.
- Backward timeline 44→16, keyboard Home→0, next/back step controls, pause with Escape and resume were observed. Values/phase changed together, no accumulated future-state traces.
- ID switch at final state: dog [1.8,1.2], distance .283 and cosine .992; book [−1,−2], distance 4.243 and cosine −.800. Same table retained.
- Wrong quiz answer explained why ID multiplication is incorrect; correct answer and verify returned ID 2 and its unchanged vector in the diagram, with focus restored to the challenge trigger.
- Narrow-screen table, negative-coordinate labels, plot, formulas and dialog inspected. Initial defects: output caption touched last row, and annotation line crossed table/axis labels. Repaired by spacing output and routing the annotation through an empty side corridor; inspected again at desktop and narrow width.
- Reduced-motion manual toggle was exercised at t=13.1: copied row appeared at the stable destination immediately, with ordinary time/steps retained. OS preference initializes the same flag; actual OS preference switching not independently exercised.
- `evals/browser/embedding-lifecycle.html` dispatched controlled visibilitychange events on the real app document. Hidden event paused at ~.05s, restored configuration; visible event did not auto-play or jump time. Cloud-created tabs stayed visible to their document, so this is event-path testing, not a claim of observed OS tab suspension.
- CSS/inert: configuration left the accessibility tree during playback; focus remained on persistent pause. The stage and timeline continued to reflect one sampled state.

Remaining: no physical-phone, screen-reader-device, other-browser-engine or empirical learner study. No claim of proven learning gain, production model embeddings or autostereoscopic hardware. Branch-preview and production verification are recorded in the release log; candidate source alone is not publication.
17 changes: 17 additions & 0 deletions apps/site/learn/embedding/SCENE_SPEC.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "embedding",
"question": "一个 Token 编号如何从固定表中取出向量?",
"audience": "已看过 Token 编号、初识坐标的学习者",
"durationSeconds": 44,
"inputs": {"id": {"integer": true, "min": 0, "max": 3, "default": 1}},
"objects": ["输入 Token 与 ID", "4×2 参数表 E", "被索引的整行", "输出向量", "等尺度二维坐标", "cat 参照向量"],
"relationships": ["e = E[id]", "horizontal = e[0]", "vertical = e[1]", "distance = Euclidean(e, E[0])", "cosine = dot(e,E[0]) / (norm(e)*norm(E[0]))"],
"table": [[2,1],[-2,1],[1.8,1.2],[-1,-2]],
"tokens": ["cat","car","dog","book"],
"mapping": {"color": "Token identity across input/table/output/plot", "axes": "both −3…3, equal units", "tableCellWidth": "layout only, not numerical magnitude", "depth": "decorative bevel, no quantitative perspective"},
"phases": [{"start":0,"name":"input index"},{"start":6,"name":"select row"},{"start":13,"name":"copy unchanged components"},{"start":22,"name":"construct coordinates"},{"start":30,"name":"compare geometry"},{"start":38,"name":"controlled experiment"}],
"experiment": "Keep E fixed; choose ID 1 car then ID 2 dog and compare with ID 0 cat. ID adjacency does not imply vector proximity.",
"transfer": "Predict E[2]; explain row lookup versus duplicating/multiplying ID; verify on same diagram.",
"sources": ["https://docs.pytorch.org/docs/stable/generated/torch.nn.Embedding.html", "https://docs.pytorch.org/tutorials/beginner/nlp/word_embeddings_tutorial.html"],
"simplifications": ["Authored 2D table, not trained or extracted from a model", "No tokenizer executed; IDs are unique to this teaching table", "No position processing, context, attention, training or inference", "Axes are coordinates with no named semantic interpretation", "Geometric similarity does not establish semantic truth", "SVG depth on an ordinary display, not binocular autostereoscopy"]
}
25 changes: 25 additions & 0 deletions apps/site/learn/embedding/app.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {TOKENS,STARTS,DURATION,sample,narration} from './model.mjs';
import {createStage} from './stage.mjs';
const $=s=>document.querySelector(s),$$=s=>[...document.querySelectorAll(s)],motion=matchMedia('(prefers-reduced-motion: reduce)');
let reduced=motion.matches;
const stage=createStage($('#artwork'),{reducedMotion:()=>reduced});
let id=1,time=0,playing=false,raf=0,last=0,lastCopy='',dead=false;
const names=['输入编号','定位一行','取出向量','两个坐标','比较几何','反事实实验'];
function focus(on){$('#app').classList.toggle('is-playing',on);$$('.secondary').forEach(x=>{x.inert=on;x.setAttribute('aria-hidden',String(on));});}
function anchor(){const box=$('.exhibit').getBoundingClientRect(),bubble=$('#callout').getBoundingClientRect(),art=$('#artwork');art.style.top=Math.max(185,bubble.bottom-box.top+13)+'px';const a=art.getBoundingClientRect(),point=stage.projectAnchor();if(!point)return;const x=a.left-box.left+point.x*a.width,y=a.top-box.top+point.y*a.height,cx=Math.max(bubble.width/2+17,Math.min(box.width-bubble.width/2-17,x));$('#callout').style.left=cx+'px';$('#leader').setAttribute('viewBox',`0 0 ${box.width} ${box.height}`);const sy=bubble.bottom-box.top;const side=a.left-box.left+point.bendX*a.width,start=cx-bubble.width/2+10;$('#leader-line').setAttribute('d',point.routeLeft?`M${start} ${sy}C${side} ${sy+12},${side} ${sy+12},${side} ${sy+27}V${y-8}Q${side} ${y},${side+8} ${y}H${x-8}`:`M${cx} ${sy}C${cx} ${sy+20},${x} ${y-25},${x} ${y-8}`);$('#leader-dot').setAttribute('cx',x);$('#leader-dot').setAttribute('cy',y);}
function render(){if(dead)return;const f=sample(time,{id}),c=narration(f),key=JSON.stringify(c);stage.render(f);if(key!==lastCopy){lastCopy=key;$('#copy-eyebrow').textContent=c.eyebrow;$('#copy-title').textContent=c.title;$('#copy-body').textContent=c.body;}$('#phase-label').textContent=`0${f.phase+1} / 06 · ${names[f.phase]}`;$('#counts').textContent=`ID ${id} · ${TOKENS[id]} · 2 维`;$$('[data-id]').forEach(b=>b.setAttribute('aria-pressed',String(+b.dataset.id===id)));$('#timeline').value=time;$('#timeline').style.setProperty('--fill',`${time/DURATION*100}%`);$('#time').textContent=`00:${String(Math.floor(time)).padStart(2,'0')} / 00:44`;$('#play-label').textContent=playing?'暂停':time>=DURATION?'再看一遍':time?'继续讲解':'播放讲解';$('#play-icon').textContent=playing?'Ⅱ':'▶';$('#play').setAttribute('aria-pressed',String(playing));$('#back').disabled=time===0;$('#next').disabled=time===DURATION;$('#play-note').textContent=playing?'配置已收起 · 空格暂停':'六个步骤 · 从编号到向量';$('#challenge').hidden=time<DURATION;anchor();}
function advance(now){if(playing){time=Math.min(DURATION,time+Math.max(0,now-last)/1000);last=now;}}
function stop(capture=true){if(capture)advance(performance.now());playing=false;cancelAnimationFrame(raf);focus(false);render();}
function tick(now){if(!playing)return;advance(now);render();if(time===DURATION)stop(false);else raf=requestAnimationFrame(tick);}
function play(){if(playing){stop();return;}if(time===DURATION)time=0;$('#play').focus({preventScroll:true});playing=true;focus(true);last=performance.now();render();raf=requestAnimationFrame(tick);}
function seek(t){stop();time=Math.max(0,Math.min(DURATION,t));render();}
$('#play').onclick=play;$('#timeline').oninput=e=>seek(+e.target.value);$('#back').onclick=()=>seek(STARTS.filter(t=>t<time-.1).at(-1)??0);$('#next').onclick=()=>seek(STARTS.find(t=>t>time+.1)??DURATION);$('#reset').onclick=()=>{stop();time=0;id=1;render();};
$$('[data-id]').forEach(b=>b.onclick=()=>{stop();id=+b.dataset.id;render();});$('#experiment').onclick=()=>{seek(38);$('#app [data-id="1"]').focus();};
function setReduced(value){reduced=value;$('#reduce-motion').checked=value;$('#app').classList.toggle('reduce-motion',value);render();}
$('#reduce-motion').onchange=e=>setReduced(e.target.checked);motion.addEventListener('change',e=>setReduced(e.matches));
function info(){stop();$('#info').showModal();}$('#about').onclick=info;$('#show-table').onclick=info;$('#close-info').onclick=()=>$('#info').close();
$('#challenge').onclick=()=>{stop();$('#feedback').textContent='';$('#quiz-try').hidden=true;$$('[data-answer]').forEach(b=>b.removeAttribute('aria-pressed'));$('#quiz').showModal();};
$$('[data-answer]').forEach(b=>b.onclick=()=>{$$('[data-answer]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));$('#feedback').textContent=(b.dataset.answer==='row'?'预测正确。':'输出是 [1.8, 1.2]。')+'ID 2 只定位第 2 行,不重复编号,也不乘 2。坐标 (1.8, 1.2) 离 cat 的 (2, 1) 仅 0.283;两者 ID 不相邻。这个静态查表还没有处理上下文。';$('#quiz-try').hidden=false;});
$('#quiz-try').onclick=()=>{$('#quiz').close();id=2;time=44;render();};$('#close-quiz').onclick=()=>$('#quiz').close();
document.addEventListener('visibilitychange',()=>{if(document.hidden&&playing)stop();});document.addEventListener('keydown',e=>{if($('#info').open||$('#quiz').open)return;if(e.key==='Escape')stop();if(e.code==='Space'&&!['INPUT','BUTTON','A'].includes(e.target.tagName)){e.preventDefault();play();}});
const observer=new ResizeObserver(()=>{stage.resize();anchor();});observer.observe($('#artwork'));$('.exhibit').addEventListener('transitionend',anchor);window.addEventListener('pagehide',e=>{stop();if(!e.persisted){dead=true;observer.disconnect();stage.dispose();}});setReduced(reduced);
3 changes: 3 additions & 0 deletions apps/site/learn/embedding/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>编号怎样变成向量? · Explain3D</title><meta name="description" content="用可暂停的查表动画理解 Token Embedding:编号、向量、坐标、距离与方向。"><link rel="stylesheet" href="./style.css"><script type="module" src="./app.mjs"></script></head><body><main class="app" id="app"><header><a class="brand" href="/learn/"><i>e·</i> Explain3D</a><span>AI 原理课堂 / 02</span><button class="quiet" id="about">模型与边界 ↗</button></header><section class="intro secondary"><div><p class="eyebrow">AN ADDRESS BECOMES A VECTOR</p><h1>编号,<span>怎样变成向量?</span></h1></div><p class="intro-copy">一个索引,取出一整行;两个数,定位一个向量。</p></section><section class="controls secondary" aria-label="Embedding 实验"><div class="presets" role="group" aria-label="选择输入 Token"><button data-id="0">0 · cat</button><button data-id="1">1 · car</button><button data-id="2">2 · dog</button><button data-id="3">3 · book</button></div><span class="divider"></span><span class="hint">固定教学表 · 只改变输入 ID</span><button id="experiment" class="experiment">比较相邻编号 ↗</button></section><section class="exhibit"><div class="exhibit-meta"><span id="phase-label"></span><span id="counts"></span></div><div class="callout" id="callout" role="status" aria-live="polite"><span id="copy-eyebrow"></span><h2 id="copy-title"></h2><p id="copy-body"></p><button id="show-table" class="calculation">查表与公式 +</button></div><svg class="leader" id="leader" aria-hidden="true"><path id="leader-line"/><circle id="leader-dot" r="4"/></svg><div class="artwork" id="artwork"></div><div class="stage-bottom"><span>手写二维教学表 · 静态词元向量 · 非真实模型</span><button class="quiet" id="challenge" hidden>检验理解 ↗</button></div></section><div class="transport"><button id="play" class="play"><span id="play-icon">▶</span><span id="play-label">播放讲解</span></button><button id="back" class="quiet" aria-label="上一步">←</button><div class="track"><input type="range" id="timeline" min="0" max="44" step="0.05" value="0" aria-label="演示进度"><div><span id="play-note">六个步骤 · 从编号到向量</span><span id="time">00:00 / 00:44</span></div></div><button id="next" class="quiet" aria-label="下一步">→</button><button id="reset" class="quiet">重置</button></div><footer class="secondary"><span>Explain3D · AI 原理课堂</span><span>ID 是索引 · E ∈ ℝ⁴ˣ²</span></footer></main>
<dialog id="info" aria-labelledby="info-title"><button class="close" id="close-info" aria-label="关闭模型说明">×</button><p class="eyebrow">ONE ROW, TWO COORDINATES</p><h2 id="info-title">查表不是“把编号算成词义”。</h2><p>输入是整数索引;输出是 Embedding 参数矩阵中对应的整行。这里从 0 开始编号,E 的形状为 4 × 2。固定表下,同一个 ID 每次得到同一向量。后续的位置处理、Attention 和网络层会进一步改变表示,本页不演示它们。</p><div class="formula">0 · cat → [ 2.0, 1.0]<br>1 · car → [-2.0, 1.0]<br>2 · dog → [ 1.8, 1.2]<br>3 · book → [-1.0, -2.0]</div><p>这些 Token、ID 和数值全部手写,只为看清查表和几何关系;没有训练或调用真实模型。真实模型通常从训练中学习这些参数,维度也通常更高。本页的 x₁、x₂ 没有“动物”“速度”等预设语义;距离和余弦也不能保证真实世界中的词义关系。</p><div class="formula">e = E[id]<br>距离 = √((a₁ − b₁)² + (a₂ − b₂)²)<br>方向余弦 = (a₁b₁ + a₂b₂) / (‖a‖ ‖b‖)</div><p>图中坐标轴同尺度,范围均为 −3…3;箭头指向真实坐标。颜色只标识同一个 Token;表格单元格、输入卡片的宽度不表示数值大小。查表没有混合其他 Token,也没有根据上下文消除歧义。</p><p><label><input type="checkbox" id="reduce-motion"> 减少动态效果(保留分步讲解与进度)</label></p><p><a href="https://docs.pytorch.org/docs/stable/generated/torch.nn.Embedding.html" target="_blank" rel="noopener">PyTorch Embedding 官方说明 ↗</a> · <a href="https://docs.pytorch.org/tutorials/beginner/nlp/word_embeddings_tutorial.html" target="_blank" rel="noopener">词向量与相似度官方教程 ↗</a></p></dialog>
<dialog id="quiz" aria-labelledby="quiz-title"><button class="close" id="close-quiz" aria-label="关闭学习检验">×</button><p class="eyebrow">PREDICT · TRY · EXPLAIN</p><h2 id="quiz-title">ID 变成 2,会取出什么?</h2><p>保持同一张表。已知 E 的第 2 行是 [1.8, 1.2]。选择 dog(ID 2),静态 Embedding 输出是什么?</p><div class="challenge-answers"><button data-answer="row">[1.8, 1.2] · 取出第 2 行</button><button data-answer="id">[2, 2] · 重复编号</button><button data-answer="multiply">[3.6, 2.4] · 第 2 行乘 2</button></div><p id="feedback" role="status" aria-live="polite"></p><button class="experiment" id="quiz-try" hidden>在图中验证并比较 ↗</button></dialog></body></html>
Loading
Loading