diff --git a/src/experimental_clfs/ExpClf.cpp b/src/experimental_clfs/ExpClf.cpp new file mode 100644 index 0000000..834a81d --- /dev/null +++ b/src/experimental_clfs/ExpClf.cpp @@ -0,0 +1,171 @@ +// *************************************************************** +// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez +// SPDX-FileType: SOURCE +// SPDX-License-Identifier: MIT +// *************************************************************** + +#include "ExpClf.h" +#include "TensorUtils.hpp" + +namespace platform { + ExpClf::ExpClf() : semaphore_{ CountingSemaphore::getInstance() } + { + } + void ExpClf::setHyperparameters(const nlohmann::json& hyperparameters) + { + if (!hyperparameters.empty()) { + throw std::invalid_argument("Invalid hyperparameters" + hyperparameters.dump()); + } + } + // + // Predict + // + std::vector ExpClf::predict_spode(std::vector>& test_data, int parent) + { + int test_size = test_data[0].size(); + int sample_size = test_data.size(); + auto predictions = std::vector(test_size); + + int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1); + std::vector threads; + auto worker = [&](const std::vector>& samples, int begin, int chunk, int sample_size, std::vector& predictions) { + std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk); +#if defined(__linux__) + pthread_setname_np(pthread_self(), threadName.c_str()); +#else + pthread_setname_np(threadName.c_str()); +#endif + std::vector instance(sample_size); + for (int sample = begin; sample < begin + chunk; ++sample) { + for (int feature = 0; feature < sample_size; ++feature) { + instance[feature] = samples[feature][sample]; + } + predictions[sample] = aode_.predict_spode(instance, parent); + } + semaphore_.release(); + }; + for (int begin = 0; begin < test_size; begin += chunk_size) { + int chunk = std::min(chunk_size, test_size - begin); + semaphore_.acquire(); + threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(predictions)); + } + for (auto& thread : threads) { + thread.join(); + } + return predictions; + } + torch::Tensor ExpClf::predict(torch::Tensor& X) + { + auto X_ = TensorUtils::to_matrix(X); + torch::Tensor y = torch::tensor(predict(X_)); + return y; + } + torch::Tensor ExpClf::predict_proba(torch::Tensor& X) + { + auto X_ = TensorUtils::to_matrix(X); + auto probabilities = predict_proba(X_); + auto n_samples = X.size(1); + int n_classes = probabilities[0].size(); + auto y = torch::zeros({ n_samples, n_classes }); + for (int i = 0; i < n_samples; i++) { + for (int j = 0; j < n_classes; j++) { + y[i][j] = probabilities[i][j]; + } + } + return y; + } + float ExpClf::score(torch::Tensor& X, torch::Tensor& y) + { + auto X_ = TensorUtils::to_matrix(X); + auto y_ = TensorUtils::to_vector(y); + return score(X_, y_); + } + std::vector> ExpClf::predict_proba(const std::vector>& test_data) + { + int test_size = test_data[0].size(); + int sample_size = test_data.size(); + auto probabilities = std::vector>(test_size, std::vector(aode_.statesClass())); + + int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1); + std::vector threads; + auto worker = [&](const std::vector>& samples, int begin, int chunk, int sample_size, std::vector>& predictions) { + std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk); +#if defined(__linux__) + pthread_setname_np(pthread_self(), threadName.c_str()); +#else + pthread_setname_np(threadName.c_str()); +#endif + + std::vector instance(sample_size); + for (int sample = begin; sample < begin + chunk; ++sample) { + for (int feature = 0; feature < sample_size; ++feature) { + instance[feature] = samples[feature][sample]; + } + predictions[sample] = aode_.predict_proba(instance); + } + semaphore_.release(); + }; + for (int begin = 0; begin < test_size; begin += chunk_size) { + int chunk = std::min(chunk_size, test_size - begin); + semaphore_.acquire(); + threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(probabilities)); + } + for (auto& thread : threads) { + thread.join(); + } + return probabilities; + } + std::vector ExpClf::predict(std::vector>& test_data) + { + if (!fitted) { + throw std::logic_error(CLASSIFIER_NOT_FITTED); + } + auto probabilities = predict_proba(test_data); + std::vector predictions(probabilities.size(), 0); + + for (size_t i = 0; i < probabilities.size(); i++) { + predictions[i] = std::distance(probabilities[i].begin(), std::max_element(probabilities[i].begin(), probabilities[i].end())); + } + + return predictions; + } + float ExpClf::score(std::vector>& test_data, std::vector& labels) + { + Timer timer; + timer.start(); + std::vector predictions = predict(test_data); + int correct = 0; + + for (size_t i = 0; i < predictions.size(); i++) { + if (predictions[i] == labels[i]) { + correct++; + } + } + if (debug) { + std::cout << "* Time to predict: " << timer.getDurationString() << std::endl; + } + return static_cast(correct) / predictions.size(); + } + + // + // statistics + // + int ExpClf::getNumberOfNodes() const + { + return aode_.getNumberOfNodes(); + } + int ExpClf::getNumberOfEdges() const + { + return aode_.getNumberOfEdges(); + } + int ExpClf::getNumberOfStates() const + { + return aode_.getNumberOfStates(); + } + int ExpClf::getClassNumStates() const + { + return aode_.statesClass(); + } + + +} \ No newline at end of file diff --git a/src/experimental_clfs/ExpClf.h b/src/experimental_clfs/ExpClf.h new file mode 100644 index 0000000..8db9746 --- /dev/null +++ b/src/experimental_clfs/ExpClf.h @@ -0,0 +1,74 @@ +// *************************************************************** +// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez +// SPDX-FileType: SOURCE +// SPDX-License-Identifier: MIT +// *************************************************************** + +#ifndef EXPCLF_H +#define EXPCLF_H +#include +#include +#include +#include +#include +#include +#include "bayesnet/BaseClassifier.h" +#include "common/Timer.hpp" +#include "CountingSemaphore.hpp" +#include "Xaode.hpp" + +namespace platform { + + class ExpClf : public bayesnet::BaseClassifier { + public: + ExpClf(); + virtual ~ExpClf() = default; + ExpClf& fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) { return *this; }; + // X is nxm tensor, y is nx1 tensor + ExpClf& fit(torch::Tensor& X, torch::Tensor& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) { return *this; }; + ExpClf& fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) { return *this; }; + ExpClf& fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) { return *this; }; + std::vector predict(std::vector>& X) override; + torch::Tensor predict(torch::Tensor& X) override; + torch::Tensor predict_proba(torch::Tensor& X) override; + std::vector predict_spode(std::vector>& test_data, int parent); + std::vector> predict_proba(std::vector>& X) override; + float score(std::vector>& X, std::vector& y) override; + float score(torch::Tensor& X, torch::Tensor& y) override; + int getNumberOfNodes() const override; + int getNumberOfEdges() const override; + int getNumberOfStates() const override; + int getClassNumStates() const override; + std::vector show() const override { return {}; } + std::vector topological_order() override { return {}; } + std::string dump_cpt() const override { return ""; } + void setDebug(bool debug) { this->debug = debug; } + bayesnet::status_t getStatus() const override { return status; } + std::vector getNotes() const override { return notes; } + std::vector graph(const std::string& title = "") const override { return {}; } + void setHyperparameters(const nlohmann::json& hyperparameters) override; + void set_active_parents(std::vector active_parents) { for (const auto& parent : active_parents) aode_.add_active_parent(parent); } + void add_active_parent(int parent) { aode_.add_active_parent(parent); } + void remove_last_parent() { aode_.remove_last_parent(); } + protected: + bool debug = false; + Xaode aode_; + torch::Tensor weights_; + bool fitted = false; + const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted"; + inline void normalize_weights(int num_instances) + { + double sum = weights_.sum().item(); + if (sum == 0) { + weights_ = torch::full({ num_instances }, 1.0); + } else { + for (int i = 0; i < weights_.size(0); ++i) { + weights_[i] = weights_[i].item() * num_instances / sum; + } + } + } + private: + CountingSemaphore& semaphore_; + }; +} +#endif // EXPCLF_H \ No newline at end of file diff --git a/src/experimental_clfs/XA1DE.cpp b/src/experimental_clfs/XA1DE.cpp index b9009b5..c201017 100644 --- a/src/experimental_clfs/XA1DE.cpp +++ b/src/experimental_clfs/XA1DE.cpp @@ -8,190 +8,20 @@ #include "TensorUtils.hpp" namespace platform { - XA1DE::XA1DE() : semaphore_{ CountingSemaphore::getInstance() } - { - validHyperparameters = { "use_threads" }; - } - void XA1DE::setHyperparameters(const nlohmann::json& hyperparameters_) - { - auto hyperparameters = hyperparameters_; - if (hyperparameters.contains("use_threads")) { - use_threads = hyperparameters["use_threads"].get(); - hyperparameters.erase("use_threads"); - } - if (!hyperparameters.empty()) { - throw std::invalid_argument("Invalid hyperparameters" + hyperparameters.dump()); - } - } XA1DE& XA1DE::fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) { - Timer timer, timert; - timer.start(); - timert.start(); - // debug = true; std::vector> instances = X; instances.push_back(y); int num_instances = instances[0].size(); int num_attributes = instances.size(); - normalize_weights(num_instances); - std::vector statesv; - for (int i = 0; i < num_attributes; i++) { - statesv.push_back(*max_element(instances[i].begin(), instances[i].end()) + 1); - } - // std::cout << "* States: " << statesv << std::endl; - // std::cout << "* Weights: " << weights_ << std::endl; - // std::cout << "* Instances: " << num_instances << std::endl; - // std::cout << "* Attributes: " << num_attributes << std::endl; - // std::cout << "* y: " << y << std::endl; - // std::cout << "* x shape: " << X.size() << "x" << X[0].size() << std::endl; - // for (int i = 0; i < num_attributes - 1; i++) { - // std::cout << "* " << features[i] << ": " << instances[i] << std::endl; - // } - // std::cout << "Starting to build the model" << std::endl; - aode_.init(statesv); - aode_.duration_first += timer.getDuration(); timer.start(); - std::vector instance; - for (int n_instance = 0; n_instance < num_instances; n_instance++) { - instance.clear(); - for (int feature = 0; feature < num_attributes; feature++) { - instance.push_back(instances[feature][n_instance]); - } - aode_.addSample(instance, weights_[n_instance]); - } - aode_.duration_second += timer.getDuration(); timer.start(); - // if (debug) aode_.show(); - aode_.computeProbabilities(); - aode_.duration_third += timer.getDuration(); - if (debug) { - // std::cout << "* Checking coherence... "; - // aode_.checkCoherenceApprox(1e-6); - // std::cout << "Ok!" << std::endl; - aode_.show(); - // std::cout << "* Accumulated first time: " << aode_.duration_first << std::endl; - // std::cout << "* Accumulated second time: " << aode_.duration_second << std::endl; - // std::cout << "* Accumulated third time: " << aode_.duration_third << std::endl; - std::cout << "* Time to build the model: " << timert.getDuration() << " seconds" << std::endl; - // exit(1); - } + aode_.fit(X, y, features, className, states, weights_, true); fitted = true; return *this; } - std::vector> XA1DE::predict_proba(std::vector>& test_data) - { - if (use_threads) { - return predict_proba_threads(test_data); - } - int test_size = test_data[0].size(); - std::vector> probabilities; - - std::vector instance; - for (int i = 0; i < test_size; i++) { - instance.clear(); - for (int j = 0; j < (int)test_data.size(); j++) { - instance.push_back(test_data[j][i]); - } - probabilities.push_back(aode_.predict_proba(instance)); - } - return probabilities; - } - std::vector> XA1DE::predict_proba_threads(const std::vector>& test_data) - { - int test_size = test_data[0].size(); - int sample_size = test_data.size(); - auto probabilities = std::vector>(test_size, std::vector(aode_.statesClass())); - - int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1); - std::vector threads; - auto worker = [&](const std::vector>& samples, int begin, int chunk, int sample_size, std::vector>& predictions) { - std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk); -#if defined(__linux__) - pthread_setname_np(pthread_self(), threadName.c_str()); -#else - pthread_setname_np(threadName.c_str()); -#endif - - std::vector instance(sample_size); - for (int sample = begin; sample < begin + chunk; ++sample) { - for (int feature = 0; feature < sample_size; ++feature) { - instance[feature] = samples[feature][sample]; - } - predictions[sample] = aode_.predict_proba(instance); - } - semaphore_.release(); - }; - for (int begin = 0; begin < test_size; begin += chunk_size) { - int chunk = std::min(chunk_size, test_size - begin); - semaphore_.acquire(); - threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(probabilities)); - } - for (auto& thread : threads) { - thread.join(); - } - return probabilities; - } - std::vector XA1DE::predict(std::vector>& test_data) - { - if (!fitted) { - throw std::logic_error(CLASSIFIER_NOT_FITTED); - } - auto probabilities = predict_proba(test_data); - std::vector predictions(probabilities.size(), 0); - - for (size_t i = 0; i < probabilities.size(); i++) { - predictions[i] = std::distance(probabilities[i].begin(), std::max_element(probabilities[i].begin(), probabilities[i].end())); - } - - return predictions; - } - float XA1DE::score(std::vector>& test_data, std::vector& labels) - { - aode_.duration_first = 0.0; - aode_.duration_second = 0.0; - aode_.duration_third = 0.0; - Timer timer; - timer.start(); - std::vector predictions = predict(test_data); - int correct = 0; - - for (size_t i = 0; i < predictions.size(); i++) { - if (predictions[i] == labels[i]) { - correct++; - } - } - if (debug) { - std::cout << "* Time to predict: " << timer.getDurationString() << std::endl; - std::cout << "* Accumulated first time: " << aode_.duration_first << std::endl; - std::cout << "* Accumulated second time: " << aode_.duration_second << std::endl; - std::cout << "* Accumulated third time: " << aode_.duration_third << std::endl; - } - return static_cast(correct) / predictions.size(); - } - - // - // statistics - // - int XA1DE::getNumberOfNodes() const - { - return aode_.getNumberOfNodes(); - } - int XA1DE::getNumberOfEdges() const - { - return aode_.getNumberOfEdges(); - } - int XA1DE::getNumberOfStates() const - { - return aode_.getNumberOfStates(); - } - int XA1DE::getClassNumStates() const - { - return aode_.statesClass(); - } - // // Fit // - // fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) XA1DE& XA1DE::fit(torch::Tensor& X, torch::Tensor& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) { auto X_ = TensorUtils::to_matrix(X); @@ -206,71 +36,7 @@ namespace platform { } XA1DE& XA1DE::fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) { - weights_ = TensorUtils::to_vector(weights); + weights_ = weights; return fit(dataset, features, className, states, smoothing); } - // - // Predict - // - std::vector XA1DE::predict_spode(std::vector>& test_data, int parent) - { - int test_size = test_data[0].size(); - int sample_size = test_data.size(); - auto predictions = std::vector(test_size); - - int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1); - std::vector threads; - auto worker = [&](const std::vector>& samples, int begin, int chunk, int sample_size, std::vector& predictions) { - std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk); -#if defined(__linux__) - pthread_setname_np(pthread_self(), threadName.c_str()); -#else - pthread_setname_np(threadName.c_str()); -#endif - std::vector instance(sample_size); - for (int sample = begin; sample < begin + chunk; ++sample) { - for (int feature = 0; feature < sample_size; ++feature) { - instance[feature] = samples[feature][sample]; - } - predictions[sample] = aode_.predict_spode(instance, parent); - } - semaphore_.release(); - }; - for (int begin = 0; begin < test_size; begin += chunk_size) { - int chunk = std::min(chunk_size, test_size - begin); - semaphore_.acquire(); - threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(predictions)); - } - for (auto& thread : threads) { - thread.join(); - } - return predictions; - } - torch::Tensor XA1DE::predict(torch::Tensor& X) - { - auto X_ = TensorUtils::to_matrix(X); - torch::Tensor y = torch::tensor(predict(X_)); - return y; - } - torch::Tensor XA1DE::predict_proba(torch::Tensor& X) - { - auto X_ = TensorUtils::to_matrix(X); - auto probabilities = predict_proba(X_); - auto n_samples = X.size(1); - int n_classes = probabilities[0].size(); - auto y = torch::zeros({ n_samples, n_classes }); - for (int i = 0; i < n_samples; i++) { - for (int j = 0; j < n_classes; j++) { - y[i][j] = probabilities[i][j]; - } - } - return y; - } - float XA1DE::score(torch::Tensor& X, torch::Tensor& y) - { - auto X_ = TensorUtils::to_matrix(X); - auto y_ = TensorUtils::to_vector(y); - return score(X_, y_); - } - } \ No newline at end of file diff --git a/src/experimental_clfs/XA1DE.h b/src/experimental_clfs/XA1DE.h index f9305d0..aa9bcfa 100644 --- a/src/experimental_clfs/XA1DE.h +++ b/src/experimental_clfs/XA1DE.h @@ -11,71 +11,24 @@ #include #include #include -#include "bayesnet/BaseClassifier.h" #include "common/Timer.hpp" -#include "CountingSemaphore.hpp" #include "Xaode.hpp" +#include "ExpClf.h" namespace platform { - - class XA1DE : public bayesnet::BaseClassifier { + class XA1DE : public ExpClf { public: - XA1DE(); + XA1DE() = default; virtual ~XA1DE() = default; XA1DE& fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) override; XA1DE& fit(torch::Tensor& X, torch::Tensor& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) override; XA1DE& fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) override; XA1DE& fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override; - std::vector predict(std::vector>& X) override; - torch::Tensor predict(torch::Tensor& X) override; - torch::Tensor predict_proba(torch::Tensor& X) override; - std::vector predict_spode(std::vector>& test_data, int parent); - std::vector> predict_proba_threads(const std::vector>& test_data); - std::vector> predict_proba(std::vector>& X) override; - float score(std::vector>& X, std::vector& y) override; - float score(torch::Tensor& X, torch::Tensor& y) override; - int getNumberOfNodes() const override; - int getNumberOfEdges() const override; - int getNumberOfStates() const override; - int getClassNumStates() const override; - bayesnet::status_t getStatus() const override { return status; } std::string getVersion() override { return version; }; - std::vector show() const override { return {}; } - std::vector topological_order() override { return {}; } - std::vector getNotes() const override { return notes; } - std::string dump_cpt() const override { return ""; } - void setHyperparameters(const nlohmann::json& hyperparameters) override; - std::vector& getValidHyperparameters() { return validHyperparameters; } - void setDebug(bool debug) { this->debug = debug; } - std::vector graph(const std::string& title = "") const override { return {}; } - void set_active_parents(std::vector active_parents) { for (const auto& parent : active_parents) aode_.set_active_parent(parent); } - void add_active_parent(int parent) { aode_.set_active_parent(parent); } - void remove_last_parent() { aode_.remove_last_parent(); } protected: void trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override {}; - private: - const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted"; - inline void normalize_weights(int num_instances) - { - double sum = std::accumulate(weights_.begin(), weights_.end(), 0.0); - if (sum == 0) { - weights_ = std::vector(num_instances, 1.0); - } else { - for (double& w : weights_) { - w = w * num_instances / sum; - } - } - } - Xaode aode_; - std::vector weights_; - CountingSemaphore& semaphore_; - bool debug = false; - bayesnet::status_t status = bayesnet::NORMAL; - std::vector notes; - bool use_threads = true; std::string version = "1.0.0"; - bool fitted = false; }; } #endif // XA1DE_H \ No newline at end of file diff --git a/src/experimental_clfs/XBAODE.cpp b/src/experimental_clfs/XBAODE.cpp index 11c4b83..5da29ca 100644 --- a/src/experimental_clfs/XBAODE.cpp +++ b/src/experimental_clfs/XBAODE.cpp @@ -11,17 +11,16 @@ #include "XBAODE.h" #include "TensorUtils.hpp" #include -#include namespace platform { - XBAODE::XBAODE() : semaphore_{ CountingSemaphore::getInstance() }, Boost(false) + XBAODE::XBAODE() : Boost(false) { - validHyperparameters = { "alpha_block", "order", "convergence", "convergence_best", "bisection", "threshold", "maxTolerance", + Boost::validHyperparameters = { "alpha_block", "order", "convergence", "convergence_best", "bisection", "threshold", "maxTolerance", "predict_voting", "select_features" }; } void XBAODE::trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) { - fitted = true; + Boost::fitted = true; X_train_ = TensorUtils::to_matrix(X_train); y_train_ = TensorUtils::to_vector(y_train); X_test_ = TensorUtils::to_matrix(X_test); @@ -40,18 +39,17 @@ namespace platform { torch::Tensor weights_ = torch::full({ m }, 1.0 / m, torch::kFloat64); bool finished = false; std::vector featuresUsed; - significanceModels.resize(n, 0.0); // n possible spodes - aode_.fit(X_train_, y_train_, features, className, states, smoothing); + aode_.fit(X_train_, y_train_, features, className, states, weights_, false); n_models = 0; if (selectFeatures) { featuresUsed = featureSelection(weights_); - aode_.set_active_parents(featuresUsed); - notes.push_back("Used features in initialization: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size()) + " with " + select_features_algorithm); - auto ypred = aode_.predict(X_train); + set_active_parents(featuresUsed); + Boost::notes.push_back("Used features in initialization: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size()) + " with " + select_features_algorithm); + auto ypred = ExpClf::predict(X_train); std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred, weights_); // Update significance of the models for (const auto& parent : featuresUsed) { - significanceModels[parent] = alpha_t; + aode_.significance_models[parent] = alpha_t; } n_models = featuresUsed.size(); VLOG_SCOPE_F(1, "SelectFeatures. alpha_t: %f n_models: %d", alpha_t, n_models); @@ -88,7 +86,7 @@ namespace platform { while (counter++ < k && featureSelection.size() > 0) { auto feature = featureSelection[0]; featureSelection.erase(featureSelection.begin()); - aode_.add_active_parent(feature); + add_active_parent(feature); alpha_t = 0.0; std::vector ypred; if (alpha_block) { @@ -97,16 +95,16 @@ namespace platform { // // Add the model to the ensemble n_models++; - significanceModels[feature] = 1.0; + aode_.significance_models[feature] = 1.0; aode_.add_active_parent(feature); // Compute the prediction - ypred = aode_.predict(X_train_); + ypred = ExpClf::predict(X_train_); // Remove the model from the ensemble - significanceModels[feature] = 0.0; + aode_.significance_models[feature] = 0.0; aode_.remove_last_parent(); n_models--; } else { - ypred = aode_.predict_spode(X_train_, feature); + ypred = predict_spode(X_train_, feature); } // Step 3.1: Compute the classifier amout of say auto ypred_t = torch::tensor(ypred); @@ -115,12 +113,12 @@ namespace platform { numItemsPack++; featuresUsed.push_back(feature); aode_.add_active_parent(feature); - significanceModels.push_back(alpha_t); + aode_.significance_models[feature] = alpha_t; n_models++; VLOG_SCOPE_F(2, "finished: %d numItemsPack: %d n_models: %d featuresUsed: %zu", finished, numItemsPack, n_models, featuresUsed.size()); } // End of the pack if (convergence && !finished) { - auto y_val_predict = predict(X_test); + auto y_val_predict = ExpClf::predict(X_test); double accuracy = (y_val_predict == y_test).sum().item() / (double)y_test.size(0); if (priorAccuracy == 0) { priorAccuracy = accuracy; @@ -148,79 +146,24 @@ namespace platform { } if (tolerance > maxTolerance) { if (numItemsPack < n_models) { - notes.push_back("Convergence threshold reached & " + std::to_string(numItemsPack) + " models eliminated"); + Boost::notes.push_back("Convergence threshold reached & " + std::to_string(numItemsPack) + " models eliminated"); VLOG_SCOPE_F(4, "Convergence threshold reached & %d models eliminated of %d", numItemsPack, n_models); - for (int i = 0; i < numItemsPack; ++i) { - significanceModels.pop_back(); - models.pop_back(); + for (int i = featuresUsed.size() - 1; i >= featuresUsed.size() - numItemsPack; --i) { + aode_.remove_last_parent(); + aode_.significance_models[featuresUsed[i]] = 0.0; n_models--; } + VLOG_SCOPE_F(4, "*Convergence threshold %d models left & %d features used.", n_models, featuresUsed.size()); } else { - notes.push_back("Convergence threshold reached & 0 models eliminated"); + Boost::notes.push_back("Convergence threshold reached & 0 models eliminated"); VLOG_SCOPE_F(4, "Convergence threshold reached & 0 models eliminated n_models=%d numItemsPack=%d", n_models, numItemsPack); } } if (featuresUsed.size() != features.size()) { - notes.push_back("Used features in train: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size())); - status = bayesnet::WARNING; + Boost::notes.push_back("Used features in train: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size())); + Boost::status = bayesnet::WARNING; } - notes.push_back("Number of models: " + std::to_string(n_models)); + Boost::notes.push_back("Number of models: " + std::to_string(n_models)); return; } - - // - // Predict - // - std::vector> XBAODE::predict_proba(std::vector>& test_data) - { - return aode_.predict_proba_threads(test_data); - } - std::vector XBAODE::predict(std::vector>& test_data) - { - if (!fitted) { - throw std::logic_error(CLASSIFIER_NOT_FITTED); - } - return aode_.predict(test_data); - } - float XBAODE::score(std::vector>& test_data, std::vector& labels) - { - return aode_.score(test_data, labels); - } - - // - // statistics - // - int XBAODE::getNumberOfNodes() const - { - return aode_.getNumberOfNodes(); - } - int XBAODE::getNumberOfEdges() const - { - return aode_.getNumberOfEdges(); - } - int XBAODE::getNumberOfStates() const - { - return aode_.getNumberOfStates(); - } - int XBAODE::getClassNumStates() const - { - return aode_.getClassNumStates(); - } - - // - // Predict - // - torch::Tensor XBAODE::predict(torch::Tensor& X) - { - return aode_.predict(X); - } - torch::Tensor XBAODE::predict_proba(torch::Tensor& X) - { - return aode_.predict_proba(X); - } - float XBAODE::score(torch::Tensor& X, torch::Tensor& y) - { - return aode_.score(X, y); - } - } \ No newline at end of file diff --git a/src/experimental_clfs/XBAODE.h b/src/experimental_clfs/XBAODE.h index fdb0e7a..93ae33d 100644 --- a/src/experimental_clfs/XBAODE.h +++ b/src/experimental_clfs/XBAODE.h @@ -12,52 +12,23 @@ #include #include #include "common/Timer.hpp" -#include "CountingSemaphore.hpp" #include "bayesnet/ensembles/Boost.h" -#include "XA1DE.h" +#include "ExpClf.h" namespace platform { - class XBAODE : public bayesnet::Boost { + class XBAODE : public bayesnet::Boost, public ExpClf { public: XBAODE(); virtual ~XBAODE() = default; - const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted"; - std::vector predict(std::vector>& X) override; - torch::Tensor predict(torch::Tensor& X) override; - torch::Tensor predict_proba(torch::Tensor& X) override; - std::vector> predict_proba(std::vector>& X) override; - float score(std::vector>& X, std::vector& y) override; - float score(torch::Tensor& X, torch::Tensor& y) override; - int getNumberOfNodes() const override; - int getNumberOfEdges() const override; - int getNumberOfStates() const override; - int getClassNumStates() const override; - bayesnet::status_t getStatus() const override { return status; } std::string getVersion() override { return version; }; - std::vector show() const override { return {}; } - std::vector topological_order() override { return {}; } - std::vector getNotes() const override { return notes; } - std::string dump_cpt() const override { return ""; } - std::vector& getValidHyperparameters() { return validHyperparameters; } - void setDebug(bool debug) { this->debug = debug; } - std::vector graph(const std::string& title = "") const override { return {}; } - void set_active_parents(std::vector active_parents) { aode_.set_active_parents(active_parents); } protected: void trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override; private: std::vector> X_train_, X_test_; std::vector y_train_, y_test_; torch::Tensor dataset; - XA1DE aode_; int n_models; - std::vector weights_; - CountingSemaphore& semaphore_; - bool debug = false; - bayesnet::status_t status = bayesnet::NORMAL; - std::vector notes; - bool use_threads = true; std::string version = "0.9.7"; - bool fitted = false; }; } #endif // XBAODE_H \ No newline at end of file diff --git a/src/experimental_clfs/Xaode.hpp b/src/experimental_clfs/Xaode.hpp index 11e850c..9a74135 100644 --- a/src/experimental_clfs/Xaode.hpp +++ b/src/experimental_clfs/Xaode.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace platform { class Xaode { @@ -28,11 +29,49 @@ namespace platform { COUNTS, PROBS }; - double duration_first = 0.0; - double duration_second = 0.0; - double duration_third = 0.0; + std::vector significance_models; Xaode() : nFeatures_{ 0 }, statesClass_{ 0 }, matrixState_{ MatrixState::EMPTY } {} // ------------------------------------------------------- + // fit + // ------------------------------------------------------- + // + // Classifiers interface + // all parameter decide if the model is initialized with all the parents active or none of them + // + void fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const torch::Tensor& weights, const bool all_parents) + { + int num_instances = X[0].size(); + int n_features_ = X.size(); + + significance_models.resize(n_features_, (all_parents ? 1.0 : 0.0)); + std::vector statesv; + for (int i = 0; i < n_features_; i++) { + if (all_parents) active_parents.push_back(i); + statesv.push_back(*max_element(X[i].begin(), X[i].end()) + 1); + } + statesv.push_back(*max_element(y.begin(), y.end()) + 1); + // std::cout << "* States: " << statesv << std::endl; + // std::cout << "* Weights: " << weights_ << std::endl; + // std::cout << "* Instances: " << num_instances << std::endl; + // std::cout << "* Attributes: " << n_features_ +1 << std::endl; + // std::cout << "* y: " << y << std::endl; + // std::cout << "* x shape: " << X.size() << "x" << X[0].size() << std::endl; + // for (int i = 0; i < n_features_; i++) { + // std::cout << "* " << features[i] << ": " << instances[i] << std::endl; + // } + // std::cout << "Starting to build the model" << std::endl; + init(statesv); + std::vector instance(n_features_ + 1); + for (int n_instance = 0; n_instance < num_instances; n_instance++) { + for (int feature = 0; feature < n_features_; feature++) { + instance[feature] = X[feature][n_instance]; + } + instance[n_features_] = y[n_instance]; + addSample(instance, weights[n_instance].item()); + } + computeProbabilities(); + } + // ------------------------------------------------------- // init // ------------------------------------------------------- // @@ -406,7 +445,7 @@ namespace platform { { return (nFeatures_ + 1) * nFeatures_; } - void set_active_parent(int active_parent) + void add_active_parent(int active_parent) { active_parents.push_back(active_parent); }