diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..bc72265 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ceb85c6..7823189 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.25) -project(COSC-4P80-Assignment-2 VERSION 0.0.2) +project(COSC-4P80-Assignment-2 VERSION 0.0.3) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) option(ENABLE_TSAN "Enable the thread data race sanitizer" OFF) -option(EIGEN_TEST_CXX11 "Enable testing with C++11 and C++11 features (e.g. Tensor module)." ON) +#option(EIGEN_TEST_CXX11 "Enable testing with C++11 and C++11 features (e.g. Tensor module)." ON) set(CMAKE_CXX_STANDARD 17) @@ -14,7 +14,7 @@ endif() add_subdirectory(lib/blt) -add_subdirectory(lib/eigen-3.4.0) +#add_subdirectory(lib/eigen-3.4.0) include_directories(include/) file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") @@ -24,7 +24,8 @@ add_executable(COSC-4P80-Assignment-2 ${PROJECT_BUILD_FILES}) target_compile_options(COSC-4P80-Assignment-2 PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) target_link_options(COSC-4P80-Assignment-2 PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) -target_link_libraries(COSC-4P80-Assignment-2 PRIVATE BLT Eigen3::Eigen) +#target_link_libraries(COSC-4P80-Assignment-2 PRIVATE BLT Eigen3::Eigen) +target_link_libraries(COSC-4P80-Assignment-2 PRIVATE BLT) if (${ENABLE_ADDRSAN} MATCHES ON) target_compile_options(COSC-4P80-Assignment-2 PRIVATE -fsanitize=address) diff --git a/include/assign2/common.h b/include/assign2/common.h index b4f4642..705a86d 100644 --- a/include/assign2/common.h +++ b/include/assign2/common.h @@ -19,14 +19,15 @@ #ifndef COSC_4P80_ASSIGNMENT_2_COMMON_H #define COSC_4P80_ASSIGNMENT_2_COMMON_H -#include namespace assign2 { + using Scalar = float; + struct data_t { bool is_bad = false; - std::vector bins; + std::vector bins; }; struct data_file_t @@ -37,9 +38,57 @@ namespace assign2 class layer_t; class network_t; - using Scalar = float; - using matrix_t = Eigen::Matrix; - using vector_t = Eigen::Matrix; + struct weight_view + { + public: + weight_view(double* data, blt::size_t size): m_data(data), m_size(size) + {} + + inline double& operator[](blt::size_t index) const + { +#if BLT_DEBUG_LEVEL > 0 + if (index >= size) + throw std::runtime_error("Index is out of bounds!"); +#endif + return m_data[index]; + } + + [[nodiscard]] inline blt::size_t size() const + { + return m_size; + } + + [[nodiscard]] auto begin() const + { + return m_data; + } + + [[nodiscard]] auto end() const + { + return m_data + m_size; + } + + private: + double* m_data; + blt::size_t m_size; + }; + + /** + * this class exists purely as an optimization + */ + class weight_t + { + public: + weight_view allocate_view(blt::size_t count) + { + auto size = data.size(); + data.resize(size + count); + return {&data[size], count}; + } + + private: + std::vector data; + }; } #endif //COSC_4P80_ASSIGNMENT_2_COMMON_H diff --git a/include/assign2/functions.h b/include/assign2/functions.h index 7526155..0f60d39 100644 --- a/include/assign2/functions.h +++ b/include/assign2/functions.h @@ -35,13 +35,6 @@ namespace assign2 { return call(s) * (1 - call(s)); } - - vector_t operator()(vector_t out) const - { - for (auto& v : out) - v = call(v); - return out; - } }; struct linear_function diff --git a/include/assign2/initializers.h b/include/assign2/initializers.h index 3d22b67..bdfc2e8 100644 --- a/include/assign2/initializers.h +++ b/include/assign2/initializers.h @@ -27,50 +27,35 @@ namespace assign2 { struct empty_init { - template - inline void operator()(Eigen::Matrix& matrix) const + inline Scalar operator()(blt::i32) const { - for (auto r : matrix.rowwise()) - { - for (auto& v : r) - v = 0; - } + return 0; } }; struct half_init { - template - inline void operator()(Eigen::Matrix& matrix) const + inline Scalar operator()(blt::i32) const { - for (auto r : matrix.rowwise()) - { - for (auto& v : r) - v = 0.5f; - } + return 0; } }; struct random_init { public: - explicit random_init(blt::size_t seed, float min = 0.5 - 0.125, float max = 0.5 + 0.125): seed(seed), min(min), max(max) + explicit random_init(blt::size_t seed, Scalar min = -0.5, Scalar max = 0.5): random(seed), seed(seed), min(min), max(max) {} - template - inline void operator()(Eigen::Matrix& matrix) const + inline Scalar operator()(blt::i32) { - blt::random::random_t random(seed); - for (auto r : matrix.rowwise()) - { - for (auto& v : r) - v = random.get_float(min, max); - } + return static_cast(random.get_double(min, max)); } private: + blt::random::random_t random; blt::size_t seed; - float min, max; + Scalar min, max; }; } diff --git a/include/assign2/layer.h b/include/assign2/layer.h index 53b3173..a18f6e5 100644 --- a/include/assign2/layer.h +++ b/include/assign2/layer.h @@ -21,41 +21,109 @@ #include #include +#include "blt/iterator/zip.h" +#include "blt/iterator/iterator.h" namespace assign2 { - class layer_t + class neuron_t { public: - layer_t(const blt::i32 in, const blt::i32 out): in_size(in), out_size(out) + // empty neuron for loading from a stream + explicit neuron_t(weight_view weights): weights(weights) {} - template - void init(WeightsFunc weightFunc = empty_init{}, BiasFunc biasFunc = empty_init{}) + // neuron with bias + explicit neuron_t(weight_view weights, Scalar bias): bias(bias), weights(weights) + {} + + template + Scalar activate(const Scalar* inputs, ActFunc func) const { - weights.resize(in_size, out_size); - bias.resize(out_size); - - weightFunc(weights); - biasFunc(bias); + auto sum = bias; + for (auto [x, w] : blt::zip_iterator_container({inputs, inputs + weights.size()}, {weights.begin(), weights.end()})) + sum += x * w; + return func.call(sum); } - template - vector_t call(const vector_t& in, ActFunction func = ActFunction{}) + template + OStream& serialize(OStream& stream) { - vector_t out; - out.resize(out_size, Eigen::NoChange_t{}); - out.noalias() = weights.transpose() * in; - out.colwise() += bias; - return func(std::move(out)); + stream << bias; + for (auto d : weights) + stream << d; + } + + template + IStream& deserialize(IStream& stream) + { + for (auto& d : blt::iterate(weights).rev()) + stream >> d; + stream >> bias; } + private: + Scalar bias = 0; + weight_view weights; + }; + + class layer_t + { + public: + template + layer_t(const blt::i32 in, const blt::i32 out, WeightFunc w, BiasFunc b): in_size(in), out_size(out) + { + neurons.reserve(out_size); + for (blt::i32 i = 0; i < out_size; i++) + { + auto weight = weights.allocate_view(in_size); + for (auto& v : weight) + v = w(i); + neurons.push_back(neuron_t{weight, b(i)}); + } + } + + template + std::vector call(const std::vector& in, ActFunction func = ActFunction{}) + { + std::vector out; + out.reserve(out_size); +#if BLT_DEBUG_LEVEL > 0 + if (in.size() != in_size) + throw std::runtime_exception("Input vector doesn't match expected input size!"); +#endif + for (auto& n : neurons) + out.push_back(n.activate(in.data(), func)); + return out; + } + + template + OStream& serialize(OStream& stream) + { + for (auto d : neurons) + stream << d; + } + + template + IStream& deserialize(IStream& stream) + { + for (auto& d : blt::iterate(neurons).rev()) + stream >> d; + } + + [[nodiscard]] inline blt::i32 get_in_size() const + { + return in_size; + } + + [[nodiscard]] inline blt::i32 get_out_size() const + { + return out_size; + } private: const blt::i32 in_size, out_size; - matrix_t weights{}; - matrix_t dweights{}; // derivative of weights - vector_t bias{}; - vector_t dbias{}; // derivative of bias + weight_t weights; + std::vector neurons; }; } diff --git a/include/assign2/network.h b/include/assign2/network.h index 40940f4..e56ea62 100644 --- a/include/assign2/network.h +++ b/include/assign2/network.h @@ -27,17 +27,98 @@ namespace assign2 class network_t { public: - network_t(blt::i32 input_size, blt::i32 output_size, blt::i32 hidden_count, blt::i32 hidden_size): - input_size(input_size), output_size(output_size), hidden_count(hidden_count), hidden_size(hidden_size) + template + network_t(blt::i32 input_size, blt::i32 output_size, blt::i32 layer_count, blt::i32 hidden_size, WeightFunc w, BiasFunc b): + input_size(input_size), output_size(output_size), hidden_count(layer_count), hidden_size(hidden_size) { - if (hidden_count > 0) + if (layer_count > 0) { - layers.push_back(layer_t{input_size, hidden_size}); - for (blt::i32 i = 1; i < hidden_count; i++) - layers.push_back(layer_t{hidden_size, hidden_size}); - layers.push_back(layer_t{hidden_size, output_size}); + for (blt::i32 i = 0; i < layer_count; i++) + { + if (i == 0) + layers.push_back(layer_t{input_size, hidden_size, w, b}); + else + layers.push_back(layer_t{hidden_size, hidden_size, w, b}); + } + layers.push_back(layer_t{hidden_size, output_size, w, b}); } else - layers.push_back(layer_t{input_size, output_size}); + { + layers.push_back(layer_t{input_size, output_size, w, b}); + } + } + + template + network_t(blt::i32 input_size, blt::i32 output_size, blt::i32 layer_count, blt::i32 hidden_size, + WeightFunc w, BiasFunc b, OutputWeightFunc ow, OutputBiasFunc ob): + input_size(input_size), output_size(output_size), hidden_count(layer_count), hidden_size(hidden_size) + { + if (layer_count > 0) + { + for (blt::i32 i = 0; i < layer_count; i++) + { + if (i == 0) + layers.push_back(layer_t{input_size, hidden_size, w, b}); + else + layers.push_back(layer_t{hidden_size, hidden_size, w, b}); + } + layers.push_back(layer_t{hidden_size, output_size, ow, ob}); + } else + { + layers.push_back(layer_t{input_size, output_size, ow, ob}); + } + } + + explicit network_t(std::vector layers): + input_size(layers.begin()->get_in_size()), output_size(layers.end()->get_out_size()), + hidden_count(static_cast(layers.size()) - 1), hidden_size(layers.end()->get_in_size()), layers(std::move(layers)) + {} + + network_t() = default; + + template + std::vector execute(const std::vector& input, ActFunc func, ActFuncOut outFunc) + { + std::vector previous_output; + std::vector current_output; + + for (auto [i, v] : blt::enumerate(layers)) + { + previous_output = current_output; + if (i == 0) + current_output = v.call(input, func); + else if (i == layers.size() - 1) + current_output = v.call(previous_output, outFunc); + else + current_output = v.call(previous_output, func); + } + + return current_output; + } + + Scalar train(const data_file_t& example) + { + const Scalar learn_rate = 0.1; + + Scalar total_error = 0; + for (const auto& x : example.data_points) + { + auto o = execute(x.bins, sigmoid_function{}, sigmoid_function{}); + auto y = x.is_bad ? 1.0f : 0.0f; + + Scalar is_bad = 0; + if (o[0] >= 1) + is_bad = 0; + else if (o[1] >= 1) + is_bad = 1; + + auto error = y - is_bad; + if (o[0] >= 1 && o[1] >= 1) + error += 1; + + total_error += error; + + } + return total_error; } private: diff --git a/src/main.cpp b/src/main.cpp index c7b1603..68bf5fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "blt/iterator/enumerate.h" #include #include +#include using namespace assign2; @@ -67,6 +68,18 @@ std::vector load_data_files(const std::vector& files) return loaded_data; } +template +decltype(std::cout)& print_vec(const std::vector& vec) +{ + for (auto [i, v] : blt::enumerate(vec)) + { + std::cout << v; + if (i != vec.size() - 1) + std::cout << ", "; + } + return std::cout; +} + int main(int argc, const char** argv) { blt::arg_parse parser; @@ -77,35 +90,29 @@ int main(int argc, const char** argv) auto data_files = load_data_files(get_data_files(data_directory)); - vector_t input; + std::vector input; input.resize(16); for (auto f : data_files) { if (f.data_points.begin()->bins.size() == 16) { for (auto [i, b] : blt::enumerate(f.data_points.begin()->bins)) - input(static_cast(i)) = b; + input[i] = b; } } random_init randomizer{619}; sigmoid_function sig; - layer_t layer1{16, 4}; - layer_t layer2{4, 4}; - layer_t layer3{4, 4}; - layer_t layer_output{4, 1}; - layer1.init(randomizer, empty_init{}); - layer2.init(randomizer, empty_init{}); - layer3.init(randomizer, empty_init{}); - layer_output.init(randomizer, empty_init{}); + layer_t layer1{16, 4, randomizer, empty_init{}}; + layer_t layer2{4, 4, randomizer, empty_init{}}; + layer_t layer3{4, 4, randomizer, empty_init{}}; + layer_t layer_output{4, 1, randomizer, empty_init{}}; - auto output = layer1.call(input, sig); - output = layer2.call(output, sig); - output = layer3.call(output, sig); - output = layer_output.call(output, sig); + network_t network{{layer1, layer2, layer3, layer_output}}; - std::cout << output << std::endl; + auto output = network.execute(input, sig, sig); + print_vec(output) << std::endl; // for (auto d : data_files) // {