Add interactive HTML pages for planning explainer and spectral explainer
Sync to site1 / sync (push) Has been cancelled
Sync to site1 / sync (push) Has been cancelled
- Created planning_explainer.html to demonstrate orthogonal invariance and optimal planning with interactive controls and visualizations. - Created spectral_explainer.html to illustrate spectral decomposition and linear identifiability, featuring adjustable spectral weights and real-time correlation calculations.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>正交不变性与最优规划 — 交互式探索</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,400;0,600;1,400&family=Sora:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root{--bg:#0e1116;--panel:#161b22;--panel-2:#1b222c;--line:#232b36;--txt:#e6edf3;--txt-2:#8b949e;--txt-3:#5c6672;--linear:#4cc9f0;--linear-soft:rgba(76,201,240,.16);--nl:#f4a261;--nl-soft:rgba(244,162,97,.16);--ok:#56d364;--r:14px;}
|
||||
*{box-sizing:border-box;}html,body{margin:0;height:100%;}
|
||||
body{background:radial-gradient(1200px 700px at 78% -8%,rgba(76,201,240,.06),transparent 60%),var(--bg);color:var(--txt);font-family:'Sora',system-ui,sans-serif;-webkit-font-smoothing:antialiased;overflow:hidden;}
|
||||
.app{height:100vh;display:grid;grid-template-columns:300px 1fr 330px;grid-template-rows:auto 1fr;grid-template-areas:"head head head" "ctrl stage read";gap:18px;padding:22px 26px;}
|
||||
header{grid-area:head;display:flex;align-items:baseline;gap:18px;flex-wrap:wrap;}
|
||||
header .kicker{font:500 12px/1 'JetBrains Mono',monospace;letter-spacing:.32em;color:var(--linear);text-transform:uppercase;}
|
||||
header h1{margin:0;font-family:'Spectral',serif;font-weight:600;font-size:30px;}
|
||||
header h1 em{color:var(--txt-2);font-style:italic;font-weight:400;}
|
||||
header .sub{font-size:13px;color:var(--txt-2);max-width:560px;line-height:1.5;}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);}
|
||||
.controls{grid-area:ctrl;padding:20px;display:flex;flex-direction:column;gap:22px;overflow-y:auto;}
|
||||
.block .label{font:600 11px/1 'JetBrains Mono',monospace;letter-spacing:.18em;color:var(--txt-3);text-transform:uppercase;margin-bottom:12px;}
|
||||
.aval{font:700 40px/1 'JetBrains Mono',monospace;color:var(--linear);}
|
||||
.aval small{font-size:14px;color:var(--txt-2);font-weight:400;}
|
||||
input[type=range]{-webkit-appearance:none;width:100%;height:4px;border-radius:4px;background:linear-gradient(90deg,var(--linear) 0%,var(--linear) var(--fill,25%),#2a323d var(--fill,25%));margin-top:14px;cursor:pointer;}
|
||||
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;border-radius:50%;background:#eaf6fb;border:3px solid var(--linear);box-shadow:0 0 0 4px var(--linear-soft);}
|
||||
input[type=range]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:#eaf6fb;border:3px solid var(--linear);}
|
||||
.hint{font-size:12px;color:var(--txt-3);line-height:1.55;margin-top:10px;}
|
||||
.toggle{display:flex;gap:8px;}
|
||||
.toggle button{flex:1;padding:11px 8px;border-radius:10px;border:1px solid var(--line);background:var(--panel-2);color:var(--txt-2);font:500 12px 'Sora';cursor:pointer;transition:.16s;line-height:1.3;}
|
||||
.toggle button.on{border-color:var(--linear);color:var(--linear);background:var(--linear-soft);}
|
||||
.play{margin-top:4px;display:flex;gap:8px;}
|
||||
.play button{flex:1;padding:10px;border-radius:10px;border:1px solid var(--line);background:var(--panel-2);color:var(--txt);font:500 13px 'Sora';cursor:pointer;}
|
||||
.play button.live{background:var(--linear-soft);border-color:var(--linear);color:var(--linear);}
|
||||
.costcmp{display:flex;flex-direction:column;gap:8px;}
|
||||
.costcmp .row{display:flex;justify-content:space-between;align-items:baseline;padding:9px 12px;border-radius:9px;border:1px solid var(--line);background:var(--panel-2);font:500 13px 'JetBrains Mono',monospace;}
|
||||
.costcmp .row b{font-weight:700;}
|
||||
.stage{grid-area:stage;padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;min-height:0;}
|
||||
.stage .ttl h2{margin:0;font-family:'Spectral',serif;font-weight:600;font-size:18px;}
|
||||
.twin{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:14px;min-height:0;}
|
||||
.canvas-wrap{position:relative;min-height:0;border-radius:10px;overflow:hidden;background:#0b0e13;border:1px solid var(--line);}
|
||||
.canvas-wrap .cap{position:absolute;top:9px;left:13px;font:500 12px 'JetBrains Mono',monospace;color:var(--txt-2);}
|
||||
canvas{display:block;width:100%;height:100%;}
|
||||
.reading{grid-area:read;padding:22px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;}
|
||||
.reading h3{margin:0;font-family:'Spectral',serif;font-weight:600;font-size:17px;}
|
||||
.formula{font:500 13.5px 'JetBrains Mono',monospace;background:#0b0e13;border:1px solid var(--line);border-radius:8px;padding:14px 16px;line-height:1.7;}
|
||||
.formula .hi{color:var(--linear);}.formula .hl{color:var(--nl);}
|
||||
.verdict{margin-top:auto;padding:16px;border-radius:10px;font-family:'Spectral',serif;font-size:14px;line-height:1.6;}
|
||||
.verdict.win{background:linear-gradient(135deg,rgba(86,211,100,.16),transparent 70%);border:1px solid rgba(86,211,100,.4);}
|
||||
.verdict.lose{background:linear-gradient(135deg,var(--nl-soft),transparent 70%);border:1px solid rgba(244,162,97,.4);}
|
||||
.verdict b{color:var(--ok);}
|
||||
.src{font:400 11px 'JetBrains Mono',monospace;color:var(--txt-3);}
|
||||
.navbar{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);display:flex;gap:6px;background:rgba(22,27,34,.9);border:1px solid var(--line);border-radius:30px;padding:6px 10px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:20;}
|
||||
.navbar a{font:500 12px 'JetBrains Mono',monospace;color:var(--txt-2);text-decoration:none;padding:5px 11px;border-radius:20px;}
|
||||
.navbar a:hover{color:var(--linear);}.navbar a.on{background:var(--linear-soft);color:var(--linear);}
|
||||
::-webkit-scrollbar{width:8px;height:8px;}::-webkit-scrollbar-thumb{background:#2a323d;border-radius:8px;}
|
||||
@media (max-width:1080px){.app{grid-template-columns:1fr;grid-template-rows:repeat(4,auto);grid-template-areas:"head" "ctrl" "stage" "read";overflow:auto;height:auto;}body{overflow:auto;}.twin{height:360px;flex:none;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<header>
|
||||
<div style="display:flex;flex-direction:column;gap:6px;">
|
||||
<span class="kicker">LeJEPA · Topic 6 · 定理 4</span>
|
||||
<h1>正交不变性 <em>与最优规划</em></h1>
|
||||
</div>
|
||||
<p class="sub">学到的潜空间 <b style="color:var(--nl)">ẑ = Qz</b> 只是真实空间 <b style="color:var(--linear)">z</b> 的旋转。旋转角 θ 任意,只要代价函数是 <b>O(n)-不变的</b>,两边规划的总代价就完全相同。</p>
|
||||
</header>
|
||||
|
||||
<section class="card controls">
|
||||
<div class="block">
|
||||
<div class="label">旋转角 θ(Q ∈ O(n))</div>
|
||||
<div class="aval"><span id="thTxt">45</span><small>°</small></div>
|
||||
<input id="theta" type="range" min="0" max="180" step="1" value="45" aria-label="旋转角 θ" title="旋转角 θ" />
|
||||
<div class="hint">ẑ = Qz,Q 是旋转 θ 的正交矩阵。无论 θ 多少,点云形状不变(保距离、保角度)。</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="label">代价函数类型</div>
|
||||
<div class="toggle">
|
||||
<button id="btnInv" class="on">O(n)-不变<br>‖z−g‖²(距离)</button>
|
||||
<button id="btnNon">非不变<br>z₁²(坐标)</button>
|
||||
</div>
|
||||
<div class="hint">距离类代价旋转不变 → 定理4成立;坐标类代价依赖方向 → 不适用。</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="label">总规划代价对比</div>
|
||||
<div class="costcmp">
|
||||
<div class="row"><span style="color:var(--linear)">真实空间 J(z)</span><b id="cz" style="color:var(--linear)">—</b></div>
|
||||
<div class="row"><span style="color:var(--nl)">潜空间 J(ẑ)</span><b id="czh" style="color:var(--nl)">—</b></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="label">演示</div>
|
||||
<div class="play"><button id="autoBtn" class="live">⏸ 自动旋转 θ</button></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card stage">
|
||||
<div class="ttl"><h2>两个空间中的最优路径(start → goal)</h2></div>
|
||||
<div class="twin">
|
||||
<div class="canvas-wrap"><div class="cap" style="color:var(--linear)">真实潜空间 z</div><canvas id="left"></canvas></div>
|
||||
<div class="canvas-wrap"><div class="cap" style="color:var(--nl)">学到的潜空间 ẑ = Qz</div><canvas id="right"></canvas></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card reading">
|
||||
<h3>代价等价(核心)</h3>
|
||||
<div class="formula">ℓ(<span class="hl">Qz</span>, a) = ℓ(z, a) <span style="color:var(--txt-3)">(O(n)-不变)</span><br><br>J(a; <span class="hl">Qz₀</span>) = J(a; z₀)<br>→ <span class="hi">â* = a* 且 V̂* = V*</span></div>
|
||||
<h3 style="margin-top:2px;">为什么足够?</h3>
|
||||
<div class="formula">正交变换保持:<br>‖Qz₁−Qz₂‖ = ‖z₁−z₂‖<br>⟨Qz₁,Qz₂⟩ = ⟨z₁,z₂⟩</div>
|
||||
<div class="verdict win" id="verdict"></div>
|
||||
<div class="src">来源:JEPA/math/06_planning_equivalence.md</div>
|
||||
</section>
|
||||
</div>
|
||||
<nav class="navbar" id="nav"></nav>
|
||||
|
||||
<script>
|
||||
const NAV=[["01","Hermite","hermite_explainer.html"],["02","OU·Mehler","ou_mehler_explainer.html"],["03","谱分解","spectral_explainer.html"],["04","高斯唯一","gaussian_uniqueness_explainer.html"],["05","近似界","approx_bound_explainer.html"],["06","规划等价","planning_explainer.html"]];
|
||||
const nav=document.getElementById('nav');NAV.forEach(([n,t,f])=>{const a=document.createElement('a');a.href=f;a.textContent=n+' '+t;if(f==='planning_explainer.html')a.className='on';nav.appendChild(a);});
|
||||
|
||||
let theta=45, invariant=true, auto=true, lastTick=performance.now();
|
||||
// 真实空间的 start/goal(固定)
|
||||
const start=[-1.6,-1.1], goal=[1.7,1.3];
|
||||
function rot(p,deg){const r=deg*Math.PI/180,c=Math.cos(r),s=Math.sin(r);return [c*p[0]-s*p[1], s*p[0]+c*p[1]];}
|
||||
// O(n)-不变代价:路径长度 ‖goal-start‖(直线);非不变:沿路径 z₁² 积分(近似端点平方和)
|
||||
function pathCost(s,g,inv){
|
||||
if(inv){const dx=g[0]-s[0],dy=g[1]-s[1];return Math.sqrt(dx*dx+dy*dy);}
|
||||
// 非不变:对 z₁ 维度的惩罚,沿直线采样 z₁² 平均
|
||||
let sum=0,n=20;for(let i=0;i<=n;i++){const t=i/n;const x=s[0]+(g[0]-s[0])*t;sum+=x*x;}return sum/(n+1);
|
||||
}
|
||||
|
||||
const thEl=document.getElementById('theta'),thTxt=document.getElementById('thTxt');
|
||||
function setTheta(v,fromUser){theta=((v%360)+360)%360;if(theta>180)theta=180;thEl.value=theta;thTxt.textContent=Math.round(theta);thEl.style.setProperty('--fill',(theta/180*100).toFixed(1)+'%');if(fromUser){auto=false;const b=document.getElementById('autoBtn');b.classList.remove('live');b.textContent='▶ 自动旋转 θ';}draw();}
|
||||
thEl.addEventListener('input',e=>setTheta(+e.target.value,true));
|
||||
document.getElementById('btnInv').addEventListener('click',()=>{invariant=true;document.getElementById('btnInv').classList.add('on');document.getElementById('btnNon').classList.remove('on');draw();});
|
||||
document.getElementById('btnNon').addEventListener('click',()=>{invariant=false;document.getElementById('btnNon').classList.add('on');document.getElementById('btnInv').classList.remove('on');draw();});
|
||||
document.getElementById('autoBtn').addEventListener('click',e=>{auto=!auto;e.target.classList.toggle('live',auto);e.target.textContent=auto?'⏸ 自动旋转 θ':'▶ 自动旋转 θ';lastTick=performance.now();});
|
||||
|
||||
const lC=document.getElementById('left'),lx=lC.getContext('2d');
|
||||
const rC=document.getElementById('right'),rx=rC.getContext('2d');
|
||||
function fit(cv){const r=cv.getBoundingClientRect(),dpr=devicePixelRatio||1;cv.width=r.width*dpr;cv.height=r.height*dpr;return{w:r.width,h:r.height,dpr};}
|
||||
|
||||
function drawSpace(ctx,cv,s,g,col,showAxis){
|
||||
const {w,h,dpr}=fit(cv);ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,w,h);
|
||||
const pad=34,x0=pad,x1=w-pad,y0=pad,y1=h-pad,lim=2.6;
|
||||
const sx=z=>x0+(z+lim)/(2*lim)*(x1-x0),sy=z=>y1-(z+lim)/(2*lim)*(y1-y0);
|
||||
ctx.strokeStyle='#1c2530';ctx.lineWidth=1;for(let gd=-2;gd<=2;gd++){ctx.beginPath();ctx.moveTo(sx(gd),y0);ctx.lineTo(sx(gd),y1);ctx.stroke();ctx.beginPath();ctx.moveTo(x0,sy(gd));ctx.lineTo(x1,sy(gd));ctx.stroke();}
|
||||
ctx.strokeStyle='#33404e';ctx.beginPath();ctx.moveTo(x0,sy(0));ctx.lineTo(x1,sy(0));ctx.moveTo(sx(0),y0);ctx.lineTo(sx(0),y1);ctx.stroke();
|
||||
// 非不变代价的方向场(z₁² 等高线 = 竖条阴影)
|
||||
if(!invariant){for(let xx=-2;xx<=2;xx+=0.25){const a=Math.min(.22,xx*xx*0.06);ctx.fillStyle='rgba(244,162,97,'+a+')';ctx.fillRect(sx(xx),y0,(x1-x0)/(2*lim)*0.25,y1-y0);}}
|
||||
// 最优路径直线
|
||||
ctx.strokeStyle=col;ctx.lineWidth=3;ctx.setLineDash([]);ctx.beginPath();ctx.moveTo(sx(s[0]),sy(s[1]));ctx.lineTo(sx(g[0]),sy(g[1]));ctx.stroke();
|
||||
// start/goal
|
||||
ctx.fillStyle='#56d364';ctx.beginPath();ctx.arc(sx(s[0]),sy(s[1]),7,0,7);ctx.fill();ctx.fillStyle='#0b0e13';ctx.font="700 10px 'JetBrains Mono'";ctx.textAlign='center';ctx.fillText('S',sx(s[0]),sy(s[1])+3);
|
||||
ctx.fillStyle=col;ctx.beginPath();ctx.arc(sx(g[0]),sy(g[1]),7,0,7);ctx.fill();ctx.fillStyle='#0b0e13';ctx.fillText('G',sx(g[0]),sy(g[1])+3);ctx.textAlign='left';
|
||||
}
|
||||
|
||||
function draw(){
|
||||
const sR=rot(start,theta), gR=rot(goal,theta);
|
||||
drawSpace(lx,lC,start,goal,'#4cc9f0',true);
|
||||
drawSpace(rx,rC,sR,gR,'#f4a261',true);
|
||||
const cz=pathCost(start,goal,invariant), czh=pathCost(sR,gR,invariant);
|
||||
document.getElementById('cz').textContent=cz.toFixed(3);
|
||||
document.getElementById('czh').textContent=czh.toFixed(3);
|
||||
const eq=Math.abs(cz-czh)<1e-3;
|
||||
const v=document.getElementById('verdict');
|
||||
if(invariant){v.className='verdict win';v.innerHTML='<b>O(n)-不变代价:J(z) = J(ẑ) = '+cz.toFixed(3)+'</b><br>无论旋转角 θ 多少,两空间总代价完全相等 → 最优动作序列 â* = a*。<strong>线性可识别性足以支持最优规划</strong>。';}
|
||||
else{v.className='verdict lose';v.innerHTML='非不变代价 z₁²:J(z) = <b style="color:var(--linear)">'+cz.toFixed(3)+'</b> ≠ J(ẑ) = <b style="color:var(--nl)">'+czh.toFixed(3)+'</b>。<br>代价依赖坐标方向 → 旋转后改变 → <b style="color:var(--nl)">定理4 不适用</b>。这类代价超出 O(n)-不变范围。';}
|
||||
}
|
||||
function loop(now){if(auto){const dt=(now-lastTick)/1000;lastTick=now;let v=theta+dt*30;if(v>180)v=0;setTheta(v,false);}else lastTick=now;requestAnimationFrame(loop);}
|
||||
document.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();document.getElementById('autoBtn').click();}else if(e.key==='ArrowLeft')setTheta(theta-5,true);else if(e.key==='ArrowRight')setTheta(theta+5,true);});
|
||||
window.addEventListener('resize',draw);
|
||||
setTheta(45,false);draw();requestAnimationFrame(loop);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user