diff --git a/CHANGELOG.md b/CHANGELOG.md index 95666a1..b7ea351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.7] 2025-03-16 + + ### Added -- Add a new hyperparameter to the BoostAODE class, *alphablock*, to control the way α is computed, with the last model or with the ensmble built so far. Default value is *false*. -- Add a new hyperparameter to the SPODE class, *parent*, to set the root node of the model. If no value is set the root parameter of the constructor is used. -- Add a new hyperparameter to the TAN class, *parent*, to set the root node of the model. If not set the first feature is used as root. +- A new hyperparameter to the BoostAODE class, *alphablock*, to control the way α is computed, with the last model or with the ensmble built so far. Default value is *false*. +- A new hyperparameter to the SPODE class, *parent*, to set the root node of the model. If no value is set the root parameter of the constructor is used. +- A new hyperparameter to the TAN class, *parent*, to set the root node of the model. If not set the first feature is used as root. +- A new model named XSPODE, an optimized for speed averaged one dependence estimator. +- A new model named XSP2DE, an optimized for speed averaged two dependence estimator. +- A new model named XBAODE, an optimized for speed BoostAODE model. +- A new model named XBA2DE, an optimized for speed BoostA2DE model. ### Internal - Optimize ComputeCPT method in the Node class. +- Add methods getCount and getMaxCount to the CountingSemaphore class, returning the current count and the maximum count of threads respectively. ### Changed diff --git a/CMakeLists.txt b/CMakeLists.txt index 396c590..359c806 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.20) project(BayesNet - VERSION 1.0.6 + VERSION 1.0.7 DESCRIPTION "Bayesian Network and basic classifiers Library." HOMEPAGE_URL "https://github.com/rmontanana/bayesnet" LANGUAGES CXX diff --git a/bayesnet/classifiers/XSPnDE.cc b/bayesnet/classifiers/XSP2DE.cc similarity index 93% rename from bayesnet/classifiers/XSPnDE.cc rename to bayesnet/classifiers/XSP2DE.cc index 4eedc55..8a5be7b 100644 --- a/bayesnet/classifiers/XSPnDE.cc +++ b/bayesnet/classifiers/XSP2DE.cc @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT // *************************************************************** -#include "XSPnDE.h" +#include "XSP2DE.h" #include // for pthread_setname_np on linux #include #include @@ -18,7 +18,7 @@ namespace bayesnet { // -------------------------------------- // Constructor // -------------------------------------- -XSpnde::XSpnde(int spIndex1, int spIndex2) +XSp2de::XSp2de(int spIndex1, int spIndex2) : superParent1_{ spIndex1 } , superParent2_{ spIndex2 } , nFeatures_{0} @@ -34,7 +34,7 @@ XSpnde::XSpnde(int spIndex1, int spIndex2) // -------------------------------------- // setHyperparameters // -------------------------------------- -void XSpnde::setHyperparameters(const nlohmann::json &hyperparameters_) +void XSp2de::setHyperparameters(const nlohmann::json &hyperparameters_) { auto hyperparameters = hyperparameters_; if (hyperparameters.contains("parent1")) { @@ -52,7 +52,7 @@ void XSpnde::setHyperparameters(const nlohmann::json &hyperparameters_) // -------------------------------------- // fitx // -------------------------------------- -void XSpnde::fitx(torch::Tensor & X, torch::Tensor & y, +void XSp2de::fitx(torch::Tensor & X, torch::Tensor & y, torch::Tensor & weights_, const Smoothing_t smoothing) { m = X.size(1); // number of samples @@ -73,7 +73,7 @@ void XSpnde::fitx(torch::Tensor & X, torch::Tensor & y, // -------------------------------------- // buildModel // -------------------------------------- -void XSpnde::buildModel(const torch::Tensor &weights) +void XSp2de::buildModel(const torch::Tensor &weights) { nFeatures_ = n; @@ -122,7 +122,7 @@ void XSpnde::buildModel(const torch::Tensor &weights) // -------------------------------------- // trainModel // -------------------------------------- -void XSpnde::trainModel(const torch::Tensor &weights, +void XSp2de::trainModel(const torch::Tensor &weights, const bayesnet::Smoothing_t smoothing) { // Accumulate raw counts @@ -158,7 +158,7 @@ void XSpnde::trainModel(const torch::Tensor &weights, // -------------------------------------- // addSample // -------------------------------------- -void XSpnde::addSample(const std::vector &instance, double weight) +void XSp2de::addSample(const std::vector &instance, double weight) { if (weight <= 0.0) return; @@ -205,7 +205,7 @@ void XSpnde::addSample(const std::vector &instance, double weight) // -------------------------------------- // computeProbabilities // -------------------------------------- -void XSpnde::computeProbabilities() +void XSp2de::computeProbabilities() { double totalCount = std::accumulate(classCounts_.begin(), classCounts_.end(), 0.0); @@ -305,7 +305,7 @@ void XSpnde::computeProbabilities() // -------------------------------------- // predict_proba (single instance) // -------------------------------------- -std::vector XSpnde::predict_proba(const std::vector &instance) const +std::vector XSp2de::predict_proba(const std::vector &instance) const { if (!fitted) { throw std::logic_error(CLASSIFIER_NOT_FITTED); @@ -355,7 +355,7 @@ std::vector XSpnde::predict_proba(const std::vector &instance) cons // -------------------------------------- // predict_proba (batch) // -------------------------------------- -std::vector> XSpnde::predict_proba(std::vector> &test_data) +std::vector> XSp2de::predict_proba(std::vector> &test_data) { int test_size = test_data[0].size(); // each feature is test_data[f], size = #samples int sample_size = test_data.size(); // = nFeatures_ @@ -372,7 +372,7 @@ std::vector> XSpnde::predict_proba(std::vector> &predictions) { std::string threadName = - "XSpnde-" + std::to_string(begin) + "-" + std::to_string(chunk); + "XSp2de-" + std::to_string(begin) + "-" + std::to_string(chunk); #if defined(__linux__) pthread_setname_np(pthread_self(), threadName.c_str()); #else @@ -404,7 +404,7 @@ std::vector> XSpnde::predict_proba(std::vector &instance) const +int XSp2de::predict(const std::vector &instance) const { auto p = predict_proba(instance); return static_cast( @@ -415,7 +415,7 @@ int XSpnde::predict(const std::vector &instance) const // -------------------------------------- // predict (batch of data) // -------------------------------------- -std::vector XSpnde::predict(std::vector> &test_data) +std::vector XSp2de::predict(std::vector> &test_data) { auto probabilities = predict_proba(test_data); std::vector predictions(probabilities.size(), 0); @@ -433,7 +433,7 @@ std::vector XSpnde::predict(std::vector> &test_data) // -------------------------------------- // predict (torch::Tensor version) // -------------------------------------- -torch::Tensor XSpnde::predict(torch::Tensor &X) +torch::Tensor XSp2de::predict(torch::Tensor &X) { auto X_ = TensorUtils::to_matrix(X); auto result_v = predict(X_); @@ -443,7 +443,7 @@ torch::Tensor XSpnde::predict(torch::Tensor &X) // -------------------------------------- // predict_proba (torch::Tensor version) // -------------------------------------- -torch::Tensor XSpnde::predict_proba(torch::Tensor &X) +torch::Tensor XSp2de::predict_proba(torch::Tensor &X) { auto X_ = TensorUtils::to_matrix(X); auto result_v = predict_proba(X_); @@ -459,7 +459,7 @@ torch::Tensor XSpnde::predict_proba(torch::Tensor &X) // -------------------------------------- // score (torch::Tensor version) // -------------------------------------- -float XSpnde::score(torch::Tensor &X, torch::Tensor &y) +float XSp2de::score(torch::Tensor &X, torch::Tensor &y) { torch::Tensor y_pred = predict(X); return (y_pred == y).sum().item() / y.size(0); @@ -468,7 +468,7 @@ float XSpnde::score(torch::Tensor &X, torch::Tensor &y) // -------------------------------------- // score (vector version) // -------------------------------------- -float XSpnde::score(std::vector> &X, std::vector &y) +float XSp2de::score(std::vector> &X, std::vector &y) { auto y_pred = predict(X); int correct = 0; @@ -483,7 +483,7 @@ float XSpnde::score(std::vector> &X, std::vector &y) // -------------------------------------- // Utility: normalize // -------------------------------------- -void XSpnde::normalize(std::vector &v) const +void XSp2de::normalize(std::vector &v) const { double sum = 0.0; for (auto &val : v) { @@ -499,10 +499,10 @@ void XSpnde::normalize(std::vector &v) const // -------------------------------------- // to_string // -------------------------------------- -std::string XSpnde::to_string() const +std::string XSp2de::to_string() const { std::ostringstream oss; - oss << "----- XSpnde Model -----\n" + oss << "----- XSp2de Model -----\n" << "nFeatures_ = " << nFeatures_ << "\n" << "superParent1_ = " << superParent1_ << "\n" << "superParent2_ = " << superParent2_ << "\n" @@ -533,30 +533,30 @@ std::string XSpnde::to_string() const // -------------------------------------- // Some introspection about the graph // -------------------------------------- -int XSpnde::getNumberOfNodes() const +int XSp2de::getNumberOfNodes() const { // nFeatures + 1 class node return nFeatures_ + 1; } -int XSpnde::getClassNumStates() const +int XSp2de::getClassNumStates() const { return statesClass_; } -int XSpnde::getNFeatures() const +int XSp2de::getNFeatures() const { return nFeatures_; } -int XSpnde::getNumberOfStates() const +int XSp2de::getNumberOfStates() const { // purely an example. Possibly you want to sum up actual // cardinalities or something else. return std::accumulate(states_.begin(), states_.end(), 0) * nFeatures_; } -int XSpnde::getNumberOfEdges() const +int XSp2de::getNumberOfEdges() const { // In an SPNDE with n=2, for each feature we have edges from class, sp1, sp2. // So that’s 3*(nFeatures_) edges, minus the ones for the superparents themselves, diff --git a/bayesnet/classifiers/XSPnDE.h b/bayesnet/classifiers/XSP2DE.h similarity index 95% rename from bayesnet/classifiers/XSPnDE.h rename to bayesnet/classifiers/XSP2DE.h index 0e9c9b9..1436d9e 100644 --- a/bayesnet/classifiers/XSPnDE.h +++ b/bayesnet/classifiers/XSP2DE.h @@ -4,8 +4,8 @@ // SPDX-License-Identifier: MIT // *************************************************************** -#ifndef XSPNDE_H -#define XSPNDE_H +#ifndef XSP2DE_H +#define XSP2DE_H #include "Classifier.h" #include "bayesnet/utils/CountingSemaphore.h" @@ -14,9 +14,9 @@ namespace bayesnet { -class XSpnde : public Classifier { +class XSp2de : public Classifier { public: - XSpnde(int spIndex1, int spIndex2); + XSp2de(int spIndex1, int spIndex2); void setHyperparameters(const nlohmann::json &hyperparameters_) override; void fitx(torch::Tensor &X, torch::Tensor &y, torch::Tensor &weights_, const Smoothing_t smoothing); std::vector predict_proba(const std::vector &instance) const; @@ -72,4 +72,4 @@ class XSpnde : public Classifier { }; } // namespace bayesnet -#endif // XSPNDE_H +#endif // XSP2DE_H diff --git a/bayesnet/ensembles/XBA2DE.cc b/bayesnet/ensembles/XBA2DE.cc index 856af4b..98f8dcc 100644 --- a/bayesnet/ensembles/XBA2DE.cc +++ b/bayesnet/ensembles/XBA2DE.cc @@ -7,7 +7,7 @@ #include #include #include "XBA2DE.h" -#include "bayesnet/classifiers/XSPnDE.h" +#include "bayesnet/classifiers/XSP2DE.h" #include "bayesnet/utils/TensorUtils.h" namespace bayesnet { @@ -23,7 +23,7 @@ std::vector XBA2DE::initializeModels(const Smoothing_t smoothing) { } for (int i = 0; i < featuresSelected.size() - 1; i++) { for (int j = i + 1; j < featuresSelected.size(); j++) { - std::unique_ptr model = std::make_unique(featuresSelected[i], featuresSelected[j]); + std::unique_ptr model = std::make_unique(featuresSelected[i], featuresSelected[j]); model->fit(dataset, features, className, states, weights_, smoothing); add_model(std::move(model), 1.0); } @@ -94,7 +94,7 @@ void XBA2DE::trainModel(const torch::Tensor &weights, const Smoothing_t smoothin auto feature_pair = pairSelection[0]; pairSelection.erase(pairSelection.begin()); std::unique_ptr model; - model = std::make_unique(feature_pair.first, feature_pair.second); + model = std::make_unique(feature_pair.first, feature_pair.second); model->fit(dataset, features, className, states, weights_, smoothing); alpha_t = 0.0; if (!block_update) { diff --git a/tests/TestBayesModels.cc b/tests/TestBayesModels.cc index 34ee728..67c52e3 100644 --- a/tests/TestBayesModels.cc +++ b/tests/TestBayesModels.cc @@ -20,7 +20,7 @@ #include "bayesnet/ensembles/AODELd.h" #include "bayesnet/ensembles/BoostAODE.h" -const std::string ACTUAL_VERSION = "1.0.6"; +const std::string ACTUAL_VERSION = "1.0.7"; TEST_CASE("Test Bayesian Classifiers score & version", "[Models]") { diff --git a/tests/TestXSPnDE.cc b/tests/TestXSPnDE.cc index 4b6afd5..cb03341 100644 --- a/tests/TestXSPnDE.cc +++ b/tests/TestXSPnDE.cc @@ -7,7 +7,7 @@ #include #include #include -#include "bayesnet/classifiers/XSPnDE.h" // <-- your new 2-superparent classifier +#include "bayesnet/classifiers/XSP2DE.h" // <-- your new 2-superparent classifier #include "TestUtils.h" // for RawDatasets, etc. // Helper function to handle each (sp1, sp2) pair in tests @@ -19,7 +19,7 @@ static void check_spnde_pair( bool fitTensor) { // Create our classifier - bayesnet::XSpnde clf(sp1, sp2); + bayesnet::XSp2de clf(sp1, sp2); // Option A: fit with vector-based data if (fitVector) { @@ -48,7 +48,7 @@ static void check_spnde_pair( // ------------------------------------------------------------ // 1) Fit vector test // ------------------------------------------------------------ -TEST_CASE("fit vector test (XSPNDE)", "[XSPNDE]") { +TEST_CASE("fit vector test (XSP2DE)", "[XSP2DE]") { auto raw = RawDatasets("iris", true); std::vector> parentPairs = { @@ -62,7 +62,7 @@ TEST_CASE("fit vector test (XSPNDE)", "[XSPNDE]") { // ------------------------------------------------------------ // 2) Fit dataset test // ------------------------------------------------------------ -TEST_CASE("fit dataset test (XSPNDE)", "[XSPNDE]") { +TEST_CASE("fit dataset test (XSP2DE)", "[XSP2DE]") { auto raw = RawDatasets("iris", true); // Again test multiple pairs: @@ -77,7 +77,7 @@ TEST_CASE("fit dataset test (XSPNDE)", "[XSPNDE]") { // ------------------------------------------------------------ // 3) Tensors dataset predict & predict_proba // ------------------------------------------------------------ -TEST_CASE("tensors dataset predict & predict_proba (XSPNDE)", "[XSPNDE]") { +TEST_CASE("tensors dataset predict & predict_proba (XSP2DE)", "[XSP2DE]") { auto raw = RawDatasets("iris", true); std::vector> parentPairs = { @@ -85,7 +85,7 @@ TEST_CASE("tensors dataset predict & predict_proba (XSPNDE)", "[XSPNDE]") { }; for (auto &p : parentPairs) { - bayesnet::XSpnde clf(p.first, p.second); + bayesnet::XSp2de clf(p.first, p.second); clf.fit(raw.Xt, raw.yt, raw.features, raw.className, raw.states, raw.smoothing); REQUIRE(clf.getNumberOfNodes() == 5); @@ -100,26 +100,26 @@ TEST_CASE("tensors dataset predict & predict_proba (XSPNDE)", "[XSPNDE]") { auto proba = clf.predict_proba(X_reduced); } } -TEST_CASE("Check hyperparameters", "[XSPNDE]") +TEST_CASE("Check hyperparameters", "[XSP2DE]") { auto raw = RawDatasets("iris", true); - auto clf = bayesnet::XSpnde(0, 1); + auto clf = bayesnet::XSp2de(0, 1); clf.fit(raw.Xv, raw.yv, raw.features, raw.className, raw.states, raw.smoothing); - auto clf2 = bayesnet::XSpnde(2, 3); + auto clf2 = bayesnet::XSp2de(2, 3); clf2.setHyperparameters({{"parent1", 0}, {"parent2", 1}}); clf2.fit(raw.Xv, raw.yv, raw.features, raw.className, raw.states, raw.smoothing); REQUIRE(clf.to_string() == clf2.to_string()); } -TEST_CASE("Check different smoothing", "[XSPNDE]") +TEST_CASE("Check different smoothing", "[XSP2DE]") { auto raw = RawDatasets("iris", true); - auto clf = bayesnet::XSpnde(0, 1); + auto clf = bayesnet::XSp2de(0, 1); clf.fit(raw.Xv, raw.yv, raw.features, raw.className, raw.states, bayesnet::Smoothing_t::ORIGINAL); - auto clf2 = bayesnet::XSpnde(0, 1); + auto clf2 = bayesnet::XSp2de(0, 1); clf2.fit(raw.Xv, raw.yv, raw.features, raw.className, raw.states, bayesnet::Smoothing_t::LAPLACE); - auto clf3 = bayesnet::XSpnde(0, 1); + auto clf3 = bayesnet::XSp2de(0, 1); clf3.fit(raw.Xv, raw.yv, raw.features, raw.className, raw.states, bayesnet::Smoothing_t::NONE); auto score = clf.score(raw.X_test, raw.y_test); auto score2 = clf2.score(raw.X_test, raw.y_test); @@ -128,10 +128,10 @@ TEST_CASE("Check different smoothing", "[XSPNDE]") REQUIRE(score2 == Catch::Approx(0.7333333).epsilon(raw.epsilon)); REQUIRE(score3 == Catch::Approx(0.966667).epsilon(raw.epsilon)); } -TEST_CASE("Check rest", "[XSPNDE]") +TEST_CASE("Check rest", "[XSP2DE]") { auto raw = RawDatasets("iris", true); - auto clf = bayesnet::XSpnde(0, 1); + auto clf = bayesnet::XSp2de(0, 1); REQUIRE_THROWS_AS(clf.predict_proba(std::vector({1,2,3,4})), std::logic_error); clf.fitx(raw.Xt, raw.yt, raw.weights, bayesnet::Smoothing_t::ORIGINAL); REQUIRE(clf.getNFeatures() == 4);