TANNew as a TAN variant working

This commit is contained in:
Ricardo Montañana Gómez 2023-08-04 19:42:18 +02:00
parent c568ba111d
commit 64ac8fb4f2
Signed by: rmontanana
GPG Key ID: 46064262FD9A7ADE
9 changed files with 50 additions and 113 deletions

View File

@ -10,6 +10,7 @@ namespace bayesnet {
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 // 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() = default;
torch::Tensor virtual predict(torch::Tensor& X) = 0; 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;
@ -19,7 +20,6 @@ namespace bayesnet {
int virtual getNumberOfStates() = 0; int virtual getNumberOfStates() = 0;
vector<string> virtual show() = 0; vector<string> virtual show() = 0;
vector<string> virtual graph(const string& title = "") = 0; vector<string> virtual graph(const string& title = "") = 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; vector<string> virtual topological_order() = 0;
void virtual dump_cpt() = 0; void virtual dump_cpt() = 0;

View File

@ -1,4 +1,4 @@
include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp) include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp)
include_directories(${BayesNet_SOURCE_DIR}/lib/Files) 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) 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)
target_link_libraries(BayesNet mdlp ArffFiles "${TORCH_LIBRARIES}") target_link_libraries(BayesNet mdlp ArffFiles "${TORCH_LIBRARIES}")

View File

@ -1,6 +1,5 @@
#include "Classifier.h" #include "Classifier.h"
#include "bayesnetUtils.h" #include "bayesnetUtils.h"
#include "ArffFiles.h"
namespace bayesnet { namespace bayesnet {
using namespace torch; using namespace torch;
@ -40,7 +39,6 @@ namespace bayesnet {
// X is nxm where n is the number of features and m the number of samples // 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.size()), static_cast<int>(X[0].size()) }, kInt32); this->X = torch::zeros({ static_cast<int>(X.size()), static_cast<int>(X[0].size()) }, kInt32);
Xv = X; Xv = X;
for (int i = 0; i < X.size(); ++i) { for (int i = 0; i < X.size(); ++i) {
@ -141,57 +139,5 @@ namespace bayesnet {
{ {
model.dump_cpt(); model.dump_cpt();
} }
void Classifier::localDiscretizationProposal(map<string, mdlp::CPPFImdlp*>& discretizers, Tensor& Xf)
{
// order of local discretization is important. no good 0, 1, 2...
auto order = model.topological_sort();
auto& nodes = model.getNodes();
vector<int> indicesToReDiscretize;
auto n_samples = Xf.size(1);
bool upgrade = false; // Flag to check if we need to upgrade the model
for (auto feature : order) {
auto nodeParents = nodes[feature]->getParents();
int index = find(features.begin(), features.end(), feature) - features.begin();
vector<string> parents;
transform(nodeParents.begin(), nodeParents.end(), back_inserter(parents), [](const auto& p) {return p->getName(); });
if (parents.size() == 1) continue; // Only has class as parent
upgrade = true;
// Remove class as parent as it will be added later
parents.erase(remove(parents.begin(), parents.end(), className), parents.end());
// Get the indices of the parents
vector<int> indices;
transform(parents.begin(), parents.end(), back_inserter(indices), [&](const auto& p) {return find(features.begin(), features.end(), p) - features.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;
transform(yv.begin(), yv.end(), back_inserter(yJoinParents), [&](const auto& p) {return to_string(p); });
for (auto idx : indices) {
for (int i = 0; i < n_samples; ++i) {
yJoinParents[i] += to_string(Xv[idx][i]);
}
}
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);
indicesToReDiscretize.push_back(index);
}
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));
Xv[index] = discretizers[features[index]]->transform(Xt);
auto xStates = vector<int>(discretizers[features[index]]->getCutPoints().size() + 1);
iota(xStates.begin(), xStates.end(), 0);
states[features[index]] = xStates;
}
// Now we fit the model again with the new values
cout << "Classifier: Upgrading model" << endl;
// To update the nodes states
addNodes();
model.fit(Xv, yv, features, className);
cout << "Classifier: Model upgraded" << endl;
}
}
} }

View File

@ -4,7 +4,6 @@
#include "BaseClassifier.h" #include "BaseClassifier.h"
#include "Network.h" #include "Network.h"
#include "BayesMetrics.h" #include "BayesMetrics.h"
#include "CPPFImdlp.h"
using namespace std; using namespace std;
using namespace torch; using namespace torch;
@ -27,7 +26,6 @@ namespace bayesnet {
map<string, vector<int>> states; map<string, vector<int>> states;
void checkFitParameters(); void checkFitParameters();
virtual void train() = 0; virtual void train() = 0;
void localDiscretizationProposal(map<string, mdlp::CPPFImdlp*>& discretizers, Tensor& Xf);
public: public:
Classifier(Network model); Classifier(Network model);
virtual ~Classifier() = default; virtual ~Classifier() = default;

View File

@ -211,7 +211,10 @@ namespace bayesnet {
result = torch::zeros({ samples.size(1), classNumStates }, torch::kFloat64); result = torch::zeros({ samples.size(1), classNumStates }, torch::kFloat64);
for (int i = 0; i < samples.size(1); ++i) { for (int i = 0; i < samples.size(1); ++i) {
auto sample = samples.index({ "...", i }); auto sample = samples.index({ "...", i });
result.index_put_({ i, "..." }, torch::tensor(predict_sample(sample), torch::kFloat64)); 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) if (proba)
return result; return result;
@ -333,7 +336,6 @@ 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; });

View File

@ -2,8 +2,14 @@
#include "ArffFiles.h" #include "ArffFiles.h"
namespace bayesnet { namespace bayesnet {
Proposal::Proposal(vector<vector<int>>& Xv_, vector<int>& yv_) : Xv(Xv_), yv(yv_) {} Proposal::Proposal(vector<vector<int>>& Xv_, vector<int>& yv_, vector<string>& features_, string& className_) : Xv(Xv_), yv(yv_), pFeatures(features_), pClassName(className_) {}
void Proposal::localDiscretizationProposal(Network& model, vector<string>& features, string className, map<string, vector<int>>& states) Proposal::~Proposal()
{
for (auto& [key, value] : discretizers) {
delete value;
}
}
void Proposal::localDiscretizationProposal(map<string, vector<int>>& states, Network& model)
{ {
// order of local discretization is important. no good 0, 1, 2... // order of local discretization is important. no good 0, 1, 2...
auto order = model.topological_sort(); auto order = model.topological_sort();
@ -13,16 +19,17 @@ namespace bayesnet {
bool upgrade = false; // Flag to check if we need to upgrade the model bool upgrade = false; // Flag to check if we need to upgrade the model
for (auto feature : order) { for (auto feature : order) {
auto nodeParents = nodes[feature]->getParents(); auto nodeParents = nodes[feature]->getParents();
int index = find(features.begin(), features.end(), feature) - features.begin(); if (nodeParents.size() < 2) continue; // Only has class as parent
vector<string> parents;
transform(nodeParents.begin(), nodeParents.end(), back_inserter(parents), [](const auto& p) {return p->getName(); });
if (parents.size() == 1) continue; // Only has class as parent
upgrade = true; upgrade = true;
int index = find(pFeatures.begin(), pFeatures.end(), feature) - pFeatures.begin();
indicesToReDiscretize.push_back(index);
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 // Remove class as parent as it will be added later
parents.erase(remove(parents.begin(), parents.end(), className), parents.end()); parents.erase(remove(parents.begin(), parents.end(), pClassName), parents.end());
// Get the indices of the parents // Get the indices of the parents
vector<int> indices; vector<int> indices;
transform(parents.begin(), parents.end(), back_inserter(indices), [&](const auto& p) {return find(features.begin(), features.end(), p) - features.begin(); }); 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) // 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; vector<string> yJoinParents;
transform(yv.begin(), yv.end(), back_inserter(yJoinParents), [&](const auto& p) {return to_string(p); }); transform(yv.begin(), yv.end(), back_inserter(yJoinParents), [&](const auto& p) {return to_string(p); });
@ -36,27 +43,27 @@ namespace bayesnet {
auto xvf_ptr = Xf.index({ index }).data_ptr<float>(); auto xvf_ptr = Xf.index({ index }).data_ptr<float>();
auto xvf = vector<mdlp::precision_t>(xvf_ptr, xvf_ptr + Xf.size(1)); auto xvf = vector<mdlp::precision_t>(xvf_ptr, xvf_ptr + Xf.size(1));
discretizers[feature]->fit(xvf, yxv); discretizers[feature]->fit(xvf, yxv);
indicesToReDiscretize.push_back(index);
} }
if (upgrade) { if (upgrade) {
// Discretize again X (only the affected indices) with the new fitted discretizers // Discretize again X (only the affected indices) with the new fitted discretizers
for (auto index : indicesToReDiscretize) { for (auto index : indicesToReDiscretize) {
auto Xt_ptr = Xf.index({ index }).data_ptr<float>(); auto Xt_ptr = Xf.index({ index }).data_ptr<float>();
auto Xt = vector<float>(Xt_ptr, Xt_ptr + Xf.size(1)); auto Xt = vector<float>(Xt_ptr, Xt_ptr + Xf.size(1));
Xv[index] = discretizers[features[index]]->transform(Xt); Xv[index] = discretizers[pFeatures[index]]->transform(Xt);
auto xStates = vector<int>(discretizers[features[index]]->getCutPoints().size() + 1); auto xStates = vector<int>(discretizers[pFeatures[index]]->getCutPoints().size() + 1);
iota(xStates.begin(), xStates.end(), 0); iota(xStates.begin(), xStates.end(), 0);
states[features[index]] = xStates; //Update new states of the feature/node
states[pFeatures[index]] = xStates;
} }
} }
} }
void Proposal::fit_local_discretization(vector<string>& features, string className, map<string, vector<int>>& states, torch::Tensor& y) void Proposal::fit_local_discretization(map<string, vector<int>>& states, torch::Tensor& y)
{ {
// Sharing Xv and yv with Classifier
Xv = vector<vector<int>>(); Xv = vector<vector<int>>();
yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0)); yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
// discretize input data by feature(row) // discretize input data by feature(row)
for (int i = 0; i < features.size(); ++i) { for (int i = 0; i < pFeatures.size(); ++i) {
auto* discretizer = new mdlp::CPPFImdlp(); auto* discretizer = new mdlp::CPPFImdlp();
auto Xt_ptr = Xf.index({ i }).data_ptr<float>(); auto Xt_ptr = Xf.index({ i }).data_ptr<float>();
auto Xt = vector<float>(Xt_ptr, Xt_ptr + Xf.size(1)); auto Xt = vector<float>(Xt_ptr, Xt_ptr + Xf.size(1));
@ -64,12 +71,12 @@ namespace bayesnet {
Xv.push_back(discretizer->transform(Xt)); Xv.push_back(discretizer->transform(Xt));
auto xStates = vector<int>(discretizer->getCutPoints().size() + 1); auto xStates = vector<int>(discretizer->getCutPoints().size() + 1);
iota(xStates.begin(), xStates.end(), 0); iota(xStates.begin(), xStates.end(), 0);
states[features[i]] = xStates; states[pFeatures[i]] = xStates;
discretizers[features[i]] = discretizer; discretizers[pFeatures[i]] = discretizer;
} }
int n_classes = torch::max(y).item<int>() + 1; int n_classes = torch::max(y).item<int>() + 1;
auto yStates = vector<int>(n_classes); auto yStates = vector<int>(n_classes);
iota(yStates.begin(), yStates.end(), 0); iota(yStates.begin(), yStates.end(), 0);
states[className] = yStates; states[pClassName] = yStates;
} }
} }

View File

@ -9,14 +9,16 @@
namespace bayesnet { namespace bayesnet {
class Proposal { class Proposal {
public: public:
Proposal(vector<vector<int>>& Xv_, vector<int>& yv_); Proposal(vector<vector<int>>& Xv_, vector<int>& yv_, vector<string>& features_, string& className_);
virtual ~Proposal() = default; virtual ~Proposal();
protected: protected:
void localDiscretizationProposal(Network& model, vector<string>& features, string className, map<string, vector<int>>& states); void localDiscretizationProposal(map<string, vector<int>>& states, Network& model);
void fit_local_discretization(vector<string>& features, string className, map<string, vector<int>>& states, torch::Tensor& y); void fit_local_discretization(map<string, vector<int>>& states, torch::Tensor& y);
torch::Tensor Xf; // X continuous nxm tensor torch::Tensor Xf; // X continuous nxm tensor
map<string, mdlp::CPPFImdlp*> discretizers; map<string, mdlp::CPPFImdlp*> discretizers;
private: private:
vector<string>& pFeatures;
string& pClassName;
vector<vector<int>>& Xv; // X discrete nxm vector vector<vector<int>>& Xv; // X discrete nxm vector
vector<int>& yv; vector<int>& yv;
}; };

View File

@ -2,41 +2,25 @@
namespace bayesnet { namespace bayesnet {
using namespace std; using namespace std;
TANNew::TANNew() : TAN() {} TANNew::TANNew() : TAN(), Proposal(TAN::Xv, TAN::yv, TAN::features, TAN::className) {}
TANNew::~TANNew() {} TANNew::~TANNew() {}
TANNew& TANNew::fit(torch::Tensor& X_, torch::Tensor& y_, vector<string>& features_, string className_, map<string, vector<int>>& states_) 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_;
Xf = X_; Xf = X_;
y = y_; y = y_;
features = features_; fit_local_discretization(states, y);
className = className_; // We have discretized the input data
Xv = vector<vector<int>>(); // 1st we need to fit the model to build the normal TAN structure, TAN::fit initializes the base Bayesian network
yv = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
// discretize input data by feature(row)
for (int i = 0; i < features.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);
Xv.push_back(discretizer->transform(Xt));
auto xStates = vector<int>(discretizer->getCutPoints().size() + 1);
iota(xStates.begin(), xStates.end(), 0);
states[features[i]] = xStates;
discretizers[features[i]] = discretizer;
}
int n_classes = torch::max(y).item<int>() + 1;
auto yStates = vector<int>(n_classes);
iota(yStates.begin(), yStates.end(), 0);
states[className] = yStates;
// Now we have standard TAN and now we implement the proposal
// 1st we need to fit the model to build the TAN structure
cout << "TANNew: Fitting model" << endl; cout << "TANNew: Fitting model" << endl;
TAN::fit(Xv, yv, features, className, states); TAN::fit(TAN::Xv, TAN::yv, TAN::features, TAN::className, states);
cout << "TANNew: Model fitted" << endl; cout << "TANNew: Model fitted" << endl;
localDiscretizationProposal(discretizers, Xf); //localDiscretizationProposal(states, model);
//addNodes();
return *this; return *this;
} }
Tensor TANNew::predict(Tensor& X) Tensor TANNew::predict(Tensor& X)
{ {
auto Xtd = torch::zeros_like(X, torch::kInt32); auto Xtd = torch::zeros_like(X, torch::kInt32);

View File

@ -1,14 +1,12 @@
#ifndef TANNEW_H #ifndef TANNEW_H
#define TANNEW_H #define TANNEW_H
#include "TAN.h" #include "TAN.h"
#include "CPPFImdlp.h" #include "Proposal.h"
namespace bayesnet { namespace bayesnet {
using namespace std; using namespace std;
class TANNew : public TAN { class TANNew : public TAN, public Proposal {
private: private:
map<string, mdlp::CPPFImdlp*> discretizers;
torch::Tensor Xf; // X continuous nxm tensor
public: public:
TANNew(); TANNew();
virtual ~TANNew(); virtual ~TANNew();