hey almost done
parent
1e3ed0755a
commit
f84919ccb5
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(COSC-4P80-Assignment-3 VERSION 0.0.19)
|
project(COSC-4P80-Assignment-3 VERSION 0.0.20)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
|
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!bash
|
||||||
|
mkdir cmake-build-emscript
|
||||||
|
emcmake cmake -DCMAKE_BUILD_TYPE=Release -S ./ -B ./cmake-build-emscript
|
||||||
|
emmake cmake --build ./cmake-build-emscript -j 32
|
||||||
|
scp ./cmake-build-emscript/COSC-4P80-Assignment-3.* administrator@supercomputer:/var/www/tpgc.me/playground/self_organizing_maps/motors_galore/
|
|
@ -44,7 +44,8 @@ namespace assign3
|
||||||
case shape_t::GRID_OFFSET_WRAP:
|
case shape_t::GRID_OFFSET_WRAP:
|
||||||
for (blt::size_t j = 0; j < height; j++)
|
for (blt::size_t j = 0; j < height; j++)
|
||||||
for (blt::size_t i = 0; i < width; i++)
|
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);
|
map.emplace_back(dimensions, (j % 2 == 0 ? static_cast<Scalar>(i) : static_cast<Scalar>(i) + 0.5f),
|
||||||
|
static_cast<Scalar>(static_cast<double>(j) * (std::sqrt(3) / 2.0)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace assign3
|
||||||
"Edge Wrapped Honey Comb"
|
"Edge Wrapped Honey Comb"
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class debug_type
|
enum class debug_t
|
||||||
{
|
{
|
||||||
DATA_POINT,
|
DATA_POINT,
|
||||||
DISTANCE
|
DISTANCE
|
||||||
|
@ -55,6 +55,25 @@ namespace assign3
|
||||||
"Distance to Datapoint",
|
"Distance to Datapoint",
|
||||||
"Distance to Neighbours"
|
"Distance to Neighbours"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class init_t
|
||||||
|
{
|
||||||
|
COMPLETELY_RANDOM,
|
||||||
|
RANDOM_DATA,
|
||||||
|
SAMPLED_DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::array<std::string, 3> init_names{
|
||||||
|
"Completely Random",
|
||||||
|
"Random Data Based",
|
||||||
|
"Sample Based"
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::array<std::string, 3> init_helps{
|
||||||
|
"Initializes weights randomly between -1 and 1",
|
||||||
|
"Find min and max of each data element, then initialize weights between that range",
|
||||||
|
"Initialize weights based on the input data"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //COSC_4P80_ASSIGNMENT_3_FWDECL_H
|
#endif //COSC_4P80_ASSIGNMENT_3_FWDECL_H
|
||||||
|
|
|
@ -107,7 +107,8 @@ namespace assign3
|
||||||
}
|
}
|
||||||
error_plotting.clear();
|
error_plotting.clear();
|
||||||
som = std::make_unique<som_t>(motor_data.files[currently_selected_network], som_width, som_height, max_epochs,
|
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));
|
distance_function.get(), static_cast<shape_t>(selected_som_mode),
|
||||||
|
static_cast<init_t>(selected_init_type), normalize_init);
|
||||||
error_plotting.push_back(som->topological_error(motor_data.files[currently_selected_network]));
|
error_plotting.push_back(som->topological_error(motor_data.files[currently_selected_network]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,12 +130,15 @@ namespace assign3
|
||||||
|
|
||||||
int currently_selected_network = 0;
|
int currently_selected_network = 0;
|
||||||
int selected_som_mode = 0;
|
int selected_som_mode = 0;
|
||||||
|
int selected_init_type = 0;
|
||||||
|
bool normalize_init = false;
|
||||||
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;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
int debug_state = 0;
|
int debug_state = 0;
|
||||||
int selected_data_point = 0;
|
int selected_data_point = 0;
|
||||||
|
int selected_neuron = 0;
|
||||||
|
|
||||||
float requested_activation = 0.5;
|
float requested_activation = 0.5;
|
||||||
float at_distance_measurement = 2;
|
float at_distance_measurement = 2;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <assign3/fwdecl.h>
|
#include <assign3/fwdecl.h>
|
||||||
#include "blt/std/types.h"
|
#include "blt/std/types.h"
|
||||||
#include <assign3/functions.h>
|
#include <assign3/functions.h>
|
||||||
|
#include <assign3/file.h>
|
||||||
|
|
||||||
namespace assign3
|
namespace assign3
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,7 @@ namespace assign3
|
||||||
data.resize(dimensions);
|
data.resize(dimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
neuron_t& randomize(blt::size_t seed);
|
neuron_t& randomize(blt::size_t seed, init_t init, bool normalize, const data_file_t& file);
|
||||||
|
|
||||||
neuron_t& update(const std::vector<Scalar>& new_data, Scalar dist, Scalar eta);
|
neuron_t& update(const std::vector<Scalar>& new_data, Scalar dist, Scalar eta);
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ 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, distance_function_t* dist_func, shape_t shape);
|
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, init_t init, bool normalize);
|
||||||
|
|
||||||
blt::size_t get_closest_neuron(const std::vector<Scalar>& data);
|
blt::size_t get_closest_neuron(const std::vector<Scalar>& data);
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,8 @@ namespace assign3
|
||||||
Scalar toroidal_euclidean_distance_function_t::distance(blt::span<const Scalar> x, blt::span<const Scalar> y) const
|
Scalar toroidal_euclidean_distance_function_t::distance(blt::span<const Scalar> x, blt::span<const Scalar> y) const
|
||||||
{
|
{
|
||||||
BLT_ASSERT(x.size() == 2 && y.size() == 2);
|
BLT_ASSERT(x.size() == 2 && y.size() == 2);
|
||||||
Scalar x_diff = x[0] - y[0];
|
Scalar x_diff = std::abs(x[0] - y[0]);
|
||||||
Scalar y_diff = x[1] - y[1];
|
Scalar y_diff = std::abs(x[1] - y[1]);
|
||||||
Scalar x_min = std::min(x_diff, static_cast<Scalar>(width) - x_diff);
|
Scalar x_min = std::min(x_diff, static_cast<Scalar>(width) - x_diff);
|
||||||
Scalar y_min = std::min(y_diff, static_cast<Scalar>(height) - y_diff);
|
Scalar y_min = std::min(y_diff, static_cast<Scalar>(height) - y_diff);
|
||||||
return std::sqrt(x_min * x_min + y_min * y_min);
|
return std::sqrt(x_min * x_min + y_min * y_min);
|
||||||
|
|
|
@ -144,10 +144,16 @@ namespace assign3
|
||||||
ImGui::SetNextItemOpen(true, ImGuiCond_Appearing);
|
ImGui::SetNextItemOpen(true, ImGuiCond_Appearing);
|
||||||
if (ImGui::CollapsingHeader("SOM Settings"))
|
if (ImGui::CollapsingHeader("SOM Settings"))
|
||||||
{
|
{
|
||||||
ImGui::Text("Network Shape");
|
ImGui::SeparatorText("Network Shape");
|
||||||
if (ImGui::ListBox("##NetworkShape", &selected_som_mode, get_selection_string, shape_names.data(),
|
if (ImGui::ListBox("##NetworkShape", &selected_som_mode, get_selection_string, shape_names.data(),
|
||||||
static_cast<int>(shape_names.size())))
|
static_cast<int>(shape_names.size())))
|
||||||
regenerate_network();
|
regenerate_network();
|
||||||
|
ImGui::SeparatorText("Init Type");
|
||||||
|
if (ImGui::ListBox("##InitType", &selected_init_type, get_selection_string, init_names.data(), static_cast<int>(init_names.size())))
|
||||||
|
regenerate_network();
|
||||||
|
ImGui::TextWrapped("Help: %s", init_helps[selected_init_type].c_str());
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Checkbox("Normalize Init Data", &normalize_init);
|
||||||
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();
|
||||||
|
@ -160,14 +166,15 @@ namespace assign3
|
||||||
if (debug_mode)
|
if (debug_mode)
|
||||||
{
|
{
|
||||||
ImGui::ListBox("##DebugStateSelect", &debug_state, get_selection_string, debug_names.data(), debug_names.size());
|
ImGui::ListBox("##DebugStateSelect", &debug_state, get_selection_string, debug_names.data(), debug_names.size());
|
||||||
switch (static_cast<debug_type>(debug_state))
|
switch (static_cast<debug_t>(debug_state))
|
||||||
{
|
{
|
||||||
case debug_type::DATA_POINT:
|
case debug_t::DATA_POINT:
|
||||||
{
|
{
|
||||||
ImGui::Checkbox("Data Type Color", &draw_colors);
|
ImGui::Checkbox("Data Type Color", &draw_colors);
|
||||||
ImGui::Checkbox("Data Lines", &draw_data_lines);
|
ImGui::Checkbox("Data Lines", &draw_data_lines);
|
||||||
auto current_data_file = motor_data.files[currently_selected_network];
|
auto current_data_file = motor_data.files[currently_selected_network];
|
||||||
std::vector<std::string> names;
|
static std::vector<std::string> names;
|
||||||
|
names.clear();
|
||||||
for (const auto& [i, v] : blt::enumerate(current_data_file.data_points))
|
for (const auto& [i, v] : blt::enumerate(current_data_file.data_points))
|
||||||
names.push_back("#" + std::to_string(i) + " (" + (v.is_bad ? "Bad)" : "Good)"));
|
names.push_back("#" + std::to_string(i) + " (" + (v.is_bad ? "Bad)" : "Good)"));
|
||||||
ImGui::Text("Select Data Point");
|
ImGui::Text("Select Data Point");
|
||||||
|
@ -175,8 +182,15 @@ namespace assign3
|
||||||
static_cast<int>(names.size()));
|
static_cast<int>(names.size()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case debug_type::DISTANCE:
|
case debug_t::DISTANCE:
|
||||||
|
{
|
||||||
|
static std::vector<std::string> names;
|
||||||
|
names.clear();
|
||||||
|
for (blt::size_t i = 0; i < som->get_array().get_map().size(); i++)
|
||||||
|
names.push_back("Neuron " + std::to_string(i));
|
||||||
|
ImGui::Text("Select Neuron");
|
||||||
|
ImGui::ListBox("##SelectNeuron", &selected_neuron, get_selection_string, names.data(), static_cast<int>(names.size()));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,9 +276,9 @@ namespace assign3
|
||||||
|
|
||||||
void renderer_t::draw_debug(const data_file_t& file)
|
void renderer_t::draw_debug(const data_file_t& file)
|
||||||
{
|
{
|
||||||
switch (static_cast<debug_type>(debug_state))
|
switch (static_cast<debug_t>(debug_state))
|
||||||
{
|
{
|
||||||
case debug_type::DATA_POINT:
|
case debug_t::DATA_POINT:
|
||||||
{
|
{
|
||||||
std::vector<blt::vec2> data_positions;
|
std::vector<blt::vec2> data_positions;
|
||||||
std::vector<blt::vec2> neuron_positions;
|
std::vector<blt::vec2> neuron_positions;
|
||||||
|
@ -300,6 +314,7 @@ namespace assign3
|
||||||
auto scale = topology_function->scale(half, requested_activation);
|
auto scale = topology_function->scale(half, requested_activation);
|
||||||
auto ds = topology_function->call(context.neuron.dist(data_point.bins), scale);
|
auto ds = topology_function->call(context.neuron.dist(data_point.bins), scale);
|
||||||
|
|
||||||
|
if (draw_data_lines)
|
||||||
neuron_positions.push_back(context.neuron_padded);
|
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 = fr2d.render_text(std::to_string(ds), 18).setColor(0.2, 0.2, 0.8);
|
||||||
auto text_width = text.getAssociatedText().getTextWidth();
|
auto text_width = text.getAssociatedText().getTextWidth();
|
||||||
|
@ -317,9 +332,34 @@ namespace assign3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case debug_type::DISTANCE:
|
case debug_t::DISTANCE:
|
||||||
{
|
{
|
||||||
|
auto closest_type = normalize_data(get_neuron_activations(file));
|
||||||
|
auto& selected_neuron_ref = som->get_array().get_map()[selected_neuron];
|
||||||
|
static std::vector<Scalar> distances_2d;
|
||||||
|
static std::vector<Scalar> distances_nd;
|
||||||
|
distances_2d.clear();
|
||||||
|
distances_nd.clear();
|
||||||
|
|
||||||
|
for (const auto& n : som->get_array().get_map())
|
||||||
|
{
|
||||||
|
distances_2d.push_back(neuron_t::distance(distance_function.get(), selected_neuron_ref, n));
|
||||||
|
distances_nd.push_back(selected_neuron_ref.dist(n.get_data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_som(neuron_render_info_t{}.set_base_pos({370, 145}).set_neuron_scale(120).set_neuron_padding({0, 0}),
|
||||||
|
[this, &closest_type](render_data_t context) {
|
||||||
|
auto& text = fr2d.render_text(
|
||||||
|
"2D: " + std::to_string(distances_2d[context.index]) + "\nND: " +
|
||||||
|
std::to_string(distances_nd[context.index]), 18).setColor(0.2, 0.2, 0.8);
|
||||||
|
auto text_width = text.getAssociatedText().getTextWidth();
|
||||||
|
text.setPosition(context.neuron_padded - blt::vec2{text_width / 2.0f, 0}).setZIndex(1);
|
||||||
|
|
||||||
|
if (static_cast<blt::size_t>(selected_neuron) == context.index)
|
||||||
|
return blt::make_color(0, 0, 1);
|
||||||
|
auto type = closest_type[context.index];
|
||||||
|
return type >= 0 ? blt::make_color(0, type, 0) : blt::make_color(-type, 0, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,59 @@
|
||||||
|
|
||||||
namespace assign3
|
namespace assign3
|
||||||
{
|
{
|
||||||
neuron_t& neuron_t::randomize(blt::size_t seed)
|
neuron_t& neuron_t::randomize(blt::size_t seed, init_t init, bool normalize, const data_file_t& file)
|
||||||
{
|
{
|
||||||
blt::random::random_t rand{seed};
|
blt::random::random_t rand{seed};
|
||||||
|
switch (init)
|
||||||
|
{
|
||||||
|
case init_t::COMPLETELY_RANDOM:
|
||||||
for (auto& v : data)
|
for (auto& v : data)
|
||||||
v = static_cast<Scalar>(rand.get_double(-1, 1));
|
v = static_cast<Scalar>(rand.get_double(-1, 1));
|
||||||
|
break;
|
||||||
|
case init_t::RANDOM_DATA:
|
||||||
|
{
|
||||||
|
static std::vector<Scalar> min_values, max_values;
|
||||||
|
min_values.clear();
|
||||||
|
max_values.clear();
|
||||||
|
|
||||||
|
min_values.resize(data.size());
|
||||||
|
max_values.resize(data.size());
|
||||||
|
|
||||||
|
for (const auto& [min, max] : blt::in_pairs(min_values, max_values))
|
||||||
|
{
|
||||||
|
min = std::numeric_limits<Scalar>::max();
|
||||||
|
max = std::numeric_limits<Scalar>::min();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& point : file.data_points)
|
||||||
|
{
|
||||||
|
for (const auto& [i, bin] : blt::enumerate(point.bins))
|
||||||
|
{
|
||||||
|
min_values[i] = std::min(min_values[i], bin);
|
||||||
|
max_values[i] = std::max(max_values[i], bin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& [i, v] : blt::enumerate(data))
|
||||||
|
v = static_cast<Scalar>(rand.get_double(min_values[i], max_values[i]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case init_t::SAMPLED_DATA:
|
||||||
|
{
|
||||||
|
auto selected = rand.select(file.data_points);
|
||||||
|
std::memcpy(data.data(), selected.bins.data(), data.size() * sizeof(Scalar));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalize)
|
||||||
|
{
|
||||||
|
Scalar total = 0;
|
||||||
|
for (auto v : data)
|
||||||
|
total += v * v;
|
||||||
|
Scalar mag = std::sqrt(total);
|
||||||
|
for (auto& v : data)
|
||||||
|
v /= mag;
|
||||||
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -36,14 +83,14 @@ namespace assign3
|
||||||
// apply the distance based on the update neuron function
|
// apply the distance based on the update neuron function
|
||||||
neuron_t& neuron_t::update(const std::vector<Scalar>& new_data, Scalar dist, Scalar eta)
|
neuron_t& neuron_t::update(const std::vector<Scalar>& new_data, Scalar dist, Scalar eta)
|
||||||
{
|
{
|
||||||
static thread_local std::vector<Scalar> diff;
|
// static thread_local std::vector<Scalar> diff;
|
||||||
diff.clear();
|
// diff.clear();
|
||||||
|
|
||||||
for (auto [v, x] : blt::in_pairs(data, new_data))
|
// for (auto [v, x] : blt::in_pairs(data, new_data))
|
||||||
diff.push_back(x - v);
|
// diff.push_back(x - v);
|
||||||
|
|
||||||
for (auto [v, d] : blt::in_pairs(data, diff))
|
for (auto [v, d] : blt::in_pairs(data, new_data))
|
||||||
v += eta * dist * d;
|
v += eta * dist * (d - v);
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,11 @@ namespace assign3
|
||||||
{
|
{
|
||||||
|
|
||||||
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,
|
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,
|
||||||
shape_t shape):
|
shape_t shape, init_t init, bool normalize):
|
||||||
array(file.data_points.begin()->bins.size(), width, height, shape), file(file), max_epochs(max_epochs), dist_func(dist_func)
|
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{}(), init, normalize, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void som_t::train_epoch(Scalar initial_learn_rate, topology_function_t* basis_func)
|
void som_t::train_epoch(Scalar initial_learn_rate, topology_function_t* basis_func)
|
||||||
|
|
Loading…
Reference in New Issue