Spaces:
Paused
Paused
| <!-- 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> | |