diff --git a/src/main.zig b/src/main.zig index f8c1b7f..250975a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,69 +1,39 @@ const std = @import("std"); -const Neuron = @import("neuron.zig").Neuron; -const Tensor = @import("tensor.zig").Tensor; +const SimpleNetwork = @import("network.zig").SimpleNetwork; pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - defer _ = gpa.deinit(); + var net = SimpleNetwork.init(1234); - // 1. Inizializziamo il neurone (2 input perché la porta AND ha 2 ingressi) - var my_neuron = try Neuron.init(allocator, 2); - defer my_neuron.deinit(); - - // 2. Prepariamo il Dataset (AND Gate) - // Creiamo un tensore riutilizzabile per gli input - var input_tensor = try Tensor.init(allocator, &[_]usize{2}); - defer input_tensor.deinit(); - - // I 4 casi possibili (Training Data) - const training_data = [_][2]f32{ + const inputs = [_][2]f32{ .{ 0.0, 0.0 }, .{ 0.0, 1.0 }, .{ 1.0, 0.0 }, .{ 1.0, 1.0 }, }; - // Le 4 risposte corrette (Labels) - const targets = [_]f32{ 0.0, 0.0, 0.0, 1.0 }; + const targets = [_]f32{ 0.0, 1.0, 1.0, 0.0 }; - const lr: f32 = 0.1; // Learning rate un po' più aggressivo + const lr: f32 = 0.5; - std.debug.print("--- INIZIO TRAINING (AND GATE) ---\n", .{}); + std.debug.print("--- TRAINING XOR (2 LAYERS) ---\n", .{}); - // 3. Ciclo di Training var epoch: usize = 0; - while (epoch < 2000) : (epoch += 1) { // 2000 Epoche - var total_error: f32 = 0.0; + while (epoch < 10000) : (epoch += 1) { + var total_loss: f32 = 0.0; - // Per ogni epoca, passiamo attraverso TUTTI gli esempi - for (training_data, 0..) |data, index| { - // Carichiamo i dati nel tensore - input_tensor.data[0] = data[0]; - input_tensor.data[1] = data[1]; - - // Train su questo specifico esempio - const loss = my_neuron.train(input_tensor, targets[index], lr); - total_error += loss; + for (inputs, 0..) |inp, i| { + total_loss += net.train(inp, targets[i], lr); } - // Stampiamo ogni 200 epoche - if (epoch % 200 == 0) { - std.debug.print("Epoca {d}: Errore Medio = {d:.6}\n", .{ epoch, total_error / 4.0 }); + if (epoch % 1000 == 0) { + std.debug.print("Epoca {d}: Loss = {d:.6}\n", .{ epoch, total_loss / 4.0 }); } } - std.debug.print("\n--- TEST FINALE ---\n", .{}); - - // Verifichiamo cosa ha imparato - for (training_data) |data| { - input_tensor.data[0] = data[0]; - input_tensor.data[1] = data[1]; - const prediction = my_neuron.forward(input_tensor); - - // Arrotondiamo visivamente per capire se è 0 o 1 - const result_bool: u8 = if (prediction > 0.5) 1 else 0; - - std.debug.print("Input: {d:.1}, {d:.1} -> Predizione: {d:.4} (Interpretato: {d})\n", .{ data[0], data[1], prediction, result_bool }); + std.debug.print("\n--- TEST XOR ---\n", .{}); + for (inputs) |inp| { + const out = net.forward(inp); + const bit: u8 = if (out > 0.5) 1 else 0; + std.debug.print("In: {d:.0},{d:.0} -> Out: {d:.4} -> {d}\n", .{ inp[0], inp[1], out, bit }); } } diff --git a/src/network.zig b/src/network.zig new file mode 100644 index 0000000..83ab735 --- /dev/null +++ b/src/network.zig @@ -0,0 +1,83 @@ +const std = @import("std"); + +pub const SimpleNetwork = struct { + w_hidden: [2][2]f32, + b_hidden: [2]f32, + + w_output: [2]f32, + b_output: f32, + + hidden_outputs: [2]f32, + + pub fn init(seed: u64) SimpleNetwork { + var prng = std.Random.DefaultPrng.init(seed); + const rand = prng.random(); + + var net = SimpleNetwork{ + .w_hidden = undefined, + .b_hidden = undefined, + .w_output = undefined, + .b_output = 0.0, + .hidden_outputs = undefined, + }; + + for (&net.w_hidden) |*row| { + row[0] = rand.float(f32) * 2.0 - 1.0; + row[1] = rand.float(f32) * 2.0 - 1.0; + } + for (&net.b_hidden) |*b| b.* = 0.0; + + for (&net.w_output) |*w| w.* = rand.float(f32) * 2.0 - 1.0; + + return net; + } + + fn sigmoid(x: f32) f32 { + return 1.0 / (1.0 + std.math.exp(-x)); + } + + fn sigmoid_derivative(x: f32) f32 { + return x * (1.0 - x); + } + + pub fn forward(self: *SimpleNetwork, input: [2]f32) f32 { + for (0..2) |i| { + const sum = (input[0] * self.w_hidden[i][0]) + + (input[1] * self.w_hidden[i][1]) + + self.b_hidden[i]; + self.hidden_outputs[i] = sigmoid(sum); + } + + const sum_out = (self.hidden_outputs[0] * self.w_output[0]) + + (self.hidden_outputs[1] * self.w_output[1]) + + self.b_output; + + return sigmoid(sum_out); + } + + pub fn train(self: *SimpleNetwork, input: [2]f32, target: f32, lr: f32) f32 { + const prediction = self.forward(input); + + const output_error = target - prediction; + const output_delta = output_error * sigmoid_derivative(prediction); + + var hidden_deltas: [2]f32 = undefined; + for (0..2) |i| { + const error_contrib = output_delta * self.w_output[i]; + hidden_deltas[i] = error_contrib * sigmoid_derivative(self.hidden_outputs[i]); + } + + for (0..2) |i| { + self.w_output[i] += lr * output_delta * self.hidden_outputs[i]; + } + self.b_output += lr * output_delta; + + for (0..2) |i| { + self.w_hidden[i][0] += lr * hidden_deltas[i] * input[0]; + self.w_hidden[i][1] += lr * hidden_deltas[i] * input[1]; + self.b_hidden[i] += lr * hidden_deltas[i]; + } + + return output_error * output_error; + } +};