grid don't work
parent
45ccb3647e
commit
1e3ed0755a
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(COSC-4P80-Assignment-3 VERSION 0.0.18)
|
project(COSC-4P80-Assignment-3 VERSION 0.0.19)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
|
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
|
||||||
|
|
|
@ -29,12 +29,24 @@ namespace assign3
|
||||||
class array_t
|
class array_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit array_t(blt::size_t dimensions, blt::size_t width, blt::size_t height):
|
explicit array_t(blt::size_t dimensions, blt::size_t width, blt::size_t height, shape_t shape):
|
||||||
width(static_cast<blt::i64>(width)), height(static_cast<blt::i64>(height))
|
width(static_cast<blt::i64>(width)), height(static_cast<blt::i64>(height))
|
||||||
{
|
{
|
||||||
for (blt::size_t i = 0; i < width; i++)
|
switch (shape)
|
||||||
for (blt::size_t j = 0; j < height; j++)
|
{
|
||||||
map.emplace_back(dimensions, (j % 2 == 0 ? static_cast<Scalar>(i) : static_cast<Scalar>(i) + 0.5f), j);
|
case shape_t::GRID:
|
||||||
|
case shape_t::GRID_WRAP:
|
||||||
|
for (blt::size_t j = 0; j < height; j++)
|
||||||
|
for (blt::size_t i = 0; i < width; i++)
|
||||||
|
map.emplace_back(dimensions, i, j);
|
||||||
|
break;
|
||||||
|
case shape_t::GRID_OFFSET:
|
||||||
|
case shape_t::GRID_OFFSET_WRAP:
|
||||||
|
for (blt::size_t j = 0; j < height; j++)
|
||||||
|
for (blt::size_t i = 0; i < width; i++)
|
||||||
|
map.emplace_back(dimensions, (j % 2 == 0 ? static_cast<Scalar>(i) : static_cast<Scalar>(i) + 0.5f), j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] blt::vec2ul from_index(blt::size_t index) const
|
[[nodiscard]] blt::vec2ul from_index(blt::size_t index) const
|
||||||
|
|
|
@ -30,15 +30,19 @@ namespace assign3
|
||||||
inline constexpr blt::i32 RENDER_2D = 0x0;
|
inline constexpr blt::i32 RENDER_2D = 0x0;
|
||||||
inline constexpr blt::i32 RENDER_3D = 0x1;
|
inline constexpr blt::i32 RENDER_3D = 0x1;
|
||||||
|
|
||||||
enum class shape : blt::i32
|
enum class shape_t : blt::i32
|
||||||
{
|
{
|
||||||
GRID = RENDER_2D,
|
GRID,
|
||||||
GRID_WRAP = RENDER_2D,
|
GRID_WRAP,
|
||||||
GRID_OFFSET = RENDER_2D,
|
GRID_OFFSET,
|
||||||
GRID_OFFSET_WRAP = RENDER_2D,
|
GRID_OFFSET_WRAP
|
||||||
GAUSSIAN_DIST = RENDER_2D,
|
};
|
||||||
TOROIDAL = RENDER_3D,
|
|
||||||
CYLINDER = RENDER_3D
|
inline std::array<std::string, 4> shape_names{
|
||||||
|
"Grid",
|
||||||
|
"Edge Wrapped Grid",
|
||||||
|
"Honey Comb Grid",
|
||||||
|
"Edge Wrapped Honey Comb"
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class debug_type
|
enum class debug_type
|
||||||
|
@ -47,7 +51,7 @@ namespace assign3
|
||||||
DISTANCE
|
DISTANCE
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::array<std::string, 2> debug_names {
|
inline std::array<std::string, 2> debug_names{
|
||||||
"Distance to Datapoint",
|
"Distance to Datapoint",
|
||||||
"Distance to Neighbours"
|
"Distance to Neighbours"
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,31 +92,43 @@ namespace assign3
|
||||||
|
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
void update_graphics();
|
|
||||||
|
|
||||||
void regenerate_network()
|
void regenerate_network()
|
||||||
{
|
{
|
||||||
som = std::make_unique<som_t>(motor_data.files[currently_selected_network].normalize(), som_width, som_height, max_epochs);
|
switch (static_cast<shape_t>(selected_som_mode))
|
||||||
|
{
|
||||||
|
case shape_t::GRID:
|
||||||
|
case shape_t::GRID_OFFSET:
|
||||||
|
distance_function = std::make_unique<euclidean_distance_function_t>();
|
||||||
|
break;
|
||||||
|
case shape_t::GRID_OFFSET_WRAP:
|
||||||
|
case shape_t::GRID_WRAP:
|
||||||
|
distance_function = std::make_unique<toroidal_euclidean_distance_function_t>(som_width, som_height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
error_plotting.clear();
|
||||||
|
som = std::make_unique<som_t>(motor_data.files[currently_selected_network], som_width, som_height, max_epochs,
|
||||||
|
distance_function.get(), static_cast<shape_t>(selected_som_mode));
|
||||||
|
error_plotting.push_back(som->topological_error(motor_data.files[currently_selected_network]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
motor_data_t& motor_data;
|
motor_data_t& motor_data;
|
||||||
std::unique_ptr<som_t> som;
|
std::unique_ptr<som_t> som;
|
||||||
std::unique_ptr<topology_function_t> topology_function;
|
std::unique_ptr<topology_function_t> topology_function;
|
||||||
|
std::unique_ptr<distance_function_t> distance_function;
|
||||||
|
|
||||||
|
std::vector<Scalar> error_plotting;
|
||||||
|
|
||||||
blt::gfx::font_renderer_t fr2d{};
|
blt::gfx::font_renderer_t fr2d{};
|
||||||
blt::gfx::batch_renderer_2d br2d;
|
blt::gfx::batch_renderer_2d br2d;
|
||||||
|
|
||||||
float draw_width = 0;
|
|
||||||
float draw_height = 0;
|
|
||||||
float neuron_scale = 35;
|
|
||||||
|
|
||||||
blt::i32 som_width = 5;
|
blt::i32 som_width = 5;
|
||||||
blt::i32 som_height = 5;
|
blt::i32 som_height = 5;
|
||||||
blt::i32 max_epochs = 100;
|
blt::i32 max_epochs = 2000;
|
||||||
Scalar initial_learn_rate = 0.1;
|
Scalar initial_learn_rate = 1;
|
||||||
|
|
||||||
int currently_selected_network = 0;
|
int currently_selected_network = 0;
|
||||||
|
int selected_som_mode = 0;
|
||||||
bool debug_mode = false;
|
bool debug_mode = false;
|
||||||
bool draw_colors = true;
|
bool draw_colors = true;
|
||||||
bool draw_data_lines = false;
|
bool draw_data_lines = false;
|
||||||
|
|
|
@ -42,17 +42,30 @@ namespace assign3
|
||||||
|
|
||||||
[[nodiscard]] Scalar dist(const std::vector<Scalar>& X) const;
|
[[nodiscard]] Scalar dist(const std::vector<Scalar>& X) const;
|
||||||
|
|
||||||
[[nodiscard]] inline const std::vector<Scalar>& get_data() const
|
neuron_t& set_activation(Scalar act)
|
||||||
|
{
|
||||||
|
activation = act;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void activate(Scalar act)
|
||||||
|
{ activation += act; }
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<Scalar>& get_data() const
|
||||||
{ return data; }
|
{ return data; }
|
||||||
|
|
||||||
[[nodiscard]] inline Scalar get_x() const
|
[[nodiscard]] Scalar get_x() const
|
||||||
{ return x_pos; }
|
{ return x_pos; }
|
||||||
|
|
||||||
[[nodiscard]] inline Scalar get_y() const
|
[[nodiscard]] Scalar get_y() const
|
||||||
{ return y_pos; }
|
{ return y_pos; }
|
||||||
|
|
||||||
|
[[nodiscard]] Scalar get_activation() const
|
||||||
|
{ return activation; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Scalar x_pos, y_pos;
|
Scalar x_pos, y_pos;
|
||||||
|
Scalar activation = 0;
|
||||||
std::vector<Scalar> data;
|
std::vector<Scalar> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace assign3
|
||||||
class som_t
|
class som_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
som_t(const data_file_t& file, blt::size_t width, blt::size_t height, blt::size_t max_epochs);
|
som_t(const data_file_t& file, blt::size_t width, blt::size_t height, blt::size_t max_epochs, distance_function_t* dist_func, shape_t shape);
|
||||||
|
|
||||||
blt::size_t get_closest_neuron(const std::vector<Scalar>& data);
|
blt::size_t get_closest_neuron(const std::vector<Scalar>& data);
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ namespace assign3
|
||||||
|
|
||||||
blt::vec2 get_topological_position(const std::vector<Scalar>& data);
|
blt::vec2 get_topological_position(const std::vector<Scalar>& data);
|
||||||
|
|
||||||
|
Scalar topological_error(const data_file_t& data);
|
||||||
|
|
||||||
[[nodiscard]] const array_t& get_array() const
|
[[nodiscard]] const array_t& get_array() const
|
||||||
{ return array; }
|
{ return array; }
|
||||||
|
|
||||||
|
@ -53,6 +55,7 @@ namespace assign3
|
||||||
data_file_t file;
|
data_file_t file;
|
||||||
blt::size_t current_epoch = 0;
|
blt::size_t current_epoch = 0;
|
||||||
blt::size_t max_epochs;
|
blt::size_t max_epochs;
|
||||||
|
distance_function_t* dist_func;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,6 @@ namespace assign3
|
||||||
topology_function = std::make_unique<gaussian_function_t>();
|
topology_function = std::make_unique<gaussian_function_t>();
|
||||||
|
|
||||||
regenerate_network();
|
regenerate_network();
|
||||||
update_graphics();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_t::cleanup()
|
void renderer_t::cleanup()
|
||||||
|
@ -142,8 +141,13 @@ namespace assign3
|
||||||
ImGui::Checkbox("Run to completion", &running);
|
ImGui::Checkbox("Run to completion", &running);
|
||||||
ImGui::Text("Epoch %ld / %ld", som->get_current_epoch(), som->get_max_epochs());
|
ImGui::Text("Epoch %ld / %ld", som->get_current_epoch(), som->get_max_epochs());
|
||||||
}
|
}
|
||||||
|
ImGui::SetNextItemOpen(true, ImGuiCond_Appearing);
|
||||||
if (ImGui::CollapsingHeader("SOM Settings"))
|
if (ImGui::CollapsingHeader("SOM Settings"))
|
||||||
{
|
{
|
||||||
|
ImGui::Text("Network Shape");
|
||||||
|
if (ImGui::ListBox("##NetworkShape", &selected_som_mode, get_selection_string, shape_names.data(),
|
||||||
|
static_cast<int>(shape_names.size())))
|
||||||
|
regenerate_network();
|
||||||
if (ImGui::InputInt("SOM Width", &som_width) || ImGui::InputInt("SOM Height", &som_height) ||
|
if (ImGui::InputInt("SOM Width", &som_width) || ImGui::InputInt("SOM Height", &som_height) ||
|
||||||
ImGui::InputInt("Max Epochs", &max_epochs))
|
ImGui::InputInt("Max Epochs", &max_epochs))
|
||||||
regenerate_network();
|
regenerate_network();
|
||||||
|
@ -195,13 +199,22 @@ namespace assign3
|
||||||
ImPlotPoint(som_width, som_height), ImPlotHeatmapFlags_ColMajor);
|
ImPlotPoint(som_width, som_height), ImPlotHeatmapFlags_ColMajor);
|
||||||
ImPlot::EndPlot();
|
ImPlot::EndPlot();
|
||||||
}
|
}
|
||||||
|
ImPlot::SetNextAxesLimits(0, max_epochs, 0, 1, ImPlotCond_Once);
|
||||||
|
if (ImPlot::BeginPlot("Error"))
|
||||||
|
{
|
||||||
|
ImPlot::PlotLine("##error", error_plotting.data(), static_cast<int>(error_plotting.size()));
|
||||||
|
ImPlot::EndPlot();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if (running)
|
if (running)
|
||||||
{
|
{
|
||||||
if (som->get_current_epoch() < som->get_max_epochs())
|
if (som->get_current_epoch() < som->get_max_epochs())
|
||||||
|
{
|
||||||
som->train_epoch(initial_learn_rate, topology_function.get());
|
som->train_epoch(initial_learn_rate, topology_function.get());
|
||||||
|
error_plotting.push_back(som->topological_error(current_data_file));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -220,24 +233,6 @@ namespace assign3
|
||||||
fr2d.render();
|
fr2d.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_t::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);
|
|
||||||
draw_height = (max_y - min_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<float> renderer_t::get_neuron_activations(const data_file_t& file)
|
std::vector<float> renderer_t::get_neuron_activations(const data_file_t& file)
|
||||||
{
|
{
|
||||||
static std::vector<float> closest_type;
|
static std::vector<float> closest_type;
|
||||||
|
@ -248,6 +243,9 @@ namespace assign3
|
||||||
for (auto [i, v] : blt::enumerate(som->get_array().get_map()))
|
for (auto [i, v] : blt::enumerate(som->get_array().get_map()))
|
||||||
{
|
{
|
||||||
auto half = som->find_closest_neighbour_distance(i) / at_distance_measurement;
|
auto half = som->find_closest_neighbour_distance(i) / at_distance_measurement;
|
||||||
|
// auto sigma = std::sqrt(-(half * half) / (2 * std::log(requested_activation)));
|
||||||
|
// auto r = 1 / (2 * sigma * sigma);
|
||||||
|
//
|
||||||
auto scale = topology_function->scale(half, requested_activation);
|
auto scale = topology_function->scale(half, requested_activation);
|
||||||
for (const auto& data : file.data_points)
|
for (const auto& data : file.data_points)
|
||||||
{
|
{
|
||||||
|
|
48
src/som.cpp
48
src/som.cpp
|
@ -26,8 +26,9 @@
|
||||||
namespace assign3
|
namespace assign3
|
||||||
{
|
{
|
||||||
|
|
||||||
som_t::som_t(const data_file_t& file, blt::size_t width, blt::size_t height, blt::size_t max_epochs):
|
som_t::som_t(const data_file_t& file, blt::size_t width, blt::size_t height, blt::size_t max_epochs, distance_function_t* dist_func,
|
||||||
array(file.data_points.begin()->bins.size(), width, height), file(file), max_epochs(max_epochs)
|
shape_t shape):
|
||||||
|
array(file.data_points.begin()->bins.size(), width, height, shape), file(file), max_epochs(max_epochs), dist_func(dist_func)
|
||||||
{
|
{
|
||||||
for (auto& v : array.get_map())
|
for (auto& v : array.get_map())
|
||||||
v.randomize(std::random_device{}());
|
v.randomize(std::random_device{}());
|
||||||
|
@ -57,8 +58,7 @@ namespace assign3
|
||||||
{
|
{
|
||||||
if (i == v0_idx)
|
if (i == v0_idx)
|
||||||
continue;
|
continue;
|
||||||
toroidal_euclidean_distance_function_t dist_func{static_cast<blt::i32>(array.get_width()), static_cast<blt::i32>(array.get_height())};
|
auto dist = basis_func->call(neuron_t::distance(dist_func, v0, n), time_ratio * scale);
|
||||||
auto dist = basis_func->call(neuron_t::distance(&dist_func, v0, n), time_ratio * scale);
|
|
||||||
n.update(current_data.bins, dist, eta);
|
n.update(current_data.bins, dist, eta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,12 +84,11 @@ namespace assign3
|
||||||
|
|
||||||
Scalar som_t::find_closest_neighbour_distance(blt::size_t v0)
|
Scalar som_t::find_closest_neighbour_distance(blt::size_t v0)
|
||||||
{
|
{
|
||||||
toroidal_euclidean_distance_function_t dist_func{static_cast<blt::i32>(array.get_width()), static_cast<blt::i32>(array.get_height())};
|
|
||||||
Scalar distance_min = std::numeric_limits<Scalar>::max();
|
Scalar distance_min = std::numeric_limits<Scalar>::max();
|
||||||
for (const auto& [i, n] : blt::enumerate(array.get_map()))
|
for (const auto& [i, n] : blt::enumerate(array.get_map()))
|
||||||
{
|
{
|
||||||
if (i != v0)
|
if (i != v0)
|
||||||
distance_min = std::min(distance_min, neuron_t::distance(&dist_func, array.get_map()[v0], n));
|
distance_min = std::min(distance_min, neuron_t::distance(dist_func, array.get_map()[v0], n));
|
||||||
}
|
}
|
||||||
return distance_min;
|
return distance_min;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +131,7 @@ namespace assign3
|
||||||
auto n_1 = array.get_map()[ni_1];
|
auto n_1 = array.get_map()[ni_1];
|
||||||
auto n_2 = array.get_map()[ni_2];
|
auto n_2 = array.get_map()[ni_2];
|
||||||
auto n_3 = array.get_map()[ni_3];
|
auto n_3 = array.get_map()[ni_3];
|
||||||
|
|
||||||
auto p_1 = blt::vec2{n_1.get_x(), n_1.get_y()};
|
auto p_1 = blt::vec2{n_1.get_x(), n_1.get_y()};
|
||||||
auto p_2 = blt::vec2{n_2.get_x(), n_2.get_y()};
|
auto p_2 = blt::vec2{n_2.get_x(), n_2.get_y()};
|
||||||
auto p_3 = blt::vec2{n_3.get_x(), n_3.get_y()};
|
auto p_3 = blt::vec2{n_3.get_x(), n_3.get_y()};
|
||||||
|
@ -140,5 +139,40 @@ namespace assign3
|
||||||
return (dp1 * p_1) + (dp2 * p_2) + (dp3 * p_3);
|
return (dp1 * p_1) + (dp2 * p_2) + (dp3 * p_3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Scalar som_t::topological_error(const data_file_t& data)
|
||||||
|
{
|
||||||
|
Scalar total = 0;
|
||||||
|
std::vector<std::pair<blt::size_t, Scalar>> distances;
|
||||||
|
|
||||||
|
for (const auto& x : data.data_points)
|
||||||
|
{
|
||||||
|
distances.clear();
|
||||||
|
for (const auto& [i, n] : blt::enumerate(array.get_map()))
|
||||||
|
distances.emplace_back(i, n.dist(x.bins));
|
||||||
|
|
||||||
|
std::pair<blt::size_t, Scalar> min1 = {0, std::numeric_limits<Scalar>::max()};
|
||||||
|
std::pair<blt::size_t, Scalar> min2 = {0, std::numeric_limits<Scalar>::max()};
|
||||||
|
|
||||||
|
for (const auto& elem : distances)
|
||||||
|
{
|
||||||
|
if (elem.second < min1.second)
|
||||||
|
{
|
||||||
|
min2 = min1;
|
||||||
|
min1 = elem;
|
||||||
|
} else if (elem.second < min2.second)
|
||||||
|
min2 = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can assert the neurons are neighbours if the distance between the BMUs and the nearest neighbour are equal.
|
||||||
|
auto min_distances = neuron_t::distance(dist_func, array.get_map()[min1.first], array.get_map()[min2.first]);
|
||||||
|
auto neighbour_distances = find_closest_neighbour_distance(min1.first);
|
||||||
|
|
||||||
|
if (!blt::f_equal(min_distances, neighbour_distances))
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total / static_cast<Scalar>(data.data_points.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue