diff options
author | makefunstuff <[email protected]> | 2024-07-09 20:18:19 +0200 |
---|---|---|
committer | makefunstuff <[email protected]> | 2024-07-09 20:18:19 +0200 |
commit | e5a064e82fe7d36f4423abdf798f9f1f0a90ae0e (patch) | |
tree | 68ff4828f28a659ac3235f0e3b9eda92d5039129 | |
parent | ec82637e7f9414c947bad9e8bc5943b253a3766b (diff) | |
download | tinkerbunk-e5a064e82fe7d36f4423abdf798f9f1f0a90ae0e.tar.gz |
linear
-rw-r--r-- | build.zig | 56 | ||||
-rw-r--r-- | src/monkey_brain/test.zig | 6 | ||||
-rw-r--r-- | src/monkey_learns/linear_regression.zig | 89 | ||||
-rw-r--r-- | src/monkey_learns/main.zig | 1 | ||||
-rw-r--r-- | src/monkey_learns/test.zig | 3 |
5 files changed, 126 insertions, 29 deletions
diff --git a/build.zig b/build.zig index dbaf428..aea990e 100644 --- a/build.zig +++ b/build.zig @@ -1,9 +1,35 @@ const std = @import("std"); -// Although this function looks imperative, note that its job is to -// declaratively construct a build graph that will be executed by an external -// runner. -pub fn build(b: *std.Build) void { +fn define_subproj(name: []const u8, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !void { + const exe_path = try std.fmt.allocPrint(b.allocator, "src/{s}/main.zig", .{name}); + const test_path = try std.fmt.allocPrint(b.allocator, "src/{s}/test.zig", .{name}); + const run_task_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{name}); + const run_task_desc = try std.fmt.allocPrint(b.allocator, "Run {s}", .{name}); + const test_task_name = try std.fmt.allocPrint(b.allocator, "test_{s}", .{name}); + const test_task_desc = try std.fmt.allocPrint(b.allocator, "Run tests for {s}", .{name}); + + const exe = b.addExecutable(.{ + .name = name, + .root_source_file = b.path(exe_path), + .target = target, + .optimize = optimize, + }); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step(run_task_name, run_task_desc); + run_step.dependOn(&run_cmd.step); + + const test_exe = b.addTest(.{ .root_source_file = b.path(test_path), .target = target, .optimize = optimize }); + + const test_cmd = b.addRunArtifact(test_exe); + const test_step = b.step(test_task_name, test_task_desc); + test_step.dependOn(&test_cmd.step); +} + +pub fn build(b: *std.Build) !void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options @@ -54,24 +80,6 @@ pub fn build(b: *std.Build) void { test_step.dependOn(&run_exe_unit_tests.step); } - // monkey brains - const monkey_brain = b.addExecutable(.{ - .name = "monkey_brain", - .root_source_file = b.path("src/monkey_brain/main.zig"), - .target = target, - .optimize = optimize, - }); - b.installArtifact(monkey_brain); - - const monkey_brain_run = b.addRunArtifact(monkey_brain); - monkey_brain_run.step.dependOn(b.getInstallStep()); - - const monkey_run_step = b.step("monkey_run", "Run the monkey brain"); - monkey_run_step.dependOn(&monkey_brain_run.step); - - const monkey_test_exec = b.addTest(.{ .root_source_file = b.path("src/monkey_brain/test.zig"), .target = target, .optimize = optimize }); - - const monkey_test_run = b.addRunArtifact(monkey_test_exec); - const monkey_test_step = b.step("monkey_test", "Run monkey brain cells test"); - monkey_test_step.dependOn(&monkey_test_run.step); + try define_subproj("monkey_brain", b, target, optimize); + try define_subproj("monkey_learns", b, target, optimize); } diff --git a/src/monkey_brain/test.zig b/src/monkey_brain/test.zig index 4d4a04b..b3363e9 100644 --- a/src/monkey_brain/test.zig +++ b/src/monkey_brain/test.zig @@ -1,5 +1,3 @@ -pub const perceptron = @import("perceptron.zig"); - -test { - @import("std").testing.refAllDecls(@This()); +comptime { + _ = @import("perceptron.zig"); } diff --git a/src/monkey_learns/linear_regression.zig b/src/monkey_learns/linear_regression.zig index 70b786d..4cc5a9f 100644 --- a/src/monkey_learns/linear_regression.zig +++ b/src/monkey_learns/linear_regression.zig @@ -1 +1,88 @@ -// TODO +const std = @import("std"); +const testing = std.testing; +const math = std.math; + +const LinearRegression = struct { + const Self = @This(); + + weight: f64, + bias: f64, + + fn init() LinearRegression { + return Self{ .weight = 0.0, .bias = 0.0 }; + } + + fn predict(self: Self, x: f64) f64 { + return self.weight * x + self.bias; + } + + fn train(self: *LinearRegression, x: []const f64, y: []const f64, learning_rate: f64, epochs: usize) void { + const n: f64 = @floatFromInt(x.len); + + for (0..epochs) |epoch| { + var total_error: f64 = 0; + + for (x, y) |xi, yi| { + const prediction = self.predict(xi); + const err = prediction - yi; + + self.weight -= learning_rate * err * xi; + self.bias -= learning_rate * err; + + total_error += err * err; + } + + const current_mse = total_error / n; + + if (epoch % 1000 == 0 or epoch == epochs - 1) { + std.debug.print("Epoch {d}: MSE = {d:.6}\n", .{ epoch, current_mse }); + } + } + } + + // https://en.wikipedia.org/wiki/Mean_squared_error + fn loss_function(self: Self, x: []const f64, y: []const f64) f64 { + var squared_sum: f64 = 0.0; + for (x, y) |xi, yi| { + const predicted = self.predict(xi); + const err = predicted - yi; + squared_sum += err * err; + } + const n: f64 = @floatFromInt(x.len); + return squared_sum / n; + } +}; + +test "Linear Regression" { + // Initialize the model + var model = LinearRegression.init(); + + const x = [_]f64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + const y = [_]f64{ 3.1, 4.9, 7.2, 9.1, 11.0, 12.8, 14.9, 17.2, 18.8, 21.1 }; + + const learning_rate: f64 = 0.01; + const epochs: usize = 1000; + model.train(&x, &y, learning_rate, epochs); + + try testing.expect(@abs(model.weight - 2.0) < 0.1); + try testing.expect(@abs(model.bias - 1.0) < 0.1); + + const test_x = [_]f64{ 0, 5, 10 }; + const expected_y = [_]f64{ 1, 11, 21 }; + for (test_x, expected_y) |xi, yi| { + const prediction = model.predict(xi); + try testing.expect(@abs(prediction - yi) < 0.5); + } + + const mse = model.loss_function(&x, &y); + try testing.expect(mse < 0.1); + + const new_x = [_]f64{ 11, 12, 13 }; + const new_y = [_]f64{ 23.1, 24.9, 27.2 }; + const new_mse = model.loss_function(&new_x, &new_y); + try testing.expect(new_mse < 0.2); + + std.debug.print("\nTrained model: y = {d:.4}x + {d:.4}\n", .{ model.weight, model.bias }); + std.debug.print("Mean Squared Error on training data: {d:.4}\n", .{mse}); + std.debug.print("Mean Squared Error on new data: {d:.4}\n", .{new_mse}); +} diff --git a/src/monkey_learns/main.zig b/src/monkey_learns/main.zig new file mode 100644 index 0000000..902b554 --- /dev/null +++ b/src/monkey_learns/main.zig @@ -0,0 +1 @@ +pub fn main() void {} diff --git a/src/monkey_learns/test.zig b/src/monkey_learns/test.zig new file mode 100644 index 0000000..7c3472e --- /dev/null +++ b/src/monkey_learns/test.zig @@ -0,0 +1,3 @@ +comptime { + _ = @import("linear_regression.zig"); +} |