diff --git a/sample/main.cc b/sample/main.cc index 1b412df..0973c27 100644 --- a/sample/main.cc +++ b/sample/main.cc @@ -7,6 +7,7 @@ #include "Network.h" #include "Metrics.hpp" #include "CPPFImdlp.h" +#include "KDB.h" using namespace std; @@ -247,5 +248,14 @@ int main(int argc, char** argv) long m2 = features.size() + 1; auto matrix2 = torch::from_blob(conditional2.data(), { m, m }); cout << matrix2 << endl; + cout << "****************** KDB ******************" << endl; + map> states; + for (auto feature : features) { + states[feature] = vector(maxes[feature]); + } + states[className] = vector(maxes[className]); + auto kdb = bayesnet::KDB(1); + kdb.fit(Xd, y, features, className, states); + cout << "****************** KDB ******************" << endl; return 0; } \ No newline at end of file diff --git a/src/BaseClassifier.cc b/src/BaseClassifier.cc new file mode 100644 index 0000000..6b67cac --- /dev/null +++ b/src/BaseClassifier.cc @@ -0,0 +1,90 @@ +#include "BaseClassifier.h" + +namespace bayesnet { + using namespace std; + using namespace torch; + + BaseClassifier::BaseClassifier(Network model) : model(model), m(0), n(0) {} + BaseClassifier& BaseClassifier::build(vector& features, string className, map>& states) + { + + dataset = torch::cat({ X, y.view({150, 1}) }, 1); + this->features = features; + this->className = className; + this->states = states; + cout << "Checking fit parameters" << endl; + checkFitParameters(); + train(); + return *this; + } + BaseClassifier& BaseClassifier::fit(Tensor& X, Tensor& y, vector& features, string className, map>& states) + { + this->X = X; + this->y = y; + return build(features, className, states); + } + BaseClassifier& BaseClassifier::fit(vector>& X, vector& y, vector& features, string className, map>& states) + { + this->X = torch::zeros({ static_cast(X[0].size()), static_cast(X.size()) }, kInt64); + for (int i = 0; i < X.size(); ++i) { + this->X.index_put_({ "...", i }, torch::tensor(X[i], kInt64)); + } + this->y = torch::tensor(y, kInt64); + return build(features, className, states); + } + void BaseClassifier::checkFitParameters() + { + auto sizes = X.sizes(); + m = sizes[0]; + n = sizes[1]; + if (m != y.size(0)) { + throw invalid_argument("X and y must have the same number of samples"); + } + if (n != features.size()) { + throw invalid_argument("X and features must have the same number of features"); + } + if (states.find(className) == states.end()) { + throw invalid_argument("className not found in states"); + } + for (auto feature : features) { + if (states.find(feature) == states.end()) { + throw invalid_argument("feature [" + feature + "] not found in states"); + } + } + } + vector> tensorToVector(const torch::Tensor& tensor) + { + // convert mxn tensor to nxm vector + vector> result; + auto tensor_accessor = tensor.accessor(); + + // Iterate over columns and rows of the tensor + for (int j = 0; j < tensor.size(1); ++j) { + vector column; + for (int i = 0; i < tensor.size(0); ++i) { + column.push_back(tensor_accessor[i][j]); + } + result.push_back(column); + } + + return result; + } + Tensor BaseClassifier::predict(Tensor& X) + { + auto m_ = X.size(0); + auto n_ = X.size(1); + vector> Xd(n_, vector(m_, 0)); + for (auto i = 0; i < n_; i++) { + auto temp = X.index({ "...", i }); + Xd[i] = vector(temp.data_ptr(), temp.data_ptr() + m_); + } + auto yp = model.predict(Xd); + auto ypred = torch::tensor(yp, torch::kInt64); + return ypred; + } + float BaseClassifier::score(Tensor& X, Tensor& y) + { + Tensor y_pred = predict(X); + return (y_pred == y).sum().item() / y.size(0); + } +} \ No newline at end of file diff --git a/src/BaseClassifier.h b/src/BaseClassifier.h new file mode 100644 index 0000000..0a6b744 --- /dev/null +++ b/src/BaseClassifier.h @@ -0,0 +1,39 @@ +#ifndef CLASSIFIERS_H +#include +#include "Network.h" +using namespace std; +using namespace torch; + +namespace bayesnet { + class BaseClassifier { + private: + BaseClassifier& build(vector& features, string className, map>& states); + protected: + Network model; + int m, n; // m: number of samples, n: number of features + Tensor X; + Tensor y; + Tensor dataset; + vector features; + string className; + map> states; + void checkFitParameters(); + virtual void train() = 0; + public: + BaseClassifier(Network model); + Tensor& getX(); + vector& getFeatures(); + string& getClassName(); + BaseClassifier& fit(Tensor& X, Tensor& y, vector& features, string className, map>& states); + BaseClassifier& fit(vector>& X, vector& y, vector& features, string className, map>& states); + Tensor predict(Tensor& X); + float score(Tensor& X, Tensor& y); + }; + +} +#endif + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b923ad7..d6caf2e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,2 +1,2 @@ -add_library(BayesNet Network.cc Node.cc Metrics.cc) +add_library(BayesNet Network.cc Node.cc Metrics.cc BaseClassifier.cc KDB.cc) target_link_libraries(BayesNet "${TORCH_LIBRARIES}") \ No newline at end of file diff --git a/src/KDB.cc b/src/KDB.cc new file mode 100644 index 0000000..df6ad77 --- /dev/null +++ b/src/KDB.cc @@ -0,0 +1,44 @@ +#include "KDB.h" +#include "Metrics.hpp" + +namespace bayesnet { + using namespace std; + using namespace torch; + KDB::KDB(int k) : BaseClassifier(Network()), k(k) {} + void KDB::train() + { + /* + 1. For each feature Xi, compute mutual information, I(X;C), + where C is the class. + 2. Compute class conditional mutual information I(Xi;XjIC), f or each + pair of features Xi and Xj, where i#j. + 3. Let the used variable list, S, be empty. + 4. Let the DAG network being constructed, BN, begin with a single + class node, C. + 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). + 5.2. Add a node to BN representing Xmax. + 5.3. Add an arc from C to Xmax in BN. + 5.4. Add m = min(lSl,/c) arcs from m distinct features Xj in S with + the highest value for I(Xmax;X,jC). + 5.5. Add Xmax to S. + Compute the conditional probabilility infered by the structure of BN by + using counts from DB, and output BN. + */ + // 1. For each feature Xi, compute mutual information, I(X;C), + // where C is the class. + cout << "Computing mutual information between features and class" << endl; + auto n_classes = states[className].size(); + auto metrics = Metrics(dataset, features, className, n_classes); + for (auto i = 0; i < features.size(); i++) { + Tensor firstFeature = X.index({ "...", i }); + Tensor secondFeature = y; + double mi = metrics.mutualInformation(firstFeature, y); + cout << "Mutual information between " << features[i] << " and " << className << " is " << mi << endl; + + } + + + } +} \ No newline at end of file diff --git a/src/KDB.h b/src/KDB.h new file mode 100644 index 0000000..2714cb7 --- /dev/null +++ b/src/KDB.h @@ -0,0 +1,16 @@ +#ifndef KDB_H +#define KDB_H +#include "BaseClassifier.h" +namespace bayesnet { + using namespace std; + using namespace torch; + class KDB : public BaseClassifier { + private: + int k; + protected: + void train(); + public: + KDB(int k); + }; +} +#endif \ No newline at end of file diff --git a/src/Metrics.hpp b/src/Metrics.hpp index 2587d80..2754f0a 100644 --- a/src/Metrics.hpp +++ b/src/Metrics.hpp @@ -14,8 +14,8 @@ namespace bayesnet { vector> doCombinations(const vector&); double entropy(torch::Tensor&); double conditionalEntropy(torch::Tensor&, torch::Tensor&); - double mutualInformation(torch::Tensor&, torch::Tensor&); public: + double mutualInformation(torch::Tensor&, torch::Tensor&); Metrics(torch::Tensor&, vector&, string&, int); Metrics(const vector>&, const vector&, const vector&, const string&, const int); vector conditionalEdgeWeights(); diff --git a/src/Network.h b/src/Network.h index 42403a7..b8a75f4 100644 --- a/src/Network.h +++ b/src/Network.h @@ -4,7 +4,6 @@ #include #include - namespace bayesnet { class Network { private: