21 lines
14 KiB
HTML
21 lines
14 KiB
HTML
<!doctype html><html lang="ru"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Кот или пёс — AltAI</title>
|
||
<style>
|
||
:root{color-scheme:dark;--bg:#111512;--panel:#1b211c;--line:#343e35;--ink:#f1f3e9;--muted:#a6b1a4;--lime:#d1f88a;--orange:#ffa875}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 system-ui,sans-serif}main{max-width:1260px;padding:32px 28px 56px;margin:auto}header{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:38px}.brand{display:flex;gap:10px;align-items:center;font-weight:750;letter-spacing:-.6px}.paw{width:38px;height:38px;color:var(--lime)}.chip{font:11px ui-monospace,monospace;border:1px solid var(--line);border-radius:30px;padding:7px 12px;color:var(--lime)}a{color:var(--muted);text-decoration:none}a:hover{color:var(--lime)}.hero{display:flex;justify-content:space-between;gap:30px;align-items:end;margin-bottom:28px}h1{font-size:clamp(40px,6vw,70px);line-height:1.04;letter-spacing:-3px;margin:0 0 16px;font-weight:650}h1 span{color:var(--lime)}.intro{max-width:660px;color:var(--muted);margin:0;font-size:17px}.metric{border-left:1px solid var(--line);padding-left:28px;flex-shrink:0}.metric strong{font-size:40px;letter-spacing:-2px;display:block;line-height:1.2}.metric small{color:var(--muted);font-size:12px}.grid{display:grid;grid-template-columns:1fr 1.05fr;gap:18px}.panel{background:var(--panel);border:1px solid var(--line);border-radius:22px;padding:22px}.eyebrow{font:11px ui-monospace,monospace;letter-spacing:1.6px;text-transform:uppercase;color:var(--muted);margin-bottom:18px}.upload{height:280px;border:1px dashed #56604d;border-radius:14px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;cursor:pointer;background:#171c17}.upload.drag{border-color:var(--lime);background:#263321}.upload img{height:100%;width:100%;object-fit:contain}.placeholder{text-align:center;padding:20px;color:var(--muted)}.placeholder b{color:var(--ink);font-size:21px;font-weight:550;display:block;margin:8px}.placeholder svg{width:58px;height:58px;color:var(--lime)}input[type=file]{display:none}button{font:inherit;cursor:pointer;border:0;border-radius:11px;padding:12px 18px;background:var(--lime);color:#17200e;font-weight:650;transition:transform .15s}button:hover{transform:translateY(-1px)}button:disabled{opacity:.6;cursor:wait;transform:none}.actions{display:flex;gap:10px;margin-top:14px;align-items:center}.secondary{background:transparent;color:var(--ink);border:1px solid var(--line);font-size:14px}.hint{font-size:12px;color:var(--muted);margin:12px 0 0}.result{min-height:122px;display:flex;gap:18px;align-items:center}.animal{font-size:63px;line-height:1}.animal svg{width:64px;height:64px;display:block;color:var(--lime)}.answer{font-size:38px;letter-spacing:-1.6px;line-height:1.15;margin:0}.sub{color:var(--muted);font-size:13px;margin-top:8px;min-height:20px}.status{display:inline-block;color:var(--lime);font:11px ui-monospace,monospace;margin-top:6px}.views{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px;padding-top:20px;border-top:1px solid var(--line)}.views canvas,.views img{display:block;width:100%;aspect-ratio:1;object-fit:contain;background:#121712;border-radius:10px;image-rendering:pixelated}.views figcaption{font-size:12px;color:var(--muted);margin-top:8px}figure{margin:0}.signals{display:flex;gap:7px;flex-wrap:wrap;margin-top:15px}.signals span{background:#252e25;color:var(--muted);border-radius:6px;font:11px ui-monospace,monospace;padding:5px 8px}.gallery{margin-top:22px}.galleryhead{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:13px}.galleryhead h2{font-size:18px;letter-spacing:-.4px;margin:0}.galleryhead small{color:var(--muted);font-size:12px}.samples{display:grid;grid-template-columns:repeat(12,1fr);gap:9px}.sample{padding:0;aspect-ratio:1;background:#232c24;border:2px solid transparent;border-radius:9px;overflow:hidden;position:relative}.sample img{width:100%;height:100%;display:block;image-rendering:auto}.sample.active{border-color:var(--lime)}.sample:focus-visible{outline:2px solid var(--orange);outline-offset:3px}.notes{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:30px;color:var(--muted);font-size:13px}.notes b{color:var(--ink);display:block;margin-bottom:5px;font-size:14px}.loading .animal{animation:pulse 1s infinite}.error{color:var(--orange)}footer{border-top:1px solid var(--line);margin-top:30px;padding-top:16px;color:var(--muted);font:11px ui-monospace,monospace;display:flex;justify-content:space-between;gap:20px}@keyframes pulse{50%{opacity:.35}}@media(max-width:700px){main{padding:20px 16px 32px}header{margin-bottom:28px}.hero{display:block}.metric{border-left:0;padding:0;margin-top:20px;display:flex;gap:12px;align-items:center}.metric strong{font-size:30px}.grid{grid-template-columns:1fr}.upload{height:250px}.samples{grid-template-columns:repeat(6,1fr)}.notes{grid-template-columns:1fr;gap:14px}h1{letter-spacing:-1.8px}.panel{padding:18px}.galleryhead{display:block}.views{gap:12px}footer{flex-wrap:wrap}.brand{font-size:15px}.chip{font-size:9px;padding:6px 8px}.hero{margin-bottom:22px}}
|
||
</style><main>
|
||
<header><div class="brand"><svg class="paw" viewBox="0 0 40 40" fill="currentColor"><ellipse cx="20" cy="28" rx="11" ry="8"/><ellipse cx="8" cy="17" rx="4" ry="5" transform="rotate(-25 8 17)"/><ellipse cx="16" cy="10" rx="4" ry="5"/><ellipse cx="25" cy="10" rx="4" ry="5"/><ellipse cx="33" cy="17" rx="4" ry="5" transform="rotate(25 33 17)"/></svg>AltAI · Pet lab</div><span class="chip">● KNP × GPU</span></header>
|
||
<section class="hero"><div><h1>Кот? Пёс?<br><span>Спросим нейроны.</span></h1><p class="intro">Загрузите фото или выберите пример. Спайковые детекторы найдут признаки, а выходные нейроны KNP выберут свой класс.</p></div><div class="metric"><strong id="accuracy">—</strong><small id="metricnote">точность на отдельном<br>тесте CIFAR-10</small></div></section>
|
||
<section class="grid"><article class="panel"><div class="eyebrow">01 / Что показываем сети</div><div class="upload" id="drop" role="button" tabindex="0" aria-label="Выбрать фотографию"><div class="placeholder" id="placeholder"><svg viewBox="0 0 60 60" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="9" y="9" width="42" height="42" rx="9"/><circle cx="23" cy="23" r="4"/><path d="M10 44l12-12 10 8 8-13 11 17"/></svg><b>Киньте сюда кота. Или пса.</b>Нажмите или перетащите фото</div><img id="photo" alt="Выбранное изображение" hidden></div><input type="file" id="file" accept="image/png,image/jpeg,image/webp,image/bmp"><div class="actions"><button id="run" disabled>Распознать →</button><button class="secondary" id="choose">Другое фото</button></div><p class="hint">PNG, JPG, WebP · до 8 МБ и 16 Мп · лучше один зверь крупным планом</p></article>
|
||
<article class="panel" id="result"><div class="eyebrow">02 / Что ответила сеть</div><div class="result" aria-live="polite"><div class="animal" id="animal">✳</div><div><p class="answer" id="answer">Кто там?</p><div class="sub" id="subtitle">Выберите картинку — и запустим спайки.</div><span class="status" id="status">ОЖИДАЮ ИЗОБРАЖЕНИЕ</span></div></div><div class="views"><figure><img id="tiny" alt="Вход сети 32 на 32 пикселя"><figcaption>Так сеть видит фото · 32 × 32</figcaption></figure><figure><canvas id="heat" width="196" height="196" aria-label="Карта активности детекторов"></canvas><figcaption>Где сработали детекторы</figcaption></figure></div><div class="signals"><span id="spikes">— спайков</span><span id="timing">— мс</span><span id="output">выходы: —</span></div></article></section>
|
||
<section class="gallery"><div class="galleryhead"><h2>Проверим на примерах</h2><small>24 фото из отдельного test · без отбора удачных ответов</small></div><div class="samples" id="samples"></div><p class="hint">Выбор примера сразу запускает KNP. Правильный класс покажем вместе с ответом.</p></section>
|
||
<section class="notes"><div><b>Настоящие спайковые слои</b>Свёрточные AltAILIF-детекторы и обученный выход работают в KNP на GPU. Хост суммирует и нормализует спайки по областям. Обученный скрытый слой и выход тоже исполняются в KNP.</div><div><b>Маленькие фото — сложная задача</b>Модель училась на CIFAR-10, где фото всего 32 × 32. На ваших снимках качество может отличаться от тестовой оценки.</div><div><b>Только кот или пёс</b>Модель не проверяет, есть ли животное на фото. На людях, предметах и пустом фоне она тоже выберет один из двух классов.</div></section>
|
||
<footer><span>OFFLINE TRAINING / KNP CUDA INFERENCE<br>GoldenModel для этой модели пока не проверен</span><a href="http://10.9.8.230:8081/native_viz.html">Рисовать цифры ↗</a></footer></main>
|
||
<script>
|
||
const $=id=>document.getElementById(id);let current=null,truth=null,seq=0,busy=false;
|
||
const blank=document.createElement('canvas');blank.width=blank.height=32;$('tiny').src=blank.toDataURL();const ctx=$('heat').getContext('2d');ctx.fillStyle='#121712';ctx.fillRect(0,0,196,196);
|
||
function select(uri,label=null,id=null){seq++;current=uri;truth=label;$('photo').src=uri;$('photo').hidden=false;$('placeholder').hidden=true;$('run').disabled=busy;$('answer').textContent='Готовы?';$('subtitle').textContent='Нажмите «Распознать», чтобы запустить сеть.';$('animal').textContent='✳';$('status').textContent='ИЗОБРАЖЕНИЕ ВЫБРАНО';$('tiny').src=blank.toDataURL();ctx.clearRect(0,0,196,196);$('spikes').textContent='— спайков';$('timing').textContent='— мс';$('output').textContent='выходы: —';document.querySelectorAll('.sample').forEach(b=>b.classList.toggle('active',b.dataset.id===String(id)));}
|
||
async function run(){if(!current||busy)return;const mine=seq;busy=true;$('run').disabled=true;$('result').classList.add('loading');$('answer').textContent='Думаем…';$('subtitle').textContent='Пропускаем фото через спайковые слои KNP.';$('status').textContent='GPU · СЧИТАЕМ СПАЙКИ';try{const r=await fetch('/api/recognize',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({image:current})});const d=await r.json();if(!r.ok)throw Error(d.error);if(mine!==seq)return;$('answer').textContent=d.prediction?'Это пёс':'Это кот';$('animal').innerHTML=d.prediction?'<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21c-2-16-16-5-13 12 1 8 7 7 12 1m31-13c2-16 16-5 13 12-1 8-7 7-12 1M16 26c0-21 32-21 32 0v16c0 21-32 21-32 0Z"/><path d="M24 31h1m14 0h1M27 40l5 4 5-4M32 44v5m-6 0c0 7 12 7 12 0"/></svg>':'<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 29V10l16 10h8L52 10v19c9 23-2 29-20 29S3 52 12 29Z"/><path d="M22 35h2m16 0h2M28 43l4 3 4-3M32 46v5m-19-9-9-2m9 9-9 2m47-9 9-2m-9 9 9 2"/></svg>';$('subtitle').textContent=truth===null?'Ответ спайковых нейронов, не вероятность.':(d.prediction===truth?'Верно!':'Промах.')+' В тесте это '+(truth?'собака':'кошка')+'.';$('status').textContent=d.tie?'НИЧЬЯ · ВЫБРАН КЛАСС «КОТ»':'KNP CUDA · ГОТОВО';$('tiny').src=d.input;const max=Math.max(...d.heat,1);for(let y=0;y<14;y++)for(let x=0;x<14;x++){let v=d.heat[y*14+x]/max;ctx.fillStyle=`rgb(${Math.round(28+181*v)},${Math.round(38+210*v)},${Math.round(29+109*v)})`;ctx.fillRect(x*14,y*14,13,13)}$('spikes').textContent=d.detectorSpikes.toLocaleString('ru')+' спайков';$('timing').textContent=(d.milliseconds/1000).toFixed(2)+' с';$('output').textContent='кот '+d.outputs[0]+' · пёс '+d.outputs[1];}catch(e){if(mine===seq){$('answer').textContent='Не вышло';$('subtitle').textContent=e.message;$('status').textContent='ПОПРОБУЙТЕ ЕЩЁ РАЗ';}}finally{busy=false;$('run').disabled=!current;$('result').classList.remove('loading');}}
|
||
function upload(file){if(!file)return;if(file.size>8000000){$('subtitle').textContent='Файл больше 8 МБ';return}const reader=new FileReader();reader.onload=()=>select(reader.result);reader.readAsDataURL(file)}
|
||
$('run').onclick=run;$('choose').onclick=()=>$('file').click();$('drop').onclick=()=>$('file').click();$('drop').onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();$('file').click()}};$('file').onchange=e=>{upload(e.target.files[0]);e.target.value=''};for(const event of ['dragover','dragenter'])$('drop').addEventListener(event,e=>{e.preventDefault();$('drop').classList.add('drag')});for(const event of ['drop','dragleave'])$('drop').addEventListener(event,e=>{e.preventDefault();$('drop').classList.remove('drag');if(event==='drop')upload(e.dataTransfer.files[0])});
|
||
fetch('/api/info').then(r=>r.json()).then(d=>{$('accuracy').textContent=(100*d.test_accuracy).toFixed(1).replace('.',',')+'%';$('metricnote').innerHTML='на '+d.test_total.toLocaleString('ru')+' test-фото<br>CIFAR-10 · кошки / собаки'}).catch(()=>{$('metricnote').textContent='Метрики временно недоступны'});
|
||
fetch('/api/samples').then(r=>r.json()).then(rows=>{for(const s of rows){const b=document.createElement('button');b.className='sample';b.dataset.id=s.id;b.setAttribute('aria-label','Тестовое фото '+s.id);const im=document.createElement('img');im.src=s.image;im.alt='Пример из отдельного теста';b.append(im);b.onclick=()=>{if(busy)return;select(s.image,s.label,s.id);run()};$('samples').append(b)}}).catch(()=>{$('samples').textContent='Не удалось загрузить примеры'});
|
||
</script></html>
|