AI_Zig/visualizer.html

170 lines
6.7 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Zig MNIST Visualizer</title>
<style>
body {
margin: 0;
background-color: #111;
color: #eee;
font-family: monospace;
overflow: auto; /* Abilita lo scroll */
}
#info {
position: fixed; /* Rimane fisso mentre scrolli */
top: 10px;
left: 10px;
background: rgba(0,0,0,0.8);
padding: 15px;
border: 1px solid #444;
border-radius: 5px;
z-index: 1000;
}
h1 { margin: 0; font-size: 1.2em; color: #f4a261; }
.stat { font-size: 0.9em; color: #aaa; margin-top: 5px; }
canvas {
display: block;
/* Il canvas non ha dimensioni fisse CSS, le decide JS */
}
</style>
</head>
<body>
<div id="info">
<h1>Zig MNIST Network</h1>
<div class="stat">Epoca: <span id="epoch" style="color: white">Waiting...</span></div>
<div class="stat">Loss: <span id="loss" style="color: white">Waiting...</span></div>
<div class="stat" style="font-size: 0.8em; margin-top:10px; border-top: 1px solid #444; padding-top:5px">
Scrolla per vedere tutti i 784 Input!<br>
Verde: Pesi Positivi<br>
Rosso: Pesi Negativi
</div>
</div>
<canvas id="netCanvas"></canvas>
<script>
const canvas = document.getElementById('netCanvas');
const ctx = canvas.getContext('2d');
const epochEl = document.getElementById('epoch');
const lossEl = document.getElementById('loss');
// Configurazione Spaziatura
const MIN_NODE_SPACING = 20; // Spazio minimo verticale tra i nodi (pixel)
const LAYER_PADDING = 100; // Margine sopra e sotto
function drawNetwork(data) {
const layers = data.layers;
// Ricostruiamo la struttura completa [Input, Hidden1, Hidden2, Output]
const structure = [layers[0].inputs];
layers.forEach(l => structure.push(l.neurons));
// 1. CALCOLO DIMENSIONI CANVAS
// Troviamo il layer più grande (probabilmente l'input con 784)
const maxNeurons = Math.max(...structure);
// Calcoliamo l'altezza necessaria
const requiredHeight = (maxNeurons * MIN_NODE_SPACING) + (LAYER_PADDING * 2);
// Ridimensioniamo il canvas se necessario (evita flickering se la size non cambia)
if (canvas.height !== requiredHeight || canvas.width !== window.innerWidth) {
canvas.width = window.innerWidth;
canvas.height = Math.max(window.innerHeight, requiredHeight);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
const layerWidth = canvas.width / structure.length;
let nodePositions = [];
// 2. CALCOLO POSIZIONI NODI
structure.forEach((neuronCount, layerIdx) => {
const x = (layerIdx * layerWidth) + (layerWidth / 2);
const layerNodes = [];
// Calcoliamo lo spazio disponibile per QUESTO layer
// Se sono pochi neuroni, li spalmiamo su tutta l'altezza del canvas per estetica
// Se sono tanti, usiamo lo spacing minimo
let availableHeight = canvas.height - (LAYER_PADDING * 2);
let spacing = availableHeight / neuronCount;
// Cap sulla spaziatura massima per non averli troppo distanti nei layer piccoli
if (spacing > 100) spacing = 100;
// Altezza totale occupata da questo layer
const layerTotalHeight = spacing * neuronCount;
const startY = (canvas.height / 2) - (layerTotalHeight / 2);
for (let i = 0; i < neuronCount; i++) {
const y = startY + (i * spacing);
layerNodes.push({x, y});
}
nodePositions.push(layerNodes);
});
// 3. DISEGNO CONNESSIONI (Pesi)
// Per MNIST, disegniamo solo connessioni forti per non uccidere la CPU del browser
const DRAW_THRESHOLD = 0.05; // Disegna solo se peso > 0.05
layers.forEach((layer, lIdx) => {
const sourceNodes = nodePositions[lIdx];
const targetNodes = nodePositions[lIdx + 1];
targetNodes.forEach((target, neuronIdx) => {
sourceNodes.forEach((source, inputIdx) => {
const weightIdx = (neuronIdx * layer.inputs) + inputIdx;
const weight = layer.weights[weightIdx];
// Ottimizzazione: salta pesi quasi nulli
if (Math.abs(weight) < DRAW_THRESHOLD) return;
ctx.beginPath();
ctx.moveTo(source.x, source.y);
ctx.lineTo(target.x, target.y);
const alpha = Math.min(Math.abs(weight), 0.8);
ctx.strokeStyle = weight > 0 ? `rgba(0, 255, 128, ${alpha})`
: `rgba(255, 60, 60, ${alpha})`;
ctx.lineWidth = 1;
ctx.stroke();
});
});
});
// 4. DISEGNO NODI
nodePositions.forEach((layerNodes, lIdx) => {
// Dimensione dinamica del nodo: più ce ne sono, più sono piccoli
const nodeRadius = Math.max(3, Math.min(15, 300 / layerNodes.length));
layerNodes.forEach((node) => {
ctx.beginPath();
ctx.arc(node.x, node.y, nodeRadius, 0, Math.PI * 2);
ctx.fillStyle = '#222';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.stroke();
});
});
}
async function update() {
try {
const response = await fetch('network_state.json?t=' + new Date().getTime());
if (!response.ok) throw new Error("File missing");
const data = await response.json();
epochEl.innerText = data.epoch;
lossEl.innerText = data.loss;
drawNetwork(data);
} catch (e) {
// console.log("Waiting...", e);
}
}
setInterval(update, 100); // Aggiorna ogni 500ms
window.addEventListener('resize', () => canvas.width = window.innerWidth);
</script>
</body>
</html>