From 45ccb3647ea074fac874843f4dc7c97470e8b3c5 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Sat, 16 Nov 2024 18:13:17 -0500 Subject: [PATCH] working closer --- CMakeLists.txt | 2 +- include/assign3/functions.h | 12 ++++ include/assign3/manager.h | 7 +++ include/assign3/neuron.h | 2 +- src/functions.cpp | 11 ++++ src/main.cpp | 2 + src/manager.cpp | 110 ++++++++++++++++++++++++++++-------- src/neuron.cpp | 5 +- src/som.cpp | 6 +- 9 files changed, 128 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 27eac26..f689a49 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.17) +project(COSC-4P80-Assignment-3 VERSION 0.0.18) include(FetchContent) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) diff --git a/include/assign3/functions.h b/include/assign3/functions.h index a84af81..1c6bd52 100644 --- a/include/assign3/functions.h +++ b/include/assign3/functions.h @@ -65,6 +65,18 @@ namespace assign3 [[nodiscard]] Scalar distance(blt::span x, blt::span y) const final; }; + struct toroidal_euclidean_distance_function_t : public distance_function_t + { + public: + toroidal_euclidean_distance_function_t(blt::i32 width, blt::i32 height): width(width), height(height) + {} + + [[nodiscard]] Scalar distance(blt::span x, blt::span y) const final; + + private: + blt::i32 width, height; + }; + } #endif //COSC_4P80_ASSIGNMENT_3_FUNCTIONS_H diff --git a/include/assign3/manager.h b/include/assign3/manager.h index 079d129..a912a09 100644 --- a/include/assign3/manager.h +++ b/include/assign3/manager.h @@ -84,6 +84,8 @@ namespace assign3 std::vector get_neuron_activations(const data_file_t& file); + static std::vector normalize_data(const std::vector& data); + void draw_som(neuron_render_info_t info, const std::function& color_func); void draw_debug(const data_file_t& file); @@ -116,9 +118,14 @@ namespace assign3 int currently_selected_network = 0; bool debug_mode = false; + bool draw_colors = true; + bool draw_data_lines = false; bool running = false; int debug_state = 0; int selected_data_point = 0; + + float requested_activation = 0.5; + float at_distance_measurement = 2; }; } diff --git a/include/assign3/neuron.h b/include/assign3/neuron.h index 54f4812..81b7afc 100644 --- a/include/assign3/neuron.h +++ b/include/assign3/neuron.h @@ -38,7 +38,7 @@ namespace assign3 neuron_t& update(const std::vector& new_data, Scalar dist, Scalar eta); - static Scalar distance(const neuron_t& n1, const neuron_t& n2); + static Scalar distance(distance_function_t* dist_func, const neuron_t& n1, const neuron_t& n2); [[nodiscard]] Scalar dist(const std::vector& X) const; diff --git a/src/functions.cpp b/src/functions.cpp index 5b5f289..4e5ae2b 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -18,6 +18,7 @@ #include #include #include "blt/iterator/zip.h" +#include namespace assign3 { @@ -43,4 +44,14 @@ namespace assign3 } return std::sqrt(dist); } + + Scalar toroidal_euclidean_distance_function_t::distance(blt::span x, blt::span y) const + { + BLT_ASSERT(x.size() == 2 && y.size() == 2); + Scalar x_diff = x[0] - y[0]; + Scalar y_diff = x[1] - y[1]; + Scalar x_min = std::min(x_diff, static_cast(width) - x_diff); + Scalar y_min = std::min(y_diff, static_cast(height) - y_diff); + return std::sqrt(x_min * x_min + y_min * y_min); + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 318e244..7de4ab8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,6 +58,8 @@ int main(int argc, const char** argv) auto args = parser.parse_args(argc, argv); data.files = assign3::data_file_t::load_data_files_from_path(args.get("file")); + for (auto& v : data.files) + v = v.normalize(); data.update(); blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update, destroy}.setSyncInterval(1).setMaximized(true)); diff --git a/src/manager.cpp b/src/manager.cpp index c9a0d10..4fe0a4e 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -20,6 +20,7 @@ #include #include #include +#include static void HelpMarker(const std::string& desc) { @@ -38,6 +39,23 @@ const char* get_selection_string(void* user_data, int selection) return (reinterpret_cast(user_data))[selection].c_str(); } +std::vector rotate90Clockwise(const std::vector& input, int width, int height) +{ + std::vector rotated(width * height); + + for (int row = 0; row < height; ++row) + { + for (int col = 0; col < width; ++col) + { + int newRow = col; + int newCol = height - row - 1; + rotated[newRow * height + newCol] = input[row * width + col]; + } + } + + return rotated; +} + namespace assign3 { @@ -142,7 +160,9 @@ namespace assign3 { case debug_type::DATA_POINT: { - auto current_data_file = motor_data.files[currently_selected_network].normalize(); + ImGui::Checkbox("Data Type Color", &draw_colors); + ImGui::Checkbox("Data Lines", &draw_data_lines); + auto current_data_file = motor_data.files[currently_selected_network]; std::vector names; for (const auto& [i, v] : blt::enumerate(current_data_file.data_points)) names.push_back("#" + std::to_string(i) + " (" + (v.is_bad ? "Bad)" : "Good)")); @@ -160,17 +180,34 @@ namespace assign3 } ImGui::End(); + auto current_data_file = motor_data.files[currently_selected_network]; + auto closest_type = get_neuron_activations(current_data_file); + + if (ImGui::Begin("Plots & Data")) + { + ImPlot::SetNextAxesLimits(0, som_width, 0, som_height, ImPlotCond_Always); + if (ImPlot::BeginPlot("Activations", ImVec2(-1, 0), ImPlotFlags_NoInputs)) + { + auto rev = rotate90Clockwise(closest_type, som_width, som_height); +// auto rev = closest_type; +// std::reverse(rev.begin(), rev.end()); + ImPlot::PlotHeatmap("##data_map", rev.data(), som_height, som_width, 0, 0, "%.1f", ImPlotPoint(0, 0), + ImPlotPoint(som_width, som_height), ImPlotHeatmapFlags_ColMajor); + ImPlot::EndPlot(); + } + } + ImGui::End(); + if (running) { if (som->get_current_epoch() < som->get_max_epochs()) som->train_epoch(initial_learn_rate, topology_function.get()); } - auto current_data_file = motor_data.files[currently_selected_network].normalize(); if (!debug_mode) { - auto closest_type = get_neuron_activations(current_data_file); + closest_type = normalize_data(closest_type); draw_som(neuron_render_info_t{}.set_base_pos({370, 145}).set_neuron_scale(120).set_neuron_padding({5, 5}), [&closest_type](render_data_t context) { auto type = closest_type[context.index]; @@ -207,27 +244,20 @@ namespace assign3 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); + auto half = som->find_closest_neighbour_distance(i) / at_distance_measurement; + auto scale = topology_function->scale(half, requested_activation); for (const auto& data : file.data_points) { - auto dist = v.dist(data.bins); - auto ds = topology_function->call(dist, scale); + auto ds = topology_function->call(v.dist(data.bins), 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; - } return closest_type; } @@ -239,24 +269,41 @@ namespace assign3 case debug_type::DATA_POINT: { std::vector data_positions; + std::vector neuron_positions; for (const auto& [i, v] : blt::enumerate(file.data_points)) { auto pos = som->get_topological_position(v.bins) * 120 + blt::vec2{370, 145}; - auto color = blt::make_color(1,1,1); - float z_index = 1; + data_positions.push_back(pos); + auto color = blt::make_color(1, 1, 1); + float z_index = 2; + if (draw_colors) + { + if (v.is_bad) + color = blt::make_color(1, 0, 0); + else + color = blt::make_color(0, 1, 0); + } if (i == static_cast(selected_data_point)) { - color = blt::make_color(1, 0, 1); - z_index = 2; + if (!draw_colors) + { + color = blt::make_color(1, 0, 1); + z_index = 3; + } } - br2d.drawRectangleInternal(color, blt::gfx::rectangle2d_t{pos, blt::vec2{8,8}}, z_index); + br2d.drawRectangleInternal(color, blt::gfx::rectangle2d_t{pos, blt::vec2{8, 8}}, z_index); } const auto& data_point = file.data_points[selected_data_point]; - auto closest_type = get_neuron_activations(file); + auto closest_type = normalize_data(get_neuron_activations(file)); draw_som(neuron_render_info_t{}.set_base_pos({370, 145}).set_neuron_scale(120).set_neuron_padding({0, 0}), - [this, &data_point, &closest_type](render_data_t context) { - auto& text = fr2d.render_text(std::to_string(context.neuron.dist(data_point.bins)), 18).setColor(0.2, 0.2, 0.8); + [this, &neuron_positions, &data_point, &closest_type](render_data_t context) { + auto half = som->find_closest_neighbour_distance(context.index) / at_distance_measurement; + auto scale = topology_function->scale(half, requested_activation); + auto ds = topology_function->call(context.neuron.dist(data_point.bins), scale); + + neuron_positions.push_back(context.neuron_padded); + auto& text = fr2d.render_text(std::to_string(ds), 18).setColor(0.2, 0.2, 0.8); auto text_width = text.getAssociatedText().getTextWidth(); auto text_height = text.getAssociatedText().getTextHeight(); text.setPosition(context.neuron_padded - blt::vec2{text_width / 2.0f, text_height / 2.0f}).setZIndex(1); @@ -264,6 +311,12 @@ namespace assign3 auto type = closest_type[context.index]; return type >= 0 ? blt::make_color(0, type, 0) : blt::make_color(-type, 0, 0); }); + + if (draw_data_lines) + { + for (const auto& neuron : neuron_positions) + br2d.drawLineInternal(blt::make_color(1, 1, 0), blt::gfx::line2d_t{neuron, data_positions[selected_data_point]}, 1); + } } break; case debug_type::DISTANCE: @@ -273,4 +326,17 @@ namespace assign3 break; } } + + std::vector renderer_t::normalize_data(const std::vector& data) + { + std::vector return_data; + auto min_act = *std::min_element(data.begin(), data.end()); + auto max_act = *std::max_element(data.begin(), data.end()); + for (auto& v : data) + { + auto n = 2 * (v - min_act) / (max_act - min_act) - 1; + return_data.push_back(n); + } + return return_data; + } } \ No newline at end of file diff --git a/src/neuron.cpp b/src/neuron.cpp index 0ef413b..661f97b 100644 --- a/src/neuron.cpp +++ b/src/neuron.cpp @@ -56,9 +56,8 @@ namespace assign3 } // distance between two neurons, in 2d - Scalar neuron_t::distance(const neuron_t& n1, const neuron_t& n2) + Scalar neuron_t::distance(distance_function_t* dist_func, const neuron_t& n1, const neuron_t& n2) { - euclidean_distance_function_t dist_func; - return dist_func.distance({n1.get_x(), n1.get_y()}, {n2.get_x(), n2.get_y()}); + return dist_func->distance({n1.get_x(), n1.get_y()}, {n2.get_x(), n2.get_y()}); } } \ No newline at end of file diff --git a/src/som.cpp b/src/som.cpp index 82226ce..558ff16 100644 --- a/src/som.cpp +++ b/src/som.cpp @@ -57,7 +57,8 @@ namespace assign3 { if (i == v0_idx) continue; - auto dist = basis_func->call(neuron_t::distance(v0, n), time_ratio * scale); + toroidal_euclidean_distance_function_t dist_func{static_cast(array.get_width()), static_cast(array.get_height())}; + auto dist = basis_func->call(neuron_t::distance(&dist_func, v0, n), time_ratio * scale); n.update(current_data.bins, dist, eta); } } @@ -83,11 +84,12 @@ namespace assign3 Scalar som_t::find_closest_neighbour_distance(blt::size_t v0) { + toroidal_euclidean_distance_function_t dist_func{static_cast(array.get_width()), static_cast(array.get_height())}; 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)); + distance_min = std::min(distance_min, neuron_t::distance(&dist_func, array.get_map()[v0], n)); } return distance_min; }