Pulizia codice
This commit is contained in:
parent
9653eada03
commit
ed8080e2d6
56
src/env.zig
56
src/env.zig
|
|
@ -1,16 +1,13 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
pub const GRID_SIZE = 100; // Mappa Gigante!
|
pub const GRID_SIZE = 100;
|
||||||
pub const NUM_ANTS = 20; // La Colonia
|
pub const NUM_ANTS = 20;
|
||||||
|
|
||||||
pub const TILE_EMPTY = 0;
|
pub const TILE_EMPTY = 0;
|
||||||
pub const TILE_WALL = 1;
|
pub const TILE_WALL = 1;
|
||||||
pub const TILE_FOOD = 2;
|
pub const TILE_FOOD = 2;
|
||||||
|
|
||||||
// Non c'è più TILE_ANT nella griglia statica, perché le formiche si muovono sopra
|
|
||||||
// Useremo una lista dinamica.
|
|
||||||
|
|
||||||
pub const Ant = struct {
|
pub const Ant = struct {
|
||||||
x: usize,
|
x: usize,
|
||||||
y: usize,
|
y: usize,
|
||||||
|
|
@ -20,8 +17,8 @@ pub const Ant = struct {
|
||||||
|
|
||||||
pub const World = struct {
|
pub const World = struct {
|
||||||
grid: [GRID_SIZE][GRID_SIZE]u8,
|
grid: [GRID_SIZE][GRID_SIZE]u8,
|
||||||
pheromones: [GRID_SIZE][GRID_SIZE]f32, // 0.0 = Neutro, 1.0 = Appena visitato
|
pheromones: [GRID_SIZE][GRID_SIZE]f32,
|
||||||
ants: [NUM_ANTS]Ant, // Array fisso di formiche
|
ants: [NUM_ANTS]Ant,
|
||||||
food_x: usize,
|
food_x: usize,
|
||||||
food_y: usize,
|
food_y: usize,
|
||||||
max_steps: usize,
|
max_steps: usize,
|
||||||
|
|
@ -34,7 +31,7 @@ pub const World = struct {
|
||||||
.ants = undefined,
|
.ants = undefined,
|
||||||
.food_x = 0,
|
.food_x = 0,
|
||||||
.food_y = 0,
|
.food_y = 0,
|
||||||
.max_steps = 1000, // Più passi per mappa grande
|
.max_steps = 1000,
|
||||||
.prng = std.Random.DefaultPrng.init(seed),
|
.prng = std.Random.DefaultPrng.init(seed),
|
||||||
};
|
};
|
||||||
w.reset();
|
w.reset();
|
||||||
|
|
@ -42,8 +39,6 @@ pub const World = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(self: *World) void {
|
pub fn reset(self: *World) void {
|
||||||
//const random = self.prng.random();
|
|
||||||
|
|
||||||
for (0..GRID_SIZE) |y| {
|
for (0..GRID_SIZE) |y| {
|
||||||
for (0..GRID_SIZE) |x| {
|
for (0..GRID_SIZE) |x| {
|
||||||
self.pheromones[y][x] = 0.0;
|
self.pheromones[y][x] = 0.0;
|
||||||
|
|
@ -55,10 +50,8 @@ pub const World = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Spawn Cibo
|
|
||||||
self.respawnFood();
|
self.respawnFood();
|
||||||
|
|
||||||
// 3. Spawn Formiche (Tutte al centro, come un formicaio)
|
|
||||||
const center = GRID_SIZE / 2;
|
const center = GRID_SIZE / 2;
|
||||||
for (0..NUM_ANTS) |i| {
|
for (0..NUM_ANTS) |i| {
|
||||||
self.ants[i] = Ant{
|
self.ants[i] = Ant{
|
||||||
|
|
@ -72,7 +65,6 @@ pub const World = struct {
|
||||||
|
|
||||||
fn respawnFood(self: *World) void {
|
fn respawnFood(self: *World) void {
|
||||||
const random = self.prng.random();
|
const random = self.prng.random();
|
||||||
// Semplice loop per trovare posto libero
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const rx = random.intRangeAtMost(usize, 1, GRID_SIZE - 2);
|
const rx = random.intRangeAtMost(usize, 1, GRID_SIZE - 2);
|
||||||
const ry = random.intRangeAtMost(usize, 1, GRID_SIZE - 2);
|
const ry = random.intRangeAtMost(usize, 1, GRID_SIZE - 2);
|
||||||
|
|
@ -84,8 +76,6 @@ pub const World = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcola l'olfatto (Distanza inversa dal cibo)
|
|
||||||
// 0.0 (Lontanissimo) -> 1.0 (Sopra il cibo)
|
|
||||||
fn getScent(self: *World, x: usize, y: usize) f32 {
|
fn getScent(self: *World, x: usize, y: usize) f32 {
|
||||||
const dx = if (x > self.food_x) x - self.food_x else self.food_x - x;
|
const dx = if (x > self.food_x) x - self.food_x else self.food_x - x;
|
||||||
const dy = if (y > self.food_y) y - self.food_y else self.food_y - y;
|
const dy = if (y > self.food_y) y - self.food_y else self.food_y - y;
|
||||||
|
|
@ -93,7 +83,6 @@ pub const World = struct {
|
||||||
return 1.0 / (dist + 1.0);
|
return 1.0 / (dist + 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controlla se una cella è occupata da UN'ALTRA formica
|
|
||||||
fn isOccupied(self: *World, x: usize, y: usize) bool {
|
fn isOccupied(self: *World, x: usize, y: usize) bool {
|
||||||
for (self.ants) |ant| {
|
for (self.ants) |ant| {
|
||||||
if (ant.alive and ant.x == x and ant.y == y) return true;
|
if (ant.alive and ant.x == x and ant.y == y) return true;
|
||||||
|
|
@ -101,14 +90,12 @@ pub const World = struct {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Esegue step per UNA formica specifica
|
|
||||||
pub fn stepAnt(self: *World, ant_idx: usize, action: usize) struct { f32, bool } {
|
pub fn stepAnt(self: *World, ant_idx: usize, action: usize) struct { f32, bool } {
|
||||||
var ant = &self.ants[ant_idx];
|
var ant = &self.ants[ant_idx];
|
||||||
if (!ant.alive) return .{ 0.0, true };
|
if (!ant.alive) return .{ 0.0, true };
|
||||||
|
|
||||||
ant.steps += 1;
|
ant.steps += 1;
|
||||||
|
|
||||||
// Salviamo lo stato PRECEDENTE per fare i confronti
|
|
||||||
const old_dist_x = if (ant.x > self.food_x) ant.x - self.food_x else self.food_x - ant.x;
|
const old_dist_x = if (ant.x > self.food_x) ant.x - self.food_x else self.food_x - ant.x;
|
||||||
const old_dist_y = if (ant.y > self.food_y) ant.y - self.food_y else self.food_y - ant.y;
|
const old_dist_y = if (ant.y > self.food_y) ant.y - self.food_y else self.food_y - ant.y;
|
||||||
const old_dist = old_dist_x + old_dist_y;
|
const old_dist = old_dist_x + old_dist_y;
|
||||||
|
|
@ -116,18 +103,14 @@ pub const World = struct {
|
||||||
var new_x = ant.x;
|
var new_x = ant.x;
|
||||||
var new_y = ant.y;
|
var new_y = ant.y;
|
||||||
|
|
||||||
if (action == 0) new_y -= 1; // UP
|
if (action == 0) new_y -= 1;
|
||||||
if (action == 1) new_y += 1; // DOWN
|
if (action == 1) new_y += 1;
|
||||||
if (action == 2) new_x -= 1; // LEFT
|
if (action == 2) new_x -= 1;
|
||||||
if (action == 3) new_x += 1; // RIGHT
|
if (action == 3) new_x += 1;
|
||||||
|
|
||||||
// --- 1. MURI (Limiti Mappa) ---
|
|
||||||
if (self.grid[new_y][new_x] == TILE_WALL) return .{ -5.0, false };
|
if (self.grid[new_y][new_x] == TILE_WALL) return .{ -5.0, false };
|
||||||
|
|
||||||
// --- 2. COLLISIONI TRA FORMICHE ---
|
|
||||||
// Se c'è traffico, penalità leggera ma non bloccante se c'è cibo vicino
|
|
||||||
if (self.isOccupied(new_x, new_y)) {
|
if (self.isOccupied(new_x, new_y)) {
|
||||||
// Se siamo vicini al cibo, spingi! Altrimenti aspetta.
|
|
||||||
if (old_dist > 5) return .{ -0.5, false };
|
if (old_dist > 5) return .{ -0.5, false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,16 +171,14 @@ pub const World = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input aumentati a 15
|
|
||||||
pub fn getAntObservation(self: *World, allocator: Allocator, ant_idx: usize) ![]f32 {
|
pub fn getAntObservation(self: *World, allocator: Allocator, ant_idx: usize) ![]f32 {
|
||||||
var obs = try allocator.alloc(f32, 15); // AUMENTATO A 15
|
var obs = try allocator.alloc(f32, 15);
|
||||||
const ant = self.ants[ant_idx];
|
const ant = self.ants[ant_idx];
|
||||||
var idx: usize = 0;
|
var idx: usize = 0;
|
||||||
|
|
||||||
const ax = @as(i32, @intCast(ant.x));
|
const ax = @as(i32, @intCast(ant.x));
|
||||||
const ay = @as(i32, @intCast(ant.y));
|
const ay = @as(i32, @intCast(ant.y));
|
||||||
|
|
||||||
// 1. Visione 3x3 (0-8)
|
|
||||||
var dy: i32 = -1;
|
var dy: i32 = -1;
|
||||||
while (dy <= 1) : (dy += 1) {
|
while (dy <= 1) : (dy += 1) {
|
||||||
var dx: i32 = -1;
|
var dx: i32 = -1;
|
||||||
|
|
@ -209,27 +190,22 @@ pub const World = struct {
|
||||||
if (self.grid[py][px] == TILE_WALL) {
|
if (self.grid[py][px] == TILE_WALL) {
|
||||||
val = -1.0;
|
val = -1.0;
|
||||||
} else if (px == self.food_x and py == self.food_y) {
|
} else if (px == self.food_x and py == self.food_y) {
|
||||||
val = 1.0; // Vedo il cibo vicino!
|
val = 1.0;
|
||||||
} else if (self.isOccupied(px, py) and (dx != 0 or dy != 0)) {
|
} else if (self.isOccupied(px, py) and (dx != 0 or dy != 0)) {
|
||||||
val = -0.5; // Vedo una sorella
|
val = -0.5;
|
||||||
}
|
}
|
||||||
obs[idx] = val;
|
obs[idx] = val;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Sensi Chimici (9-10)
|
|
||||||
obs[9] = self.getScent(ant.x, ant.y);
|
obs[9] = self.getScent(ant.x, ant.y);
|
||||||
obs[10] = self.pheromones[ant.y][ant.x];
|
obs[10] = self.pheromones[ant.y][ant.x];
|
||||||
|
|
||||||
// 3. BUSSOLA BINARIA (11-14)
|
obs[11] = if (self.food_y < ant.y) 1.0 else 0.0;
|
||||||
// Risponde alla domanda: "In che quadrante è il cibo?"
|
obs[12] = if (self.food_y > ant.y) 1.0 else 0.0;
|
||||||
// È molto più facile da capire per l'IA rispetto a un float.
|
obs[13] = if (self.food_x < ant.x) 1.0 else 0.0;
|
||||||
|
obs[14] = if (self.food_x > ant.x) 1.0 else 0.0;
|
||||||
obs[11] = if (self.food_y < ant.y) 1.0 else 0.0; // Cibo è SOPRA?
|
|
||||||
obs[12] = if (self.food_y > ant.y) 1.0 else 0.0; // Cibo è SOTTO?
|
|
||||||
obs[13] = if (self.food_x < ant.x) 1.0 else 0.0; // Cibo è SINISTRA?
|
|
||||||
obs[14] = if (self.food_x > ant.x) 1.0 else 0.0; // Cibo è DESTRA?
|
|
||||||
|
|
||||||
return obs;
|
return obs;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ pub const DenseLayer = struct {
|
||||||
output: []f32,
|
output: []f32,
|
||||||
inputs_count: usize,
|
inputs_count: usize,
|
||||||
neurons_count: usize,
|
neurons_count: usize,
|
||||||
use_sigmoid: bool, // NUOVO CAMPO
|
use_sigmoid: bool,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
fn sigmoid(x: f32) f32 {
|
fn sigmoid(x: f32) f32 {
|
||||||
|
|
@ -21,7 +21,6 @@ pub const DenseLayer = struct {
|
||||||
return x * (1.0 - x);
|
return x * (1.0 - x);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggiunto parametro 'use_sigmoid'
|
|
||||||
pub fn init(allocator: Allocator, inputs: usize, neurons: usize, seed: u64, use_sigmoid: bool) !DenseLayer {
|
pub fn init(allocator: Allocator, inputs: usize, neurons: usize, seed: u64, use_sigmoid: bool) !DenseLayer {
|
||||||
const weights = try allocator.alloc(f32, inputs * neurons);
|
const weights = try allocator.alloc(f32, inputs * neurons);
|
||||||
const biases = try allocator.alloc(f32, neurons);
|
const biases = try allocator.alloc(f32, neurons);
|
||||||
|
|
@ -30,7 +29,6 @@ pub const DenseLayer = struct {
|
||||||
var prng = std.Random.DefaultPrng.init(seed);
|
var prng = std.Random.DefaultPrng.init(seed);
|
||||||
const random = prng.random();
|
const random = prng.random();
|
||||||
|
|
||||||
// Pesi più piccoli per stabilità iniziale
|
|
||||||
for (weights) |*w| w.* = (random.float(f32) * 2.0 - 1.0) * 0.1;
|
for (weights) |*w| w.* = (random.float(f32) * 2.0 - 1.0) * 0.1;
|
||||||
for (biases) |*b| b.* = 0.0;
|
for (biases) |*b| b.* = 0.0;
|
||||||
|
|
||||||
|
|
@ -56,7 +54,6 @@ pub const DenseLayer = struct {
|
||||||
var sum: f32 = self.biases[n];
|
var sum: f32 = self.biases[n];
|
||||||
const w_start = n * self.inputs_count;
|
const w_start = n * self.inputs_count;
|
||||||
|
|
||||||
// SIMD
|
|
||||||
var vec_sum: Vec = @splat(0.0);
|
var vec_sum: Vec = @splat(0.0);
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i + SimdWidth <= self.inputs_count) : (i += SimdWidth) {
|
while (i + SimdWidth <= self.inputs_count) : (i += SimdWidth) {
|
||||||
|
|
@ -66,12 +63,10 @@ pub const DenseLayer = struct {
|
||||||
}
|
}
|
||||||
sum += @reduce(.Add, vec_sum);
|
sum += @reduce(.Add, vec_sum);
|
||||||
|
|
||||||
// Tail Loop
|
|
||||||
while (i < self.inputs_count) : (i += 1) {
|
while (i < self.inputs_count) : (i += 1) {
|
||||||
sum += input[i] * self.weights[w_start + i];
|
sum += input[i] * self.weights[w_start + i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// CORREZIONE: Se non usiamo sigmoide, è lineare (passa 'sum' diretto)
|
|
||||||
if (self.use_sigmoid) {
|
if (self.use_sigmoid) {
|
||||||
self.output[n] = sigmoid(sum);
|
self.output[n] = sigmoid(sum);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -86,9 +81,6 @@ pub const DenseLayer = struct {
|
||||||
@memset(input_gradient, 0.0);
|
@memset(input_gradient, 0.0);
|
||||||
|
|
||||||
for (0..self.neurons_count) |n| {
|
for (0..self.neurons_count) |n| {
|
||||||
// CORREZIONE DERIVATA:
|
|
||||||
// Se Sigmoide: f'(x) = out * (1 - out)
|
|
||||||
// Se Lineare: f'(x) = 1.0
|
|
||||||
var derivative: f32 = 1.0;
|
var derivative: f32 = 1.0;
|
||||||
if (self.use_sigmoid) {
|
if (self.use_sigmoid) {
|
||||||
derivative = sigmoidDerivative(self.output[n]);
|
derivative = sigmoidDerivative(self.output[n]);
|
||||||
|
|
@ -99,7 +91,6 @@ pub const DenseLayer = struct {
|
||||||
|
|
||||||
self.biases[n] -= learning_rate * delta;
|
self.biases[n] -= learning_rate * delta;
|
||||||
|
|
||||||
// SIMD LOOP (Backprop)
|
|
||||||
const v_delta: Vec = @splat(delta);
|
const v_delta: Vec = @splat(delta);
|
||||||
const v_lr: Vec = @splat(learning_rate);
|
const v_lr: Vec = @splat(learning_rate);
|
||||||
const v_change_factor = v_delta * v_lr;
|
const v_change_factor = v_delta * v_lr;
|
||||||
|
|
|
||||||
23
src/main.zig
23
src/main.zig
|
|
@ -64,14 +64,10 @@ pub fn main() !void {
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
defer _ = gpa.deinit();
|
defer _ = gpa.deinit();
|
||||||
|
|
||||||
// 1. Inizializza Ambiente 100x100 e Rete
|
|
||||||
var world = World.init(12345);
|
var world = World.init(12345);
|
||||||
var net = Network.init(allocator);
|
var net = Network.init(allocator);
|
||||||
defer net.deinit();
|
defer net.deinit();
|
||||||
|
|
||||||
// ARCHITETTURA RETE
|
|
||||||
// Input 11: (9 Vista + 1 Olfatto + 1 Feromone)
|
|
||||||
// Output 4: (Su, Giù, Sx, Dx) LINEARE
|
|
||||||
try net.addLayer(15, 40, 111, true);
|
try net.addLayer(15, 40, 111, true);
|
||||||
try net.addLayer(40, 4, 222, false);
|
try net.addLayer(40, 4, 222, false);
|
||||||
|
|
||||||
|
|
@ -84,19 +80,13 @@ pub fn main() !void {
|
||||||
var global_step: usize = 0;
|
var global_step: usize = 0;
|
||||||
var epsilon: f32 = EPSILON_START;
|
var epsilon: f32 = EPSILON_START;
|
||||||
|
|
||||||
// Ciclo infinito (o molto lungo)
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
// 1. Evaporazione Feromoni (Memoria collettiva che sbiadisce)
|
|
||||||
world.evaporatePheromones();
|
world.evaporatePheromones();
|
||||||
|
|
||||||
// 2. Turno di ogni formica
|
|
||||||
for (0..env.NUM_ANTS) |i| {
|
for (0..env.NUM_ANTS) |i| {
|
||||||
// A. OSSERVAZIONE
|
|
||||||
const current_obs = try world.getAntObservation(allocator, i);
|
const current_obs = try world.getAntObservation(allocator, i);
|
||||||
defer allocator.free(current_obs);
|
defer allocator.free(current_obs);
|
||||||
|
|
||||||
// B. SCEGLI AZIONE (Epsilon-Greedy)
|
|
||||||
var action: usize = 0;
|
var action: usize = 0;
|
||||||
const q_values = net.forward(current_obs);
|
const q_values = net.forward(current_obs);
|
||||||
|
|
||||||
|
|
@ -106,24 +96,16 @@ pub fn main() !void {
|
||||||
action = argmax(q_values);
|
action = argmax(q_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// C. ESEGUI AZIONE
|
|
||||||
const result = world.stepAnt(i, action);
|
const result = world.stepAnt(i, action);
|
||||||
const reward = result[0];
|
const reward = result[0];
|
||||||
// Nota: in multi-agente 'done' è meno rilevante per il loop principale,
|
|
||||||
// perché se una formica "finisce" (mangia), respawna o continua.
|
|
||||||
|
|
||||||
// D. ADDESTRAMENTO (Hive Mind Learning)
|
|
||||||
// Calcoliamo target Q-Value
|
|
||||||
var target_val = reward;
|
var target_val = reward;
|
||||||
|
|
||||||
// Se non è uno stato terminale (morte o vittoria netta), aggiungiamo stima futuro
|
|
||||||
// Consideriamo la vittoria (mangiare) come continuativa qui per non rompere il flusso
|
|
||||||
const next_obs = try world.getAntObservation(allocator, i);
|
const next_obs = try world.getAntObservation(allocator, i);
|
||||||
defer allocator.free(next_obs);
|
defer allocator.free(next_obs);
|
||||||
const next_q_values = net.forward(next_obs);
|
const next_q_values = net.forward(next_obs);
|
||||||
target_val += GAMMA * maxVal(next_q_values);
|
target_val += GAMMA * maxVal(next_q_values);
|
||||||
|
|
||||||
// Backpropagation
|
|
||||||
var target_vector = try allocator.alloc(f32, 4);
|
var target_vector = try allocator.alloc(f32, 4);
|
||||||
defer allocator.free(target_vector);
|
defer allocator.free(target_vector);
|
||||||
for (0..4) |j| target_vector[j] = q_values[j];
|
for (0..4) |j| target_vector[j] = q_values[j];
|
||||||
|
|
@ -132,24 +114,19 @@ pub fn main() !void {
|
||||||
_ = try net.train(current_obs, target_vector, LR);
|
_ = try net.train(current_obs, target_vector, LR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Gestione Loop Globale
|
|
||||||
global_step += 1;
|
global_step += 1;
|
||||||
|
|
||||||
// Decadimento Epsilon
|
|
||||||
if (epsilon > EPSILON_END) {
|
if (epsilon > EPSILON_END) {
|
||||||
epsilon -= DECAY_RATE;
|
epsilon -= DECAY_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Export e Log (Ogni 10 step globali per fluidità)
|
|
||||||
if (global_step % 10 == 0) {
|
if (global_step % 10 == 0) {
|
||||||
try exportAntJSON(&world, "ant_state.json", global_step, epsilon);
|
try exportAntJSON(&world, "ant_state.json", global_step, epsilon);
|
||||||
|
|
||||||
// Log console ogni 100 step
|
|
||||||
if (global_step % 100 == 0) {
|
if (global_step % 100 == 0) {
|
||||||
std.debug.print("Step: {d} | Epsilon: {d:.3} | Cibo: [{d},{d}]\r", .{ global_step, epsilon, world.food_x, world.food_y });
|
std.debug.print("Step: {d} | Epsilon: {d:.3} | Cibo: [{d},{d}]\r", .{ global_step, epsilon, world.food_x, world.food_y });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pausa per vedere l'animazione (rimuovi per training ultra-veloce)
|
|
||||||
std.Thread.sleep(100 * 1_000_000);
|
std.Thread.sleep(100 * 1_000_000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
overflow: auto; /* Abilita lo scroll */
|
overflow: auto;
|
||||||
}
|
}
|
||||||
#info {
|
#info {
|
||||||
position: fixed; /* Rimane fisso mentre scrolli */
|
position: fixed;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
background: rgba(0,0,0,0.8);
|
background: rgba(0,0,0,0.8);
|
||||||
|
|
@ -24,8 +24,7 @@
|
||||||
h1 { margin: 0; font-size: 1.2em; color: #f4a261; }
|
h1 { margin: 0; font-size: 1.2em; color: #f4a261; }
|
||||||
.stat { font-size: 0.9em; color: #aaa; margin-top: 5px; }
|
.stat { font-size: 0.9em; color: #aaa; margin-top: 5px; }
|
||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
/* Il canvas non ha dimensioni fisse CSS, le decide JS */
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -50,20 +49,16 @@
|
||||||
const epochEl = document.getElementById('epoch');
|
const epochEl = document.getElementById('epoch');
|
||||||
const lossEl = document.getElementById('loss');
|
const lossEl = document.getElementById('loss');
|
||||||
|
|
||||||
// Configurazione
|
|
||||||
const MIN_NODE_SPACING = 20;
|
const MIN_NODE_SPACING = 20;
|
||||||
const LAYER_PADDING = 100;
|
const LAYER_PADDING = 100;
|
||||||
|
|
||||||
// --- NUOVO: Funzione per disegnare la cifra input ---
|
|
||||||
function drawInputImage(pixels) {
|
function drawInputImage(pixels) {
|
||||||
if (!pixels || pixels.length === 0) return;
|
if (!pixels || pixels.length === 0) return;
|
||||||
|
|
||||||
// Disegniamo un box in alto a sinistra
|
const scale = 4;
|
||||||
const scale = 4; // Zoom 4x
|
|
||||||
const startX = 20;
|
const startX = 20;
|
||||||
const startY = 120; // Sotto le scritte di info
|
const startY = 120;
|
||||||
|
|
||||||
// Sfondo nero
|
|
||||||
ctx.fillStyle = "#000";
|
ctx.fillStyle = "#000";
|
||||||
ctx.fillRect(startX - 2, startY - 2, (28 * scale) + 4, (28 * scale) + 4);
|
ctx.fillRect(startX - 2, startY - 2, (28 * scale) + 4, (28 * scale) + 4);
|
||||||
ctx.strokeStyle = "#f4a261";
|
ctx.strokeStyle = "#f4a261";
|
||||||
|
|
@ -72,18 +67,16 @@
|
||||||
|
|
||||||
for (let i = 0; i < 784; i++) {
|
for (let i = 0; i < 784; i++) {
|
||||||
const val = pixels[i];
|
const val = pixels[i];
|
||||||
if (val > 0.1) { // Disegna solo se non è nero
|
if (val > 0.1) {
|
||||||
const x = (i % 28);
|
const x = (i % 28);
|
||||||
const y = Math.floor(i / 28);
|
const y = Math.floor(i / 28);
|
||||||
|
|
||||||
// Scala di grigi basata sul valore
|
|
||||||
const brightness = Math.floor(val * 255);
|
const brightness = Math.floor(val * 255);
|
||||||
ctx.fillStyle = `rgb(${brightness}, ${brightness}, ${brightness})`;
|
ctx.fillStyle = `rgb(${brightness}, ${brightness}, ${brightness})`;
|
||||||
ctx.fillRect(startX + (x * scale), startY + (y * scale), scale, scale);
|
ctx.fillRect(startX + (x * scale), startY + (y * scale), scale, scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Etichetta
|
|
||||||
ctx.fillStyle = "#fff";
|
ctx.fillStyle = "#fff";
|
||||||
ctx.font = "14px monospace";
|
ctx.font = "14px monospace";
|
||||||
ctx.fillText("AI INPUT:", startX, startY - 10);
|
ctx.fillText("AI INPUT:", startX, startY - 10);
|
||||||
|
|
@ -104,13 +97,12 @@
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- DISEGNA L'INPUT PRIMA DI TUTTO ---
|
|
||||||
if (data.input_pixels) {
|
if (data.input_pixels) {
|
||||||
drawInputImage(data.input_pixels);
|
drawInputImage(data.input_pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
const layerWidth = (canvas.width - 200) / structure.length; // Lasciamo spazio a sx per l'immagine
|
const layerWidth = (canvas.width - 200) / structure.length;
|
||||||
const offsetX = 150; // Spostiamo tutto a destra
|
const offsetX = 150;
|
||||||
|
|
||||||
let nodePositions = [];
|
let nodePositions = [];
|
||||||
|
|
||||||
|
|
@ -129,7 +121,6 @@
|
||||||
nodePositions.push(layerNodes);
|
nodePositions.push(layerNodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disegno connessioni
|
|
||||||
const DRAW_THRESHOLD = 0.05;
|
const DRAW_THRESHOLD = 0.05;
|
||||||
layers.forEach((layer, lIdx) => {
|
layers.forEach((layer, lIdx) => {
|
||||||
const sourceNodes = nodePositions[lIdx];
|
const sourceNodes = nodePositions[lIdx];
|
||||||
|
|
@ -145,7 +136,7 @@
|
||||||
ctx.moveTo(source.x, source.startY);
|
ctx.moveTo(source.x, source.startY);
|
||||||
ctx.lineTo(target.x, target.startY);
|
ctx.lineTo(target.x, target.startY);
|
||||||
|
|
||||||
const alpha = Math.min(Math.abs(weight), 0.5); // Più trasparente per chiarezza
|
const alpha = Math.min(Math.abs(weight), 0.5);
|
||||||
ctx.strokeStyle = weight > 0 ? `rgba(0, 255, 128, ${alpha})` : `rgba(255, 60, 60, ${alpha})`;
|
ctx.strokeStyle = weight > 0 ? `rgba(0, 255, 128, ${alpha})` : `rgba(255, 60, 60, ${alpha})`;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
@ -153,15 +144,12 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disegno Nodi
|
|
||||||
nodePositions.forEach((layerNodes, lIdx) => {
|
nodePositions.forEach((layerNodes, lIdx) => {
|
||||||
const nodeRadius = Math.max(3, Math.min(15, 300 / layerNodes.length));
|
const nodeRadius = Math.max(3, Math.min(15, 300 / layerNodes.length));
|
||||||
|
|
||||||
// Evidenziamo l'output attivo nell'ultimo layer!
|
|
||||||
let maxOutIdx = -1;
|
let maxOutIdx = -1;
|
||||||
let maxOutVal = -999;
|
let maxOutVal = -999;
|
||||||
|
|
||||||
// Se siamo nell'ultimo layer, cerchiamo il vincitore
|
|
||||||
if (lIdx === nodePositions.length - 1) {
|
if (lIdx === nodePositions.length - 1) {
|
||||||
// Nota: Non abbiamo i valori di output nel JSON, solo i pesi statici.
|
// Nota: Non abbiamo i valori di output nel JSON, solo i pesi statici.
|
||||||
// Quindi coloriamo solo i nodi genericamente
|
// Quindi coloriamo solo i nodi genericamente
|
||||||
|
|
@ -172,10 +160,8 @@
|
||||||
ctx.arc(node.x, node.startY, nodeRadius, 0, Math.PI * 2);
|
ctx.arc(node.x, node.startY, nodeRadius, 0, Math.PI * 2);
|
||||||
ctx.fillStyle = '#222';
|
ctx.fillStyle = '#222';
|
||||||
|
|
||||||
// Hack visivo: Se siamo nell'ultimo layer, coloriamo i nodi in base all'indice (0-9)
|
|
||||||
if (lIdx === nodePositions.length - 1) {
|
if (lIdx === nodePositions.length - 1) {
|
||||||
ctx.fillStyle = '#444';
|
ctx.fillStyle = '#444';
|
||||||
// Aggiungiamo il numero dentro il nodo
|
|
||||||
ctx.fillText(nIdx, node.x + 20, node.startY + 5);
|
ctx.fillText(nIdx, node.x + 20, node.startY + 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue