From 85cb4472835becac54243852cb54dd822be99637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana?= Date: Sat, 29 Jul 2023 16:21:38 +0200 Subject: [PATCH] Add Dataset, Models and DotEnv --- src/Platform/CMakeLists.txt | 2 +- src/Platform/Datasets.cc | 10 ------ src/Platform/DotEnv.h | 62 ++++++++++++++++++++++++++++++++ src/Platform/Models.cc | 8 +++++ src/Platform/Models.h | 33 +++++++++++++++++ src/Platform/main.cc | 68 ++++++++++++++++++++++------------- src/Platform/platformUtils.cc | 11 ++++++ src/Platform/platformUtils.h | 1 + 8 files changed, 160 insertions(+), 35 deletions(-) create mode 100644 src/Platform/DotEnv.h create mode 100644 src/Platform/Models.cc create mode 100644 src/Platform/Models.h diff --git a/src/Platform/CMakeLists.txt b/src/Platform/CMakeLists.txt index f1fea17..7de4c29 100644 --- a/src/Platform/CMakeLists.txt +++ b/src/Platform/CMakeLists.txt @@ -4,5 +4,5 @@ include_directories(${BayesNet_SOURCE_DIR}/lib/Files) include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp) include_directories(${BayesNet_SOURCE_DIR}/lib/argparse/include) include_directories(${BayesNet_SOURCE_DIR}/lib/json/include) -add_executable(main main.cc Folding.cc platformUtils.cc Experiment.cc Datasets.cc) +add_executable(main main.cc Folding.cc platformUtils.cc Experiment.cc Datasets.cc Models.cc) target_link_libraries(main BayesNet ArffFiles mdlp "${TORCH_LIBRARIES}") \ No newline at end of file diff --git a/src/Platform/Datasets.cc b/src/Platform/Datasets.cc index 0c09c59..0e1b169 100644 --- a/src/Platform/Datasets.cc +++ b/src/Platform/Datasets.cc @@ -2,16 +2,6 @@ #include "platformUtils.h" #include "ArffFiles.h" namespace platform { - vector split(string text, char delimiter) - { - vector result; - stringstream ss(text); - string token; - while (getline(ss, token, delimiter)) { - result.push_back(token); - } - return result; - } void Datasets::load() { string line; diff --git a/src/Platform/DotEnv.h b/src/Platform/DotEnv.h new file mode 100644 index 0000000..af6eda2 --- /dev/null +++ b/src/Platform/DotEnv.h @@ -0,0 +1,62 @@ +#ifndef DOTENV_H +#define DOTENV_H +#include +#include +#include +#include +#include "platformUtils.h" +namespace platform { + class DotEnv { + private: + std::map env; + std::string trim(const std::string& str) + { + std::string result = str; + result.erase(result.begin(), std::find_if(result.begin(), result.end(), [](int ch) { + return !std::isspace(ch); + })); + result.erase(std::find_if(result.rbegin(), result.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), result.end()); + return result; + } + public: + DotEnv() + { + std::ifstream file(".env"); + if (!file.is_open()) { + std::cerr << "File .env not found" << std::endl; + exit(1); + } + std::string line; + while (std::getline(file, line)) { + line = trim(line); + if (line.empty() || line[0] == '#') { + continue; + } + std::istringstream iss(line); + std::string key, value; + if (std::getline(iss, key, '=') && std::getline(iss, value)) { + env[key] = value; + } + } + } + std::string get(const std::string& key) + { + return env[key]; + } + std::vector getSeeds() + { + auto seeds = std::vector(); + auto seeds_str = env["seeds"]; + seeds_str = trim(seeds_str); + seeds_str = seeds_str.substr(1, seeds_str.size() - 2); + auto seeds_str_split = split(seeds_str, ','); + for (auto seed_str : seeds_str_split) { + seeds.push_back(stoi(seed_str)); + } + return seeds; + } + }; +} +#endif \ No newline at end of file diff --git a/src/Platform/Models.cc b/src/Platform/Models.cc new file mode 100644 index 0000000..b6aaa66 --- /dev/null +++ b/src/Platform/Models.cc @@ -0,0 +1,8 @@ +#include "Models.h" +namespace platform { + using namespace std; + map Models::classifiers = map({ + { "AODE", new bayesnet::AODE() }, { "KDB", new bayesnet::KDB(2) }, + { "SPODE", new bayesnet::SPODE(2) }, { "TAN", new bayesnet::TAN() } + }); +} \ No newline at end of file diff --git a/src/Platform/Models.h b/src/Platform/Models.h new file mode 100644 index 0000000..2851036 --- /dev/null +++ b/src/Platform/Models.h @@ -0,0 +1,33 @@ +#ifndef MODELS_H +#define MODELS_H +#include +#include "BaseClassifier.h" +#include "AODE.h" +#include "TAN.h" +#include "KDB.h" +#include "SPODE.h" +namespace platform { + class Models { + private: + static map classifiers; + public: + static bayesnet::BaseClassifier* get(string name) { return classifiers[name]; } + static vector getNames() + { + vector names; + for (auto& [name, classifier] : classifiers) { + names.push_back(name); + } + return names; + } + static string toString() + { + string names = ""; + for (auto& [name, classifier] : classifiers) { + names += name + ", "; + } + return "{" + names.substr(0, names.size() - 2) + "}"; + } + }; +} +#endif \ No newline at end of file diff --git a/src/Platform/main.cc b/src/Platform/main.cc index 9cce8ad..0faa5b3 100644 --- a/src/Platform/main.cc +++ b/src/Platform/main.cc @@ -3,33 +3,37 @@ #include "platformUtils.h" #include "Experiment.h" #include "Datasets.h" +#include "DotEnv.h" +#include "Models.h" using namespace std; const string PATH_RESULTS = "results"; +const string PATH_DATASETS = "datasets"; argparse::ArgumentParser manageArguments(int argc, char** argv) { + auto env = platform::DotEnv(); argparse::ArgumentParser program("BayesNetSample"); program.add_argument("-d", "--dataset").default_value("").help("Dataset file name"); program.add_argument("-p", "--path") .help("folder where the data files are located, default") - .default_value(string{ PATH } + .default_value(string{ PATH_DATASETS } ); program.add_argument("-m", "--model") - .help("Model to use {AODE, KDB, SPODE, TAN}") + .help("Model to use " + platform::Models::toString()) .action([](const std::string& value) { - static const vector choices = { "AODE", "KDB", "SPODE", "TAN" }; + static const vector choices = platform::Models::getNames(); if (find(choices.begin(), choices.end(), value) != choices.end()) { return value; } - throw runtime_error("Model must be one of {AODE, KDB, SPODE, TAN}"); + throw runtime_error("Model must be one of " + platform::Models::toString()); } ); - program.add_argument("--title").required().help("Experiment title"); - program.add_argument("--discretize").help("Discretize input dataset").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("-f", "--folds").help("Number of folds").default_value(5).scan<'i', int>().action([](const string& value) { + program.add_argument("--title").default_value("").help("Experiment title"); + program.add_argument("--discretize").help("Discretize input dataset").default_value((bool)stoi(env.get("discretize"))).implicit_value(true); + program.add_argument("--stratified").help("If Stratified KFold is to be done").default_value((bool)stoi(env.get("stratified"))).implicit_value(true); + program.add_argument("-f", "--folds").help("Number of folds").default_value(stoi(env.get("n_folds"))).scan<'i', int>().action([](const string& value) { try { auto k = stoi(value); if (k < 2) { @@ -43,9 +47,11 @@ argparse::ArgumentParser manageArguments(int argc, char** argv) catch (...) { throw runtime_error("Number of folds must be an integer"); }}); - program.add_argument("-s", "--seed").help("Random seed").default_value(-1).scan<'i', int>(); + auto seed_values = env.getSeeds(); + program.add_argument("-s", "--seeds").nargs(1, 10).help("Random seeds. Set to -1 to have pseudo random").scan<'i', int>().default_value(seed_values); bool class_last, discretize_dataset, stratified; - int n_folds, seed; + int n_folds; + vector seeds; string model_name, file_name, path, complete_file_name, title; try { program.parse_args(argc, argv); @@ -55,10 +61,13 @@ argparse::ArgumentParser manageArguments(int argc, char** argv) discretize_dataset = program.get("discretize"); stratified = program.get("stratified"); n_folds = program.get("folds"); - seed = program.get("seed"); + seeds = program.get>("seeds"); complete_file_name = path + file_name + ".arff"; class_last = false;//datasets[file_name]; title = program.get("title"); + if (title == "" && file_name == "") { + throw runtime_error("title is mandatory if dataset is not provided"); + } } catch (const exception& err) { cerr << err.what() << endl; @@ -71,25 +80,30 @@ argparse::ArgumentParser manageArguments(int argc, char** argv) int main(int argc, char** argv) { auto program = manageArguments(argc, argv); + bool saveResults = false; auto file_name = program.get("dataset"); auto path = program.get("path"); auto model_name = program.get("model"); auto discretize_dataset = program.get("discretize"); auto stratified = program.get("stratified"); auto n_folds = program.get("folds"); - auto seed = program.get("seed"); + auto seeds = program.get>("seeds"); vector filesToProcess; auto datasets = platform::Datasets(path, true, platform::ARFF); + auto title = program.get("title"); if (file_name != "") { if (!datasets.isDataset(file_name)) { cerr << "Dataset " << file_name << " not found" << endl; exit(1); } + if (title == "") { + title = "Test " + file_name + " " + model_name + " " + to_string(n_folds) + " folds"; + } filesToProcess.push_back(file_name); } else { filesToProcess = platform::Datasets(path, true, platform::ARFF).getNames(); + saveResults = true; } - auto title = program.get("title"); /* * Begin Processing @@ -97,7 +111,10 @@ int main(int argc, char** argv) auto experiment = platform::Experiment(); experiment.setTitle(title).setLanguage("cpp").setLanguageVersion("1.0.0"); experiment.setDiscretized(discretize_dataset).setModel(model_name).setPlatform("BayesNet"); - experiment.setStratified(stratified).setNFolds(n_folds).addRandomSeed(seed).setScoreName("accuracy"); + experiment.setStratified(stratified).setNFolds(n_folds).setScoreName("accuracy"); + for (auto seed : seeds) { + experiment.addRandomSeed(seed); + } platform::Timer timer; cout << "*** Starting experiment: " << title << " ***" << endl; timer.start(); @@ -109,16 +126,19 @@ int main(int argc, char** argv) auto samples = datasets.getNSamples(fileName); auto className = datasets.getClassName(fileName); cout << " (" << setw(5) << samples << "," << setw(3) << features.size() << ") " << flush; - Fold* fold; - if (stratified) - fold = new StratifiedKFold(n_folds, y, seed); - else - fold = new KFold(n_folds, samples, seed); - auto result = platform::cross_validation(fold, model_name, X, y, features, className, states); - result.setDataset(fileName); - experiment.setModelVersion(result.getModelVersion()); - experiment.addResult(result); - delete fold; + for (auto seed : seeds) { + cout << "(" << seed << ") " << flush; + Fold* fold; + if (stratified) + fold = new StratifiedKFold(n_folds, y, seed); + else + fold = new KFold(n_folds, samples, seed); + auto result = platform::cross_validation(fold, model_name, X, y, features, className, states); + result.setDataset(fileName); + experiment.setModelVersion(result.getModelVersion()); + experiment.addResult(result); + delete fold; + } } experiment.setDuration(timer.getDuration()); experiment.save(PATH_RESULTS); diff --git a/src/Platform/platformUtils.cc b/src/Platform/platformUtils.cc index ea8fad3..f318831 100644 --- a/src/Platform/platformUtils.cc +++ b/src/Platform/platformUtils.cc @@ -2,6 +2,17 @@ using namespace torch; +vector split(string text, char delimiter) +{ + vector result; + stringstream ss(text); + string token; + while (getline(ss, token, delimiter)) { + result.push_back(token); + } + return result; +} + pair, map> discretize(vector& X, mdlp::labels_t& y, vector features) { vector Xd; diff --git a/src/Platform/platformUtils.h b/src/Platform/platformUtils.h index abc69bd..9515bbf 100644 --- a/src/Platform/platformUtils.h +++ b/src/Platform/platformUtils.h @@ -11,6 +11,7 @@ using namespace std; const string PATH = "../../data/"; bool file_exists(const std::string& name); +vector split(string text, char delimiter); pair, map> discretize(vector& X, mdlp::labels_t& y, vector features); vector discretizeDataset(vector& X, mdlp::labels_t& y); pair>> discretizeTorch(torch::Tensor& X, torch::Tensor& y, vector& features, string className);