13#include "bayesnet/utils/TensorUtils.h"
20 XSpode::XSpode(
int spIndex)
21 : superParent_{ spIndex }, nFeatures_{ 0 }, statesClass_{ 0 }, alpha_{ 1.0 },
22 initializer_{ 1.0 }, semaphore_{ CountingSemaphore::getInstance() },
25 validHyperparameters = {
"parent" };
28 void XSpode::setHyperparameters(
const nlohmann::json& hyperparameters_)
30 auto hyperparameters = hyperparameters_;
31 if (hyperparameters.contains(
"parent")) {
32 superParent_ = hyperparameters[
"parent"];
33 hyperparameters.erase(
"parent");
35 Classifier::setHyperparameters(hyperparameters);
38 void XSpode::fitx(torch::Tensor & X, torch::Tensor& y, torch::Tensor& weights_,
const Smoothing_t smoothing)
45 trainModel(weights_, smoothing);
55 void XSpode::buildModel(
const torch::Tensor& weights)
65 states_.resize(nFeatures_);
66 for (
int f = 0; f < nFeatures_; f++) {
69 states_[f] = dataset[f].max().item<
int>() + 1;
72 statesClass_ = dataset[-1].max().item<
int>() + 1;
75 classCounts_.resize(statesClass_, 0.0);
78 spFeatureCounts_.resize(states_[superParent_] * statesClass_, 0.0);
84 childOffsets_.resize(nFeatures_, -1);
86 for (
int f = 0; f < nFeatures_; f++) {
87 if (f == superParent_)
89 childOffsets_[f] = totalSize;
92 totalSize += (states_[f] * statesClass_ * states_[superParent_]);
94 childCounts_.resize(totalSize, 0.0);
105 void XSpode::trainModel(
const torch::Tensor& weights,
106 const bayesnet::Smoothing_t smoothing)
109 for (
int i = 0; i < m; i++) {
110 std::vector<int> instance(nFeatures_ + 1);
111 for (
int f = 0; f < nFeatures_; f++) {
112 instance[f] = dataset[f][i].item<
int>();
114 instance[nFeatures_] = dataset[-1][i].item<
int>();
115 addSample(instance, weights[i].item<double>());
118 case bayesnet::Smoothing_t::ORIGINAL:
121 case bayesnet::Smoothing_t::LAPLACE:
127 initializer_ = std::numeric_limits<double>::max() /
128 (nFeatures_ * nFeatures_);
130 computeProbabilities();
140 void XSpode::addSample(
const std::vector<int>& instance,
double weight)
145 int c = instance.back();
147 classCounts_[c] += weight;
150 int spVal = instance[superParent_];
151 spFeatureCounts_[spVal * statesClass_ + c] += weight;
154 for (
int f = 0; f < nFeatures_; f++) {
155 if (f == superParent_)
157 int childVal = instance[f];
158 int offset = childOffsets_[f];
161 int blockSize = states_[f] * statesClass_;
162 int idx = offset + spVal * blockSize + childVal * statesClass_ + c;
163 childCounts_[idx] += weight;
177 void XSpode::computeProbabilities()
180 std::accumulate(classCounts_.begin(), classCounts_.end(), 0.0);
183 classPriors_.resize(statesClass_, 0.0);
184 if (totalCount <= 0.0) {
186 double unif = 1.0 /
static_cast<double>(statesClass_);
187 for (
int c = 0; c < statesClass_; c++) {
188 classPriors_[c] = unif;
191 for (
int c = 0; c < statesClass_; c++) {
193 (classCounts_[c] + alpha_) / (totalCount + alpha_ * statesClass_);
198 spFeatureProbs_.resize(spFeatureCounts_.size());
201 int spCard = states_[superParent_];
202 for (
int spVal = 0; spVal < spCard; spVal++) {
203 for (
int c = 0; c < statesClass_; c++) {
204 double denom = classCounts_[c] + alpha_ * spCard;
205 double num = spFeatureCounts_[spVal * statesClass_ + c] + alpha_;
206 spFeatureProbs_[spVal * statesClass_ + c] = (denom <= 0.0 ? 0.0 : num / denom);
211 childProbs_.resize(childCounts_.size());
212 for (
int f = 0; f < nFeatures_; f++) {
213 if (f == superParent_)
215 int offset = childOffsets_[f];
216 int childCard = states_[f];
219 for (
int spVal = 0; spVal < spCard; spVal++) {
220 for (
int childVal = 0; childVal < childCard; childVal++) {
221 for (
int c = 0; c < statesClass_; c++) {
222 int idx = offset + spVal * (childCard * statesClass_) +
223 childVal * statesClass_ + c;
225 double num = childCounts_[idx] + alpha_;
229 spFeatureCounts_[spVal * statesClass_ + c] + alpha_ * childCard;
230 childProbs_[idx] = (denom <= 0.0 ? 0.0 : num / denom);
245 std::vector<double> XSpode::predict_proba(
const std::vector<int>& instance)
const
248 throw std::logic_error(CLASSIFIER_NOT_FITTED);
250 std::vector<double> probs(statesClass_, 0.0);
252 int spVal = instance[superParent_];
253 for (
int c = 0; c < statesClass_; c++) {
254 double pc = classPriors_[c];
255 double pSpC = spFeatureProbs_[spVal * statesClass_ + c];
256 probs[c] = pc * pSpC * initializer_;
260 for (
int feature = 0; feature < nFeatures_; feature++) {
261 if (feature == superParent_)
263 int sf = instance[feature];
264 int offset = childOffsets_[feature];
265 int childCard = states_[feature];
268 int base = offset + spVal * (childCard * statesClass_) + sf * statesClass_;
269 for (
int c = 0; c < statesClass_; c++) {
270 probs[c] *= childProbs_[base + c];
278 std::vector<std::vector<double>> XSpode::predict_proba(std::vector<std::vector<int>>& test_data)
280 int test_size = test_data[0].size();
281 int sample_size = test_data.size();
282 auto probabilities = std::vector<std::vector<double>>(
283 test_size, std::vector<double>(statesClass_));
285 int chunk_size = std::min(150,
int(test_size / semaphore_.getMaxCount()) + 1);
286 std::vector<std::thread> threads;
287 auto worker = [&](
const std::vector<std::vector<int>>& samples,
int begin,
288 int chunk,
int sample_size,
289 std::vector<std::vector<double>>& predictions) {
290 std::string threadName =
291 "(V)PWorker-" + std::to_string(begin) +
"-" + std::to_string(chunk);
292#if defined(__linux__)
293 pthread_setname_np(pthread_self(), threadName.c_str());
295 pthread_setname_np(threadName.c_str());
298 std::vector<int> instance(sample_size);
299 for (
int sample = begin; sample < begin + chunk; ++sample) {
300 for (
int feature = 0; feature < sample_size; ++feature) {
301 instance[feature] = samples[feature][sample];
303 predictions[sample] = predict_proba(instance);
305 semaphore_.release();
307 for (
int begin = 0; begin < test_size; begin += chunk_size) {
308 int chunk = std::min(chunk_size, test_size - begin);
309 semaphore_.acquire();
310 threads.emplace_back(worker, test_data, begin, chunk, sample_size, std::ref(probabilities));
312 for (
auto& thread : threads) {
315 return probabilities;
321 void XSpode::normalize(std::vector<double>& v)
const
330 for (
auto& val : v) {
338 std::string XSpode::to_string()
const
340 std::ostringstream oss;
341 oss <<
"----- XSpode Model -----" << std::endl
342 <<
"nFeatures_ = " << nFeatures_ << std::endl
343 <<
"superParent_ = " << superParent_ << std::endl
344 <<
"statesClass_ = " << statesClass_ << std::endl
348 for (
int s : states_)
350 oss <<
"]" << std::endl;
351 oss <<
"classCounts_: [";
352 for (
double c : classCounts_)
354 oss <<
"]" << std::endl;
355 oss <<
"classPriors_: [";
356 for (
double c : classPriors_)
358 oss <<
"]" << std::endl;
359 oss <<
"spFeatureCounts_: size = " << spFeatureCounts_.size() << std::endl
361 for (
double c : spFeatureCounts_)
363 oss <<
"]" << std::endl;
364 oss <<
"spFeatureProbs_: size = " << spFeatureProbs_.size() << std::endl
366 for (
double c : spFeatureProbs_)
368 oss <<
"]" << std::endl;
369 oss <<
"childCounts_: size = " << childCounts_.size() << std::endl <<
"[";
370 for (
double cc : childCounts_)
372 oss <<
"]" << std::endl;
374 for (
double cp : childProbs_)
376 oss <<
"]" << std::endl;
377 oss <<
"childOffsets_: [";
378 for (
int co : childOffsets_)
380 oss <<
"]" << std::endl;
381 oss << std::string(40,
'-') << std::endl;
384 int XSpode::getNumberOfNodes()
const {
return nFeatures_ + 1; }
385 int XSpode::getClassNumStates()
const {
return statesClass_; }
386 int XSpode::getNFeatures()
const {
return nFeatures_; }
387 int XSpode::getNumberOfStates()
const
389 return std::accumulate(states_.begin(), states_.end(), 0) * nFeatures_;
391 int XSpode::getNumberOfEdges()
const
393 return 2 * nFeatures_ + 1;
399 int XSpode::predict(
const std::vector<int>& instance)
const
401 auto p = predict_proba(instance);
402 return static_cast<int>(std::distance(p.begin(), std::max_element(p.begin(), p.end())));
404 std::vector<int> XSpode::predict(std::vector<std::vector<int>>& test_data)
406 auto probabilities = predict_proba(test_data);
407 std::vector<int> predictions(probabilities.size(), 0);
409 for (
size_t i = 0; i < probabilities.size(); i++) {
410 predictions[i] = std::distance(
411 probabilities[i].begin(),
412 std::max_element(probabilities[i].begin(), probabilities[i].end()));
416 torch::Tensor XSpode::predict(torch::Tensor& X)
418 auto X_ = TensorUtils::to_matrix(X);
419 auto result_v = predict(X_);
420 return torch::tensor(result_v, torch::kInt32);
422 torch::Tensor XSpode::predict_proba(torch::Tensor& X)
424 auto X_ = TensorUtils::to_matrix(X);
425 auto result_v = predict_proba(X_);
426 int n_samples = X.size(1);
427 torch::Tensor result =
428 torch::zeros({ n_samples, statesClass_ }, torch::kDouble);
429 for (
int i = 0; i < result_v.size(); ++i) {
430 result.index_put_({ i,
"..." }, torch::tensor(result_v[i]));
434 float XSpode::score(torch::Tensor& X, torch::Tensor& y)
436 torch::Tensor y_pred = predict(X);
437 return (y_pred == y).sum().item<
float>() / y.size(0);
439 float XSpode::score(std::vector<std::vector<int>>& X, std::vector<int>& y)
441 auto y_pred = this->predict(X);
443 for (
int i = 0; i < y_pred.size(); ++i) {
444 if (y_pred[i] == y[i]) {
448 return (
double)correct / y_pred.size();