diff --git a/Makefile b/Makefile index dadc0ee..eeb6ebe 100644 --- a/Makefile +++ b/Makefile @@ -98,8 +98,8 @@ test: ## Run tests (opt="-s") to verbose output the tests, (opt="-c='Test Maximu fname = iris example: ## Build sample @echo ">>> Building Sample..."; - @cmake --build build_debug -t sample - build_debug/sample/PlatformSample --model BoostAODE --dataset $(fname) --discretize --stratified + @cmake --build $(f_release) -t sample + $(f_release)/sample/PlatformSample --model BoostAODE --dataset $(fname) --discretize --stratified @echo ">>> Done"; diff --git a/src/experimental_clfs/TensorUtils.hpp b/src/experimental_clfs/TensorUtils.hpp new file mode 100644 index 0000000..6f09859 --- /dev/null +++ b/src/experimental_clfs/TensorUtils.hpp @@ -0,0 +1,51 @@ +#ifndef TENSORUTILS_HPP +#define TENSORUTILS_HPP +#include +#include +namespace platform { + class TensorUtils { + public: + static std::vector> to_matrix(const torch::Tensor& X) + { + // Ensure tensor is contiguous in memory + auto X_contig = X.contiguous(); + + // Access tensor data pointer directly + auto data_ptr = X_contig.data_ptr(); + + // IF you are using int64_t as the data type, use the following line + //auto data_ptr = X_contig.data_ptr(); + //std::vector> data(X.size(0), std::vector(X.size(1))); + + // Prepare output container + std::vector> data(X.size(0), std::vector(X.size(1))); + + // Fill the 2D vector in a single loop using pointer arithmetic + int rows = X.size(0); + int cols = X.size(1); + for (int i = 0; i < rows; ++i) { + std::copy(data_ptr + i * cols, data_ptr + (i + 1) * cols, data[i].begin()); + } + return data; + } + template + static std::vector to_vector(const torch::Tensor& y) + { + // Ensure the tensor is contiguous in memory + auto y_contig = y.contiguous(); + + // Access data pointer + auto data_ptr = y_contig.data_ptr(); + + // Prepare output container + std::vector data(y.size(0)); + + // Copy data efficiently + std::copy(data_ptr, data_ptr + y.size(0), data.begin()); + + return data; + } + }; +} + +#endif // TENSORUTILS_HPP \ No newline at end of file diff --git a/src/experimental_clfs/XA1DE.cpp b/src/experimental_clfs/XA1DE.cpp index f8c2849..b9009b5 100644 --- a/src/experimental_clfs/XA1DE.cpp +++ b/src/experimental_clfs/XA1DE.cpp @@ -5,6 +5,7 @@ // *************************************************************** #include "XA1DE.h" +#include "TensorUtils.hpp" namespace platform { XA1DE::XA1DE() : semaphore_{ CountingSemaphore::getInstance() } @@ -32,6 +33,7 @@ namespace platform { 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++) { @@ -166,47 +168,6 @@ namespace platform { return static_cast(correct) / predictions.size(); } - std::vector> XA1DE::to_matrix(const torch::Tensor& X) - { - // Ensure tensor is contiguous in memory - auto X_contig = X.contiguous(); - - // Access tensor data pointer directly - auto data_ptr = X_contig.data_ptr(); - - // IF you are using int64_t as the data type, use the following line - //auto data_ptr = X_contig.data_ptr(); - //std::vector> data(X.size(0), std::vector(X.size(1))); - - // Prepare output container - std::vector> data(X.size(0), std::vector(X.size(1))); - - // Fill the 2D vector in a single loop using pointer arithmetic - int rows = X.size(0); - int cols = X.size(1); - for (int i = 0; i < rows; ++i) { - std::copy(data_ptr + i * cols, data_ptr + (i + 1) * cols, data[i].begin()); - } - return data; - } - template - std::vector XA1DE::to_vector(const torch::Tensor& y) - { - // Ensure the tensor is contiguous in memory - auto y_contig = y.contiguous(); - - // Access data pointer - auto data_ptr = y_contig.data_ptr(); - - // Prepare output container - std::vector data(y.size(0)); - - // Copy data efficiently - std::copy(data_ptr, data_ptr + y.size(0), data.begin()); - - return data; - } - // // statistics // @@ -233,8 +194,8 @@ namespace platform { // 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_ = to_matrix(X); - auto y_ = to_vector(y); + auto X_ = TensorUtils::to_matrix(X); + auto y_ = TensorUtils::to_vector(y); return fit(X_, y_, features, className, states, smoothing); } XA1DE& XA1DE::fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) @@ -245,21 +206,55 @@ 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_ = to_vector(weights); + weights_ = TensorUtils::to_vector(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_ = to_matrix(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_ = to_matrix(X); + auto X_ = TensorUtils::to_matrix(X); auto probabilities = predict_proba(X_); auto n_samples = X.size(1); int n_classes = probabilities[0].size(); @@ -273,8 +268,8 @@ namespace platform { } float XA1DE::score(torch::Tensor& X, torch::Tensor& y) { - auto X_ = to_matrix(X); - auto y_ = to_vector(y); + auto X_ = TensorUtils::to_matrix(X); + auto y_ = TensorUtils::to_vector(y); return score(X_, y_); } diff --git a/src/experimental_clfs/XA1DE.h b/src/experimental_clfs/XA1DE.h index b4bd5ce..f9305d0 100644 --- a/src/experimental_clfs/XA1DE.h +++ b/src/experimental_clfs/XA1DE.h @@ -29,6 +29,7 @@ namespace platform { 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; @@ -47,7 +48,9 @@ namespace platform { 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); } + 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 {}; @@ -64,9 +67,6 @@ namespace platform { } } } - template - std::vector to_vector(const torch::Tensor& y); - std::vector> to_matrix(const torch::Tensor& X); Xaode aode_; std::vector weights_; CountingSemaphore& semaphore_; diff --git a/src/experimental_clfs/XBAODE.cpp b/src/experimental_clfs/XBAODE.cpp index 56f95af..912faae 100644 --- a/src/experimental_clfs/XBAODE.cpp +++ b/src/experimental_clfs/XBAODE.cpp @@ -9,34 +9,23 @@ #include #include #include "XBAODE.h" +#include "TensorUtils.hpp" namespace platform { XBAODE::XBAODE() : semaphore_{ CountingSemaphore::getInstance() }, Boost(false) { } - std::vector XBAODE::initializeModels(const bayesnet::Smoothing_t smoothing) + void XBAODE::trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) { - torch::Tensor weights_ = torch::full({ m }, 1.0, torch::kFloat64); - std::vector featuresSelected = featureSelection(weights_); - for (const int& feature : featuresSelected) { - // std::unique_ptr model = std::make_unique(feature); - // model->fit(dataset, features, className, states, weights_, smoothing); - // models.push_back(std::move(model)); - significanceModels.push_back(1.0); // They will be updated later in trainModel - n_models++; - } - notes.push_back("Used features in initialization: " + std::to_string(featuresSelected.size()) + " of " + std::to_string(features.size()) + " with " + select_features_algorithm); - return featuresSelected; - } - - XBAODE& XBAODE::fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) - { - // aode_.fit(X, y, features, className, states, smoothing); fitted = true; + X_train_ = TensorUtils::to_matrix(X_train); + y_train_ = TensorUtils::to_vector(y_train); + X_test_ = TensorUtils::to_matrix(X_test); + y_test_ = TensorUtils::to_vector(y_test); // // Logging setup // - // loguru::set_thread_name("BoostAODE"); + // loguru::set_thread_name("XBAODE"); // loguru::g_stderr_verbosity = loguru::Verbosity_OFF; // loguru::add_file("boostAODE.log", loguru::Truncate, loguru::Verbosity_MAX); @@ -46,16 +35,22 @@ namespace platform { torch::Tensor weights_ = torch::full({ m }, 1.0, torch::kFloat64); bool finished = false; std::vector featuresUsed; + int num_instances = m; + int num_attributes = n; + significanceModels.resize(num_attributes, 0.0); + aode_.fit(X_train_, y_train_, features, className, states, smoothing); if (selectFeatures) { - featuresUsed = initializeModels(smoothing); - auto ypred = predict(X_train); + 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); std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred, weights_); // Update significance of the models - for (int i = 0; i < n_models; ++i) { - significanceModels[i] = alpha_t; + for (const auto& parent : featuresUsed) { + significanceModels[parent] = alpha_t; } if (finished) { - return *this; + return; } } int numItemsPack = 0; // The counter of the models inserted in the current pack @@ -87,37 +82,35 @@ namespace platform { while (counter++ < k && featureSelection.size() > 0) { auto feature = featureSelection[0]; featureSelection.erase(featureSelection.begin()); - std::unique_ptr model; - //model = std::make_unique(feature); - //model->fit(dataset, features, className, states, weights_, smoothing); + aode_.add_active_parent(feature); alpha_t = 0.0; if (!block_update) { - torch::Tensor ypred; + std::vector ypred; if (alpha_block) { // // Compute the prediction with the current ensemble + model // // Add the model to the ensemble n_models++; - //models.push_back(std::move(model)); - significanceModels.push_back(1); + significanceModels[feature] = 1.0; + aode_.add_active_parent(feature); // Compute the prediction - ypred = predict(X_train); + ypred = aode_.predict(X_train_); // Remove the model from the ensemble - //model = std::move(models.back()); - models.pop_back(); - significanceModels.pop_back(); + significanceModels[feature] = 0.0; + aode_.remove_last_parent(); n_models--; } else { - ypred = model->predict(X_train); + ypred = aode_.predict_spode(X_train_, feature); } // Step 3.1: Compute the classifier amout of say - std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred, weights_); + auto ypred_t = torch::tensor(ypred); + std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred_t, weights_); } // Step 3.4: Store classifier and its accuracy to weigh its future vote numItemsPack++; featuresUsed.push_back(feature); - //models.push_back(std::move(model)); + aode_.add_active_parent(feature); significanceModels.push_back(alpha_t); n_models++; // VLOG_SCOPE_F(2, "numItemsPack: %d n_models: %d featuresUsed: %zu", numItemsPack, n_models, featuresUsed.size()); @@ -171,7 +164,7 @@ namespace platform { status = bayesnet::WARNING; } notes.push_back("Number of models: " + std::to_string(n_models)); - return *this; + return; } // @@ -213,25 +206,6 @@ namespace platform { return aode_.getClassNumStates(); } - // - // Fit - // - // fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) - XBAODE& XBAODE::fit(torch::Tensor& X, torch::Tensor& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) - { - aode_.fit(X, y, features, className, states, smoothing); - return *this; - } - XBAODE& XBAODE::fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) - { - aode_.fit(dataset, features, className, states, smoothing); - return *this; - } - XBAODE& XBAODE::fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) - { - aode_.fit(dataset, features, className, states, weights, smoothing); - return *this; - } // // Predict // diff --git a/src/experimental_clfs/XBAODE.h b/src/experimental_clfs/XBAODE.h index 9cdf5ff..fdb0e7a 100644 --- a/src/experimental_clfs/XBAODE.h +++ b/src/experimental_clfs/XBAODE.h @@ -17,21 +17,14 @@ #include "XA1DE.h" namespace platform { - class XBAODE : public bayesnet::Boost { public: XBAODE(); virtual ~XBAODE() = default; const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted"; - - XBAODE& fit(std::vector>& X, std::vector& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) override; - XBAODE& fit(torch::Tensor& X, torch::Tensor& y, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) override; - XBAODE& fit(torch::Tensor& dataset, const std::vector& features, const std::string& className, std::map>& states, const bayesnet::Smoothing_t smoothing) override; - XBAODE& 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_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; @@ -50,10 +43,11 @@ namespace platform { 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 {}; - + void trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override; private: - std::vector initializeModels(const bayesnet::Smoothing_t smoothing); + std::vector> X_train_, X_test_; + std::vector y_train_, y_test_; + torch::Tensor dataset; XA1DE aode_; int n_models; std::vector weights_; diff --git a/src/experimental_clfs/Xaode.hpp b/src/experimental_clfs/Xaode.hpp index 93a8d2d..7190ece 100644 --- a/src/experimental_clfs/Xaode.hpp +++ b/src/experimental_clfs/Xaode.hpp @@ -49,6 +49,9 @@ namespace platform { // void init(const std::vector& states) { + // + // Check Valid input data + // if (matrixState_ != MatrixState::EMPTY) { throw std::logic_error("Xaode: already initialized."); } @@ -61,6 +64,10 @@ namespace platform { if (statesClass_ <= 0) { throw std::invalid_argument("Xaode: class states must be > 0."); } + // + // Initialize data structures + // + active_parents.resize(nFeatures_); int totalStates = std::accumulate(states.begin(), states.end(), 0) - statesClass_; // For p(x_i=si | c), we store them in a 1D array classFeatureProbs_ after we compute. @@ -322,6 +329,11 @@ namespace platform { normalize(scores); return scores; } + int predict_spode(const std::vector& instance, int parent) const + { + auto probs = predict_proba_spode(instance, parent); + return (int)std::distance(probs.begin(), std::max_element(probs.begin(), probs.end())); + } std::vector predict_proba(std::vector& instance) { Timer timer; @@ -346,6 +358,10 @@ namespace platform { duration_first += timer.getDuration(); timer.start(); int idx, base, sp, sc, parent_offset; for (int parent = 1; parent < nFeatures_; ++parent) { + // if parent is not in the active_parents, skip it + if (std::find(active_parents.begin(), active_parents.end(), parent) == active_parents.end()) { + continue; + } sp = instance[parent]; parent_offset = pairOffset_[featureClassOffset_[parent] + sp]; for (int child = 0; child < parent; ++child) { @@ -552,9 +568,13 @@ namespace platform { { return (nFeatures_ + 1) * nFeatures_; } - void set_active_parents(std::vector active_parents) + void set_active_parent(int active_parent) { - this->active_parents = active_parents; + active_parents.push_back(active_parent); + } + void remove_last_parent() + { + active_parents.pop_back(); }