Compare commits

...

16 Commits

Author SHA1 Message Date
464fe029ea Add dump_cpt classifier test 2024-04-11 18:16:06 +02:00
09a1369122 Add copyright header to source files 2024-04-11 18:02:49 +02:00
503ad687dc Add some more tests to 97% coverage 2024-04-11 17:29:46 +02:00
8eeaa1beee Update changelog with the latest changes 2024-04-11 00:35:55 +02:00
a2de1c9522 Implement block update algorithm fix in BoostAODE 2024-04-11 00:02:43 +02:00
cf9b5716ac block_update and install in local folder 2024-04-10 00:55:36 +02:00
1326891d6a Fix previous tests of BoostAODE
Due to the change of default values for hyperparameters in BoostAODE
2024-04-09 00:13:45 +02:00
da2a969686 Create hyperparameter block_update 2024-04-08 23:36:05 +02:00
f9553a38d7 Fix BoostAODE.md doc 2024-04-08 22:45:32 +02:00
8b6121eaf2 Update readme and boostAODE docs 2024-04-08 22:41:23 +02:00
fbbed8ad68 Make some boostAODE tests 2024-04-08 22:30:55 +02:00
a1178554ff Add Ensemble tests 2024-04-08 19:09:51 +02:00
d12a779bd9 Merge pull request 'bisection proposal' (#24) from bisection into main
Reviewed-on: #24
2024-04-08 14:29:25 +00:00
a8fc29e2b2 Create coverage badge 2024-04-08 11:24:25 +02:00
50543e7929 Add tests for Classifier class 2024-04-08 01:25:14 +02:00
9014649a0d Refactor hyperparameters classifier management 2024-04-08 00:55:30 +02:00
68 changed files with 1112 additions and 2039 deletions

32
.vscode/launch.json vendored
View File

@@ -8,7 +8,7 @@
"program": "${workspaceFolder}/build_release/sample/bayesnet_sample", "program": "${workspaceFolder}/build_release/sample/bayesnet_sample",
"args": [ "args": [
"${workspaceFolder}/tests/data/glass.arff" "${workspaceFolder}/tests/data/glass.arff"
], ]
}, },
{ {
"type": "lldb", "type": "lldb",
@@ -16,11 +16,33 @@
"name": "test", "name": "test",
"program": "${workspaceFolder}/build_debug/tests/TestBayesNet", "program": "${workspaceFolder}/build_debug/tests/TestBayesNet",
"args": [ "args": [
"[Network]" "Block Update"
//"-c=\"Metrics Test\"",
// "-s",
], ],
"cwd": "${workspaceFolder}/build_debug/tests", "cwd": "${workspaceFolder}/build_debug/tests"
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "enter program name, for example ${workspaceFolder}/a.out",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
} }
] ]
} }

View File

@@ -10,18 +10,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Install command and instructions in README.md - Install command and instructions in README.md
- Prefix to install command to install the package in the any location.
- The 'block_update' hyperparameter to the BoostAODE class, to control the way weights/significances are updated. Default value is false.
- Html report of coverage in the coverage folder. It is created with *make viewcoverage*
- Badges of coverage and code quality (codacy) in README.md. Coverage badge is updated with *make viewcoverage*
- Tests to reach 97% of coverage.
- Copyright header to source files.
### Changed ### Changed
- Sample app now is a separate target in the Makefile and shows how to use the library with a sample dataset - Sample app now is a separate target in the Makefile and shows how to use the library with a sample dataset
- The worse model count in BoostAODE is reset to 0 every time a new model produces better accuracy, so the tolerance of the model is meant to be the number of **consecutive** models that produce worse accuracy. - The worse model count in BoostAODE is reset to 0 every time a new model produces better accuracy, so the tolerance of the model is meant to be the number of **consecutive** models that produce worse accuracy.
- Default hyperparameter values in BoostAODE: bisection is true, maxTolerance is 3, convergence is true
## [1.0.4] 2024-03-06 ## [1.0.4] 2024-03-06
### Added ### Added
- Change _ascending_ hyperparameter to _order_ with these possible values _{"asc", "desc", "rand"}_, Default is _"desc"_. - Change *ascending* hyperparameter to *order* with these possible values *{"asc", "desc", "rand"}*, Default is *"desc"*.
- Add the _predict_single_ hyperparameter to control if only the last model created is used to predict in boost training or the whole ensemble (all the models built so far). Default is true. - Add the *predict_single* hyperparameter to control if only the last model created is used to predict in boost training or the whole ensemble (all the models built so far). Default is true.
- sample app to show how to use the library (make sample) - sample app to show how to use the library (make sample)
### Changed ### Changed

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(BayesNet project(BayesNet
VERSION 1.0.4 VERSION 1.0.4.1
DESCRIPTION "Bayesian Network and basic classifiers Library." DESCRIPTION "Bayesian Network and basic classifiers Library."
HOMEPAGE_URL "https://github.com/rmontanana/bayesnet" HOMEPAGE_URL "https://github.com/rmontanana/bayesnet"
LANGUAGES CXX LANGUAGES CXX

View File

@@ -1,6 +1,6 @@
SHELL := /bin/bash SHELL := /bin/bash
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
.PHONY: viewcoverage coverage setup help install uninstall buildr buildd test clean debug release sample .PHONY: viewcoverage coverage setup help install uninstall buildr buildd test clean debug release sample updatebadge
f_release = build_release f_release = build_release
f_debug = build_debug f_debug = build_debug
@@ -53,9 +53,10 @@ uninstall: ## Uninstall library
xargs rm < $(f_release)/install_manifest.txt xargs rm < $(f_release)/install_manifest.txt
@echo ">>> Done"; @echo ">>> Done";
prefix = "/usr/local"
install: ## Install library install: ## Install library
@echo ">>> Installing BayesNet..."; @echo ">>> Installing BayesNet...";
@cmake --install $(f_release) @cmake --install $(f_release) --prefix $(prefix)
@echo ">>> Done"; @echo ">>> Done";
debug: ## Build a debug version of the project debug: ## Build a debug version of the project
@@ -112,10 +113,15 @@ viewcoverage: ## Run tests, generate coverage report and upload it to codecov (b
lcov --remove coverage.info 'libtorch/*' --output-file coverage.info >/dev/null 2>&1; \ lcov --remove coverage.info 'libtorch/*' --output-file coverage.info >/dev/null 2>&1; \
lcov --remove coverage.info 'tests/*' --output-file coverage.info >/dev/null 2>&1; \ lcov --remove coverage.info 'tests/*' --output-file coverage.info >/dev/null 2>&1; \
lcov --remove coverage.info 'bayesnet/utils/loguru.*' --output-file coverage.info >/dev/null 2>&1; \ lcov --remove coverage.info 'bayesnet/utils/loguru.*' --output-file coverage.info >/dev/null 2>&1; \
genhtml coverage.info --output-directory $(f_debug)/tests/coverage >/dev/null 2>&1; \ genhtml coverage.info --output-directory coverage >/dev/null 2>&1;
xdg-open $(f_debug)/tests/coverage/index.html || open $(f_debug)/tests/coverage/index.html 2>/dev/null @$(MAKE) updatebadge
@xdg-open $(f_debug)/tests/coverage/index.html || open $(f_debug)/tests/coverage/index.html 2>/dev/null
@echo ">>> Done"; @echo ">>> Done";
updatebadge: ## Update the coverage badge in README.md
@echo ">>> Updating coverage badge..."
@env python update_coverage.py $(f_debug)/tests
@echo ">>> Done";
help: ## Show help message help: ## Show help message
@IFS=$$'\n' ; \ @IFS=$$'\n' ; \

View File

@@ -5,10 +5,20 @@
![Gitea Release](https://img.shields.io/gitea/v/release/rmontanana/bayesnet?gitea_url=https://gitea.rmontanana.es:3000) ![Gitea Release](https://img.shields.io/gitea/v/release/rmontanana/bayesnet?gitea_url=https://gitea.rmontanana.es:3000)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/cf3e0ac71d764650b1bf4d8d00d303b1)](https://app.codacy.com/gh/Doctorado-ML/BayesNet/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/cf3e0ac71d764650b1bf4d8d00d303b1)](https://app.codacy.com/gh/Doctorado-ML/BayesNet/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
![Gitea Last Commit](https://img.shields.io/gitea/last-commit/rmontanana/bayesnet?gitea_url=https://gitea.rmontanana.es:3000&logo=gitea) ![Gitea Last Commit](https://img.shields.io/gitea/last-commit/rmontanana/bayesnet?gitea_url=https://gitea.rmontanana.es:3000&logo=gitea)
![Static Badge](https://img.shields.io/badge/Coverage-97,2%25-green)
Bayesian Network Classifiers using libtorch from scratch Bayesian Network Classifiers using libtorch from scratch
## Installation ## Dependencies
The only external dependency is [libtorch](https://pytorch.org/cppdocs/installing.html) which can be installed with the following commands:
```bash
wget https://download.pytorch.org/libtorch/nightly/cpu/libtorch-shared-with-deps-latest.zip
unzip libtorch-shared-with-deps-latest.zips
```
## Setup
### Release ### Release

View File

@@ -1,5 +1,10 @@
#ifndef BASE_H // ***************************************************************
#define BASE_H // SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#pragma once
#include <vector> #include <vector>
#include <torch/torch.h> #include <torch/torch.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -30,12 +35,11 @@ namespace bayesnet {
virtual std::string getVersion() = 0; virtual std::string getVersion() = 0;
std::vector<std::string> virtual topological_order() = 0; std::vector<std::string> virtual topological_order() = 0;
std::vector<std::string> virtual getNotes() const = 0; std::vector<std::string> virtual getNotes() const = 0;
void virtual dump_cpt()const = 0; std::string virtual dump_cpt()const = 0;
virtual void setHyperparameters(const nlohmann::json& hyperparameters) = 0; virtual void setHyperparameters(const nlohmann::json& hyperparameters) = 0;
std::vector<std::string>& getValidHyperparameters() { return validHyperparameters; } std::vector<std::string>& getValidHyperparameters() { return validHyperparameters; }
protected: protected:
virtual void trainModel(const torch::Tensor& weights) = 0; virtual void trainModel(const torch::Tensor& weights) = 0;
std::vector<std::string> validHyperparameters; std::vector<std::string> validHyperparameters;
}; };
} }
#endif

View File

@@ -1,3 +1,10 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <sstream>
#include "bayesnet/utils/bayesnetUtils.h" #include "bayesnet/utils/bayesnetUtils.h"
#include "Classifier.h" #include "Classifier.h"
@@ -27,10 +34,11 @@ namespace bayesnet {
dataset = torch::cat({ dataset, yresized }, 0); dataset = torch::cat({ dataset, yresized }, 0);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << e.what() << '\n'; std::stringstream oss;
std::cout << "X dimensions: " << dataset.sizes() << "\n"; oss << "* Error in X and y dimensions *\n";
std::cout << "y dimensions: " << ytmp.sizes() << "\n"; oss << "X dimensions: " << dataset.sizes() << "\n";
exit(1); oss << "y dimensions: " << ytmp.sizes();
throw std::runtime_error(oss.str());
} }
} }
void Classifier::trainModel(const torch::Tensor& weights) void Classifier::trainModel(const torch::Tensor& weights)
@@ -73,11 +81,11 @@ namespace bayesnet {
if (torch::is_floating_point(dataset)) { if (torch::is_floating_point(dataset)) {
throw std::invalid_argument("dataset (X, y) must be of type Integer"); throw std::invalid_argument("dataset (X, y) must be of type Integer");
} }
if (n != features.size()) { if (dataset.size(0) - 1 != features.size()) {
throw std::invalid_argument("Classifier: X " + std::to_string(n) + " and features " + std::to_string(features.size()) + " must have the same number of features"); throw std::invalid_argument("Classifier: X " + std::to_string(dataset.size(0) - 1) + " and features " + std::to_string(features.size()) + " must have the same number of features");
} }
if (states.find(className) == states.end()) { if (states.find(className) == states.end()) {
throw std::invalid_argument("className not found in states"); throw std::invalid_argument("class name not found in states");
} }
for (auto feature : features) { for (auto feature : features) {
if (states.find(feature) == states.end()) { if (states.find(feature) == states.end()) {
@@ -173,12 +181,14 @@ namespace bayesnet {
{ {
return model.topological_sort(); return model.topological_sort();
} }
void Classifier::dump_cpt() const std::string Classifier::dump_cpt() const
{ {
model.dump_cpt(); return model.dump_cpt();
} }
void Classifier::setHyperparameters(const nlohmann::json& hyperparameters) void Classifier::setHyperparameters(const nlohmann::json& hyperparameters)
{ {
//For classifiers that don't have hyperparameters if (!hyperparameters.empty()) {
throw std::invalid_argument("Invalid hyperparameters" + hyperparameters.dump());
}
} }
} }

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef CLASSIFIER_H #ifndef CLASSIFIER_H
#define CLASSIFIER_H #define CLASSIFIER_H
#include <torch/torch.h> #include <torch/torch.h>
@@ -30,7 +36,7 @@ namespace bayesnet {
std::vector<std::string> show() const override; std::vector<std::string> show() const override;
std::vector<std::string> topological_order() override; std::vector<std::string> topological_order() override;
std::vector<std::string> getNotes() const override { return notes; } std::vector<std::string> getNotes() const override { return notes; }
void dump_cpt() const override; std::string dump_cpt() const override;
void setHyperparameters(const nlohmann::json& hyperparameters) override; //For classifiers that don't have hyperparameters void setHyperparameters(const nlohmann::json& hyperparameters) override; //For classifiers that don't have hyperparameters
protected: protected:
bool fitted; bool fitted;

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "KDB.h" #include "KDB.h"
namespace bayesnet { namespace bayesnet {
@@ -6,14 +12,18 @@ namespace bayesnet {
validHyperparameters = { "k", "theta" }; validHyperparameters = { "k", "theta" };
} }
void KDB::setHyperparameters(const nlohmann::json& hyperparameters) void KDB::setHyperparameters(const nlohmann::json& hyperparameters_)
{ {
auto hyperparameters = hyperparameters_;
if (hyperparameters.contains("k")) { if (hyperparameters.contains("k")) {
k = hyperparameters["k"]; k = hyperparameters["k"];
hyperparameters.erase("k");
} }
if (hyperparameters.contains("theta")) { if (hyperparameters.contains("theta")) {
theta = hyperparameters["theta"]; theta = hyperparameters["theta"];
hyperparameters.erase("theta");
} }
Classifier::setHyperparameters(hyperparameters);
} }
void KDB::buildModel(const torch::Tensor& weights) void KDB::buildModel(const torch::Tensor& weights)
{ {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef KDB_H #ifndef KDB_H
#define KDB_H #define KDB_H
#include <torch/torch.h> #include <torch/torch.h>
@@ -14,7 +20,7 @@ namespace bayesnet {
public: public:
explicit KDB(int k, float theta = 0.03); explicit KDB(int k, float theta = 0.03);
virtual ~KDB() = default; virtual ~KDB() = default;
void setHyperparameters(const nlohmann::json& hyperparameters) override; void setHyperparameters(const nlohmann::json& hyperparameters_) override;
std::vector<std::string> graph(const std::string& name = "KDB") const override; std::vector<std::string> graph(const std::string& name = "KDB") const override;
}; };
} }

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "KDBLd.h" #include "KDBLd.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef KDBLD_H #ifndef KDBLD_H
#define KDBLD_H #define KDBLD_H
#include "Proposal.h" #include "Proposal.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <ArffFiles.h> #include <ArffFiles.h>
#include "Proposal.h" #include "Proposal.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef PROPOSAL_H #ifndef PROPOSAL_H
#define PROPOSAL_H #define PROPOSAL_H
#include <string> #include <string>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "SPODE.h" #include "SPODE.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef SPODE_H #ifndef SPODE_H
#define SPODE_H #define SPODE_H
#include "Classifier.h" #include "Classifier.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "SPODELd.h" #include "SPODELd.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef SPODELD_H #ifndef SPODELD_H
#define SPODELD_H #define SPODELD_H
#include "SPODE.h" #include "SPODE.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "TAN.h" #include "TAN.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef TAN_H #ifndef TAN_H
#define TAN_H #define TAN_H
#include "Classifier.h" #include "Classifier.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "TANLd.h" #include "TANLd.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef TANLD_H #ifndef TANLD_H
#define TANLD_H #define TANLD_H
#include "TAN.h" #include "TAN.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "AODE.h" #include "AODE.h"
namespace bayesnet { namespace bayesnet {
@@ -13,9 +19,7 @@ namespace bayesnet {
predict_voting = hyperparameters["predict_voting"]; predict_voting = hyperparameters["predict_voting"];
hyperparameters.erase("predict_voting"); hyperparameters.erase("predict_voting");
} }
if (!hyperparameters.empty()) { Classifier::setHyperparameters(hyperparameters);
throw std::invalid_argument("Invalid hyperparameters" + hyperparameters.dump());
}
} }
void AODE::buildModel(const torch::Tensor& weights) void AODE::buildModel(const torch::Tensor& weights)
{ {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef AODE_H #ifndef AODE_H
#define AODE_H #define AODE_H
#include "bayesnet/classifiers/SPODE.h" #include "bayesnet/classifiers/SPODE.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "AODELd.h" #include "AODELd.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef AODELD_H #ifndef AODELD_H
#define AODELD_H #define AODELD_H
#include "bayesnet/classifiers/Proposal.h" #include "bayesnet/classifiers/Proposal.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <set> #include <set>
#include <functional> #include <functional>
#include <limits.h> #include <limits.h>
@@ -16,7 +22,7 @@ namespace bayesnet {
{ {
validHyperparameters = { validHyperparameters = {
"maxModels", "bisection", "order", "convergence", "threshold", "maxModels", "bisection", "order", "convergence", "threshold",
"select_features", "maxTolerance", "predict_voting" "select_features", "maxTolerance", "predict_voting", "block_update"
}; };
} }
@@ -94,9 +100,11 @@ namespace bayesnet {
} }
hyperparameters.erase("select_features"); hyperparameters.erase("select_features");
} }
if (!hyperparameters.empty()) { if (hyperparameters.contains("block_update")) {
throw std::invalid_argument("Invalid hyperparameters" + hyperparameters.dump()); block_update = hyperparameters["block_update"];
hyperparameters.erase("block_update");
} }
Classifier::setHyperparameters(hyperparameters);
} }
std::tuple<torch::Tensor&, double, bool> update_weights(torch::Tensor& ytrain, torch::Tensor& ypred, torch::Tensor& weights) std::tuple<torch::Tensor&, double, bool> update_weights(torch::Tensor& ytrain, torch::Tensor& ypred, torch::Tensor& weights)
{ {
@@ -125,6 +133,103 @@ namespace bayesnet {
} }
return { weights, alpha_t, terminate }; return { weights, alpha_t, terminate };
} }
std::tuple<torch::Tensor&, double, bool> BoostAODE::update_weights_block(int k, torch::Tensor& ytrain, torch::Tensor& weights)
{
/* Update Block algorithm
k = # of models in block
n_models = # of models in ensemble to make predictions
n_models_bak = # models saved
models = vector of models to make predictions
models_bak = models not used to make predictions
significances_bak = backup of significances vector
Case list
A) k = 1, n_models = 1 => n = 0 , n_models = n + k
B) k = 1, n_models = n + 1 => n_models = n + k
C) k > 1, n_models = k + 1 => n= 1, n_models = n + k
D) k > 1, n_models = k => n = 0, n_models = n + k
E) k > 1, n_models = k + n => n_models = n + k
A, D) n=0, k > 0, n_models == k
1. n_models_bak <- n_models
2. significances_bak <- significances
3. significances = vector(k, 1)
4. Dont move any classifiers out of models
5. n_models <- k
6. Make prediction, compute alpha, update weights
7. Dont restore any classifiers to models
8. significances <- significances_bak
9. Update last k significances
10. n_models <- n_models_bak
B, C, E) n > 0, k > 0, n_models == n + k
1. n_models_bak <- n_models
2. significances_bak <- significances
3. significances = vector(k, 1)
4. Move first n classifiers to models_bak
5. n_models <- k
6. Make prediction, compute alpha, update weights
7. Insert classifiers in models_bak to be the first n models
8. significances <- significances_bak
9. Update last k significances
10. n_models <- n_models_bak
*/
//
// Make predict with only the last k models
//
std::unique_ptr<Classifier> model;
std::vector<std::unique_ptr<Classifier>> models_bak;
// 1. n_models_bak <- n_models 2. significances_bak <- significances
auto significance_bak = significanceModels;
auto n_models_bak = n_models;
// 3. significances = vector(k, 1)
significanceModels = std::vector<double>(k, 1.0);
// 4. Move first n classifiers to models_bak
// backup the first n_models - k models (if n_models == k, don't backup any)
VLOG_SCOPE_F(1, "upd_weights_block n_models=%d k=%d", n_models, k);
for (int i = 0; i < n_models - k; ++i) {
model = std::move(models[0]);
models.erase(models.begin());
models_bak.push_back(std::move(model));
}
assert(models.size() == k);
// 5. n_models <- k
n_models = k;
// 6. Make prediction, compute alpha, update weights
auto ypred = predict(X_train);
//
// Update weights
//
double alpha_t;
bool terminate;
std::tie(weights, alpha_t, terminate) = update_weights(y_train, ypred, weights);
//
// Restore the models if needed
//
// 7. Insert classifiers in models_bak to be the first n models
// if n_models_bak == k, don't restore any, because none of them were moved
if (k != n_models_bak) {
// Insert in the same order as they were extracted
int bak_size = models_bak.size();
for (int i = 0; i < bak_size; ++i) {
model = std::move(models_bak[bak_size - 1 - i]);
models_bak.erase(models_bak.end() - 1);
models.insert(models.begin(), std::move(model));
}
}
// 8. significances <- significances_bak
significanceModels = significance_bak;
//
// Update the significance of the last k models
//
// 9. Update last k significances
for (int i = 0; i < k; ++i) {
significanceModels[n_models_bak - k + i] = alpha_t;
}
// 10. n_models <- n_models_bak
n_models = n_models_bak;
return { weights, alpha_t, terminate };
}
std::vector<int> BoostAODE::initializeModels() std::vector<int> BoostAODE::initializeModels()
{ {
std::vector<int> featuresUsed; std::vector<int> featuresUsed;
@@ -154,7 +259,7 @@ namespace bayesnet {
std::unique_ptr<Classifier> model = std::make_unique<SPODE>(feature); std::unique_ptr<Classifier> model = std::make_unique<SPODE>(feature);
model->fit(dataset, features, className, states, weights_); model->fit(dataset, features, className, states, weights_);
models.push_back(std::move(model)); models.push_back(std::move(model));
significanceModels.push_back(1.0); significanceModels.push_back(1.0); // They will be updated later in trainModel
n_models++; n_models++;
} }
notes.push_back("Used features in initialization: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size()) + " with " + select_features_algorithm); notes.push_back("Used features in initialization: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size()) + " with " + select_features_algorithm);
@@ -219,21 +324,22 @@ namespace bayesnet {
); );
int k = pow(2, tolerance); int k = pow(2, tolerance);
int counter = 0; // The model counter of the current pack int counter = 0; // The model counter of the current pack
VLOG_SCOPE_F(1, "k=%d featureSelection.size: %zu", k, featureSelection.size()); VLOG_SCOPE_F(1, "counter=%d k=%d featureSelection.size: %zu", counter, k, featureSelection.size());
while (counter++ < k && featureSelection.size() > 0) { while (counter++ < k && featureSelection.size() > 0) {
VLOG_SCOPE_F(2, "counter: %d numItemsPack: %d", counter, numItemsPack);
auto feature = featureSelection[0]; auto feature = featureSelection[0];
featureSelection.erase(featureSelection.begin()); featureSelection.erase(featureSelection.begin());
std::unique_ptr<Classifier> model; std::unique_ptr<Classifier> model;
model = std::make_unique<SPODE>(feature); model = std::make_unique<SPODE>(feature);
model->fit(dataset, features, className, states, weights_); model->fit(dataset, features, className, states, weights_);
torch::Tensor ypred; alpha_t = 0.0;
ypred = model->predict(X_train); if (!block_update) {
// Step 3.1: Compute the classifier amout of say auto ypred = model->predict(X_train);
std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred, weights_); // Step 3.1: Compute the classifier amout of say
if (finished) { std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred, weights_);
VLOG_SCOPE_F(2, "** epsilon_t > 0.5 **"); if (finished) {
break; VLOG_SCOPE_F(2, "** epsilon_t > 0.5 **");
break;
}
} }
// Step 3.4: Store classifier and its accuracy to weigh its future vote // Step 3.4: Store classifier and its accuracy to weigh its future vote
numItemsPack++; numItemsPack++;
@@ -243,6 +349,9 @@ namespace bayesnet {
n_models++; n_models++;
VLOG_SCOPE_F(2, "numItemsPack: %d n_models: %d featuresUsed: %zu", numItemsPack, n_models, featuresUsed.size()); VLOG_SCOPE_F(2, "numItemsPack: %d n_models: %d featuresUsed: %zu", numItemsPack, n_models, featuresUsed.size());
} }
if (block_update) {
std::tie(weights_, alpha_t, finished) = update_weights_block(k, y_train, weights_);
}
if (convergence && !finished) { if (convergence && !finished) {
auto y_val_predict = predict(X_test); auto y_val_predict = predict(X_test);
double accuracy = (y_val_predict == y_test).sum().item<double>() / (double)y_test.size(0); double accuracy = (y_val_predict == y_test).sum().item<double>() / (double)y_test.size(0);

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef BOOSTAODE_H #ifndef BOOSTAODE_H
#define BOOSTAODE_H #define BOOSTAODE_H
#include <map> #include <map>
@@ -20,22 +26,24 @@ namespace bayesnet {
BoostAODE(bool predict_voting = false); BoostAODE(bool predict_voting = false);
virtual ~BoostAODE() = default; virtual ~BoostAODE() = default;
std::vector<std::string> graph(const std::string& title = "BoostAODE") const override; std::vector<std::string> graph(const std::string& title = "BoostAODE") const override;
void setHyperparameters(const nlohmann::json& hyperparameters) override; void setHyperparameters(const nlohmann::json& hyperparameters_) override;
protected: protected:
void buildModel(const torch::Tensor& weights) override; void buildModel(const torch::Tensor& weights) override;
void trainModel(const torch::Tensor& weights) override; void trainModel(const torch::Tensor& weights) override;
private: private:
std::tuple<torch::Tensor&, double, bool> update_weights_block(int k, torch::Tensor& ytrain, torch::Tensor& weights);
std::vector<int> initializeModels(); std::vector<int> initializeModels();
torch::Tensor X_train, y_train, X_test, y_test; torch::Tensor X_train, y_train, X_test, y_test;
// Hyperparameters // Hyperparameters
bool bisection = false; // if true, use bisection stratety to add k models at once to the ensemble bool bisection = true; // if true, use bisection stratety to add k models at once to the ensemble
int maxTolerance = 1; int maxTolerance = 3;
std::string order_algorithm; // order to process the KBest features asc, desc, rand std::string order_algorithm; // order to process the KBest features asc, desc, rand
bool convergence = false; //if true, stop when the model does not improve bool convergence = true; //if true, stop when the model does not improve
bool selectFeatures = false; // if true, use feature selection bool selectFeatures = false; // if true, use feature selection
std::string select_features_algorithm = Orders.DESC; // Selected feature selection algorithm std::string select_features_algorithm = Orders.DESC; // Selected feature selection algorithm
FeatureSelect* featureSelector = nullptr; FeatureSelect* featureSelector = nullptr;
double threshold = -1; double threshold = -1;
bool block_update = false;
}; };
} }
#endif #endif

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "Ensemble.h" #include "Ensemble.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef ENSEMBLE_H #ifndef ENSEMBLE_H
#define ENSEMBLE_H #define ENSEMBLE_H
#include <torch/torch.h> #include <torch/torch.h>
@@ -25,8 +31,9 @@ namespace bayesnet {
{ {
return std::vector<std::string>(); return std::vector<std::string>();
} }
void dump_cpt() const override std::string dump_cpt() const override
{ {
return "";
} }
protected: protected:
torch::Tensor predict_average_voting(torch::Tensor& X); torch::Tensor predict_average_voting(torch::Tensor& X);

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <limits> #include <limits>
#include "bayesnet/utils/bayesnetUtils.h" #include "bayesnet/utils/bayesnetUtils.h"
#include "CFS.h" #include "CFS.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef CFS_H #ifndef CFS_H
#define CFS_H #define CFS_H
#include <torch/torch.h> #include <torch/torch.h>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "bayesnet/utils/bayesnetUtils.h" #include "bayesnet/utils/bayesnetUtils.h"
#include "FCBF.h" #include "FCBF.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef FCBF_H #ifndef FCBF_H
#define FCBF_H #define FCBF_H
#include <torch/torch.h> #include <torch/torch.h>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <limits> #include <limits>
#include "bayesnet/utils/bayesnetUtils.h" #include "bayesnet/utils/bayesnetUtils.h"
#include "FeatureSelect.h" #include "FeatureSelect.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef FEATURE_SELECT_H #ifndef FEATURE_SELECT_H
#define FEATURE_SELECT_H #define FEATURE_SELECT_H
#include <torch/torch.h> #include <torch/torch.h>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <limits> #include <limits>
#include "bayesnet/utils/bayesnetUtils.h" #include "bayesnet/utils/bayesnetUtils.h"
#include "IWSS.h" #include "IWSS.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef IWSS_H #ifndef IWSS_H
#define IWSS_H #define IWSS_H
#include <vector> #include <vector>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <sstream> #include <sstream>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef NETWORK_H #ifndef NETWORK_H
#define NETWORK_H #define NETWORK_H
#include <map> #include <map>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "Node.h" #include "Node.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef NODE_H #ifndef NODE_H
#define NODE_H #define NODE_H
#include <unordered_set> #include <unordered_set>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "Mst.h" #include "Mst.h"
#include "BayesMetrics.h" #include "BayesMetrics.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef BAYESNET_METRICS_H #ifndef BAYESNET_METRICS_H
#define BAYESNET_METRICS_H #define BAYESNET_METRICS_H
#include <vector> #include <vector>

View File

@@ -1,3 +1,10 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <sstream>
#include <vector> #include <vector>
#include <list> #include <list>
#include "Mst.h" #include "Mst.h"
@@ -45,15 +52,6 @@ namespace bayesnet {
} }
} }
} }
void Graph::display_mst()
{
std::cout << "Edge :" << " Weight" << std::endl;
for (int i = 0; i < T.size(); i++) {
std::cout << T[i].second.first << " - " << T[i].second.second << " : "
<< T[i].first;
std::cout << std::endl;
}
}
void insertElement(std::list<int>& variables, int variable) void insertElement(std::list<int>& variables, int variable)
{ {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef MST_H #ifndef MST_H
#define MST_H #define MST_H
#include <vector> #include <vector>
@@ -5,29 +11,28 @@
#include <torch/torch.h> #include <torch/torch.h>
namespace bayesnet { namespace bayesnet {
class MST { class MST {
private:
torch::Tensor weights;
std::vector<std::string> features;
int root = 0;
public: public:
MST() = default; MST() = default;
MST(const std::vector<std::string>& features, const torch::Tensor& weights, const int root); MST(const std::vector<std::string>& features, const torch::Tensor& weights, const int root);
std::vector<std::pair<int, int>> maximumSpanningTree(); std::vector<std::pair<int, int>> maximumSpanningTree();
private:
torch::Tensor weights;
std::vector<std::string> features;
int root = 0;
}; };
class Graph { class Graph {
private:
int V; // number of nodes in graph
std::vector <std::pair<float, std::pair<int, int>>> G; // std::vector for graph
std::vector <std::pair<float, std::pair<int, int>>> T; // std::vector for mst
std::vector<int> parent;
public: public:
explicit Graph(int V); explicit Graph(int V);
void addEdge(int u, int v, float wt); void addEdge(int u, int v, float wt);
int find_set(int i); int find_set(int i);
void union_set(int u, int v); void union_set(int u, int v);
void kruskal_algorithm(); void kruskal_algorithm();
void display_mst();
std::vector <std::pair<float, std::pair<int, int>>> get_mst() { return T; } std::vector <std::pair<float, std::pair<int, int>>> get_mst() { return T; }
private:
int V; // number of nodes in graph
std::vector <std::pair<float, std::pair<int, int>>> G; // std::vector for graph
std::vector <std::pair<float, std::pair<int, int>>> T; // std::vector for mst
std::vector<int> parent;
}; };
} }
#endif #endif

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "bayesnetUtils.h" #include "bayesnetUtils.h"
namespace bayesnet { namespace bayesnet {

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef BAYESNET_UTILS_H #ifndef BAYESNET_UTILS_H
#define BAYESNET_UTILS_H #define BAYESNET_UTILS_H
#include <vector> #include <vector>

View File

@@ -1,18 +1,18 @@
# BoostAODE Algorithm Operation # BoostAODE Algorithm Operation
## Algorithm
## Hyperparameters ## Hyperparameters
The hyperparameters defined in the algorithm are: The hyperparameters defined in the algorithm are:
- ***bisection*** (*boolean*): If set to true allows the algorithm to add *k* models at once (as specified in the algorithm) to the ensemble. Default value: *false*. - ***bisection*** (*boolean*): If set to true allows the algorithm to add *k* models at once (as specified in the algorithm) to the ensemble. Default value: *true*.
- ***order*** (*{"asc", "desc", "rand"}*): Sets the order (ascending/descending/random) in which dataset variables will be processed to choose the parents of the *SPODEs*. Default value: *"desc"*. - ***order*** (*{"asc", "desc", "rand"}*): Sets the order (ascending/descending/random) in which dataset variables will be processed to choose the parents of the *SPODEs*. Default value: *"desc"*.
- ***convergence*** (*boolean*): Sets whether the convergence of the result will be used as a termination condition. If this hyperparameter is set to true, the training dataset passed to the model is divided into two sets, one serving as training data and the other as a test set (so the original test partition will become a validation partition in this case). The partition is made by taking the first partition generated by a process of generating a 5 fold partition with stratification using a predetermined seed. The exit condition used in this *convergence* is that the difference between the accuracy obtained by the current model and that obtained by the previous model is greater than *1e-4*; otherwise, one will be added to the number of models that worsen the result (see next hyperparameter). Default value: *false*. - ***block_update*** (*boolean*): Sets whether the algorithm will update the weights of the models in blocks. If set to false, the algorithm will update the weights of the models one by one. Default value: *false*.
- ***maxTolerance*** (*int*): Sets the maximum number of models that can worsen the result without constituting a termination condition. Default value: *1*. if ***bisection*** is set to *true*, the value of this hyperparameter will be exponent of base 2 to compute the number of models to insert at once. - ***convergence*** (*boolean*): Sets whether the convergence of the result will be used as a termination condition. If this hyperparameter is set to true, the training dataset passed to the model is divided into two sets, one serving as training data and the other as a test set (so the original test partition will become a validation partition in this case). The partition is made by taking the first partition generated by a process of generating a 5 fold partition with stratification using a predetermined seed. The exit condition used in this *convergence* is that the difference between the accuracy obtained by the current model and that obtained by the previous model is greater than *1e-4*; otherwise, one will be added to the number of models that worsen the result (see next hyperparameter). Default value: *true*.
- ***maxTolerance*** (*int*): Sets the maximum number of models that can worsen the result without constituting a termination condition. if ***bisection*** is set to *true*, the value of this hyperparameter will be exponent of base 2 to compute the number of models to insert at once. Default value: *3*
- ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. Once the models of the selected variables are built, the algorithm will update the weights using the ensemble and set the significance of all the models built with the same &alpha;<sub>t</sub>. Default value: *""*. - ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. Once the models of the selected variables are built, the algorithm will update the weights using the ensemble and set the significance of all the models built with the same &alpha;<sub>t</sub>. Default value: *""*.
@@ -26,42 +26,4 @@ The hyperparameters defined in the algorithm are:
## Operation ## Operation
The algorithm performs the following steps: ### [Algorithm](./algorithm.md)
1. **Initialization**
- If ***select_features*** is set, as many *SPODEs* are created as variables selected by the corresponding feature selection algorithm, and these variables are marked as used.
- Initial weights of the examples are set to *1/m*.
1. **Main Training Loop:**
- Variables are sorted by mutual information order with the class variable and processed in ascending, descending or random order, according to the value of the *order* hyperparameter. If it is random, the variables are shuffled.
- If the parent repetition is not established, the variable is marked as used.
- A *SPODE* is created using the selected variable as the parent.
- The model is trained, and the class variable corresponding to the training dataset is calculated. The calculation can be done using the last trained model or the set of models trained up to that point, according to the value of the *predict_single* hyperparameter.
- The weights associated with the examples are updated using this expression:
- w<sub>i</sub> · e<sup>&alpha;<sub>t</sub></sup> (if the example has been misclassified)
- w<sub>i</sub> · e<sup>-&alpha;<sub>t</sub></sup> (if the example has been correctly classified)
- The model significance is set to &alpha;<sub>t</sub>.
- If the ***convergence*** hyperparameter is set, the accuracy value on the test dataset that we separated in an initial step is calculated.
1. **Exit Conditions:**
- &epsilon;<sub>t</sub> > 0.5 => misclassified examples are penalized.
- Number of models with worse accuracy greater than ***tolerance*** and ***convergence*** established.
- There are no more variables to create models, and ***repeatSparent*** is not set.
- Number of models > ***maxModels*** if ***repeatSparent*** is set.
### [Proposal for *predict_single = false*](./BoostAODE_train_predict.pdf)

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "ArffFiles.h" #include "ArffFiles.h"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef ARFFFILES_H #ifndef ARFFFILES_H
#define ARFFFILES_H #define ARFFFILES_H

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef CPPFIMDLP_H #ifndef CPPFIMDLP_H
#define CPPFIMDLP_H #define CPPFIMDLP_H

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef CCMETRICS_H #ifndef CCMETRICS_H
#define CCMETRICS_H #define CCMETRICS_H

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef TYPES_H #ifndef TYPES_H
#define TYPES_H #define TYPES_H

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <ArffFiles.h> #include <ArffFiles.h>
#include <CPPFImdlp.h> #include <CPPFImdlp.h>
#include <bayesnet/ensembles/BoostAODE.h> #include <bayesnet/ensembles/BoostAODE.h>

View File

@@ -8,12 +8,17 @@ if(ENABLE_TESTING)
${CMAKE_BINARY_DIR}/configured_files/include ${CMAKE_BINARY_DIR}/configured_files/include
) )
file(GLOB_RECURSE BayesNet_SOURCES "${BayesNet_SOURCE_DIR}/bayesnet/*.cc") file(GLOB_RECURSE BayesNet_SOURCES "${BayesNet_SOURCE_DIR}/bayesnet/*.cc")
add_executable(TestBayesNet TestBayesNetwork.cc TestBayesNode.cc TestBayesModels.cc TestBayesMetrics.cc TestFeatureSelection.cc TestUtils.cc ${BayesNet_SOURCES}) add_executable(TestBayesNet TestBayesNetwork.cc TestBayesNode.cc TestBayesClassifier.cc
TestBayesModels.cc TestBayesMetrics.cc TestFeatureSelection.cc TestBoostAODE.cc
TestUtils.cc TestBayesEnsemble.cc ${BayesNet_SOURCES})
target_link_libraries(TestBayesNet PUBLIC "${TORCH_LIBRARIES}" ArffFiles mdlp Catch2::Catch2WithMain ) target_link_libraries(TestBayesNet PUBLIC "${TORCH_LIBRARIES}" ArffFiles mdlp Catch2::Catch2WithMain )
add_test(NAME BayesNetworkTest COMMAND TestBayesNet) add_test(NAME BayesNetworkTest COMMAND TestBayesNet)
add_test(NAME Network COMMAND TestBayesNet "[Network]") add_test(NAME Network COMMAND TestBayesNet "[Network]")
add_test(NAME Node COMMAND TestBayesNet "[Node]") add_test(NAME Node COMMAND TestBayesNet "[Node]")
add_test(NAME Metrics COMMAND TestBayesNet "[Metrics]") add_test(NAME Metrics COMMAND TestBayesNet "[Metrics]")
add_test(NAME FeatureSelection COMMAND TestBayesNet "[FeatureSelection]") add_test(NAME FeatureSelection COMMAND TestBayesNet "[FeatureSelection]")
add_test(NAME Classifier COMMAND TestBayesNet "[Classifier]")
add_test(NAME Ensemble COMMAND TestBayesNet "[Ensemble]")
add_test(NAME Models COMMAND TestBayesNet "[Models]") add_test(NAME Models COMMAND TestBayesNet "[Models]")
add_test(NAME BoostAODE COMMAND TestBayesNet "[BoostAODE]")
endif(ENABLE_TESTING) endif(ENABLE_TESTING)

View File

@@ -0,0 +1,125 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <string>
#include "TestUtils.h"
#include "bayesnet/classifiers/TAN.h"
#include "bayesnet/classifiers/KDB.h"
#include "bayesnet/classifiers/KDBLd.h"
TEST_CASE("Test Cannot build dataset with wrong data vector", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
raw.yv.pop_back();
REQUIRE_THROWS_AS(model.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv), std::runtime_error);
REQUIRE_THROWS_WITH(model.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv), "* Error in X and y dimensions *\nX dimensions: [4, 150]\ny dimensions: [149]");
}
TEST_CASE("Test Cannot build dataset with wrong data tensor", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
auto yshort = torch::zeros({ 149 }, torch::kInt32);
REQUIRE_THROWS_AS(model.fit(raw.Xt, yshort, raw.featurest, raw.classNamet, raw.statest), std::runtime_error);
REQUIRE_THROWS_WITH(model.fit(raw.Xt, yshort, raw.featurest, raw.classNamet, raw.statest), "* Error in X and y dimensions *\nX dimensions: [4, 150]\ny dimensions: [149]");
}
TEST_CASE("Invalid data type", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", false);
REQUIRE_THROWS_AS(model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest), std::invalid_argument);
REQUIRE_THROWS_WITH(model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest), "dataset (X, y) must be of type Integer");
}
TEST_CASE("Invalid number of features", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
auto Xt = torch::cat({ raw.Xt, torch::zeros({ 1, 150 }, torch::kInt32) }, 0);
REQUIRE_THROWS_AS(model.fit(Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest), std::invalid_argument);
REQUIRE_THROWS_WITH(model.fit(Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest), "Classifier: X 5 and features 4 must have the same number of features");
}
TEST_CASE("Invalid class name", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
REQUIRE_THROWS_AS(model.fit(raw.Xt, raw.yt, raw.featurest, "duck", raw.statest), std::invalid_argument);
REQUIRE_THROWS_WITH(model.fit(raw.Xt, raw.yt, raw.featurest, "duck", raw.statest), "class name not found in states");
}
TEST_CASE("Invalid feature name", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
auto statest = raw.statest;
statest.erase("petallength");
REQUIRE_THROWS_AS(model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, statest), std::invalid_argument);
REQUIRE_THROWS_WITH(model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, statest), "feature [petallength] not found in states");
}
TEST_CASE("Invalid hyperparameter", "[Classifier]")
{
auto model = bayesnet::KDB(2);
auto raw = RawDatasets("iris", true);
REQUIRE_THROWS_AS(model.setHyperparameters({ { "alpha", "0.0" } }), std::invalid_argument);
REQUIRE_THROWS_WITH(model.setHyperparameters({ { "alpha", "0.0" } }), "Invalid hyperparameters{\"alpha\":\"0.0\"}");
}
TEST_CASE("Topological order", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest);
auto order = model.topological_order();
REQUIRE(order.size() == 4);
REQUIRE(order[0] == "petallength");
REQUIRE(order[1] == "sepallength");
REQUIRE(order[2] == "sepalwidth");
REQUIRE(order[3] == "petalwidth");
}
TEST_CASE("Dump_cpt", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest);
auto cpt = model.dump_cpt();
REQUIRE(cpt.size() == 1713);
}
TEST_CASE("Not fitted model", "[Classifier]")
{
auto model = bayesnet::TAN();
auto raw = RawDatasets("iris", true);
auto message = "Classifier has not been fitted";
// tensors
REQUIRE_THROWS_AS(model.predict(raw.Xt), std::logic_error);
REQUIRE_THROWS_WITH(model.predict(raw.Xt), message);
REQUIRE_THROWS_AS(model.predict_proba(raw.Xt), std::logic_error);
REQUIRE_THROWS_WITH(model.predict_proba(raw.Xt), message);
REQUIRE_THROWS_AS(model.score(raw.Xt, raw.yt), std::logic_error);
REQUIRE_THROWS_WITH(model.score(raw.Xt, raw.yt), message);
// vectors
REQUIRE_THROWS_AS(model.predict(raw.Xv), std::logic_error);
REQUIRE_THROWS_WITH(model.predict(raw.Xv), message);
REQUIRE_THROWS_AS(model.predict_proba(raw.Xv), std::logic_error);
REQUIRE_THROWS_WITH(model.predict_proba(raw.Xv), message);
REQUIRE_THROWS_AS(model.score(raw.Xv, raw.yv), std::logic_error);
REQUIRE_THROWS_WITH(model.score(raw.Xv, raw.yv), message);
}
TEST_CASE("KDB Graph", "[Classifier]")
{
auto model = bayesnet::KDB(2);
auto raw = RawDatasets("iris", true);
model.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto graph = model.graph();
REQUIRE(graph.size() == 15);
}
TEST_CASE("KDBLd Graph", "[Classifier]")
{
auto model = bayesnet::KDBLd(2);
auto raw = RawDatasets("iris", false);
model.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest);
auto graph = model.graph();
REQUIRE(graph.size() == 15);
}

126
tests/TestBayesEnsemble.cc Normal file
View File

@@ -0,0 +1,126 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp>
#include "bayesnet/ensembles/BoostAODE.h"
#include "bayesnet/ensembles/AODE.h"
#include "bayesnet/ensembles/AODELd.h"
#include "TestUtils.h"
TEST_CASE("Topological Order", "[Ensemble]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto order = clf.topological_order();
REQUIRE(order.size() == 0);
}
TEST_CASE("Dump CPT", "[Ensemble]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto dump = clf.dump_cpt();
REQUIRE(dump == "");
}
TEST_CASE("Number of States", "[Ensemble]")
{
auto clf = bayesnet::BoostAODE();
auto raw = RawDatasets("iris", true);
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfStates() == 76);
}
TEST_CASE("Show", "[Ensemble]")
{
auto clf = bayesnet::BoostAODE();
auto raw = RawDatasets("iris", true);
clf.setHyperparameters({
{"bisection", false},
{"maxTolerance", 1},
{"convergence", false},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
std::vector<std::string> expected = {
"class -> sepallength, sepalwidth, petallength, petalwidth, ",
"petallength -> sepallength, sepalwidth, petalwidth, ",
"petalwidth -> ",
"sepallength -> ",
"sepalwidth -> ",
"class -> sepallength, sepalwidth, petallength, petalwidth, ",
"petallength -> ",
"petalwidth -> sepallength, sepalwidth, petallength, ",
"sepallength -> ",
"sepalwidth -> ",
"class -> sepallength, sepalwidth, petallength, petalwidth, ",
"petallength -> ",
"petalwidth -> ",
"sepallength -> sepalwidth, petallength, petalwidth, ",
"sepalwidth -> ",
"class -> sepallength, sepalwidth, petallength, petalwidth, ",
"petallength -> ",
"petalwidth -> ",
"sepallength -> ",
"sepalwidth -> sepallength, petallength, petalwidth, ",
};
auto show = clf.show();
REQUIRE(show.size() == expected.size());
for (size_t i = 0; i < show.size(); i++)
REQUIRE(show[i] == expected[i]);
}
TEST_CASE("Graph", "[Ensemble]")
{
auto clf = bayesnet::BoostAODE();
auto raw = RawDatasets("iris", true);
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto graph = clf.graph();
REQUIRE(graph.size() == 56);
auto clf2 = bayesnet::AODE();
clf2.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
graph = clf2.graph();
REQUIRE(graph.size() == 56);
raw = RawDatasets("glass", false);
auto clf3 = bayesnet::AODELd();
clf3.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest);
graph = clf3.graph();
REQUIRE(graph.size() == 261);
}
TEST_CASE("Compute ArgMax", "[Ensemble]")
{
class TestEnsemble : public bayesnet::BoostAODE {
public:
TestEnsemble() : bayesnet::BoostAODE() {}
torch::Tensor compute_arg_max(torch::Tensor& X) { return Ensemble::compute_arg_max(X); }
std::vector<int> compute_arg_max(std::vector<std::vector<double>>& X) { return Ensemble::compute_arg_max(X); }
};
TestEnsemble clf;
std::vector<std::vector<double>> X = {
{0.1f, 0.2f, 0.3f},
{0.4f, 0.9f, 0.6f},
{0.7f, 0.8f, 0.9f},
{0.5f, 0.2f, 0.1f},
{0.3f, 0.7f, 0.2f},
{0.5f, 0.5f, 0.2f}
};
std::vector<int> expected = { 2, 1, 2, 0, 1, 0 };
auto argmax = clf.compute_arg_max(X);
REQUIRE(argmax.size() == expected.size());
REQUIRE(argmax == expected);
auto Xt = torch::zeros({ 6, 3 }, torch::kFloat32);
Xt[0][0] = 0.1f; Xt[0][1] = 0.2f; Xt[0][2] = 0.3f;
Xt[1][0] = 0.4f; Xt[1][1] = 0.9f; Xt[1][2] = 0.6f;
Xt[2][0] = 0.7f; Xt[2][1] = 0.8f; Xt[2][2] = 0.9f;
Xt[3][0] = 0.5f; Xt[3][1] = 0.2f; Xt[3][2] = 0.1f;
Xt[4][0] = 0.3f; Xt[4][1] = 0.7f; Xt[4][2] = 0.2f;
Xt[5][0] = 0.5f; Xt[5][1] = 0.5f; Xt[5][2] = 0.2f;
auto argmaxt = clf.compute_arg_max(Xt);
REQUIRE(argmaxt.size(0) == expected.size());
for (int i = 0; i < argmaxt.size(0); i++)
REQUIRE(argmaxt[i].item<int>() == expected[i]);
}

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp> #include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp> #include <catch2/generators/catch_generators.hpp>

View File

@@ -1,7 +1,14 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <type_traits> #include <type_traits>
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp> #include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp> #include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include "bayesnet/classifiers/KDB.h" #include "bayesnet/classifiers/KDB.h"
#include "bayesnet/classifiers/TAN.h" #include "bayesnet/classifiers/TAN.h"
#include "bayesnet/classifiers/SPODE.h" #include "bayesnet/classifiers/SPODE.h"
@@ -13,7 +20,7 @@
#include "bayesnet/ensembles/BoostAODE.h" #include "bayesnet/ensembles/BoostAODE.h"
#include "TestUtils.h" #include "TestUtils.h"
const std::string ACTUAL_VERSION = "1.0.4"; const std::string ACTUAL_VERSION = "1.0.4.1";
TEST_CASE("Test Bayesian Classifiers score & version", "[Models]") TEST_CASE("Test Bayesian Classifiers score & version", "[Models]")
{ {
@@ -51,6 +58,7 @@ TEST_CASE("Test Bayesian Classifiers score & version", "[Models]")
auto score = clf->score(raw.Xt, raw.yt); auto score = clf->score(raw.Xt, raw.yt);
INFO("Classifier: " + name + " File: " + file_name); INFO("Classifier: " + name + " File: " + file_name);
REQUIRE(score == Catch::Approx(scores[{file_name, name}]).epsilon(raw.epsilon)); REQUIRE(score == Catch::Approx(scores[{file_name, name}]).epsilon(raw.epsilon));
REQUIRE(clf->getStatus() == bayesnet::NORMAL);
} }
} }
SECTION("Library check version") SECTION("Library check version")
@@ -60,7 +68,7 @@ TEST_CASE("Test Bayesian Classifiers score & version", "[Models]")
} }
delete clf; delete clf;
} }
TEST_CASE("Models features", "[Models]") TEST_CASE("Models features & Graph", "[Models]")
{ {
auto graph = std::vector<std::string>({ "digraph BayesNet {\nlabel=<BayesNet Test>\nfontsize=30\nfontcolor=blue\nlabelloc=t\nlayout=circo\n", auto graph = std::vector<std::string>({ "digraph BayesNet {\nlabel=<BayesNet Test>\nfontsize=30\nfontcolor=blue\nlabelloc=t\nlayout=circo\n",
"class [shape=circle, fontcolor=red, fillcolor=lightblue, style=filled ] \n", "class [shape=circle, fontcolor=red, fillcolor=lightblue, style=filled ] \n",
@@ -69,15 +77,30 @@ TEST_CASE("Models features", "[Models]")
"sepallength -> sepalwidth", "sepalwidth [shape=circle] \n", "sepalwidth -> petalwidth", "}\n" "sepallength -> sepalwidth", "sepalwidth [shape=circle] \n", "sepalwidth -> petalwidth", "}\n"
} }
); );
auto raw = RawDatasets("iris", true); SECTION("Test TAN")
auto clf = bayesnet::TAN(); {
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv); auto raw = RawDatasets("iris", true);
REQUIRE(clf.getNumberOfNodes() == 5); auto clf = bayesnet::TAN();
REQUIRE(clf.getNumberOfEdges() == 7); clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfStates() == 19); REQUIRE(clf.getNumberOfNodes() == 5);
REQUIRE(clf.getClassNumStates() == 3); REQUIRE(clf.getNumberOfEdges() == 7);
REQUIRE(clf.show() == std::vector<std::string>{"class -> sepallength, sepalwidth, petallength, petalwidth, ", "petallength -> sepallength, ", "petalwidth -> ", "sepallength -> sepalwidth, ", "sepalwidth -> petalwidth, "}); REQUIRE(clf.getNumberOfStates() == 19);
REQUIRE(clf.graph("Test") == graph); REQUIRE(clf.getClassNumStates() == 3);
REQUIRE(clf.show() == std::vector<std::string>{"class -> sepallength, sepalwidth, petallength, petalwidth, ", "petallength -> sepallength, ", "petalwidth -> ", "sepallength -> sepalwidth, ", "sepalwidth -> petalwidth, "});
REQUIRE(clf.graph("Test") == graph);
}
SECTION("Test TANLd")
{
auto clf = bayesnet::TANLd();
auto raw = RawDatasets("iris", false);
clf.fit(raw.Xt, raw.yt, raw.featurest, raw.classNamet, raw.statest);
REQUIRE(clf.getNumberOfNodes() == 5);
REQUIRE(clf.getNumberOfEdges() == 7);
REQUIRE(clf.getNumberOfStates() == 19);
REQUIRE(clf.getClassNumStates() == 3);
REQUIRE(clf.show() == std::vector<std::string>{"class -> sepallength, sepalwidth, petallength, petalwidth, ", "petallength -> sepallength, ", "petalwidth -> ", "sepallength -> sepalwidth, ", "sepalwidth -> petalwidth, "});
REQUIRE(clf.graph("Test") == graph);
}
} }
TEST_CASE("Get num features & num edges", "[Models]") TEST_CASE("Get num features & num edges", "[Models]")
{ {
@@ -87,62 +110,7 @@ TEST_CASE("Get num features & num edges", "[Models]")
REQUIRE(clf.getNumberOfNodes() == 5); REQUIRE(clf.getNumberOfNodes() == 5);
REQUIRE(clf.getNumberOfEdges() == 8); REQUIRE(clf.getNumberOfEdges() == 8);
} }
TEST_CASE("BoostAODE feature_select CFS", "[Models]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({ {"select_features", "CFS"} });
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 90);
REQUIRE(clf.getNumberOfEdges() == 153);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 6 of 9 with CFS");
REQUIRE(clf.getNotes()[1] == "Number of models: 9");
}
TEST_CASE("BoostAODE feature_select IWSS", "[Models]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({ {"select_features", "IWSS"}, {"threshold", 0.5 } });
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 90);
REQUIRE(clf.getNumberOfEdges() == 153);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 5 of 9 with IWSS");
REQUIRE(clf.getNotes()[1] == "Number of models: 9");
}
TEST_CASE("BoostAODE feature_select FCBF", "[Models]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({ {"select_features", "FCBF"}, {"threshold", 1e-7 } });
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 90);
REQUIRE(clf.getNumberOfEdges() == 153);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 5 of 9 with FCBF");
REQUIRE(clf.getNotes()[1] == "Number of models: 9");
}
TEST_CASE("BoostAODE test used features in train note and score", "[Models]")
{
auto raw = RawDatasets("diabetes", true);
auto clf = bayesnet::BoostAODE(true);
clf.setHyperparameters({
{"order", "asc"},
{"convergence", true},
{"select_features","CFS"},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 72);
REQUIRE(clf.getNumberOfEdges() == 120);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 6 of 8 with CFS");
REQUIRE(clf.getNotes()[1] == "Number of models: 8");
auto score = clf.score(raw.Xv, raw.yv);
auto scoret = clf.score(raw.Xt, raw.yt);
REQUIRE(score == Catch::Approx(0.80078).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(0.80078).epsilon(raw.epsilon));
}
TEST_CASE("Model predict_proba", "[Models]") TEST_CASE("Model predict_proba", "[Models]")
{ {
std::string model = GENERATE("TAN", "SPODE", "BoostAODEproba", "BoostAODEvoting"); std::string model = GENERATE("TAN", "SPODE", "BoostAODEproba", "BoostAODEvoting");
@@ -169,15 +137,15 @@ TEST_CASE("Model predict_proba", "[Models]")
{0.003135, 0.991799, 0.0050661} {0.003135, 0.991799, 0.0050661}
}); });
auto res_prob_baode = std::vector<std::vector<double>>({ auto res_prob_baode = std::vector<std::vector<double>>({
{0.00803291, 0.9676, 0.0243672}, {0.0112349, 0.962274, 0.0264907},
{0.00398714, 0.945126, 0.050887}, {0.00371025, 0.950592, 0.0456973},
{0.00398714, 0.945126, 0.050887}, {0.00371025, 0.950592, 0.0456973},
{0.00398714, 0.945126, 0.050887}, {0.00371025, 0.950592, 0.0456973},
{0.00189227, 0.859575, 0.138533}, {0.00369275, 0.84967, 0.146637},
{0.0118341, 0.442149, 0.546017}, {0.0252205, 0.113564, 0.861215},
{0.0216135, 0.785781, 0.192605}, {0.0284828, 0.770524, 0.200993},
{0.0204803, 0.844276, 0.135244}, {0.0213182, 0.857189, 0.121493},
{0.00576313, 0.961665, 0.0325716}, {0.00868436, 0.949494, 0.0418215}
}); });
auto res_prob_voting = std::vector<std::vector<double>>({ auto res_prob_voting = std::vector<std::vector<double>>({
{0, 1, 0}, {0, 1, 0},
@@ -185,8 +153,8 @@ TEST_CASE("Model predict_proba", "[Models]")
{0, 1, 0}, {0, 1, 0},
{0, 1, 0}, {0, 1, 0},
{0, 1, 0}, {0, 1, 0},
{0, 0.447909, 0.552091}, {0, 0, 1},
{0, 0.811482, 0.188517}, {0, 1, 0},
{0, 1, 0}, {0, 1, 0},
{0, 1, 0} {0, 1, 0}
}); });
@@ -209,7 +177,7 @@ TEST_CASE("Model predict_proba", "[Models]")
REQUIRE(y_pred.size() == raw.yv.size()); REQUIRE(y_pred.size() == raw.yv.size());
REQUIRE(y_pred_proba[0].size() == 3); REQUIRE(y_pred_proba[0].size() == 3);
REQUIRE(yt_pred_proba.size(1) == y_pred_proba[0].size()); REQUIRE(yt_pred_proba.size(1) == y_pred_proba[0].size());
for (int i = 0; i < y_pred_proba.size(); ++i) { for (int i = 0; i < 9; ++i) {
auto maxElem = max_element(y_pred_proba[i].begin(), y_pred_proba[i].end()); auto maxElem = max_element(y_pred_proba[i].begin(), y_pred_proba[i].end());
int predictedClass = distance(y_pred_proba[i].begin(), maxElem); int predictedClass = distance(y_pred_proba[i].begin(), maxElem);
REQUIRE(predictedClass == y_pred[i]); REQUIRE(predictedClass == y_pred[i]);
@@ -220,7 +188,7 @@ TEST_CASE("Model predict_proba", "[Models]")
} }
} }
// Check predict_proba values for vectors and tensors // Check predict_proba values for vectors and tensors
for (int i = 0; i < res_prob.size(); i++) { for (int i = 0; i < 9; i++) {
REQUIRE(y_pred[i] == yt_pred[i].item<int>()); REQUIRE(y_pred[i] == yt_pred[i].item<int>());
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
REQUIRE(res_prob[model][i][j] == Catch::Approx(y_pred_proba[i + init_index][j]).epsilon(raw.epsilon)); REQUIRE(res_prob[model][i][j] == Catch::Approx(y_pred_proba[i + init_index][j]).epsilon(raw.epsilon));
@@ -230,25 +198,7 @@ TEST_CASE("Model predict_proba", "[Models]")
delete clf; delete clf;
} }
} }
TEST_CASE("BoostAODE voting-proba", "[Models]")
{
auto raw = RawDatasets("iris", true);
auto clf = bayesnet::BoostAODE(false);
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto score_proba = clf.score(raw.Xv, raw.yv);
auto pred_proba = clf.predict_proba(raw.Xv);
clf.setHyperparameters({
{"predict_voting",true},
});
auto score_voting = clf.score(raw.Xv, raw.yv);
auto pred_voting = clf.predict_proba(raw.Xv);
REQUIRE(score_proba == Catch::Approx(0.97333).epsilon(raw.epsilon));
REQUIRE(score_voting == Catch::Approx(0.98).epsilon(raw.epsilon));
REQUIRE(pred_voting[83][2] == Catch::Approx(0.552091).epsilon(raw.epsilon));
REQUIRE(pred_proba[83][2] == Catch::Approx(0.546017).epsilon(raw.epsilon));
clf.dump_cpt();
REQUIRE(clf.topological_order() == std::vector<std::string>());
}
TEST_CASE("AODE voting-proba", "[Models]") TEST_CASE("AODE voting-proba", "[Models]")
{ {
auto raw = RawDatasets("glass", true); auto raw = RawDatasets("glass", true);
@@ -294,22 +244,27 @@ TEST_CASE("KDB with hyperparameters", "[Models]")
REQUIRE(score == Catch::Approx(0.827103).epsilon(raw.epsilon)); REQUIRE(score == Catch::Approx(0.827103).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(0.761682).epsilon(raw.epsilon)); REQUIRE(scoret == Catch::Approx(0.761682).epsilon(raw.epsilon));
} }
TEST_CASE("BoostAODE order asc, desc & random", "[Models]") TEST_CASE("Incorrect type of data for SPODELd", "[Models]")
{ {
auto raw = RawDatasets("glass", true); auto raw = RawDatasets("iris", true);
std::map<std::string, double> scores{ auto clf = bayesnet::SPODELd(0);
{"asc", 0.83645f }, { "desc", 0.84579f }, { "rand", 0.84112 } REQUIRE_THROWS_AS(clf.fit(raw.dataset, raw.featurest, raw.classNamet, raw.statest), std::runtime_error);
};
for (const std::string& order : { "asc", "desc", "rand" }) {
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({
{"order", order},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto score = clf.score(raw.Xv, raw.yv);
auto scoret = clf.score(raw.Xt, raw.yt);
INFO("BoostAODE order: " + order);
REQUIRE(score == Catch::Approx(scores[order]).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(scores[order]).epsilon(raw.epsilon));
}
} }
TEST_CASE("Predict, predict_proba & score without fitting", "[Models]")
{
auto clf = bayesnet::AODE();
auto raw = RawDatasets("iris", true);
std::string message = "Ensemble has not been fitted";
REQUIRE_THROWS_AS(clf.predict(raw.Xv), std::logic_error);
REQUIRE_THROWS_AS(clf.predict_proba(raw.Xv), std::logic_error);
REQUIRE_THROWS_AS(clf.predict(raw.Xt), std::logic_error);
REQUIRE_THROWS_AS(clf.predict_proba(raw.Xt), std::logic_error);
REQUIRE_THROWS_AS(clf.score(raw.Xv, raw.yv), std::logic_error);
REQUIRE_THROWS_AS(clf.score(raw.Xt, raw.yt), std::logic_error);
REQUIRE_THROWS_WITH(clf.predict(raw.Xv), message);
REQUIRE_THROWS_WITH(clf.predict_proba(raw.Xv), message);
REQUIRE_THROWS_WITH(clf.predict(raw.Xt), message);
REQUIRE_THROWS_WITH(clf.predict_proba(raw.Xt), message);
REQUIRE_THROWS_WITH(clf.score(raw.Xv, raw.yv), message);
REQUIRE_THROWS_WITH(clf.score(raw.Xt, raw.yt), message);
}

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp> #include <catch2/catch_approx.hpp>

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp> #include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp> #include <catch2/generators/catch_generators.hpp>

184
tests/TestBoostAODE.cc Normal file
View File

@@ -0,0 +1,184 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp>
#include "bayesnet/ensembles/BoostAODE.h"
#include "TestUtils.h"
TEST_CASE("Feature_select CFS", "[BoostAODE]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({ {"select_features", "CFS"} });
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 90);
REQUIRE(clf.getNumberOfEdges() == 153);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 6 of 9 with CFS");
REQUIRE(clf.getNotes()[1] == "Number of models: 9");
}
TEST_CASE("Feature_select IWSS", "[BoostAODE]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({ {"select_features", "IWSS"}, {"threshold", 0.5 } });
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 90);
REQUIRE(clf.getNumberOfEdges() == 153);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 4 of 9 with IWSS");
REQUIRE(clf.getNotes()[1] == "Number of models: 9");
}
TEST_CASE("Feature_select FCBF", "[BoostAODE]")
{
auto raw = RawDatasets("glass", true);
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({ {"select_features", "FCBF"}, {"threshold", 1e-7 } });
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 90);
REQUIRE(clf.getNumberOfEdges() == 153);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 5 of 9 with FCBF");
REQUIRE(clf.getNotes()[1] == "Number of models: 9");
}
TEST_CASE("Test used features in train note and score", "[BoostAODE]")
{
auto raw = RawDatasets("diabetes", true);
auto clf = bayesnet::BoostAODE(true);
clf.setHyperparameters({
{"order", "asc"},
{"convergence", true},
{"select_features","CFS"},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 72);
REQUIRE(clf.getNumberOfEdges() == 120);
REQUIRE(clf.getNotes().size() == 2);
REQUIRE(clf.getNotes()[0] == "Used features in initialization: 6 of 8 with CFS");
REQUIRE(clf.getNotes()[1] == "Number of models: 8");
auto score = clf.score(raw.Xv, raw.yv);
auto scoret = clf.score(raw.Xt, raw.yt);
REQUIRE(score == Catch::Approx(0.80078).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(0.80078).epsilon(raw.epsilon));
}
TEST_CASE("Voting vs proba", "[BoostAODE]")
{
auto raw = RawDatasets("iris", true);
auto clf = bayesnet::BoostAODE(false);
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto score_proba = clf.score(raw.Xv, raw.yv);
auto pred_proba = clf.predict_proba(raw.Xv);
clf.setHyperparameters({
{"predict_voting",true},
});
auto score_voting = clf.score(raw.Xv, raw.yv);
auto pred_voting = clf.predict_proba(raw.Xv);
REQUIRE(score_proba == Catch::Approx(0.97333).epsilon(raw.epsilon));
REQUIRE(score_voting == Catch::Approx(0.98).epsilon(raw.epsilon));
REQUIRE(pred_voting[83][2] == Catch::Approx(1.0).epsilon(raw.epsilon));
REQUIRE(pred_proba[83][2] == Catch::Approx(0.86121525).epsilon(raw.epsilon));
REQUIRE(clf.dump_cpt() == "");
REQUIRE(clf.topological_order() == std::vector<std::string>());
}
TEST_CASE("Order asc, desc & random", "[BoostAODE]")
{
auto raw = RawDatasets("glass", true);
std::map<std::string, double> scores{
{"asc", 0.83645f }, { "desc", 0.84579f }, { "rand", 0.84112 }
};
for (const std::string& order : { "asc", "desc", "rand" }) {
auto clf = bayesnet::BoostAODE();
clf.setHyperparameters({
{"order", order},
{"bisection", false},
{"maxTolerance", 1},
{"convergence", false},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
auto score = clf.score(raw.Xv, raw.yv);
auto scoret = clf.score(raw.Xt, raw.yt);
INFO("BoostAODE order: " + order);
REQUIRE(score == Catch::Approx(scores[order]).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(scores[order]).epsilon(raw.epsilon));
}
}
TEST_CASE("Oddities", "[BoostAODE]")
{
auto clf = bayesnet::BoostAODE();
auto raw = RawDatasets("iris", true);
auto bad_hyper = nlohmann::json{
{ { "order", "duck" } },
{ { "select_features", "duck" } },
{ { "maxTolerance", 0 } },
{ { "maxTolerance", 5 } },
};
for (const auto& hyper : bad_hyper.items()) {
INFO("BoostAODE hyper: " + hyper.value().dump());
REQUIRE_THROWS_AS(clf.setHyperparameters(hyper.value()), std::invalid_argument);
}
REQUIRE_THROWS_AS(clf.setHyperparameters({ {"maxTolerance", 0 } }), std::invalid_argument);
auto bad_hyper_fit = nlohmann::json{
{ { "select_features","IWSS" }, { "threshold", -0.01 } },
{ { "select_features","IWSS" }, { "threshold", 0.51 } },
{ { "select_features","FCBF" }, { "threshold", 1e-8 } },
{ { "select_features","FCBF" }, { "threshold", 1.01 } },
};
for (const auto& hyper : bad_hyper_fit.items()) {
INFO("BoostAODE hyper: " + hyper.value().dump());
clf.setHyperparameters(hyper.value());
REQUIRE_THROWS_AS(clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv), std::invalid_argument);
}
}
TEST_CASE("Bisection", "[BoostAODE]")
{
auto clf = bayesnet::BoostAODE();
auto raw = RawDatasets("mfeat-factors", true);
clf.setHyperparameters({
{"bisection", true},
{"maxTolerance", 3},
{"convergence", true},
{"block_update", false},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 217);
REQUIRE(clf.getNumberOfEdges() == 431);
REQUIRE(clf.getNotes().size() == 3);
REQUIRE(clf.getNotes()[0] == "Convergence threshold reached & 15 models eliminated");
REQUIRE(clf.getNotes()[1] == "Used features in train: 16 of 216");
REQUIRE(clf.getNotes()[2] == "Number of models: 1");
auto score = clf.score(raw.Xv, raw.yv);
auto scoret = clf.score(raw.Xt, raw.yt);
REQUIRE(score == Catch::Approx(1.0f).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(1.0f).epsilon(raw.epsilon));
}
TEST_CASE("Block Update", "[BoostAODE]")
{
auto clf = bayesnet::BoostAODE();
auto raw = RawDatasets("mfeat-factors", true);
clf.setHyperparameters({
{"bisection", true},
{"block_update", true},
{"maxTolerance", 3},
{"convergence", true},
});
clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv);
REQUIRE(clf.getNumberOfNodes() == 217);
REQUIRE(clf.getNumberOfEdges() == 431);
REQUIRE(clf.getNotes().size() == 3);
REQUIRE(clf.getNotes()[0] == "Convergence threshold reached & 15 models eliminated");
REQUIRE(clf.getNotes()[1] == "Used features in train: 16 of 216");
REQUIRE(clf.getNotes()[2] == "Number of models: 1");
auto score = clf.score(raw.Xv, raw.yv);
auto scoret = clf.score(raw.Xt, raw.yt);
REQUIRE(score == Catch::Approx(1.0f).epsilon(raw.epsilon));
REQUIRE(scoret == Catch::Approx(1.0f).epsilon(raw.epsilon));
}

View File

@@ -1,6 +1,13 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp> #include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp> #include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include "bayesnet/utils/BayesMetrics.h" #include "bayesnet/utils/BayesMetrics.h"
#include "bayesnet/feature_selection/CFS.h" #include "bayesnet/feature_selection/CFS.h"
#include "bayesnet/feature_selection/FCBF.h" #include "bayesnet/feature_selection/FCBF.h"
@@ -68,4 +75,15 @@ TEST_CASE("Features Selected", "[FeatureSelection]")
delete featureSelector; delete featureSelector;
} }
} }
}
TEST_CASE("Oddities", "[FeatureSelection]")
{
auto raw = RawDatasets("iris", true);
// FCBF Limits
REQUIRE_THROWS_AS(bayesnet::FCBF(raw.dataset, raw.featuresv, raw.classNamev, raw.featuresv.size(), raw.classNumStates, raw.weights, 1e-8), std::invalid_argument);
REQUIRE_THROWS_WITH(bayesnet::FCBF(raw.dataset, raw.featuresv, raw.classNamev, raw.featuresv.size(), raw.classNumStates, raw.weights, 1e-8), "Threshold cannot be less than 1e-7");
REQUIRE_THROWS_AS(bayesnet::IWSS(raw.dataset, raw.featuresv, raw.classNamev, raw.featuresv.size(), raw.classNumStates, raw.weights, -1e4), std::invalid_argument);
REQUIRE_THROWS_WITH(bayesnet::IWSS(raw.dataset, raw.featuresv, raw.classNamev, raw.featuresv.size(), raw.classNumStates, raw.weights, -1e4), "Threshold has to be in [0, 0.5]");
REQUIRE_THROWS_AS(bayesnet::IWSS(raw.dataset, raw.featuresv, raw.classNamev, raw.featuresv.size(), raw.classNumStates, raw.weights, 0.501), std::invalid_argument);
REQUIRE_THROWS_WITH(bayesnet::IWSS(raw.dataset, raw.featuresv, raw.classNamev, raw.featuresv.size(), raw.classNumStates, raw.weights, 0.501), "Threshold has to be in [0, 0.5]");
} }

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#include "TestUtils.h" #include "TestUtils.h"
#include "bayesnet/config.h" #include "bayesnet/config.h"

View File

@@ -1,3 +1,9 @@
// ***************************************************************
// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: MIT
// ***************************************************************
#ifndef TEST_UTILS_H #ifndef TEST_UTILS_H
#define TEST_UTILS_H #define TEST_UTILS_H
#include <torch/torch.h> #include <torch/torch.h>

File diff suppressed because it is too large Load Diff

35
update_coverage.py Normal file
View File

@@ -0,0 +1,35 @@
# ***************************************************************
# SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: MIT
# ***************************************************************
import subprocess
import sys
readme_file = "README.md"
print("Updating coverage...")
# Generate badge line
output = subprocess.check_output(
"lcov --summary " + sys.argv[1] + "/coverage.info|cut -d' ' -f4 |head -2|"
"tail -1",
shell=True,
)
value = float(output.decode("utf-8").strip().replace("%", ""))
if value < 90:
print("⛔Coverage is less than 90%. I won't update the badge.")
sys.exit(1)
percentage = output.decode("utf-8").strip().replace(".", ",")
coverage_line = (
f"![Static Badge](https://img.shields.io/badge/Coverage-{percentage}25-green)"
)
# Update README.md
with open(readme_file, "r") as f:
lines = f.readlines()
with open(readme_file, "w") as f:
for line in lines:
if "Coverage" in line:
f.write(coverage_line + "\n")
else:
f.write(line)
print(f"✅Coverage updated with value: {percentage}")