Add interactive HTML pages for planning explainer and spectral explainer
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:
gaojie
2026-06-02 05:05:39 +08:00
parent 14ff05d9b8
commit 5de86388db
8 changed files with 1403 additions and 0 deletions
@@ -0,0 +1,197 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>高斯唯一性(Sturm-Liouville)— 交互式探索</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,40%),#2a323d var(--fill,40%));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;}
.preset{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;}
.preset button{flex:1 1 45%;padding:9px 6px;border-radius:8px;border:1px solid var(--line);background:var(--panel-2);color:var(--txt-2);font:500 11px 'Sora';cursor:pointer;}
.preset button:hover{border-color:var(--linear);color:var(--linear);}
.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);}
.badge{display:inline-block;padding:4px 10px;border-radius:20px;font:600 12px 'JetBrains Mono',monospace;}
.stage{grid-area:stage;padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;min-height:0;}
.stage .ttl{display:flex;align-items:baseline;justify-content:space-between;}
.stage .ttl h2{margin:0;font-family:'Spectral',serif;font-weight:600;font-size:18px;}
.stage .ttl span{font:400 12px 'JetBrains Mono',monospace;color:var(--txt-3);}
.grid2{flex:1;display:grid;grid-template-rows: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:400 12px 'JetBrains Mono',monospace;color:var(--txt-3);}
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;}
.r2box{position:relative;height:150px;border-radius:10px;overflow:hidden;background:#0b0e13;border:1px solid var(--line);}
.formula{font:500 13.5px 'JetBrains Mono',monospace;background:#0b0e13;border:1px solid var(--line);border-radius:8px;padding:13px 15px;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;}.grid2{height:520px;flex:none;}}
</style>
</head>
<body>
<div class="app">
<header>
<div style="display:flex;flex-direction:column;gap:6px;">
<span class="kicker">LeJEPA · Topic 4 · 定理 2</span>
<h1>高斯唯一性 <em>Sturm-Liouville</em></h1>
</div>
<p class="sub">扫描广义正态分布的形状参数 <b style="color:var(--linear)">α</b>,看密度 p(z;α) 的<strong>得分函数 (log p)</strong> 是否线性——只有 α=2(高斯)时得分线性,线性可识别性才成立。</p>
</header>
<section class="card controls">
<div class="block">
<div class="label">形状参数 α</div>
<div class="aval"><span id="aTxt">2.00</span></div>
<div id="aName" style="margin-top:8px;"></div>
<input id="alpha" type="range" min="0.6" max="6" step="0.05" value="2" aria-label="形状参数 α" title="形状参数 α" />
<div class="hint">广义正态 p(z;α) ∝ exp(−|z/β|^α),β 调成单位方差。α=1 拉普拉斯,α=2 高斯,α→∞ 均匀。</div>
<div class="preset">
<button data-a="1">α=1 拉普拉斯</button>
<button data-a="2">α=2 高斯 ✓</button>
<button data-a="3">α=3 超高斯</button>
<button data-a="6">α=6 ≈均匀</button>
</div>
</div>
<div class="block">
<div class="label">得分函数线性度</div>
<div id="scoreBadge" class="badge"></div>
<div class="hint">得分 (log p) = (α/βᵅ)·|z|^{α−1}·sign(z)。仅 α=2 时退化为线性的 −z。</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>密度与得分函数</h2><span>α=2 是唯一让得分变直线的点</span></div>
<div class="grid2">
<div class="canvas-wrap"><div class="cap">密度 p(z; α)</div><canvas id="density"></canvas></div>
<div class="canvas-wrap"><div class="cap">得分函数 (log p)′(z) — 直线 = 高斯</div><canvas id="score"></canvas></div>
</div>
</section>
<section class="card reading">
<h3>充要链条</h3>
<div class="formula">p 高斯<br>&nbsp;⟺ 得分 (log p) <span class="hi">线性</span><br>&nbsp;⟺ 第一特征函数仿射<br>&nbsp;<span class="hi">线性可识别性成立</span></div>
<h3 style="margin-top:2px;">线性可识别性 R²(α)</h3>
<div class="r2box"><canvas id="r2"></canvas></div>
<div class="verdict win" id="verdict"></div>
<div class="src">来源:JEPA/math/04_sturm_liouville_uniqueness.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==='gaussian_uniqueness_explainer.html')a.className='on';nav.appendChild(a);});
let alpha=2, auto=true, dir=1, lastTick=performance.now();
function lgamma(x){const g=7,c=[0.99999999999980993,676.5203681218851,-1259.1392167224028,771.32342877765313,-176.61502916214059,12.507343278686905,-0.13857109526572012,9.9843695780195716e-6,1.5056327351493116e-7];if(x<0.5)return Math.log(Math.PI/Math.sin(Math.PI*x))-lgamma(1-x);x-=1;let a=c[0];const t=x+g+0.5;for(let i=1;i<g+2;i++)a+=c[i]/(x+i);return 0.5*Math.log(2*Math.PI)+(x+0.5)*Math.log(t)-t+Math.log(a);}
function beta_(a){return Math.exp(0.5*(lgamma(1/a)-lgamma(3/a)));}// unit variance scale
function density(z,a){const b=beta_(a);const norm=a/(2*b*Math.exp(lgamma(1/a)));return norm*Math.exp(-Math.pow(Math.abs(z/b),a));}
function score(z,a){const b=beta_(a);if(Math.abs(z)<1e-6)return 0;return -(a/Math.pow(b,a))*Math.pow(Math.abs(z),a-1)*Math.sign(z);}
// R²(α): 启发式建模——得分线性度。线性回归 score≈k·z 的 R²
function r2OfAlpha(a){
let sx=0,sy=0,sxx=0,sxy=0,syy=0,n=0;
for(let z=-3;z<=3;z+=0.05){const s=score(z,a);const wgt=density(z,a);sx+=z*wgt;sy+=s*wgt;sxx+=z*z*wgt;sxy+=z*s*wgt;syy+=s*s*wgt;n+=wgt;}
const mx=sx/n,my=sy/n;const cov=sxy/n-mx*my,vx=sxx/n-mx*mx,vy=syy/n-my*my;
const r=cov/Math.sqrt(vx*vy+1e-12);return Math.max(0,Math.min(1,r*r));
}
const alphaEl=document.getElementById('alpha'),aTxt=document.getElementById('aTxt'),aName=document.getElementById('aName');
function nameOf(a){if(a<0.9)return['重尾','#e76f51'];if(a<1.2)return['拉普拉斯 (α≈1)','#f4a261'];if(a<1.7)return['亚高斯','#f4a261'];if(a<2.3)return['高斯 (α=2) ✓','#56d364'];if(a<4)return['超高斯','#f4a261'];return['≈ 均匀','#e76f51'];}
function setAlpha(v,fromUser){alpha=Math.max(0.6,Math.min(6,v));alphaEl.value=alpha;aTxt.textContent=alpha.toFixed(2);alphaEl.style.setProperty('--fill',((alpha-0.6)/(6-0.6)*100).toFixed(1)+'%');const[nm,col]=nameOf(alpha);aName.innerHTML='<span class="badge" style="background:'+col+'22;color:'+col+'">'+nm+'</span>';if(fromUser){auto=false;const b=document.getElementById('autoBtn');b.classList.remove('live');b.textContent='▶ 自动扫描 α';}draw();}
alphaEl.addEventListener('input',e=>setAlpha(+e.target.value,true));
document.querySelectorAll('.preset button').forEach(b=>b.addEventListener('click',()=>setAlpha(+b.dataset.a,true)));
document.getElementById('autoBtn').addEventListener('click',e=>{auto=!auto;e.target.classList.toggle('live',auto);e.target.textContent=auto?'⏸ 自动扫描 α':'▶ 自动扫描 α';lastTick=performance.now();});
const dC=document.getElementById('density'),dx=dC.getContext('2d');
const sC=document.getElementById('score'),sx2=sC.getContext('2d');
const rC=document.getElementById('r2'),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 drawDensity(){const {w,h,dpr}=fit(dC);dx.setTransform(dpr,0,0,dpr,0,0);dx.clearRect(0,0,w,h);
const padL=20,padR=16,padT=22,padB=22,x0=padL,x1=w-padR,y0=padT,y1=h-padB,lim=3.4;
const sx=z=>x0+(z+lim)/(2*lim)*(x1-x0);
let mx=0;for(let z=-lim;z<=lim;z+=0.05)mx=Math.max(mx,density(z,alpha));
const sy=v=>y1-v/(mx*1.1)*(y1-y0);
dx.strokeStyle='#1c2530';dx.lineWidth=1;for(let g=-3;g<=3;g++){dx.beginPath();dx.moveTo(sx(g),y0);dx.lineTo(sx(g),y1);dx.stroke();}
// 高斯参考
dx.strokeStyle='rgba(86,211,100,.35)';dx.setLineDash([5,4]);dx.beginPath();for(let i=0,z=-lim;i<=200;i++,z=-lim+2*lim*i/200){const py=sy(density(z,2));i?dx.lineTo(sx(z),py):dx.moveTo(sx(z),py);}dx.stroke();dx.setLineDash([]);
// 当前密度
const isG=Math.abs(alpha-2)<0.08;dx.strokeStyle=isG?'#56d364':'#4cc9f0';dx.lineWidth=2.5;dx.shadowColor=dx.strokeStyle;dx.shadowBlur=8;dx.beginPath();for(let i=0,z=-lim;i<=240;i++,z=-lim+2*lim*i/240){const py=sy(density(z,alpha));i?dx.lineTo(sx(z),py):dx.moveTo(sx(z),py);}dx.stroke();dx.shadowBlur=0;
}
function drawScore(){const {w,h,dpr}=fit(sC);sx2.setTransform(dpr,0,0,dpr,0,0);sx2.clearRect(0,0,w,h);
const padL=20,padR=16,padT=18,padB=20,x0=padL,x1=w-padR,y0=padT,y1=h-padB,lim=2.8,vlim=4;
const sx=z=>x0+(z+lim)/(2*lim)*(x1-x0),sy=v=>y1-(Math.max(-vlim,Math.min(vlim,v))+vlim)/(2*vlim)*(y1-y0);
sx2.strokeStyle='#1c2530';sx2.lineWidth=1;for(let g=-2;g<=2;g++){sx2.beginPath();sx2.moveTo(sx(g),y0);sx2.lineTo(sx(g),y1);sx2.stroke();}
sx2.strokeStyle='#33404e';sx2.beginPath();sx2.moveTo(x0,sy(0));sx2.lineTo(x1,sy(0));sx2.stroke();
// 高斯参考 -z(线性)
sx2.strokeStyle='rgba(86,211,100,.4)';sx2.setLineDash([5,4]);sx2.lineWidth=1.5;sx2.beginPath();sx2.moveTo(sx(-lim),sy(lim));sx2.lineTo(sx(lim),sy(-lim));sx2.stroke();sx2.setLineDash([]);
sx2.fillStyle='rgba(86,211,100,.7)';sx2.font="11px 'JetBrains Mono',monospace";sx2.fillText('z (线性)',x1-78,sy(-lim)+14);
// 当前 score
const isG=Math.abs(alpha-2)<0.08;sx2.strokeStyle=isG?'#56d364':'#f4a261';sx2.lineWidth=2.5;sx2.shadowColor=sx2.strokeStyle;sx2.shadowBlur=7;sx2.beginPath();let first=true;for(let i=0,z=-lim;i<=240;i++,z=-lim+2*lim*i/240){const v=score(z,alpha);const py=sy(v);if(first){sx2.moveTo(sx(z),py);first=false;}else sx2.lineTo(sx(z),py);}sx2.stroke();sx2.shadowBlur=0;
}
function drawR2(){const {w,h,dpr}=fit(rC);rx.setTransform(dpr,0,0,dpr,0,0);rx.clearRect(0,0,w,h);
const padL=30,padR=14,padT=16,padB=22,x0=padL,x1=w-padR,y0=padT,y1=h-padB,aMin=0.6,aMax=6;
const sx=a=>x0+(a-aMin)/(aMax-aMin)*(x1-x0),sy=v=>y1-v*(y1-y0);
rx.strokeStyle='#1c2530';rx.lineWidth=1;rx.font="10px 'JetBrains Mono',monospace";rx.fillStyle='#5c6672';
[0,0.5,1].forEach(v=>{rx.beginPath();rx.moveTo(x0,sy(v));rx.lineTo(x1,sy(v));rx.stroke();rx.fillText(v.toFixed(1),4,sy(v)+3);});
[1,2,3,4,5,6].forEach(a=>{rx.fillText(a,sx(a)-3,y1+15);});
// 曲线
rx.strokeStyle='#4cc9f0';rx.lineWidth=2;rx.beginPath();for(let i=0;i<=160;i++){const a=aMin+(aMax-aMin)*i/160;const v=r2OfAlpha(a);i?rx.lineTo(sx(a),sy(v)):rx.moveTo(sx(a),sy(v));}rx.stroke();
// α=2 峰标
rx.strokeStyle='rgba(86,211,100,.5)';rx.setLineDash([4,4]);rx.beginPath();rx.moveTo(sx(2),y0);rx.lineTo(sx(2),y1);rx.stroke();rx.setLineDash([]);
rx.fillStyle='#56d364';rx.font="600 10px 'JetBrains Mono',monospace";rx.fillText('α=2 峰值',sx(2)+4,y0+10);
// 当前 α 标记
const cv=r2OfAlpha(alpha);rx.fillStyle=Math.abs(alpha-2)<0.08?'#56d364':'#f4a261';rx.beginPath();rx.arc(sx(alpha),sy(cv),4.5,0,7);rx.fill();
}
function draw(){
drawDensity();drawScore();drawR2();
const r2=r2OfAlpha(alpha), isG=Math.abs(alpha-2)<0.08;
const sb=document.getElementById('scoreBadge');
if(isG){sb.style.background='rgba(86,211,100,.18)';sb.style.color='#56d364';sb.textContent='线性 ✓(−z';}
else{sb.style.background='var(--nl-soft)';sb.style.color='#f4a261';sb.textContent='非线性 ✗';}
const v=document.getElementById('verdict');
if(isG){v.className='verdict win';v.innerHTML='<b>α = 2:得分函数 = z(线性)</b><br>第一特征函数仿射 → 线性可识别性成立,R² ≈ '+r2.toFixed(3)+'。高斯是<strong>唯一</strong>满足此条件的分布。';}
else{v.className='verdict lose';v.innerHTML='当前得分函数<b style="color:var(--nl)">非线性</b>(α≠2),第一特征函数不再仿射 → 线性可识别性失败,R² ≈ <b style="color:var(--nl)">'+r2.toFixed(3)+'</b>。把 α 拨回 <b style="color:var(--ok)">2</b> 看 R² 冲上峰值。';}
}
function loop(now){if(auto){const dt=(now-lastTick)/1000;lastTick=now;let v=alpha+dir*dt*0.9;if(v<=0.6){v=0.6;dir=1;}if(v>=6){v=6;dir=-1;}setAlpha(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')setAlpha(alpha-0.1,true);else if(e.key==='ArrowRight')setAlpha(alpha+0.1,true);});
window.addEventListener('resize',draw);
setAlpha(2,false);draw();requestAnimationFrame(loop);
</script>
</body>
</html>