Complete nxm

This commit is contained in:
Ricardo Montañana Gómez 2023-08-03 20:22:33 +02:00
parent f520b40016
commit eb1cec58a3
Signed by: rmontanana
GPG Key ID: 46064262FD9A7ADE
15 changed files with 193 additions and 111 deletions

5
.vscode/launch.json vendored
View File

@ -10,10 +10,11 @@
"-d",
"iris",
"-m",
"TANNew",
"KDB",
"-s",
"271",
"-p",
"/Users/rmontanana/Code/discretizbench/datasets/",
"--tensors"
],
//"cwd": "${workspaceFolder}/build/sample/",
},

View File

@ -14,7 +14,7 @@ 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
debug: ## Build the project
debug: ## Build a debug version of the project
@echo ">>> Building Debug BayesNet ...";
@if [ -d ./build ]; then rm -rf ./build; fi
@mkdir build;
@ -22,12 +22,12 @@ debug: ## Build the project
cmake --build build -j 32;
@echo ">>> Done";
release:
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 -j 32;
cmake --build build -t main -t BayesNetSample -j 32;
@echo ">>> Done";
test: ## Run tests

View File

@ -95,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("--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("--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) {
@ -112,7 +113,7 @@ int main(int argc, char** argv)
throw runtime_error("Number of folds must be an integer");
}});
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;
int nFolds, seed;
try {
@ -125,6 +126,7 @@ int main(int argc, char** argv)
tensors = program.get<bool>("tensors");
nFolds = program.get<int>("folds");
seed = program.get<int>("seed");
dump_cpt = program.get<bool>("dumpcpt");
class_last = datasets[file_name];
if (!file_exists(complete_file_name)) {
throw runtime_error("Data File " + path + file_name + ".arff" + " does not exist");
@ -158,21 +160,25 @@ int main(int argc, char** argv)
states[feature] = vector<int>(maxes[feature]);
}
states[className] = vector<int>(maxes[className]);
auto clf = platform::Models::instance()->create(model_name);
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 graph = clf->graph();
for (auto line : lines) {
cout << line << endl;
}
cout << "--- Topological Order ---" << endl;
for (auto name : clf->topological_order()) {
auto order = clf->topological_order();
for (auto name : order) {
cout << name << ", ";
}
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;
@ -211,9 +217,14 @@ int main(int argc, char** argv)
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;

View File

@ -6,7 +6,9 @@ namespace bayesnet {
using namespace std;
class BaseClassifier {
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;
// 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;
torch::Tensor virtual predict(torch::Tensor& X) = 0;
vector<int> virtual predict(vector<vector<int>>& X) = 0;
@ -20,6 +22,7 @@ namespace bayesnet {
virtual ~BaseClassifier() = default;
const string inline getVersion() const { return "0.1.0"; };
vector<string> virtual topological_order() = 0;
void virtual dump_cpt() = 0;
};
}
#endif

View File

@ -1,6 +1,7 @@
#include "BayesMetrics.h"
#include "Mst.h"
namespace bayesnet {
//samples is nxm tensor used to fit the model
Metrics::Metrics(torch::Tensor& samples, vector<string>& features, string& className, int classNumStates)
: samples(samples)
, features(features)
@ -8,6 +9,7 @@ namespace bayesnet {
, 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)
: features(features)
, 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))
{
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)
{
@ -39,17 +41,17 @@ namespace bayesnet {
// Compute class prior
auto margin = torch::zeros({ classNumStates });
for (int value = 0; value < classNumStates; ++value) {
auto mask = samples.index({ "...", -1 }) == value;
margin[value] = mask.sum().item<float>() / samples.sizes()[0];
auto mask = samples.index({ -1, "..." }) == value;
margin[value] = mask.sum().item<float>() / samples.size(1);
}
for (auto [first, second] : combinations) {
int index_first = find(features.begin(), features.end(), first) - features.begin();
int index_second = find(features.begin(), features.end(), second) - features.begin();
double accumulated = 0;
for (int value = 0; value < classNumStates; ++value) {
auto mask = samples.index({ "...", -1 }) == value;
auto first_dataset = samples.index({ mask, index_first });
auto second_dataset = samples.index({ mask, index_second });
auto mask = samples.index({ -1, "..." }) == value;
auto first_dataset = samples.index({ index_first, mask });
auto second_dataset = samples.index({ index_second, mask });
auto mi = mutualInformation(first_dataset, second_dataset);
auto pb = margin[value].item<float>();
accumulated += pb * mi;
@ -67,6 +69,7 @@ namespace bayesnet {
}
return matrix;
}
// To use in Python
vector<float> Metrics::conditionalEdgeWeights()
{
auto matrix = conditionalEdge();

View File

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

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)
target_link_libraries(BayesNet mdlp arff "${TORCH_LIBRARIES}")
target_link_libraries(BayesNet mdlp ArffFiles "${TORCH_LIBRARIES}")

View File

@ -7,15 +7,18 @@ namespace bayesnet {
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)
{
dataset = torch::cat({ X, y.view({y.size(0), 1}) }, 1);
Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1);
samples = torch::cat({ X, ytmp }, 0);
this->features = features;
this->className = className;
this->states = states;
cout << "Classifier samples: " << samples.sizes() << endl;
checkFitParameters();
auto n_classes = states[className].size();
metrics = Metrics(dataset, features, className, n_classes);
metrics = Metrics(samples, features, className, n_classes);
model.initialize();
train();
if (Xv == vector<vector<int>>()) {
if (Xv.empty()) {
// fit with tensors
model.fit(X, y, features, className);
} else {
@ -25,21 +28,23 @@ namespace bayesnet {
fitted = true;
return *this;
}
// 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)
{
this->X = torch::transpose(X, 0, 1);
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);
}
// 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[0].size()), static_cast<int>(X.size()) }, kInt32);
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));
this->X.index_put_({ i, "..." }, torch::tensor(X[i], kInt32));
}
this->y = torch::tensor(y, kInt32);
yv = y;
@ -48,8 +53,8 @@ namespace bayesnet {
void Classifier::checkFitParameters()
{
auto sizes = X.sizes();
m = sizes[0];
n = sizes[1];
m = sizes[1];
n = sizes[0];
if (m != y.size(0)) {
throw invalid_argument("X and y must have the same number of samples");
}
@ -70,9 +75,7 @@ namespace bayesnet {
if (!fitted) {
throw logic_error("Classifier has not been fitted");
}
auto Xt = torch::transpose(X, 0, 1); // Base classifiers expect samples as columns
auto y_proba = model.predict(Xt);
return y_proba.argmax(1);
return model.predict(X);
}
vector<int> Classifier::predict(vector<vector<int>>& X)
{
@ -101,12 +104,6 @@ namespace bayesnet {
if (!fitted) {
throw logic_error("Classifier has not been fitted");
}
// auto m_ = X[0].size();
// 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(X, y);
}
vector<string> Classifier::show()
@ -116,7 +113,7 @@ namespace bayesnet {
void Classifier::addNodes()
{
// Add all nodes to the network
for (auto feature : features) {
for (const auto& feature : features) {
model.addNode(feature, states[feature].size());
}
model.addNode(className, states[className].size());
@ -138,4 +135,8 @@ namespace bayesnet {
{
return model.topological_sort();
}
void Classifier::dump_cpt()
{
model.dump_cpt();
}
}

View File

@ -15,11 +15,11 @@ namespace bayesnet {
protected:
Network model;
int m, n; // m: number of samples, n: number of features
Tensor X;
vector<vector<int>> Xv;
Tensor X; // nxm tensor
vector<vector<int>> Xv; // nxm vector
Tensor y;
vector<int> yv;
Tensor dataset;
Tensor samples; // (n+1)xm tensor
Metrics metrics;
vector<string> features;
string className;
@ -41,6 +41,7 @@ namespace bayesnet {
float score(vector<vector<int>>& X, vector<int>& y) override;
vector<string> show() override;
vector<string> topological_order() override;
void dump_cpt() override;
};
}
#endif

View File

@ -45,6 +45,9 @@ namespace bayesnet {
{
return vector<string>();
}
void dump_cpt() override
{
}
};
}
#endif

View File

@ -27,9 +27,10 @@ namespace bayesnet {
*/
// 1. For each feature Xi, compute mutual information, I(X;C),
// where C is the class.
addNodes();
vector <float> mi;
for (auto i = 0; i < features.size(); i++) {
Tensor firstFeature = X.index({ "...", i });
Tensor firstFeature = X.index({ i, "..." });
mi.push_back(metrics.mutualInformation(firstFeature, y));
}
// 2. Compute class conditional mutual information I(Xi;XjIC), f or each
@ -38,14 +39,12 @@ namespace bayesnet {
vector<int> S;
// 4. Let the DAG network being constructed, BN, begin with a single
// class node, C.
model.addNode(className, states[className].size());
// 5. Repeat until S includes all domain features
// 5.1. Select feature Xmax which is not in S and has the largest value
// I(Xmax;C).
auto order = argsort(mi);
for (auto idx : order) {
// 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.
model.addEdge(className, features[idx]);
// 5.4. Add m = min(lSl,/c) arcs from m distinct features Xj in S with

View File

@ -6,12 +6,23 @@ namespace bayesnet {
Network::Network() : features(vector<string>()), className(""), classNumStates(0), 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(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) {
nodes[pair.first] = std::make_unique<Node>(*pair.second);
}
}
void Network::initialize()
{
features = vector<string>();
className = "";
classNumStates = 0;
fitted = false;
nodes.clear();
dataset.clear();
samples = torch::Tensor();
}
float Network::getmaxThreads()
{
return maxThreads;
@ -22,6 +33,9 @@ namespace bayesnet {
}
void Network::addNode(const string& name, int numStates)
{
if (name == "") {
throw invalid_argument("Node name cannot be empty");
}
if (find(features.begin(), features.end(), name) == features.end()) {
features.push_back(name);
}
@ -94,40 +108,59 @@ namespace bayesnet {
{
return nodes;
}
void Network::checkFitData(int n_samples, int n_features, int n_samples_y, const vector<string>& featureNames, const string& className)
{
if (n_samples != n_samples_y) {
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) + ")");
}
if (n_features != featureNames.size()) {
throw invalid_argument("X and features must have the same number of features in Network::fit (" + to_string(n_features) + " != " + to_string(featureNames.size()) + ")");
}
if (n_features != features.size() - 1) {
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");
}
}
}
// 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)
{
features = featureNames;
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;
samples = torch::cat({ X, y.view({ y.size(0), 1 }) }, 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) {
auto column = torch::flatten(X.index({ "...", i }));
auto k = vector<int>();
for (auto z = 0; z < X.size(0); ++z) {
k.push_back(column[z].item<int>());
}
dataset[featureNames[i]] = k;
auto row_feature = X.index({ i, "..." });
dataset[featureNames[i]] = vector<int>(row_feature.data_ptr<int>(), row_feature.data_ptr<int>() + row_feature.size(0));;
}
dataset[className] = vector<int>(y.data_ptr<int>(), y.data_ptr<int>() + y.size(0));
completeFit();
}
// 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<string>& featureNames, const string& className)
{
features = featureNames;
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
samples = torch::zeros({ static_cast<int>(input_data[0].size()), static_cast<int>(input_data.size() + 1) }, torch::kInt32);
// 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) {
dataset[featureNames[i]] = input_data[i];
samples.index_put_({ "...", i }, torch::tensor(input_data[i], torch::kInt32));
samples.index_put_({ i, "..." }, torch::tensor(input_data[i], torch::kInt32));
}
dataset[className] = labels;
samples.index_put_({ "...", -1 }, torch::tensor(labels, torch::kInt32));
samples.index_put_({ -1, "..." }, torch::tensor(labels, torch::kInt32));
completeFit();
}
void Network::completeFit()
@ -169,35 +202,36 @@ namespace bayesnet {
}
fitted = true;
}
Tensor Network::predict_proba(const Tensor& samples)
{
if (!fitted) {
throw logic_error("You must call fit() before calling predict_proba()");
}
Tensor result = torch::zeros({ samples.size(0), classNumStates }, torch::kFloat64);
auto Xt = torch::transpose(samples, 0, 1);
for (int i = 0; i < samples.size(0); ++i) {
auto sample = Xt.index({ "...", i });
auto classProbabilities = predict_sample(sample);
result.index_put_({ i, "..." }, torch::tensor(classProbabilities, torch::kFloat64));
}
return result;
}
Tensor Network::predict(const Tensor& samples)
torch::Tensor Network::predict_tensor(const torch::Tensor& samples, const bool proba)
{
if (!fitted) {
throw logic_error("You must call fit() before calling predict()");
}
Tensor result = torch::zeros({ samples.size(0), classNumStates }, torch::kFloat64);
auto Xt = torch::transpose(samples, 0, 1);
for (int i = 0; i < samples.size(0); ++i) {
auto sample = Xt.index({ "...", i });
auto classProbabilities = predict_sample(sample);
result.index_put_({ i, "..." }, torch::tensor(classProbabilities, torch::kFloat64));
torch::Tensor result;
result = torch::zeros({ samples.size(1), classNumStates }, torch::kFloat64);
for (int i = 0; i < samples.size(1); ++i) {
auto sample = samples.index({ "...", i });
result.index_put_({ i, "..." }, torch::tensor(predict_sample(sample), torch::kFloat64));
}
return result;
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)
{
if (!fitted) {
@ -218,6 +252,7 @@ namespace bayesnet {
}
return predictions;
}
// Return mxn vector of probabilities
vector<vector<double>> Network::predict_proba(const vector<vector<int>>& tsamples)
{
if (!fitted) {
@ -245,12 +280,13 @@ namespace bayesnet {
}
return (double)correct / y_pred.size();
}
// Return 1xn vector of probabilities
vector<double> Network::predict_sample(const vector<int>& sample)
{
// 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()) +
") 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;
for (int i = 0; i < sample.size(); ++i) {
@ -258,17 +294,21 @@ namespace bayesnet {
}
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()) {
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()) + ")");
") 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>();
cout << "Evidence: " << features[i] << " = " << sample[i].item<int>() << endl;
}
cout << "BEfore exact inference" << endl;
return exactInference(evidence);
}
double Network::computeFactor(map<string, int>& completeEvidence)
@ -345,25 +385,25 @@ namespace bayesnet {
{
/* 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) {
if (feature == className) {
continue;
}
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;
@ -377,9 +417,12 @@ namespace bayesnet {
}
}
}
return result;
}
void Network::dump_cpt()
{
for (auto& node : nodes) {
cout << "* " << node.first << ": " << node.second->getCPT() << endl;
}
}
}

View File

@ -12,10 +12,10 @@ namespace bayesnet {
bool fitted;
float maxThreads = 0.95;
int classNumStates;
vector<string> features;
vector<string> features; // Including class
string className;
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>&);
vector<double> predict_sample(const vector<int>&);
vector<double> predict_sample(const torch::Tensor&);
@ -26,6 +26,7 @@ namespace bayesnet {
double conditionalEntropy(torch::Tensor&, torch::Tensor&);
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);
public:
Network();
explicit Network(float, int);
@ -43,16 +44,19 @@ namespace bayesnet {
string getClassName();
void fit(const vector<vector<int>>&, const vector<int>&, const vector<string>&, const string&);
void fit(torch::Tensor&, torch::Tensor&, const vector<string>&, const string&);
vector<int> predict(const vector<vector<int>>&);
torch::Tensor predict(const torch::Tensor&);
vector<int> predict(const vector<vector<int>>&); // Return mx1 vector of predictions
torch::Tensor predict(const torch::Tensor&); // Return mx1 tensor of predictions
//Computes the conditional edge weight of variable index u and v conditioned on class_node
torch::Tensor conditionalEdgeWeight();
vector<vector<double>> predict_proba(const vector<vector<int>>&);
torch::Tensor predict_proba(const torch::Tensor&);
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>&);
vector<string> topological_sort();
vector<string> show();
vector<string> graph(const string& title); // Returns a vector of strings representing the graph in graphviz format
void initialize();
void dump_cpt();
inline string version() { return "0.1.0"; }
};
}

View File

@ -12,13 +12,17 @@ namespace bayesnet {
// 1. Compute mutual information between each feature and the class and set the root node
// as the highest mutual information with the class
auto mi = vector <pair<int, float >>();
Tensor class_dataset = dataset.index({ "...", -1 });
Tensor class_dataset = samples.index({ -1, "..." });
for (int i = 0; i < static_cast<int>(features.size()); ++i) {
Tensor feature_dataset = dataset.index({ "...", i });
Tensor feature_dataset = samples.index({ i, "..." });
auto mi_value = metrics.mutualInformation(class_dataset, feature_dataset);
mi.push_back({ i, mi_value });
}
sort(mi.begin(), mi.end(), [](const auto& left, const auto& right) {return left.second < right.second;});
cout << "MI: " << endl;
for (int i = 0; i < mi.size(); ++i) {
cout << mi[i].first << " " << mi[i].second << endl;
}
auto root = mi[mi.size() - 1].first;
// 2. Compute mutual information between each feature and the class
auto weights = metrics.conditionalEdge();

View File

@ -32,19 +32,26 @@ namespace bayesnet {
iota(yStates.begin(), yStates.end(), 0);
this->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;
TAN::fit(Xv, yv, features, className, this->states);
cout << "TANNew: Model fitted" << endl;
// order of local discretization is important. no good 0, 1, 2...
auto edges = model.getEdges();
auto order = model.topological_sort();
auto nodes = model.getNodes();
auto& nodes = model.getNodes();
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();
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;
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(), 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)
@ -58,22 +65,23 @@ namespace bayesnet {
auto arff = ArffFiles();
auto yxv = arff.factorize(yJoinParents);
discretizers[feature]->fit(Xvf[index], yxv);
indicesToReDiscretize.push_back(index);
}
if (upgrade) {
// Discretize again X with the new fitted discretizers
Xv = vector<vector<int>>();
for (int i = 0; i < features.size(); ++i) {
auto Xt_ptr = X.index({ i }).data_ptr<float>();
// Discretize again X (only the affected indices) with the new fitted discretizers
for (auto index : indicesToReDiscretize) {
auto Xt_ptr = X.index({ index }).data_ptr<float>();
auto Xt = vector<float>(Xt_ptr, Xt_ptr + X.size(1));
Xv.push_back(discretizers[features[i]]->transform(Xt));
auto xStates = vector<int>(discretizers[features[i]]->getCutPoints().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);
this->states[features[i]] = xStates;
this->states[features[index]] = xStates;
}
// Now we fit the model again with the new values
cout << "TANNew: Upgrading model" << endl;
model.fit(Xv, yv, features, className);
cout << "TANNew: Model upgraded" << endl;
}
TAN::fit(Xv, yv, features, className, this->states);
return *this;
}
void TANNew::train()
@ -88,6 +96,7 @@ namespace bayesnet {
auto Xd = discretizers[features[i]]->transform(Xt);
Xtd.index_put_({ i }, torch::tensor(Xd, torch::kInt32));
}
cout << "TANNew Xtd: " << Xtd.sizes() << endl;
return TAN::predict(Xtd);
}
vector<string> TANNew::graph(const string& name)