Compare commits

...

34 Commits

Author SHA1 Message Date
f26ea1f0ac Add weights to BayesMetrics 2023-08-13 12:56:06 +02:00
af0419c9da First approx with const 1 weights 2023-08-13 00:59:02 +02:00
90c92e5c56 Merge pull request 'Add states as result in Proposal methods' (#3) from optimize_memory into main
Reviewed-on: https://gitea.rmontanana.es:11000/rmontanana/BayesNet/pulls/3
2023-08-12 14:16:55 +00:00
182b52a887 Add states as result in Proposal methods 2023-08-12 16:16:17 +02:00
6679b90a82 Merge pull request 'optimize_memory' (#2) from optimize_memory into main
Reviewed-on: https://gitea.rmontanana.es:11000/rmontanana/BayesNet/pulls/2
2023-08-12 14:15:03 +00:00
405887f833 Solved Ld poor results 2023-08-12 11:49:18 +02:00
3a85481a5a Redo pass states to Network Fit needed in crossval
fix mistake in headerline (report)
2023-08-12 11:10:53 +02:00
0ad5505c16 Spodeld working with poor accuracy 2023-08-10 02:06:18 +02:00
323444b74a const functions 2023-08-08 01:53:41 +02:00
ef1bffcac3 Fixed normal classifiers 2023-08-07 13:50:11 +02:00
06db8f51ce Refactor library and models to lighten data stored
Refactro Ensemble to inherit from Classifier insted of BaseClassifier
2023-08-07 12:49:37 +02:00
e74565ba01 update clang-tidy 2023-08-07 00:44:12 +02:00
2da0fb5d8f Merge branch 'main' into TANNew 2023-08-06 11:40:10 +02:00
14ea51648a Complete AODELd 2023-08-06 11:31:44 +02:00
9e94f4e140 Rename suffix of proposal classifier to Ld 2023-08-05 23:23:31 +02:00
1d0fd629c9 Add SPODENew to models 2023-08-05 23:11:36 +02:00
506ef34c6f Add report output to main 2023-08-05 20:29:05 +02:00
7f45495837 Refactor New classifiers to extract predict 2023-08-05 18:39:48 +02:00
1a09ccca4c Add KDBNew fix computeCPT error 2023-08-05 14:40:42 +02:00
a1c6ab18f3 TANNew restructured with poor results 2023-08-04 20:11:22 +02:00
64ac8fb4f2 TANNew as a TAN variant working 2023-08-04 19:42:18 +02:00
c568ba111d Add Proposal class 2023-08-04 13:05:12 +02:00
45c1d052ac Compile TANNew with poor accuracy 2023-08-04 01:35:45 +02:00
eb1cec58a3 Complete nxm 2023-08-03 20:22:33 +02:00
f520b40016 Almost complete proposal in TANNew 2023-08-02 02:21:55 +02:00
cdfb45d2cb Add topological order to Network 2023-08-02 00:56:52 +02:00
f63a9a64f9 Update Makefile to add Release & Debug build 2023-08-01 19:02:37 +02:00
285f0938a6 Update mdlp library 2023-08-01 17:33:01 +02:00
8f8f9773ce Make TANNew same as TAN with local discretization 2023-08-01 13:17:12 +02:00
a9ba21560d Add environment platform to experiment result 2023-08-01 10:55:53 +02:00
a18fbe5594 Begin implementation 2023-07-31 19:53:55 +02:00
adf650d257 Max threading 2023-07-31 18:49:18 +02:00
43bb017d5d Fix problem with tensors way 2023-07-30 19:00:02 +02:00
53697648e7 Merge branch 'aftermath' into main 2023-07-30 01:05:31 +02:00
51 changed files with 1071 additions and 409 deletions

View File

@@ -13,5 +13,4 @@ HeaderFilterRegex: 'src/*'
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
WarningsAsErrors: '' WarningsAsErrors: ''
FormatStyle: file FormatStyle: file
FormatStyleOptions: ''
... ...

19
.vscode/launch.json vendored
View File

@@ -10,12 +10,13 @@
"-d", "-d",
"iris", "iris",
"-m", "-m",
"TAN", "KDB",
"-s",
"271",
"-p", "-p",
"../../data/", "/Users/rmontanana/Code/discretizbench/datasets/",
"--tensors"
], ],
"cwd": "${workspaceFolder}/build/sample/", //"cwd": "${workspaceFolder}/build/sample/",
}, },
{ {
"type": "lldb", "type": "lldb",
@@ -24,17 +25,15 @@
"program": "${workspaceFolder}/build/src/Platform/main", "program": "${workspaceFolder}/build/src/Platform/main",
"args": [ "args": [
"-m", "-m",
"TAN", "SPODE",
"-p", "-p",
"/Users/rmontanana/Code/discretizbench/datasets", "/Users/rmontanana/Code/discretizbench/datasets",
"--discretize",
"--stratified", "--stratified",
"--title", "--discretize",
"Debug test",
"-d", "-d",
"ionosphere" "letter"
], ],
"cwd": "${workspaceFolder}/build/src/Platform", "cwd": "/Users/rmontanana/Code/discretizbench",
}, },
{ {
"name": "Build & debug active file", "name": "Build & debug active file",

View File

@@ -7,10 +7,14 @@ project(BayesNet
LANGUAGES CXX LANGUAGES CXX
) )
if (CODE_COVERAGE AND NOT ENABLE_TESTING)
MESSAGE(FATAL_ERROR "Code coverage requires testing enabled")
endif (CODE_COVERAGE AND NOT ENABLE_TESTING)
find_package(Torch REQUIRED) find_package(Torch REQUIRED)
if (POLICY CMP0135) if (POLICY CMP0135)
cmake_policy(SET CMP0135 NEW) cmake_policy(SET CMP0135 NEW)
endif () endif ()
# Global CMake variables # Global CMake variables
@@ -24,24 +28,31 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
# Options # Options
# ------- # -------
option(ENABLE_CLANG_TIDY "Enable to add clang tidy." OFF) option(ENABLE_CLANG_TIDY "Enable to add clang tidy." OFF)
option(ENABLE_TESTING "Unit testing build" ON) option(ENABLE_TESTING "Unit testing build" OFF)
option(CODE_COVERAGE "Collect coverage from test library" ON) option(CODE_COVERAGE "Collect coverage from test library" OFF)
set(CMAKE_BUILD_TYPE "Debug")
# CMakes modules # CMakes modules
# -------------- # --------------
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(AddGitSubmodule) include(AddGitSubmodule)
include(StaticAnalyzers) # clang-tidy if (CODE_COVERAGE)
include(CodeCoverage) enable_testing()
include(CodeCoverage)
MESSAGE("Code coverage enabled")
set(CMAKE_C_FLAGS " ${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
SET(GCC_COVERAGE_LINK_FLAGS " ${GCC_COVERAGE_LINK_FLAGS} -lgcov --coverage")
endif (CODE_COVERAGE)
if (ENABLE_CLANG_TIDY)
include(StaticAnalyzers) # clang-tidy
endif (ENABLE_CLANG_TIDY)
# External libraries - dependencies of BayesNet # External libraries - dependencies of BayesNet
# --------------------------------------------- # ---------------------------------------------
# include(FetchContent) # include(FetchContent)
add_git_submodule("lib/mdlp") add_git_submodule("lib/mdlp")
add_git_submodule("lib/catch2")
add_git_submodule("lib/argparse") add_git_submodule("lib/argparse")
add_git_submodule("lib/json") add_git_submodule("lib/json")
@@ -59,18 +70,11 @@ file(GLOB Platform_SOURCES CONFIGURE_DEPENDS ${BayesNet_SOURCE_DIR}/src/Platform
# Testing # Testing
# ------- # -------
if (ENABLE_TESTING) if (ENABLE_TESTING)
MESSAGE("Testing enabled") MESSAGE("Testing enabled")
enable_testing() add_git_submodule("lib/catch2")
if (CODE_COVERAGE)
#include(CodeCoverage)
MESSAGE("Code coverage enabled")
set(CMAKE_C_FLAGS " ${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
SET(GCC_COVERAGE_LINK_FLAGS " ${GCC_COVERAGE_LINK_FLAGS} -lgcov --coverage")
endif (CODE_COVERAGE)
#find_package(Catch2 3 REQUIRED)
include(CTest) include(CTest)
#include(Catch)
add_subdirectory(tests) add_subdirectory(tests)
endif (ENABLE_TESTING) endif (ENABLE_TESTING)

View File

@@ -14,16 +14,30 @@ setup: ## Install dependencies for tests and coverage
dependency: ## Create a dependency graph diagram of the project (build/dependency.png) dependency: ## Create a dependency graph diagram of the project (build/dependency.png)
cd build && cmake .. --graphviz=dependency.dot && dot -Tpng dependency.dot -o dependency.png cd build && cmake .. --graphviz=dependency.dot && dot -Tpng dependency.dot -o dependency.png
build: ## Build the project build: ## Build the main and BayesNetSample
@echo ">>> Building BayesNet ..."; cmake --build build -t main -t BayesNetSample -j 32
clean: ## Clean the debug info
@echo ">>> Cleaning Debug BayesNet ...";
find . -name "*.gcda" -print0 | xargs -0 rm
@echo ">>> Done";
debug: ## Build a debug version of the project
@echo ">>> Building Debug BayesNet ...";
@if [ -d ./build ]; then rm -rf ./build; fi @if [ -d ./build ]; then rm -rf ./build; fi
@mkdir build; @mkdir build;
cmake -S . -B build; \ cmake -S . -B build -D CMAKE_BUILD_TYPE=Debug -D ENABLE_TESTING=ON -D CODE_COVERAGE=ON; \
cd build; \ cmake --build build -j 32;
make; \
@echo ">>> Done"; @echo ">>> Done";
release: ## Build a Release version of the project
@echo ">>> Building Release BayesNet ...";
@if [ -d ./build ]; then rm -rf ./build; fi
@mkdir build;
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release; \
cmake --build build -t main -t BayesNetSample -j 32;
@echo ">>> Done";
test: ## Run tests test: ## Run tests
@echo "* Running tests..."; @echo "* Running tests...";
find . -name "*.gcda" -print0 | xargs -0 rm find . -name "*.gcda" -print0 | xargs -0 rm

12
TAN_iris.dot Normal file
View File

@@ -0,0 +1,12 @@
digraph BayesNet {
label=<BayesNet >
fontsize=30
fontcolor=blue
labelloc=t
layout=circo
class [shape=circle, fontcolor=red, fillcolor=lightblue, style=filled ]
class -> sepallength class -> sepalwidth class -> petallength class -> petalwidth petallength [shape=circle]
petallength -> sepallength petalwidth [shape=circle]
sepallength [shape=circle]
sepallength -> sepalwidth sepalwidth [shape=circle]
sepalwidth -> petalwidth }

View File

@@ -1,5 +1,4 @@
filter = src/ filter = src/
exclude = external/ exclude-directories = build/lib/
exclude = tests/
print-summary = yes print-summary = yes
sort-percentage = yes sort-percentage = yes

View File

@@ -1,7 +1,6 @@
#include <iostream> #include <iostream>
#include <torch/torch.h> #include <torch/torch.h>
#include <string> #include <string>
#include <thread>
#include <map> #include <map>
#include <argparse/argparse.hpp> #include <argparse/argparse.hpp>
#include "ArffFiles.h" #include "ArffFiles.h"
@@ -42,7 +41,7 @@ bool file_exists(const std::string& name)
} }
pair<vector<vector<int>>, vector<int>> extract_indices(vector<int> indices, vector<vector<int>> X, vector<int> y) pair<vector<vector<int>>, vector<int>> extract_indices(vector<int> indices, vector<vector<int>> X, vector<int> y)
{ {
vector<vector<int>> Xr; vector<vector<int>> Xr; // nxm
vector<int> yr; vector<int> yr;
for (int col = 0; col < X.size(); ++col) { for (int col = 0; col < X.size(); ++col) {
Xr.push_back(vector<int>()); Xr.push_back(vector<int>());
@@ -96,6 +95,7 @@ int main(int argc, char** argv)
} }
); );
program.add_argument("--discretize").help("Discretize input dataset").default_value(false).implicit_value(true); program.add_argument("--discretize").help("Discretize input dataset").default_value(false).implicit_value(true);
program.add_argument("--dumpcpt").help("Dump CPT Tables").default_value(false).implicit_value(true);
program.add_argument("--stratified").help("If Stratified KFold is to be done").default_value(false).implicit_value(true); program.add_argument("--stratified").help("If Stratified KFold is to be done").default_value(false).implicit_value(true);
program.add_argument("--tensors").help("Use tensors to store samples").default_value(false).implicit_value(true); program.add_argument("--tensors").help("Use tensors to store samples").default_value(false).implicit_value(true);
program.add_argument("-f", "--folds").help("Number of folds").default_value(5).scan<'i', int>().action([](const string& value) { program.add_argument("-f", "--folds").help("Number of folds").default_value(5).scan<'i', int>().action([](const string& value) {
@@ -113,7 +113,7 @@ int main(int argc, char** argv)
throw runtime_error("Number of folds must be an integer"); throw runtime_error("Number of folds must be an integer");
}}); }});
program.add_argument("-s", "--seed").help("Random seed").default_value(-1).scan<'i', int>(); program.add_argument("-s", "--seed").help("Random seed").default_value(-1).scan<'i', int>();
bool class_last, stratified, tensors; bool class_last, stratified, tensors, dump_cpt;
string model_name, file_name, path, complete_file_name; string model_name, file_name, path, complete_file_name;
int nFolds, seed; int nFolds, seed;
try { try {
@@ -126,6 +126,7 @@ int main(int argc, char** argv)
tensors = program.get<bool>("tensors"); tensors = program.get<bool>("tensors");
nFolds = program.get<int>("folds"); nFolds = program.get<int>("folds");
seed = program.get<int>("seed"); seed = program.get<int>("seed");
dump_cpt = program.get<bool>("dumpcpt");
class_last = datasets[file_name]; class_last = datasets[file_name];
if (!file_exists(complete_file_name)) { if (!file_exists(complete_file_name)) {
throw runtime_error("Data File " + path + file_name + ".arff" + " does not exist"); throw runtime_error("Data File " + path + file_name + ".arff" + " does not exist");
@@ -161,61 +162,75 @@ int main(int argc, char** argv)
states[className] = vector<int>(maxes[className]); states[className] = vector<int>(maxes[className]);
auto clf = platform::Models::instance()->create(model_name); auto clf = platform::Models::instance()->create(model_name);
clf->fit(Xd, y, features, className, states); clf->fit(Xd, y, features, className, states);
auto score = clf->score(Xd, y); if (dump_cpt) {
cout << "--- CPT Tables ---" << endl;
clf->dump_cpt();
}
auto lines = clf->show(); auto lines = clf->show();
auto graph = clf->graph();
for (auto line : lines) { for (auto line : lines) {
cout << line << endl; cout << line << endl;
} }
cout << "--- Topological Order ---" << endl;
auto order = clf->topological_order();
for (auto name : order) {
cout << name << ", ";
}
cout << "end." << endl;
auto score = clf->score(Xd, y);
cout << "Score: " << score << endl; cout << "Score: " << score << endl;
auto dot_file = model_name + "_" + file_name; // auto graph = clf->graph();
ofstream file(dot_file + ".dot"); // auto dot_file = model_name + "_" + file_name;
file << graph; // ofstream file(dot_file + ".dot");
file.close(); // file << graph;
cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl; // file.close();
cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl; // cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl;
string stratified_string = stratified ? " Stratified" : ""; // cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl;
cout << nFolds << " Folds" << stratified_string << " Cross validation" << endl; // string stratified_string = stratified ? " Stratified" : "";
cout << "==========================================" << endl; // cout << nFolds << " Folds" << stratified_string << " Cross validation" << endl;
torch::Tensor Xt = torch::zeros({ static_cast<int>(Xd.size()), static_cast<int>(Xd[0].size()) }, torch::kInt32); // cout << "==========================================" << endl;
torch::Tensor yt = torch::tensor(y, torch::kInt32); // torch::Tensor Xt = torch::zeros({ static_cast<int>(Xd.size()), static_cast<int>(Xd[0].size()) }, torch::kInt32);
for (int i = 0; i < features.size(); ++i) { // torch::Tensor yt = torch::tensor(y, torch::kInt32);
Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], 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; // }
Fold* fold; // float total_score = 0, total_score_train = 0, score_train, score_test;
if (stratified) // Fold* fold;
fold = new StratifiedKFold(nFolds, y, seed); // if (stratified)
else // fold = new StratifiedKFold(nFolds, y, seed);
fold = new KFold(nFolds, y.size(), seed); // else
for (auto i = 0; i < nFolds; ++i) { // fold = new KFold(nFolds, y.size(), seed);
auto [train, test] = fold->getFold(i); // for (auto i = 0; i < nFolds; ++i) {
cout << "Fold: " << i + 1 << endl; // auto [train, test] = fold->getFold(i);
if (tensors) { // cout << "Fold: " << i + 1 << endl;
auto ttrain = torch::tensor(train, torch::kInt64); // if (tensors) {
auto ttest = torch::tensor(test, torch::kInt64); // auto ttrain = torch::tensor(train, torch::kInt64);
torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain); // auto ttest = torch::tensor(test, torch::kInt64);
torch::Tensor ytraint = yt.index({ ttrain }); // torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain);
torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest); // torch::Tensor ytraint = yt.index({ ttrain });
torch::Tensor ytestt = yt.index({ ttest }); // torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest);
clf->fit(Xtraint, ytraint, features, className, states); // torch::Tensor ytestt = yt.index({ ttest });
score_train = clf->score(Xtraint, ytraint); // clf->fit(Xtraint, ytraint, features, className, states);
score_test = clf->score(Xtestt, ytestt); // auto temp = clf->predict(Xtraint);
} else { // score_train = clf->score(Xtraint, ytraint);
auto [Xtrain, ytrain] = extract_indices(train, Xd, y); // score_test = clf->score(Xtestt, ytestt);
auto [Xtest, ytest] = extract_indices(test, Xd, y); // } else {
clf->fit(Xtrain, ytrain, features, className, states); // auto [Xtrain, ytrain] = extract_indices(train, Xd, y);
score_train = clf->score(Xtrain, ytrain); // auto [Xtest, ytest] = extract_indices(test, Xd, y);
score_test = clf->score(Xtest, ytest); // clf->fit(Xtrain, ytrain, features, className, states);
} // score_train = clf->score(Xtrain, ytrain);
total_score_train += score_train; // score_test = clf->score(Xtest, ytest);
total_score += score_test; // }
cout << "Score Train: " << score_train << endl; // if (dump_cpt) {
cout << "Score Test : " << score_test << endl; // cout << "--- CPT Tables ---" << endl;
cout << "-------------------------------------------------------------------------------" << endl; // clf->dump_cpt();
} // }
cout << "**********************************************************************************" << endl; // total_score_train += score_train;
cout << "Average Score Train: " << total_score_train / nFolds << endl; // total_score += score_test;
cout << "Average Score Test : " << total_score / nFolds << endl; // cout << "Score Train: " << score_train << endl;
return 0; // cout << "Score Test : " << score_test << endl;
// cout << "-------------------------------------------------------------------------------" << endl;
// }
// cout << "**********************************************************************************" << endl;
// cout << "Average Score Train: " << total_score_train / nFolds << endl;
// cout << "Average Score Test : " << total_score / nFolds << endl;return 0;
} }

View File

@@ -2,14 +2,14 @@
namespace bayesnet { namespace bayesnet {
AODE::AODE() : Ensemble() {} AODE::AODE() : Ensemble() {}
void AODE::train() void AODE::buildModel()
{ {
models.clear(); models.clear();
for (int i = 0; i < features.size(); ++i) { for (int i = 0; i < features.size(); ++i) {
models.push_back(std::make_unique<SPODE>(i)); models.push_back(std::make_unique<SPODE>(i));
} }
} }
vector<string> AODE::graph(string title) vector<string> AODE::graph(const string& title) const
{ {
return Ensemble::graph(title); return Ensemble::graph(title);
} }

View File

@@ -5,11 +5,11 @@
namespace bayesnet { namespace bayesnet {
class AODE : public Ensemble { class AODE : public Ensemble {
protected: protected:
void train() override; void buildModel() override;
public: public:
AODE(); AODE();
virtual ~AODE() {}; virtual ~AODE() {};
vector<string> graph(string title = "AODE") override; vector<string> graph(const string& title = "AODE") const override;
}; };
} }
#endif #endif

40
src/BayesNet/AODELd.cc Normal file
View File

@@ -0,0 +1,40 @@
#include "AODELd.h"
#include "Models.h"
namespace bayesnet {
using namespace std;
AODELd::AODELd() : Ensemble(), Proposal(dataset, features, className) {}
AODELd& AODELd::fit(torch::Tensor& X_, torch::Tensor& y_, vector<string>& features_, string className_, map<string, vector<int>>& states_)
{
// This first part should go in a Classifier method called fit_local_discretization o fit_float...
features = features_;
className = className_;
Xf = X_;
y = y_;
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
states = fit_local_discretization(y);
// We have discretized the input data
// 1st we need to fit the model to build the normal TAN structure, TAN::fit initializes the base Bayesian network
Ensemble::fit(dataset, features, className, states);
return *this;
}
void AODELd::buildModel()
{
models.clear();
for (int i = 0; i < features.size(); ++i) {
models.push_back(std::make_unique<SPODELd>(i));
}
n_models = models.size();
}
void AODELd::trainModel()
{
for (const auto& model : models) {
model->fit(Xf, y, features, className, states);
}
}
vector<string> AODELd::graph(const string& name) const
{
return Ensemble::graph(name);
}
}

21
src/BayesNet/AODELd.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef AODELD_H
#define AODELD_H
#include "Ensemble.h"
#include "Proposal.h"
#include "SPODELd.h"
namespace bayesnet {
using namespace std;
class AODELd : public Ensemble, public Proposal {
protected:
void trainModel() override;
void buildModel() override;
public:
AODELd();
AODELd& fit(torch::Tensor& X_, torch::Tensor& y_, vector<string>& features_, string className_, map<string, vector<int>>& states_) override;
virtual ~AODELd() = default;
vector<string> graph(const string& name = "AODE") const override;
static inline string version() { return "0.0.1"; };
};
}
#endif // !AODELD_H

View File

@@ -5,19 +5,27 @@
namespace bayesnet { namespace bayesnet {
using namespace std; using namespace std;
class BaseClassifier { class BaseClassifier {
protected:
virtual void trainModel() = 0;
public: public:
// X is nxm vector, y is nx1 vector
virtual BaseClassifier& fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states) = 0; virtual BaseClassifier& fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states) = 0;
// X is nxm tensor, y is nx1 tensor
virtual BaseClassifier& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) = 0; virtual BaseClassifier& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) = 0;
virtual BaseClassifier& fit(torch::Tensor& dataset, vector<string>& features, string className, map<string, vector<int>>& states) = 0;
virtual ~BaseClassifier() = default;
torch::Tensor virtual predict(torch::Tensor& X) = 0;
vector<int> virtual predict(vector<vector<int>>& X) = 0; vector<int> virtual predict(vector<vector<int>>& X) = 0;
float virtual score(vector<vector<int>>& X, vector<int>& y) = 0; float virtual score(vector<vector<int>>& X, vector<int>& y) = 0;
float virtual score(torch::Tensor& X, torch::Tensor& y) = 0; float virtual score(torch::Tensor& X, torch::Tensor& y) = 0;
int virtual getNumberOfNodes() = 0; int virtual getNumberOfNodes()const = 0;
int virtual getNumberOfEdges() = 0; int virtual getNumberOfEdges()const = 0;
int virtual getNumberOfStates() = 0; int virtual getNumberOfStates() const = 0;
vector<string> virtual show() = 0; vector<string> virtual show() const = 0;
vector<string> virtual graph(string title = "") = 0; vector<string> virtual graph(const string& title = "") const = 0;
virtual ~BaseClassifier() = default;
const string inline getVersion() const { return "0.1.0"; }; const string inline getVersion() const { return "0.1.0"; };
vector<string> virtual topological_order() = 0;
void virtual dump_cpt()const = 0;
}; };
} }
#endif #endif

View File

@@ -1,13 +1,15 @@
#include "BayesMetrics.h" #include "BayesMetrics.h"
#include "Mst.h" #include "Mst.h"
namespace bayesnet { namespace bayesnet {
Metrics::Metrics(torch::Tensor& samples, vector<string>& features, string& className, int classNumStates) //samples is nxm tensor used to fit the model
Metrics::Metrics(const torch::Tensor& samples, const vector<string>& features, const string& className, const int classNumStates)
: samples(samples) : samples(samples)
, features(features) , features(features)
, className(className) , className(className)
, classNumStates(classNumStates) , classNumStates(classNumStates)
{ {
} }
//samples is nxm vector used to fit the model
Metrics::Metrics(const vector<vector<int>>& vsamples, const vector<int>& labels, const vector<string>& features, const string& className, const int classNumStates) Metrics::Metrics(const vector<vector<int>>& vsamples, const vector<int>& labels, const vector<string>& features, const string& className, const int classNumStates)
: features(features) : features(features)
, className(className) , className(className)
@@ -15,9 +17,9 @@ namespace bayesnet {
, samples(torch::zeros({ static_cast<int>(vsamples[0].size()), static_cast<int>(vsamples.size() + 1) }, torch::kInt32)) , samples(torch::zeros({ static_cast<int>(vsamples[0].size()), static_cast<int>(vsamples.size() + 1) }, torch::kInt32))
{ {
for (int i = 0; i < vsamples.size(); ++i) { for (int i = 0; i < vsamples.size(); ++i) {
samples.index_put_({ "...", i }, torch::tensor(vsamples[i], torch::kInt32)); samples.index_put_({ i, "..." }, torch::tensor(vsamples[i], torch::kInt32));
} }
samples.index_put_({ "...", -1 }, torch::tensor(labels, torch::kInt32)); samples.index_put_({ -1, "..." }, torch::tensor(labels, torch::kInt32));
} }
vector<pair<string, string>> Metrics::doCombinations(const vector<string>& source) vector<pair<string, string>> Metrics::doCombinations(const vector<string>& source)
{ {
@@ -30,7 +32,7 @@ namespace bayesnet {
} }
return result; return result;
} }
torch::Tensor Metrics::conditionalEdge() torch::Tensor Metrics::conditionalEdge(const torch::Tensor& weights)
{ {
auto result = vector<double>(); auto result = vector<double>();
auto source = vector<string>(features); auto source = vector<string>(features);
@@ -39,18 +41,18 @@ namespace bayesnet {
// Compute class prior // Compute class prior
auto margin = torch::zeros({ classNumStates }); auto margin = torch::zeros({ classNumStates });
for (int value = 0; value < classNumStates; ++value) { for (int value = 0; value < classNumStates; ++value) {
auto mask = samples.index({ "...", -1 }) == value; auto mask = samples.index({ -1, "..." }) == value;
margin[value] = mask.sum().item<float>() / samples.sizes()[0]; margin[value] = mask.sum().item<float>() / samples.size(1);
} }
for (auto [first, second] : combinations) { for (auto [first, second] : combinations) {
int index_first = find(features.begin(), features.end(), first) - features.begin(); int index_first = find(features.begin(), features.end(), first) - features.begin();
int index_second = find(features.begin(), features.end(), second) - features.begin(); int index_second = find(features.begin(), features.end(), second) - features.begin();
double accumulated = 0; double accumulated = 0;
for (int value = 0; value < classNumStates; ++value) { for (int value = 0; value < classNumStates; ++value) {
auto mask = samples.index({ "...", -1 }) == value; auto mask = samples.index({ -1, "..." }) == value;
auto first_dataset = samples.index({ mask, index_first }); auto first_dataset = samples.index({ index_first, mask });
auto second_dataset = samples.index({ mask, index_second }); auto second_dataset = samples.index({ index_second, mask });
auto mi = mutualInformation(first_dataset, second_dataset); auto mi = mutualInformation(first_dataset, second_dataset, weights);
auto pb = margin[value].item<float>(); auto pb = margin[value].item<float>();
accumulated += pb * mi; accumulated += pb * mi;
} }
@@ -67,15 +69,17 @@ namespace bayesnet {
} }
return matrix; return matrix;
} }
vector<float> Metrics::conditionalEdgeWeights() // To use in Python
vector<float> Metrics::conditionalEdgeWeights(vector<float>& weights_)
{ {
auto matrix = conditionalEdge(); const torch::Tensor weights = torch::tensor(weights_);
auto matrix = conditionalEdge(weights);
std::vector<float> v(matrix.data_ptr<float>(), matrix.data_ptr<float>() + matrix.numel()); std::vector<float> v(matrix.data_ptr<float>(), matrix.data_ptr<float>() + matrix.numel());
return v; return v;
} }
double Metrics::entropy(torch::Tensor& feature) double Metrics::entropy(const torch::Tensor& feature, const torch::Tensor& weights)
{ {
torch::Tensor counts = feature.bincount(); torch::Tensor counts = feature.bincount(weights);
int totalWeight = counts.sum().item<int>(); int totalWeight = counts.sum().item<int>();
torch::Tensor probs = counts.to(torch::kFloat) / totalWeight; torch::Tensor probs = counts.to(torch::kFloat) / totalWeight;
torch::Tensor logProbs = torch::log(probs); torch::Tensor logProbs = torch::log(probs);
@@ -83,15 +87,15 @@ namespace bayesnet {
return entropy.nansum().item<double>(); return entropy.nansum().item<double>();
} }
// H(Y|X) = sum_{x in X} p(x) H(Y|X=x) // H(Y|X) = sum_{x in X} p(x) H(Y|X=x)
double Metrics::conditionalEntropy(torch::Tensor& firstFeature, torch::Tensor& secondFeature) double Metrics::conditionalEntropy(const torch::Tensor& firstFeature, const torch::Tensor& secondFeature, const torch::Tensor& weights)
{ {
int numSamples = firstFeature.sizes()[0]; int numSamples = firstFeature.sizes()[0];
torch::Tensor featureCounts = secondFeature.bincount(); torch::Tensor featureCounts = secondFeature.bincount(weights);
unordered_map<int, unordered_map<int, double>> jointCounts; unordered_map<int, unordered_map<int, double>> jointCounts;
double totalWeight = 0; double totalWeight = 0;
for (auto i = 0; i < numSamples; i++) { for (auto i = 0; i < numSamples; i++) {
jointCounts[secondFeature[i].item<int>()][firstFeature[i].item<int>()] += 1; jointCounts[secondFeature[i].item<int>()][firstFeature[i].item<int>()] += 1;
totalWeight += 1; totalWeight += weights[i].item<float>();
} }
if (totalWeight == 0) if (totalWeight == 0)
return 0; return 0;
@@ -112,16 +116,16 @@ namespace bayesnet {
return entropyValue; return entropyValue;
} }
// I(X;Y) = H(Y) - H(Y|X) // I(X;Y) = H(Y) - H(Y|X)
double Metrics::mutualInformation(torch::Tensor& firstFeature, torch::Tensor& secondFeature) double Metrics::mutualInformation(const torch::Tensor& firstFeature, const torch::Tensor& secondFeature, const torch::Tensor& weights)
{ {
return entropy(firstFeature) - conditionalEntropy(firstFeature, secondFeature); return entropy(firstFeature, weights) - conditionalEntropy(firstFeature, secondFeature, weights);
} }
/* /*
Compute the maximum spanning tree considering the weights as distances Compute the maximum spanning tree considering the weights as distances
and the indices of the weights as nodes of this square matrix using and the indices of the weights as nodes of this square matrix using
Kruskal algorithm Kruskal algorithm
*/ */
vector<pair<int, int>> Metrics::maximumSpanningTree(vector<string> features, Tensor& weights, int root) vector<pair<int, int>> Metrics::maximumSpanningTree(const vector<string>& features, const Tensor& weights, const int root)
{ {
auto mst = MST(features, weights, root); auto mst = MST(features, weights, root);
return mst.maximumSpanningTree(); return mst.maximumSpanningTree();

View File

@@ -8,21 +8,21 @@ namespace bayesnet {
using namespace torch; using namespace torch;
class Metrics { class Metrics {
private: private:
Tensor samples; Tensor samples; // nxm tensor used to fit the model
vector<string> features; vector<string> features;
string className; string className;
int classNumStates = 0; int classNumStates = 0;
double entropy(const Tensor& feature, const Tensor& weights);
double conditionalEntropy(const Tensor& firstFeature, const Tensor& secondFeature, const Tensor& weights);
vector<pair<string, string>> doCombinations(const vector<string>&);
public: public:
Metrics() = default; Metrics() = default;
Metrics(Tensor&, vector<string>&, string&, int); Metrics(const torch::Tensor& samples, const vector<string>& features, const string& className, const int classNumStates);
Metrics(const vector<vector<int>>&, const vector<int>&, const vector<string>&, const string&, const int); Metrics(const vector<vector<int>>& vsamples, const vector<int>& labels, const vector<string>& features, const string& className, const int classNumStates);
double entropy(Tensor&); double mutualInformation(const Tensor& firstFeature, const Tensor& secondFeature, const Tensor& weights);
double conditionalEntropy(Tensor&, Tensor&); vector<float> conditionalEdgeWeights(vector<float>& weights); // To use in Python
double mutualInformation(Tensor&, Tensor&); Tensor conditionalEdge(const torch::Tensor& weights);
vector<float> conditionalEdgeWeights(); vector<pair<int, int>> maximumSpanningTree(const vector<string>& features, const Tensor& weights, const int root);
Tensor conditionalEdge();
vector<pair<string, string>> doCombinations(const vector<string>&);
vector<pair<int, int>> maximumSpanningTree(vector<string> features, Tensor& weights, int root);
}; };
} }
#endif #endif

View File

@@ -1,2 +1,7 @@
add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc Mst.cc) include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp)
target_link_libraries(BayesNet "${TORCH_LIBRARIES}") include_directories(${BayesNet_SOURCE_DIR}/lib/Files)
include_directories(${BayesNet_SOURCE_DIR}/src/BayesNet)
include_directories(${BayesNet_SOURCE_DIR}/src/Platform)
add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc
KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc TANLd.cc KDBLd.cc SPODELd.cc AODELd.cc Mst.cc Proposal.cc ${BayesNet_SOURCE_DIR}/src/Platform/Models.cc)
target_link_libraries(BayesNet mdlp ArffFiles "${TORCH_LIBRARIES}")

View File

@@ -7,54 +7,66 @@ namespace bayesnet {
Classifier::Classifier(Network model) : model(model), m(0), n(0), metrics(Metrics()), fitted(false) {} Classifier::Classifier(Network model) : model(model), m(0), n(0), metrics(Metrics()), fitted(false) {}
Classifier& Classifier::build(vector<string>& features, string className, map<string, vector<int>>& states) Classifier& Classifier::build(vector<string>& features, string className, map<string, vector<int>>& states)
{ {
dataset = torch::cat({ X, y.view({y.size(0), 1}) }, 1);
this->features = features; this->features = features;
this->className = className; this->className = className;
this->states = states; this->states = states;
m = dataset.size(1);
n = dataset.size(0) - 1;
checkFitParameters(); checkFitParameters();
auto n_classes = states[className].size(); auto n_classes = states[className].size();
metrics = Metrics(dataset, features, className, n_classes); metrics = Metrics(dataset, features, className, n_classes);
train(); model.initialize();
if (Xv == vector<vector<int>>()) { buildModel();
// fit with tensors trainModel();
model.fit(X, y, features, className);
} else {
// fit with vectors
model.fit(Xv, yv, features, className);
}
fitted = true; fitted = true;
return *this; return *this;
} }
void Classifier::buildDataset(Tensor& ytmp)
{
try {
auto yresized = torch::transpose(ytmp.view({ ytmp.size(0), 1 }), 0, 1);
dataset = torch::cat({ dataset, yresized }, 0);
}
catch (const std::exception& e) {
std::cerr << e.what() << '\n';
cout << "X dimensions: " << dataset.sizes() << "\n";
cout << "y dimensions: " << ytmp.sizes() << "\n";
exit(1);
}
}
void Classifier::trainModel()
{
const torch::Tensor weights = torch::ones({ m });
model.fit(dataset, weights, features, className, states);
}
// X is nxm where n is the number of features and m the number of samples
Classifier& Classifier::fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) Classifier& Classifier::fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states)
{ {
this->X = torch::transpose(X, 0, 1); dataset = X;
this->y = y; buildDataset(y);
Xv = vector<vector<int>>();
yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
return build(features, className, states); return build(features, className, states);
} }
// X is nxm where n is the number of features and m the number of samples
Classifier& Classifier::fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states) Classifier& Classifier::fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states)
{ {
this->X = torch::zeros({ static_cast<int>(X[0].size()), static_cast<int>(X.size()) }, kInt32); dataset = torch::zeros({ static_cast<int>(X.size()), static_cast<int>(X[0].size()) }, kInt32);
Xv = X;
for (int i = 0; i < X.size(); ++i) { for (int i = 0; i < X.size(); ++i) {
this->X.index_put_({ "...", i }, torch::tensor(X[i], kInt32)); dataset.index_put_({ i, "..." }, torch::tensor(X[i], kInt32));
} }
this->y = torch::tensor(y, kInt32); auto ytmp = torch::tensor(y, kInt32);
yv = y; buildDataset(ytmp);
return build(features, className, states);
}
Classifier& Classifier::fit(torch::Tensor& dataset, vector<string>& features, string className, map<string, vector<int>>& states)
{
this->dataset = dataset;
return build(features, className, states); return build(features, className, states);
} }
void Classifier::checkFitParameters() void Classifier::checkFitParameters()
{ {
auto sizes = X.sizes();
m = sizes[0];
n = sizes[1];
if (m != y.size(0)) {
throw invalid_argument("X and y must have the same number of samples");
}
if (n != features.size()) { if (n != features.size()) {
throw invalid_argument("X and features must have the same number of features"); throw invalid_argument("X " + to_string(n) + " and features " + to_string(features.size()) + " must have the same number of features");
} }
if (states.find(className) == states.end()) { if (states.find(className) == states.end()) {
throw invalid_argument("className not found in states"); throw invalid_argument("className not found in states");
@@ -65,23 +77,12 @@ namespace bayesnet {
} }
} }
} }
Tensor Classifier::predict(Tensor& X) Tensor Classifier::predict(Tensor& X)
{ {
if (!fitted) { if (!fitted) {
throw logic_error("Classifier has not been fitted"); throw logic_error("Classifier has not been fitted");
} }
auto m_ = X.size(0); return model.predict(X);
auto n_ = X.size(1);
//auto Xt = torch::transpose(X, 0, 1);
vector<vector<int>> Xd(n_, vector<int>(m_, 0));
for (auto i = 0; i < n_; i++) {
auto temp = X.index({ "...", i });
Xd[i] = vector<int>(temp.data_ptr<int>(), temp.data_ptr<int>() + temp.numel());
}
auto yp = model.predict(Xd);
auto ypred = torch::tensor(yp, torch::kInt32);
return ypred;
} }
vector<int> Classifier::predict(vector<vector<int>>& X) vector<int> Classifier::predict(vector<vector<int>>& X)
{ {
@@ -102,8 +103,7 @@ namespace bayesnet {
if (!fitted) { if (!fitted) {
throw logic_error("Classifier has not been fitted"); throw logic_error("Classifier has not been fitted");
} }
auto Xt = torch::transpose(X, 0, 1); Tensor y_pred = predict(X);
Tensor y_pred = predict(Xt);
return (y_pred == y).sum().item<float>() / y.size(0); return (y_pred == y).sum().item<float>() / y.size(0);
} }
float Classifier::score(vector<vector<int>>& X, vector<int>& y) float Classifier::score(vector<vector<int>>& X, vector<int>& y)
@@ -111,37 +111,39 @@ namespace bayesnet {
if (!fitted) { if (!fitted) {
throw logic_error("Classifier has not been fitted"); throw logic_error("Classifier has not been fitted");
} }
auto m_ = X[0].size(); return model.score(X, y);
auto n_ = X.size();
vector<vector<int>> Xd(n_, vector<int>(m_, 0));
for (auto i = 0; i < n_; i++) {
Xd[i] = vector<int>(X[i].begin(), X[i].end());
}
return model.score(Xd, y);
} }
vector<string> Classifier::show() vector<string> Classifier::show() const
{ {
return model.show(); return model.show();
} }
void Classifier::addNodes() void Classifier::addNodes()
{ {
// Add all nodes to the network // Add all nodes to the network
for (auto feature : features) { for (const auto& feature : features) {
model.addNode(feature, states[feature].size()); model.addNode(feature);
} }
model.addNode(className, states[className].size()); model.addNode(className);
} }
int Classifier::getNumberOfNodes() int Classifier::getNumberOfNodes() const
{ {
// Features does not include class // Features does not include class
return fitted ? model.getFeatures().size() + 1 : 0; return fitted ? model.getFeatures().size() + 1 : 0;
} }
int Classifier::getNumberOfEdges() int Classifier::getNumberOfEdges() const
{ {
return fitted ? model.getEdges().size() : 0; return fitted ? model.getNumEdges() : 0;
} }
int Classifier::getNumberOfStates() int Classifier::getNumberOfStates() const
{ {
return fitted ? model.getStates() : 0; return fitted ? model.getStates() : 0;
} }
vector<string> Classifier::topological_order()
{
return model.topological_sort();
}
void Classifier::dump_cpt() const
{
model.dump_cpt();
}
} }

View File

@@ -10,36 +10,38 @@ using namespace torch;
namespace bayesnet { namespace bayesnet {
class Classifier : public BaseClassifier { class Classifier : public BaseClassifier {
private: private:
bool fitted; void buildDataset(torch::Tensor& y);
Classifier& build(vector<string>& features, string className, map<string, vector<int>>& states); Classifier& build(vector<string>& features, string className, map<string, vector<int>>& states);
protected: protected:
Network model; bool fitted;
int m, n; // m: number of samples, n: number of features int m, n; // m: number of samples, n: number of features
Tensor X; Network model;
vector<vector<int>> Xv;
Tensor y;
vector<int> yv;
Tensor dataset;
Metrics metrics; Metrics metrics;
vector<string> features; vector<string> features;
string className; string className;
map<string, vector<int>> states; map<string, vector<int>> states;
Tensor dataset; // (n+1)xm tensor
Tensor weights;
void checkFitParameters(); void checkFitParameters();
virtual void train() = 0; virtual void buildModel() = 0;
void trainModel() override;
public: public:
Classifier(Network model); Classifier(Network model);
virtual ~Classifier() = default; virtual ~Classifier() = default;
Classifier& fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states) override; Classifier& fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states) override;
Classifier& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) override; Classifier& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) override;
Classifier& fit(torch::Tensor& dataset, vector<string>& features, string className, map<string, vector<int>>& states) override;
void addNodes(); void addNodes();
int getNumberOfNodes() override; int getNumberOfNodes() const override;
int getNumberOfEdges() override; int getNumberOfEdges() const override;
int getNumberOfStates() override; int getNumberOfStates() const override;
Tensor predict(Tensor& X); Tensor predict(Tensor& X) override;
vector<int> predict(vector<vector<int>>& X) override; vector<int> predict(vector<vector<int>>& X) override;
float score(Tensor& X, Tensor& y) override; float score(Tensor& X, Tensor& y) override;
float score(vector<vector<int>>& X, vector<int>& y) override; float score(vector<vector<int>>& X, vector<int>& y) override;
vector<string> show() override; vector<string> show() const override;
vector<string> topological_order() override;
void dump_cpt() const override;
}; };
} }
#endif #endif

View File

@@ -3,69 +3,52 @@
namespace bayesnet { namespace bayesnet {
using namespace torch; using namespace torch;
Ensemble::Ensemble() : m(0), n(0), n_models(0), metrics(Metrics()), fitted(false) {} Ensemble::Ensemble() : Classifier(Network()) {}
Ensemble& Ensemble::build(vector<string>& features, string className, map<string, vector<int>>& states)
void Ensemble::trainModel()
{ {
dataset = cat({ X, y.view({y.size(0), 1}) }, 1);
this->features = features;
this->className = className;
this->states = states;
auto n_classes = states[className].size();
metrics = Metrics(dataset, features, className, n_classes);
// Build models
train();
// Train models
n_models = models.size(); n_models = models.size();
for (auto i = 0; i < n_models; ++i) { for (auto i = 0; i < n_models; ++i) {
models[i]->fit(Xv, yv, features, className, states); // fit with vectors
models[i]->fit(dataset, features, className, states);
} }
fitted = true;
return *this;
}
Ensemble& Ensemble::fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states)
{
this->X = X;
this->y = y;
Xv = vector<vector<int>>();
yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
return build(features, className, states);
}
Ensemble& Ensemble::fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states)
{
this->X = torch::zeros({ static_cast<int>(X[0].size()), static_cast<int>(X.size()) }, kInt32);
Xv = X;
for (int i = 0; i < X.size(); ++i) {
this->X.index_put_({ "...", i }, torch::tensor(X[i], kInt32));
}
this->y = torch::tensor(y, kInt32);
yv = y;
return build(features, className, states);
}
Tensor Ensemble::predict(Tensor& X)
{
if (!fitted) {
throw logic_error("Ensemble has not been fitted");
}
Tensor y_pred = torch::zeros({ X.size(0), n_models }, kInt32);
for (auto i = 0; i < n_models; ++i) {
y_pred.index_put_({ "...", i }, models[i]->predict(X));
}
return torch::tensor(voting(y_pred));
} }
vector<int> Ensemble::voting(Tensor& y_pred) vector<int> Ensemble::voting(Tensor& y_pred)
{ {
auto y_pred_ = y_pred.accessor<int, 2>(); auto y_pred_ = y_pred.accessor<int, 2>();
vector<int> y_pred_final; vector<int> y_pred_final;
for (int i = 0; i < y_pred.size(0); ++i) { for (int i = 0; i < y_pred.size(0); ++i) {
vector<float> votes(states[className].size(), 0); vector<float> votes(y_pred.size(1), 0);
for (int j = 0; j < y_pred.size(1); ++j) { for (int j = 0; j < y_pred.size(1); ++j) {
votes[y_pred_[i][j]] += 1; votes[y_pred_[i][j]] += 1;
} }
// argsort in descending order
auto indices = argsort(votes); auto indices = argsort(votes);
y_pred_final.push_back(indices[0]); y_pred_final.push_back(indices[0]);
} }
return y_pred_final; return y_pred_final;
} }
Tensor Ensemble::predict(Tensor& X)
{
if (!fitted) {
throw logic_error("Ensemble has not been fitted");
}
Tensor y_pred = torch::zeros({ X.size(1), n_models }, kInt32);
//Create a threadpool
auto threads{ vector<thread>() };
mutex mtx;
for (auto i = 0; i < n_models; ++i) {
threads.push_back(thread([&, i]() {
auto ypredict = models[i]->predict(X);
lock_guard<mutex> lock(mtx);
y_pred.index_put_({ "...", i }, ypredict);
}));
}
for (auto& thread : threads) {
thread.join();
}
return torch::tensor(voting(y_pred));
}
vector<int> Ensemble::predict(vector<vector<int>>& X) vector<int> Ensemble::predict(vector<vector<int>>& X)
{ {
if (!fitted) { if (!fitted) {
@@ -110,9 +93,8 @@ namespace bayesnet {
} }
} }
return (double)correct / y_pred.size(); return (double)correct / y_pred.size();
} }
vector<string> Ensemble::show() vector<string> Ensemble::show() const
{ {
auto result = vector<string>(); auto result = vector<string>();
for (auto i = 0; i < n_models; ++i) { for (auto i = 0; i < n_models; ++i) {
@@ -121,7 +103,7 @@ namespace bayesnet {
} }
return result; return result;
} }
vector<string> Ensemble::graph(string title) vector<string> Ensemble::graph(const string& title) const
{ {
auto result = vector<string>(); auto result = vector<string>();
for (auto i = 0; i < n_models; ++i) { for (auto i = 0; i < n_models; ++i) {
@@ -130,7 +112,7 @@ namespace bayesnet {
} }
return result; return result;
} }
int Ensemble::getNumberOfNodes() int Ensemble::getNumberOfNodes() const
{ {
int nodes = 0; int nodes = 0;
for (auto i = 0; i < n_models; ++i) { for (auto i = 0; i < n_models; ++i) {
@@ -138,7 +120,7 @@ namespace bayesnet {
} }
return nodes; return nodes;
} }
int Ensemble::getNumberOfEdges() int Ensemble::getNumberOfEdges() const
{ {
int edges = 0; int edges = 0;
for (auto i = 0; i < n_models; ++i) { for (auto i = 0; i < n_models; ++i) {
@@ -146,7 +128,7 @@ namespace bayesnet {
} }
return edges; return edges;
} }
int Ensemble::getNumberOfStates() int Ensemble::getNumberOfStates() const
{ {
int nstates = 0; int nstates = 0;
for (auto i = 0; i < n_models; ++i) { for (auto i = 0; i < n_models; ++i) {

View File

@@ -8,39 +8,33 @@ using namespace std;
using namespace torch; using namespace torch;
namespace bayesnet { namespace bayesnet {
class Ensemble : public BaseClassifier { class Ensemble : public Classifier {
private: private:
bool fitted;
long n_models;
Ensemble& build(vector<string>& features, string className, map<string, vector<int>>& states); Ensemble& build(vector<string>& features, string className, map<string, vector<int>>& states);
protected: protected:
unsigned n_models;
vector<unique_ptr<Classifier>> models; vector<unique_ptr<Classifier>> models;
int m, n; // m: number of samples, n: number of features void trainModel() override;
Tensor X;
vector<vector<int>> Xv;
Tensor y;
vector<int> yv;
Tensor dataset;
Metrics metrics;
vector<string> features;
string className;
map<string, vector<int>> states;
void virtual train() = 0;
vector<int> voting(Tensor& y_pred); vector<int> voting(Tensor& y_pred);
public: public:
Ensemble(); Ensemble();
virtual ~Ensemble() = default; virtual ~Ensemble() = default;
Ensemble& fit(vector<vector<int>>& X, vector<int>& y, vector<string>& features, string className, map<string, vector<int>>& states) override; Tensor predict(Tensor& X) override;
Ensemble& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) override;
Tensor predict(Tensor& X);
vector<int> predict(vector<vector<int>>& X) override; vector<int> predict(vector<vector<int>>& X) override;
float score(Tensor& X, Tensor& y) override; float score(Tensor& X, Tensor& y) override;
float score(vector<vector<int>>& X, vector<int>& y) override; float score(vector<vector<int>>& X, vector<int>& y) override;
int getNumberOfNodes() override; int getNumberOfNodes() const override;
int getNumberOfEdges() override; int getNumberOfEdges() const override;
int getNumberOfStates() override; int getNumberOfStates() const override;
vector<string> show() override; vector<string> show() const override;
vector<string> graph(string title) override; vector<string> graph(const string& title) const override;
vector<string> topological_order() override
{
return vector<string>();
}
void dump_cpt() const override
{
}
}; };
} }
#endif #endif

View File

@@ -4,7 +4,7 @@ namespace bayesnet {
using namespace torch; using namespace torch;
KDB::KDB(int k, float theta) : Classifier(Network()), k(k), theta(theta) {} KDB::KDB(int k, float theta) : Classifier(Network()), k(k), theta(theta) {}
void KDB::train() void KDB::buildModel()
{ {
/* /*
1. For each feature Xi, compute mutual information, I(X;C), 1. For each feature Xi, compute mutual information, I(X;C),
@@ -27,25 +27,25 @@ namespace bayesnet {
*/ */
// 1. For each feature Xi, compute mutual information, I(X;C), // 1. For each feature Xi, compute mutual information, I(X;C),
// where C is the class. // where C is the class.
addNodes();
const Tensor& y = dataset.index({ -1, "..." });
vector <float> mi; vector <float> mi;
for (auto i = 0; i < features.size(); i++) { for (auto i = 0; i < features.size(); i++) {
Tensor firstFeature = X.index({ "...", i }); Tensor firstFeature = dataset.index({ i, "..." });
mi.push_back(metrics.mutualInformation(firstFeature, y)); mi.push_back(metrics.mutualInformation(firstFeature, y, weights));
} }
// 2. Compute class conditional mutual information I(Xi;XjIC), f or each // 2. Compute class conditional mutual information I(Xi;XjIC), f or each
auto conditionalEdgeWeights = metrics.conditionalEdge(); auto conditionalEdgeWeights = metrics.conditionalEdge(weights);
// 3. Let the used variable list, S, be empty. // 3. Let the used variable list, S, be empty.
vector<int> S; vector<int> S;
// 4. Let the DAG network being constructed, BN, begin with a single // 4. Let the DAG network being constructed, BN, begin with a single
// class node, C. // class node, C.
model.addNode(className, states[className].size());
// 5. Repeat until S includes all domain features // 5. Repeat until S includes all domain features
// 5.1. Select feature Xmax which is not in S and has the largest value // 5.1. Select feature Xmax which is not in S and has the largest value
// I(Xmax;C). // I(Xmax;C).
auto order = argsort(mi); auto order = argsort(mi);
for (auto idx : order) { for (auto idx : order) {
// 5.2. Add a node to BN representing Xmax. // 5.2. Add a node to BN representing Xmax.
model.addNode(features[idx], states[features[idx]].size());
// 5.3. Add an arc from C to Xmax in BN. // 5.3. Add an arc from C to Xmax in BN.
model.addEdge(className, features[idx]); model.addEdge(className, features[idx]);
// 5.4. Add m = min(lSl,/c) arcs from m distinct features Xj in S with // 5.4. Add m = min(lSl,/c) arcs from m distinct features Xj in S with
@@ -79,11 +79,12 @@ namespace bayesnet {
exit_cond = num == n_edges || candidates.size(0) == 0; exit_cond = num == n_edges || candidates.size(0) == 0;
} }
} }
vector<string> KDB::graph(string title) vector<string> KDB::graph(const string& title) const
{ {
string header{ title };
if (title == "KDB") { if (title == "KDB") {
title += " (k=" + to_string(k) + ", theta=" + to_string(theta) + ")"; header += " (k=" + to_string(k) + ", theta=" + to_string(theta) + ")";
} }
return model.graph(title); return model.graph(header);
} }
} }

View File

@@ -11,11 +11,11 @@ namespace bayesnet {
float theta; float theta;
void add_m_edges(int idx, vector<int>& S, Tensor& weights); void add_m_edges(int idx, vector<int>& S, Tensor& weights);
protected: protected:
void train() override; void buildModel() override;
public: public:
explicit KDB(int k, float theta = 0.03); explicit KDB(int k, float theta = 0.03);
virtual ~KDB() {}; virtual ~KDB() {};
vector<string> graph(string name = "KDB") override; vector<string> graph(const string& name = "KDB") const override;
}; };
} }
#endif #endif

30
src/BayesNet/KDBLd.cc Normal file
View File

@@ -0,0 +1,30 @@
#include "KDBLd.h"
namespace bayesnet {
using namespace std;
KDBLd::KDBLd(int k) : KDB(k), Proposal(dataset, features, className) {}
KDBLd& KDBLd::fit(torch::Tensor& X_, torch::Tensor& y_, vector<string>& features_, string className_, map<string, vector<int>>& states_)
{
// This first part should go in a Classifier method called fit_local_discretization o fit_float...
features = features_;
className = className_;
Xf = X_;
y = y_;
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
states = fit_local_discretization(y);
// We have discretized the input data
// 1st we need to fit the model to build the normal KDB structure, KDB::fit initializes the base Bayesian network
KDB::fit(dataset, features, className, states);
states = localDiscretizationProposal(states, model);
return *this;
}
Tensor KDBLd::predict(Tensor& X)
{
auto Xt = prepareX(X);
return KDB::predict(Xt);
}
vector<string> KDBLd::graph(const string& name) const
{
return KDB::graph(name);
}
}

19
src/BayesNet/KDBLd.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef KDBLD_H
#define KDBLD_H
#include "KDB.h"
#include "Proposal.h"
namespace bayesnet {
using namespace std;
class KDBLd : public KDB, public Proposal {
private:
public:
explicit KDBLd(int k);
virtual ~KDBLd() = default;
KDBLd& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) override;
vector<string> graph(const string& name = "KDB") const override;
Tensor predict(Tensor& X) override;
static inline string version() { return "0.0.1"; };
};
}
#endif // !KDBLD_H

View File

@@ -94,7 +94,7 @@ namespace bayesnet {
return result; return result;
} }
MST::MST(vector<string>& features, Tensor& weights, int root) : features(features), weights(weights), root(root) {} MST::MST(const vector<string>& features, const Tensor& weights, const int root) : features(features), weights(weights), root(root) {}
vector<pair<int, int>> MST::maximumSpanningTree() vector<pair<int, int>> MST::maximumSpanningTree()
{ {
auto num_features = features.size(); auto num_features = features.size();

View File

@@ -13,7 +13,7 @@ namespace bayesnet {
int root = 0; int root = 0;
public: public:
MST() = default; MST() = default;
MST(vector<string>& features, Tensor& weights, int root); MST(const vector<string>& features, const Tensor& weights, const int root);
vector<pair<int, int>> maximumSpanningTree(); vector<pair<int, int>> maximumSpanningTree();
}; };
class Graph { class Graph {

View File

@@ -3,15 +3,25 @@
#include "Network.h" #include "Network.h"
#include "bayesnetUtils.h" #include "bayesnetUtils.h"
namespace bayesnet { namespace bayesnet {
Network::Network() : laplaceSmoothing(1), features(vector<string>()), className(""), classNumStates(0), maxThreads(0.8), fitted(false) {} Network::Network() : features(vector<string>()), className(""), classNumStates(0), fitted(false) {}
Network::Network(float maxT) : laplaceSmoothing(1), features(vector<string>()), className(""), classNumStates(0), maxThreads(maxT), fitted(false) {} Network::Network(float maxT) : features(vector<string>()), className(""), classNumStates(0), maxThreads(maxT), fitted(false) {}
Network::Network(float maxT, int smoothing) : laplaceSmoothing(smoothing), features(vector<string>()), className(""), classNumStates(0), maxThreads(maxT), fitted(false) {} Network::Network(float maxT, int smoothing) : laplaceSmoothing(smoothing), features(vector<string>()), className(""), classNumStates(0), maxThreads(maxT), fitted(false) {}
Network::Network(Network& other) : laplaceSmoothing(other.laplaceSmoothing), features(other.features), className(other.className), classNumStates(other.getClassNumStates()), maxThreads(other.getmaxThreads()), fitted(other.fitted) Network::Network(Network& other) : laplaceSmoothing(other.laplaceSmoothing), features(other.features), className(other.className), classNumStates(other.getClassNumStates()), maxThreads(other.
getmaxThreads()), fitted(other.fitted)
{ {
for (const auto& pair : other.nodes) { for (const auto& pair : other.nodes) {
nodes[pair.first] = std::make_unique<Node>(*pair.second); nodes[pair.first] = std::make_unique<Node>(*pair.second);
} }
} }
void Network::initialize()
{
features = vector<string>();
className = "";
classNumStates = 0;
fitted = false;
nodes.clear();
samples = torch::Tensor();
}
float Network::getmaxThreads() float Network::getmaxThreads()
{ {
return maxThreads; return maxThreads;
@@ -20,28 +30,28 @@ namespace bayesnet {
{ {
return samples; return samples;
} }
void Network::addNode(const string& name, int numStates) void Network::addNode(const string& name)
{ {
if (name == "") {
throw invalid_argument("Node name cannot be empty");
}
if (nodes.find(name) != nodes.end()) {
return;
}
if (find(features.begin(), features.end(), name) == features.end()) { if (find(features.begin(), features.end(), name) == features.end()) {
features.push_back(name); features.push_back(name);
} }
if (nodes.find(name) != nodes.end()) { nodes[name] = std::make_unique<Node>(name);
// if node exists update its number of states and remove parents, children and CPT
nodes[name]->clear();
nodes[name]->setNumStates(numStates);
return;
}
nodes[name] = std::make_unique<Node>(name, numStates);
} }
vector<string> Network::getFeatures() vector<string> Network::getFeatures() const
{ {
return features; return features;
} }
int Network::getClassNumStates() int Network::getClassNumStates() const
{ {
return classNumStates; return classNumStates;
} }
int Network::getStates() int Network::getStates() const
{ {
int result = 0; int result = 0;
for (auto& node : nodes) { for (auto& node : nodes) {
@@ -49,7 +59,7 @@ namespace bayesnet {
} }
return result; return result;
} }
string Network::getClassName() string Network::getClassName() const
{ {
return className; return className;
} }
@@ -94,45 +104,76 @@ namespace bayesnet {
{ {
return nodes; return nodes;
} }
void Network::fit(torch::Tensor& X, torch::Tensor& y, const vector<string>& featureNames, const string& className) void Network::checkFitData(int n_samples, int n_features, int n_samples_y, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states, const torch::Tensor& weights)
{ {
features = featureNames; if (weights.size(0) != n_samples) {
this->className = className; throw invalid_argument("Weights must have the same number of elements as samples in Network::fit");
dataset.clear(); }
// Specific part if (n_samples != n_samples_y) {
classNumStates = torch::max(y).item<int>() + 1; throw invalid_argument("X and y must have the same number of samples in Network::fit (" + to_string(n_samples) + " != " + to_string(n_samples_y) + ")");
samples = torch::cat({ X, y.view({ y.size(0), 1 }) }, 1); }
for (int i = 0; i < featureNames.size(); ++i) { if (n_features != featureNames.size()) {
auto column = torch::flatten(X.index({ "...", i })); throw invalid_argument("X and features must have the same number of features in Network::fit (" + to_string(n_features) + " != " + to_string(featureNames.size()) + ")");
auto k = vector<int>(); }
for (auto z = 0; z < X.size(0); ++z) { if (n_features != features.size() - 1) {
k.push_back(column[z].item<int>()); throw invalid_argument("X and local features must have the same number of features in Network::fit (" + to_string(n_features) + " != " + to_string(features.size() - 1) + ")");
}
if (find(features.begin(), features.end(), className) == features.end()) {
throw invalid_argument("className not found in Network::features");
}
for (auto& feature : featureNames) {
if (find(features.begin(), features.end(), feature) == features.end()) {
throw invalid_argument("Feature " + feature + " not found in Network::features");
}
if (states.find(feature) == states.end()) {
throw invalid_argument("Feature " + feature + " not found in states");
} }
dataset[featureNames[i]] = k;
} }
dataset[className] = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
completeFit();
} }
void Network::fit(const vector<vector<int>>& input_data, const vector<int>& labels, const vector<string>& featureNames, const string& className) void Network::setStates(const map<string, vector<int>>& states)
{ {
features = featureNames; // Set states to every Node in the network
for (int i = 0; i < features.size(); ++i) {
nodes[features[i]]->setNumStates(states.at(features[i]).size());
}
classNumStates = nodes[className]->getNumStates();
}
// X comes in nxm, where n is the number of features and m the number of samples
void Network::fit(const torch::Tensor& X, const torch::Tensor& y, const torch::Tensor& weights, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states)
{
checkFitData(X.size(1), X.size(0), y.size(0), featureNames, className, states, weights);
this->className = className; this->className = className;
dataset.clear(); Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1);
// Specific part samples = torch::cat({ X , ytmp }, 0);
classNumStates = *max_element(labels.begin(), labels.end()) + 1;
// Build dataset & tensor of samples
samples = torch::zeros({ static_cast<int>(input_data[0].size()), static_cast<int>(input_data.size() + 1) }, torch::kInt32);
for (int i = 0; i < featureNames.size(); ++i) { for (int i = 0; i < featureNames.size(); ++i) {
dataset[featureNames[i]] = input_data[i]; auto row_feature = X.index({ i, "..." });
samples.index_put_({ "...", i }, torch::tensor(input_data[i], torch::kInt32));
} }
dataset[className] = labels; completeFit(states, weights);
samples.index_put_({ "...", -1 }, torch::tensor(labels, torch::kInt32));
completeFit();
} }
void Network::completeFit() void Network::fit(const torch::Tensor& samples, const torch::Tensor& weights, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states)
{ {
checkFitData(samples.size(1), samples.size(0) - 1, samples.size(1), featureNames, className, states, weights);
this->className = className;
this->samples = samples;
completeFit(states, weights);
}
// input_data comes in nxm, where n is the number of features and m the number of samples
void Network::fit(const vector<vector<int>>& input_data, const vector<int>& labels, const vector<float>& weights_, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states)
{
const torch::Tensor weights = torch::tensor(weights_, torch::kFloat64);
checkFitData(input_data[0].size(), input_data.size(), labels.size(), featureNames, className, states, weights);
this->className = className;
// Build tensor of samples (nxm) (n+1 because of the class)
samples = torch::zeros({ static_cast<int>(input_data.size() + 1), static_cast<int>(input_data[0].size()) }, torch::kInt32);
for (int i = 0; i < featureNames.size(); ++i) {
samples.index_put_({ i, "..." }, torch::tensor(input_data[i], torch::kInt32));
}
samples.index_put_({ -1, "..." }, torch::tensor(labels, torch::kInt32));
completeFit(states, weights);
}
void Network::completeFit(const map<string, vector<int>>& states, const torch::Tensor& weights)
{
setStates(states);
int maxThreadsRunning = static_cast<int>(std::thread::hardware_concurrency() * maxThreads); int maxThreadsRunning = static_cast<int>(std::thread::hardware_concurrency() * maxThreads);
if (maxThreadsRunning < 1) { if (maxThreadsRunning < 1) {
maxThreadsRunning = 1; maxThreadsRunning = 1;
@@ -145,7 +186,7 @@ namespace bayesnet {
while (nextNodeIndex < nodes.size()) { while (nextNodeIndex < nodes.size()) {
unique_lock<mutex> lock(mtx); unique_lock<mutex> lock(mtx);
cv.wait(lock, [&activeThreads, &maxThreadsRunning]() { return activeThreads < maxThreadsRunning; }); cv.wait(lock, [&activeThreads, &maxThreadsRunning]() { return activeThreads < maxThreadsRunning; });
threads.emplace_back([this, &nextNodeIndex, &mtx, &cv, &activeThreads]() { threads.emplace_back([this, &nextNodeIndex, &mtx, &cv, &activeThreads, &weights]() {
while (true) { while (true) {
unique_lock<mutex> lock(mtx); unique_lock<mutex> lock(mtx);
if (nextNodeIndex >= nodes.size()) { if (nextNodeIndex >= nodes.size()) {
@@ -154,7 +195,7 @@ namespace bayesnet {
auto& pair = *std::next(nodes.begin(), nextNodeIndex); auto& pair = *std::next(nodes.begin(), nextNodeIndex);
++nextNodeIndex; ++nextNodeIndex;
lock.unlock(); lock.unlock();
pair.second->computeCPT(dataset, laplaceSmoothing); pair.second->computeCPT(samples, features, laplaceSmoothing, weights);
lock.lock(); lock.lock();
nodes[pair.first] = std::move(pair.second); nodes[pair.first] = std::move(pair.second);
lock.unlock(); lock.unlock();
@@ -170,7 +211,39 @@ namespace bayesnet {
} }
fitted = true; fitted = true;
} }
torch::Tensor Network::predict_tensor(const torch::Tensor& samples, const bool proba)
{
if (!fitted) {
throw logic_error("You must call fit() before calling predict()");
}
torch::Tensor result;
result = torch::zeros({ samples.size(1), classNumStates }, torch::kFloat64);
for (int i = 0; i < samples.size(1); ++i) {
const Tensor sample = samples.index({ "...", i });
auto psample = predict_sample(sample);
auto temp = torch::tensor(psample, torch::kFloat64);
// result.index_put_({ i, "..." }, torch::tensor(predict_sample(sample), torch::kFloat64));
result.index_put_({ i, "..." }, temp);
}
if (proba)
return result;
else
return result.argmax(1);
}
// Return mxn tensor of probabilities
Tensor Network::predict_proba(const Tensor& samples)
{
return predict_tensor(samples, true);
}
// Return mxn tensor of probabilities
Tensor Network::predict(const Tensor& samples)
{
return predict_tensor(samples, false);
}
// Return mx1 vector of predictions
// tsamples is nxm vector of samples
vector<int> Network::predict(const vector<vector<int>>& tsamples) vector<int> Network::predict(const vector<vector<int>>& tsamples)
{ {
if (!fitted) { if (!fitted) {
@@ -191,6 +264,7 @@ namespace bayesnet {
} }
return predictions; return predictions;
} }
// Return mxn vector of probabilities
vector<vector<double>> Network::predict_proba(const vector<vector<int>>& tsamples) vector<vector<double>> Network::predict_proba(const vector<vector<int>>& tsamples)
{ {
if (!fitted) { if (!fitted) {
@@ -218,12 +292,13 @@ namespace bayesnet {
} }
return (double)correct / y_pred.size(); return (double)correct / y_pred.size();
} }
// Return 1xn vector of probabilities
vector<double> Network::predict_sample(const vector<int>& sample) vector<double> Network::predict_sample(const vector<int>& sample)
{ {
// Ensure the sample size is equal to the number of features // Ensure the sample size is equal to the number of features
if (sample.size() != features.size()) { if (sample.size() != features.size() - 1) {
throw invalid_argument("Sample size (" + to_string(sample.size()) + throw invalid_argument("Sample size (" + to_string(sample.size()) +
") does not match the number of features (" + to_string(features.size()) + ")"); ") does not match the number of features (" + to_string(features.size() - 1) + ")");
} }
map<string, int> evidence; map<string, int> evidence;
for (int i = 0; i < sample.size(); ++i) { for (int i = 0; i < sample.size(); ++i) {
@@ -231,6 +306,20 @@ namespace bayesnet {
} }
return exactInference(evidence); return exactInference(evidence);
} }
// Return 1xn vector of probabilities
vector<double> Network::predict_sample(const Tensor& sample)
{
// Ensure the sample size is equal to the number of features
if (sample.size(0) != features.size() - 1) {
throw invalid_argument("Sample size (" + to_string(sample.size(0)) +
") does not match the number of features (" + to_string(features.size() - 1) + ")");
}
map<string, int> evidence;
for (int i = 0; i < sample.size(0); ++i) {
evidence[features[i]] = sample[i].item<int>();
}
return exactInference(evidence);
}
double Network::computeFactor(map<string, int>& completeEvidence) double Network::computeFactor(map<string, int>& completeEvidence)
{ {
double result = 1.0; double result = 1.0;
@@ -256,13 +345,12 @@ namespace bayesnet {
for (auto& thread : threads) { for (auto& thread : threads) {
thread.join(); thread.join();
} }
// Normalize result // Normalize result
double sum = accumulate(result.begin(), result.end(), 0.0); double sum = accumulate(result.begin(), result.end(), 0.0);
transform(result.begin(), result.end(), result.begin(), [sum](double& value) { return value / sum; }); transform(result.begin(), result.end(), result.begin(), [sum](double& value) { return value / sum; });
return result; return result;
} }
vector<string> Network::show() vector<string> Network::show() const
{ {
vector<string> result; vector<string> result;
// Draw the network // Draw the network
@@ -275,7 +363,7 @@ namespace bayesnet {
} }
return result; return result;
} }
vector<string> Network::graph(const string& title) vector<string> Network::graph(const string& title) const
{ {
auto output = vector<string>(); auto output = vector<string>();
auto prefix = "digraph BayesNet {\nlabel=<BayesNet "; auto prefix = "digraph BayesNet {\nlabel=<BayesNet ";
@@ -289,7 +377,7 @@ namespace bayesnet {
output.push_back("}\n"); output.push_back("}\n");
return output; return output;
} }
vector<pair<string, string>> Network::getEdges() vector<pair<string, string>> Network::getEdges() const
{ {
auto edges = vector<pair<string, string>>(); auto edges = vector<pair<string, string>>();
for (const auto& node : nodes) { for (const auto& node : nodes) {
@@ -301,4 +389,52 @@ namespace bayesnet {
} }
return edges; return edges;
} }
int Network::getNumEdges() const
{
return getEdges().size();
}
vector<string> Network::topological_sort()
{
/* Check if al the fathers of every node are before the node */
auto result = features;
result.erase(remove(result.begin(), result.end(), className), result.end());
bool ending{ false };
int idx = 0;
while (!ending) {
ending = true;
for (auto feature : features) {
auto fathers = nodes[feature]->getParents();
for (const auto& father : fathers) {
auto fatherName = father->getName();
if (fatherName == className) {
continue;
}
// Check if father is placed before the actual feature
auto it = find(result.begin(), result.end(), fatherName);
if (it != result.end()) {
auto it2 = find(result.begin(), result.end(), feature);
if (it2 != result.end()) {
if (distance(it, it2) < 0) {
// if it is not, insert it before the feature
result.erase(remove(result.begin(), result.end(), fatherName), result.end());
result.insert(it2, fatherName);
ending = false;
}
} else {
throw logic_error("Error in topological sort because of node " + feature + " is not in result");
}
} else {
throw logic_error("Error in topological sort because of node father " + fatherName + " is not in result");
}
}
}
}
return result;
}
void Network::dump_cpt() const
{
for (auto& node : nodes) {
cout << "* " << node.first << ": (" << node.second->getNumStates() << ") : " << node.second->getCPT().sizes() << endl;
}
}
} }

View File

@@ -8,23 +8,21 @@ namespace bayesnet {
class Network { class Network {
private: private:
map<string, unique_ptr<Node>> nodes; map<string, unique_ptr<Node>> nodes;
map<string, vector<int>> dataset;
bool fitted; bool fitted;
float maxThreads; float maxThreads = 0.95;
int classNumStates; int classNumStates;
vector<string> features; vector<string> features; // Including classname
string className; string className;
int laplaceSmoothing; int laplaceSmoothing = 1;
torch::Tensor samples; torch::Tensor samples; // nxm tensor used to fit the model
bool isCyclic(const std::string&, std::unordered_set<std::string>&, std::unordered_set<std::string>&); bool isCyclic(const std::string&, std::unordered_set<std::string>&, std::unordered_set<std::string>&);
vector<double> predict_sample(const vector<int>&); vector<double> predict_sample(const vector<int>&);
vector<double> predict_sample(const torch::Tensor&);
vector<double> exactInference(map<string, int>&); vector<double> exactInference(map<string, int>&);
double computeFactor(map<string, int>&); double computeFactor(map<string, int>&);
double mutual_info(torch::Tensor&, torch::Tensor&); void completeFit(const map<string, vector<int>>& states, const torch::Tensor& weights);
double entropy(torch::Tensor&); void checkFitData(int n_features, int n_samples, int n_samples_y, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states, const torch::Tensor& weights);
double conditionalEntropy(torch::Tensor&, torch::Tensor&); void setStates(const map<string, vector<int>>&);
double mutualInformation(torch::Tensor&, torch::Tensor&);
void completeFit();
public: public:
Network(); Network();
explicit Network(float, int); explicit Network(float, int);
@@ -32,23 +30,29 @@ namespace bayesnet {
explicit Network(Network&); explicit Network(Network&);
torch::Tensor& getSamples(); torch::Tensor& getSamples();
float getmaxThreads(); float getmaxThreads();
void addNode(const string&, int); void addNode(const string&);
void addEdge(const string&, const string&); void addEdge(const string&, const string&);
map<string, std::unique_ptr<Node>>& getNodes(); map<string, std::unique_ptr<Node>>& getNodes();
vector<string> getFeatures(); vector<string> getFeatures() const;
int getStates(); int getStates() const;
vector<pair<string, string>> getEdges(); vector<pair<string, string>> getEdges() const;
int getClassNumStates(); int getNumEdges() const;
string getClassName(); int getClassNumStates() const;
void fit(const vector<vector<int>>&, const vector<int>&, const vector<string>&, const string&); string getClassName() const;
void fit(torch::Tensor&, torch::Tensor&, const vector<string>&, const string&); void fit(const vector<vector<int>>& input_data, const vector<int>& labels, const vector<float>& weights, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states);
vector<int> predict(const vector<vector<int>>&); void fit(const torch::Tensor& X, const torch::Tensor& y, const torch::Tensor& weights, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states);
//Computes the conditional edge weight of variable index u and v conditioned on class_node void fit(const torch::Tensor& samples, const torch::Tensor& weights, const vector<string>& featureNames, const string& className, const map<string, vector<int>>& states);
torch::Tensor conditionalEdgeWeight(); vector<int> predict(const vector<vector<int>>&); // Return mx1 vector of predictions
vector<vector<double>> predict_proba(const vector<vector<int>>&); torch::Tensor predict(const torch::Tensor&); // Return mx1 tensor of predictions
torch::Tensor predict_tensor(const torch::Tensor& samples, const bool proba);
vector<vector<double>> predict_proba(const vector<vector<int>>&); // Return mxn vector of probabilities
torch::Tensor predict_proba(const torch::Tensor&); // Return mxn tensor of probabilities
double score(const vector<vector<int>>&, const vector<int>&); double score(const vector<vector<int>>&, const vector<int>&);
vector<string> show(); vector<string> topological_sort();
vector<string> graph(const string& title); // Returns a vector of strings representing the graph in graphviz format vector<string> show() const;
vector<string> graph(const string& title) const; // Returns a vector of strings representing the graph in graphviz format
void initialize();
void dump_cpt() const;
inline string version() { return "0.1.0"; } inline string version() { return "0.1.0"; }
}; };
} }

View File

@@ -2,8 +2,8 @@
namespace bayesnet { namespace bayesnet {
Node::Node(const std::string& name, int numStates) Node::Node(const std::string& name)
: name(name), numStates(numStates), cpTable(torch::Tensor()), parents(vector<Node*>()), children(vector<Node*>()) : name(name), numStates(0), cpTable(torch::Tensor()), parents(vector<Node*>()), children(vector<Node*>())
{ {
} }
void Node::clear() void Node::clear()
@@ -84,8 +84,9 @@ namespace bayesnet {
} }
return result; return result;
} }
void Node::computeCPT(map<string, vector<int>>& dataset, const int laplaceSmoothing) void Node::computeCPT(const torch::Tensor& dataset, const vector<string>& features, const int laplaceSmoothing, const torch::Tensor& weights)
{ {
dimensions.clear();
// Get dimensions of the CPT // Get dimensions of the CPT
dimensions.push_back(numStates); dimensions.push_back(numStates);
transform(parents.begin(), parents.end(), back_inserter(dimensions), [](const auto& parent) { return parent->getNumStates(); }); transform(parents.begin(), parents.end(), back_inserter(dimensions), [](const auto& parent) { return parent->getNumStates(); });
@@ -93,12 +94,24 @@ namespace bayesnet {
// Create a tensor of zeros with the dimensions of the CPT // Create a tensor of zeros with the dimensions of the CPT
cpTable = torch::zeros(dimensions, torch::kFloat) + laplaceSmoothing; cpTable = torch::zeros(dimensions, torch::kFloat) + laplaceSmoothing;
// Fill table with counts // Fill table with counts
for (int n_sample = 0; n_sample < dataset[name].size(); ++n_sample) { auto pos = find(features.begin(), features.end(), name);
if (pos == features.end()) {
throw logic_error("Feature " + name + " not found in dataset");
}
int name_index = pos - features.begin();
for (int n_sample = 0; n_sample < dataset.size(1); ++n_sample) {
torch::List<c10::optional<torch::Tensor>> coordinates; torch::List<c10::optional<torch::Tensor>> coordinates;
coordinates.push_back(torch::tensor(dataset[name][n_sample])); coordinates.push_back(dataset.index({ name_index, n_sample }));
transform(parents.begin(), parents.end(), back_inserter(coordinates), [&dataset, &n_sample](const auto& parent) { return torch::tensor(dataset[parent->getName()][n_sample]); }); for (auto parent : parents) {
pos = find(features.begin(), features.end(), parent->getName());
if (pos == features.end()) {
throw logic_error("Feature parent " + parent->getName() + " not found in dataset");
}
int parent_index = pos - features.begin();
coordinates.push_back(dataset.index({ parent_index, n_sample }));
}
// Increment the count of the corresponding coordinate // Increment the count of the corresponding coordinate
cpTable.index_put_({ coordinates }, cpTable.index({ coordinates }) + 1); cpTable.index_put_({ coordinates }, cpTable.index({ coordinates }) + weights.index({ n_sample }).item<float>());
} }
// Normalize the counts // Normalize the counts
cpTable = cpTable / cpTable.sum(0); cpTable = cpTable / cpTable.sum(0);

View File

@@ -16,7 +16,7 @@ namespace bayesnet {
vector<int64_t> dimensions; // dimensions of the cpTable vector<int64_t> dimensions; // dimensions of the cpTable
public: public:
vector<pair<string, string>> combinations(const vector<string>&); vector<pair<string, string>> combinations(const vector<string>&);
Node(const string&, int); explicit Node(const string&);
void clear(); void clear();
void addParent(Node*); void addParent(Node*);
void addChild(Node*); void addChild(Node*);
@@ -26,7 +26,7 @@ namespace bayesnet {
vector<Node*>& getParents(); vector<Node*>& getParents();
vector<Node*>& getChildren(); vector<Node*>& getChildren();
torch::Tensor& getCPT(); torch::Tensor& getCPT();
void computeCPT(map<string, vector<int>>&, const int); void computeCPT(const torch::Tensor& dataset, const vector<string>& features, const int laplaceSmoothing, const torch::Tensor& weights);
int getNumStates() const; int getNumStates() const;
void setNumStates(int); void setNumStates(int);
unsigned minFill(); unsigned minFill();

110
src/BayesNet/Proposal.cc Normal file
View File

@@ -0,0 +1,110 @@
#include "Proposal.h"
#include "ArffFiles.h"
namespace bayesnet {
Proposal::Proposal(torch::Tensor& dataset_, vector<string>& features_, string& className_) : pDataset(dataset_), pFeatures(features_), pClassName(className_) {}
Proposal::~Proposal()
{
for (auto& [key, value] : discretizers) {
delete value;
}
}
map<string, vector<int>> Proposal::localDiscretizationProposal(const map<string, vector<int>>& oldStates, Network& model)
{
// order of local discretization is important. no good 0, 1, 2...
// although we rediscretize features after the local discretization of every feature
auto order = model.topological_sort();
auto& nodes = model.getNodes();
map<string, vector<int>> states = oldStates;
vector<int> indicesToReDiscretize;
bool upgrade = false; // Flag to check if we need to upgrade the model
for (auto feature : order) {
auto nodeParents = nodes[feature]->getParents();
if (nodeParents.size() < 2) continue; // Only has class as parent
upgrade = true;
int index = find(pFeatures.begin(), pFeatures.end(), feature) - pFeatures.begin();
indicesToReDiscretize.push_back(index); // We need to re-discretize this feature
vector<string> parents;
transform(nodeParents.begin(), nodeParents.end(), back_inserter(parents), [](const auto& p) { return p->getName(); });
// Remove class as parent as it will be added later
parents.erase(remove(parents.begin(), parents.end(), pClassName), parents.end());
// Get the indices of the parents
vector<int> indices;
indices.push_back(-1); // Add class index
transform(parents.begin(), parents.end(), back_inserter(indices), [&](const auto& p) {return find(pFeatures.begin(), pFeatures.end(), p) - pFeatures.begin(); });
// Now we fit the discretizer of the feature, conditioned on its parents and the class i.e. discretizer.fit(X[index], X[indices] + y)
vector<string> yJoinParents(Xf.size(1));
for (auto idx : indices) {
for (int i = 0; i < Xf.size(1); ++i) {
yJoinParents[i] += to_string(pDataset.index({ idx, i }).item<int>());
}
}
auto arff = ArffFiles();
auto yxv = arff.factorize(yJoinParents);
auto xvf_ptr = Xf.index({ index }).data_ptr<float>();
auto xvf = vector<mdlp::precision_t>(xvf_ptr, xvf_ptr + Xf.size(1));
discretizers[feature]->fit(xvf, yxv);
//
//
//
// auto tmp = discretizers[feature]->transform(xvf);
// Xv[index] = tmp;
// auto xStates = vector<int>(discretizers[pFeatures[index]]->getCutPoints().size() + 1);
// iota(xStates.begin(), xStates.end(), 0);
// //Update new states of the feature/node
// states[feature] = xStates;
}
if (upgrade) {
// Discretize again X (only the affected indices) with the new fitted discretizers
for (auto index : indicesToReDiscretize) {
auto Xt_ptr = Xf.index({ index }).data_ptr<float>();
auto Xt = vector<float>(Xt_ptr, Xt_ptr + Xf.size(1));
pDataset.index_put_({ index, "..." }, torch::tensor(discretizers[pFeatures[index]]->transform(Xt)));
auto xStates = vector<int>(discretizers[pFeatures[index]]->getCutPoints().size() + 1);
iota(xStates.begin(), xStates.end(), 0);
//Update new states of the feature/node
states[pFeatures[index]] = xStates;
}
const torch::Tensor weights = torch::ones({ pDataset.size(1) }, torch::kFloat);
model.fit(pDataset, weights, pFeatures, pClassName, states);
}
return states;
}
map<string, vector<int>> Proposal::fit_local_discretization(const torch::Tensor& y)
{
// Discretize the continuous input data and build pDataset (Classifier::dataset)
int m = Xf.size(1);
int n = Xf.size(0);
map<string, vector<int>> states;
pDataset = torch::zeros({ n + 1, m }, kInt32);
auto yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
// discretize input data by feature(row)
for (auto i = 0; i < pFeatures.size(); ++i) {
auto* discretizer = new mdlp::CPPFImdlp();
auto Xt_ptr = Xf.index({ i }).data_ptr<float>();
auto Xt = vector<float>(Xt_ptr, Xt_ptr + Xf.size(1));
discretizer->fit(Xt, yv);
pDataset.index_put_({ i, "..." }, torch::tensor(discretizer->transform(Xt)));
auto xStates = vector<int>(discretizer->getCutPoints().size() + 1);
iota(xStates.begin(), xStates.end(), 0);
states[pFeatures[i]] = xStates;
discretizers[pFeatures[i]] = discretizer;
}
int n_classes = torch::max(y).item<int>() + 1;
auto yStates = vector<int>(n_classes);
iota(yStates.begin(), yStates.end(), 0);
states[pClassName] = yStates;
pDataset.index_put_({ n, "..." }, y);
return states;
}
torch::Tensor Proposal::prepareX(torch::Tensor& X)
{
auto Xtd = torch::zeros_like(X, torch::kInt32);
for (int i = 0; i < X.size(0); ++i) {
auto Xt = vector<float>(X[i].data_ptr<float>(), X[i].data_ptr<float>() + X.size(1));
auto Xd = discretizers[pFeatures[i]]->transform(Xt);
Xtd.index_put_({ i }, torch::tensor(Xd, torch::kInt32));
}
return Xtd;
}
}

29
src/BayesNet/Proposal.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef PROPOSAL_H
#define PROPOSAL_H
#include <string>
#include <map>
#include <torch/torch.h>
#include "Network.h"
#include "CPPFImdlp.h"
#include "Classifier.h"
namespace bayesnet {
class Proposal {
public:
Proposal(torch::Tensor& pDataset, vector<string>& features_, string& className_);
virtual ~Proposal();
protected:
torch::Tensor prepareX(torch::Tensor& X);
map<string, vector<int>> localDiscretizationProposal(const map<string, vector<int>>& states, Network& model);
map<string, vector<int>> fit_local_discretization(const torch::Tensor& y);
torch::Tensor Xf; // X continuous nxm tensor
torch::Tensor y; // y discrete nx1 tensor
map<string, mdlp::CPPFImdlp*> discretizers;
private:
torch::Tensor& pDataset; // (n+1)xm tensor
vector<string>& pFeatures;
string& pClassName;
};
}
#endif

View File

@@ -4,7 +4,7 @@ namespace bayesnet {
SPODE::SPODE(int root) : Classifier(Network()), root(root) {} SPODE::SPODE(int root) : Classifier(Network()), root(root) {}
void SPODE::train() void SPODE::buildModel()
{ {
// 0. Add all nodes to the model // 0. Add all nodes to the model
addNodes(); addNodes();
@@ -17,7 +17,7 @@ namespace bayesnet {
} }
} }
} }
vector<string> SPODE::graph(string name ) vector<string> SPODE::graph(const string& name) const
{ {
return model.graph(name); return model.graph(name);
} }

View File

@@ -7,11 +7,11 @@ namespace bayesnet {
private: private:
int root; int root;
protected: protected:
void train() override; void buildModel() override;
public: public:
explicit SPODE(int root); explicit SPODE(int root);
virtual ~SPODE() {}; virtual ~SPODE() {};
vector<string> graph(string name = "SPODE") override; vector<string> graph(const string& name = "SPODE") const override;
}; };
} }
#endif #endif

47
src/BayesNet/SPODELd.cc Normal file
View File

@@ -0,0 +1,47 @@
#include "SPODELd.h"
namespace bayesnet {
using namespace std;
SPODELd::SPODELd(int root) : SPODE(root), Proposal(dataset, features, className) {}
SPODELd& SPODELd::fit(torch::Tensor& X_, torch::Tensor& y_, vector<string>& features_, string className_, map<string, vector<int>>& states_)
{
// This first part should go in a Classifier method called fit_local_discretization o fit_float...
features = features_;
className = className_;
Xf = X_;
y = y_;
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
states = fit_local_discretization(y);
// We have discretized the input data
// 1st we need to fit the model to build the normal SPODE structure, SPODE::fit initializes the base Bayesian network
SPODE::fit(dataset, features, className, states);
states = localDiscretizationProposal(states, model);
return *this;
}
SPODELd& SPODELd::fit(torch::Tensor& dataset, vector<string>& features_, string className_, map<string, vector<int>>& states_)
{
Xf = dataset.index({ torch::indexing::Slice(0, dataset.size(0) - 1), "..." }).clone();
cout << "Xf " << Xf.sizes() << " dtype: " << Xf.dtype() << endl;
y = dataset.index({ -1, "..." }).clone();
// This first part should go in a Classifier method called fit_local_discretization o fit_float...
features = features_;
className = className_;
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
states = fit_local_discretization(y);
// We have discretized the input data
// 1st we need to fit the model to build the normal SPODE structure, SPODE::fit initializes the base Bayesian network
SPODE::fit(dataset, features, className, states);
states = localDiscretizationProposal(states, model);
return *this;
}
Tensor SPODELd::predict(Tensor& X)
{
auto Xt = prepareX(X);
return SPODE::predict(Xt);
}
vector<string> SPODELd::graph(const string& name) const
{
return SPODE::graph(name);
}
}

19
src/BayesNet/SPODELd.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef SPODELD_H
#define SPODELD_H
#include "SPODE.h"
#include "Proposal.h"
namespace bayesnet {
using namespace std;
class SPODELd : public SPODE, public Proposal {
public:
explicit SPODELd(int root);
virtual ~SPODELd() = default;
SPODELd& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) override;
SPODELd& fit(torch::Tensor& dataset, vector<string>& features, string className, map<string, vector<int>>& states) override;
vector<string> graph(const string& name = "SPODE") const override;
Tensor predict(Tensor& X) override;
static inline string version() { return "0.0.1"; };
};
}
#endif // !SPODELD_H

View File

@@ -5,25 +5,25 @@ namespace bayesnet {
TAN::TAN() : Classifier(Network()) {} TAN::TAN() : Classifier(Network()) {}
void TAN::train() void TAN::buildModel()
{ {
// 0. Add all nodes to the model // 0. Add all nodes to the model
addNodes(); addNodes();
// 1. Compute mutual information between each feature and the class and set the root node // 1. Compute mutual information between each feature and the class and set the root node
// as the highest mutual information with the class // as the highest mutual information with the class
auto mi = vector <pair<int, float >>(); auto mi = vector <pair<int, float >>();
Tensor class_dataset = dataset.index({ "...", -1 }); Tensor class_dataset = dataset.index({ -1, "..." });
for (int i = 0; i < static_cast<int>(features.size()); ++i) { for (int i = 0; i < static_cast<int>(features.size()); ++i) {
Tensor feature_dataset = dataset.index({ "...", i }); Tensor feature_dataset = dataset.index({ i, "..." });
auto mi_value = metrics.mutualInformation(class_dataset, feature_dataset); auto mi_value = metrics.mutualInformation(class_dataset, feature_dataset, weights);
mi.push_back({ i, mi_value }); mi.push_back({ i, mi_value });
} }
sort(mi.begin(), mi.end(), [](const auto& left, const auto& right) {return left.second < right.second;}); sort(mi.begin(), mi.end(), [](const auto& left, const auto& right) {return left.second < right.second;});
auto root = mi[mi.size() - 1].first; auto root = mi[mi.size() - 1].first;
// 2. Compute mutual information between each feature and the class // 2. Compute mutual information between each feature and the class
auto weights = metrics.conditionalEdge(); auto weights_matrix = metrics.conditionalEdge(weights);
// 3. Compute the maximum spanning tree // 3. Compute the maximum spanning tree
auto mst = metrics.maximumSpanningTree(features, weights, root); auto mst = metrics.maximumSpanningTree(features, weights_matrix, root);
// 4. Add edges from the maximum spanning tree to the model // 4. Add edges from the maximum spanning tree to the model
for (auto i = 0; i < mst.size(); ++i) { for (auto i = 0; i < mst.size(); ++i) {
auto [from, to] = mst[i]; auto [from, to] = mst[i];
@@ -34,7 +34,7 @@ namespace bayesnet {
model.addEdge(className, feature); model.addEdge(className, feature);
} }
} }
vector<string> TAN::graph(string title) vector<string> TAN::graph(const string& title) const
{ {
return model.graph(title); return model.graph(title);
} }

View File

@@ -7,11 +7,11 @@ namespace bayesnet {
class TAN : public Classifier { class TAN : public Classifier {
private: private:
protected: protected:
void train() override; void buildModel() override;
public: public:
TAN(); TAN();
virtual ~TAN() {}; virtual ~TAN() {};
vector<string> graph(string name = "TAN") override; vector<string> graph(const string& name = "TAN") const override;
}; };
} }
#endif #endif

31
src/BayesNet/TANLd.cc Normal file
View File

@@ -0,0 +1,31 @@
#include "TANLd.h"
namespace bayesnet {
using namespace std;
TANLd::TANLd() : TAN(), Proposal(dataset, features, className) {}
TANLd& TANLd::fit(torch::Tensor& X_, torch::Tensor& y_, vector<string>& features_, string className_, map<string, vector<int>>& states_)
{
// This first part should go in a Classifier method called fit_local_discretization o fit_float...
features = features_;
className = className_;
Xf = X_;
y = y_;
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
states = fit_local_discretization(y);
// We have discretized the input data
// 1st we need to fit the model to build the normal TAN structure, TAN::fit initializes the base Bayesian network
TAN::fit(dataset, features, className, states);
states = localDiscretizationProposal(states, model);
return *this;
}
Tensor TANLd::predict(Tensor& X)
{
auto Xt = prepareX(X);
return TAN::predict(Xt);
}
vector<string> TANLd::graph(const string& name) const
{
return TAN::graph(name);
}
}

19
src/BayesNet/TANLd.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef TANLD_H
#define TANLD_H
#include "TAN.h"
#include "Proposal.h"
namespace bayesnet {
using namespace std;
class TANLd : public TAN, public Proposal {
private:
public:
TANLd();
virtual ~TANLd() = default;
TANLd& fit(torch::Tensor& X, torch::Tensor& y, vector<string>& features, string className, map<string, vector<int>>& states) override;
vector<string> graph(const string& name = "TAN") const override;
Tensor predict(Tensor& X) override;
static inline string version() { return "0.0.1"; };
};
}
#endif // !TANLD_H

View File

@@ -3,6 +3,7 @@
namespace bayesnet { namespace bayesnet {
using namespace std; using namespace std;
using namespace torch; using namespace torch;
// Return the indices in descending order
vector<int> argsort(vector<float>& nums) vector<int> argsort(vector<float>& nums)
{ {
int n = nums.size(); int n = nums.size();

View File

@@ -4,5 +4,5 @@ include_directories(${BayesNet_SOURCE_DIR}/lib/Files)
include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp) include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp)
include_directories(${BayesNet_SOURCE_DIR}/lib/argparse/include) include_directories(${BayesNet_SOURCE_DIR}/lib/argparse/include)
include_directories(${BayesNet_SOURCE_DIR}/lib/json/include) include_directories(${BayesNet_SOURCE_DIR}/lib/json/include)
add_executable(main main.cc Folding.cc platformUtils.cc Experiment.cc Datasets.cc Models.cc) add_executable(main main.cc Folding.cc platformUtils.cc Experiment.cc Datasets.cc Models.cc Report.cc)
target_link_libraries(main BayesNet ArffFiles mdlp "${TORCH_LIBRARIES}") target_link_libraries(main BayesNet ArffFiles mdlp "${TORCH_LIBRARIES}")

View File

@@ -207,9 +207,9 @@ namespace platform {
if (discretize) { if (discretize) {
Xd = discretizeDataset(Xv, yv); Xd = discretizeDataset(Xv, yv);
computeStates(); computeStates();
n_samples = Xd[0].size();
n_features = Xd.size();
} }
n_samples = Xv[0].size();
n_features = Xv.size();
loaded = true; loaded = true;
} }
void Dataset::buildTensors() void Dataset::buildTensors()

View File

@@ -1,6 +1,7 @@
#include "Experiment.h" #include "Experiment.h"
#include "Datasets.h" #include "Datasets.h"
#include "Models.h" #include "Models.h"
#include "Report.h"
namespace platform { namespace platform {
using json = nlohmann::json; using json = nlohmann::json;
@@ -86,6 +87,13 @@ namespace platform {
file.close(); file.close();
} }
void Experiment::report()
{
json data = build_json();
Report report(data);
report.show();
}
void Experiment::show() void Experiment::show()
{ {
json data = build_json(); json data = build_json();
@@ -104,7 +112,7 @@ namespace platform {
void Experiment::cross_validation(const string& path, const string& fileName) void Experiment::cross_validation(const string& path, const string& fileName)
{ {
auto datasets = platform::Datasets(path, true, platform::ARFF); auto datasets = platform::Datasets(path, discretized, platform::ARFF);
// Get dataset // Get dataset
auto [X, y] = datasets.getTensors(fileName); auto [X, y] = datasets.getTensors(fileName);
auto states = datasets.getStates(fileName); auto states = datasets.getStates(fileName);
@@ -114,7 +122,7 @@ namespace platform {
cout << " (" << setw(5) << samples << "," << setw(3) << features.size() << ") " << flush; cout << " (" << setw(5) << samples << "," << setw(3) << features.size() << ") " << flush;
// Prepare Result // Prepare Result
auto result = Result(); auto result = Result();
auto [values, counts] = at::_unique(y);; auto [values, counts] = at::_unique(y);
result.setSamples(X.size(1)).setFeatures(X.size(0)).setClasses(values.size(0)); result.setSamples(X.size(1)).setFeatures(X.size(0)).setClasses(values.size(0));
int nResults = nfolds * static_cast<int>(randomSeeds.size()); int nResults = nfolds * static_cast<int>(randomSeeds.size());
auto accuracy_test = torch::zeros({ nResults }, torch::kFloat64); auto accuracy_test = torch::zeros({ nResults }, torch::kFloat64);

View File

@@ -108,6 +108,7 @@ namespace platform {
void cross_validation(const string& path, const string& fileName); void cross_validation(const string& path, const string& fileName);
void go(vector<string> filesToProcess, const string& path); void go(vector<string> filesToProcess, const string& path);
void show(); void show();
void report();
}; };
} }
#endif #endif

View File

@@ -6,6 +6,10 @@
#include "TAN.h" #include "TAN.h"
#include "KDB.h" #include "KDB.h"
#include "SPODE.h" #include "SPODE.h"
#include "TANLd.h"
#include "KDBLd.h"
#include "SPODELd.h"
#include "AODELd.h"
namespace platform { namespace platform {
class Models { class Models {
private: private:

67
src/Platform/Report.cc Normal file
View File

@@ -0,0 +1,67 @@
#include "Report.h"
namespace platform {
string headerLine(const string& text)
{
int n = MAXL - text.length() - 3;
n = n < 0 ? 0 : n;
return "* " + text + string(n, ' ') + "*\n";
}
string Report::fromVector(const string& key)
{
string result = "";
for (auto& item : data[key]) {
result += to_string(item) + ", ";
}
return "[" + result.substr(0, result.size() - 2) + "]";
}
string fVector(const json& data)
{
string result = "";
for (const auto& item : data) {
result += to_string(item) + ", ";
}
return "[" + result.substr(0, result.size() - 2) + "]";
}
void Report::show()
{
header();
body();
}
void Report::header()
{
cout << string(MAXL, '*') << endl;
cout << headerLine("Report " + data["model"].get<string>() + " ver. " + data["version"].get<string>() + " with " + to_string(data["folds"].get<int>()) + " Folds cross validation and " + to_string(data["seeds"].size()) + " random seeds. " + data["date"].get<string>() + " " + data["time"].get<string>());
cout << headerLine(data["title"].get<string>());
cout << headerLine("Random seeds: " + fromVector("seeds") + " Stratified: " + (data["stratified"].get<bool>() ? "True" : "False"));
cout << headerLine("Execution took " + to_string(data["duration"].get<float>()) + " seconds, " + to_string(data["duration"].get<float>() / 3600) + " hours, on " + data["platform"].get<string>());
cout << headerLine("Score is " + data["score_name"].get<string>());
cout << string(MAXL, '*') << endl;
cout << endl;
}
void Report::body()
{
cout << "Dataset Sampl. Feat. Cls Nodes Edges States Score Time Hyperparameters" << endl;
cout << "============================== ====== ===== === ======= ======= ======= =============== ================= ===============" << endl;
for (const auto& r : data["results"]) {
cout << setw(30) << left << r["dataset"].get<string>() << " ";
cout << setw(6) << right << r["samples"].get<int>() << " ";
cout << setw(5) << right << r["features"].get<int>() << " ";
cout << setw(3) << right << r["classes"].get<int>() << " ";
cout << setw(7) << setprecision(2) << fixed << r["nodes"].get<float>() << " ";
cout << setw(7) << setprecision(2) << fixed << r["leaves"].get<float>() << " ";
cout << setw(7) << setprecision(2) << fixed << r["depth"].get<float>() << " ";
cout << setw(8) << right << setprecision(6) << fixed << r["score_test"].get<double>() << "±" << setw(6) << setprecision(4) << fixed << r["score_test_std"].get<double>() << " ";
cout << setw(10) << right << setprecision(6) << fixed << r["test_time"].get<double>() << "±" << setw(6) << setprecision(4) << fixed << r["test_time_std"].get<double>() << " ";
cout << " " << r["hyperparameters"].get<string>();
cout << endl;
cout << string(MAXL, '*') << endl;
cout << headerLine("Train scores: " + fVector(r["scores_train"]));
cout << headerLine("Test scores: " + fVector(r["scores_test"]));
cout << headerLine("Train times: " + fVector(r["times_train"]));
cout << headerLine("Test times: " + fVector(r["times_test"]));
cout << string(MAXL, '*') << endl;
}
}
}

23
src/Platform/Report.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef REPORT_H
#define REPORT_H
#include <string>
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
const int MAXL = 121;
namespace platform {
using namespace std;
class Report {
public:
explicit Report(json data_) { data = data_; };
virtual ~Report() = default;
void show();
private:
void header();
void body();
string fromVector(const string& key);
json data;
};
};
#endif

View File

@@ -99,13 +99,13 @@ int main(int argc, char** argv)
filesToTest = platform::Datasets(path, true, platform::ARFF).getNames(); filesToTest = platform::Datasets(path, true, platform::ARFF).getNames();
saveResults = true; saveResults = true;
} }
/* /*
* Begin Processing * Begin Processing
*/ */
auto env = platform::DotEnv();
auto experiment = platform::Experiment(); auto experiment = platform::Experiment();
experiment.setTitle(title).setLanguage("cpp").setLanguageVersion("1.0.0"); experiment.setTitle(title).setLanguage("cpp").setLanguageVersion("1.0.0");
experiment.setDiscretized(discretize_dataset).setModel(model_name).setPlatform("BayesNet"); experiment.setDiscretized(discretize_dataset).setModel(model_name).setPlatform(env.get("platform"));
experiment.setStratified(stratified).setNFolds(n_folds).setScoreName("accuracy"); experiment.setStratified(stratified).setNFolds(n_folds).setScoreName("accuracy");
for (auto seed : seeds) { for (auto seed : seeds) {
experiment.addRandomSeed(seed); experiment.addRandomSeed(seed);
@@ -117,7 +117,7 @@ int main(int argc, char** argv)
if (saveResults) if (saveResults)
experiment.save(PATH_RESULTS); experiment.save(PATH_RESULTS);
else else
experiment.show(); experiment.report();
cout << "Done!" << endl; cout << "Done!" << endl;
return 0; return 0;
} }

View File

@@ -2,10 +2,18 @@
#define MODEL_REGISTER_H #define MODEL_REGISTER_H
static platform::Registrar registrarT("TAN", static platform::Registrar registrarT("TAN",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::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", static platform::Registrar registrarS("SPODE",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODE(2);}); [](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODE(2);});
static platform::Registrar registrarSLD("SPODELd",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODELd(2);});
static platform::Registrar registrarK("KDB", static platform::Registrar registrarK("KDB",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDB(2);}); [](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", static platform::Registrar registrarA("AODE",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODE();}); [](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODE();});
static platform::Registrar registrarALD("AODELd",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODELd();});
#endif #endif

View File

@@ -9,29 +9,21 @@ TEST_CASE("Test Bayesian Network")
{ {
auto [Xd, y, features, className, states] = loadFile("iris"); auto [Xd, y, features, className, states] = loadFile("iris");
SECTION("Test Update Nodes")
{
auto net = bayesnet::Network();
net.addNode("A", 3);
REQUIRE(net.getStates() == 3);
net.addNode("A", 5);
REQUIRE(net.getStates() == 5);
}
SECTION("Test get features") SECTION("Test get features")
{ {
auto net = bayesnet::Network(); auto net = bayesnet::Network();
net.addNode("A", 3); net.addNode("A");
net.addNode("B", 5); net.addNode("B");
REQUIRE(net.getFeatures() == vector<string>{"A", "B"}); REQUIRE(net.getFeatures() == vector<string>{"A", "B"});
net.addNode("C", 2); net.addNode("C");
REQUIRE(net.getFeatures() == vector<string>{"A", "B", "C"}); REQUIRE(net.getFeatures() == vector<string>{"A", "B", "C"});
} }
SECTION("Test get edges") SECTION("Test get edges")
{ {
auto net = bayesnet::Network(); auto net = bayesnet::Network();
net.addNode("A", 3); net.addNode("A");
net.addNode("B", 5); net.addNode("B");
net.addNode("C", 2); net.addNode("C");
net.addEdge("A", "B"); net.addEdge("A", "B");
net.addEdge("B", "C"); net.addEdge("B", "C");
REQUIRE(net.getEdges() == vector<pair<string, string>>{ {"A", "B"}, { "B", "C" } }); REQUIRE(net.getEdges() == vector<pair<string, string>>{ {"A", "B"}, { "B", "C" } });