▶ 展示 · 回索引

08-bandwidth-regimes.html

<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>KP14 08 Bandwidth Regimes</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css"><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script><style>
:root{--bg:#071521;--panel:#102638;--panel2:#133149;--ink:#f7fbff;--muted:#aac1d2;--cyan:#4fd1c5;--amber:#f6c85f;--coral:#ff7b72;--lime:#8fd694;--violet:#b794f4;--grid:#17384d}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;background:#02080d;color:var(--ink);font-family:Inter,"Noto Sans TC","Microsoft JhengHei",sans-serif}.wrap{width:100vw;height:100vh;display:grid;place-items:center;background:radial-gradient(circle at 18% 0%,#10344d 0,#02080d 58%,#000 100%)}.stage{position:relative;width:min(100vw,177.7778vh);height:min(56.25vw,100vh);aspect-ratio:16/9;background:var(--bg);overflow:hidden}canvas{position:absolute;inset:0;width:100%;height:100%;touch-action:none}#math-layer{position:absolute;left:0;top:0;width:1600px;height:900px;transform-origin:top left;pointer-events:none;z-index:3}.formula{position:absolute;line-height:1;display:flex;align-items:flex-start;justify-content:flex-start}.formula.center{justify-content:center;text-align:center}.formula .katex-display{margin:0;width:100%}.hint{position:absolute;right:2.1%;top:1.9%;font-size:.78vw;color:#9ab4c7;background:#071521cc;border:1px solid #ffffff18;border-radius:999px;padding:.45em .75em;z-index:5}.note{position:absolute;left:2.1%;bottom:2.2%;font-size:.72vw;color:#89a3b8;background:#071521b8;border:1px solid #ffffff12;border-radius:12px;padding:.42em .65em;z-index:5}@media(max-width:900px){.hint,.note{font-size:1.28vw}}</style></head><body><div class="wrap"><div class="stage" id="stage"><canvas id="c" width="1600" height="900"></canvas><div id="math-layer"></div><div class="hint">互動:點擊 / 拖曳 • Click / Drag</div><div class="note">PR115 · KP14 Nonparametric Estimation · Canvas + KaTeX</div></div></div><script>
const stage=document.getElementById('stage'),c=document.getElementById('c'),ctx=c.getContext('2d'),mathLayer=document.getElementById('math-layer'),W=1600,H=900;const P={bg:'#071521',panel:'#102638',panel2:'#133149',ink:'#f7fbff',muted:'#aac1d2',cyan:'#4fd1c5',amber:'#f6c85f',coral:'#ff7b72',lime:'#8fd694',violet:'#b794f4',grid:'#17384d'};let zones=[],drag=null,formulaNodes=[];function syncMathLayer(){const r=stage.getBoundingClientRect();mathLayer.style.transform=`scale(${r.width/1600},${r.height/900})`}new ResizeObserver(syncMathLayer).observe(stage);addEventListener('resize',syncMathLayer);function reset(){zones=[];formulaNodes.forEach(n=>n.remove());formulaNodes=[];ctx.clearRect(0,0,W,H);ctx.fillStyle=P.bg;ctx.fillRect(0,0,W,H);for(let x=0;x<W;x+=80)line(x,0,x,H,'#0b2434',1);for(let y=0;y<H;y+=80)line(0,y,W,y,'#0b2434',1)}function line(x1,y1,x2,y2,color='#fff',lw=3,d=[]){ctx.save();ctx.strokeStyle=color;ctx.lineWidth=lw;ctx.setLineDash(d);ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();ctx.restore()}function rr(x,y,w,h,r=20,fill=P.panel,stroke=null,lw=2){ctx.beginPath();ctx.roundRect(x,y,w,h,r);if(fill){ctx.fillStyle=fill;ctx.fill()}if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=lw;ctx.stroke()}}function txt(t,x,y,size=30,color=P.ink,align='left',weight=700){ctx.fillStyle=color;ctx.font=`${weight} ${size}px Inter,Noto Sans TC,Microsoft JhengHei,sans-serif`;ctx.textAlign=align;ctx.textBaseline='middle';ctx.fillText(t,x,y)}function wrap(t,x,y,maxw,lh=34,size=24,color=P.muted,weight=550,align='left'){ctx.fillStyle=color;ctx.font=`${weight} ${size}px Inter,Noto Sans TC,Microsoft JhengHei,sans-serif`;ctx.textAlign=align;ctx.textBaseline='top';t=String(t).replace(/\\n/g,'\n');let ln='',yy=y;for(const ch of t){if(ch==='\n'){if(ln)ctx.fillText(ln,x,yy);ln='';yy+=lh;continue}const q=ln+ch;if(ctx.measureText(q).width>maxw&&ln){ctx.fillText(ln,x,yy);ln=ch;yy+=lh}else ln=q}if(ln)ctx.fillText(ln,x,yy)}function wrapCenter(t,cx,y,maxw,lh=34,size=24,color=P.muted,weight=550){return wrap(t,cx,y,maxw,lh,size,color,weight,'center')}function title(zh,en,no){txt(`${String(no).padStart(2,'0')}  ${zh}`,70,68,40,P.ink,'left',900);txt(en,72,111,22,P.cyan,'left',650);line(70,146,1530,146,P.grid,2)}function chip(t,x,y,w,color){rr(x,y,w,40,20,color+'20',color,2);txt(t,x+w/2,y+20,18,color,'center',800)}function btn(label,x,y,w,h,onClick,active=false,color=P.cyan){rr(x,y,w,h,15,active?color+'28':'#ffffff0c',active?color:'#ffffff22',2);txt(label,x+w/2,y+h/2,19,active?color:P.ink,'center',800);zones.push({type:'button',x,y,w,h,onClick})}function slider(x,y,w,min,max,value,onChange,label,fmt=v=>v.toFixed(2),accent=P.amber){line(x,y,x+w,y,'#ffffff35',8);const t=(value-min)/(max-min),kx=x+t*w;ctx.fillStyle=accent;ctx.beginPath();ctx.arc(kx,y,15,0,Math.PI*2);ctx.fill();txt(label,x,y-31,20,P.muted,'left',650);txt(fmt(value),x+w,y-31,22,accent,'right',850);zones.push({type:'slider',x:x-20,y:y-28,w:w+40,h:56,min,max,onChange})}function arrow(x1,y1,x2,y2,color=P.cyan,lw=5){line(x1,y1,x2,y2,color,lw);const a=Math.atan2(y2-y1,x2-x1),s=14;ctx.fillStyle=color;ctx.beginPath();ctx.moveTo(x2,y2);ctx.lineTo(x2-s*Math.cos(a-.5),y2-s*Math.sin(a-.5));ctx.lineTo(x2-s*Math.cos(a+.5),y2-s*Math.sin(a+.5));ctx.closePath();ctx.fill()}function addFormula({tex,x,y,width=220,align='left',size=30,color=P.ink,display=true}){const el=document.createElement('div');el.className='formula'+(align==='center'?' center':'');el.style.left=x+'px';el.style.top=y+'px';el.style.width=width+'px';el.style.fontSize=size+'px';el.style.color=color;mathLayer.appendChild(el);formulaNodes.push(el);if(window.katex)katex.render(tex,el,{displayMode:display,throwOnError:false});else el.textContent=tex}function pos(e){const r=c.getBoundingClientRect();return{x:(e.clientX-r.left)*W/r.width,y:(e.clientY-r.top)*H/r.height}}function gauss(x,mu,s){return Math.exp(-.5*((x-mu)/s)**2)/(Math.sqrt(2*Math.PI)*s)}function truePdf(x){return .5*gauss(x,-1.5,.55)+.5*gauss(x,1.5,.65)}function kde(x,data,b){let s=0;for(const xi of data)s+=gauss(x,xi,b);return s/data.length}function prng(seed){let a=seed>>>0;return()=>{a=(1664525*a+1013904223)>>>0;return a/4294967296}}function normalSample(r,mu,s){let u=Math.max(r(),1e-9),v=r();return mu+s*Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v)}function makeData(N,seed=42){const r=prng(seed),a=[];for(let i=0;i<N;i++)a.push(i%2===0?normalSample(r,-1.5,.55):normalSample(r,1.5,.65));return a}function drawDensityPlot(x0,y0,w,h,data,b){line(x0,y0+h,x0+w,y0+h,'#ffffff55',2);line(x0,y0,x0,y0+h,'#ffffff55',2);const xmin=-4,xmax=4,ymax=.55,px=x=>x0+(x-xmin)/(xmax-xmin)*w,py=y=>y0+h-y/ymax*h;ctx.save();ctx.beginPath();for(let i=0;i<=240;i++){const x=xmin+8*i/240,y=truePdf(x);i?ctx.lineTo(px(x),py(y)):ctx.moveTo(px(x),py(y))}ctx.strokeStyle=P.muted;ctx.lineWidth=4;ctx.setLineDash([10,8]);ctx.stroke();ctx.restore();ctx.beginPath();for(let i=0;i<=240;i++){const x=xmin+8*i/240,y=kde(x,data,b);i?ctx.lineTo(px(x),py(y)):ctx.moveTo(px(x),py(y))}ctx.strokeStyle=P.cyan;ctx.lineWidth=5;ctx.stroke();data.slice(0,100).forEach(x=>{ctx.fillStyle='#ffffff55';ctx.beginPath();ctx.arc(px(x),y0+h+10,2.8,0,Math.PI*2);ctx.fill()});[-4,-2,0,2,4].forEach(v=>txt(String(v),px(v),y0+h+34,16,P.muted,'center',650))}function start(f){function k(){syncMathLayer();f()}document.readyState==='complete'?setTimeout(k,80):addEventListener('load',()=>setTimeout(k,80))}c.addEventListener('pointerdown',e=>{const p=pos(e);for(const z of zones){if(p.x>=z.x&&p.x<=z.x+z.w&&p.y>=z.y&&p.y<=z.y+z.h){if(z.type==='button'){z.onClick();return}if(z.type==='slider'){drag=z;const t=Math.max(0,Math.min(1,(p.x-(z.x+20))/(z.w-40)));z.onChange(z.min+t*(z.max-z.min));return}}}});c.addEventListener('pointermove',e=>{if(!drag)return;const p=pos(e),t=Math.max(0,Math.min(1,(p.x-(drag.x+20))/(drag.w-40)));drag.onChange(drag.min+t*(drag.max-drag.min))});addEventListener('pointerup',()=>drag=null);
</script><script>let mode=1;const regimes=[{b:.05,rms:.092,label:'Too small / 太小',desc:'高變異:每筆樣本變成尖刺,開始追逐噪聲。',col:P.coral},{b:.25,rms:.052,label:'Balanced / 適中',desc:'保留雙峰與山谷,同時抹掉零星尖刺。',col:P.lime},{b:1.2,rms:.131,label:'Too large / 太大',desc:'高偏誤:真正雙峰被磨成一張大煎餅。',col:P.violet}];const data=makeData(80,42);function render(){reset();title('Bandwidth 決定偏誤—變異權衡','Bandwidth controls the bias–variance trade-off',8);regimes.forEach((r,i)=>btn(`${r.label}  b=${r.b}`,220+i*390,190,320,54,()=>{mode=i;render()},mode===i,r.col));const g=regimes[mode];rr(130,285,1340,440,28,P.panel);drawDensityPlot(210,340,850,280,data,g.b);rr(1100,340,290,250,22,P.panel2,g.col,2);txt('Regime',1245,382,22,g.col,'center',800);txt(g.label,1245,430,27,P.ink,'center',850);txt(`RMS = ${g.rms.toFixed(3)}`,1245,485,28,P.amber,'center',850);wrapCenter(g.desc,1245,535,240,30,20,P.muted,600);wrapCenter('虛線:真實雙峰 PDF 實線:Parzen KDE',635,670,800,30,20,P.muted,600)}start(render);</script></body></html>