▶ 展示 · 回索引
sam-sharpness-aware-minimization.html
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SAM 銳度感知最小化|Sharpness-Aware Minimization</title>
<style>
:root { color-scheme: light; }
* { box-sizing: border-box; }
body { margin: 0; background: #e9eff1; font-family: "Microsoft JhengHei", "Noto Sans CJK TC", "PingFang TC", system-ui, sans-serif; color:#132d3b; }
main { min-height: 100vh; display:grid; place-items:center; padding:20px; }
.stage { position:relative; width:min(100%, 1600px); aspect-ratio:16/9; background:#fff; box-shadow:0 20px 60px #15364724; overflow:hidden; }
canvas { display:block; width:100%; height:100%; }
.controls { position:absolute; left:17.5%; bottom:2.8%; width:18%; display:flex; align-items:center; gap:1vw; }
#rho { width:60%; accent-color:#d18435; cursor:pointer; }
button { font:600 clamp(9px,1vw,15px) "Microsoft JhengHei", system-ui, sans-serif; color:#174b55; border:1px solid #bbd3d4; background:#f5faf9; border-radius:7px; padding:.48em .8em; white-space:nowrap; cursor:pointer; }
button:hover { background:#e8f4f2; }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
@media (max-width:700px) { main { padding:8px; } .controls { bottom:2%; gap:3px; } button { padding:3px 4px; font-size:6px; border-radius:3px; } }
@media print { body { background:white; } main { padding:0; min-height:0; } .stage { width:100%; box-shadow:none; } .controls { display:none; } @page { size:landscape; margin:0; } }
</style>
</head>
<body>
<main>
<section class="stage" aria-label="SAM 銳度感知最小化雙語資訊圖;可調整鄰域半徑 rho,比較尖銳和平坦的損失盆地">
<canvas id="chart" width="1600" height="900" role="img" aria-label="比較尖銳與平坦極小值,同時呈現 SAM min-max 目標、擾動近似及兩次梯度計算流程"></canvas>
<div class="controls"><label class="sr-only" for="rho">鄰域半徑 rho</label><input id="rho" type="range" min="0.2" max="0.9" step="0.01" value="0.60"><button id="export" type="button" title="下載 PNG · Download PNG">PNG ↓</button></div>
</section>
</main>
<script>
(() => {
'use strict';
const canvas=document.getElementById('chart'), ctx=canvas.getContext('2d'), slider=document.getElementById('rho');
const C={ink:'#17313d',muted:'#627783',line:'#dce6e8',paper:'#ffffff',soft:'#f6f9f9',teal:'#178e85',tealPale:'#eaf7f3',coral:'#d56658',coralPale:'#fff0eb',amber:'#c67a27',amberPale:'#fff5e6',navy:'#294f69'};
const cn='"Microsoft JhengHei","Noto Sans CJK TC","PingFang TC",system-ui,sans-serif';
const math='"Cambria Math","STIX Two Math","Times New Roman",serif';
function text(s,x,y,size=20,color=C.ink,weight=400,align='left',face=cn) {ctx.fillStyle=color;ctx.font=`${weight} ${size}px ${face}`;ctx.textAlign=align;ctx.textBaseline='alphabetic';ctx.fillText(s,x,y);}
function round(x,y,w,h,r,fill,stroke=null,lw=1){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 line(x1,y1,x2,y2,color=C.line,lw=1,dash=[]){ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.strokeStyle=color;ctx.lineWidth=lw;ctx.setLineDash(dash);ctx.stroke();ctx.setLineDash([]);}
function arrow(x1,y1,x2,y2,color,lw=2.5){line(x1,y1,x2,y2,color,lw);const a=Math.atan2(y2-y1,x2-x1),s=8;ctx.beginPath();ctx.moveTo(x2,y2);ctx.lineTo(x2-s*Math.cos(a-.55),y2-s*Math.sin(a-.55));ctx.lineTo(x2-s*Math.cos(a+.55),y2-s*Math.sin(a+.55));ctx.closePath();ctx.fillStyle=color;ctx.fill();}
function badge(s,x,y,w,fg,bg){round(x,y,w,30,15,bg);text(s,x+w/2,y+21,15,fg,600,'center');}
function panel(x,y,w,h){round(x,y,w,h,15,C.paper,C.line,1.5);}
function section(n,zh,en,x,y){badge(n,x,y-22,34,C.navy,'#e9f1f5');text(zh,x+48,y,22,C.ink,700);text(en,x+48,y+25,14,C.muted,500);}
function bowl(cx,cy,half,curv,rho,color,title,en,descrip) {
const sx=half/1.12, scale=25;
round(cx-rho*sx,cy-166,2*rho*sx,171,7,color===C.coral?C.coralPale:C.tealPale);
line(cx-half-20,cy,cx+half+20,cy,'#b6c7cb',1.5);
line(cx,cy+8,cx,cy-161,'#cbd8db',1.2,[4,5]);
ctx.beginPath();for(let i=0;i<=150;i++){const u=-1.12+2.24*i/150,px=cx+u*sx,py=cy-curv*u*u*scale;if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);}ctx.strokeStyle=color;ctx.lineWidth=5;ctx.lineCap='round';ctx.stroke();
const py=cy-curv*rho*rho*scale,ex=cx+rho*sx;
ctx.beginPath();ctx.arc(cx,cy,5,0,Math.PI*2);ctx.fillStyle=C.ink;ctx.fill();
ctx.beginPath();ctx.arc(ex,py,5,0,Math.PI*2);ctx.fillStyle=color;ctx.fill();
arrow(ex+16,cy-3,ex+16,py+3,color,2.5);
line(cx-rho*sx,cy+14,cx+rho*sx,cy+14,color,2.5);
text('−ρ',cx-rho*sx,cy+36,14,color,600,'center',math);text('+ρ',cx+rho*sx,cy+36,14,color,600,'center',math);
text(title,cx,232,22,color,700,'center');text(en,cx,254,14,C.muted,500,'center');
text(descrip,cx,486,15,color,600,'center');
}
function draw(){
const rho=+slider.value;ctx.fillStyle=C.paper;ctx.fillRect(0,0,1600,900);
round(0,0,1600,12,0,C.navy);
text('SAM',60,77,43,C.navy,800);text('銳度感知最小化',186,77,37,C.ink,800);text('Sharpness-Aware Minimization',62,111,21,C.muted,500);
badge('核心|CORE IDEA',1240,53,296,C.navy,'#e8f2f5');
text('讓整個鄰域維持低損失',1540,113,22,C.navy,700,'right');
text('Keep the neighborhood low-loss',1540,139,16,C.muted,500,'right');
line(60,157,1540,157,C.line,1.5);
panel(60,177,720,320);section('01','相同半徑,兩種損失盆地','Same radius · different worst-case rise',82,213);
bowl(255,427,137,3.3,rho,C.coral,'尖銳極小值','Sharp minimum','最大損失增幅較大 · Larger rise');
bowl(588,427,137,.62,rho,C.teal,'平坦極小值','Flat minimum','最大損失增幅較小 · Smaller rise');
line(420,272,420,463,C.line,1.2);
badge('同一尺度 / same axes',570,278,180,C.navy,'#edf3f5');
panel(800,177,740,320);section('02','SAM 要最小化什麼?','Worst loss in a weight-space ball',822,213);
round(823,263,692,69,10,'#edf6f4');
text('min max ℒ(w + ε)',853,305,31,C.ink,600,'left',math);
text('w ‖ε‖ₚ ≤ ρ',911,325,17,C.teal,600,'left',math);
text('(49)',1480,306,16,C.muted,500,'right',math);
text('先找最壞擾動,再調整權重 / Perturb up, then optimize down',827,361,18,C.navy,600);
line(825,379,1512,379,C.line,1.3);
text('ℒSAM(w) = ℒ(w) + Sρ(w)',827,413,25,C.ink,600,'left',math);
text('Sρ(w) = max [ℒ(w+ε) − ℒ(w)] (50)',827,445,22,C.ink,500,'left',math);
text('‖ε‖ₚ≤ρ',944,465,14,C.teal,500,'left',math);
text('可加 λ‖w‖₂² 正則項 / Optional weight penalty (47)',827,484,15,C.muted,500);
panel(60,515,720,300);section('03','內層最大化:用一階近似','First-order perturbation · p = 2',82,550);
round(82,598,676,53,8,C.soft);
text('ℒ(w+ε) ≈ ℒ(w) + εᵀg, g = ∇ℒ(w)',101,633,24,C.ink,500,'left',math);
text('ε̂ = ρ g / ‖g‖₂ (52)',101,701,32,C.amber,600,'left',math);
text('g ≠ 0;沿損失上升方向走 ρ',112,733,17,C.ink,600);
text('If g = 0, use ε̂ = 0 for this step.',112,757,15,C.muted,500);
line(82,767,757,767,C.line,1);
text('一般 p:ε̂ᵢ = ρ sign(gᵢ)|gᵢ|^(q−1) / ‖g‖q^(q−1)',82,788,16,C.navy,500,'left',math);
text('1/p + 1/q = 1;此式取 1 < p < ∞,端點另處理 (51)',82,806,14,C.muted,500);
panel(800,515,740,300);section('04','一次 SAM 更新','Two gradient evaluations · one weight update',822,550);
const steps=[
['1','在 w 計算梯度','g = ∇ℒ(w)'],
['2','往上擾動','w̃ = w + ρg/‖g‖₂'],
['3','在擾動點再算梯度','gₐ = ∇ℒ(w̃)'],
['4','回到原 w,再往下更新','w₊ = w − ηgₐ']
];
steps.forEach((d,i)=>{const y=605+i*49;round(824,y-23,36,35,9,i===1?C.amberPale:(i===3?C.tealPale:'#edf3f5'));text(d[0],842,y+2,20,i===1?C.amber:(i===3?C.teal:C.navy),800,'center');text(d[1],878,y+1,18,C.ink,600);text(d[2],1497,y+1,21,i===1?C.amber:(i===3?C.teal:C.navy),600,'right',math);if(i<3)line(842,y+13,842,y+25,C.line,2);});
text('先還原 w;ε̂ 是一階近似,更新時停止對 ε̂ 求導。',824,787,15,C.muted,500);
text('若用式 (47) 的正則項,下降梯度再加 2λw / Add 2λw for (47).',824,806,14,C.muted,500);
round(60,829,1480,58,10,'#f4f7f8');
text('鄰域半徑 ρ = '+rho.toFixed(2),82,855,19,C.amber,700);
text('Neighborhood radius',82,879,13,C.muted,500);
line(596,838,596,878,C.line,1);
text('提醒:平坦度與泛化的關聯不是保證;銳度依賴參數化。',618,855,17,C.ink,600);
text('Caveat: flatness is not a guarantee; parameterization changes measured sharpness.',618,878,15,C.muted,500);
}
slider.addEventListener('input',draw);
document.getElementById('export').addEventListener('click',()=>{draw();const a=document.createElement('a');a.download='sam-sharpness-aware-minimization-bilingual.png';a.href=canvas.toDataURL('image/png');a.click();});
draw();
})();
</script>
</body>
</html>