▶ 展示 · 回索引

03-entropy-formula.html

<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>KP12 03 Entropy Formula</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<style>
:root{--bg:#071521;--panel:#0f2637;--ink:#f7fbff;--muted:#a9bfd0}
*{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:0 0;pointer-events:none}
.formula{position:absolute;transform:translateY(-50%);line-height:1.2}
.formula .katex-display{margin:0;text-align:inherit}
.formula .katex-display>.katex{display:inline-block;text-align:inherit;white-space:nowrap}
.hint{position:absolute;right:2.1%;top:1.9%;font-size:.8vw;color:#9ab4c7;background:#071521cc;border:1px solid #ffffff18;border-radius:999px;padding:.5em .75em;z-index:5}
@media(max-width:900px){.hint{font-size:1.4vw}}
</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></div>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script>
/* ===== Shared helpers (Canvas 1600x900 logical space + KaTeX math layer) ===== */
const CW=1600,CH=900;
const c=document.getElementById('c'),ctx=c.getContext('2d');
const stage=document.getElementById('stage'),mathLayer=document.getElementById('math-layer');
const P={bg:'#071521',panel:'#0f2637',panel2:'#102c40',ink:'#f7fbff',muted:'#a9bfd0',cyan:'#4fd1c5',amber:'#f6c85f',coral:'#ff7b72',lime:'#8fd694',violet:'#b794f4',blue:'#63b3ed',grid:'#17384d'};
const FONT='Inter,"Noto Sans TC","Microsoft JhengHei","PingFang TC","Noto Sans CJK TC","WenQuanYi Zen Hei",sans-serif';
let zones=[],drag=null;
window.__boxes=[];            // text boxes recorded for the layout audit
const REC=()=>window.__audit===true;

/* ---------- basic drawing ---------- */
function reset(){zones=[];window.__boxes=[];ctx.clearRect(0,0,CW,CH);ctx.fillStyle=P.bg;ctx.fillRect(0,0,CW,CH);
  for(let x=0;x<CW;x+=80)line(x,0,x,CH,'#0b2434',1);for(let y=0;y<CH;y+=80)line(0,y,CW,y,'#0b2434',1)}
function line(x1,y1,x2,y2,color='#fff',lw=3,dash=[]){ctx.save();ctx.strokeStyle=color;ctx.lineWidth=lw;ctx.setLineDash(dash);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 dot(x,y,r,color){ctx.fillStyle=color;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fill()}
function setFont(size,weight){ctx.font=`${weight} ${size}px ${FONT}`}
function rec(x0,y0,x1,y1,label){if(REC())window.__boxes.push({x0,y0,x1,y1,label})}
/* single-line text; x is the anchor according to align (left edge / centre / right edge) */
function txt(t,x,y,size=30,color=P.ink,align='left',weight=700){
  t=String(t);ctx.fillStyle=color;setFont(size,weight);ctx.textAlign=align;ctx.textBaseline='middle';ctx.fillText(t,x,y);
  if(REC()){const w=ctx.measureText(t).width,l=align==='left'?x:align==='center'?x-w/2:x-w;rec(l,y-size*.55,l+w,y+size*.55,t.slice(0,24))}}
/* wrapped paragraph; returns the y below the last line. Normalises literal "\n". Breaks CJK per char, Latin per word. */
function wrap(t,x,y,maxw,lh=34,size=24,color=P.muted,weight=550,align='left'){
  t=String(t).replace(/\\n/g,'\n');setFont(size,weight);ctx.fillStyle=color;ctx.textBaseline='top';ctx.textAlign=align;
  const out=[];
  for(const para of t.split('\n')){
    const raw=para.match(/[一-鿿]|[^\s一-鿿 -〿＀-￯]+\s*|\s+|[ -〿＀-￯]/g)||[''];
    const toks=[];for(let i=0;i<raw.length;i++){if(/^[「『(《【“‘]$/.test(raw[i])&&i+1<raw.length){toks.push(raw[i]+raw[i+1]);i++}else toks.push(raw[i])}
    let ln='';
    for(const tk of toks){
      const q=ln+tk;
      const isPunct=/^[ -〿＀-￯]$/.test(tk);
      if(ctx.measureText(q.trimEnd()).width>maxw&&ln&&!isPunct){out.push(ln.trimEnd());ln=tk.trimStart()}else ln=q}
    out.push(ln.trimEnd());
  }
  let yy=y;
  for(const l of out){ctx.fillText(l,x,yy);
    if(REC()){const w=ctx.measureText(l).width,lx=align==='left'?x:align==='center'?x-w/2:x-w;rec(lx,yy,lx+w,yy+size*1.15,l.slice(0,24))}
    yy+=lh}
  return yy}
function footer(n){txt(`PR115 · Ch02 · KP-12  最大熵估計 Maximum Entropy Estimation`,70,878,15,'#6f8da3','left',600);txt(`${n} / 12`,1530,878,15,'#6f8da3','right',700)}
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);footer(no)}
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,size=19){rr(x,y,w,h,15,active?color+'28':'#ffffff0c',active?color:'#ffffff22',2);txt(label,x+w/2,y+h/2,size,active?color:P.ink,'center',800);zones.push({type:'button',x,y,w,h,onClick})}
function area(x,y,w,h,onClick){zones.push({type:'button',x,y,w,h,onClick})}
function slider(x,y,w,min,max,value,onChange,label,fmt=v=>v.toFixed(2)){
  line(x,y,x+w,y,'#ffffff35',8);const t=(value-min)/(max-min),kx=x+t*w;line(x,y,kx,y,P.amber+'88',8);dot(kx,y,15,P.amber);
  txt(label,x,y-31,20,P.muted,'left',650);txt(fmt(value),x+w,y-31,22,P.amber,'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 card(x,y,w,h,color,active=true){rr(x,y,w,h,22,active?P.panel2:P.panel,active?color:'#ffffff14',3)}

/* ---------- pointer interaction (logical coordinates) ---------- */
function pos(e){const r=c.getBoundingClientRect();return{x:(e.clientX-r.left)*CW/r.width,y:(e.clientY-r.top)*CH/r.height}}
function sliderVal(z,p){const t=Math.max(0,Math.min(1,(p.x-(z.x+20))/(z.w-40)));return z.min+t*(z.max-z.min)}
c.addEventListener('pointerdown',e=>{const p=pos(e);
  for(let i=zones.length-1;i>=0;i--){const z=zones[i];
    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(p);return}
      if(z.type==='slider'||z.type==='drag'){drag=z;z.onChange(sliderVal(z,p),p);return}}}});
c.addEventListener('pointermove',e=>{if(!drag)return;const p=pos(e);drag.onChange(sliderVal(drag,p),p)});
addEventListener('pointerup',()=>drag=null);
addEventListener('pointercancel',()=>drag=null);

/* ---------- KaTeX math layer (shares the 1600x900 logical coordinate system) ---------- */
const FM={};
function placeFormula(f){const s=f.el.style;s.width=f.w+'px';s.fontSize=f.size+'px';s.color=f.color;s.textAlign=f.align;
  let left=f.x+f.ox;if(f.align==='center')left-=f.w/2;else if(f.align==='right')left-=f.w;
  s.left=left+'px';s.top=(f.y+f.oy)+'px'}
function setTex(id,tex){const f=FM[id];if(f.cur===tex)return;f.cur=tex;
  if(window.katex)katex.render(tex,f.el,{displayMode:true,throwOnError:false,strict:'ignore'});else f.el.textContent=tex}
/* x,y: anchor (y = vertical centre of the formula box); align: left|center|right; size: logical px */
function addFormula(o){const el=document.createElement('div');el.className='formula';el.dataset.fid=o.id;mathLayer.appendChild(el);
  const f=Object.assign({el,align:'center',size:32,ox:0,oy:0,w:600,color:'#f7fbff'},o);FM[o.id]=f;placeFormula(f);setTex(o.id,o.tex);return f}
function moveFormula(id,o){Object.assign(FM[id],o);placeFormula(FM[id])}
function showF(ids){for(const k in FM)FM[k].el.style.display=ids.includes(k)?'block':'none'}
function syncMath(){const r=stage.getBoundingClientRect();mathLayer.style.transform=`scale(${r.width/CW},${r.height/CH})`}
new ResizeObserver(syncMath).observe(stage);addEventListener('resize',syncMath);syncMath();

/* ---------- maximum-entropy candidate shapes (all normalised on [0,W]) ---------- */
const gs=(u,m,s)=>Math.exp(-.5*((u-m)/s)**2)/(s*Math.sqrt(2*Math.PI));
const SHAPES={
  uniform :{zh:'均勻',en:'Uniform',color:P.lime,q:u=>1},
  unimodal:{zh:'單峰',en:'Unimodal',color:P.cyan,q:u=>6*u*(1-u)},
  bimodal :{zh:'雙峰',en:'Bimodal',color:P.violet,q:u=>gs(u,.25,.11)+gs(u,.75,.11)},
  sharp   :{zh:'高尖峰',en:'Sharp peak',color:P.coral,q:u=>gs(u,.5,.08)}};
const SHAPE_KEYS=['uniform','unimodal','bimodal','sharp'];
(function(){const N=4000;for(const k of SHAPE_KEYS){const s=SHAPES[k];let Z=0;
  for(let i=0;i<N;i++)Z+=s.q((i+.5)/N)/N;
  s.Z=Z;s.qn=u=>(u<0||u>1)?0:s.q(u)/Z;
  let h=0;for(let i=0;i<N;i++){const p=s.qn((i+.5)/N);if(p>1e-300)h-=p*Math.log(p)/N}
  s.HQ=h}})();                                   /* entropy on the unit interval; H(W)=HQ+ln W */
const pdf=(k,x,W)=>SHAPES[k].qn(x/W)/W;
const entropy=(k,W)=>SHAPES[k].HQ+Math.log(W);
function areaOf(k,W){const N=2000;let a=0;for(let i=0;i<N;i++)a+=pdf(k,(i+.5)*W/N,W)*W/N;return a}
function peakOf(k,W){let m=0;for(let i=0;i<=400;i++)m=Math.max(m,pdf(k,i*W/400,W));return m}

/* ---------- plotting ---------- */
function mapper(px,py,pw,ph,x0,x1,y0,y1){return{X:x=>px+(x-x0)/(x1-x0)*pw,Y:y=>py+ph-(y-y0)/(y1-y0)*ph,px,py,pw,ph,x0,x1,y0,y1}}
function curvePath(f,m,xa,xb,n=240){ctx.beginPath();for(let i=0;i<=n;i++){const x=xa+(xb-xa)*i/n,X=m.X(x),Y=m.Y(f(x));i?ctx.lineTo(X,Y):ctx.moveTo(X,Y)}}
function plotCurve(f,m,xa,xb,color,lw=5,fillAlpha=null,dash=[]){
  if(fillAlpha){curvePath(f,m,xa,xb);ctx.lineTo(m.X(xb),m.Y(0));ctx.lineTo(m.X(xa),m.Y(0));ctx.closePath();ctx.fillStyle=color+fillAlpha;ctx.fill()}
  ctx.save();ctx.setLineDash(dash);curvePath(f,m,xa,xb);ctx.strokeStyle=color;ctx.lineWidth=lw;ctx.lineJoin='round';ctx.stroke();ctx.restore()}
function axes(m,xticks,ytickVals,xfmt=v=>v,yfmt=v=>v.toFixed(2),xlabel=''){
  line(m.px,m.py+m.ph,m.px+m.pw,m.py+m.ph,'#ffffff55',2);line(m.px,m.py,m.px,m.py+m.ph,'#ffffff55',2);
  for(const v of ytickVals){const Y=m.Y(v);line(m.px,Y,m.px+m.pw,Y,'#ffffff12',1);txt(yfmt(v),m.px-12,Y,17,P.muted,'right',600)}
  for(const v of xticks){const X=m.X(v);line(X,m.py+m.ph,X,m.py+m.ph+8,'#ffffff55',2);txt(xfmt(v),X,m.py+m.ph+26,17,P.muted,'center',600)}
  if(xlabel)txt(xlabel,m.px+m.pw,m.py+m.ph+52,17,P.muted,'right',600)}
const fmt3=v=>v.toFixed(3);
const rampAnim=(dur,fn)=>{const t0=performance.now();const tick=now=>{const t=Math.min(1,(now-t0)/dur);fn(1-Math.pow(1-t,3));if(t<1)requestAnimationFrame(tick)};requestAnimationFrame(tick)};

/* ===== Page script ===== */
/* 03 — Term-by-term reading of the differential-entropy formula */
let st=0,pv=.3;
const COL=[null,P.cyan,P.amber,P.violet,P.coral];
const HEX=[null,'#4fd1c5','#f6c85f','#b794f4','#ff7b72'];
function hl(k,s){return st===k?`{\\color{${HEX[k]}}${s}}`:s}
function mainTex(){return `H=${hl(4,'-')}${hl(1,'\\displaystyle\\int_{x}')}\\,${hl(2,'p(x)')}\\,${hl(3,'\\ln p(x)')}\\,${hl(1,'dx')}`}
addFormula({id:'main',tex:mainTex(),x:530,y:334,w:860,size:56});
const TERMS=[
 ['先看動作,再看符號','Read the action first, then the symbols','H 是把「每個位置的貢獻」加總起來的分數。','H adds up a contribution from every position.',P.ink],
 ['積分 ∫ … dx:掃過每一個位置','Integral: scan every possible position','把定義域內所有可能的位置一個一個掃過,全面加總。','Sweep the whole support and add everything up.',P.cyan],
 ['p(x):該位置的機率密度高度','Density: height at that position','密度越高,代表你對這個位置押的注越大。','The higher the density, the bigger the bet on that position.',P.amber],
 ['ln p(x):意外程度(驚訝度)的刻度','Logarithm: the surprise scale','−ln p(x) 越大,代表這個位置越出乎意料。','A larger −ln p(x) means a more surprising position.',P.violet],
 ['負號:把負的對數翻成正的分數','Leading minus sign','p<1 時 ln p 為負,負號使平坦分佈得到較大的正分數。','When p<1, ln p is negative; the minus makes flat densities score high.',P.coral]];
const MP=mapper(1090,296,400,230,0,1,0,.4);
function render(){
  reset();title('數學解密:微分熵公式的逐項拆解','Mathematical deconstruction of differential entropy',3);
  rr(60,170,940,680,24,P.panel,'#ffffff14',2);
  txt('連續型隨機變數的微分熵',90,205,26,P.ink,'left',850);
  txt('Differential entropy of a continuous random variable',90,236,18,P.cyan,'left',650);
  setTex('main',mainTex());
  const labs=['全部 All','∫ 掃描','p(x) 密度','ln 驚訝','− 負號'];
  labs.forEach((l,i)=>btn(l,90+i*178,426,166,50,()=>{st=i;render()},st===i,i?COL[i]:P.ink,19));
  const T=TERMS[st],tc=T[4];
  rr(90,494,880,152,20,P.panel2,tc===P.ink?'#ffffff33':tc,3);
  txt(T[0],115,518,26,tc,'left',900);txt(T[1],115,546,17,P.muted,'left',650);
  wrap(T[2],115,568,830,30,22,P.ink,650);txt(T[3],115,618,17,'#7f9bb0','left',600);
  rr(90,668,880,164,20,'#4fd1c514',P.cyan,3);
  txt('關鍵心法:最大 ≠ 資訊最多',115,702,25,P.cyan,'left',900);
  wrap('最大熵代表「最少額外偏好」,不是「掌握最多資訊」。它像一張平整的白紙:不是因為白紙知道最多,而是因為它沒有假裝自己知道更多。',115,724,830,28,20,P.ink,650);
  txt('Maximum entropy = fewest unwarranted assumptions, not the most information.',115,806,17,P.muted,'left',600);
  /* right: contribution curve -p ln p */
  rr(1030,170,510,680,24,P.panel,'#ffffff14',2);
  txt('每個位置的貢獻',1060,205,26,P.ink,'left',850);
  txt('Contribution  −p ln p  vs density p',1060,236,18,P.cyan,'left',650);
  axes(MP,[0,.25,.5,.75,1],[0,.1,.2,.3,.4],v=>v,v=>v.toFixed(1),'');
  plotCurve(p=>p<=0?0:-p*Math.log(p),MP,0.0005,1,P.coral,4);
  plotCurve(p=>p<=0?0:-Math.log(p)*0.05,MP,0.02,1,P.violet+'aa',3,null,[8,8]);
  const fy=-pv*Math.log(pv);dot(MP.X(pv),MP.Y(fy),10,P.amber);line(MP.X(pv),MP.Y(fy),MP.X(pv),MP.Y(0),P.amber+'88',2,[6,6]);
  txt('紫虛線:驚訝 −ln p(縮小 ×0.05)',1060,578,16,P.violet,'left',650);
  slider(1060,640,450,.02,1,pv,v=>{pv=Math.round(v*100)/100;render()},'密度高度 p',v=>v.toFixed(2));
  txt('ln p = '+Math.log(pv).toFixed(3),1060,694,22,P.violet,'left',800);
  txt('−ln p = '+(-Math.log(pv)).toFixed(3)+'(驚訝度)',1060,726,22,P.violet,'left',800);
  txt('−p ln p = '+fmt3(-pv*Math.log(pv))+'(貢獻)',1060,758,22,P.coral,'left',850);
  wrap('p 越小越驚訝,但佔比也越小;兩者的乘積在 p = 1/e 達到最高。',1060,790,450,22,15,P.muted,600);
  showF(['main']);
}
render();

</script></body></html>