Merge pull request 'Including XA1DE model' (#5) from XA1DE into main
Reviewed-on: #5
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -41,3 +41,4 @@ puml/**
|
||||
*.dot
|
||||
diagrams/html/**
|
||||
diagrams/latex/**
|
||||
.cache
|
||||
|
9
Makefile
9
Makefile
@@ -37,6 +37,11 @@ setup: ## Install dependencies for tests and coverage
|
||||
pip install gcovr; \
|
||||
fi
|
||||
|
||||
dest ?= ${HOME}/bin
|
||||
main: ## Build only the b_main target
|
||||
@cmake --build $(f_release) -t b_main --parallel
|
||||
@cp $(f_release)/src/b_main $(dest)
|
||||
|
||||
dest ?= ${HOME}/bin
|
||||
install: ## Copy binary files to bin folder
|
||||
@echo "Destination folder: $(dest)"
|
||||
@@ -98,8 +103,8 @@ test: ## Run tests (opt="-s") to verbose output the tests, (opt="-c='Test Maximu
|
||||
fname = iris
|
||||
example: ## Build sample
|
||||
@echo ">>> Building Sample...";
|
||||
@cmake --build build_debug -t sample
|
||||
build_debug/sample/PlatformSample --model BoostAODE --dataset $(fname) --discretize --stratified
|
||||
@cmake --build $(f_release) -t sample
|
||||
$(f_release)/sample/PlatformSample --model BoostAODE --dataset $(fname) --discretize --stratified
|
||||
@echo ">>> Done";
|
||||
|
||||
|
||||
|
2009
lib/log/loguru.cpp
Normal file
2009
lib/log/loguru.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1475
lib/log/loguru.hpp
Normal file
1475
lib/log/loguru.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
||||
#include <fimdlp/CPPFImdlp.h>
|
||||
#include <folding.hpp>
|
||||
#include <bayesnet/utils/BayesMetrics.h>
|
||||
#include <bayesnet/classifiers/SPODE.h>
|
||||
#include "Models.h"
|
||||
#include "modelRegister.h"
|
||||
#include "config_platform.h"
|
||||
@@ -160,82 +161,119 @@ int main(int argc, char** argv)
|
||||
states[feature] = std::vector<int>(maxes[feature]);
|
||||
}
|
||||
states[className] = std::vector<int>(maxes[className]);
|
||||
auto clf = platform::Models::instance()->create(model_name);
|
||||
// Output the states
|
||||
std::cout << std::string(80, '-') << std::endl;
|
||||
std::cout << "States" << std::endl;
|
||||
for (auto feature : features) {
|
||||
std::cout << feature << ": " << states[feature].size() << std::endl;
|
||||
}
|
||||
std::cout << std::string(80, '-') << std::endl;
|
||||
//auto clf = platform::Models::instance()->create("SPODE");
|
||||
auto clf = bayesnet::SPODE(2);
|
||||
|
||||
bayesnet::Smoothing_t smoothing = bayesnet::Smoothing_t::ORIGINAL;
|
||||
clf->fit(Xd, y, features, className, states, smoothing);
|
||||
clf.fit(Xd, y, features, className, states, smoothing);
|
||||
if (dump_cpt) {
|
||||
std::cout << "--- CPT Tables ---" << std::endl;
|
||||
clf->dump_cpt();
|
||||
std::cout << clf.dump_cpt();
|
||||
}
|
||||
auto lines = clf->show();
|
||||
std::cout << "--- Datos predicción ---" << std::endl;
|
||||
std::cout << "Orden de variables: " << std::endl;
|
||||
for (auto feature : features) {
|
||||
std::cout << feature << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << "X[0]: ";
|
||||
for (int i = 0; i < Xd.size(); ++i) {
|
||||
std::cout << Xd[i][0] << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << std::string(80, '-') << std::endl;
|
||||
|
||||
auto lines = clf.show();
|
||||
for (auto line : lines) {
|
||||
std::cout << line << std::endl;
|
||||
}
|
||||
std::cout << "--- Topological Order ---" << std::endl;
|
||||
auto order = clf->topological_order();
|
||||
auto order = clf.topological_order();
|
||||
for (auto name : order) {
|
||||
std::cout << name << ", ";
|
||||
}
|
||||
std::cout << "end." << std::endl;
|
||||
auto score = clf->score(Xd, y);
|
||||
std::cout << "Score: " << score << std::endl;
|
||||
auto graph = clf->graph();
|
||||
auto dot_file = model_name + "_" + file_name;
|
||||
ofstream file(dot_file + ".dot");
|
||||
file << graph;
|
||||
file.close();
|
||||
std::cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << std::endl;
|
||||
std::cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << std::endl;
|
||||
std::string stratified_string = stratified ? " Stratified" : "";
|
||||
std::cout << nFolds << " Folds" << stratified_string << " Cross validation" << std::endl;
|
||||
std::cout << "==========================================" << std::endl;
|
||||
torch::Tensor Xt = torch::zeros({ static_cast<int>(Xd.size()), static_cast<int>(Xd[0].size()) }, torch::kInt32);
|
||||
torch::Tensor yt = torch::tensor(y, torch::kInt32);
|
||||
for (int i = 0; i < features.size(); ++i) {
|
||||
Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], torch::kInt32));
|
||||
}
|
||||
float total_score = 0, total_score_train = 0, score_train, score_test;
|
||||
folding::Fold* fold;
|
||||
double nodes = 0.0;
|
||||
if (stratified)
|
||||
fold = new folding::StratifiedKFold(nFolds, y, seed);
|
||||
else
|
||||
fold = new folding::KFold(nFolds, y.size(), seed);
|
||||
for (auto i = 0; i < nFolds; ++i) {
|
||||
auto [train, test] = fold->getFold(i);
|
||||
std::cout << "Fold: " << i + 1 << std::endl;
|
||||
if (tensors) {
|
||||
auto ttrain = torch::tensor(train, torch::kInt64);
|
||||
auto ttest = torch::tensor(test, torch::kInt64);
|
||||
torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain);
|
||||
torch::Tensor ytraint = yt.index({ ttrain });
|
||||
torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest);
|
||||
torch::Tensor ytestt = yt.index({ ttest });
|
||||
clf->fit(Xtraint, ytraint, features, className, states, smoothing);
|
||||
auto temp = clf->predict(Xtraint);
|
||||
score_train = clf->score(Xtraint, ytraint);
|
||||
score_test = clf->score(Xtestt, ytestt);
|
||||
} else {
|
||||
auto [Xtrain, ytrain] = extract_indices(train, Xd, y);
|
||||
auto [Xtest, ytest] = extract_indices(test, Xd, y);
|
||||
clf->fit(Xtrain, ytrain, features, className, states, smoothing);
|
||||
std::cout << "Nodes: " << clf->getNumberOfNodes() << std::endl;
|
||||
nodes += clf->getNumberOfNodes();
|
||||
score_train = clf->score(Xtrain, ytrain);
|
||||
score_test = clf->score(Xtest, ytest);
|
||||
auto predict_proba = clf.predict_proba(Xd);
|
||||
std::cout << "Instances predict_proba: ";
|
||||
for (int i = 0; i < predict_proba.size(); i++) {
|
||||
std::cout << "Instance " << i << ": ";
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
std::cout << Xd[j][i] << ", ";
|
||||
}
|
||||
if (dump_cpt) {
|
||||
std::cout << "--- CPT Tables ---" << std::endl;
|
||||
std::cout << clf->dump_cpt();
|
||||
std::cout << ": ";
|
||||
for (auto score : predict_proba[i]) {
|
||||
std::cout << score << ", ";
|
||||
}
|
||||
total_score_train += score_train;
|
||||
total_score += score_test;
|
||||
std::cout << "Score Train: " << score_train << std::endl;
|
||||
std::cout << "Score Test : " << score_test << std::endl;
|
||||
std::cout << "-------------------------------------------------------------------------------" << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << "Nodes: " << nodes / nFolds << std::endl;
|
||||
std::cout << "**********************************************************************************" << std::endl;
|
||||
std::cout << "Average Score Train: " << total_score_train / nFolds << std::endl;
|
||||
std::cout << "Average Score Test : " << total_score / nFolds << std::endl;return 0;
|
||||
// std::cout << std::endl;
|
||||
// std::cout << "end." << std::endl;
|
||||
// auto score = clf->score(Xd, y);
|
||||
// std::cout << "Score: " << score << std::endl;
|
||||
// auto graph = clf->graph();
|
||||
// auto dot_file = model_name + "_" + file_name;
|
||||
// ofstream file(dot_file + ".dot");
|
||||
// file << graph;
|
||||
// file.close();
|
||||
// std::cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << std::endl;
|
||||
// std::cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << std::endl;
|
||||
// std::string stratified_string = stratified ? " Stratified" : "";
|
||||
// std::cout << nFolds << " Folds" << stratified_string << " Cross validation" << std::endl;
|
||||
// std::cout << "==========================================" << std::endl;
|
||||
// torch::Tensor Xt = torch::zeros({ static_cast<int>(Xd.size()), static_cast<int>(Xd[0].size()) }, torch::kInt32);
|
||||
// torch::Tensor yt = torch::tensor(y, torch::kInt32);
|
||||
// for (int i = 0; i < features.size(); ++i) {
|
||||
// Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], torch::kInt32));
|
||||
// }
|
||||
// float total_score = 0, total_score_train = 0, score_train, score_test;
|
||||
// folding::Fold* fold;
|
||||
// double nodes = 0.0;
|
||||
// if (stratified)
|
||||
// fold = new folding::StratifiedKFold(nFolds, y, seed);
|
||||
// else
|
||||
// fold = new folding::KFold(nFolds, y.size(), seed);
|
||||
// for (auto i = 0; i < nFolds; ++i) {
|
||||
// auto [train, test] = fold->getFold(i);
|
||||
// std::cout << "Fold: " << i + 1 << std::endl;
|
||||
// if (tensors) {
|
||||
// auto ttrain = torch::tensor(train, torch::kInt64);
|
||||
// auto ttest = torch::tensor(test, torch::kInt64);
|
||||
// torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain);
|
||||
// torch::Tensor ytraint = yt.index({ ttrain });
|
||||
// torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest);
|
||||
// torch::Tensor ytestt = yt.index({ ttest });
|
||||
// clf->fit(Xtraint, ytraint, features, className, states, smoothing);
|
||||
// auto temp = clf->predict(Xtraint);
|
||||
// score_train = clf->score(Xtraint, ytraint);
|
||||
// score_test = clf->score(Xtestt, ytestt);
|
||||
// } else {
|
||||
// auto [Xtrain, ytrain] = extract_indices(train, Xd, y);
|
||||
// auto [Xtest, ytest] = extract_indices(test, Xd, y);
|
||||
// clf->fit(Xtrain, ytrain, features, className, states, smoothing);
|
||||
// std::cout << "Nodes: " << clf->getNumberOfNodes() << std::endl;
|
||||
// nodes += clf->getNumberOfNodes();
|
||||
// score_train = clf->score(Xtrain, ytrain);
|
||||
// score_test = clf->score(Xtest, ytest);
|
||||
// }
|
||||
// // if (dump_cpt) {
|
||||
// // std::cout << "--- CPT Tables ---" << std::endl;
|
||||
// // std::cout << clf->dump_cpt();
|
||||
// // }
|
||||
// total_score_train += score_train;
|
||||
// total_score += score_test;
|
||||
// std::cout << "Score Train: " << score_train << std::endl;
|
||||
// std::cout << "Score Test : " << score_test << std::endl;
|
||||
// std::cout << "-------------------------------------------------------------------------------" << std::endl;
|
||||
// }
|
||||
|
||||
// std::cout << "Nodes: " << nodes / nFolds << std::endl;
|
||||
// std::cout << "**********************************************************************************" << std::endl;
|
||||
// std::cout << "Average Score Train: " << total_score_train / nFolds << std::endl;
|
||||
// std::cout << "Average Score Test : " << total_score / nFolds << std::endl;return 0;
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
include_directories(
|
||||
## Libs
|
||||
${Platform_SOURCE_DIR}/lib/log
|
||||
${Platform_SOURCE_DIR}/lib/Files
|
||||
${Platform_SOURCE_DIR}/lib/folding
|
||||
${Platform_SOURCE_DIR}/lib/mdlp/src
|
||||
@@ -25,6 +26,8 @@ add_executable(
|
||||
main/Models.cpp main/Scores.cpp
|
||||
reports/ReportExcel.cpp reports/ReportBase.cpp reports/ExcelFile.cpp
|
||||
results/Result.cpp
|
||||
experimental_clfs/XA1DE.cpp
|
||||
experimental_clfs/ExpClf.cpp
|
||||
)
|
||||
target_link_libraries(b_best Boost::boost "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy "${XLSXWRITER_LIB}")
|
||||
|
||||
@@ -36,6 +39,8 @@ add_executable(b_grid commands/b_grid.cpp ${grid_sources}
|
||||
main/HyperParameters.cpp main/Models.cpp main/Experiment.cpp main/Scores.cpp main/ArgumentsExperiment.cpp
|
||||
reports/ReportConsole.cpp reports/ReportBase.cpp
|
||||
results/Result.cpp
|
||||
experimental_clfs/XA1DE.cpp
|
||||
experimental_clfs/ExpClf.cpp
|
||||
)
|
||||
target_link_libraries(b_grid ${MPI_CXX_LIBRARIES} "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy)
|
||||
|
||||
@@ -45,6 +50,8 @@ add_executable(b_list commands/b_list.cpp
|
||||
main/Models.cpp main/Scores.cpp
|
||||
reports/ReportExcel.cpp reports/ExcelFile.cpp reports/ReportBase.cpp reports/DatasetsExcel.cpp reports/DatasetsConsole.cpp reports/ReportsPaged.cpp
|
||||
results/Result.cpp results/ResultsDatasetExcel.cpp results/ResultsDataset.cpp results/ResultsDatasetConsole.cpp
|
||||
experimental_clfs/XA1DE.cpp
|
||||
experimental_clfs/ExpClf.cpp
|
||||
)
|
||||
target_link_libraries(b_list "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy "${XLSXWRITER_LIB}")
|
||||
|
||||
@@ -55,6 +62,8 @@ add_executable(b_main commands/b_main.cpp ${main_sources}
|
||||
common/Datasets.cpp common/Dataset.cpp common/Discretization.cpp
|
||||
reports/ReportConsole.cpp reports/ReportBase.cpp
|
||||
results/Result.cpp
|
||||
experimental_clfs/XA1DE.cpp
|
||||
experimental_clfs/ExpClf.cpp
|
||||
)
|
||||
target_link_libraries(b_main "${PyClassifiers}" "${BayesNet}" fimdlp ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::python Boost::numpy)
|
||||
|
||||
|
@@ -1,14 +1,12 @@
|
||||
#include <iostream>
|
||||
#include <argparse/argparse.hpp>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <mpi.h>
|
||||
#include "main/Models.h"
|
||||
#include "main/modelRegister.h"
|
||||
#include "main/ArgumentsExperiment.h"
|
||||
#include "common/Paths.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/Timer.hpp"
|
||||
#include "common/Colors.h"
|
||||
#include "common/DotEnv.h"
|
||||
#include "grid/GridSearch.h"
|
||||
|
@@ -51,6 +51,66 @@ void handleResize(int sig)
|
||||
manager->updateSize(rows, cols);
|
||||
}
|
||||
|
||||
void openFile(const std::string& fileName)
|
||||
{
|
||||
// #ifdef __APPLE__
|
||||
// // macOS uses the "open" command
|
||||
// std::string command = "open";
|
||||
// #elif defined(__linux__)
|
||||
// // Linux typically uses "xdg-open"
|
||||
// std::string command = "xdg-open";
|
||||
// #else
|
||||
// // For other OSes, do nothing or handle differently
|
||||
// std::cerr << "Unsupported platform." << std::endl;
|
||||
// return;
|
||||
// #endif
|
||||
// execlp(command.c_str(), command.c_str(), fileName.c_str(), NULL);
|
||||
#ifdef __APPLE__
|
||||
const char* tool = "/usr/bin/open";
|
||||
#elif defined(__linux__)
|
||||
const char* tool = "/usr/bin/xdg-open";
|
||||
#else
|
||||
std::cerr << "Unsupported platform." << std::endl;
|
||||
return;
|
||||
#endif
|
||||
|
||||
// We'll build an argv array for execve:
|
||||
std::vector<char*> argv;
|
||||
argv.push_back(const_cast<char*>(tool)); // argv[0]
|
||||
argv.push_back(const_cast<char*>(fileName.c_str())); // argv[1]
|
||||
argv.push_back(nullptr);
|
||||
|
||||
// Make a new environment array, skipping BASH_FUNC_ variables
|
||||
std::vector<std::string> filteredEnv;
|
||||
for (char** env = environ; *env != nullptr; ++env) {
|
||||
// *env is a string like "NAME=VALUE"
|
||||
// We want to skip those starting with "BASH_FUNC_"
|
||||
if (strncmp(*env, "BASH_FUNC_", 10) == 0) {
|
||||
// skip it
|
||||
continue;
|
||||
}
|
||||
filteredEnv.push_back(*env);
|
||||
}
|
||||
|
||||
// Convert filteredEnv into a char* array
|
||||
std::vector<char*> envp;
|
||||
for (auto& var : filteredEnv) {
|
||||
envp.push_back(const_cast<char*>(var.c_str()));
|
||||
}
|
||||
envp.push_back(nullptr);
|
||||
|
||||
// Now call execve with the cleaned environment
|
||||
// NOTE: You may need a full path to the tool if it's not in PATH, or use which() logic
|
||||
// For now, let's assume "open" or "xdg-open" is found in the default PATH:
|
||||
execve(tool, argv.data(), envp.data());
|
||||
|
||||
// If we reach here, execve failed
|
||||
perror("execve failed");
|
||||
// This would terminate your current process if it's not in a child
|
||||
// Usually you'd do something like:
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto program = argparse::ArgumentParser("b_manage", { platform_project_version.begin(), platform_project_version.end() });
|
||||
@@ -67,6 +127,11 @@ int main(int argc, char** argv)
|
||||
auto [rows, cols] = numRowsCols();
|
||||
manager = new platform::ManageScreen(rows, cols, model, score, platform, complete, partial, compare);
|
||||
manager->doMenu();
|
||||
auto fileName = manager->getExcelFileName();
|
||||
delete manager;
|
||||
if (!fileName.empty()) {
|
||||
std::cout << "Opening " << fileName << std::endl;
|
||||
openFile(fileName);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
53
src/experimental_clfs/CountingSemaphore.hpp
Normal file
53
src/experimental_clfs/CountingSemaphore.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef COUNTING_SEMAPHORE_H
|
||||
#define COUNTING_SEMAPHORE_H
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
class CountingSemaphore {
|
||||
public:
|
||||
static CountingSemaphore& getInstance()
|
||||
{
|
||||
static CountingSemaphore instance;
|
||||
return instance;
|
||||
}
|
||||
// Delete copy constructor and assignment operator
|
||||
CountingSemaphore(const CountingSemaphore&) = delete;
|
||||
CountingSemaphore& operator=(const CountingSemaphore&) = delete;
|
||||
void acquire()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx_);
|
||||
cv_.wait(lock, [this]() { return count_ > 0; });
|
||||
--count_;
|
||||
}
|
||||
void release()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
++count_;
|
||||
if (count_ <= max_count_) {
|
||||
cv_.notify_one();
|
||||
}
|
||||
}
|
||||
uint getCount() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
uint getMaxCount() const
|
||||
{
|
||||
return max_count_;
|
||||
}
|
||||
private:
|
||||
CountingSemaphore()
|
||||
: max_count_(std::max(1u, static_cast<uint>(0.95 * std::thread::hardware_concurrency()))),
|
||||
count_(max_count_)
|
||||
{
|
||||
}
|
||||
std::mutex mtx_;
|
||||
std::condition_variable cv_;
|
||||
const uint max_count_;
|
||||
uint count_;
|
||||
};
|
||||
#endif
|
182
src/experimental_clfs/ExpClf.cpp
Normal file
182
src/experimental_clfs/ExpClf.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#include "ExpClf.h"
|
||||
#include "TensorUtils.hpp"
|
||||
|
||||
namespace platform {
|
||||
ExpClf::ExpClf() : semaphore_{ CountingSemaphore::getInstance() }, Boost(false)
|
||||
{
|
||||
validHyperparameters = {};
|
||||
}
|
||||
//
|
||||
// Parents
|
||||
//
|
||||
void ExpClf::add_active_parents(const std::vector<int>& active_parents)
|
||||
{
|
||||
for (const auto& parent : active_parents)
|
||||
aode_.add_active_parent(parent);
|
||||
}
|
||||
void ExpClf::add_active_parent(int parent)
|
||||
{
|
||||
aode_.add_active_parent(parent);
|
||||
}
|
||||
void ExpClf::remove_last_parent()
|
||||
{
|
||||
aode_.remove_last_parent();
|
||||
}
|
||||
//
|
||||
// Predict
|
||||
//
|
||||
std::vector<int> ExpClf::predict_spode(std::vector<std::vector<int>>& test_data, int parent)
|
||||
{
|
||||
int test_size = test_data[0].size();
|
||||
int sample_size = test_data.size();
|
||||
auto predictions = std::vector<int>(test_size);
|
||||
|
||||
int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1);
|
||||
std::vector<std::thread> threads;
|
||||
auto worker = [&](const std::vector<std::vector<int>>& samples, int begin, int chunk, int sample_size, std::vector<int>& predictions) {
|
||||
std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk);
|
||||
#if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
#else
|
||||
pthread_setname_np(threadName.c_str());
|
||||
#endif
|
||||
std::vector<int> instance(sample_size);
|
||||
for (int sample = begin; sample < begin + chunk; ++sample) {
|
||||
for (int feature = 0; feature < sample_size; ++feature) {
|
||||
instance[feature] = samples[feature][sample];
|
||||
}
|
||||
predictions[sample] = aode_.predict_spode(instance, parent);
|
||||
}
|
||||
semaphore_.release();
|
||||
};
|
||||
for (int begin = 0; begin < test_size; begin += chunk_size) {
|
||||
int chunk = std::min(chunk_size, test_size - begin);
|
||||
semaphore_.acquire();
|
||||
threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(predictions));
|
||||
}
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
return predictions;
|
||||
}
|
||||
torch::Tensor ExpClf::predict(torch::Tensor& X)
|
||||
{
|
||||
auto X_ = TensorUtils::to_matrix(X);
|
||||
torch::Tensor y = torch::tensor(predict(X_));
|
||||
return y;
|
||||
}
|
||||
torch::Tensor ExpClf::predict_proba(torch::Tensor& X)
|
||||
{
|
||||
auto X_ = TensorUtils::to_matrix(X);
|
||||
auto probabilities = predict_proba(X_);
|
||||
auto n_samples = X.size(1);
|
||||
int n_classes = probabilities[0].size();
|
||||
auto y = torch::zeros({ n_samples, n_classes });
|
||||
for (int i = 0; i < n_samples; i++) {
|
||||
for (int j = 0; j < n_classes; j++) {
|
||||
y[i][j] = probabilities[i][j];
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
float ExpClf::score(torch::Tensor& X, torch::Tensor& y)
|
||||
{
|
||||
auto X_ = TensorUtils::to_matrix(X);
|
||||
auto y_ = TensorUtils::to_vector<int>(y);
|
||||
return score(X_, y_);
|
||||
}
|
||||
std::vector<std::vector<double>> ExpClf::predict_proba(const std::vector<std::vector<int>>& test_data)
|
||||
{
|
||||
int test_size = test_data[0].size();
|
||||
int sample_size = test_data.size();
|
||||
auto probabilities = std::vector<std::vector<double>>(test_size, std::vector<double>(aode_.statesClass()));
|
||||
|
||||
int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1);
|
||||
std::vector<std::thread> threads;
|
||||
auto worker = [&](const std::vector<std::vector<int>>& samples, int begin, int chunk, int sample_size, std::vector<std::vector<double>>& predictions) {
|
||||
std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk);
|
||||
#if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
#else
|
||||
pthread_setname_np(threadName.c_str());
|
||||
#endif
|
||||
|
||||
std::vector<int> instance(sample_size);
|
||||
for (int sample = begin; sample < begin + chunk; ++sample) {
|
||||
for (int feature = 0; feature < sample_size; ++feature) {
|
||||
instance[feature] = samples[feature][sample];
|
||||
}
|
||||
predictions[sample] = aode_.predict_proba(instance);
|
||||
}
|
||||
semaphore_.release();
|
||||
};
|
||||
for (int begin = 0; begin < test_size; begin += chunk_size) {
|
||||
int chunk = std::min(chunk_size, test_size - begin);
|
||||
semaphore_.acquire();
|
||||
threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(probabilities));
|
||||
}
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
return probabilities;
|
||||
}
|
||||
std::vector<int> ExpClf::predict(std::vector<std::vector<int>>& test_data)
|
||||
{
|
||||
if (!fitted) {
|
||||
throw std::logic_error(CLASSIFIER_NOT_FITTED);
|
||||
}
|
||||
auto probabilities = predict_proba(test_data);
|
||||
std::vector<int> predictions(probabilities.size(), 0);
|
||||
|
||||
for (size_t i = 0; i < probabilities.size(); i++) {
|
||||
predictions[i] = std::distance(probabilities[i].begin(), std::max_element(probabilities[i].begin(), probabilities[i].end()));
|
||||
}
|
||||
|
||||
return predictions;
|
||||
}
|
||||
float ExpClf::score(std::vector<std::vector<int>>& test_data, std::vector<int>& labels)
|
||||
{
|
||||
Timer timer;
|
||||
timer.start();
|
||||
std::vector<int> predictions = predict(test_data);
|
||||
int correct = 0;
|
||||
|
||||
for (size_t i = 0; i < predictions.size(); i++) {
|
||||
if (predictions[i] == labels[i]) {
|
||||
correct++;
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
std::cout << "* Time to predict: " << timer.getDurationString() << std::endl;
|
||||
}
|
||||
return static_cast<float>(correct) / predictions.size();
|
||||
}
|
||||
|
||||
//
|
||||
// statistics
|
||||
//
|
||||
int ExpClf::getNumberOfNodes() const
|
||||
{
|
||||
return aode_.getNumberOfNodes();
|
||||
}
|
||||
int ExpClf::getNumberOfEdges() const
|
||||
{
|
||||
return aode_.getNumberOfEdges();
|
||||
}
|
||||
int ExpClf::getNumberOfStates() const
|
||||
{
|
||||
return aode_.getNumberOfStates();
|
||||
}
|
||||
int ExpClf::getClassNumStates() const
|
||||
{
|
||||
return aode_.statesClass();
|
||||
}
|
||||
|
||||
|
||||
}
|
66
src/experimental_clfs/ExpClf.h
Normal file
66
src/experimental_clfs/ExpClf.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#ifndef EXPCLF_H
|
||||
#define EXPCLF_H
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <bayesnet/ensembles/Boost.h>
|
||||
#include <bayesnet/network/Smoothing.h>
|
||||
#include "common/Timer.hpp"
|
||||
#include "CountingSemaphore.hpp"
|
||||
#include "Xaode.hpp"
|
||||
|
||||
namespace platform {
|
||||
class ExpClf : public bayesnet::Boost {
|
||||
public:
|
||||
ExpClf();
|
||||
virtual ~ExpClf() = default;
|
||||
std::vector<int> predict(std::vector<std::vector<int>>& X) override;
|
||||
torch::Tensor predict(torch::Tensor& X) override;
|
||||
torch::Tensor predict_proba(torch::Tensor& X) override;
|
||||
std::vector<int> predict_spode(std::vector<std::vector<int>>& test_data, int parent);
|
||||
std::vector<std::vector<double>> predict_proba(const std::vector<std::vector<int>>& X);
|
||||
float score(std::vector<std::vector<int>>& X, std::vector<int>& y) override;
|
||||
float score(torch::Tensor& X, torch::Tensor& y) override;
|
||||
int getNumberOfNodes() const override;
|
||||
int getNumberOfEdges() const override;
|
||||
int getNumberOfStates() const override;
|
||||
int getClassNumStates() const override;
|
||||
std::vector<std::string> show() const override { return {}; }
|
||||
std::vector<std::string> topological_order() override { return {}; }
|
||||
std::string dump_cpt() const override { return ""; }
|
||||
void setDebug(bool debug) { this->debug = debug; }
|
||||
bayesnet::status_t getStatus() const override { return status; }
|
||||
std::vector<std::string> getNotes() const override { return notes; }
|
||||
std::vector<std::string> graph(const std::string& title = "") const override { return {}; }
|
||||
void add_active_parents(const std::vector<int>& active_parents);
|
||||
void add_active_parent(int parent);
|
||||
void remove_last_parent();
|
||||
protected:
|
||||
bool debug = false;
|
||||
Xaode aode_;
|
||||
torch::Tensor weights_;
|
||||
const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted";
|
||||
inline void normalize_weights(int num_instances)
|
||||
{
|
||||
double sum = weights_.sum().item<double>();
|
||||
if (sum == 0) {
|
||||
weights_ = torch::full({ num_instances }, 1.0);
|
||||
} else {
|
||||
for (int i = 0; i < weights_.size(0); ++i) {
|
||||
weights_[i] = weights_[i].item<double>() * num_instances / sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
CountingSemaphore& semaphore_;
|
||||
};
|
||||
}
|
||||
#endif // EXPCLF_H
|
158
src/experimental_clfs/ExpEnsemble.cpp
Normal file
158
src/experimental_clfs/ExpEnsemble.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#include "ExpEnsemble.h"
|
||||
#include "TensorUtils.hpp"
|
||||
|
||||
namespace platform {
|
||||
ExpEnsemble::ExpEnsemble() : semaphore_{ CountingSemaphore::getInstance() }, Boost(false)
|
||||
{
|
||||
validHyperparameters = {};
|
||||
}
|
||||
//
|
||||
// Parents
|
||||
//
|
||||
void ExpEnsemble::add_model(std::unique_ptr<XSpode> model)
|
||||
{
|
||||
models.push_back(std::move(model));
|
||||
n_models++;
|
||||
}
|
||||
void ExpEnsemble::remove_last_model()
|
||||
{
|
||||
models.pop_back();
|
||||
n_models--;
|
||||
}
|
||||
//
|
||||
// Predict
|
||||
//
|
||||
torch::Tensor ExpEnsemble::predict(torch::Tensor& X)
|
||||
{
|
||||
auto X_ = TensorUtils::to_matrix(X);
|
||||
torch::Tensor y = torch::tensor(predict(X_));
|
||||
return y;
|
||||
}
|
||||
torch::Tensor ExpEnsemble::predict_proba(torch::Tensor& X)
|
||||
{
|
||||
auto X_ = TensorUtils::to_matrix(X);
|
||||
auto probabilities = predict_proba(X_);
|
||||
auto n_samples = X.size(1);
|
||||
int n_classes = probabilities[0].size();
|
||||
auto y = torch::zeros({ n_samples, n_classes });
|
||||
for (int i = 0; i < n_samples; i++) {
|
||||
for (int j = 0; j < n_classes; j++) {
|
||||
y[i][j] = probabilities[i][j];
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
float ExpEnsemble::score(torch::Tensor& X, torch::Tensor& y)
|
||||
{
|
||||
auto X_ = TensorUtils::to_matrix(X);
|
||||
auto y_ = TensorUtils::to_vector<int>(y);
|
||||
return score(X_, y_);
|
||||
}
|
||||
std::vector<std::vector<double>> ExpEnsemble::predict_proba(const std::vector<std::vector<int>>& test_data)
|
||||
{
|
||||
int test_size = test_data[0].size();
|
||||
int sample_size = test_data.size();
|
||||
auto probabilities = std::vector<std::vector<double>>(test_size, std::vector<double>(getClassNumStates()));
|
||||
int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1);
|
||||
std::vector<std::thread> threads;
|
||||
auto worker = [&](const std::vector<std::vector<int>>& samples, int begin, int chunk, int sample_size, std::vector<std::vector<double>>& predictions) {
|
||||
std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk);
|
||||
#if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
#else
|
||||
pthread_setname_np(threadName.c_str());
|
||||
#endif
|
||||
|
||||
std::vector<int> instance(sample_size);
|
||||
for (int sample = begin; sample < begin + chunk; ++sample) {
|
||||
for (int feature = 0; feature < sample_size; ++feature) {
|
||||
instance[feature] = samples[feature][sample];
|
||||
}
|
||||
// predictions[sample] = aode_.predict_proba(instance);
|
||||
}
|
||||
semaphore_.release();
|
||||
};
|
||||
for (int begin = 0; begin < test_size; begin += chunk_size) {
|
||||
int chunk = std::min(chunk_size, test_size - begin);
|
||||
semaphore_.acquire();
|
||||
threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(probabilities));
|
||||
}
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
return probabilities;
|
||||
}
|
||||
std::vector<int> ExpEnsemble::predict(std::vector<std::vector<int>>& test_data)
|
||||
{
|
||||
if (!fitted) {
|
||||
throw std::logic_error(CLASSIFIER_NOT_FITTED);
|
||||
}
|
||||
auto probabilities = predict_proba(test_data);
|
||||
std::vector<int> predictions(probabilities.size(), 0);
|
||||
|
||||
for (size_t i = 0; i < probabilities.size(); i++) {
|
||||
predictions[i] = std::distance(probabilities[i].begin(), std::max_element(probabilities[i].begin(), probabilities[i].end()));
|
||||
}
|
||||
|
||||
return predictions;
|
||||
}
|
||||
float ExpEnsemble::score(std::vector<std::vector<int>>& test_data, std::vector<int>& labels)
|
||||
{
|
||||
Timer timer;
|
||||
timer.start();
|
||||
std::vector<int> predictions = predict(test_data);
|
||||
int correct = 0;
|
||||
|
||||
for (size_t i = 0; i < predictions.size(); i++) {
|
||||
if (predictions[i] == labels[i]) {
|
||||
correct++;
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
std::cout << "* Time to predict: " << timer.getDurationString() << std::endl;
|
||||
}
|
||||
return static_cast<float>(correct) / predictions.size();
|
||||
}
|
||||
|
||||
//
|
||||
// statistics
|
||||
//
|
||||
int ExpEnsemble::getNumberOfNodes() const
|
||||
{
|
||||
if (models_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return n_models * (models_.at(0)->getNFeatures() + 1);
|
||||
}
|
||||
int ExpEnsemble::getNumberOfEdges() const
|
||||
{
|
||||
if (models_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return n_models * (2 * models_.at(0)->getNFeatures() - 1);
|
||||
}
|
||||
int ExpEnsemble::getNumberOfStates() const
|
||||
{
|
||||
if (models_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
auto states = models_.at(0)->getStates();
|
||||
int nFeatures = models_.at(0)->getNFeatures();
|
||||
return std::accumulate(states.begin(), states.end(), 0) * nFeatures * n_models;
|
||||
}
|
||||
int ExpEnsemble::getClassNumStates() const
|
||||
{
|
||||
if (models_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return models_.at(0)->statesClass();
|
||||
}
|
||||
|
||||
|
||||
}
|
66
src/experimental_clfs/ExpEnsemble.h
Normal file
66
src/experimental_clfs/ExpEnsemble.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#ifndef EXPENSEMBLE_H
|
||||
#define EXPENSEMBLE_H
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <bayesnet/ensembles/Boost.h>
|
||||
#include <bayesnet/network/Smoothing.h>
|
||||
#include "common/Timer.hpp"
|
||||
#include "CountingSemaphore.hpp"
|
||||
#include "XSpode.hpp"
|
||||
|
||||
namespace platform {
|
||||
class ExpEnsemble : public bayesnet::Boost {
|
||||
public:
|
||||
ExpEnsemble();
|
||||
virtual ~ExpEnsemble() = default;
|
||||
std::vector<int> predict(std::vector<std::vector<int>>& X) override;
|
||||
torch::Tensor predict(torch::Tensor& X) override;
|
||||
torch::Tensor predict_proba(torch::Tensor& X) override;
|
||||
std::vector<int> predict_spode(std::vector<std::vector<int>>& test_data, int parent);
|
||||
std::vector<std::vector<double>> predict_proba(const std::vector<std::vector<int>>& X);
|
||||
float score(std::vector<std::vector<int>>& X, std::vector<int>& y) override;
|
||||
float score(torch::Tensor& X, torch::Tensor& y) override;
|
||||
int getNumberOfNodes() const override;
|
||||
int getNumberOfEdges() const override;
|
||||
int getNumberOfStates() const override;
|
||||
int getClassNumStates() const override;
|
||||
std::vector<std::string> show() const override { return {}; }
|
||||
std::vector<std::string> topological_order() override { return {}; }
|
||||
std::string dump_cpt() const override { return ""; }
|
||||
void setDebug(bool debug) { this->debug = debug; }
|
||||
bayesnet::status_t getStatus() const override { return status; }
|
||||
std::vector<std::string> getNotes() const override { return notes; }
|
||||
std::vector<std::string> graph(const std::string& title = "") const override { return {}; }
|
||||
protected:
|
||||
void add_model(std::unique_ptr<XSpode> model);
|
||||
void remove_last_model();
|
||||
bool debug = false;
|
||||
std::vector <std::unique_ptr<XSpode>> models_;
|
||||
torch::Tensor weights_;
|
||||
std::vector<double> significanceModels_;
|
||||
const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted";
|
||||
inline void normalize_weights(int num_instances)
|
||||
{
|
||||
double sum = weights_.sum().item<double>();
|
||||
if (sum == 0) {
|
||||
weights_ = torch::full({ num_instances }, 1.0);
|
||||
} else {
|
||||
for (int i = 0; i < weights_.size(0); ++i) {
|
||||
weights_[i] = weights_[i].item<double>() * num_instances / sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
CountingSemaphore& semaphore_;
|
||||
};
|
||||
}
|
||||
#endif // EXPENSEMBLE_H
|
51
src/experimental_clfs/TensorUtils.hpp
Normal file
51
src/experimental_clfs/TensorUtils.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef TENSORUTILS_HPP
|
||||
#define TENSORUTILS_HPP
|
||||
#include <torch/torch.h>
|
||||
#include <vector>
|
||||
namespace platform {
|
||||
class TensorUtils {
|
||||
public:
|
||||
static std::vector<std::vector<int>> to_matrix(const torch::Tensor& X)
|
||||
{
|
||||
// Ensure tensor is contiguous in memory
|
||||
auto X_contig = X.contiguous();
|
||||
|
||||
// Access tensor data pointer directly
|
||||
auto data_ptr = X_contig.data_ptr<int>();
|
||||
|
||||
// IF you are using int64_t as the data type, use the following line
|
||||
//auto data_ptr = X_contig.data_ptr<int64_t>();
|
||||
//std::vector<std::vector<int64_t>> data(X.size(0), std::vector<int64_t>(X.size(1)));
|
||||
|
||||
// Prepare output container
|
||||
std::vector<std::vector<int>> data(X.size(0), std::vector<int>(X.size(1)));
|
||||
|
||||
// Fill the 2D vector in a single loop using pointer arithmetic
|
||||
int rows = X.size(0);
|
||||
int cols = X.size(1);
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
std::copy(data_ptr + i * cols, data_ptr + (i + 1) * cols, data[i].begin());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
template <typename T>
|
||||
static std::vector<T> to_vector(const torch::Tensor& y)
|
||||
{
|
||||
// Ensure the tensor is contiguous in memory
|
||||
auto y_contig = y.contiguous();
|
||||
|
||||
// Access data pointer
|
||||
auto data_ptr = y_contig.data_ptr<T>();
|
||||
|
||||
// Prepare output container
|
||||
std::vector<T> data(y.size(0));
|
||||
|
||||
// Copy data efficiently
|
||||
std::copy(data_ptr, data_ptr + y.size(0), data.begin());
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TENSORUTILS_HPP
|
20
src/experimental_clfs/XA1DE.cpp
Normal file
20
src/experimental_clfs/XA1DE.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#include "XA1DE.h"
|
||||
#include "TensorUtils.hpp"
|
||||
|
||||
namespace platform {
|
||||
void XA1DE::trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing)
|
||||
{
|
||||
auto X = TensorUtils::to_matrix(dataset.slice(0, 0, dataset.size(0) - 1));
|
||||
auto y = TensorUtils::to_vector<int>(dataset.index({ -1, "..." }));
|
||||
int num_instances = X[0].size();
|
||||
weights_ = torch::full({ num_instances }, 1.0);
|
||||
//normalize_weights(num_instances);
|
||||
aode_.fit(X, y, features, className, states, weights_, true, smoothing);
|
||||
}
|
||||
}
|
26
src/experimental_clfs/XA1DE.h
Normal file
26
src/experimental_clfs/XA1DE.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#ifndef XA1DE_H
|
||||
#define XA1DE_H
|
||||
#include "Xaode.hpp"
|
||||
#include "ExpClf.h"
|
||||
#include <bayesnet/network/Smoothing.h>
|
||||
|
||||
namespace platform {
|
||||
class XA1DE : public ExpClf {
|
||||
public:
|
||||
XA1DE() = default;
|
||||
virtual ~XA1DE() override = default;
|
||||
std::string getVersion() override { return version; };
|
||||
protected:
|
||||
void buildModel(const torch::Tensor& weights) override {};
|
||||
void trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override;
|
||||
private:
|
||||
std::string version = "1.0.0";
|
||||
};
|
||||
}
|
||||
#endif // XA1DE_H
|
183
src/experimental_clfs/XBAODE.cpp
Normal file
183
src/experimental_clfs/XBAODE.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <limits.h>
|
||||
#include <tuple>
|
||||
#include "XBAODE.h"
|
||||
#include "XSpode.hpp"
|
||||
#include "TensorUtils.hpp"
|
||||
#include <loguru.hpp>
|
||||
|
||||
namespace platform {
|
||||
XBAODE::XBAODE()
|
||||
{
|
||||
validHyperparameters = { "alpha_block", "order", "convergence", "convergence_best", "bisection", "threshold", "maxTolerance",
|
||||
"predict_voting", "select_features" };
|
||||
}
|
||||
void XBAODE::add_model(std::unique_ptr<XSpode> model)
|
||||
{
|
||||
models.push_back(std::move(model));
|
||||
n_models++;
|
||||
}
|
||||
void XBAODE::remove_last_model()
|
||||
{
|
||||
models.pop_back();
|
||||
n_models--;
|
||||
}
|
||||
void XBAODE::trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing)
|
||||
{
|
||||
fitted = true;
|
||||
X_train_ = TensorUtils::to_matrix(X_train);
|
||||
y_train_ = TensorUtils::to_vector<int>(y_train);
|
||||
X_test_ = TensorUtils::to_matrix(X_test);
|
||||
y_test_ = TensorUtils::to_vector<int>(y_test);
|
||||
maxTolerance = 3;
|
||||
//
|
||||
// Logging setup
|
||||
//
|
||||
// loguru::set_thread_name("XBAODE");
|
||||
// loguru::g_stderr_verbosity = loguru::Verbosity_OFF;
|
||||
// loguru::add_file("XBAODE.log", loguru::Truncate, loguru::Verbosity_MAX);
|
||||
|
||||
// Algorithm based on the adaboost algorithm for classification
|
||||
// as explained in Ensemble methods (Zhi-Hua Zhou, 2012)
|
||||
double alpha_t = 0;
|
||||
weights_ = torch::full({ m }, 1.0 / static_cast<double>(m), torch::kFloat64); // m initialized in Classifier.cc
|
||||
significanceModels.resize(n, 0.0); // n initialized in Classifier.cc
|
||||
bool finished = false;
|
||||
std::vector<int> featuresUsed;
|
||||
n_models = 0;
|
||||
std::unique_ptr<XSpode> model;
|
||||
if (selectFeatures) {
|
||||
featuresUsed = featureSelection(weights_);
|
||||
for (const auto& parent : featuresUsed) {
|
||||
model = std::unique_ptr<XSpode>(new XSpode(parent));
|
||||
model->fit(X_train_, y_train_, weights_, smoothing);
|
||||
std::cout << model->getNFeatures() << std::endl;
|
||||
add_model(std::move(model));
|
||||
}
|
||||
notes.push_back("Used features in initialization: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size()) + " with " + select_features_algorithm);
|
||||
auto ypred = ExpEnsemble::predict(X_train);
|
||||
std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred, weights_);
|
||||
// Update significance of the models
|
||||
for (const auto& parent : featuresUsed) {
|
||||
significanceModels_[parent] = alpha_t;
|
||||
}
|
||||
n_models = featuresUsed.size();
|
||||
// VLOG_SCOPE_F(1, "SelectFeatures. alpha_t: %f n_models: %d", alpha_t, n_models);
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
int numItemsPack = 0; // The counter of the models inserted in the current pack
|
||||
// Variables to control the accuracy finish condition
|
||||
double priorAccuracy = 0.0;
|
||||
double improvement = 1.0;
|
||||
double convergence_threshold = 1e-4;
|
||||
int tolerance = 0; // number of times the accuracy is lower than the convergence_threshold
|
||||
// Step 0: Set the finish condition
|
||||
// epsilon sub t > 0.5 => inverse the weights policy
|
||||
// validation error is not decreasing
|
||||
// run out of features
|
||||
bool ascending = order_algorithm == bayesnet::Orders.ASC;
|
||||
std::mt19937 g{ 173 };
|
||||
while (!finished) {
|
||||
// Step 1: Build ranking with mutual information
|
||||
auto featureSelection = metrics.SelectKBestWeighted(weights_, ascending, n); // Get all the features sorted
|
||||
if (order_algorithm == bayesnet::Orders.RAND) {
|
||||
std::shuffle(featureSelection.begin(), featureSelection.end(), g);
|
||||
}
|
||||
// Remove used features
|
||||
featureSelection.erase(remove_if(featureSelection.begin(), featureSelection.end(), [&](auto x)
|
||||
{ return std::find(featuresUsed.begin(), featuresUsed.end(), x) != featuresUsed.end();}),
|
||||
featureSelection.end()
|
||||
);
|
||||
int k = bisection ? pow(2, tolerance) : 1;
|
||||
int counter = 0; // The model counter of the current pack
|
||||
// VLOG_SCOPE_F(1, "counter=%d k=%d featureSelection.size: %zu", counter, k, featureSelection.size());
|
||||
while (counter++ < k && featureSelection.size() > 0) {
|
||||
auto feature = featureSelection[0];
|
||||
featureSelection.erase(featureSelection.begin());
|
||||
model = std::unique_ptr<XSpode>(new XSpode(feature));
|
||||
model->fit(X_train_, y_train_, weights_, smoothing);
|
||||
std::vector<int> ypred;
|
||||
if (alpha_block) {
|
||||
//
|
||||
// Compute the prediction with the current ensemble + model
|
||||
//
|
||||
// Add the model to the ensemble
|
||||
significanceModels[feature] = 1.0;
|
||||
add_model(std::move(model));
|
||||
// Compute the prediction
|
||||
ypred = ExpEnsemble::predict(X_train_);
|
||||
// Remove the model from the ensemble
|
||||
significanceModels[feature] = 0.0;
|
||||
model = std::move(models_.back());
|
||||
remove_last_model();
|
||||
} else {
|
||||
ypred = model->predict(X_train_);
|
||||
}
|
||||
// Step 3.1: Compute the classifier amout of say
|
||||
auto ypred_t = torch::tensor(ypred);
|
||||
std::tie(weights_, alpha_t, finished) = update_weights(y_train, ypred_t, weights_);
|
||||
// Step 3.4: Store classifier and its accuracy to weigh its future vote
|
||||
numItemsPack++;
|
||||
featuresUsed.push_back(feature);
|
||||
add_model(std::move(model));
|
||||
significanceModels[feature] = alpha_t;
|
||||
// VLOG_SCOPE_F(2, "finished: %d numItemsPack: %d n_models: %d featuresUsed: %zu", finished, numItemsPack, n_models, featuresUsed.size());
|
||||
} // End of the pack
|
||||
if (convergence && !finished) {
|
||||
auto y_val_predict = ExpEnsemble::predict(X_test);
|
||||
double accuracy = (y_val_predict == y_test).sum().item<double>() / (double)y_test.size(0);
|
||||
if (priorAccuracy == 0) {
|
||||
priorAccuracy = accuracy;
|
||||
} else {
|
||||
improvement = accuracy - priorAccuracy;
|
||||
}
|
||||
if (improvement < convergence_threshold) {
|
||||
// VLOG_SCOPE_F(3, " (improvement<threshold) tolerance: %d numItemsPack: %d improvement: %f prior: %f current: %f", tolerance, numItemsPack, improvement, priorAccuracy, accuracy);
|
||||
tolerance++;
|
||||
} else {
|
||||
// VLOG_SCOPE_F(3, "* (improvement>=threshold) Reset. tolerance: %d numItemsPack: %d improvement: %f prior: %f current: %f", tolerance, numItemsPack, improvement, priorAccuracy, accuracy);
|
||||
tolerance = 0; // Reset the counter if the model performs better
|
||||
numItemsPack = 0;
|
||||
}
|
||||
if (convergence_best) {
|
||||
// Keep the best accuracy until now as the prior accuracy
|
||||
priorAccuracy = std::max(accuracy, priorAccuracy);
|
||||
} else {
|
||||
// Keep the last accuray obtained as the prior accuracy
|
||||
priorAccuracy = accuracy;
|
||||
}
|
||||
}
|
||||
// VLOG_SCOPE_F(1, "tolerance: %d featuresUsed.size: %zu features.size: %zu", tolerance, featuresUsed.size(), features.size());
|
||||
finished = finished || tolerance > maxTolerance || featuresUsed.size() == features.size();
|
||||
}
|
||||
if (tolerance > maxTolerance) {
|
||||
if (numItemsPack < n_models) {
|
||||
notes.push_back("Convergence threshold reached & " + std::to_string(numItemsPack) + " models eliminated");
|
||||
// VLOG_SCOPE_F(4, "Convergence threshold reached & %d models eliminated of %d", numItemsPack, n_models);
|
||||
for (int i = featuresUsed.size() - 1; i >= featuresUsed.size() - numItemsPack; --i) {
|
||||
remove_last_model();
|
||||
significanceModels[featuresUsed[i]] = 0.0;
|
||||
}
|
||||
// VLOG_SCOPE_F(4, "*Convergence threshold %d models left & %d features used.", n_models, featuresUsed.size());
|
||||
} else {
|
||||
notes.push_back("Convergence threshold reached & 0 models eliminated");
|
||||
// VLOG_SCOPE_F(4, "Convergence threshold reached & 0 models eliminated n_models=%d numItemsPack=%d", n_models, numItemsPack);
|
||||
}
|
||||
}
|
||||
if (featuresUsed.size() != features.size()) {
|
||||
notes.push_back("Used features in train: " + std::to_string(featuresUsed.size()) + " of " + std::to_string(features.size()));
|
||||
status = bayesnet::WARNING;
|
||||
}
|
||||
notes.push_back("Number of models: " + std::to_string(n_models));
|
||||
return;
|
||||
}
|
||||
}
|
35
src/experimental_clfs/XBAODE.h
Normal file
35
src/experimental_clfs/XBAODE.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
|
||||
#ifndef XBAODE_H
|
||||
#define XBAODE_H
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include "common/Timer.hpp"
|
||||
#include "ExpEnsemble.h"
|
||||
|
||||
namespace platform {
|
||||
class XBAODE : public Boost {
|
||||
|
||||
// Hay que hacer un vector de modelos entrenados y hacer un predict ensemble con todos ellos
|
||||
// Probar XA1DE con smooth original y laplace y comprobar diferencias si se pasan pesos a 1 o a 1/m
|
||||
public:
|
||||
XBAODE();
|
||||
std::string getVersion() override { return version; };
|
||||
protected:
|
||||
void trainModel(const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing) override;
|
||||
private:
|
||||
void add_model(std::unique_ptr<XSpode> model);
|
||||
void remove_last_model();
|
||||
std::vector<std::vector<int>> X_train_, X_test_;
|
||||
std::vector<int> y_train_, y_test_;
|
||||
std::string version = "0.9.7";
|
||||
};
|
||||
}
|
||||
#endif // XBAODE_H
|
436
src/experimental_clfs/XSpode.hpp
Normal file
436
src/experimental_clfs/XSpode.hpp
Normal file
@@ -0,0 +1,436 @@
|
||||
#ifndef XSPODE_H
|
||||
#define XSPODE_H
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <torch/torch.h>
|
||||
#include <bayesnet/network/Smoothing.h>
|
||||
#include <bayesnet/classifiers/Classifier.h>
|
||||
#include "CountingSemaphore.hpp"
|
||||
|
||||
|
||||
namespace platform {
|
||||
|
||||
class XSpode : public bayesnet::Classifier {
|
||||
public:
|
||||
// --------------------------------------
|
||||
// Constructor
|
||||
//
|
||||
// Supply which feature index is the single super-parent (“spIndex”).
|
||||
// --------------------------------------
|
||||
explicit XSpode(int spIndex)
|
||||
: superParent_{ spIndex },
|
||||
nFeatures_{ 0 },
|
||||
statesClass_{ 0 },
|
||||
fitted_{ false },
|
||||
alpha_{ 1.0 },
|
||||
initializer_{ 1.0 },
|
||||
semaphore_{ CountingSemaphore::getInstance() } : bayesnet::Classifier(bayesnet::Network())
|
||||
{
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// fit
|
||||
// --------------------------------------
|
||||
//
|
||||
// Trains the SPODE given data:
|
||||
// X: X[f][n] is the f-th feature value for instance n
|
||||
// y: y[n] is the class value for instance n
|
||||
// states: a map or array that tells how many distinct states each feature and the class can take
|
||||
//
|
||||
// For example, states_.back() is the number of class states,
|
||||
// and states_[f] is the number of distinct values for feature f.
|
||||
//
|
||||
// We only store conditional probabilities for:
|
||||
// p(x_sp| c) (the super-parent feature)
|
||||
// p(x_child| c, x_sp) for all child ≠ sp
|
||||
//
|
||||
// The “weights” can be a vector of per-instance weights; if not used, pass them as 1.0.
|
||||
// --------------------------------------
|
||||
void fit(const std::vector<std::vector<int>>& X,
|
||||
const std::vector<int>& y,
|
||||
const torch::Tensor& weights, const bayesnet::Smoothing_t smoothing)
|
||||
{
|
||||
int numInstances = static_cast<int>(y.size());
|
||||
nFeatures_ = static_cast<int>(X.size());
|
||||
|
||||
// Derive the number of states for each feature and for the class.
|
||||
// (This is just one approach; adapt to match your environment.)
|
||||
// Here, we assume the user also gave us the total #states per feature in e.g. statesMap.
|
||||
// We'll simply reconstruct the integer states_ array. The last entry is statesClass_.
|
||||
states_.resize(nFeatures_);
|
||||
for (int f = 0; f < nFeatures_; f++) {
|
||||
// Suppose you look up in “statesMap” by the feature name, or read directly from X.
|
||||
// We'll assume states_[f] = max value in X[f] + 1.
|
||||
auto maxIt = std::max_element(X[f].begin(), X[f].end());
|
||||
states_[f] = (*maxIt) + 1;
|
||||
}
|
||||
// For the class: states_.back() = max(y)+1
|
||||
statesClass_ = (*std::max_element(y.begin(), y.end())) + 1;
|
||||
|
||||
// Initialize counts
|
||||
classCounts_.resize(statesClass_, 0.0);
|
||||
// p(x_sp = spVal | c)
|
||||
// We'll store these counts in spFeatureCounts_[spVal * statesClass_ + c].
|
||||
spFeatureCounts_.resize(states_[superParent_] * statesClass_, 0.0);
|
||||
|
||||
// For each child ≠ sp, we store p(childVal| c, spVal) in a separate block of childCounts_.
|
||||
// childCounts_ will be sized as sum_{child≠sp} (states_[child] * statesClass_ * states_[sp]).
|
||||
// We also need an offset for each child to index into childCounts_.
|
||||
childOffsets_.resize(nFeatures_, -1);
|
||||
int totalSize = 0;
|
||||
for (int f = 0; f < nFeatures_; f++) {
|
||||
if (f == superParent_) continue; // skip sp
|
||||
childOffsets_[f] = totalSize;
|
||||
// block size for this child's counts: states_[f] * statesClass_ * states_[superParent_]
|
||||
totalSize += (states_[f] * statesClass_ * states_[superParent_]);
|
||||
}
|
||||
childCounts_.resize(totalSize, 0.0);
|
||||
|
||||
// Accumulate raw counts
|
||||
for (int n = 0; n < numInstances; n++) {
|
||||
std::vector<int> instance(nFeatures_ + 1);
|
||||
for (int f = 0; f < nFeatures_; f++) {
|
||||
instance[f] = X[f][n];
|
||||
}
|
||||
instance[nFeatures_] = y[n];
|
||||
addSample(instance, weights[n].item<double>());
|
||||
}
|
||||
|
||||
switch (smoothing) {
|
||||
case bayesnet::Smoothing_t::ORIGINAL:
|
||||
alpha_ = 1.0 / numInstances;
|
||||
break;
|
||||
case bayesnet::Smoothing_t::LAPLACE:
|
||||
alpha_ = 1.0;
|
||||
break;
|
||||
default:
|
||||
alpha_ = 0.0; // No smoothing
|
||||
}
|
||||
initializer_ = initializer_ = std::numeric_limits<double>::max() / (nFeatures_ * nFeatures_);
|
||||
// Convert raw counts to probabilities
|
||||
computeProbabilities();
|
||||
fitted_ = true;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// addSample (only valid in COUNTS mode)
|
||||
// --------------------------------------
|
||||
//
|
||||
// instance has size nFeatures_ + 1, with the class at the end.
|
||||
// We add 1 to the appropriate counters for each (c, superParentVal, childVal).
|
||||
//
|
||||
void addSample(const std::vector<int>& instance, double weight)
|
||||
{
|
||||
if (weight <= 0.0) return;
|
||||
|
||||
int c = instance.back();
|
||||
// (A) increment classCounts
|
||||
classCounts_[c] += weight;
|
||||
|
||||
// (B) increment super-parent counts => p(x_sp | c)
|
||||
int spVal = instance[superParent_];
|
||||
spFeatureCounts_[spVal * statesClass_ + c] += weight;
|
||||
|
||||
// (C) increment child counts => p(childVal | c, x_sp)
|
||||
for (int f = 0; f < nFeatures_; f++) {
|
||||
if (f == superParent_) continue;
|
||||
int childVal = instance[f];
|
||||
int offset = childOffsets_[f];
|
||||
// Compute index in childCounts_.
|
||||
// Layout: [ offset + (spVal * states_[f] + childVal) * statesClass_ + c ]
|
||||
int blockSize = states_[f] * statesClass_;
|
||||
int idx = offset + spVal * blockSize + childVal * statesClass_ + c;
|
||||
childCounts_[idx] += weight;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// computeProbabilities
|
||||
// --------------------------------------
|
||||
//
|
||||
// Once all samples are added in COUNTS mode, call this to:
|
||||
// p(c)
|
||||
// p(x_sp = spVal | c)
|
||||
// p(x_child = v | c, x_sp = s_sp)
|
||||
//
|
||||
// We store them in the corresponding *Probs_ arrays for inference.
|
||||
// --------------------------------------
|
||||
void computeProbabilities()
|
||||
{
|
||||
double totalCount = std::accumulate(classCounts_.begin(), classCounts_.end(), 0.0);
|
||||
|
||||
// p(c) => classPriors_
|
||||
classPriors_.resize(statesClass_, 0.0);
|
||||
if (totalCount <= 0.0) {
|
||||
// fallback => uniform
|
||||
double unif = 1.0 / static_cast<double>(statesClass_);
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
classPriors_[c] = unif;
|
||||
}
|
||||
} else {
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
classPriors_[c] = (classCounts_[c] + alpha_)
|
||||
/ (totalCount + alpha_ * statesClass_);
|
||||
}
|
||||
}
|
||||
|
||||
// p(x_sp | c)
|
||||
spFeatureProbs_.resize(spFeatureCounts_.size());
|
||||
// denominator for spVal * statesClass_ + c is just classCounts_[c] + alpha_ * (#states of sp)
|
||||
int spCard = states_[superParent_];
|
||||
for (int spVal = 0; spVal < spCard; spVal++) {
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
double denom = classCounts_[c] + alpha_ * spCard;
|
||||
double num = spFeatureCounts_[spVal * statesClass_ + c] + alpha_;
|
||||
spFeatureProbs_[spVal * statesClass_ + c] = (denom <= 0.0 ? 0.0 : num / denom);
|
||||
}
|
||||
}
|
||||
|
||||
// p(x_child | c, x_sp)
|
||||
childProbs_.resize(childCounts_.size());
|
||||
for (int f = 0; f < nFeatures_; f++) {
|
||||
if (f == superParent_) continue;
|
||||
int offset = childOffsets_[f];
|
||||
int childCard = states_[f];
|
||||
|
||||
// For each spVal, c, childVal in childCounts_:
|
||||
for (int spVal = 0; spVal < spCard; spVal++) {
|
||||
for (int childVal = 0; childVal < childCard; childVal++) {
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
int idx = offset + spVal * (childCard * statesClass_)
|
||||
+ childVal * statesClass_
|
||||
+ c;
|
||||
|
||||
double num = childCounts_[idx] + alpha_;
|
||||
// denominator = spFeatureCounts_[spVal * statesClass_ + c] + alpha_ * (#states of child)
|
||||
double denom = spFeatureCounts_[spVal * statesClass_ + c]
|
||||
+ alpha_ * childCard;
|
||||
childProbs_[idx] = (denom <= 0.0 ? 0.0 : num / denom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// predict_proba
|
||||
// --------------------------------------
|
||||
//
|
||||
// For a single instance x of dimension nFeatures_:
|
||||
// P(c | x) ∝ p(c) × p(x_sp | c) × ∏(child ≠ sp) p(x_child | c, x_sp).
|
||||
//
|
||||
// Then we normalize the result.
|
||||
// --------------------------------------
|
||||
std::vector<double> predict_proba(const std::vector<int>& instance) const
|
||||
{
|
||||
std::vector<double> probs(statesClass_, 0.0);
|
||||
|
||||
// Multiply p(c) × p(x_sp | c)
|
||||
int spVal = instance[superParent_];
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
double pc = classPriors_[c];
|
||||
double pSpC = spFeatureProbs_[spVal * statesClass_ + c];
|
||||
probs[c] = pc * pSpC * initializer_;
|
||||
}
|
||||
|
||||
// Multiply by each child’s probability p(x_child | c, x_sp)
|
||||
for (int feature = 0; feature < nFeatures_; feature++) {
|
||||
if (feature == superParent_) continue; // skip sp
|
||||
int sf = instance[feature];
|
||||
int offset = childOffsets_[feature];
|
||||
int childCard = states_[feature]; // not used directly, but for clarity
|
||||
// Index into childProbs_ = offset + spVal*(childCard*statesClass_) + childVal*statesClass_ + c
|
||||
int base = offset + spVal * (childCard * statesClass_) + sf * statesClass_;
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
probs[c] *= childProbs_[base + c];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize
|
||||
normalize(probs);
|
||||
return probs;
|
||||
}
|
||||
std::vector<std::vector<double>> predict_proba(const std::vector<std::vector<int>>& test_data)
|
||||
{
|
||||
int test_size = test_data[0].size();
|
||||
int sample_size = test_data.size();
|
||||
auto probabilities = std::vector<std::vector<double>>(test_size, std::vector<double>(statesClass_));
|
||||
|
||||
int chunk_size = std::min(150, int(test_size / semaphore_.getMaxCount()) + 1);
|
||||
std::vector<std::thread> threads;
|
||||
auto worker = [&](const std::vector<std::vector<int>>& samples, int begin, int chunk, int sample_size, std::vector<std::vector<double>>& predictions) {
|
||||
std::string threadName = "(V)PWorker-" + std::to_string(begin) + "-" + std::to_string(chunk);
|
||||
#if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
#else
|
||||
pthread_setname_np(threadName.c_str());
|
||||
#endif
|
||||
|
||||
std::vector<int> instance(sample_size);
|
||||
for (int sample = begin; sample < begin + chunk; ++sample) {
|
||||
for (int feature = 0; feature < sample_size; ++feature) {
|
||||
instance[feature] = samples[feature][sample];
|
||||
}
|
||||
predictions[sample] = predict_proba(instance);
|
||||
}
|
||||
semaphore_.release();
|
||||
};
|
||||
for (int begin = 0; begin < test_size; begin += chunk_size) {
|
||||
int chunk = std::min(chunk_size, test_size - begin);
|
||||
semaphore_.acquire();
|
||||
threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(probabilities));
|
||||
}
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
return probabilities;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// predict
|
||||
// --------------------------------------
|
||||
//
|
||||
// Return the class argmax( P(c|x) ).
|
||||
// --------------------------------------
|
||||
int predict(const std::vector<int>& instance) const
|
||||
{
|
||||
auto p = predict_proba(instance);
|
||||
return static_cast<int>(std::distance(p.begin(),
|
||||
std::max_element(p.begin(), p.end())));
|
||||
}
|
||||
std::vector<int> predict(std::vector<std::vector<int>>& test_data)
|
||||
{
|
||||
if (!fitted_) {
|
||||
throw std::logic_error(CLASSIFIER_NOT_FITTED);
|
||||
}
|
||||
auto probabilities = predict_proba(test_data);
|
||||
std::vector<int> predictions(probabilities.size(), 0);
|
||||
|
||||
for (size_t i = 0; i < probabilities.size(); i++) {
|
||||
predictions[i] = std::distance(probabilities[i].begin(), std::max_element(probabilities[i].begin(), probabilities[i].end()));
|
||||
}
|
||||
|
||||
return predictions;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Utility: normalize
|
||||
// --------------------------------------
|
||||
void normalize(std::vector<double>& v) const
|
||||
{
|
||||
double sum = 0.0;
|
||||
for (auto val : v) { sum += val; }
|
||||
if (sum <= 0.0) {
|
||||
return;
|
||||
}
|
||||
for (auto& val : v) {
|
||||
val /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// debug printing, if desired
|
||||
// --------------------------------------
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "---- SPODE Model ----\n"
|
||||
<< "nFeatures_ = " << nFeatures_ << "\n"
|
||||
<< "superParent_ = " << superParent_ << "\n"
|
||||
<< "statesClass_ = " << statesClass_ << "\n"
|
||||
<< "\n";
|
||||
|
||||
oss << "States: [";
|
||||
for (int s : states_) oss << s << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "classCounts_: [";
|
||||
for (double c : classCounts_) oss << c << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "classPriors_: [";
|
||||
for (double c : classPriors_) oss << c << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "spFeatureCounts_: size = " << spFeatureCounts_.size() << "\n[";
|
||||
for (double c : spFeatureCounts_) oss << c << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "spFeatureProbs_: size = " << spFeatureProbs_.size() << "\n[";
|
||||
for (double c : spFeatureProbs_) oss << c << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "childCounts_: size = " << childCounts_.size() << "\n[";
|
||||
for (double cc : childCounts_) oss << cc << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "childProbs_: size = " << childProbs_.size() << "\n[";
|
||||
for (double cp : childProbs_) oss << cp << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "childOffsets_: [";
|
||||
for (int co : childOffsets_) oss << co << " ";
|
||||
oss << "]\n";
|
||||
|
||||
oss << "---------------------\n";
|
||||
return oss.str();
|
||||
}
|
||||
int statesClass() const { return statesClass_; }
|
||||
int getNFeatures() const { return nFeatures_; }
|
||||
int getNumberOfStates() const
|
||||
{
|
||||
return std::accumulate(states_.begin(), states_.end(), 0) * nFeatures_;
|
||||
}
|
||||
int getNumberOfEdges() const
|
||||
{
|
||||
return nFeatures_ * (2 * nFeatures_ - 1);
|
||||
}
|
||||
std::vector<int>& getStates() { return states_; }
|
||||
|
||||
private:
|
||||
// --------------------------------------
|
||||
// MEMBERS
|
||||
// --------------------------------------
|
||||
|
||||
int superParent_; // which feature is the single super-parent
|
||||
int nFeatures_;
|
||||
int statesClass_;
|
||||
bool fitted_ = false;
|
||||
std::vector<int> states_; // [states_feat0, ..., states_feat(N-1)] (class not included in this array)
|
||||
|
||||
const std::string CLASSIFIER_NOT_FITTED = "Classifier has not been fitted";
|
||||
|
||||
// Class counts
|
||||
std::vector<double> classCounts_; // [c], accumulative
|
||||
std::vector<double> classPriors_; // [c], after normalization
|
||||
|
||||
// For p(x_sp = spVal | c)
|
||||
std::vector<double> spFeatureCounts_; // [spVal * statesClass_ + c]
|
||||
std::vector<double> spFeatureProbs_; // same shape, after normalization
|
||||
|
||||
// For p(x_child = childVal | x_sp = spVal, c)
|
||||
// childCounts_ is big enough to hold all child features except sp:
|
||||
// For each child f, we store childOffsets_[f] as the start index, then
|
||||
// childVal, spVal, c => the data.
|
||||
std::vector<double> childCounts_;
|
||||
std::vector<double> childProbs_;
|
||||
std::vector<int> childOffsets_;
|
||||
|
||||
double alpha_ = 1.0;
|
||||
double initializer_; // for numerical stability
|
||||
CountingSemaphore& semaphore_;
|
||||
};
|
||||
|
||||
} // namespace platform
|
||||
|
||||
#endif // XSPODE_H
|
478
src/experimental_clfs/Xaode.hpp
Normal file
478
src/experimental_clfs/Xaode.hpp
Normal file
@@ -0,0 +1,478 @@
|
||||
// ***************************************************************
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Ricardo Montañana Gómez
|
||||
// SPDX-FileType: SOURCE
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ***************************************************************
|
||||
// Based on the Geoff. I. Webb A1DE java algorithm
|
||||
// https://weka.sourceforge.io/packageMetaData/AnDE/Latest.html
|
||||
|
||||
#ifndef XAODE_H
|
||||
#define XAODE_H
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <torch/torch.h>
|
||||
#include <bayesnet/network/Smoothing.h>
|
||||
|
||||
|
||||
namespace platform {
|
||||
class Xaode {
|
||||
public:
|
||||
// -------------------------------------------------------
|
||||
// The Xaode can be EMPTY (just created), in COUNTS mode (accumulating raw counts)
|
||||
// or PROBS mode (storing conditional probabilities).
|
||||
enum class MatrixState {
|
||||
EMPTY,
|
||||
COUNTS,
|
||||
PROBS
|
||||
};
|
||||
std::vector<double> significance_models_;
|
||||
Xaode() : nFeatures_{ 0 }, statesClass_{ 0 }, matrixState_{ MatrixState::EMPTY } {}
|
||||
// -------------------------------------------------------
|
||||
// fit
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// Classifiers interface
|
||||
// all parameter decide if the model is initialized with all the parents active or none of them
|
||||
//
|
||||
// states.size() = nFeatures + 1,
|
||||
// where states.back() = number of class states.
|
||||
//
|
||||
// We'll store:
|
||||
// 1) p(x_i=si | c) in classFeatureProbs_
|
||||
// 2) p(x_j=sj | c, x_i=si) in data_, with i<j => i is "superparent," j is "child."
|
||||
//
|
||||
// Internally, in COUNTS mode, data_ accumulates raw counts, then
|
||||
// computeProbabilities(...) normalizes them into conditionals.
|
||||
void fit(std::vector<std::vector<int>>& X, std::vector<int>& y, const std::vector<std::string>& features, const std::string& className, std::map<std::string, std::vector<int>>& states, const torch::Tensor& weights, const bool all_parents, const bayesnet::Smoothing_t smoothing)
|
||||
{
|
||||
int num_instances = X[0].size();
|
||||
nFeatures_ = X.size();
|
||||
|
||||
significance_models_.resize(nFeatures_, (all_parents ? 1.0 : 0.0));
|
||||
for (int i = 0; i < nFeatures_; i++) {
|
||||
if (all_parents) active_parents.push_back(i);
|
||||
states_.push_back(*max_element(X[i].begin(), X[i].end()) + 1);
|
||||
}
|
||||
states_.push_back(*max_element(y.begin(), y.end()) + 1);
|
||||
//
|
||||
statesClass_ = states_.back();
|
||||
classCounts_.resize(statesClass_, 0.0);
|
||||
classPriors_.resize(statesClass_, 0.0);
|
||||
//
|
||||
// Initialize data structures
|
||||
//
|
||||
active_parents.resize(nFeatures_);
|
||||
int totalStates = std::accumulate(states_.begin(), states_.end(), 0) - statesClass_;
|
||||
|
||||
// For p(x_i=si | c), we store them in a 1D array classFeatureProbs_ after we compute.
|
||||
// We'll need the offsets for each feature i in featureClassOffset_.
|
||||
featureClassOffset_.resize(nFeatures_);
|
||||
// We'll store p(x_child=sj | c, x_sp=si) for each pair (i<j).
|
||||
// So data_(i, si, j, sj, c) indexes into a big 1D array with an offset.
|
||||
// For p(x_i=si | c), we store them in a 1D array classFeatureProbs_ after we compute.
|
||||
// We'll need the offsets for each feature i in featureClassOffset_.
|
||||
featureClassOffset_.resize(nFeatures_);
|
||||
pairOffset_.resize(totalStates);
|
||||
int feature_offset = 0;
|
||||
int runningOffset = 0;
|
||||
int feature = 0, index = 0;
|
||||
for (int i = 0; i < nFeatures_; ++i) {
|
||||
featureClassOffset_[i] = feature_offset;
|
||||
feature_offset += states_[i];
|
||||
for (int j = 0; j < states_[i]; ++j) {
|
||||
pairOffset_[feature++] = index;
|
||||
index += runningOffset;
|
||||
}
|
||||
runningOffset += states_[i];
|
||||
}
|
||||
int totalSize = index * statesClass_;
|
||||
data_.resize(totalSize);
|
||||
dataOpp_.resize(totalSize);
|
||||
|
||||
classFeatureCounts_.resize(feature_offset * statesClass_);
|
||||
classFeatureProbs_.resize(feature_offset * statesClass_);
|
||||
|
||||
matrixState_ = MatrixState::COUNTS;
|
||||
//
|
||||
// Add samples
|
||||
//
|
||||
std::vector<int> instance(nFeatures_ + 1);
|
||||
for (int n_instance = 0; n_instance < num_instances; n_instance++) {
|
||||
for (int feature = 0; feature < nFeatures_; feature++) {
|
||||
instance[feature] = X[feature][n_instance];
|
||||
}
|
||||
instance[nFeatures_] = y[n_instance];
|
||||
addSample(instance, weights[n_instance].item<double>());
|
||||
}
|
||||
switch (smoothing) {
|
||||
case bayesnet::Smoothing_t::ORIGINAL:
|
||||
alpha_ = 1.0 / num_instances;
|
||||
break;
|
||||
case bayesnet::Smoothing_t::LAPLACE:
|
||||
alpha_ = 1.0;
|
||||
break;
|
||||
default:
|
||||
alpha_ = 0.0; // No smoothing
|
||||
}
|
||||
initializer_ = std::numeric_limits<double>::max() / (nFeatures_ * nFeatures_);
|
||||
computeProbabilities();
|
||||
}
|
||||
std::string to_string() const
|
||||
{
|
||||
std::ostringstream ostream;
|
||||
ostream << "-------- Xaode.status --------" << std::endl
|
||||
<< "- nFeatures = " << nFeatures_ << std::endl
|
||||
<< "- statesClass = " << statesClass_ << std::endl
|
||||
<< "- matrixState = " << (matrixState_ == MatrixState::COUNTS ? "COUNTS" : "PROBS") << std::endl;
|
||||
ostream << "- states: size: " << states_.size() << std::endl;
|
||||
for (int s : states_) ostream << s << " "; ostream << std::endl;
|
||||
ostream << "- classCounts: size: " << classCounts_.size() << std::endl;
|
||||
for (double cc : classCounts_) ostream << cc << " "; ostream << std::endl;
|
||||
ostream << "- classPriors: size: " << classPriors_.size() << std::endl;
|
||||
for (double cp : classPriors_) ostream << cp << " "; ostream << std::endl;
|
||||
ostream << "- classFeatureCounts: size: " << classFeatureCounts_.size() << std::endl;
|
||||
for (double cfc : classFeatureCounts_) ostream << cfc << " "; ostream << std::endl;
|
||||
ostream << "- classFeatureProbs: size: " << classFeatureProbs_.size() << std::endl;
|
||||
for (double cfp : classFeatureProbs_) ostream << cfp << " "; ostream << std::endl;
|
||||
ostream << "- featureClassOffset: size: " << featureClassOffset_.size() << std::endl;
|
||||
for (int f : featureClassOffset_) ostream << f << " "; ostream << std::endl;
|
||||
ostream << "- pairOffset_: size: " << pairOffset_.size() << std::endl;
|
||||
for (int p : pairOffset_) ostream << p << " "; ostream << std::endl;
|
||||
ostream << "- data: size: " << data_.size() << std::endl;
|
||||
for (double d : data_) ostream << d << " "; ostream << std::endl;
|
||||
ostream << "- dataOpp: size: " << dataOpp_.size() << std::endl;
|
||||
for (double d : dataOpp_) ostream << d << " "; ostream << std::endl;
|
||||
ostream << "--------------------------------" << std::endl;
|
||||
std::string output = ostream.str();
|
||||
return output;
|
||||
}
|
||||
// -------------------------------------------------------
|
||||
// addSample (only in COUNTS mode)
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// instance should have the class at the end.
|
||||
//
|
||||
void addSample(const std::vector<int>& instance, double weight)
|
||||
{
|
||||
//
|
||||
// (A) increment classCounts_
|
||||
// (B) increment feature–class counts => for p(x_i|c)
|
||||
// (C) increment pair (superparent= i, child= j) counts => data_
|
||||
//
|
||||
int c = instance.back();
|
||||
if (weight <= 0.0) {
|
||||
return;
|
||||
}
|
||||
// (A) increment classCounts_
|
||||
classCounts_[c] += weight;
|
||||
|
||||
// (B,C)
|
||||
// We'll store raw counts now and turn them into p(child| c, superparent) later.
|
||||
int idx, fcIndex, sp, sc, i_offset;
|
||||
for (int parent = 0; parent < nFeatures_; ++parent) {
|
||||
sp = instance[parent];
|
||||
// (B) increment feature–class counts => for p(x_i|c)
|
||||
fcIndex = (featureClassOffset_[parent] + sp) * statesClass_ + c;
|
||||
classFeatureCounts_[fcIndex] += weight;
|
||||
// (C) increment pair (superparent= i, child= j) counts => data_
|
||||
i_offset = pairOffset_[featureClassOffset_[parent] + sp];
|
||||
for (int child = 0; child < parent; ++child) {
|
||||
sc = instance[child];
|
||||
idx = (i_offset + featureClassOffset_[child] + sc) * statesClass_ + c;
|
||||
data_[idx] += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------
|
||||
// computeProbabilities
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// Once all samples are added in COUNTS mode, call this to:
|
||||
// 1) compute p(c) => classPriors_
|
||||
// 2) compute p(x_i=si | c) => classFeatureProbs_
|
||||
// 3) compute p(x_j=sj | c, x_i=si) => data_ (for i<j) dataOpp_ (for i>j)
|
||||
//
|
||||
void computeProbabilities()
|
||||
{
|
||||
if (matrixState_ != MatrixState::COUNTS) {
|
||||
throw std::logic_error("computeProbabilities: must be in COUNTS mode.");
|
||||
}
|
||||
double totalCount = std::accumulate(classCounts_.begin(), classCounts_.end(), 0.0);
|
||||
// (1) p(c)
|
||||
if (totalCount <= 0.0) {
|
||||
// fallback => uniform
|
||||
double unif = 1.0 / statesClass_;
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
classPriors_[c] = unif;
|
||||
}
|
||||
} else {
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
classPriors_[c] = (classCounts_[c] + alpha_) / (totalCount + alpha_ * statesClass_);
|
||||
}
|
||||
}
|
||||
// (2) p(x_i=si | c) => classFeatureProbs_
|
||||
int idx, sf;
|
||||
double denom;
|
||||
for (int feature = 0; feature < nFeatures_; ++feature) {
|
||||
sf = states_[feature];
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
denom = classCounts_[c] + alpha_ * sf;
|
||||
for (int sf_value = 0; sf_value < sf; ++sf_value) {
|
||||
idx = (featureClassOffset_[feature] + sf_value) * statesClass_ + c;
|
||||
classFeatureProbs_[idx] = (classFeatureCounts_[idx] + alpha_) / denom;
|
||||
}
|
||||
}
|
||||
}
|
||||
// getCountFromTable(int classVal, int pIndex, int childIndex)
|
||||
// (3) p(x_c=sc | c, x_p=sp) => data_(parent,sp,child,sc,c)
|
||||
// (3) p(x_p=sp | c, x_c=sc) => dataOpp_(child,sc,parent,sp,c)
|
||||
// C(x_c, x_p, c) + alpha_
|
||||
// P(x_p | x_c, c) = -----------------------------------
|
||||
// C(x_c, c) + alpha_
|
||||
double pcc_count, pc_count, cc_count;
|
||||
double conditionalProb, oppositeCondProb;
|
||||
int part1, part2, p1, part2_class, p1_class;
|
||||
for (int parent = 1; parent < nFeatures_; ++parent) {
|
||||
for (int sp = 0; sp < states_[parent]; ++sp) {
|
||||
p1 = featureClassOffset_[parent] + sp;
|
||||
part1 = pairOffset_[p1];
|
||||
p1_class = p1 * statesClass_;
|
||||
for (int child = 0; child < parent; ++child) {
|
||||
for (int sc = 0; sc < states_[child]; ++sc) {
|
||||
part2 = featureClassOffset_[child] + sc;
|
||||
part2_class = part2 * statesClass_;
|
||||
for (int c = 0; c < statesClass_; c++) {
|
||||
idx = (part1 + part2) * statesClass_ + c;
|
||||
// Parent, Child, Class Count
|
||||
pcc_count = data_[idx];
|
||||
// Parent, Class count
|
||||
pc_count = classFeatureCounts_[p1_class + c];
|
||||
// Child, Class count
|
||||
cc_count = classFeatureCounts_[part2_class + c];
|
||||
// p(x_c=sc | c, x_p=sp)
|
||||
conditionalProb = (pcc_count + alpha_) / (pc_count + alpha_ * states_[child]);
|
||||
data_[idx] = conditionalProb;
|
||||
// p(x_p=sp | c, x_c=sc)
|
||||
oppositeCondProb = (pcc_count + alpha_) / (cc_count + alpha_ * states_[parent]);
|
||||
dataOpp_[idx] = oppositeCondProb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
matrixState_ = MatrixState::PROBS;
|
||||
}
|
||||
// -------------------------------------------------------
|
||||
// predict_proba_spode
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// Single-superparent approach:
|
||||
// P(c | x) ∝ p(c) * p(x_sp| c) * ∏_{i≠sp} p(x_i | c, x_sp)
|
||||
//
|
||||
// 'instance' should have size == nFeatures_ (no class).
|
||||
// sp in [0..nFeatures_).
|
||||
// We multiply p(c) * p(x_sp| c) * p(x_i| c, x_sp).
|
||||
// Then normalize the distribution.
|
||||
//
|
||||
std::vector<double> predict_proba_spode(const std::vector<int>& instance, int parent)
|
||||
{
|
||||
// accumulates posterior probabilities for each class
|
||||
auto probs = std::vector<double>(statesClass_);
|
||||
auto spodeProbs = std::vector<double>(statesClass_, 0.0);
|
||||
if (std::find(active_parents.begin(), active_parents.end(), parent) == active_parents.end()) {
|
||||
return spodeProbs;
|
||||
}
|
||||
// Initialize the probabilities with the feature|class probabilities x class priors
|
||||
int localOffset;
|
||||
int sp = instance[parent];
|
||||
localOffset = (featureClassOffset_[parent] + sp) * statesClass_;
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
spodeProbs[c] = classFeatureProbs_[localOffset + c] * classPriors_[c] * initializer_;
|
||||
}
|
||||
int idx, base, sc, parent_offset;
|
||||
for (int child = 0; child < nFeatures_; ++child) {
|
||||
if (child == parent) {
|
||||
continue;
|
||||
}
|
||||
sc = instance[child];
|
||||
if (child > parent) {
|
||||
parent_offset = pairOffset_[featureClassOffset_[child] + sc];
|
||||
base = (parent_offset + featureClassOffset_[parent] + sp) * statesClass_;
|
||||
} else {
|
||||
parent_offset = pairOffset_[featureClassOffset_[parent] + sp];
|
||||
base = (parent_offset + featureClassOffset_[child] + sc) * statesClass_;
|
||||
}
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
/*
|
||||
* The probability P(xc|xp,c) is stored in dataOpp_, and
|
||||
* the probability P(xp|xc,c) is stored in data_
|
||||
*/
|
||||
idx = base + c;
|
||||
double factor = child > parent ? dataOpp_[idx] : data_[idx];
|
||||
// double factor = data_[idx];
|
||||
spodeProbs[c] *= factor;
|
||||
}
|
||||
}
|
||||
// Normalize the probabilities
|
||||
normalize(spodeProbs);
|
||||
return spodeProbs;
|
||||
}
|
||||
int predict_spode(const std::vector<int>& instance, int parent)
|
||||
{
|
||||
auto probs = predict_proba_spode(instance, parent);
|
||||
return (int)std::distance(probs.begin(), std::max_element(probs.begin(), probs.end()));
|
||||
}
|
||||
// -------------------------------------------------------
|
||||
// predict_proba
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// P(c | x) ∝ p(c) * ∏_{i} p(x_i | c) * ∏_{i<j} p(x_j | c, x_i) * p(x_i | c, x_j)
|
||||
//
|
||||
// 'instance' should have size == nFeatures_ (no class).
|
||||
// We multiply p(c) * p(x_i| c) * p(x_j| c, x_i) for all i, j.
|
||||
// Then normalize the distribution.
|
||||
//
|
||||
std::vector<double> predict_proba(const std::vector<int>& instance)
|
||||
{
|
||||
// accumulates posterior probabilities for each class
|
||||
auto probs = std::vector<double>(statesClass_);
|
||||
auto spodeProbs = std::vector<std::vector<double>>(nFeatures_, std::vector<double>(statesClass_));
|
||||
// Initialize the probabilities with the feature|class probabilities
|
||||
int localOffset;
|
||||
for (int feature = 0; feature < nFeatures_; ++feature) {
|
||||
// if feature is not in the active_parents, skip it
|
||||
if (std::find(active_parents.begin(), active_parents.end(), feature) == active_parents.end()) {
|
||||
continue;
|
||||
}
|
||||
localOffset = (featureClassOffset_[feature] + instance[feature]) * statesClass_;
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
spodeProbs[feature][c] = classFeatureProbs_[localOffset + c] * classPriors_[c] * initializer_;
|
||||
}
|
||||
}
|
||||
int idx, base, sp, sc, parent_offset;
|
||||
for (int parent = 1; parent < nFeatures_; ++parent) {
|
||||
// if parent is not in the active_parents, skip it
|
||||
if (std::find(active_parents.begin(), active_parents.end(), parent) == active_parents.end()) {
|
||||
continue;
|
||||
}
|
||||
sp = instance[parent];
|
||||
parent_offset = pairOffset_[featureClassOffset_[parent] + sp];
|
||||
for (int child = 0; child < parent; ++child) {
|
||||
sc = instance[child];
|
||||
if (child > parent) {
|
||||
parent_offset = pairOffset_[featureClassOffset_[child] + sc];
|
||||
base = (parent_offset + featureClassOffset_[parent] + sp) * statesClass_;
|
||||
} else {
|
||||
parent_offset = pairOffset_[featureClassOffset_[parent] + sp];
|
||||
base = (parent_offset + featureClassOffset_[child] + sc) * statesClass_;
|
||||
}
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
/*
|
||||
* The probability P(xc|xp,c) is stored in dataOpp_, and
|
||||
* the probability P(xp|xc,c) is stored in data_
|
||||
*/
|
||||
idx = base + c;
|
||||
double factor_child = child > parent ? data_[idx] : dataOpp_[idx];
|
||||
double factor_parent = child > parent ? dataOpp_[idx] : data_[idx];
|
||||
spodeProbs[child][c] *= factor_child;
|
||||
spodeProbs[parent][c] *= factor_parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* add all the probabilities for each class */
|
||||
for (int c = 0; c < statesClass_; ++c) {
|
||||
for (int i = 0; i < nFeatures_; ++i) {
|
||||
probs[c] += spodeProbs[i][c] * significance_models_[i];
|
||||
}
|
||||
}
|
||||
// Normalize the probabilities
|
||||
normalize(probs);
|
||||
return probs;
|
||||
}
|
||||
void normalize(std::vector<double>& probs) const
|
||||
{
|
||||
double sum = std::accumulate(probs.begin(), probs.end(), 0.0);
|
||||
if (std::isnan(sum)) {
|
||||
throw std::runtime_error("Can't normalize array. Sum is NaN.");
|
||||
}
|
||||
if (sum == 0) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < (int)probs.size(); i++) {
|
||||
probs[i] /= sum;
|
||||
}
|
||||
}
|
||||
// Returns current mode: INIT, COUNTS or PROBS
|
||||
MatrixState state() const
|
||||
{
|
||||
return matrixState_;
|
||||
}
|
||||
int statesClass() const
|
||||
{
|
||||
return statesClass_;
|
||||
}
|
||||
int nFeatures() const
|
||||
{
|
||||
return nFeatures_;
|
||||
}
|
||||
int getNumberOfStates() const
|
||||
{
|
||||
return std::accumulate(states_.begin(), states_.end(), 0) * nFeatures_;
|
||||
}
|
||||
int getNumberOfEdges() const
|
||||
{
|
||||
return nFeatures_ * (2 * nFeatures_ - 1);
|
||||
}
|
||||
int getNumberOfNodes() const
|
||||
{
|
||||
return (nFeatures_ + 1) * nFeatures_;
|
||||
}
|
||||
void add_active_parent(int active_parent)
|
||||
{
|
||||
active_parents.push_back(active_parent);
|
||||
}
|
||||
void remove_last_parent()
|
||||
{
|
||||
active_parents.pop_back();
|
||||
}
|
||||
|
||||
private:
|
||||
// -----------
|
||||
// MEMBER DATA
|
||||
// -----------
|
||||
std::vector<int> states_; // [states_feat0, ..., states_feat(n-1), statesClass_]
|
||||
int nFeatures_;
|
||||
int statesClass_;
|
||||
|
||||
// data_ means p(child=sj | c, superparent= si) after normalization.
|
||||
// But in COUNTS mode, it accumulates raw counts.
|
||||
std::vector<int> pairOffset_;
|
||||
// data_ stores p(child=sj | c, superparent=si) for each pair (i<j).
|
||||
std::vector<double> data_;
|
||||
// dataOpp_ stores p(superparent=si | c, child=sj) for each pair (i<j).
|
||||
std::vector<double> dataOpp_;
|
||||
|
||||
// classCounts_[c]
|
||||
std::vector<double> classCounts_;
|
||||
std::vector<double> classPriors_; // => p(c)
|
||||
|
||||
// For p(x_i=si| c), we store counts in classFeatureCounts_ => offset by featureClassOffset_[i]
|
||||
std::vector<int> featureClassOffset_;
|
||||
std::vector<double> classFeatureCounts_;
|
||||
std::vector<double> classFeatureProbs_; // => p(x_i=si | c) after normalization
|
||||
|
||||
MatrixState matrixState_;
|
||||
|
||||
double alpha_ = 1.0; // Laplace smoothing
|
||||
double initializer_ = 1.0;
|
||||
std::vector<int> active_parents;
|
||||
};
|
||||
}
|
||||
#endif // XAODE_H
|
@@ -2,9 +2,10 @@
|
||||
#include <cstddef>
|
||||
#include "common/DotEnv.h"
|
||||
#include "common/Paths.h"
|
||||
#include "common/DotEnv.h"
|
||||
#include "common/Colors.h"
|
||||
#include "GridBase.h"
|
||||
|
||||
|
||||
namespace platform {
|
||||
|
||||
GridBase::GridBase(struct ConfigGrid& config)
|
||||
@@ -63,13 +64,11 @@ namespace platform {
|
||||
* This way a task consists in process all combinations of hyperparameters for a dataset, seed and fold
|
||||
*/
|
||||
auto tasks = json::array();
|
||||
auto grid = GridData(Paths::grid_input(config.model));
|
||||
auto all_datasets = datasets.getNames();
|
||||
auto datasets_names = filterDatasets(datasets);
|
||||
for (int idx_dataset = 0; idx_dataset < datasets_names.size(); ++idx_dataset) {
|
||||
auto dataset = datasets_names[idx_dataset];
|
||||
for (const auto& seed : config.seeds) {
|
||||
auto combinations = grid.getGrid(dataset);
|
||||
for (int n_fold = 0; n_fold < config.n_folds; n_fold++) {
|
||||
json task = {
|
||||
{ "dataset", dataset },
|
||||
@@ -312,4 +311,4 @@ namespace platform {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,12 @@
|
||||
#ifndef GRIDBASE_H
|
||||
#define GRIDBASE_H
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mpi.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/Datasets.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/Colors.h"
|
||||
#include "common/Timer.hpp"
|
||||
#include "main/HyperParameters.h"
|
||||
#include "GridData.h"
|
||||
#include "GridConfig.h"
|
||||
#include "bayesnet/network/Network.h"
|
||||
|
||||
|
||||
namespace platform {
|
||||
@@ -40,4 +36,4 @@ namespace platform {
|
||||
bayesnet::Smoothing_t smooth_type{ bayesnet::Smoothing_t::NONE };
|
||||
};
|
||||
} /* namespace platform */
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -5,7 +5,7 @@
|
||||
#include <mpi.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/Datasets.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/Timer.hpp"
|
||||
#include "main/HyperParameters.h"
|
||||
#include "GridData.h"
|
||||
#include "GridConfig.h"
|
||||
|
@@ -1,18 +1,14 @@
|
||||
#ifndef GRIDEXPERIMENT_H
|
||||
#define GRIDEXPERIMENT_H
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mpi.h>
|
||||
#include <argparse/argparse.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/Datasets.h"
|
||||
#include "common/DotEnv.h"
|
||||
#include "main/Experiment.h"
|
||||
#include "main/HyperParameters.h"
|
||||
#include "main/ArgumentsExperiment.h"
|
||||
#include "GridData.h"
|
||||
#include "GridBase.h"
|
||||
#include "bayesnet/network/Network.h"
|
||||
|
||||
|
||||
namespace platform {
|
||||
@@ -39,4 +35,4 @@ namespace platform {
|
||||
void consumer_go(struct ConfigGrid& config, struct ConfigMPI& config_mpi, json& tasks, int n_task, Datasets& datasets, Task_Result* result);
|
||||
};
|
||||
} /* namespace platform */
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -1,10 +1,10 @@
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <torch/torch.h>
|
||||
#include <folding.hpp>
|
||||
#include "main/Models.h"
|
||||
#include "common/Paths.h"
|
||||
#include "common/Utils.h"
|
||||
#include "common/Colors.h"
|
||||
#include "GridSearch.h"
|
||||
|
||||
namespace platform {
|
||||
@@ -256,4 +256,4 @@ namespace platform {
|
||||
//
|
||||
std::cout << get_color_rank(config_mpi.rank) << std::flush;
|
||||
}
|
||||
} /* namespace platform */
|
||||
} /* namespace platform */
|
||||
|
@@ -6,7 +6,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <folding.hpp>
|
||||
#include "common/Datasets.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/Timer.hpp"
|
||||
#include "main/HyperParameters.h"
|
||||
#include "GridData.h"
|
||||
#include "GridBase.h"
|
||||
|
@@ -178,6 +178,11 @@ namespace platform {
|
||||
}
|
||||
}
|
||||
filesToTest = file_names;
|
||||
sort(filesToTest.begin(), filesToTest.end(), [](const auto& lhs, const auto& rhs) {
|
||||
const auto result = mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const auto& lhs, const auto& rhs) {return tolower(lhs) == tolower(rhs);});
|
||||
|
||||
return result.second != rhs.cend() && (result.first == lhs.cend() || tolower(*result.first) < tolower(*result.second));
|
||||
});
|
||||
saveResults = true;
|
||||
if (title == "") {
|
||||
title = "Test " + to_string(file_names.size()) + " datasets " + model_name + " " + to_string(n_folds) + " folds";
|
||||
|
@@ -82,8 +82,6 @@ namespace platform {
|
||||
std::cout << Colors::RESET() << std::endl;
|
||||
}
|
||||
int num = 0;
|
||||
// Sort files to test to have a consistent order even if --datasets is used
|
||||
std::stable_sort(filesToTest.begin(), filesToTest.end());
|
||||
for (auto fileName : filesToTest) {
|
||||
if (!quiet)
|
||||
std::cout << " " << setw(3) << right << num++ << " " << setw(max_name) << left << fileName << right << flush;
|
||||
|
@@ -5,11 +5,15 @@
|
||||
#include <bayesnet/ensembles/AODE.h>
|
||||
#include <bayesnet/ensembles/A2DE.h>
|
||||
#include <bayesnet/ensembles/AODELd.h>
|
||||
#include <bayesnet/ensembles/XBAODE.h>
|
||||
#include <bayesnet/ensembles/XBA2DE.h>
|
||||
#include <bayesnet/ensembles/BoostAODE.h>
|
||||
#include <bayesnet/ensembles/BoostA2DE.h>
|
||||
#include <bayesnet/classifiers/TAN.h>
|
||||
#include <bayesnet/classifiers/KDB.h>
|
||||
#include <bayesnet/classifiers/SPODE.h>
|
||||
#include <bayesnet/classifiers/XSPODE.h>
|
||||
#include <bayesnet/classifiers/XSP2DE.h>
|
||||
#include <bayesnet/classifiers/SPnDE.h>
|
||||
#include <bayesnet/classifiers/TANLd.h>
|
||||
#include <bayesnet/classifiers/KDBLd.h>
|
||||
@@ -20,6 +24,8 @@
|
||||
#include <pyclassifiers/SVC.h>
|
||||
#include <pyclassifiers/XGBoost.h>
|
||||
#include <pyclassifiers/RandomForest.h>
|
||||
#include "../experimental_clfs/XA1DE.h"
|
||||
|
||||
namespace platform {
|
||||
class Models {
|
||||
public:
|
||||
@@ -42,4 +48,4 @@ namespace platform {
|
||||
Registrar(const std::string& className, function<bayesnet::BaseClassifier* (void)> classFactoryFunction);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -1,39 +1,49 @@
|
||||
#ifndef MODELREGISTER_H
|
||||
#define MODELREGISTER_H
|
||||
|
||||
static platform::Registrar registrarT("TAN",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::TAN();});
|
||||
static platform::Registrar registrarTLD("TANLd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::TANLd();});
|
||||
static platform::Registrar registrarS("SPODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODE(2);});
|
||||
static platform::Registrar registrarSn("SPnDE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPnDE({ 0, 1 });});
|
||||
static platform::Registrar registrarSLD("SPODELd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODELd(2);});
|
||||
static platform::Registrar registrarK("KDB",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDB(2);});
|
||||
static platform::Registrar registrarKLD("KDBLd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDBLd(2);});
|
||||
static platform::Registrar registrarA("AODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODE();});
|
||||
static platform::Registrar registrarA2("A2DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::A2DE();});
|
||||
static platform::Registrar registrarALD("AODELd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODELd();});
|
||||
static platform::Registrar registrarBA("BoostAODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::BoostAODE();});
|
||||
static platform::Registrar registrarBA2("BoostA2DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::BoostA2DE();});
|
||||
static platform::Registrar registrarSt("STree",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::STree();});
|
||||
static platform::Registrar registrarOdte("Odte",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::ODTE();});
|
||||
static platform::Registrar registrarSvc("SVC",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::SVC();});
|
||||
static platform::Registrar registrarRaF("RandomForest",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::RandomForest();});
|
||||
static platform::Registrar registrarXGB("XGBoost",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::XGBoost();});
|
||||
|
||||
#endif
|
||||
namespace platform {
|
||||
static Registrar registrarT("TAN",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::TAN();});
|
||||
static Registrar registrarTLD("TANLd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::TANLd();});
|
||||
static Registrar registrarS("SPODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODE(2);});
|
||||
static Registrar registrarSn("SPnDE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPnDE({ 0, 1 });});
|
||||
static Registrar registrarSLD("SPODELd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODELd(2);});
|
||||
static Registrar registrarK("KDB",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDB(2);});
|
||||
static Registrar registrarKLD("KDBLd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDBLd(2);});
|
||||
static Registrar registrarA("AODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODE();});
|
||||
static Registrar registrarA2("A2DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::A2DE();});
|
||||
static Registrar registrarALD("AODELd",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODELd();});
|
||||
static Registrar registrarBA("BoostAODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::BoostAODE();});
|
||||
static Registrar registrarBA2("BoostA2DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::BoostA2DE();});
|
||||
static Registrar registrarSt("STree",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::STree();});
|
||||
static Registrar registrarOdte("Odte",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::ODTE();});
|
||||
static Registrar registrarSvc("SVC",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::SVC();});
|
||||
static Registrar registrarRaF("RandomForest",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::RandomForest();});
|
||||
static Registrar registrarXGB("XGBoost",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new pywrap::XGBoost();});
|
||||
static Registrar registrarXSPODE("XSPODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::XSpode(0);});
|
||||
static Registrar registrarXSP2DE("XSP2DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::XSp2de(0, 1);});
|
||||
static Registrar registrarXBAODE("XBAODE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::XBAODE();});
|
||||
static Registrar registrarXBA2DE("XBA2DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::XBA2DE();});
|
||||
static Registrar registrarXA1DE("XA1DE",
|
||||
[](void) -> bayesnet::BaseClassifier* { return new XA1DE();});
|
||||
}
|
||||
#endif
|
||||
|
@@ -82,10 +82,12 @@ namespace platform {
|
||||
workbook_close(workbook);
|
||||
}
|
||||
if (didExcel) {
|
||||
std::cout << Colors::MAGENTA() << "Excel file created: " << Paths::excel() + Paths::excelResults() << std::endl;
|
||||
excelFileName = Paths::excel() + Paths::excelResults();
|
||||
std::cout << Colors::MAGENTA() << "Excel file created: " << excelFileName << std::endl;
|
||||
}
|
||||
std::cout << Colors::RESET() << "Done!" << std::endl;
|
||||
}
|
||||
|
||||
std::string ManageScreen::getVersions()
|
||||
{
|
||||
std::string kfold_version = folding::KFold(5, 100).version();
|
||||
@@ -487,20 +489,19 @@ namespace platform {
|
||||
index_A = index;
|
||||
list("A set to " + std::to_string(index), Colors::GREEN());
|
||||
break;
|
||||
case 'B': // set_b or back to list
|
||||
if (output_type == OutputType::EXPERIMENTS) {
|
||||
if (index == index_A) {
|
||||
list("A and B cannot be the same!", Colors::RED());
|
||||
break;
|
||||
}
|
||||
index_B = index;
|
||||
list("B set to " + std::to_string(index), Colors::GREEN());
|
||||
} else {
|
||||
// back to show the report
|
||||
output_type = OutputType::RESULT;
|
||||
paginator[static_cast<int>(OutputType::DETAIL)].setPage(1);
|
||||
list(STATUS_OK, STATUS_COLOR);
|
||||
case 'B': // set_b
|
||||
if (index == index_A) {
|
||||
list("A and B cannot be the same!", Colors::RED());
|
||||
break;
|
||||
}
|
||||
index_B = index;
|
||||
list("B set to " + std::to_string(index), Colors::GREEN());
|
||||
break;
|
||||
case 'b': // back to list
|
||||
// back to show the report
|
||||
output_type = OutputType::RESULT;
|
||||
paginator[static_cast<int>(OutputType::DETAIL)].setPage(1);
|
||||
list(STATUS_OK, STATUS_COLOR);
|
||||
break;
|
||||
case 'c':
|
||||
if (index_A == -1 || index_B == -1) {
|
||||
|
@@ -19,6 +19,7 @@ namespace platform {
|
||||
~ManageScreen() = default;
|
||||
void doMenu();
|
||||
void updateSize(int rows, int cols);
|
||||
std::string getExcelFileName() const { return excelFileName; }
|
||||
private:
|
||||
void list(const std::string& status, const std::string& color);
|
||||
void list_experiments(const std::string& status, const std::string& color);
|
||||
@@ -58,6 +59,7 @@ namespace platform {
|
||||
std::vector<Paginator> paginator;
|
||||
ResultsManager results;
|
||||
lxw_workbook* workbook;
|
||||
std::string excelFileName;
|
||||
};
|
||||
}
|
||||
#endif
|
@@ -2,7 +2,7 @@
|
||||
#include <locale>
|
||||
#include "best/BestScore.h"
|
||||
#include "common/CLocale.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/Timer.hpp"
|
||||
#include "ReportConsole.h"
|
||||
#include "main/Scores.h"
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace platform {
|
||||
}
|
||||
std::vector<std::string> header_labels = { " #", "Dataset", "Sampl.", "Feat.", "Cls", nodes_label, leaves_label, depth_label, "Score", "Time", "Hyperparameters" };
|
||||
sheader << Colors::GREEN();
|
||||
std::vector<int> header_lengths = { 3, maxDataset, 6, 5, 3, 9, 9, 9, 15, 20, maxHyper };
|
||||
std::vector<int> header_lengths = { 3, maxDataset, 6, 6, 3, 13, 13, 13, 15, 20, maxHyper };
|
||||
for (int i = 0; i < header_labels.size(); i++) {
|
||||
sheader << std::setw(header_lengths[i]) << std::left << header_labels[i] << " ";
|
||||
}
|
||||
@@ -108,11 +108,11 @@ namespace platform {
|
||||
line << std::setw(3) << std::right << index++ << " ";
|
||||
line << std::setw(maxDataset) << std::left << r["dataset"].get<std::string>() << " ";
|
||||
line << std::setw(6) << std::right << r["samples"].get<int>() << " ";
|
||||
line << std::setw(5) << std::right << r["features"].get<int>() << " ";
|
||||
line << std::setw(6) << std::right << r["features"].get<int>() << " ";
|
||||
line << std::setw(3) << std::right << r["classes"].get<int>() << " ";
|
||||
line << std::setw(9) << std::setprecision(2) << std::fixed << r["nodes"].get<float>() << " ";
|
||||
line << std::setw(9) << std::setprecision(2) << std::fixed << r["leaves"].get<float>() << " ";
|
||||
line << std::setw(9) << std::setprecision(2) << std::fixed << r["depth"].get<float>() << " ";
|
||||
line << std::setw(13) << std::setprecision(2) << std::fixed << r["nodes"].get<float>() << " ";
|
||||
line << std::setw(13) << std::setprecision(2) << std::fixed << r["leaves"].get<float>() << " ";
|
||||
line << std::setw(13) << std::setprecision(2) << std::fixed << r["depth"].get<float>() << " ";
|
||||
line << std::setw(8) << std::right << std::setprecision(6) << std::fixed << r["score"].get<double>() << "±" << std::setw(6) << std::setprecision(4) << std::fixed << r["score_std"].get<double>();
|
||||
const std::string status = compareResult(r["dataset"].get<std::string>(), r["score"].get<double>());
|
||||
line << status;
|
||||
@@ -251,7 +251,7 @@ namespace platform {
|
||||
if (train_data) {
|
||||
oss << color_line << std::left << std::setw(maxLine) << output_train[i]
|
||||
<< suffix << Colors::BLUE() << " | " << color_line << std::left << std::setw(maxLine)
|
||||
<< output_test[i] << std::endl;
|
||||
<< output_test[i] << std::endl;
|
||||
} else {
|
||||
oss << color_line << output_test[i] << std::endl;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/Timer.h"
|
||||
#include "common/Timer.hpp"
|
||||
#include "main/HyperParameters.h"
|
||||
#include "main/PartialResult.h"
|
||||
|
||||
|
Reference in New Issue
Block a user