Add Makefile & tests
This commit is contained in:
parent
f530e69dae
commit
ca72a34131
@ -1,14 +1,52 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(BayesNet)
|
||||
project(BayesNet
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Bayesian Network and basic classifiers Library."
|
||||
HOMEPAGE_URL "https://github.com/rmontanana/bayesnet"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
find_package(Torch REQUIRED)
|
||||
|
||||
if (POLICY CMP0135)
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif ()
|
||||
|
||||
# Global CMake variables
|
||||
# ----------------------
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
|
||||
|
||||
# Options
|
||||
# -------
|
||||
option(ENABLE_CLANG_TIDY "Enable to add clang tidy." OFF)
|
||||
option(ENABLE_TESTING "Unit testing build" ON)
|
||||
option(CODE_COVERAGE "Collect coverage from test library" ON)
|
||||
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
|
||||
|
||||
# Subdirectories
|
||||
# --------------
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(sample)
|
||||
add_subdirectory(sample)
|
||||
|
||||
# Testing
|
||||
# -------
|
||||
|
||||
if (ENABLE_TESTING)
|
||||
enable_testing()
|
||||
#if (CODE_COVERAGE)
|
||||
SET(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage")
|
||||
SET(GCC_COVERAGE_LINK_FLAGS "--coverage")
|
||||
#endif (CODE_COVERAGE)
|
||||
find_package(Catch2 3 REQUIRED)
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
add_subdirectory(tests)
|
||||
endif (ENABLE_TESTING)
|
57
Makefile
Normal file
57
Makefile
Normal file
@ -0,0 +1,57 @@
|
||||
SHELL := /bin/bash
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: coverage setup help build test
|
||||
|
||||
setup: ## Install dependencies for tests and coverage
|
||||
@if [ "$(shell uname)" = "Darwin" ]; then \
|
||||
brew install gcovr; \
|
||||
brew install lcov; \
|
||||
fi
|
||||
@if [ "$(shell uname)" = "Linux" ]; then \
|
||||
pip install gcovr; \
|
||||
fi
|
||||
|
||||
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 project
|
||||
@echo ">>> Building BayesNet ...";
|
||||
@if [ -d ./build ]; then rm -rf ./build; fi
|
||||
@mkdir build;
|
||||
cmake -S . -B build; \
|
||||
cd build; \
|
||||
make; \
|
||||
|
||||
@echo ">>> Done";
|
||||
|
||||
test: ## Run tests
|
||||
@echo "* Running tests...";
|
||||
find . -name "*.gcda" -print0 | xargs -0 rm
|
||||
@cd build; \
|
||||
cmake --build . --target unit_tests ;
|
||||
@cd build/tests; \
|
||||
./unit_tests;
|
||||
|
||||
coverage: ## Run tests and generate coverage report (build/index.html)
|
||||
@echo "*Building tests...";
|
||||
find . -name "*.gcda" -print0 | xargs -0 rm
|
||||
@cd build; \
|
||||
cmake --build . --target unit_tests ;
|
||||
gcovr ;
|
||||
|
||||
help: ## Show help message
|
||||
@IFS=$$'\n' ; \
|
||||
help_lines=(`fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##/:/'`); \
|
||||
printf "%s\n\n" "Usage: make [task]"; \
|
||||
printf "%-20s %s\n" "task" "help" ; \
|
||||
printf "%-20s %s\n" "------" "----" ; \
|
||||
for help_line in $${help_lines[@]}; do \
|
||||
IFS=$$':' ; \
|
||||
help_split=($$help_line) ; \
|
||||
help_command=`echo $${help_split[0]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \
|
||||
help_info=`echo $${help_split[2]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \
|
||||
printf '\033[36m'; \
|
||||
printf "%-20s %s" $$help_command ; \
|
||||
printf '\033[0m'; \
|
||||
printf "%s\n" $$help_info; \
|
||||
done
|
4
config/CMakeLists.txt
Normal file
4
config/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
configure_file(
|
||||
"config.h.in"
|
||||
"${CMAKE_BINARY_DIR}/configured_files/include/config.h" ESCAPE_QUOTES
|
||||
)
|
13
config/config.h.in
Normal file
13
config/config.h.in
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR @
|
||||
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR @
|
||||
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH @
|
||||
|
||||
static constexpr std::string_view project_name = " @PROJECT_NAME@ ";
|
||||
static constexpr std::string_view project_version = "@PROJECT_VERSION@";
|
||||
static constexpr std::string_view project_description = "@PROJECT_DESCRIPTION@";
|
||||
static constexpr std::string_view git_sha = "@GIT_SHA@";
|
@ -1,6 +1,4 @@
|
||||
include_directories(${BayesNet_SOURCE_DIR}/src)
|
||||
link_directories(${MyProject_SOURCE_DIR}/src)
|
||||
add_executable(main main.cc ArffFiles.cc CPPFImdlp.cpp Metrics.cpp)
|
||||
add_executable(test test.cc)
|
||||
target_link_libraries(main BayesNet "${TORCH_LIBRARIES}")
|
||||
target_link_libraries(test "${TORCH_LIBRARIES}")
|
||||
target_link_libraries(main BayesNet "${TORCH_LIBRARIES}")
|
221
sample/main.cc
221
sample/main.cc
@ -30,23 +30,23 @@ void usage(const char* path)
|
||||
<< " -f, --file[=FILENAME]\t {diabetes, glass, iris, kdd_JapaneseVowels, letter, liver-disorders, mfeat-factors}."
|
||||
<< endl;
|
||||
cout << " -p, --path[=FILENAME]\t folder where the data files are located, default " << PATH << endl;
|
||||
cout << " -n, --net=[FILENAME]\t default=file parameter value" << endl;
|
||||
cout << " -m, --model={AODE, KDB, SPODE, TAN}\t " << endl;
|
||||
}
|
||||
|
||||
tuple<string, string, string> parse_arguments(int argc, char** argv)
|
||||
{
|
||||
string file_name;
|
||||
string network_name;
|
||||
string model_name;
|
||||
string path = PATH;
|
||||
const vector<struct option> long_options = {
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"file", required_argument, nullptr, 'f'},
|
||||
{"path", required_argument, nullptr, 'p'},
|
||||
{"net", required_argument, nullptr, 'n'},
|
||||
{"model", required_argument, nullptr, 'm'},
|
||||
{nullptr, no_argument, nullptr, 0}
|
||||
};
|
||||
while (true) {
|
||||
const auto c = getopt_long(argc, argv, "hf:p:n:", long_options.data(), nullptr);
|
||||
const auto c = getopt_long(argc, argv, "hf:p:m:", long_options.data(), nullptr);
|
||||
if (c == -1)
|
||||
break;
|
||||
switch (c) {
|
||||
@ -56,8 +56,8 @@ tuple<string, string, string> parse_arguments(int argc, char** argv)
|
||||
case 'f':
|
||||
file_name = string(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
network_name = string(optarg);
|
||||
case 'm':
|
||||
model_name = string(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
path = optarg;
|
||||
@ -75,12 +75,22 @@ tuple<string, string, string> parse_arguments(int argc, char** argv)
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
if (network_name.empty()) {
|
||||
network_name = file_name;
|
||||
}
|
||||
return make_tuple(file_name, path, network_name);
|
||||
return make_tuple(file_name, path, model_name);
|
||||
}
|
||||
|
||||
inline constexpr auto hash_conv(const std::string_view sv)
|
||||
{
|
||||
unsigned long hash{ 5381 };
|
||||
for (unsigned char c : sv) {
|
||||
hash = ((hash << 5) + hash) ^ c;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline constexpr auto operator"" _sh(const char* str, size_t len)
|
||||
{
|
||||
return hash_conv(std::string_view{ str, len });
|
||||
}
|
||||
|
||||
pair<vector<mdlp::labels_t>, map<string, int>> discretize(vector<mdlp::samples_t>& X, mdlp::labels_t& y, vector<string> features)
|
||||
{
|
||||
@ -96,39 +106,6 @@ pair<vector<mdlp::labels_t>, map<string, int>> discretize(vector<mdlp::samples_t
|
||||
}
|
||||
return { Xd, maxes };
|
||||
}
|
||||
void showNodesInfo(bayesnet::Network& network, string className)
|
||||
{
|
||||
cout << "Nodes:" << endl;
|
||||
for (auto& node : network.getNodes()) {
|
||||
auto name = node.first;
|
||||
cout << "*" << node.second->getName() << " States -> " << node.second->getNumStates() << endl;
|
||||
cout << "-Parents:";
|
||||
for (auto parent : node.second->getParents()) {
|
||||
cout << " " << parent->getName();
|
||||
}
|
||||
cout << endl;
|
||||
cout << "-Children:";
|
||||
for (auto child : node.second->getChildren()) {
|
||||
cout << " " << child->getName();
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
void showCPDS(bayesnet::Network& network)
|
||||
{
|
||||
cout << "CPDs:" << endl;
|
||||
auto& nodes = network.getNodes();
|
||||
for (auto it = nodes.begin(); it != nodes.end(); it++) {
|
||||
cout << "* Name: " << it->first << " " << it->second->getName() << " -> " << it->second->getNumStates() << endl;
|
||||
cout << "Parents: ";
|
||||
for (auto parent : it->second->getParents()) {
|
||||
cout << parent->getName() << " -> " << parent->getNumStates() << ", ";
|
||||
}
|
||||
cout << endl;
|
||||
auto cpd = it->second->getCPT();
|
||||
cout << cpd << endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool file_exists(const std::string& name)
|
||||
{
|
||||
@ -140,7 +117,7 @@ bool file_exists(const std::string& name)
|
||||
}
|
||||
}
|
||||
|
||||
pair<string, string> get_options(int argc, char** argv)
|
||||
tuple<string, string, string> get_options(int argc, char** argv)
|
||||
{
|
||||
map<string, bool> datasets = {
|
||||
{"diabetes", true},
|
||||
@ -152,58 +129,35 @@ pair<string, string> get_options(int argc, char** argv)
|
||||
{"liver-disorders", true},
|
||||
{"mfeat-factors", true},
|
||||
};
|
||||
vector <string> models = { "AODE", "KDB", "SPODE", "TAN" };
|
||||
string file_name;
|
||||
string path;
|
||||
string network_name;
|
||||
tie(file_name, path, network_name) = parse_arguments(argc, argv);
|
||||
string model_name;
|
||||
tie(file_name, path, model_name) = parse_arguments(argc, argv);
|
||||
if (datasets.find(file_name) == datasets.end()) {
|
||||
cout << "Invalid file name: " << file_name << endl;
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
file_name = path + file_name + ".arff";
|
||||
if (!file_exists(file_name)) {
|
||||
cout << "Data File " << file_name << " does not exist" << endl;
|
||||
if (!file_exists(path + file_name + ".arff")) {
|
||||
cout << "Data File " << path + file_name + ".arff" << " does not exist" << endl;
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
network_name = path + network_name + ".net";
|
||||
if (!file_exists(network_name)) {
|
||||
cout << "Network File " << network_name << " does not exist" << endl;
|
||||
if (find(models.begin(), models.end(), model_name) == models.end()) {
|
||||
cout << "Invalid model name: " << model_name << endl;
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
return { file_name, network_name };
|
||||
return { file_name, path, model_name };
|
||||
}
|
||||
|
||||
void build_network(bayesnet::Network& network, string network_name, map<string, int> maxes)
|
||||
{
|
||||
ifstream file(network_name);
|
||||
string line;
|
||||
while (getline(file, line)) {
|
||||
if (line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
istringstream iss(line);
|
||||
string parent, child;
|
||||
if (!(iss >> parent >> child)) {
|
||||
break;
|
||||
}
|
||||
network.addNode(parent, maxes[parent]);
|
||||
network.addNode(child, maxes[child]);
|
||||
network.addEdge(parent, child);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
string file_name, network_name;
|
||||
tie(file_name, network_name) = get_options(argc, argv);
|
||||
|
||||
string file_name, path, model_name;
|
||||
tie(file_name, path, model_name) = get_options(argc, argv);
|
||||
auto handler = ArffFiles();
|
||||
handler.load(file_name);
|
||||
handler.load(path + file_name + ".arff");
|
||||
// Get Dataset X, y
|
||||
vector<mdlp::samples_t>& X = handler.getX();
|
||||
mdlp::labels_t& y = handler.getY();
|
||||
@ -218,91 +172,54 @@ int main(int argc, char** argv)
|
||||
map<string, int> maxes;
|
||||
tie(Xd, maxes) = discretize(X, y, features);
|
||||
maxes[className] = *max_element(y.begin(), y.end()) + 1;
|
||||
cout << "Features: ";
|
||||
for (auto feature : features) {
|
||||
cout << "[" << feature << "] ";
|
||||
}
|
||||
cout << endl;
|
||||
cout << "Class name: " << className << endl;
|
||||
// Build Network
|
||||
// auto network = bayesnet::Network(1.0);
|
||||
// build_network(network, network_name, maxes);
|
||||
// network.fit(Xd, y, features, className);
|
||||
// cout << "Hello, Bayesian Networks!" << endl;
|
||||
// showNodesInfo(network, className);
|
||||
// //showCPDS(network);
|
||||
// cout << "Score: " << network.score(Xd, y) << endl;
|
||||
// cout << "PyTorch version: " << TORCH_VERSION << endl;
|
||||
// cout << "BayesNet version: " << network.version() << endl;
|
||||
// unsigned int nthreads = std::thread::hardware_concurrency();
|
||||
// cout << "Computer has " << nthreads << " cores." << endl;
|
||||
// cout << "****************** First ******************" << endl;
|
||||
// auto metrics = bayesnet::Metrics(network.getSamples(), features, className, network.getClassNumStates());
|
||||
// cout << "conditionalEdgeWeight " << endl;
|
||||
// auto conditional = metrics.conditionalEdgeWeights();
|
||||
// cout << conditional << endl;
|
||||
// long m = features.size() + 1;
|
||||
// auto matrix = torch::from_blob(conditional.data(), { m, m });
|
||||
// cout << matrix << endl;
|
||||
// cout << "****************** Second ******************" << endl;
|
||||
// auto metrics2 = bayesnet::Metrics(Xd, y, features, className, network.getClassNumStates());
|
||||
// cout << "conditionalEdgeWeight " << endl;
|
||||
// auto conditional2 = metrics2.conditionalEdgeWeights();
|
||||
// cout << conditional2 << endl;
|
||||
// long m2 = features.size() + 1;
|
||||
// auto matrix2 = torch::from_blob(conditional2.data(), { m, m });
|
||||
// cout << matrix2 << endl;
|
||||
cout << "****************** Preparing ******************" << endl;
|
||||
map<string, vector<int>> states;
|
||||
for (auto feature : features) {
|
||||
states[feature] = vector<int>(maxes[feature]);
|
||||
}
|
||||
states[className] = vector<int>(
|
||||
maxes[className]);
|
||||
cout << "****************** KDB ******************" << endl;
|
||||
double score;
|
||||
vector<string> lines;
|
||||
vector<string> graph;
|
||||
auto kdb = bayesnet::KDB(2);
|
||||
kdb.fit(Xd, y, features, className, states);
|
||||
for (auto line : kdb.show()) {
|
||||
cout << line << endl;
|
||||
}
|
||||
cout << "Score: " << kdb.score(Xd, y) << endl;
|
||||
ofstream file("kdb.dot");
|
||||
file << kdb.graph();
|
||||
file.close();
|
||||
cout << "****************** KDB ******************" << endl;
|
||||
cout << "****************** SPODE ******************" << endl;
|
||||
auto spode = bayesnet::SPODE(2);
|
||||
spode.fit(Xd, y, features, className, states);
|
||||
for (auto line : spode.show()) {
|
||||
cout << line << endl;
|
||||
}
|
||||
cout << "Score: " << spode.score(Xd, y) << endl;
|
||||
file.open("spode.dot");
|
||||
file << spode.graph();
|
||||
file.close();
|
||||
cout << "****************** SPODE ******************" << endl;
|
||||
cout << "****************** AODE ******************" << endl;
|
||||
auto aode = bayesnet::AODE();
|
||||
aode.fit(Xd, y, features, className, states);
|
||||
for (auto line : aode.show()) {
|
||||
cout << line << endl;
|
||||
}
|
||||
cout << "Score: " << aode.score(Xd, y) << endl;
|
||||
file.open("aode.dot");
|
||||
for (auto line : aode.graph())
|
||||
file << line;
|
||||
file.close();
|
||||
cout << "****************** AODE ******************" << endl;
|
||||
cout << "****************** TAN ******************" << endl;
|
||||
auto spode = bayesnet::SPODE(2);
|
||||
auto tan = bayesnet::TAN();
|
||||
tan.fit(Xd, y, features, className, states);
|
||||
for (auto line : tan.show()) {
|
||||
switch (hash_conv(model_name)) {
|
||||
case "AODE"_sh:
|
||||
aode.fit(Xd, y, features, className, states);
|
||||
lines = aode.show();
|
||||
score = aode.score(Xd, y);
|
||||
graph = aode.graph();
|
||||
break;
|
||||
case "KDB"_sh:
|
||||
kdb.fit(Xd, y, features, className, states);
|
||||
lines = kdb.show();
|
||||
score = kdb.score(Xd, y);
|
||||
graph = kdb.graph();
|
||||
break;
|
||||
case "SPODE"_sh:
|
||||
spode.fit(Xd, y, features, className, states);
|
||||
lines = spode.show();
|
||||
score = spode.score(Xd, y);
|
||||
graph = spode.graph();
|
||||
break;
|
||||
case "TAN"_sh:
|
||||
tan.fit(Xd, y, features, className, states);
|
||||
lines = tan.show();
|
||||
score = tan.score(Xd, y);
|
||||
graph = tan.graph();
|
||||
break;
|
||||
}
|
||||
for (auto line : lines) {
|
||||
cout << line << endl;
|
||||
}
|
||||
cout << "Score: " << tan.score(Xd, y) << endl;
|
||||
file.open("tan.dot");
|
||||
file << tan.graph();
|
||||
cout << "Score: " << score << endl;
|
||||
auto dot_file = model_name + "_" + file_name;
|
||||
ofstream file(dot_file + ".dot");
|
||||
file << graph;
|
||||
file.close();
|
||||
cout << "****************** TAN ******************" << endl;
|
||||
cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl;
|
||||
cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl;
|
||||
return 0;
|
||||
}
|
208
sample/test.cc
208
sample/test.cc
@ -1,208 +0,0 @@
|
||||
// #include <torch/torch.h>
|
||||
|
||||
// int main()
|
||||
// {
|
||||
// torch::Tensor t = torch::rand({ 5, 5 });
|
||||
|
||||
// // Print original tensor
|
||||
// std::cout << t << std::endl;
|
||||
|
||||
// // New value
|
||||
// torch::Tensor new_val = torch::tensor(10.0f);
|
||||
|
||||
// // Indices for the cell you want to update
|
||||
// auto index_i = torch::tensor({ 2 });
|
||||
// auto index_j = torch::tensor({ 3 });
|
||||
|
||||
// // Update cell
|
||||
// t.index_put_({ index_i, index_j }, new_val);
|
||||
|
||||
// // Print updated tensor
|
||||
// std::cout << t << std::endl;
|
||||
// }
|
||||
#include <torch/torch.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
double entropy(torch::Tensor feature)
|
||||
{
|
||||
torch::Tensor counts = feature.bincount();
|
||||
int totalWeight = counts.sum().item<int>();
|
||||
torch::Tensor probs = counts.to(torch::kFloat) / totalWeight;
|
||||
torch::Tensor logProbs = torch::log2(probs);
|
||||
torch::Tensor entropy = -probs * logProbs;
|
||||
return entropy.sum().item<double>();
|
||||
}
|
||||
// H(Y|X) = sum_{x in X} p(x) H(Y|X=x)
|
||||
double conditionalEntropy(torch::Tensor firstFeature, torch::Tensor secondFeature)
|
||||
{
|
||||
int numSamples = firstFeature.sizes()[0];
|
||||
torch::Tensor featureCounts = secondFeature.bincount();
|
||||
unordered_map<int, unordered_map<int, double>> jointCounts;
|
||||
double totalWeight = 0;
|
||||
for (auto i = 0; i < numSamples; i++) {
|
||||
jointCounts[secondFeature[i].item<int>()][firstFeature[i].item<int>()] += 1;
|
||||
totalWeight += 1;
|
||||
}
|
||||
if (totalWeight == 0)
|
||||
throw invalid_argument("Total weight should not be zero");
|
||||
double entropy = 0;
|
||||
for (int value = 0; value < featureCounts.sizes()[0]; ++value) {
|
||||
double p_f = featureCounts[value].item<double>() / totalWeight;
|
||||
double entropy_f = 0;
|
||||
for (auto& [label, jointCount] : jointCounts[value]) {
|
||||
double p_l_f = jointCount / featureCounts[value].item<double>();
|
||||
if (p_l_f > 0) {
|
||||
entropy_f -= p_l_f * log2(p_l_f);
|
||||
} else {
|
||||
entropy_f = 0;
|
||||
}
|
||||
}
|
||||
entropy += p_f * entropy_f;
|
||||
}
|
||||
return entropy;
|
||||
}
|
||||
|
||||
// I(X;Y) = H(Y) - H(Y|X)
|
||||
double mutualInformation(torch::Tensor firstFeature, torch::Tensor secondFeature)
|
||||
{
|
||||
return entropy(firstFeature) - conditionalEntropy(firstFeature, secondFeature);
|
||||
}
|
||||
double entropy2(torch::Tensor feature)
|
||||
{
|
||||
return torch::special::entr(feature).sum().item<double>();
|
||||
}
|
||||
int main()
|
||||
{
|
||||
//int i = 3, j = 1, k = 2; // Indices for the cell you want to update
|
||||
// Print original tensor
|
||||
// torch::Tensor t = torch::tensor({ {1, 2, 3}, {4, 5, 6} }); // 3D tensor for this example
|
||||
// auto variables = vector<string>{ "A", "B" };
|
||||
// auto cardinalities = vector<int>{ 5, 4 };
|
||||
// torch::Tensor values = torch::rand({ 5, 4 });
|
||||
// auto candidate = "B";
|
||||
// vector<string> newVariables;
|
||||
// vector<int> newCardinalities;
|
||||
// for (int i = 0; i < variables.size(); i++) {
|
||||
// if (variables[i] != candidate) {
|
||||
// newVariables.push_back(variables[i]);
|
||||
// newCardinalities.push_back(cardinalities[i]);
|
||||
// }
|
||||
// }
|
||||
// torch::Tensor newValues = values.sum(1);
|
||||
// cout << "original values" << endl;
|
||||
// cout << values << endl;
|
||||
// cout << "newValues" << endl;
|
||||
// cout << newValues << endl;
|
||||
// cout << "newVariables" << endl;
|
||||
// for (auto& variable : newVariables) {
|
||||
// cout << variable << endl;
|
||||
// }
|
||||
// cout << "newCardinalities" << endl;
|
||||
// for (auto& cardinality : newCardinalities) {
|
||||
// cout << cardinality << endl;
|
||||
// }
|
||||
// auto row2 = values.index({ torch::tensor(1) }); //
|
||||
// cout << "row2" << endl;
|
||||
// cout << row2 << endl;
|
||||
// auto col2 = values.index({ "...", 1 });
|
||||
// cout << "col2" << endl;
|
||||
// cout << col2 << endl;
|
||||
// auto col_last = values.index({ "...", -1 });
|
||||
// cout << "col_last" << endl;
|
||||
// cout << col_last << endl;
|
||||
// values.index_put_({ "...", -1 }, torch::tensor({ 1,2,3,4,5 }));
|
||||
// cout << "col_last" << endl;
|
||||
// cout << col_last << endl;
|
||||
// auto slice2 = values.index({ torch::indexing::Slice(1, torch::indexing::None) });
|
||||
// cout << "slice2" << endl;
|
||||
// cout << slice2 << endl;
|
||||
// auto mask = values.index({ "...", -1 }) % 2 == 0;
|
||||
// auto filter = values.index({ mask, 2 }); // Filter values
|
||||
// cout << "filter" << endl;
|
||||
// cout << filter << endl;
|
||||
// torch::Tensor dataset = torch::tensor({ {1,0,0,1},{1,1,1,2},{0,0,0,1},{1,0,2,0},{0,0,3,0} });
|
||||
// cout << "dataset" << endl;
|
||||
// cout << dataset << endl;
|
||||
// cout << "entropy(dataset.indices('...', 2))" << endl;
|
||||
// cout << dataset.index({ "...", 2 }) << endl;
|
||||
// cout << "*********************************" << endl;
|
||||
// for (int i = 0; i < 4; i++) {
|
||||
// cout << "datset(" << i << ")" << endl;
|
||||
// cout << dataset.index({ "...", i }) << endl;
|
||||
// cout << "entropy(" << i << ")" << endl;
|
||||
// cout << entropy(dataset.index({ "...", i })) << endl;
|
||||
// }
|
||||
// cout << "......................................" << endl;
|
||||
// //cout << entropy2(dataset.index({ "...", 2 }));
|
||||
// cout << "conditional entropy 0 2" << endl;
|
||||
// cout << conditionalEntropy(dataset.index({ "...", 0 }), dataset.index({ "...", 2 })) << endl;
|
||||
// cout << "mutualInformation(dataset.index({ '...', 0 }), dataset.index({ '...', 2 }))" << endl;
|
||||
// cout << mutualInformation(dataset.index({ "...", 0 }), dataset.index({ "...", 2 })) << endl;
|
||||
// auto test = torch::tensor({ .1, .2, .3 }, torch::kFloat);
|
||||
// auto result = torch::zeros({ 3, 3 }, torch::kFloat);
|
||||
// result.index_put_({ indices }, test);
|
||||
// cout << "indices" << endl;
|
||||
// cout << indices << endl;
|
||||
// cout << "result" << endl;
|
||||
// cout << result << endl;
|
||||
// cout << "Test" << endl;
|
||||
// cout << torch::triu(test.reshape(3, 3), torch::kFloat)) << endl;
|
||||
|
||||
|
||||
// Create a 3x3 tensor with zeros
|
||||
torch::Tensor tensor_3x3 = torch::zeros({ 3, 3 }, torch::kFloat);
|
||||
|
||||
// Create a 1D tensor with the three elements you want to set in the upper corner
|
||||
torch::Tensor tensor_1d = torch::tensor({ 10, 11, 12 }, torch::kFloat);
|
||||
|
||||
// Set the upper corner of the 3x3 tensor
|
||||
auto indices = torch::triu_indices(3, 3, 1);
|
||||
for (auto i = 0; i < tensor_1d.sizes()[0]; ++i) {
|
||||
auto x = indices[0][i];
|
||||
auto y = indices[1][i];
|
||||
tensor_3x3[x][y] = tensor_1d[i];
|
||||
tensor_3x3[y][x] = tensor_1d[i];
|
||||
}
|
||||
// Print the resulting 3x3 tensor
|
||||
std::cout << tensor_3x3 << std::endl;
|
||||
vector<int> v = { 1,2,3,4,5 };
|
||||
torch::Tensor t = torch::tensor(v);
|
||||
cout << t << endl;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// std::cout << t << std::endl;
|
||||
// std::cout << "sum(0)" << std::endl;
|
||||
// std::cout << t.sum(0) << std::endl;
|
||||
// std::cout << "sum(1)" << std::endl;
|
||||
// std::cout << t.sum(1) << std::endl;
|
||||
// std::cout << "Normalized" << std::endl;
|
||||
// std::cout << t / t.sum(0) << std::endl;
|
||||
|
||||
// New value
|
||||
// torch::Tensor new_val = torch::tensor(10.0f);
|
||||
|
||||
// // Indices for the cell you want to update
|
||||
// std::vector<torch::Tensor> indices;
|
||||
// indices.push_back(torch::tensor(i)); // Replace i with your index for the 1st dimension
|
||||
// indices.push_back(torch::tensor(j)); // Replace j with your index for the 2nd dimension
|
||||
// indices.push_back(torch::tensor(k)); // Replace k with your index for the 3rd dimension
|
||||
// //torch::ArrayRef<at::indexing::TensorIndex> indices_ref(indices);
|
||||
// // Update cell
|
||||
// //torch::Tensor result = torch::stack(indices);
|
||||
// //torch::List<c10::optional<torch::Tensor>> indices_list = { torch::tensor(i), torch::tensor(j), torch::tensor(k) };
|
||||
// torch::List<c10::optional<torch::Tensor>> indices_list;
|
||||
// indices_list.push_back(torch::tensor(i));
|
||||
// indices_list.push_back(torch::tensor(j));
|
||||
// indices_list.push_back(torch::tensor(k));
|
||||
// //t.index_put_({ torch::tensor(i), torch::tensor(j), torch::tensor(k) }, new_val);
|
||||
// t.index_put_(indices_list, new_val);
|
||||
|
||||
// // Print updated tensor
|
||||
// std::cout << t << std::endl;
|
||||
}
|
12
tests/CMakeLists.txt
Normal file
12
tests/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
if(ENABLE_TESTING)
|
||||
set(TEST_MAIN "unit_tests")
|
||||
include_directories(src)
|
||||
SET(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage --coverage")
|
||||
SET(GCC_COVERAGE_LINK_FLAGS "--coverage")
|
||||
set(TEST_SOURCES main.cc ../sample/ArffFiles.cc ../sample/CPPFImdlp.cpp ../sample/Metrics.cpp
|
||||
../src/utils.cc ../src/Network.cc ../src/Node.cc ../src/Metrics.cc ../src/BaseClassifier.cc ../src/KDB.cc
|
||||
../src/TAN.cc ../src/SPODE.cc ../src/Ensemble.cc ../src/AODE.cc ../src/Mst.cc)
|
||||
add_executable(${TEST_MAIN} ${TEST_SOURCES})
|
||||
target_link_libraries(${TEST_MAIN} PUBLIC "${TORCH_LIBRARIES}" Catch2::Catch2WithMain)
|
||||
add_test(NAME ${TEST_MAIN} COMMAND ${TEST_MAIN})
|
||||
endif(ENABLE_TESTING)
|
102
tests/main.cc
Normal file
102
tests/main.cc
Normal file
@ -0,0 +1,102 @@
|
||||
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <torch/torch.h>
|
||||
#include "../sample/ArffFiles.h"
|
||||
#include "../sample/CPPFImdlp.h"
|
||||
#include "../src/KDB.h"
|
||||
#include "../src/TAN.h"
|
||||
#include "../src/SPODE.h"
|
||||
#include "../src/AODE.h"
|
||||
|
||||
const string PATH = "data/";
|
||||
using namespace std;
|
||||
|
||||
pair<vector<mdlp::labels_t>, map<string, int>> discretize(vector<mdlp::samples_t>& X, mdlp::labels_t& y, vector<string> features)
|
||||
{
|
||||
vector<mdlp::labels_t>Xd;
|
||||
map<string, int> maxes;
|
||||
|
||||
auto fimdlp = mdlp::CPPFImdlp();
|
||||
for (int i = 0; i < X.size(); i++) {
|
||||
fimdlp.fit(X[i], y);
|
||||
mdlp::labels_t& xd = fimdlp.transform(X[i]);
|
||||
maxes[features[i]] = *max_element(xd.begin(), xd.end()) + 1;
|
||||
Xd.push_back(xd);
|
||||
}
|
||||
return { Xd, maxes };
|
||||
}
|
||||
|
||||
TEST_CASE("Test Bayesian Classifiers score", "[BayesNet]")
|
||||
{
|
||||
auto path = "../../data/";
|
||||
map <pair<string, string>, float> scores = {
|
||||
{{"diabetes", "AODE"}, 0.811198}, {{"diabetes", "KDB"}, 0.852865}, {{"diabetes", "SPODE"}, 0.802083}, {{"diabetes", "TAN"}, 0.821615},
|
||||
{{"ecoli", "AODE"}, 0.889881}, {{"ecoli", "KDB"}, 0.889881}, {{"ecoli", "SPODE"}, 0.880952}, {{"ecoli", "TAN"}, 0.892857},
|
||||
{{"glass", "AODE"}, 0.78972}, {{"glass", "KDB"}, 0.827103}, {{"glass", "SPODE"}, 0.775701}, {{"glass", "TAN"}, 0.827103},
|
||||
{{"iris", "AODE"}, 0.973333}, {{"iris", "KDB"}, 0.973333}, {{"iris", "SPODE"}, 0.973333}, {{"iris", "TAN"}, 0.973333}
|
||||
};
|
||||
|
||||
string file_name = GENERATE("glass", "iris", "ecoli", "diabetes");
|
||||
auto handler = ArffFiles();
|
||||
handler.load(path + static_cast<string>(file_name) + ".arff");
|
||||
// Get Dataset X, y
|
||||
vector<mdlp::samples_t>& X = handler.getX();
|
||||
mdlp::labels_t& y = handler.getY();
|
||||
// Get className & Features
|
||||
auto className = handler.getClassName();
|
||||
vector<string> features;
|
||||
for (auto feature : handler.getAttributes()) {
|
||||
features.push_back(feature.first);
|
||||
}
|
||||
// Discretize Dataset
|
||||
vector<mdlp::labels_t> Xd;
|
||||
map<string, int> maxes;
|
||||
tie(Xd, maxes) = discretize(X, y, features);
|
||||
maxes[className] = *max_element(y.begin(), y.end()) + 1;
|
||||
map<string, vector<int>> states;
|
||||
for (auto feature : features) {
|
||||
states[feature] = vector<int>(maxes[feature]);
|
||||
}
|
||||
states[className] = vector<int>(maxes[className]);
|
||||
SECTION("Test TAN classifier (" + file_name + ")")
|
||||
{
|
||||
auto clf = bayesnet::TAN();
|
||||
clf.fit(Xd, y, features, className, states);
|
||||
auto score = clf.score(Xd, y);
|
||||
//scores[{file_name, "TAN"}] = score;
|
||||
REQUIRE(score == Catch::Approx(scores[{file_name, "TAN"}]).epsilon(1e-6));
|
||||
}
|
||||
SECTION("Test KDB classifier (" + file_name + ")")
|
||||
{
|
||||
auto clf = bayesnet::KDB(2);
|
||||
clf.fit(Xd, y, features, className, states);
|
||||
auto score = clf.score(Xd, y);
|
||||
//scores[{file_name, "KDB"}] = score;
|
||||
REQUIRE(score == Catch::Approx(scores[{file_name, "KDB"
|
||||
}]).epsilon(1e-6));
|
||||
}
|
||||
SECTION("Test SPODE classifier (" + file_name + ")")
|
||||
{
|
||||
auto clf = bayesnet::SPODE(1);
|
||||
clf.fit(Xd, y, features, className, states);
|
||||
auto score = clf.score(Xd, y);
|
||||
// scores[{file_name, "SPODE"}] = score;
|
||||
REQUIRE(score == Catch::Approx(scores[{file_name, "SPODE"}]).epsilon(1e-6));
|
||||
}
|
||||
SECTION("Test AODE classifier (" + file_name + ")")
|
||||
{
|
||||
auto clf = bayesnet::AODE();
|
||||
clf.fit(Xd, y, features, className, states);
|
||||
auto score = clf.score(Xd, y);
|
||||
// scores[{file_name, "AODE"}] = score;
|
||||
REQUIRE(score == Catch::Approx(scores[{file_name, "AODE"}]).epsilon(1e-6));
|
||||
}
|
||||
// for (auto scores : scores) {
|
||||
// cout << "{{\"" << scores.first.first << "\", \"" << scores.first.second << "\"}, " << scores.second << "}, ";
|
||||
// }
|
||||
}
|
Loading…
Reference in New Issue
Block a user