From e589a075310f4897964dce4f1586406e0fc3170a Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 10 Nov 2024 15:37:10 -0500 Subject: [PATCH] update python commit file for nix + changes to som. working now? --- CMakeLists.txt | 2 +- commit.py | 2 +- include/assign3/array.h | 21 ++++++---- include/assign3/functions.h | 17 +++++++- include/assign3/fwdecl.h | 15 ++++++- include/assign3/som.h | 2 + src/functions.cpp | 5 +++ src/main.cpp | 83 ++++++++++++++++++++++++++++++++----- src/neuron.cpp | 3 ++ src/som.cpp | 22 ++++++++-- 10 files changed, 146 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5778c3..52a7b2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(COSC-4P80-Assignment-3 VERSION 0.0.9) +project(COSC-4P80-Assignment-3 VERSION 0.0.10) include(FetchContent) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) diff --git a/commit.py b/commit.py index b885026..2af4749 100755 --- a/commit.py +++ b/commit.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!python3 import subprocess import argparse diff --git a/include/assign3/array.h b/include/assign3/array.h index 79c54bc..d220caa 100644 --- a/include/assign3/array.h +++ b/include/assign3/array.h @@ -36,28 +36,33 @@ namespace assign3 map.emplace_back(dimensions, (j % 2 == 0 ? static_cast(i) : static_cast(i) + 0.5f), j); } - inline neuron_t& get(blt::size_t x, blt::size_t y) + [[nodiscard]] std::pair from_index(blt::size_t index) const + { + return {index % width, index / width}; + } + + neuron_t& get(blt::size_t x, blt::size_t y) { return map[y * width + x]; } - [[nodiscard]] inline const neuron_t& get(blt::size_t x, blt::size_t y) const + [[nodiscard]] const neuron_t& get(blt::size_t x, blt::size_t y) const { return map[y * width + x]; } - [[nodiscard]] inline blt::size_t get_width() const + [[nodiscard]] blt::size_t get_width() const { return width; } - [[nodiscard]] inline blt::size_t get_height() const + [[nodiscard]] blt::size_t get_height() const { return height; } - [[nodiscard]] inline std::vector& get_map() + [[nodiscard]] std::vector& get_map() { return map; } - [[nodiscard]] inline const std::vector& get_map() const + [[nodiscard]] const std::vector& get_map() const { return map; } private: - [[nodiscard]] inline blt::i64 wrap_width(blt::i64 x) const; + [[nodiscard]] blt::i64 wrap_width(blt::i64 x) const; - [[nodiscard]] inline blt::i64 wrap_height(blt::i64 y) const; + [[nodiscard]] blt::i64 wrap_height(blt::i64 y) const; private: blt::i64 width, height; diff --git a/include/assign3/functions.h b/include/assign3/functions.h index 507d50c..a84af81 100644 --- a/include/assign3/functions.h +++ b/include/assign3/functions.h @@ -33,16 +33,31 @@ namespace assign3 * @return basis results */ [[nodiscard]] virtual Scalar call(Scalar dist, Scalar r) const = 0; + + /** + * produces a non-time scaled r value for making the function activate at target_strength when a point is half_distance between two nodes + * so for the example of unit distance 1, half distance is 0.5, target_strength is 0.5 + * this function will produce a value which can be time scaled for achieving this result at t=0 + * + * this function can return a 1 to indicate this function doesn't care for this. + */ + [[nodiscard]] virtual Scalar scale(Scalar half_distance, Scalar target_strength) const = 0; + + virtual ~topology_function_t() = default; }; - struct gaussian_function_t : public topology_function_t + struct gaussian_function_t final : public topology_function_t { [[nodiscard]] Scalar call(Scalar dist, Scalar r) const final; + + [[nodiscard]] Scalar scale(Scalar half_distance, Scalar target_strength) const final; }; struct distance_function_t { [[nodiscard]] virtual Scalar distance(blt::span x, blt::span y) const = 0; + + virtual ~distance_function_t() = default; }; struct euclidean_distance_function_t : public distance_function_t diff --git a/include/assign3/fwdecl.h b/include/assign3/fwdecl.h index 978d602..74eb791 100644 --- a/include/assign3/fwdecl.h +++ b/include/assign3/fwdecl.h @@ -19,13 +19,24 @@ #ifndef COSC_4P80_ASSIGNMENT_3_FWDECL_H #define COSC_4P80_ASSIGNMENT_3_FWDECL_H +#include + namespace assign3 { using Scalar = float; - enum class shape + inline constexpr blt::i32 RENDER_2D = 0x0; + inline constexpr blt::i32 RENDER_3D = 0x1; + + enum class shape : blt::i32 { - GRID, HONEYCOMB + GRID = RENDER_2D, + GRID_WRAP = RENDER_2D, + GRID_OFFSET = RENDER_2D, + GRID_OFFSET_WRAP = RENDER_2D, + GAUSSIAN_DIST = RENDER_2D, + TOROIDAL = RENDER_3D, + CYLINDER = RENDER_3D }; } diff --git a/include/assign3/som.h b/include/assign3/som.h index 70e89a7..b454d86 100644 --- a/include/assign3/som.h +++ b/include/assign3/som.h @@ -33,6 +33,8 @@ namespace assign3 blt::size_t get_closest_neuron(const std::vector& data); + Scalar find_closest_neighbour_distance(blt::size_t v0); + void train_epoch(Scalar initial_learn_rate, topology_function_t* basis_func); [[nodiscard]] const array_t& get_array() const diff --git a/src/functions.cpp b/src/functions.cpp index d29e19f..5b5f289 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -28,6 +28,11 @@ namespace assign3 return std::exp(-r * dist_sq); } + Scalar gaussian_function_t::scale(Scalar half_distance, Scalar target_strength) const + { + return -std::log(target_strength) / (half_distance * half_distance); + } + Scalar euclidean_distance_function_t::distance(blt::span x, blt::span y) const { Scalar dist = 0; diff --git a/src/main.cpp b/src/main.cpp index 72d0b1d..ddb2c24 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace assign3; @@ -19,17 +20,38 @@ blt::gfx::resource_manager resources; blt::gfx::batch_renderer_2d renderer_2d(resources, global_matrices); blt::gfx::first_person_camera_2d camera; -blt::size_t som_width = 7; -blt::size_t som_height = 7; -blt::size_t max_epochs = 1000; +blt::size_t som_width = 5; +blt::size_t som_height = 5; +blt::size_t max_epochs = 100; Scalar initial_learn_rate = 0.1; int currently_selected_network = 0; std::vector map_files_names; +std::unique_ptr topology_function; float neuron_scale = 35; -float draw_width = neuron_scale * static_cast(som_width); -float draw_height = neuron_scale * static_cast(som_height); +float draw_width; +float draw_height; + +void update_graphics() +{ + // find the min x / y for the currently drawn som as positions may depend on type. + const auto x_comparator = [](const auto& a, const auto& b) { + return a.get_x() < b.get_x(); + }; + const auto y_comparator = [](const auto& a, const auto& b) { + return a.get_y() < b.get_y(); + }; + const auto& som_neurons = som->get_array().get_map(); + auto min_x = std::min_element(som_neurons.begin(), som_neurons.end(), x_comparator)->get_x(); + auto max_x = std::max_element(som_neurons.begin(), som_neurons.end(), x_comparator)->get_x(); + auto min_y = std::min_element(som_neurons.begin(), som_neurons.end(), y_comparator)->get_y(); + auto max_y = std::max_element(som_neurons.begin(), som_neurons.end(), y_comparator)->get_y(); + draw_width = (max_x - min_x) * neuron_scale; + draw_height = (max_y - min_y) * neuron_scale; + + topology_function = std::make_unique(); +} void generate_network(int selection) { @@ -65,6 +87,8 @@ void update(const blt::gfx::window_data& window_data) camera.update_view(global_matrices); global_matrices.update(); + update_graphics(); + if (ImGui::Begin("Controls")) { ImGui::Text("Network Select"); @@ -73,16 +97,14 @@ void update(const blt::gfx::window_data& window_data) if (ImGui::Button("Run Epoch")) { - static gaussian_function_t func; - som->train_epoch(initial_learn_rate, &func); + som->train_epoch(initial_learn_rate, topology_function.get()); } static bool run; ImGui::Checkbox("Run to completion", &run); if (run) { - static gaussian_function_t func; if (som->get_current_epoch() < som->get_max_epochs()) - som->train_epoch(initial_learn_rate, &func); + som->train_epoch(initial_learn_rate, topology_function.get()); } ImGui::Text("Epoch %ld / %ld", som->get_current_epoch(), som->get_max_epochs()); } @@ -156,6 +178,47 @@ void update(const blt::gfx::window_data& window_data) v.get_y() * neuron_scale + neuron_scale, neuron_scale}); } + closest_type.clear(); + closest_type.resize(som->get_array().get_map().size()); + for (auto [i, v] : blt::enumerate(som->get_array().get_map())) + { + auto half = som->find_closest_neighbour_distance(i); + auto scale = topology_function->scale(half * 0.5f, 0.5); + for (const auto& data : current_data_file.data_points) + { + auto dist = v.dist(data.bins); + auto ds = topology_function->call(dist, scale); +// BLT_TRACE("%f, %f, %f", ds, dist, scale); + if (data.is_bad) + closest_type[i] -= ds; + else + closest_type[i] += ds; + } + } + auto min_act = *std::min_element(closest_type.begin(), closest_type.end()); + auto max_act = *std::max_element(closest_type.begin(), closest_type.end()); + for (auto& v : closest_type) + { + auto n = 2 * (v - min_act) / (max_act - min_act) - 1; + v = n; + } + BLT_TRACE("Min %f Max %f", min_act, max_act); + + for (auto [i, v] : blt::enumerate(som->get_array().get_map())) + { + auto type = closest_type[i]; + + blt::vec4 color; + if (type >= 0) + color = blt::make_color(0, type, 0); + else + color = blt::make_color(-type, 0, 0); + + renderer_2d.drawPointInternal(color, + point2d_t{draw_width + neuron_scale * 2 + v.get_x() * neuron_scale + neuron_scale, + draw_height + neuron_scale * 2 + v.get_y() * neuron_scale + neuron_scale, neuron_scale}); + } + renderer_2d.render(window_data.width, window_data.height); } @@ -178,5 +241,5 @@ int main(int argc, const char** argv) files = assign3::data_file_t::load_data_files_from_path(args.get("file")); - blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update, destroy}.setSyncInterval(1)); + blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update, destroy}.setSyncInterval(1).setMaximized(true)); } diff --git a/src/neuron.cpp b/src/neuron.cpp index c8390f5..0ef413b 100644 --- a/src/neuron.cpp +++ b/src/neuron.cpp @@ -33,6 +33,7 @@ namespace assign3 return *this; } + // apply the distance based on the update neuron function neuron_t& neuron_t::update(const std::vector& new_data, Scalar dist, Scalar eta) { static thread_local std::vector diff; @@ -47,12 +48,14 @@ namespace assign3 return *this; } + // distance between an input vector and the neuron, in the n-space Scalar neuron_t::dist(const std::vector& X) const { euclidean_distance_function_t dist_func; return dist_func.distance(data, X); } + // distance between two neurons, in 2d Scalar neuron_t::distance(const neuron_t& n1, const neuron_t& n2) { euclidean_distance_function_t dist_func; diff --git a/src/som.cpp b/src/som.cpp index 7dd5658..3af5ff5 100644 --- a/src/som.cpp +++ b/src/som.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace assign3 { @@ -46,11 +47,17 @@ namespace assign3 auto v0 = array.get_map()[v0_idx]; v0.update(current_data.bins, 1, eta); + // find the closest neighbour neuron to v0 + auto distance_min = find_closest_neighbour_distance(v0_idx); + // this will find the required scaling factor to make a point in the middle between v0 and its closest neighbour activate 50% + // from the perspective of the gaussian function + auto scale = basis_func->scale(distance_min * 0.5f, 0.5); + for (auto [i, n] : blt::enumerate(array.get_map())) { if (i == v0_idx) continue; - auto dist = basis_func->call(neuron_t::distance(v0, n), time_ratio); + auto dist = basis_func->call(neuron_t::distance(v0, n), time_ratio * scale); n.update(current_data.bins, dist, eta); } } @@ -62,7 +69,6 @@ namespace assign3 { blt::size_t index = 0; Scalar distance = std::numeric_limits::max(); - for (auto [i, d] : blt::enumerate(array.get_map())) { auto dist = d.dist(data); @@ -72,9 +78,19 @@ namespace assign3 distance = dist; } } - return index; } + Scalar som_t::find_closest_neighbour_distance(blt::size_t v0) + { + Scalar distance_min = std::numeric_limits::max(); + for (const auto& [i, n] : blt::enumerate(array.get_map())) + { + if (i != v0) + distance_min = std::min(distance_min, neuron_t::distance(array.get_map()[v0], n)); + } + return distance_min; + } + } \ No newline at end of file