Add KDBNew fix computeCPT error

This commit is contained in:
Ricardo Montañana Gómez 2023-08-05 14:40:42 +02:00
parent a1c6ab18f3
commit 1a09ccca4c
Signed by: rmontanana
GPG Key ID: 46064262FD9A7ADE
17 changed files with 192 additions and 111 deletions

View File

@ -14,6 +14,9 @@ setup: ## Install dependencies for tests and coverage
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
build: ## Build the main and BayesNetSample
cmake --build build -t main -t BayesNetSample -j 32
debug: ## Build a debug version of the project
@echo ">>> Building Debug BayesNet ...";
@if [ -d ./build ]; then rm -rf ./build; fi

View File

@ -178,61 +178,59 @@ int main(int argc, char** argv)
cout << "end." << endl;
auto score = clf->score(Xd, y);
cout << "Score: " << score << endl;
auto graph = clf->graph();
auto dot_file = model_name + "_" + file_name;
ofstream file(dot_file + ".dot");
file << graph;
file.close();
cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl;
cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl;
string stratified_string = stratified ? " Stratified" : "";
cout << nFolds << " Folds" << stratified_string << " Cross validation" << endl;
cout << "==========================================" << endl;
torch::Tensor Xt = torch::zeros({ static_cast<int>(Xd.size()), static_cast<int>(Xd[0].size()) }, torch::kInt32);
torch::Tensor yt = torch::tensor(y, torch::kInt32);
for (int i = 0; i < features.size(); ++i) {
Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], torch::kInt32));
}
float total_score = 0, total_score_train = 0, score_train, score_test;
Fold* fold;
if (stratified)
fold = new StratifiedKFold(nFolds, y, seed);
else
fold = new KFold(nFolds, y.size(), seed);
for (auto i = 0; i < nFolds; ++i) {
auto [train, test] = fold->getFold(i);
cout << "Fold: " << i + 1 << endl;
if (tensors) {
auto ttrain = torch::tensor(train, torch::kInt64);
auto ttest = torch::tensor(test, torch::kInt64);
torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain);
torch::Tensor ytraint = yt.index({ ttrain });
torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest);
torch::Tensor ytestt = yt.index({ ttest });
clf->fit(Xtraint, ytraint, features, className, states);
auto temp = clf->predict(Xtraint);
score_train = clf->score(Xtraint, ytraint);
score_test = clf->score(Xtestt, ytestt);
} else {
auto [Xtrain, ytrain] = extract_indices(train, Xd, y);
auto [Xtest, ytest] = extract_indices(test, Xd, y);
clf->fit(Xtrain, ytrain, features, className, states);
score_train = clf->score(Xtrain, ytrain);
score_test = clf->score(Xtest, ytest);
}
if (dump_cpt) {
cout << "--- CPT Tables ---" << endl;
clf->dump_cpt();
}
total_score_train += score_train;
total_score += score_test;
cout << "Score Train: " << score_train << endl;
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;
// auto graph = clf->graph();
// auto dot_file = model_name + "_" + file_name;
// ofstream file(dot_file + ".dot");
// file << graph;
// file.close();
// cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl;
// cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl;
// string stratified_string = stratified ? " Stratified" : "";
// cout << nFolds << " Folds" << stratified_string << " Cross validation" << endl;
// cout << "==========================================" << endl;
// torch::Tensor Xt = torch::zeros({ static_cast<int>(Xd.size()), static_cast<int>(Xd[0].size()) }, torch::kInt32);
// torch::Tensor yt = torch::tensor(y, torch::kInt32);
// for (int i = 0; i < features.size(); ++i) {
// Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], torch::kInt32));
// }
// float total_score = 0, total_score_train = 0, score_train, score_test;
// Fold* fold;
// if (stratified)
// fold = new StratifiedKFold(nFolds, y, seed);
// else
// fold = new KFold(nFolds, y.size(), seed);
// for (auto i = 0; i < nFolds; ++i) {
// auto [train, test] = fold->getFold(i);
// cout << "Fold: " << i + 1 << endl;
// if (tensors) {
// auto ttrain = torch::tensor(train, torch::kInt64);
// auto ttest = torch::tensor(test, torch::kInt64);
// torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain);
// torch::Tensor ytraint = yt.index({ ttrain });
// torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest);
// torch::Tensor ytestt = yt.index({ ttest });
// clf->fit(Xtraint, ytraint, features, className, states);
// auto temp = clf->predict(Xtraint);
// score_train = clf->score(Xtraint, ytraint);
// score_test = clf->score(Xtestt, ytestt);
// } else {
// auto [Xtrain, ytrain] = extract_indices(train, Xd, y);
// auto [Xtest, ytest] = extract_indices(test, Xd, y);
// clf->fit(Xtrain, ytrain, features, className, states);
// score_train = clf->score(Xtrain, ytrain);
// score_test = clf->score(Xtest, ytest);
// }
// if (dump_cpt) {
// cout << "--- CPT Tables ---" << endl;
// clf->dump_cpt();
// }
// total_score_train += score_train;
// total_score += score_test;
// cout << "Score Train: " << score_train << endl;
// 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

@ -1,4 +1,4 @@
include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp)
include_directories(${BayesNet_SOURCE_DIR}/lib/Files)
add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc TANNew.cc Mst.cc Proposal.cc)
add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc TANNew.cc KDBNew.cc Mst.cc Proposal.cc)
target_link_libraries(BayesNet mdlp ArffFiles "${TORCH_LIBRARIES}")

View File

@ -36,14 +36,18 @@ namespace bayesnet {
yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
return build(features, className, states);
}
void Classifier::generateTensorXFromVector()
{
X = torch::zeros({ static_cast<int>(Xv.size()), static_cast<int>(Xv[0].size()) }, kInt32);
for (int i = 0; i < Xv.size(); ++i) {
X.index_put_({ i, "..." }, torch::tensor(Xv[i], kInt32));
}
}
// 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)
{
this->X = torch::zeros({ static_cast<int>(X.size()), static_cast<int>(X[0].size()) }, kInt32);
Xv = X;
for (int i = 0; i < X.size(); ++i) {
this->X.index_put_({ i, "..." }, torch::tensor(X[i], kInt32));
}
generateTensorXFromVector();
this->y = torch::tensor(y, kInt32);
yv = y;
return build(features, className, states);
@ -112,11 +116,9 @@ namespace bayesnet {
{
// Add all nodes to the network
for (const auto& feature : features) {
model.addNode(feature, states[feature].size());
cout << "-Adding node " << feature << " with " << states[feature].size() << " states" << endl;
model.addNode(feature);
}
model.addNode(className, states[className].size());
cout << "*Adding class " << className << " with " << states[className].size() << " states" << endl;
model.addNode(className);
}
int Classifier::getNumberOfNodes()
{

View File

@ -25,6 +25,7 @@ namespace bayesnet {
string className;
map<string, vector<int>> states;
void checkFitParameters();
void generateTensorXFromVector();
virtual void train() = 0;
public:
Classifier(Network model);

View File

@ -35,6 +35,7 @@ namespace bayesnet {
}
// 2. Compute class conditional mutual information I(Xi;XjIC), f or each
auto conditionalEdgeWeights = metrics.conditionalEdge();
cout << "Conditional edge weights: " << conditionalEdgeWeights << endl;
// 3. Let the used variable list, S, be empty.
vector<int> S;
// 4. Let the DAG network being constructed, BN, begin with a single

48
src/BayesNet/KDBNew.cc Normal file
View File

@ -0,0 +1,48 @@
#include "KDBNew.h"
namespace bayesnet {
using namespace std;
KDBNew::KDBNew(int k) : KDB(k), Proposal(KDB::Xv, KDB::yv, features, className) {}
KDBNew& KDBNew::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_;
model.initialize();
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
fit_local_discretization(states, y);
generateTensorXFromVector();
// 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
cout << "KDBNew: Fitting model" << endl;
KDB::fit(KDB::Xv, KDB::yv, features, className, states);
cout << "KDBNew: Model fitted" << endl;
localDiscretizationProposal(states, model);
generateTensorXFromVector();
Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1);
samples = torch::cat({ X, ytmp }, 0);
model.fit(KDB::Xv, KDB::yv, features, className);
return *this;
}
void KDBNew::train()
{
KDB::train();
}
Tensor KDBNew::predict(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[features[i]]->transform(Xt);
Xtd.index_put_({ i }, torch::tensor(Xd, torch::kInt32));
}
cout << "KDBNew Xtd: " << Xtd.sizes() << endl;
return KDB::predict(Xtd);
}
vector<string> KDBNew::graph(const string& name)
{
return KDB::graph(name);
}
}

20
src/BayesNet/KDBNew.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef KDBNEW_H
#define KDBNEW_H
#include "KDB.h"
#include "Proposal.h"
namespace bayesnet {
using namespace std;
class KDBNew : public KDB, public Proposal {
private:
public:
KDBNew(int k);
virtual ~KDBNew() = default;
KDBNew& 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") override;
Tensor predict(Tensor& X) override;
void train() override;
static inline string version() { return "0.0.1"; };
};
}
#endif // !KDBNew_H

View File

@ -31,21 +31,18 @@ namespace bayesnet {
{
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()) {
features.push_back(name);
}
if (nodes.find(name) != nodes.end()) {
// 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);
nodes[name] = std::make_unique<Node>(name);
}
vector<string> Network::getFeatures()
{
@ -128,14 +125,20 @@ namespace bayesnet {
}
}
}
void Network::setStates()
{
// Set states to every Node in the network
for (int i = 0; i < features.size(); ++i) {
nodes[features[i]]->setNumStates(static_cast<int>(torch::max(samples.index({ i, "..." })).item<int>()) + 1);
}
classNumStates = nodes[className]->getNumStates();
}
// X comes in nxm, where n is the number of features and m the number of samples
void Network::fit(torch::Tensor& X, torch::Tensor& y, const vector<string>& featureNames, const string& className)
{
checkFitData(X.size(1), X.size(0), y.size(0), featureNames, className);
this->className = className;
dataset.clear();
// Specific part
classNumStates = torch::max(y).item<int>() + 1;
Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1);
samples = torch::cat({ X , ytmp }, 0);
for (int i = 0; i < featureNames.size(); ++i) {
@ -151,8 +154,6 @@ namespace bayesnet {
checkFitData(input_data[0].size(), input_data.size(), labels.size(), featureNames, className);
this->className = className;
dataset.clear();
// Specific part
classNumStates = *max_element(labels.begin(), labels.end()) + 1;
// Build dataset & 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) {
@ -165,6 +166,7 @@ namespace bayesnet {
}
void Network::completeFit()
{
setStates();
int maxThreadsRunning = static_cast<int>(std::thread::hardware_concurrency() * maxThreads);
if (maxThreadsRunning < 1) {
maxThreadsRunning = 1;
@ -213,7 +215,7 @@ namespace bayesnet {
auto 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, "..." }, torch::tensor(predict_sample(sample), torch::kFloat64));
result.index_put_({ i, "..." }, temp);
}
if (proba)
@ -325,17 +327,17 @@ namespace bayesnet {
vector<thread> threads;
mutex mtx;
for (int i = 0; i < classNumStates; ++i) {
// threads.emplace_back([this, &result, &evidence, i, &mtx]() {
auto completeEvidence = map<string, int>(evidence);
completeEvidence[getClassName()] = i;
threads.emplace_back([this, &result, &evidence, i, &mtx]() {
auto completeEvidence = map<string, int>(evidence);
completeEvidence[getClassName()] = i;
double factor = computeFactor(completeEvidence);
// lock_guard<mutex> lock(mtx);
lock_guard<mutex> lock(mtx);
result[i] = factor;
// });
});
}
for (auto& thread : threads) {
thread.join();
}
// for (auto& thread : threads) {
// thread.join();
// }
// Normalize result
double sum = accumulate(result.begin(), result.end(), 0.0);
transform(result.begin(), result.end(), result.begin(), [sum](double& value) { return value / sum; });
@ -421,7 +423,7 @@ namespace bayesnet {
void Network::dump_cpt()
{
for (auto& node : nodes) {
cout << "* " << node.first << ": " << node.second->getCPT() << endl;
cout << "* " << node.first << ": (" << node.second->getNumStates() << ") : " << node.second->getCPT().sizes() << endl;
}
}
}

View File

@ -27,6 +27,7 @@ namespace bayesnet {
double mutualInformation(torch::Tensor&, torch::Tensor&);
void completeFit();
void checkFitData(int n_features, int n_samples, int n_samples_y, const vector<string>& featureNames, const string& className);
void setStates();
public:
Network();
explicit Network(float, int);
@ -34,7 +35,7 @@ namespace bayesnet {
explicit Network(Network&);
torch::Tensor& getSamples();
float getmaxThreads();
void addNode(const string&, int);
void addNode(const string&);
void addEdge(const string&, const string&);
map<string, std::unique_ptr<Node>>& getNodes();
vector<string> getFeatures();

View File

@ -2,8 +2,8 @@
namespace bayesnet {
Node::Node(const std::string& name, int numStates)
: name(name), numStates(numStates), cpTable(torch::Tensor()), parents(vector<Node*>()), children(vector<Node*>())
Node::Node(const std::string& name)
: name(name), numStates(0), cpTable(torch::Tensor()), parents(vector<Node*>()), children(vector<Node*>())
{
}
void Node::clear()
@ -86,6 +86,7 @@ namespace bayesnet {
}
void Node::computeCPT(map<string, vector<int>>& dataset, const int laplaceSmoothing)
{
dimensions.clear();
// Get dimensions of the CPT
dimensions.push_back(numStates);
transform(parents.begin(), parents.end(), back_inserter(dimensions), [](const auto& parent) { return parent->getNumStates(); });

View File

@ -16,7 +16,7 @@ namespace bayesnet {
vector<int64_t> dimensions; // dimensions of the cpTable
public:
vector<pair<string, string>> combinations(const vector<string>&);
Node(const string&, int);
explicit Node(const string&);
void clear();
void addParent(Node*);
void addChild(Node*);

View File

@ -2,22 +2,26 @@
namespace bayesnet {
using namespace std;
TANNew::TANNew() : TAN(), Proposal(TAN::Xv, TAN::yv, TAN::features, TAN::className) {}
TANNew::TANNew() : TAN(), Proposal(TAN::Xv, TAN::yv, features, className) {}
TANNew& TANNew::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...
TAN::features = features_;
TAN::className = className_;
features = features_;
className = className_;
Xf = X_;
y = y_;
// Fills vectors Xv & yv with the data from tensors X_ (discretized) & y
fit_local_discretization(states, y);
generateTensorXFromVector();
// 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
cout << "TANNew: Fitting model" << endl;
TAN::fit(TAN::Xv, TAN::yv, TAN::features, TAN::className, states);
TAN::fit(TAN::Xv, TAN::yv, features, className, states);
cout << "TANNew: Model fitted" << endl;
localDiscretizationProposal(states, model);
addNodes();
generateTensorXFromVector();
Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1);
samples = torch::cat({ X, ytmp }, 0);
model.fit(TAN::Xv, TAN::yv, features, className);
return *this;
}

View File

@ -146,6 +146,11 @@ namespace platform {
auto y_test = y.index({ test_t });
cout << nfold + 1 << ", " << flush;
clf->fit(X_train, y_train, features, className, states);
cout << endl;
auto lines = clf->show();
for (auto line : lines) {
cout << line << endl;
}
nodes[item] = clf->getNumberOfNodes();
edges[item] = clf->getNumberOfEdges();
num_states[item] = clf->getNumberOfStates();

View File

@ -7,6 +7,7 @@
#include "KDB.h"
#include "SPODE.h"
#include "TANNew.h"
#include "KDBNew.h"
namespace platform {
class Models {
private:

View File

@ -8,6 +8,8 @@ static platform::Registrar registrarS("SPODE",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODE(2);});
static platform::Registrar registrarK("KDB",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDB(2);});
static platform::Registrar registrarKN("KDBNew",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDBNew(2);});
static platform::Registrar registrarA("AODE",
[](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODE();});
#endif

View File

@ -9,29 +9,21 @@ TEST_CASE("Test Bayesian Network")
{
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")
{
auto net = bayesnet::Network();
net.addNode("A", 3);
net.addNode("B", 5);
net.addNode("A");
net.addNode("B");
REQUIRE(net.getFeatures() == vector<string>{"A", "B"});
net.addNode("C", 2);
net.addNode("C");
REQUIRE(net.getFeatures() == vector<string>{"A", "B", "C"});
}
SECTION("Test get edges")
{
auto net = bayesnet::Network();
net.addNode("A", 3);
net.addNode("B", 5);
net.addNode("C", 2);
net.addNode("A");
net.addNode("B");
net.addNode("C");
net.addEdge("A", "B");
net.addEdge("B", "C");
REQUIRE(net.getEdges() == vector<pair<string, string>>{ {"A", "B"}, { "B", "C" } });