ConfidenceManifold / app /src /content /embeds /classifiers.html
seonglae-holistic's picture
fix: relative data paths for subpath hosting, correct publish date to Feb 8
72696dc
Raw
History Blame Contribute Delete
7.22 kB
<!-- Geometric Classifiers: 6 methods compared in 8D PLS space -->
<div class="clf-viz">
<div class="clf-controls">
<select id="clf-model-select"></select>
</div>
<svg id="clf-svg"></svg>
</div>
<style>
.clf-viz { position: relative; width: 100%; }
#clf-svg { width: 100%; height: 300px; }
.clf-controls { display: flex; justify-content: flex-end; margin-bottom: 8px; }
#clf-model-select {
padding: 5px 10px; border: 1px solid var(--border-color); border-radius: 8px;
background: var(--surface-bg); color: var(--text-color); font-size: 12px; font-weight: 500;
}
.clf-tip {
position: absolute; background: var(--surface-bg); border: 1px solid var(--border-color);
border-radius: 8px; padding: 6px 10px; font-size: 11px; pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10; color: var(--text-color);
}
@media (max-width: 640px) { #clf-svg { height: 240px; } }
</style>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
(function(){
const container = document.querySelector('.clf-viz');
const svg = d3.select('#clf-svg');
const select = document.getElementById('clf-model-select');
const tip = d3.select(container).append('div').attr('class','clf-tip').style('display','none');
const clfMap = {
'GPT-2':'gpt2','GPT-2 Medium':'gpt2-medium','GPT-2 Large':'gpt2-large',
'Qwen2-1.5B':'qwen15b','Qwen2-7B':'qwen7b','Mistral-7B':'mistral',
'Llama-1B':'llama1b','Llama-3B':'llama3b','Gemma-2B':'gemma'
};
const methods = ['Linear','Centroid','Mahal','KNN-10','SVM-RBF','NCH'];
const barColors = ['#ea580c','#f97316','#c2410c','#9a3412','#7c2d12','#a8a29e'];
let data = null;
fetch('data/geometry_classifiers.json').then(r => r.json()).then(d => {
data = d;
select.innerHTML = '<option value="all">All Models (average)</option>';
Object.keys(clfMap).forEach(m => { select.innerHTML += `<option value="${m}">${m}</option>`; });
draw('all');
});
function getVals(model) {
return methods.map(clf => {
let seeds;
if (model === 'all') {
// Collect all per-model means, then compute spread of means
const means = [];
Object.values(clfMap).forEach(key => {
if (data[key] && data[key][clf]) {
const s = data[key][clf];
means.push(s.reduce((a,b)=>a+b,0)/s.length);
}
});
if (!means.length) return { mean: 0, lo: 0, hi: 0, seeds: [] };
const mean = means.reduce((a,b)=>a+b,0)/means.length;
return { mean, lo: Math.min(...means), hi: Math.max(...means), seeds: means };
}
const key = clfMap[model];
if (!key || !data[key] || !data[key][clf]) return { mean: 0, lo: 0, hi: 0, seeds: [] };
seeds = data[key][clf];
const mean = seeds.reduce((a,b)=>a+b,0)/seeds.length;
return { mean, lo: Math.min(...seeds), hi: Math.max(...seeds), seeds };
});
}
function draw(model) {
svg.selectAll('*').remove();
if (!data) return;
const values = getVals(model);
const means = values.map(v => v.mean);
const rect = document.getElementById('clf-svg').getBoundingClientRect();
const W = rect.width, H = rect.height;
const m = {top:15,right:16,bottom:45,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})`);
const x = d3.scaleBand().domain(methods).range([0,w]).padding(0.25);
const allLo = values.map(v => v.lo).filter(v => v > 0);
const yMin = Math.max(0.6, Math.floor(d3.min(allLo)*10)/10 - 0.05);
const y = d3.scaleLinear().domain([yMin, 1.0]).range([h,0]).nice();
// Grid
y.ticks(4).forEach(v => {
g.append('line').attr('x1',0).attr('x2',w).attr('y1',y(v)).attr('y2',y(v))
.attr('stroke','var(--grid-color)').attr('stroke-dasharray','2,3');
});
// Bars + whiskers
values.forEach((v,i) => {
if (!v.mean) return;
const cx = x(methods[i]) + x.bandwidth()/2;
// Bar
const bar = g.append('rect').attr('x',x(methods[i])).attr('width',x.bandwidth())
.attr('y',y(yMin)).attr('height',0).attr('rx',4).attr('fill',barColors[i]);
bar.transition().duration(500).delay(i*70)
.attr('y',y(Math.max(v.mean,yMin))).attr('height',Math.max(0,h-y(Math.max(v.mean,yMin))));
// Whisker (error bar): lo to hi
const capW = x.bandwidth() * 0.35;
const whiskerG = g.append('g').attr('opacity',0);
whiskerG.transition().duration(300).delay(i*70+450).attr('opacity',1);
// Vertical line
whiskerG.append('line').attr('x1',cx).attr('x2',cx)
.attr('y1',y(v.hi)).attr('y2',y(v.lo))
.attr('stroke',barColors[i]).attr('stroke-width',1.5);
// Top cap
whiskerG.append('line').attr('x1',cx-capW).attr('x2',cx+capW)
.attr('y1',y(v.hi)).attr('y2',y(v.hi))
.attr('stroke',barColors[i]).attr('stroke-width',1.5);
// Bottom cap
whiskerG.append('line').attr('x1',cx-capW).attr('x2',cx+capW)
.attr('y1',y(v.lo)).attr('y2',y(v.lo))
.attr('stroke',barColors[i]).attr('stroke-width',1.5);
// Mean label
g.append('text').attr('x',cx).attr('y',y(v.hi)-8)
.attr('text-anchor','middle').attr('fill',barColors[i]).attr('font-size',11).attr('font-weight',600)
.attr('opacity',0).transition().duration(300).delay(i*70+400).attr('opacity',1)
.text(v.mean.toFixed(3));
// Hover
bar.on('mouseenter', function(e) {
const spread = (v.hi - v.lo).toFixed(3);
const seedStr = model === 'all'
? 'Range across 9 models'
: 'Seeds: ' + v.seeds.map(s=>s.toFixed(3)).join(', ');
tip.style('display','block')
.html(`<strong>${methods[i]}</strong><br/>Mean: ${v.mean.toFixed(3)} · Spread: \u00B1${spread}<br/><span style="opacity:0.6">${seedStr}</span>`)
.style('left',Math.min(e.offsetX+12,W-180)+'px').style('top',(e.offsetY-50)+'px');
}).on('mouseleave',()=>tip.style('display','none'));
});
// Best indicator
const best = d3.max(means);
const bestI = means.indexOf(best);
g.append('text').attr('x',x(methods[bestI])+x.bandwidth()/2).attr('y',y(values[bestI].hi)-22)
.attr('text-anchor','middle').attr('fill',barColors[bestI]).attr('font-size',9).attr('opacity',0.7)
.text('\u2605 best');
// Axes
g.append('g').attr('transform',`translate(0,${h})`).call(d3.axisBottom(x).tickSize(0))
.selectAll('text').attr('fill','var(--text-color)').attr('font-size',10);
g.append('g').call(d3.axisLeft(y).ticks(4).tickFormat(d3.format('.2f')))
.selectAll('text').attr('fill','var(--tick-color)');
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('AUC');
svg.selectAll('.domain').attr('stroke','var(--axis-color)');
svg.selectAll('.tick line').attr('stroke','var(--axis-color)');
}
select.addEventListener('change', () => draw(select.value));
new ResizeObserver(() => draw(select.value)).observe(document.getElementById('clf-svg'));
})();
</script>