BayesNet 1.0.7.
Bayesian Network and basic classifiers Library.
Loading...
Searching...
No Matches
Ensemble.cc
1// ***************************************************************
2// SPDX-FileCopyrightText: Copyright 2024 Ricardo Montañana Gómez
3// SPDX-FileType: SOURCE
4// SPDX-License-Identifier: MIT
5// ***************************************************************
6#include "Ensemble.h"
7
8namespace bayesnet {
9
10 Ensemble::Ensemble(bool predict_voting) : Classifier(Network()), n_models(0), predict_voting(predict_voting)
11 {
12 };
13 const std::string ENSEMBLE_NOT_FITTED = "Ensemble has not been fitted";
14 void Ensemble::trainModel(const torch::Tensor& weights, const Smoothing_t smoothing)
15 {
16 n_models = models.size();
17 for (auto i = 0; i < n_models; ++i) {
18 // fit with std::vectors
19 models[i]->fit(dataset, features, className, states, smoothing);
20 }
21 }
22 std::vector<int> Ensemble::compute_arg_max(std::vector<std::vector<double>>& X)
23 {
24 std::vector<int> y_pred;
25 for (auto i = 0; i < X.size(); ++i) {
26 auto max = std::max_element(X[i].begin(), X[i].end());
27 y_pred.push_back(std::distance(X[i].begin(), max));
28 }
29 return y_pred;
30 }
31 torch::Tensor Ensemble::compute_arg_max(torch::Tensor& X)
32 {
33 auto y_pred = torch::argmax(X, 1);
34 return y_pred;
35 }
36 torch::Tensor Ensemble::voting(torch::Tensor& votes)
37 {
38 // Convert m x n_models tensor to a m x n_class_states with voting probabilities
39 auto y_pred_ = votes.accessor<int, 2>();
40 std::vector<int> y_pred_final;
41 int numClasses = states.at(className).size();
42 // votes is m x n_models with the prediction of every model for each sample
43 auto result = torch::zeros({ votes.size(0), numClasses }, torch::kFloat32);
44 auto sum = std::reduce(significanceModels.begin(), significanceModels.end());
45 for (int i = 0; i < votes.size(0); ++i) {
46 // n_votes store in each index (value of class) the significance added by each model
47 // i.e. n_votes[0] contains how much value has the value 0 of class. That value is generated by the models predictions
48 std::vector<double> n_votes(numClasses, 0.0);
49 for (int j = 0; j < n_models; ++j) {
50 n_votes[y_pred_[i][j]] += significanceModels.at(j);
51 }
52 result[i] = torch::tensor(n_votes);
53 }
54 // To only do one division and gain precision
55 result /= sum;
56 return result;
57 }
58 std::vector<std::vector<double>> Ensemble::predict_proba(std::vector<std::vector<int>>& X)
59 {
60 if (!fitted) {
61 throw std::logic_error(ENSEMBLE_NOT_FITTED);
62 }
63 return predict_voting ? predict_average_voting(X) : predict_average_proba(X);
64 }
65 torch::Tensor Ensemble::predict_proba(torch::Tensor& X)
66 {
67 if (!fitted) {
68 throw std::logic_error(ENSEMBLE_NOT_FITTED);
69 }
70 return predict_voting ? predict_average_voting(X) : predict_average_proba(X);
71 }
72 std::vector<int> Ensemble::predict(std::vector<std::vector<int>>& X)
73 {
74 auto res = predict_proba(X);
75 return compute_arg_max(res);
76 }
77 torch::Tensor Ensemble::predict(torch::Tensor& X)
78 {
79 auto res = predict_proba(X);
80 return compute_arg_max(res);
81 }
82 torch::Tensor Ensemble::predict_average_proba(torch::Tensor& X)
83 {
84 auto n_states = models[0]->getClassNumStates();
85 torch::Tensor y_pred = torch::zeros({ X.size(1), n_states }, torch::kFloat32);
86 for (auto i = 0; i < n_models; ++i) {
87 auto ypredict = models[i]->predict_proba(X);
88 /*std::cout << "model " << i << " prediction: " << ypredict << " significance " << significanceModels[i] << std::endl;*/
89 y_pred += ypredict * significanceModels[i];
90 }
91 auto sum = std::reduce(significanceModels.begin(), significanceModels.end());
92 y_pred /= sum;
93 return y_pred;
94 }
95 std::vector<std::vector<double>> Ensemble::predict_average_proba(std::vector<std::vector<int>>& X)
96 {
97 auto n_states = models[0]->getClassNumStates();
98 std::vector<std::vector<double>> y_pred(X[0].size(), std::vector<double>(n_states, 0.0));
99 for (auto i = 0; i < n_models; ++i) {
100 auto ypredict = models[i]->predict_proba(X);
101 assert(ypredict.size() == y_pred.size());
102 assert(ypredict[0].size() == y_pred[0].size());
103 // Multiply each prediction by the significance of the model and then add it to the final prediction
104 for (auto j = 0; j < ypredict.size(); ++j) {
105 std::transform(y_pred[j].begin(), y_pred[j].end(), ypredict[j].begin(), y_pred[j].begin(),
106 [significanceModels = significanceModels[i]](double x, double y) { return x + y * significanceModels; });
107 }
108 }
109 auto sum = std::reduce(significanceModels.begin(), significanceModels.end());
110 //Divide each element of the prediction by the sum of the significances
111 for (auto j = 0; j < y_pred.size(); ++j) {
112 std::transform(y_pred[j].begin(), y_pred[j].end(), y_pred[j].begin(), [sum](double x) { return x / sum; });
113 }
114 return y_pred;
115 }
116 std::vector<std::vector<double>> Ensemble::predict_average_voting(std::vector<std::vector<int>>& X)
117 {
118 torch::Tensor Xt = bayesnet::vectorToTensor(X, false);
119 auto y_pred = predict_average_voting(Xt);
120 std::vector<std::vector<double>> result = tensorToVectorDouble(y_pred);
121 return result;
122 }
123 torch::Tensor Ensemble::predict_average_voting(torch::Tensor& X)
124 {
125 // Build a m x n_models tensor with the predictions of each model
126 torch::Tensor y_pred = torch::zeros({ X.size(1), n_models }, torch::kInt32);
127 for (auto i = 0; i < n_models; ++i) {
128 auto ypredict = models[i]->predict(X);
129 y_pred.index_put_({ "...", i }, ypredict);
130 }
131 return voting(y_pred);
132 }
133 float Ensemble::score(torch::Tensor& X, torch::Tensor& y)
134 {
135 auto y_pred = predict(X);
136 int correct = 0;
137 for (int i = 0; i < y_pred.size(0); ++i) {
138 if (y_pred[i].item<int>() == y[i].item<int>()) {
139 correct++;
140 }
141 }
142 return (double)correct / y_pred.size(0);
143 }
144 float Ensemble::score(std::vector<std::vector<int>>& X, std::vector<int>& y)
145 {
146 auto y_pred = predict(X);
147 int correct = 0;
148 for (int i = 0; i < y_pred.size(); ++i) {
149 if (y_pred[i] == y[i]) {
150 correct++;
151 }
152 }
153 return (double)correct / y_pred.size();
154 }
155 std::vector<std::string> Ensemble::show() const
156 {
157 auto result = std::vector<std::string>();
158 for (auto i = 0; i < n_models; ++i) {
159 auto res = models[i]->show();
160 result.insert(result.end(), res.begin(), res.end());
161 }
162 return result;
163 }
164 std::vector<std::string> Ensemble::graph(const std::string& title) const
165 {
166 auto result = std::vector<std::string>();
167 for (auto i = 0; i < n_models; ++i) {
168 auto res = models[i]->graph(title + "_" + std::to_string(i));
169 result.insert(result.end(), res.begin(), res.end());
170 }
171 return result;
172 }
173 int Ensemble::getNumberOfNodes() const
174 {
175 int nodes = 0;
176 for (auto i = 0; i < n_models; ++i) {
177 nodes += models[i]->getNumberOfNodes();
178 }
179 return nodes;
180 }
181 int Ensemble::getNumberOfEdges() const
182 {
183 int edges = 0;
184 for (auto i = 0; i < n_models; ++i) {
185 edges += models[i]->getNumberOfEdges();
186 }
187 return edges;
188 }
189 int Ensemble::getNumberOfStates() const
190 {
191 int nstates = 0;
192 for (auto i = 0; i < n_models; ++i) {
193 nstates += models[i]->getNumberOfStates();
194 }
195 return nstates;
196 }
197}