CMP++: Uncertainty Quantification & Bayesian Calibration
Loading...
Searching...
No Matches
model_cluster.h
Go to the documentation of this file.
1#ifndef MODEL_CLUSTER_H
2#define MODEL_CLUSTER_H
3
4#include <iostream>
5#include <limits>
6#include <distribution.h>
7#include <cmp_defines.h>
8#include <grid.h>
9#include <classifier.h>
10#include <gp.h>
11#include <unordered_set>
12#include <unordered_map>
13#include <string>
14#include <cstring>
15
16
21namespace cmp {
22
66
67 private:
68 std::default_random_engine rng_;
69
70 Eigen::MatrixXd xObs_;
71 Eigen::VectorXd yObs_;
72
73 size_t nClusters_;
74 size_t nObs_;
75 size_t dimX_;
76
77 // The labels of the points
79
80 // The local index table
82
83 // The GPs for each cluster, along with their centroids and fit status
84 std::vector<bool> fit_;
85 std::vector<Eigen::VectorXd> centroids_;
86 std::vector<cmp::gp::GaussianProcess> gps_;
87
88 // The cluster sizes
89 std::vector<size_t> clusterSize_;
90
91 // The Gamma parameter
92 double gamma_{1.0};
93
94 // Kernel, mean and nugget for each cluster
95 std::shared_ptr<covariance::Covariance> kernel_;
96 std::shared_ptr<mean::Mean> mean_;
97 Eigen::VectorXd parameters_;
98 double nugget_{1e-8};
99 public:
100
101 ModelCluster() = default;
102
112 void set(std::shared_ptr<covariance::Covariance> kernel, std::shared_ptr<mean::Mean> mean, Eigen::VectorXd parameters, double nugget, double gamma, unsigned int seed) {
113 kernel_ = kernel;
114 mean_ = mean;
115 nugget_ = nugget;
116 parameters_ = parameters;
117 gamma_ = gamma;
118 rng_.seed(seed);
119 }
120
127 void condition(const Eigen::Ref<const Eigen::MatrixXd> &xObs, const Eigen::Ref<const Eigen::VectorXd> &yObs, const Eigen::Ref<const Eigen::VectorXs> &labels) {
128
129 // Initialize the members
130 nObs_ = xObs.rows();
131 dimX_ = xObs.cols();
132 xObs_ = xObs;
133 yObs_ = yObs;
134
135 // Count the number of clusters
136 nClusters_ = 0;
137 for(size_t i = 0; i < nObs_; i++) {
138 if(labels(i) + 1 > nClusters_) {
139 nClusters_ = labels(i) + 1;
140 }
141 }
142
143 // Set the GPs
144 gps_.clear();
145 for(size_t i = 0; i < nClusters_; i++) {
146 gps_.emplace_back(kernel_, mean_, parameters_, nugget_);
147 }
148
149 // Initialize the containers
150 labels_ = labels;
151 localIndexTable_ = Eigen::VectorXs::Zero(nObs_);
152 centroids_ = std::vector<Eigen::VectorXd>(nClusters_, Eigen::VectorXd::Zero(dimX_));
153 // Ensure per-cluster bookkeeping is correctly sized now that nClusters_ is known
154 fit_ = std::vector<bool>(nClusters_, false);
155 clusterSize_ = std::vector<size_t>(nClusters_, 0);
156
157 // Call the update model function
158 updateModel(std::vector<bool>(nClusters_, true));
159 }
160
161 std::pair<double, double> predict(const Eigen::VectorXd &xStar, cmp::classifier::Classifier *classifier) const {
162
163 double mu = 0.0;
164 double var = 0.0;
165 std::vector<double> probs = classifier->predictProbabilities(xStar);
166 for(size_t i = 0; i < nClusters_; i++) {
167 auto [mu_i, var_i] = gps_[i].predict(xStar);
168 mu += probs[i] * mu_i;
169 var += probs[i] * probs[i] * var_i;
170 }
171
172 return {mu, var};
173 }
174
175 size_t nClusters() const {
176 return nClusters_;
177 }
178
179 void fit(Eigen::Ref<const Eigen::VectorXd> lowerBound, Eigen::Ref<const Eigen::VectorXd> upperBound, cmp::gp::method fitType, nlopt::algorithm algorithm = nlopt::LN_SBPLX, double tol = 1e-3, std::shared_ptr<cmp::prior::Prior> prior = nullptr, std::vector<bool> logScale = {}) {
180
181
182 for(size_t i = 0; i < nClusters(); i++) {
183 if(!fit_[i]) {
184 auto index = getIndices(i);
185 auto xObsi = cmp::slice(xObs_, index);
186 auto yObsi = cmp::slice(yObs_, index);
187
188 gps_[i].fit(xObsi, yObsi, lowerBound, upperBound, fitType, algorithm, tol, true, false, prior, logScale);
189 fit_[i] = true;
190 }
191 }
192 }
193
194 size_t nPoints() const {
195 return nObs_;
196 }
197
198 size_t dim() const {
199 return dimX_;
200 }
201
202 size_t getMembership(size_t i) const {
203 return labels_[i];
204 }
205
206 const Eigen::VectorXs &getLabels() const {
207 return labels_;
208 }
209
210 size_t getClusterSize(size_t i) const {
211 return clusterSize_[i];
212 }
213
215 return gps_[i];
216 }
217
218 const Eigen::VectorXd &centroid(size_t i) const {
219 return centroids_[i];
220 }
221
222 Eigen::VectorXs getIndices(size_t clusterIndex) const {
223 Eigen::VectorXs indices = Eigen::VectorXs::Zero(clusterSize_[clusterIndex]);
224 size_t counter = 0;
225 for(size_t j = 0; j < nObs_; j++) {
226 if(labels_[j] == clusterIndex) {
227 indices[counter] = j;
228 counter++;
229 }
230 }
231 return indices;
232 }
233
234 void updateModel(const std::vector<bool> &affectedClusters) {
235
236 // Set the observations
237 for(size_t i = 0; i < nClusters_; i++) {
238
239 // Check if the cluster is affected
240 if(!affectedClusters[i]) {
241 continue;
242 }
243
244 // Centroids
245 centroids_[i] = Eigen::VectorXd::Zero(dimX_);
246
247 // Counter for the cluster size
248 size_t counter = 0;
249
250 // Set the observations
251 for(size_t j = 0; j < nObs_; j++) {
252 if(labels_[j] == i) {
253
254 // Update the centroid
255 centroids_[i] += xObs_.row(j).transpose();
256
257 // Set the membership
258 localIndexTable_[j] = counter;
259
260 // Update the counter
261 counter++;
262 }
263 }
264
265 // Compute the centroid
266 centroids_[i] /= double(counter);
267
268 // Set the cluster size
269 clusterSize_[i] = counter;
270
271 // GP is not fit
272 fit_[i] = false;
273 }
274 }
275
281 bool performSwitches(const std::vector<std::pair<size_t, size_t>> &newOwners, size_t minSize) {
282
283 // Affected clusters
284 std::vector<bool> affectedClusters(nClusters_, false);
285
286 // Iterate through the points
287 for(size_t i = 0; i < newOwners.size(); i++) {
288
289 // Get the global index
290 size_t globalIndex = newOwners[i].first;
291
292 // Get the new and old owners
293 size_t newOwner = newOwners[i].second;
294 size_t oldOwner = labels_[globalIndex];
295
296 // The clusters are affected
297 affectedClusters[oldOwner] = true;
298 affectedClusters[newOwner] = true;
299
300 // Update the membership
301 labels_[globalIndex] = newOwner;
302 }
303
304 // Call the update model function
305 updateModel(affectedClusters);
306
307 // Now check if we can purge clusters
308 size_t numPurges = 0;
309 bool finished = false;
310 while(!finished) {
311 finished = true;
312 for(size_t i = 0; i < nClusters_; i++) {
313 if(clusterSize_[i] < minSize) {
314 purge(i);
315 numPurges++;
316 finished = false;
317 break;
318 }
319 }
320 }
321
322 return (numPurges > 0) || (newOwners.size() > 0);
323 }
324
325
326
333 double computeScore(size_t globalIndex) const {
334
335 // Find the owner of the point
336 size_t owner = labels_[globalIndex];
337 size_t localIndex = localIndexTable_[globalIndex];
338
339 // Evaluate the error
340 auto [mean, var] = gps_[owner].predictLOO(localIndex);
341 return cmp::distribution::NormalDistribution::logPDF(yObs_(globalIndex) - mean, std::sqrt(var));
342 }
343
352 double computeScore(size_t clusterIndex, size_t globalIndex) const {
353
354 auto [mean, var] = gps_[clusterIndex].predict(xObs_.row(globalIndex).transpose());
355 return cmp::distribution::NormalDistribution::logPDF(yObs_(globalIndex) - mean, std::sqrt(var));
356 }
357
358 std::vector<std::pair<size_t, size_t>> switchStep(cmp::classifier::Classifier* classifier, size_t maxAllowedSwitches = 10, double minProb = 0.1, double T = 1.0) {
359
360 // Compute membership probabilities
361 auto probabilities = computeProbabilities(T, classifier, minProb);
362
363 // Compute The probability of actually changing the membership for each point
364 // This is done by summing the probabilities of all clusters except the current owner
365 std::vector<double> switchingProbabilities(nObs_, 0.0);
366 for(size_t i = 0; i < nObs_; i++) {
367 size_t owner = labels_[i];
368 double sumProb = 0.0;
369 for(size_t j = 0; j < nClusters_; ++j) {
370 if(j != owner) {
371 sumProb += probabilities[i][j];
372 }
373 }
374 switchingProbabilities[i] = sumProb;
375 }
376
377 // Precompute a list with the active indices (i.e., those with non-zero switching probability)
378 std::vector<size_t> activeIndices;
379 activeIndices.reserve(nObs_);
380 for(size_t i = 0; i < nObs_; ++i)
381 if(switchingProbabilities[i] > 0.0)
382 activeIndices.push_back(i);
383
384 std::vector<std::pair<size_t, size_t>> switches;
385 switches.reserve(maxAllowedSwitches);
386
387 // Iteratively sample a new cluster and remove selected entries
388 for(size_t s = 0; s < maxAllowedSwitches && !activeIndices.empty(); ++s) {
389
390 // Build discrete distribution from current weights
391 std::vector<double> weights;
392 weights.reserve(activeIndices.size());
393 for(auto idx : activeIndices)
394 weights.push_back(switchingProbabilities[idx]);
395
396 std::discrete_distribution<size_t> dist(weights.begin(), weights.end());
397 size_t sampledPos = dist(rng_);
398 size_t globalIndex = activeIndices[sampledPos];
399
400 // Remove selected index from active pool (swap & pop)
401 activeIndices[sampledPos] = activeIndices.back();
402 activeIndices.pop_back();
403
404 // Sample new cluster for this point
405 size_t owner = labels_[globalIndex];
406 const auto& probs = probabilities[globalIndex];
407 std::discrete_distribution<size_t> clusterDist(probs.begin(), probs.end());
408 size_t newOwner = clusterDist(rng_);
409
410 if(newOwner != owner)
411 switches.emplace_back(globalIndex, newOwner);
412 }
413
414 return switches;
415 }
416
417 std::vector<std::pair<size_t, size_t>> deterministicSwitchStep(cmp::classifier::Classifier *cls, const size_t &maxAllowedSwitches, const double &minProb) {
418
419 // Compute the score changes
420 std::vector<std::pair<double, std::pair<size_t, size_t>>> allSwitches;
421 allSwitches.reserve(nObs_);
422
423 // Precompute classifier probabilities once for all observations
424 std::vector<std::vector<double>> classifierProbabilities(nObs_);
425 for(size_t i = 0; i < nObs_; i++) {
426 classifierProbabilities[i] = cls->predictProbabilities(xObs_.row(i));
427 }
428
429 for(size_t i = 0; i < nObs_; i++) {
430
431 // Compute SVM probabilities
432 const std::vector<double> &prob = classifierProbabilities[i];
433
434 // Find the owner
435 size_t owner = labels_[i];
436
437 // Skip expensive delta evaluations if no eligible alternative cluster.
438 bool hasEligibleAlternative = false;
439 for(size_t c = 0; c < nClusters_; ++c) {
440 if(c != owner && prob[c] > minProb) {
441 hasEligibleAlternative = true;
442 break;
443 }
444 }
445 if(!hasEligibleAlternative) {
446 continue;
447 }
448
449 // Compute remove-from-owner term once.
450 const double ownerRemovalDelta = computeScore(i);
451
452 // Strict monotone mode: only accept positive total move deltas.
453 double startingScore = 0.0;
454
455 // Iterate through the clusters
456 std::pair<double, size_t> bestCluster{startingScore, owner};
457 for(size_t c = 0; c < nClusters_; c++) {
458 if(c == owner) {
459 continue;
460 } else if(prob[c] <= minProb) {
461 continue;
462 } else {
463 // Exact move gain = remove-from-owner delta + add-to-candidate delta.
464 double deltaScore = computeScore(c, i) - ownerRemovalDelta;
465
466 // Check if it is better than the owner
467 if(deltaScore > bestCluster.first) {
468 bestCluster.first = deltaScore;
469 bestCluster.second = c;
470 }
471
472 }
473 }
474
475 // If it is the owner we skip
476 if(bestCluster.second == owner) {
477 continue;
478 } else {
479 // We shall add it in the list
480 allSwitches.push_back({bestCluster.first, {i, bestCluster.second}});
481 }
482 }
483
484
485
486
487 // We sort the previous vector
488 std::sort(allSwitches.begin(), allSwitches.end(), [](std::pair<double, std::pair<size_t, size_t>> a, std::pair<double, std::pair<size_t, size_t>> b) {
489 return a.first > b.first;
490 });
491
492 size_t nSwitches = std::min(maxAllowedSwitches, allSwitches.size());
493
494 std::vector<std::pair<size_t, size_t>> switches(nSwitches);
495 for(size_t i = 0; i < nSwitches; i++) {
496 switches[i] = allSwitches[i].second;
497 }
498
499 return switches;
500 }
501
508 void purge(const size_t &clusterIndex) {
509
510 std::vector<bool> affectedClusters(nClusters_, false);
511 std::cout << "Purging cluster " << clusterIndex << " with size " << getClusterSize(clusterIndex) << std::endl;
512
513 // Cycle through the observations
514 for(size_t i = 0; i < nObs_; i++) {
515
516 // Check if the point is in the cluster to purge
517 if(labels_[i] == clusterIndex) {
518
519 // We need to redistribute the point
520 std::pair<size_t, double> chosenCluster = std::make_pair(0, std::numeric_limits<double>::infinity());
521
522 for(size_t j = 0; j < nClusters_; j++) {
523
524 // Skip the current cluster
525 if(j == clusterIndex) {
526 continue;
527 }
528
529 // Compute the error
530 double centroidDistance = (xObs_.row(i).transpose() - centroids_[j]).squaredNorm();
531
532 // Check if we have a new minimum
533 if(centroidDistance < chosenCluster.second) {
534 chosenCluster = std::make_pair(j, centroidDistance);
535 }
536 }
537
538 // Perform the switch
539 labels_[i] = chosenCluster.first;
540 affectedClusters[chosenCluster.first] = true;
541 }
542 }
543
544 // Purge the cluster
545 gps_.erase(gps_.begin() + clusterIndex);
546 fit_.erase(fit_.begin() + clusterIndex);
547 clusterSize_.erase(clusterSize_.begin() + clusterIndex);
548 centroids_.erase(centroids_.begin() + clusterIndex);
549 affectedClusters.erase(affectedClusters.begin() + clusterIndex);
550
551 nClusters_--;
552
553 // Now we need to decrease the cluster index of the points
554 for(size_t i = 0; i < nObs_; i++) {
555 if(labels_[i] > clusterIndex) {
556 labels_[i]--;
557 }
558 }
559
560 // Now we need to update the model
561 updateModel(affectedClusters);
562 }
563
564 std::vector<std::vector<double>> computeProbabilities(const double &T, cmp::classifier::Classifier *classifier, const double &minProb = 0.1) const {
565
566 std::vector<std::vector<double>> switchingProbabilities(nObs_, std::vector<double>(nClusters_, 0.0));
567
568 // Precompute classifier probabilities once per point
569 std::vector<std::vector<double>> classifierProbabilities(nObs_);
570 for(size_t i = 0; i < nObs_; i++) {
571 classifierProbabilities[i] = classifier->predictProbabilities(xObs_.row(i));
572 }
573
574 // Iterate through the points
575 for(size_t i = 0; i < nObs_; i++) {
576
577 // Find the owner of the point
578 size_t owner = labels_[i];
579
580 // Exact cluster-level delta for removing point i from its owner.
581 const double ownerScore = computeScore(i);
582
583 // Compute the log-weights for each cluster
584 std::vector<double> logWeights(nClusters_, -std::numeric_limits<double>::infinity());
585
586 for(size_t j = 0; j < nClusters_; j++) {
587
588
589 if(j == owner) {
590
591 // If j is the owner, we see if it can actually own the point (i.e., if the classifier probability is above the threshold)
592 if(classifierProbabilities[i][j] <= minProb) {
593 logWeights[j] = -std::numeric_limits<double>::infinity();
594 } else {
595 // If j is the owner and can own the point, we compute the delta score and combine it with the classifier probability.
596 logWeights[j] = std::log(classifierProbabilities[i][j]);
597 }
598
599 } else {
600
601 // If j is not the owner but can't own the point due to low classifier probability, we skip it.
602 if(classifierProbabilities[i][j] <= minProb) {
603 logWeights[j] = -std::numeric_limits<double>::infinity();
604 } else {
605
606 // If j is not the owner and can own the point, we compute the delta score and combine it with the classifier probability.
607 const double deltaMove = computeScore(j, i) - ownerScore;
608 logWeights[j] = std::log(classifierProbabilities[i][j]) + (deltaMove / T);
609 }
610 }
611 }
612
613 // Find the maximum log-weight to prevent overflow during exponentiation
614 double maxLogWeight = -std::numeric_limits<double>::infinity();
615 for(size_t j = 0; j < nClusters_; j++) {
616 if(logWeights[j] > maxLogWeight) {
617 maxLogWeight = logWeights[j];
618 }
619 }
620
621 // Exponentiate safely and normalize
622 double sum = 0.0;
623 for(size_t j = 0; j < nClusters_; j++) {
624 if(logWeights[j] > -std::numeric_limits<double>::infinity()) {
625 // Subtracting maxLogWeight ensures the largest value exponentiated is 0 (exp(0) = 1)
626 switchingProbabilities[i][j] = std::exp(logWeights[j] - maxLogWeight);
627 sum += switchingProbabilities[i][j];
628 } else {
629 switchingProbabilities[i][j] = 0.0;
630 }
631 }
632
633 // Final normalization
634 if(sum > 0.0) {
635 for(size_t j = 0; j < nClusters_; j++) {
636 switchingProbabilities[i][j] /= sum;
637 }
638 } else {
639 // Extremely rare edge case where all weights are -inf (e.g. invalid probability inputs, we fall back to classifier probabilities)
640 for(size_t j = 0; j < nClusters_; j++) {
641 switchingProbabilities[i][j] = classifierProbabilities[i][j];
642 }
643 }
644 }
645
646 return switchingProbabilities;
647 }
648
649 Eigen::MatrixXd confusionMatrix(std::vector<std::vector<double>> switchingProbability) const {
650 Eigen::MatrixXd confusion_num(nClusters_, nClusters_);
651 Eigen::MatrixXd confusion_den(nClusters_, nClusters_);
652 confusion_num.setZero();
653 confusion_den.setZero();
654
655 for(size_t i = 0; i < nObs_; i++) {
656 for(size_t j = 0; j < nClusters_; j++) {
657 for(size_t k = 0; k < nClusters_; k++) {
658
659 // Only consider points that are in cluster j
660 if(labels_[i] == j) {
661 confusion_num(j, k) += std::abs(switchingProbability[i][j] - switchingProbability[i][k]);
662 confusion_den(j, k) += switchingProbability[i][j] + switchingProbability[i][k];
663 }
664 }
665 }
666 }
667
668 // Divide the matrices elementwise
669 for(size_t i = 0; i < nClusters_; i++) {
670 for(size_t j = 0; j < nClusters_; j++) {
671 if(confusion_den(i, j) != 0) {
672 confusion_num(i, j) /= confusion_den(i, j);
673 }
674 }
675 }
676
677 // Now we return the confusion matrix
678 return confusion_num;
679 }
680
681 // Merge two clusters
682 void merge(const size_t &clusterIndex1, const size_t &clusterIndex2) {
683
684 // Guard invalid indices.
685 if(clusterIndex1 >= nClusters_ || clusterIndex2 >= nClusters_) {
686 return;
687 }
688
689 // Check if the clusters are the same
690 if(clusterIndex1 == clusterIndex2) {
691 return;
692 }
693
694 std::vector<std::pair<size_t, size_t>> switches;
695
696 // Cycle through the observations
697 for(size_t i = 0; i < nObs_; i++) {
698
699 // Check if the point is in the cluster to purge
700 if(labels_[i] == clusterIndex2) {
701 switches.push_back(std::make_pair(i, clusterIndex1));
702 }
703 }
704
705 // Perform the switches
706 // Use minSize=1 so the emptied source cluster is purged immediately.
707 performSwitches(switches, 1);
708 }
709
710};
711
712namespace covariance {
732 private:
735 mutable std::unordered_map<std::string, std::vector<double>> probabilityCache_;
736
737 std::string makeProbabilityCacheKey(const Eigen::Ref<const Eigen::VectorXd> &x) const {
738 const std::size_t n = static_cast<std::size_t>(x.size());
739 std::string key(sizeof(std::size_t) + n * sizeof(double), '\0');
740 std::memcpy(key.data(), &n, sizeof(std::size_t));
741 std::memcpy(key.data() + sizeof(std::size_t), x.data(), n * sizeof(double));
742 return key;
743 }
744
745 const std::vector<double> &getCachedProbabilities(const Eigen::Ref<const Eigen::VectorXd> &x) const {
746 std::string key = makeProbabilityCacheKey(x);
747 auto it = probabilityCache_.find(key);
748 if(it == probabilityCache_.end()) {
749 auto probs = pClassifier_->predictProbabilities(x);
750 it = probabilityCache_.emplace(std::move(key), std::move(probs)).first;
751 }
752 return it->second;
753 }
754 public:
755
756 ModelClusterCovariance(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier) : pModelCluster_(modelCluster), pClassifier_(classifier) {};
757
759 probabilityCache_.clear();
760 }
761
762 void precomputeProbabilities(const Eigen::Ref<const Eigen::MatrixXd> &xObs) const {
763 probabilityCache_.reserve(probabilityCache_.size() + static_cast<std::size_t>(xObs.rows()));
764 for(size_t i = 0; i < xObs.rows(); i++) {
765 (void)getCachedProbabilities(xObs.row(i));
766 }
767 }
768
769 double eval(const Eigen::VectorXd& x1, const Eigen::VectorXd &x2, const Eigen::VectorXd& par) const {
770 const auto &p1 = getCachedProbabilities(x1);
771 const auto &p2 = getCachedProbabilities(x2);
772 double result = 0.0;
773 for(size_t k = 0; k < p1.size(); k++) {
774 double cov = (*pModelCluster_)[k].getKernel()->eval(x1, x2, (*pModelCluster_)[k].getParameters());
775 result += std::sqrt(p1[k] * p2[k]) * cov;
776 }
777 return result;
778 };
779
780 double evalGradient(const Eigen::VectorXd& x1, const Eigen::VectorXd &x2, const Eigen::VectorXd& par, const size_t &i) const {
781 return 0.0;
782 };
783
784 double evalHessian(const Eigen::VectorXd& x1, const Eigen::VectorXd &x2, const Eigen::VectorXd& par, const size_t &i, const size_t &j) const {
785 return 0.0;
786 };
787
788 static std::shared_ptr<Covariance> make(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier) {
789 return std::make_shared<ModelClusterCovariance>(modelCluster, classifier);
790 };
791};
792} // namespace covariance
793
794namespace mean {
813 private:
816 public:
817
818 ModelClusterMean(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier) : pModelCluster_(modelCluster), pClassifier_(classifier) {};
819 double eval(const Eigen::VectorXd& x, const Eigen::VectorXd &par) const {
820 // Preidict the probabilities
821 auto probs = pClassifier_->predictProbabilities(x);
822 double mu = 0.0;
823 for(size_t i = 0; i < pModelCluster_->nClusters(); i++) {
824 mu += probs[i] * (*pModelCluster_)[i].getMean()->eval(x, (*pModelCluster_)[i].getParameters());
825 }
826 return mu;
827
828 };
829 double evalGradient(const Eigen::VectorXd& x, const Eigen::VectorXd &par, const size_t &i) const {
830 return 0.0;
831 };
832 double evalHessian(const Eigen::VectorXd& x, const Eigen::VectorXd &par, const size_t &i, const size_t &j) const {
833 return 0.0;
834 };
835
836 static std::shared_ptr<Mean> make(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier) {
837 return std::make_shared<ModelClusterMean>(modelCluster, classifier);
838 };
839};
840} // namespace mean
841
842} // namespace cmp
845#endif
Manages a clustered set of Gaussian Processes for localized regression.
Definition model_cluster.h:65
void fit(Eigen::Ref< const Eigen::VectorXd > lowerBound, Eigen::Ref< const Eigen::VectorXd > upperBound, cmp::gp::method fitType, nlopt::algorithm algorithm=nlopt::LN_SBPLX, double tol=1e-3, std::shared_ptr< cmp::prior::Prior > prior=nullptr, std::vector< bool > logScale={})
Definition model_cluster.h:179
double computeScore(size_t clusterIndex, size_t globalIndex) const
Definition model_cluster.h:352
std::shared_ptr< mean::Mean > mean_
Mean function shared across clusters.
Definition model_cluster.h:96
size_t dim() const
Definition model_cluster.h:198
std::vector< size_t > clusterSize_
Number of points assigned to each cluster.
Definition model_cluster.h:89
void condition(const Eigen::Ref< const Eigen::MatrixXd > &xObs, const Eigen::Ref< const Eigen::VectorXd > &yObs, const Eigen::Ref< const Eigen::VectorXs > &labels)
Conditions the model on observations and initial cluster labels.
Definition model_cluster.h:127
double gamma_
Regularization blending parameter gamma.
Definition model_cluster.h:92
ModelCluster()=default
size_t nClusters_
Number of active clusters.
Definition model_cluster.h:73
size_t nObs_
Number of training observations.
Definition model_cluster.h:74
void set(std::shared_ptr< covariance::Covariance > kernel, std::shared_ptr< mean::Mean > mean, Eigen::VectorXd parameters, double nugget, double gamma, unsigned int seed)
Configures parameters, kernel, and random seed for the clustered local GP model.
Definition model_cluster.h:112
Eigen::MatrixXd confusionMatrix(std::vector< std::vector< double > > switchingProbability) const
Definition model_cluster.h:649
void purge(const size_t &clusterIndex)
Definition model_cluster.h:508
std::pair< double, double > predict(const Eigen::VectorXd &xStar, cmp::classifier::Classifier *classifier) const
Definition model_cluster.h:161
void merge(const size_t &clusterIndex1, const size_t &clusterIndex2)
Definition model_cluster.h:682
void updateModel(const std::vector< bool > &affectedClusters)
Definition model_cluster.h:234
std::vector< Eigen::VectorXd > centroids_
Coordinates for each cluster's centroid.
Definition model_cluster.h:85
std::vector< bool > fit_
Cluster fit/convergence status flag vector.
Definition model_cluster.h:84
std::vector< std::pair< size_t, size_t > > switchStep(cmp::classifier::Classifier *classifier, size_t maxAllowedSwitches=10, double minProb=0.1, double T=1.0)
Definition model_cluster.h:358
std::vector< std::pair< size_t, size_t > > deterministicSwitchStep(cmp::classifier::Classifier *cls, const size_t &maxAllowedSwitches, const double &minProb)
Definition model_cluster.h:417
Eigen::MatrixXd xObs_
Training input matrix of observations.
Definition model_cluster.h:70
size_t getClusterSize(size_t i) const
Definition model_cluster.h:210
Eigen::VectorXs labels_
Cluster assignments label vector.
Definition model_cluster.h:78
bool performSwitches(const std::vector< std::pair< size_t, size_t > > &newOwners, size_t minSize)
Definition model_cluster.h:281
size_t nPoints() const
Definition model_cluster.h:194
std::shared_ptr< covariance::Covariance > kernel_
Covariance kernel function shared across clusters.
Definition model_cluster.h:95
const Eigen::VectorXd & centroid(size_t i) const
Definition model_cluster.h:218
const Eigen::VectorXs & getLabels() const
Definition model_cluster.h:206
double computeScore(size_t globalIndex) const
Definition model_cluster.h:333
Eigen::VectorXs getIndices(size_t clusterIndex) const
Definition model_cluster.h:222
std::vector< std::vector< double > > computeProbabilities(const double &T, cmp::classifier::Classifier *classifier, const double &minProb=0.1) const
Definition model_cluster.h:564
Eigen::VectorXs localIndexTable_
Local coordinate lookup index mapping.
Definition model_cluster.h:81
size_t getMembership(size_t i) const
Definition model_cluster.h:202
size_t dimX_
Dimension of input features.
Definition model_cluster.h:75
Eigen::VectorXd parameters_
Kernel hyperparameter values.
Definition model_cluster.h:97
std::vector< cmp::gp::GaussianProcess > gps_
Gaussian process models for each cluster.
Definition model_cluster.h:86
double nugget_
Standard noise variance nugget.
Definition model_cluster.h:98
cmp::gp::GaussianProcess & operator[](size_t i)
Definition model_cluster.h:214
size_t nClusters() const
Definition model_cluster.h:175
std::default_random_engine rng_
Pseudo-random number generator.
Definition model_cluster.h:68
Eigen::VectorXd yObs_
Training target response vector.
Definition model_cluster.h:71
Abstract base class for all classifiers.
Definition classifier.h:41
virtual std::vector< double > predictProbabilities(const Eigen::Ref< const Eigen::VectorXd > &x) const =0
Abstract base class for all covariance (kernel) functions.
Definition covariance.h:30
Blended covariance kernel that interpolates local GP kernels using classifier probabilities.
Definition model_cluster.h:731
ModelClusterCovariance(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier)
Definition model_cluster.h:756
void precomputeProbabilities(const Eigen::Ref< const Eigen::MatrixXd > &xObs) const
Definition model_cluster.h:762
cmp::classifier::Classifier * pClassifier_
Pointer to the classifier used for coordinate probability assignment.
Definition model_cluster.h:734
const std::vector< double > & getCachedProbabilities(const Eigen::Ref< const Eigen::VectorXd > &x) const
Definition model_cluster.h:745
double evalGradient(const Eigen::VectorXd &x1, const Eigen::VectorXd &x2, const Eigen::VectorXd &par, const size_t &i) const
Definition model_cluster.h:780
static std::shared_ptr< Covariance > make(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier)
Definition model_cluster.h:788
double evalHessian(const Eigen::VectorXd &x1, const Eigen::VectorXd &x2, const Eigen::VectorXd &par, const size_t &i, const size_t &j) const
Definition model_cluster.h:784
std::unordered_map< std::string, std::vector< double > > probabilityCache_
Thread-local mutable cache to avoid redundant classifier evaluations.
Definition model_cluster.h:735
void clearProbabilityCache() const
Definition model_cluster.h:758
cmp::ModelCluster * pModelCluster_
Pointer to the underlying model cluster manager.
Definition model_cluster.h:733
double eval(const Eigen::VectorXd &x1, const Eigen::VectorXd &x2, const Eigen::VectorXd &par) const
Definition model_cluster.h:769
std::string makeProbabilityCacheKey(const Eigen::Ref< const Eigen::VectorXd > &x) const
Definition model_cluster.h:737
static double logPDF(const double &res, const double &std)
Definition distribution.h:225
This class implements a Gaussian Process (GP) regression model for non-parametric Bayesian regression...
Definition gp.h:79
Abstract base class for Gaussian Process prior mean functions.
Definition mean++.h:25
Blended mean function that interpolates local GP means using classifier probabilities.
Definition model_cluster.h:812
cmp::ModelCluster * pModelCluster_
Pointer to the underlying model cluster manager.
Definition model_cluster.h:814
double evalHessian(const Eigen::VectorXd &x, const Eigen::VectorXd &par, const size_t &i, const size_t &j) const
Definition model_cluster.h:832
static std::shared_ptr< Mean > make(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier)
Definition model_cluster.h:836
ModelClusterMean(cmp::ModelCluster *modelCluster, cmp::classifier::Classifier *classifier)
Definition model_cluster.h:818
cmp::classifier::Classifier * pClassifier_
Pointer to the classifier used for coordinate probability assignment.
Definition model_cluster.h:815
double evalGradient(const Eigen::VectorXd &x, const Eigen::VectorXd &par, const size_t &i) const
Definition model_cluster.h:829
double eval(const Eigen::VectorXd &x, const Eigen::VectorXd &par) const
Definition model_cluster.h:819
Matrix< size_t, Eigen::Dynamic, 1 > VectorXs
Definition cmp_defines.h:20
method
Optimization method for GP hyperparameters.
Definition gp.h:22
Definition classifier.h:17
Derived::PlainObject slice(const Eigen::MatrixBase< Derived > &mat, const Eigen::VectorXs &indices)
Slices a matrix along its rows based on a set of indices.
Definition cmp_defines.h:178