diff --git a/src/activations.zig b/src/activations.zig index a15fe86..384e08f 100644 --- a/src/activations.zig +++ b/src/activations.zig @@ -6,7 +6,6 @@ pub const Sigmoid = struct { } pub fn derivative(y: f32) f32 { - // Nota: qui assumiamo che 'y' sia già il risultato della sigmoide return y * (1.0 - y); } }; diff --git a/src/main.zig b/src/main.zig index 9c3b555..36a7f42 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,18 +1,16 @@ const std = @import("std"); const World = @import("env.zig").World; -const env = @import("env.zig"); // Per accedere a costanti come GRID_SIZE +const env = @import("env.zig"); const Network = @import("modular_network.zig").Network; -// --- IPERPARAMETRI --- const GAMMA: f32 = 0.9; -const LR: f32 = 0.005; // Basso perché output lineare +const LR: f32 = 0.005; const EPSILON_START: f32 = 1.0; const EPSILON_END: f32 = 0.05; -const DECAY_RATE: f32 = 0.0001; // Decadimento più lento dato che abbiamo tanti step +const DECAY_RATE: f32 = 0.0001; -// Helper per trovare max e argmax fn maxVal(slice: []const f32) f32 { - var m: f32 = -1.0e20; // Numero molto basso + var m: f32 = -1.0e20; for (slice) |v| if (v > m) { m = v; }; @@ -31,17 +29,14 @@ fn argmax(slice: []const f32) usize { return idx; } -// Export aggiornato per Multi-Formica fn exportAntJSON(world: *World, file_path: []const u8, episode: usize, epsilon: f32) !void { const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); - // Buffer grande per contenere le coordinate di 20 formiche var buffer: [65536]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&buffer); const allocator = fba.allocator(); - // Costruiamo la lista delle formiche in JSON: [[x,y], [x,y], ...] var ants_json = std.ArrayList(u8){}; defer ants_json.deinit(allocator); try ants_json.appendSlice(allocator, "["); @@ -52,8 +47,6 @@ fn exportAntJSON(world: *World, file_path: []const u8, episode: usize, epsilon: } try ants_json.appendSlice(allocator, "]"); - // Scriviamo il JSON completo - // QUI RISOLVIAMO L'ERRORE: Usiamo 'epsilon' nella stringa const json = try std.fmt.allocPrint(allocator, "{{\n \"grid_size\": {d},\n \"food\": [{d}, {d}],\n \"ants\": {s},\n \"episode\": {d},\n \"epsilon\": {d:.3}\n}}", .{ env.GRID_SIZE, world.food_x, world.food_y, ants_json.items, episode, epsilon }); try file.writeAll(json); diff --git a/src/mnist.zig b/src/mnist.zig index 86b74a3..93370d9 100644 --- a/src/mnist.zig +++ b/src/mnist.zig @@ -7,17 +7,14 @@ pub const MnistData = struct { allocator: Allocator, pub fn init(allocator: Allocator, img_path: []const u8, lbl_path: []const u8, max_items: usize) !MnistData { - // --- 1. CARICAMENTO IMMAGINI --- const img_file = try std.fs.cwd().openFile(img_path, .{}); defer img_file.close(); try img_file.seekTo(16); - // --- 2. CARICAMENTO ETICHETTE --- const lbl_file = try std.fs.cwd().openFile(lbl_path, .{}); defer lbl_file.close(); try lbl_file.seekTo(8); - // --- 3. ALLOCAZIONE --- var images: std.ArrayList([]f32) = .{}; defer images.deinit(allocator); var labels: std.ArrayList([]f32) = .{}; @@ -29,11 +26,9 @@ pub const MnistData = struct { var i: usize = 0; while (i < max_items) : (i += 1) { - // Leggi direttamente dal file const bytes_read = try img_file.read(&buffer); if (bytes_read < img_size) break; - // Leggi 1 byte per l'etichetta const lbl_read = try lbl_file.read(&label_byte); if (lbl_read < 1) break; diff --git a/src/modular_network.zig b/src/modular_network.zig index 0aae241..851a991 100644 --- a/src/modular_network.zig +++ b/src/modular_network.zig @@ -33,7 +33,6 @@ pub const Network = struct { return current_input; } - // --- QUESTA ERA LA FUNZIONE MANCANTE --- pub fn printTopology(self: *Network) void { std.debug.print("Architettura Rete: [Input]", .{}); for (self.layers.items) |layer| { @@ -41,7 +40,6 @@ pub const Network = struct { } std.debug.print("\n", .{}); } - // --------------------------------------- pub fn train(self: *Network, input: []const f32, target: []const f32, lr: f32) !f32 { _ = self.forward(input); @@ -49,9 +47,8 @@ pub const Network = struct { const last_layer_idx = self.layers.items.len - 1; const last_layer = &self.layers.items[last_layer_idx]; - // Questo è il buffer iniziale degli errori var output_errors = try self.allocator.alloc(f32, last_layer.neurons_count); - defer self.allocator.free(output_errors); // Zig pulirà questo automaticamente alla fine + defer self.allocator.free(output_errors); var total_loss: f32 = 0.0; for (0..last_layer.neurons_count) |i| { @@ -68,23 +65,15 @@ pub const Network = struct { var layer = &self.layers.items[i]; const prev_input = if (i == 0) input else self.layers.items[i - 1].output; - // 1. Calcoliamo i nuovi gradienti (nuova allocazione in memoria) const new_gradients = layer.backward(next_gradients, prev_input, lr); - // 2. CHECK ANTI-LEAK: - // Se il vecchio next_gradients NON è output_errors (che è protetto dal defer), - // allora è un buffer intermedio creato dal layer precedente. Dobbiamo distruggerlo. if (next_gradients.ptr != output_errors.ptr) { self.allocator.free(next_gradients); } - // 3. Aggiorniamo il puntatore per il prossimo giro next_gradients = new_gradients; } - // 4. Pulizia Finale: - // L'ultimo buffer creato dal primo layer (input layer) è rimasto in mano nostra. - // Dobbiamo liberare anche quello. if (next_gradients.ptr != output_errors.ptr) { self.allocator.free(next_gradients); } @@ -92,27 +81,23 @@ pub const Network = struct { return total_loss; } - // --- SALVATAGGIO BINARIO --- pub fn save(self: *Network, file_path: []const u8) !void { const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); const MagicNumber: u64 = 0xDEADBEEF; - // Scriviamo Header try file.writeAll(std.mem.asBytes(&MagicNumber)); const layer_count = @as(u64, self.layers.items.len); try file.writeAll(std.mem.asBytes(&layer_count)); - // Scriviamo Layers for (self.layers.items) |layer| { const inputs = @as(u64, layer.inputs_count); const neurons = @as(u64, layer.neurons_count); try file.writeAll(std.mem.asBytes(&inputs)); try file.writeAll(std.mem.asBytes(&neurons)); - // Scriviamo Pesi e Bias const weights_bytes = std.mem.sliceAsBytes(layer.weights); try file.writeAll(weights_bytes); @@ -121,7 +106,6 @@ pub const Network = struct { } } - // --- CARICAMENTO BINARIO --- pub fn load(allocator: Allocator, file_path: []const u8) !Network { const file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); @@ -131,7 +115,6 @@ pub const Network = struct { defer if (!success) net.deinit(); - // Header Check var magic: u64 = 0; _ = try file.readAll(std.mem.asBytes(&magic)); if (magic != 0xDEADBEEF) return error.InvalidNetworkFile; @@ -139,7 +122,6 @@ pub const Network = struct { var layer_count: u64 = 0; _ = try file.readAll(std.mem.asBytes(&layer_count)); - // Layers Loop var i: u64 = 0; while (i < layer_count) : (i += 1) { var inputs: u64 = 0; @@ -148,7 +130,6 @@ pub const Network = struct { _ = try file.readAll(std.mem.asBytes(&inputs)); _ = try file.readAll(std.mem.asBytes(&neurons)); - // SE è l'ultimo layer, niente sigmoide! const is_last = (i == layer_count - 1); try net.addLayer(@intCast(inputs), @intCast(neurons), 0, !is_last); @@ -165,8 +146,6 @@ pub const Network = struct { return net; } - // --- EXPORT JSON CON IMMAGINE INPUT --- - // Aggiungiamo il parametro 'input_snapshot' (può essere null se non vogliamo stampare nulla) pub fn exportJSON(self: *Network, file_path: []const u8, epoch: usize, loss: f32, input_snapshot: ?[]const f32) !void { const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); @@ -181,11 +160,9 @@ pub const Network = struct { try Utils.print(file, "{{\n \"epoch\": {d},\n \"loss\": {d:.6},\n", .{ epoch, loss }); - // SEZIONE NUOVA: Stampiamo l'array dei pixel se presente if (input_snapshot) |pixels| { try file.writeAll(" \"input_pixels\": ["); for (pixels, 0..) |p, idx| { - // Arrotondiamo a 2 decimali per risparmiare spazio try Utils.print(file, "{d:.2}", .{p}); if (idx < pixels.len - 1) try file.writeAll(","); } @@ -197,7 +174,6 @@ pub const Network = struct { for (self.layers.items, 0..) |layer, i| { try Utils.print(file, " {{\n \"layer_index\": {d},\n \"neurons\": {d},\n \"inputs\": {d},\n \"weights\": [", .{ i, layer.neurons_count, layer.inputs_count }); - // Salviamo solo un sottoinsieme dei pesi per non intasare il file const max_w = if (layer.weights.len > 1000) 100 else layer.weights.len; for (layer.weights[0..max_w], 0..) |w, w_idx| {