ConfidenceManifold / app /src /content /embeds /layer-evolution.html
seonglae-holistic's picture
fix: relative data paths for subpath hosting, correct publish date to Feb 8
72696dc
Raw
History Blame Contribute Delete
11.3 kB
<!-- Layer Evolution: AUC and intrinsic dimension per layer across models -->
<div class="layer-evo">
<div class="layer-controls">
<div class="model-legend" id="layer-legend"></div>
<div class="metric-toggle">
<button class="metric-btn" data-metric="aucs">AUC</button>
<button class="metric-btn" data-metric="dims">Intrinsic Dim</button>
<button class="metric-btn active" data-metric="grassmann">Grassmann Dist</button>
</div>
</div>
<svg id="layer-evo-svg"></svg>
</div>
<style>
.layer-evo { position: relative; width: 100%; }
#layer-evo-svg { width: 100%; height: 360px; }
.layer-controls { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.metric-toggle { display: flex; gap: 2px; background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 2px; flex-shrink: 0; }
.metric-btn {
padding: 4px 12px; border: none; border-radius: 6px; font-size: 11px; font-weight: 600;
background: transparent; color: var(--muted-color); cursor: pointer; transition: all 0.2s;
}
.metric-btn.active { background: var(--primary-color); color: #fff; }
#layer-legend { display: flex; flex-wrap: wrap; gap: 4px; }
.lchip {
display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 12px;
font-size: 10px; font-weight: 600; cursor: pointer; transition: all 0.2s;
border: 1.5px solid transparent; user-select: none;
}
.lchip .cdot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.lchip.inactive { opacity: 0.25; }
.lchip:hover { transform: scale(1.05); }
.layer-tip {
position: absolute; background: var(--surface-bg); border: 1px solid var(--border-color);
border-radius: 8px; padding: 8px 12px; font-size: 11px; pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10; color: var(--text-color); line-height: 1.5;
}
@media (max-width: 640px) { #layer-evo-svg { height: 280px; } .layer-controls { flex-direction: column; align-items: flex-start; } }
</style>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
(function(){
const container = document.querySelector('.layer-evo');
const svg = d3.select('#layer-evo-svg');
const legendEl = document.getElementById('layer-legend');
const tip = d3.select(container).append('div').attr('class','layer-tip').style('display','none');
const colors = {
'GPT-2':'#fbbf24','GPT-2-Med':'#f59e0b','GPT-2-Large':'#d97706',
'Gemma-2B':'#fb923c','Qwen2-1.5B':'#f97316','Llama-1B':'#ea580c',
'Llama-3B':'#c2410c','Qwen2-7B':'#dc2626','Mistral-7B':'#7c2d12'
};
let data = null, metric = 'grassmann', active = new Set(Object.keys(colors)), hovered = null;
Object.entries(colors).forEach(([name, c]) => {
const ch = document.createElement('span');
ch.className = 'lchip'; ch.dataset.model = name;
ch.innerHTML = `<span class="cdot" style="background:${c}"></span>${name}`;
ch.style.background = `color-mix(in srgb, ${c} 12%, transparent)`;
ch.style.color = c; ch.style.borderColor = c;
ch.addEventListener('click', () => {
if (active.has(name)) { if (active.size === 1) return; active.delete(name); ch.classList.add('inactive'); }
else { active.add(name); ch.classList.remove('inactive'); }
draw();
});
ch.addEventListener('mouseenter', () => { hovered = name; draw(); });
ch.addEventListener('mouseleave', () => { hovered = null; draw(); });
legendEl.appendChild(ch);
});
fetch('data/layer_evolution.json').then(r => r.json()).then(d => { data = d; draw(); });
function draw() {
svg.selectAll('*').remove();
if (!data) return;
const rect = document.getElementById('layer-evo-svg').getBoundingClientRect();
const W = rect.width, H = rect.height;
const m = { top: 20, right: 16, bottom: 40, left: 50 };
const w = W - m.left - m.right, h = H - m.top - m.bottom;
svg.attr('viewBox', `0 0 ${W} ${H}`);
const g = svg.append('g').attr('transform', `translate(${m.left},${m.top})`);
// X: normalized depth (0-100%)
const x = d3.scaleLinear().domain([0, 100]).range([0, w]);
// Y: depends on metric
let yDomain;
if (metric === 'aucs') yDomain = [0.45, 1.0];
else if (metric === 'grassmann') {
const allG = Object.entries(data).filter(([n]) => active.has(n)).flatMap(([,d]) => d.grassmann || []);
yDomain = [0, Math.ceil(d3.max(allG) * 2) / 2 + 0.5];
} else {
const allDims = Object.entries(data).filter(([n]) => active.has(n)).flatMap(([,d]) => d.dims || []);
yDomain = [0, Math.ceil(d3.max(allDims) / 10) * 10 + 5];
}
const y = d3.scaleLinear().domain(yDomain).range([h, 0]).nice();
// Phase bands
const phases = [{name:'I',start:0,end:30,color:'#ea580c'},{name:'II',start:30,end:70,color:'#f59e0b'},{name:'III',start:70,end:100,color:'#c2410c'}];
phases.forEach(p => {
g.append('rect').attr('x',x(p.start)).attr('width',x(p.end)-x(p.start)).attr('y',0).attr('height',h)
.attr('fill',p.color).attr('opacity',0.04).attr('rx',2);
g.append('text').attr('x',(x(p.start)+x(p.end))/2).attr('y',12)
.attr('text-anchor','middle').attr('fill',p.color).attr('font-size',9).attr('opacity',0.5)
.attr('font-weight',600).text('Phase '+p.name);
});
// Grid + axes
g.append('g').selectAll('line').data(y.ticks(5)).enter().append('line')
.attr('x1',0).attr('x2',w).attr('y1',d=>y(d)).attr('y2',d=>y(d))
.attr('stroke','var(--grid-color)').attr('stroke-dasharray','2,3');
g.append('g').attr('transform',`translate(0,${h})`).call(d3.axisBottom(x).ticks(5).tickFormat(d=>d+'%'))
.selectAll('text').attr('fill','var(--tick-color)');
const yFmt = metric==='aucs' ? d3.format('.2f') : metric==='grassmann' ? d3.format('.1f') : d3.format('d');
const yLabel = metric==='aucs' ? 'AUC' : metric==='grassmann' ? 'Grassmann Distance' : 'Intrinsic Dimension (MLE)';
g.append('g').call(d3.axisLeft(y).ticks(5).tickFormat(yFmt))
.selectAll('text').attr('fill','var(--tick-color)');
g.append('text').attr('x',w/2).attr('y',h+35).attr('text-anchor','middle')
.attr('fill','var(--muted-color)').attr('font-size',12).text('Normalized depth (%)');
g.append('text').attr('transform','rotate(-90)').attr('x',-h/2).attr('y',-38)
.attr('text-anchor','middle').attr('fill','var(--muted-color)').attr('font-size',12)
.text(yLabel);
// Build all model line data
const isG = metric === 'grassmann';
const allLines = [];
Object.entries(data).forEach(([name, d]) => {
const c = colors[name] || '#888';
if (!active.has(name)) return;
const vals = d[metric] || [];
if (!vals.length) return;
const pts = isG
? vals.map((v, i) => ({ x: ((i + 0.5) / (d.layers - 1)) * 100, y: v, layer: i, layerTo: i + 1 }))
: vals.map((v, i) => ({ x: (i / (d.layers - 1)) * 100, y: v, layer: i }));
allLines.push({ name, c, pts, d, vals });
});
// Draw lines
const lineGen = d3.line().x(p => x(p.x)).y(p => y(p.y)).curve(d3.curveMonotoneX);
allLines.forEach(({ name, c, pts, d, vals }) => {
const isH = hovered === name;
const isDim = hovered && hovered !== name;
if (isG && (isH || !hovered)) {
const areaGen = d3.area().x(p => x(p.x)).y0(h).y1(p => y(p.y)).curve(d3.curveMonotoneX);
g.append('path').datum(pts).attr('d', areaGen)
.attr('fill', c).attr('opacity', isH ? 0.12 : 0.04);
}
g.append('path').datum(pts).attr('d', lineGen)
.attr('fill','none').attr('stroke',c)
.attr('stroke-width', isH ? 3.5 : 2).attr('opacity', isDim ? 0.12 : isH ? 1 : 0.8);
if (!isG) {
const bestI = d.bestLayer;
if (bestI < vals.length) {
const bx = (bestI / (d.layers - 1)) * 100;
g.append('circle').attr('cx',x(bx)).attr('cy',y(vals[bestI]))
.attr('r', isH ? 6 : 4).attr('fill',c).attr('stroke','var(--surface-bg)').attr('stroke-width',2)
.attr('opacity', isDim ? 0.12 : 1);
}
}
});
// Crosshair overlay
const crossLine = g.append('line').attr('y1',0).attr('y2',h)
.attr('stroke','var(--muted-color)').attr('stroke-width',0.8).attr('stroke-dasharray','4,3')
.attr('opacity',0).attr('pointer-events','none');
const crossDots = g.append('g').attr('pointer-events','none');
const depthLabel = g.append('text').attr('y',-4).attr('text-anchor','middle')
.attr('fill','var(--muted-color)').attr('font-size',9).attr('opacity',0);
function interp(pts, xVal) {
if (!pts.length) return null;
if (xVal <= pts[0].x) return pts[0];
if (xVal >= pts[pts.length-1].x) return pts[pts.length-1];
for (let i = 0; i < pts.length - 1; i++) {
if (xVal >= pts[i].x && xVal <= pts[i+1].x) {
const t = (xVal - pts[i].x) / (pts[i+1].x - pts[i].x);
return { y: pts[i].y + t * (pts[i+1].y - pts[i].y), layer: pts[i].layer, layerTo: pts[i].layerTo };
}
}
return null;
}
g.append('rect').attr('width',w).attr('height',h).attr('fill','transparent').attr('cursor','crosshair')
.on('mousemove', function(e) {
const [mx] = d3.pointer(e);
const depth = x.invert(mx);
crossLine.attr('x1',mx).attr('x2',mx).attr('opacity',0.5);
depthLabel.attr('x',mx).attr('opacity',0.7).text(Math.round(depth)+'%');
crossDots.selectAll('*').remove();
const metricLabel = metric==='aucs' ? 'AUC' : metric==='grassmann' ? 'Dist' : 'Dim';
let rows = [];
allLines.forEach(({ name, c, pts }) => {
const isDim = hovered && hovered !== name;
if (isDim) return;
const p = interp(pts, depth);
if (!p) return;
crossDots.append('circle').attr('cx',mx).attr('cy',y(p.y))
.attr('r',4).attr('fill',c).attr('stroke','var(--surface-bg)').attr('stroke-width',1.5);
const fVal = metric==='aucs' ? p.y.toFixed(3) : metric==='grassmann' ? p.y.toFixed(2) : Math.round(p.y);
rows.push(`<span style="color:${c}">\u25CF</span> ${name}: <strong>${fVal}</strong>`);
});
if (rows.length) {
tip.style('display','block')
.html(`<strong>${Math.round(depth)}% depth</strong><br/>${rows.join('<br/>')}`)
.style('left', Math.min(e.offsetX+16, W-180)+'px')
.style('top', Math.max(0, e.offsetY-20-rows.length*10)+'px');
}
})
.on('mouseleave', function() {
crossLine.attr('opacity',0);
depthLabel.attr('opacity',0);
crossDots.selectAll('*').remove();
tip.style('display','none');
});
svg.selectAll('.domain').attr('stroke','var(--axis-color)');
svg.selectAll('.tick line').attr('stroke','var(--axis-color)');
}
container.querySelectorAll('.metric-btn').forEach(btn => {
btn.addEventListener('click', () => {
container.querySelectorAll('.metric-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
metric = btn.dataset.metric;
draw();
});
});
new ResizeObserver(draw).observe(document.getElementById('layer-evo-svg'));
})();
</script>