Add XBAODE & XSPODE from bayesnet

This commit is contained in:
2025-03-09 19:20:51 +01:00
parent ae7b89b134
commit 664a6a5aeb
8 changed files with 302 additions and 49 deletions

View File

@@ -27,7 +27,6 @@ add_executable(
reports/ReportExcel.cpp reports/ReportBase.cpp reports/ExcelFile.cpp
results/Result.cpp
experimental_clfs/XA1DE.cpp
experimental_clfs/XBAODE.cpp
experimental_clfs/ExpClf.cpp
)
target_link_libraries(b_best Boost::boost "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy "${XLSXWRITER_LIB}")
@@ -41,7 +40,6 @@ add_executable(b_grid commands/b_grid.cpp ${grid_sources}
reports/ReportConsole.cpp reports/ReportBase.cpp
results/Result.cpp
experimental_clfs/XA1DE.cpp
experimental_clfs/XBAODE.cpp
experimental_clfs/ExpClf.cpp
)
target_link_libraries(b_grid ${MPI_CXX_LIBRARIES} "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy)
@@ -53,7 +51,6 @@ add_executable(b_list commands/b_list.cpp
reports/ReportExcel.cpp reports/ExcelFile.cpp reports/ReportBase.cpp reports/DatasetsExcel.cpp reports/DatasetsConsole.cpp reports/ReportsPaged.cpp
results/Result.cpp results/ResultsDatasetExcel.cpp results/ResultsDataset.cpp results/ResultsDatasetConsole.cpp
experimental_clfs/XA1DE.cpp
experimental_clfs/XBAODE.cpp
experimental_clfs/ExpClf.cpp
)
target_link_libraries(b_list "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy "${XLSXWRITER_LIB}")
@@ -66,7 +63,6 @@ add_executable(b_main commands/b_main.cpp ${main_sources}
reports/ReportConsole.cpp reports/ReportBase.cpp
results/Result.cpp
experimental_clfs/XA1DE.cpp
experimental_clfs/XBAODE.cpp
experimental_clfs/ExpClf.cpp
)
target_link_libraries(b_main "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy)

View File

@@ -0,0 +1,158 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "ExpEnsemble.h"
#include "TensorUtils.hpp"
namespace platform {
ExpEnsemble::ExpEnsemble() : semaphore_{ CountingSemaphore::getInstance() }, Boost(false)
{
validHyperparameters = {};
}
//
// Parents
//
void ExpEnsemble::add_model(std::unique_ptr<XSpode> model)
{
models.push_back(std::move(model));
n_models++;
}
void ExpEnsemble::remove_last_model()
{
models.pop_back();
n_models--;
}
//
// Predict
//
torch::Tensor ExpEnsemble::predict(torch::Tensor& X)
{
auto X_ = TensorUtils::to_matrix(X);
torch::Tensor y = torch::tensor(predict(X_));
return y;
}
torch::Tensor ExpEnsemble::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 ExpEnsemble::score(torch::Tensor& X, torch::Tensor& y)
{
auto X_ = TensorUtils::to_matrix(X);
auto y_ = TensorUtils::to_vector<int>(y);
return score(X_, y_);
}
std::vector<std::vector<double>> ExpEnsemble::predict_proba(const std::vector<std::vector<int>>& test_data)
{
int test_size = test_data[0].size();
int sample_size = test_data.size();
auto probabilities = std::vector<std::vector<double>>(test_size, std::vector<double>(getClassNumStates()));
int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1);
std::vector<std::thread> threads;
auto worker = [&](const std::vector<std::vector<int>>& samples, int begin, int chunk, int sample_size, std::vector<std::vector<double>>& 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<int> 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<int> ExpEnsemble::predict(std::vector<std::vector<int>>& test_data)
{
if (!fitted) {
throw std::logic_error(CLASSIFIER_NOT_FITTED);
}
auto probabilities = predict_proba(test_data);
std::vector<int> 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 ExpEnsemble::score(std::vector<std::vector<int>>& test_data, std::vector<int>& labels)
{
Timer timer;
timer.start();
std::vector<int> 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<float>(correct) / predictions.size();
}
//
// statistics
//
int ExpEnsemble::getNumberOfNodes() const
{
if (models_.empty()) {
return 0;
}
return n_models * (models_.at(0)->getNFeatures() + 1);
}
int ExpEnsemble::getNumberOfEdges() const
{
if (models_.empty()) {
return 0;
}
return n_models * (2 * models_.at(0)->getNFeatures() - 1);
}
int ExpEnsemble::getNumberOfStates() const
{
if (models_.empty()) {
return 0;
}
auto states = models_.at(0)->getStates();
int nFeatures = models_.at(0)->getNFeatures();
return std::accumulate(states.begin(), states.end(), 0) * nFeatures * n_models;
}
int ExpEnsemble::getClassNumStates() const
{
if (models_.empty()) {
return 0;
}
return models_.at(0)->statesClass();
}
}

View File

@@ -0,0 +1,66 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef EXPENSEMBLE_H
#define EXPENSEMBLE_H
#include <vector>
#include <string>
#include <cmath>
#include <algorithm>
#include <limits>
#include <bayesnet/ensembles/Boost.h>
#include <bayesnet/network/Smoothing.h>
#include "common/Timer.hpp"
#include "CountingSemaphore.hpp"
#include "XSpode.hpp"
namespace platform {
class ExpEnsemble : public bayesnet::Boost {
public:
ExpEnsemble();
virtual ~ExpEnsemble() = default;
std::vector<int> predict(std::vector<std::vector<int>>& X) override;
torch::Tensor predict(torch::Tensor& X) override;
torch::Tensor predict_proba(torch::Tensor& X) override;
std::vector<int> predict_spode(std::vector<std::vector<int>>& test_data, int parent);
std::vector<std::vector<double>> predict_proba(const std::vector<std::vector<int>>& X);
float score(std::vector<std::vector<int>>& X, std::vector<int>& 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<std::string> show() const override { return {}; }
std::vector<std::string> 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<std::string> getNotes() const override { return notes; }
std::vector<std::string> graph(const std::string& title = "") const override { return {}; }
protected:
void add_model(std::unique_ptr<XSpode> model);
void remove_last_model();
bool debug = false;
std::vector <std::unique_ptr<XSpode>> models_;
torch::Tensor weights_;
std::vector<double> significanceModels_;
const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted";
inline void normalize_weights(int num_instances)
{
double sum = weights_.sum().item<double>();
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<double>() * num_instances / sum;
}
}
}
private:
CountingSemaphore& semaphore_;
};
}
#endif // EXPENSEMBLE_H

View File

@@ -19,6 +19,16 @@ namespace platform {
validHyperparameters = { "alpha_block", "order", "convergence", "convergence_best", "bisection", "threshold", "maxTolerance",
"predict_voting", "select_features" };
}
void XBAODE::add_model(std::unique_ptr<XSpode> model)
{
models.push_back(std::move(model));
n_models++;
}
void XBAODE::remove_last_model()
{
models.pop_back();
n_models--;
}
void XBAODE::trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing)
{
fitted = true;
@@ -30,30 +40,36 @@ namespace platform {
//
// Logging setup
//
loguru::set_thread_name("XBAODE");
loguru::g_stderr_verbosity = loguru::Verbosity_OFF;
loguru::add_file("XBAODE.log", loguru::Truncate, loguru::Verbosity_MAX);
// loguru::set_thread_name("XBAODE");
// loguru::g_stderr_verbosity = loguru::Verbosity_OFF;
// loguru::add_file("XBAODE.log", loguru::Truncate, loguru::Verbosity_MAX);
// Algorithm based on the adaboost algorithm for classification
// as explained in Ensemble methods (Zhi-Hua Zhou, 2012)
double alpha_t = 0;
weights_ = torch::full({ m }, 1.0 / static_cast<double>(m), torch::kFloat64);
weights_ = torch::full({ m }, 1.0 / static_cast<double>(m), torch::kFloat64); // m initialized in Classifier.cc
significanceModels.resize(n, 0.0); // n initialized in Classifier.cc
bool finished = false;
std::vector<int> featuresUsed;
aode_.fit(X_train_, y_train_, features, className, states, weights_, false);
n_models = 0;
std::unique_ptr<XSpode> model;
if (selectFeatures) {
featuresUsed = featureSelection(weights_);
add_active_parents(featuresUsed);
for (const auto& parent : featuresUsed) {
model = std::unique_ptr<XSpode>(new XSpode(parent));
model->fit(X_train_, y_train_, weights_, smoothing);
std::cout << model->getNFeatures() << std::endl;
add_model(std::move(model));
}
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);
auto ypred = ExpEnsemble::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) {
aode_.significance_models_[parent] = alpha_t;
significanceModels_[parent] = alpha_t;
}
n_models = featuresUsed.size();
VLOG_SCOPE_F(1, "SelectFeatures. alpha_t: %f n_models: %d", alpha_t, n_models);
// VLOG_SCOPE_F(1, "SelectFeatures. alpha_t: %f n_models: %d", alpha_t, n_models);
if (finished) {
return;
}
@@ -83,29 +99,28 @@ namespace platform {
);
int k = bisection ? pow(2, tolerance) : 1;
int counter = 0; // The model counter of the current pack
VLOG_SCOPE_F(1, "counter=%d k=%d featureSelection.size: %zu", counter, k, featureSelection.size());
// VLOG_SCOPE_F(1, "counter=%d k=%d featureSelection.size: %zu", counter, k, featureSelection.size());
while (counter++ < k && featureSelection.size() > 0) {
auto feature = featureSelection[0];
featureSelection.erase(featureSelection.begin());
auto model = XSpode(feature);
model.fit(X_train_, y_train_, weights_, smoothing);
model = std::unique_ptr<XSpode>(new XSpode(feature));
model->fit(X_train_, y_train_, weights_, smoothing);
std::vector<int> ypred;
if (alpha_block) {
//
// Compute the prediction with the current ensemble + model
//
// Add the model to the ensemble
n_models++;
aode_.significance_models_[feature] = 1.0;
aode_.add_active_parent(feature);
significanceModels[feature] = 1.0;
add_model(std::move(model));
// Compute the prediction
ypred = ExpClf::predict(X_train_);
ypred = ExpEnsemble::predict(X_train_);
// Remove the model from the ensemble
aode_.significance_models_[feature] = 0.0;
aode_.remove_last_parent();
n_models--;
significanceModels[feature] = 0.0;
model = std::move(models_.back());
remove_last_model();
} else {
ypred = model.predict(X_train_);
ypred = model->predict(X_train_);
}
// Step 3.1: Compute the classifier amout of say
auto ypred_t = torch::tensor(ypred);
@@ -113,13 +128,12 @@ namespace platform {
// Step 3.4: Store classifier and its accuracy to weigh its future vote
numItemsPack++;
featuresUsed.push_back(feature);
aode_.add_active_parent(feature);
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());
add_model(std::move(model));
significanceModels[feature] = alpha_t;
// 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 = ExpClf::predict(X_test);
auto y_val_predict = ExpEnsemble::predict(X_test);
double accuracy = (y_val_predict == y_test).sum().item<double>() / (double)y_test.size(0);
if (priorAccuracy == 0) {
priorAccuracy = accuracy;
@@ -127,10 +141,10 @@ namespace platform {
improvement = accuracy - priorAccuracy;
}
if (improvement < convergence_threshold) {
VLOG_SCOPE_F(3, " (improvement<threshold) tolerance: %d numItemsPack: %d improvement: %f prior: %f current: %f", tolerance, numItemsPack, improvement, priorAccuracy, accuracy);
// VLOG_SCOPE_F(3, " (improvement<threshold) tolerance: %d numItemsPack: %d improvement: %f prior: %f current: %f", tolerance, numItemsPack, improvement, priorAccuracy, accuracy);
tolerance++;
} else {
VLOG_SCOPE_F(3, "* (improvement>=threshold) Reset. tolerance: %d numItemsPack: %d improvement: %f prior: %f current: %f", tolerance, numItemsPack, improvement, priorAccuracy, accuracy);
// VLOG_SCOPE_F(3, "* (improvement>=threshold) Reset. tolerance: %d numItemsPack: %d improvement: %f prior: %f current: %f", tolerance, numItemsPack, improvement, priorAccuracy, accuracy);
tolerance = 0; // Reset the counter if the model performs better
numItemsPack = 0;
}
@@ -142,22 +156,21 @@ namespace platform {
priorAccuracy = accuracy;
}
}
VLOG_SCOPE_F(1, "tolerance: %d featuresUsed.size: %zu features.size: %zu", tolerance, featuresUsed.size(), features.size());
// VLOG_SCOPE_F(1, "tolerance: %d featuresUsed.size: %zu features.size: %zu", tolerance, featuresUsed.size(), features.size());
finished = finished || tolerance > maxTolerance || featuresUsed.size() == features.size();
}
if (tolerance > maxTolerance) {
if (numItemsPack < n_models) {
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);
// VLOG_SCOPE_F(4, "Convergence threshold reached & %d models eliminated of %d", numItemsPack, n_models);
for (int i = featuresUsed.size() - 1; i >= featuresUsed.size() - numItemsPack; --i) {
aode_.remove_last_parent();
aode_.significance_models_[featuresUsed[i]] = 0.0;
n_models--;
remove_last_model();
significanceModels[featuresUsed[i]] = 0.0;
}
VLOG_SCOPE_F(4, "*Convergence threshold %d models left & %d features used.", n_models, featuresUsed.size());
// 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");
VLOG_SCOPE_F(4, "Convergence threshold reached & 0 models eliminated n_models=%d numItemsPack=%d", n_models, numItemsPack);
// VLOG_SCOPE_F(4, "Convergence threshold reached & 0 models eliminated n_models=%d numItemsPack=%d", n_models, numItemsPack);
}
}
if (featuresUsed.size() != features.size()) {

View File

@@ -12,10 +12,10 @@
#include <algorithm>
#include <limits>
#include "common/Timer.hpp"
#include "ExpClf.h"
#include "ExpEnsemble.h"
namespace platform {
class XBAODE {
class XBAODE : public Boost {
// Hay que hacer un vector de modelos entrenados y hacer un predict ensemble con todos ellos
// Probar XA1DE con smooth original y laplace y comprobar diferencias si se pasan pesos a 1 o a 1/m
@@ -25,10 +25,10 @@ namespace platform {
protected:
void trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override;
private:
void add_model(std::unique_ptr<XSpode> model);
void remove_last_model();
std::vector<std::vector<int>> X_train_, X_test_;
std::vector<int> y_train_, y_test_;
torch::Tensor dataset;
int n_models;
std::string version = "0.9.7";
};
}

View File

@@ -11,24 +11,29 @@
#include <limits>
#include <sstream>
#include <iostream>
#include <torch/torch.h>
#include <bayesnet/network/Smoothing.h>
#include <bayesnet/classifiers/Classifier.h>
#include "CountingSemaphore.hpp"
namespace platform {
class XSpode {
class XSpode : public bayesnet::Classifier {
public:
// --------------------------------------
// Constructor
//
// Supply which feature index is the single super-parent (“spIndex”).
// --------------------------------------
XSpode(int spIndex)
explicit XSpode(int spIndex)
: superParent_{ spIndex },
nFeatures_{ 0 },
statesClass_{ 0 },
fitted_{ false },
alpha_{ 1.0 },
semaphore_{ CountingSemaphore::getInstance() }
initializer_{ 1.0 },
semaphore_{ CountingSemaphore::getInstance() } : bayesnet::Classifier(bayesnet::Network())
{
}
@@ -380,6 +385,17 @@ namespace platform {
oss << "---------------------\n";
return oss.str();
}
int statesClass() const { return statesClass_; }
int getNFeatures() const { return nFeatures_; }
int getNumberOfStates() const
{
return std::accumulate(states_.begin(), states_.end(), 0) * nFeatures_;
}
int getNumberOfEdges() const
{
return nFeatures_ * (2 * nFeatures_ - 1);
}
std::vector<int>& getStates() { return states_; }
private:
// --------------------------------------

View File

@@ -5,11 +5,13 @@
#include <bayesnet/ensembles/AODE.h>
#include <bayesnet/ensembles/A2DE.h>
#include <bayesnet/ensembles/AODELd.h>
#include <bayesnet/ensembles/XBAODE.h>
#include <bayesnet/ensembles/BoostAODE.h>
#include <bayesnet/ensembles/BoostA2DE.h>
#include <bayesnet/classifiers/TAN.h>
#include <bayesnet/classifiers/KDB.h>
#include <bayesnet/classifiers/SPODE.h>
#include <bayesnet/classifiers/XSPODE.h>
#include <bayesnet/classifiers/SPnDE.h>
#include <bayesnet/classifiers/TANLd.h>
#include <bayesnet/classifiers/KDBLd.h>
@@ -21,7 +23,7 @@
#include <pyclassifiers/XGBoost.h>
#include <pyclassifiers/RandomForest.h>
#include "../experimental_clfs/XA1DE.h"
#include "../experimental_clfs/XBAODE.h"
namespace platform {
class Models {
public:

View File

@@ -35,9 +35,11 @@ namespace platform {
[](void) -> bayesnet::BaseClassifier* { return new pywrap::RandomForest();});
static Registrar registrarXGB("XGBoost",
[](void) -> bayesnet::BaseClassifier* { return new pywrap::XGBoost();});
static Registrar registrarXA1DE("XA1DE",
[](void) -> bayesnet::BaseClassifier* { return new XA1DE();});
static Registrar registrarXSPODE("XSPODE",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::XSpode(0);});
static Registrar registrarXBAODE("XBAODE",
[](void) -> bayesnet::BaseClassifier* { return new XBAODE();});
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::XBAODE();});
static Registrar registrarXA1DE("XA1DE",
[](void) -> bayesnet::BaseClassifier* { return new XA1DE();});
}
#endif