11#include <unordered_set>
12#include <unordered_map>
68 std::default_random_engine
rng_;
86 std::vector<cmp::gp::GaussianProcess>
gps_;
95 std::shared_ptr<covariance::Covariance>
kernel_;
96 std::shared_ptr<mean::Mean>
mean_;
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) {
127 void condition(
const Eigen::Ref<const Eigen::MatrixXd> &xObs,
const Eigen::Ref<const Eigen::VectorXd> &yObs,
const Eigen::Ref<const Eigen::VectorXs> &labels) {
137 for(
size_t i = 0; i <
nObs_; 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;
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 = {}) {
182 for(
size_t i = 0; i <
nClusters(); i++) {
188 gps_[i].fit(xObsi, yObsi, lowerBound, upperBound, fitType, algorithm, tol,
true,
false, prior, logScale);
225 for(
size_t j = 0; j <
nObs_; j++) {
226 if(
labels_[j] == clusterIndex) {
227 indices[counter] = j;
240 if(!affectedClusters[i]) {
251 for(
size_t j = 0; j <
nObs_; j++) {
281 bool performSwitches(
const std::vector<std::pair<size_t, size_t>> &newOwners,
size_t minSize) {
284 std::vector<bool> affectedClusters(
nClusters_,
false);
287 for(
size_t i = 0; i < newOwners.size(); i++) {
290 size_t globalIndex = newOwners[i].first;
293 size_t newOwner = newOwners[i].second;
294 size_t oldOwner =
labels_[globalIndex];
297 affectedClusters[oldOwner] =
true;
298 affectedClusters[newOwner] =
true;
301 labels_[globalIndex] = newOwner;
308 size_t numPurges = 0;
309 bool finished =
false;
322 return (numPurges > 0) || (newOwners.size() > 0);
336 size_t owner =
labels_[globalIndex];
340 auto [mean, var] =
gps_[owner].predictLOO(localIndex);
354 auto [mean, var] =
gps_[clusterIndex].predict(
xObs_.row(globalIndex).transpose());
365 std::vector<double> switchingProbabilities(
nObs_, 0.0);
366 for(
size_t i = 0; i <
nObs_; i++) {
368 double sumProb = 0.0;
371 sumProb += probabilities[i][j];
374 switchingProbabilities[i] = sumProb;
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);
384 std::vector<std::pair<size_t, size_t>> switches;
385 switches.reserve(maxAllowedSwitches);
388 for(
size_t s = 0; s < maxAllowedSwitches && !activeIndices.empty(); ++s) {
391 std::vector<double> weights;
392 weights.reserve(activeIndices.size());
393 for(
auto idx : activeIndices)
394 weights.push_back(switchingProbabilities[idx]);
396 std::discrete_distribution<size_t> dist(weights.begin(), weights.end());
397 size_t sampledPos = dist(
rng_);
398 size_t globalIndex = activeIndices[sampledPos];
401 activeIndices[sampledPos] = activeIndices.back();
402 activeIndices.pop_back();
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_);
410 if(newOwner != owner)
411 switches.emplace_back(globalIndex, newOwner);
420 std::vector<std::pair<double, std::pair<size_t, size_t>>> allSwitches;
421 allSwitches.reserve(
nObs_);
424 std::vector<std::vector<double>> classifierProbabilities(
nObs_);
425 for(
size_t i = 0; i <
nObs_; i++) {
429 for(
size_t i = 0; i <
nObs_; i++) {
432 const std::vector<double> &prob = classifierProbabilities[i];
438 bool hasEligibleAlternative =
false;
440 if(c != owner && prob[c] > minProb) {
441 hasEligibleAlternative =
true;
445 if(!hasEligibleAlternative) {
453 double startingScore = 0.0;
456 std::pair<double, size_t> bestCluster{startingScore, owner};
460 }
else if(prob[c] <= minProb) {
464 double deltaScore =
computeScore(c, i) - ownerRemovalDelta;
467 if(deltaScore > bestCluster.first) {
468 bestCluster.first = deltaScore;
469 bestCluster.second = c;
476 if(bestCluster.second == owner) {
480 allSwitches.push_back({bestCluster.first, {i, bestCluster.second}});
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;
492 size_t nSwitches = std::min(maxAllowedSwitches, allSwitches.size());
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;
508 void purge(
const size_t &clusterIndex) {
510 std::vector<bool> affectedClusters(
nClusters_,
false);
511 std::cout <<
"Purging cluster " << clusterIndex <<
" with size " <<
getClusterSize(clusterIndex) << std::endl;
514 for(
size_t i = 0; i <
nObs_; i++) {
517 if(
labels_[i] == clusterIndex) {
520 std::pair<size_t, double> chosenCluster = std::make_pair(0, std::numeric_limits<double>::infinity());
525 if(j == clusterIndex) {
530 double centroidDistance = (
xObs_.row(i).transpose() -
centroids_[j]).squaredNorm();
533 if(centroidDistance < chosenCluster.second) {
534 chosenCluster = std::make_pair(j, centroidDistance);
539 labels_[i] = chosenCluster.first;
540 affectedClusters[chosenCluster.first] =
true;
545 gps_.erase(
gps_.begin() + clusterIndex);
546 fit_.erase(
fit_.begin() + clusterIndex);
549 affectedClusters.erase(affectedClusters.begin() + clusterIndex);
554 for(
size_t i = 0; i <
nObs_; i++) {
555 if(
labels_[i] > clusterIndex) {
566 std::vector<std::vector<double>> switchingProbabilities(
nObs_, std::vector<double>(
nClusters_, 0.0));
569 std::vector<std::vector<double>> classifierProbabilities(
nObs_);
570 for(
size_t i = 0; i <
nObs_; i++) {
575 for(
size_t i = 0; i <
nObs_; i++) {
584 std::vector<double> logWeights(
nClusters_, -std::numeric_limits<double>::infinity());
592 if(classifierProbabilities[i][j] <= minProb) {
593 logWeights[j] = -std::numeric_limits<double>::infinity();
596 logWeights[j] = std::log(classifierProbabilities[i][j]);
602 if(classifierProbabilities[i][j] <= minProb) {
603 logWeights[j] = -std::numeric_limits<double>::infinity();
607 const double deltaMove =
computeScore(j, i) - ownerScore;
608 logWeights[j] = std::log(classifierProbabilities[i][j]) + (deltaMove / T);
614 double maxLogWeight = -std::numeric_limits<double>::infinity();
616 if(logWeights[j] > maxLogWeight) {
617 maxLogWeight = logWeights[j];
624 if(logWeights[j] > -std::numeric_limits<double>::infinity()) {
626 switchingProbabilities[i][j] = std::exp(logWeights[j] - maxLogWeight);
627 sum += switchingProbabilities[i][j];
629 switchingProbabilities[i][j] = 0.0;
636 switchingProbabilities[i][j] /= sum;
641 switchingProbabilities[i][j] = classifierProbabilities[i][j];
646 return switchingProbabilities;
649 Eigen::MatrixXd
confusionMatrix(std::vector<std::vector<double>> switchingProbability)
const {
652 confusion_num.setZero();
653 confusion_den.setZero();
655 for(
size_t i = 0; i <
nObs_; i++) {
661 confusion_num(j, k) += std::abs(switchingProbability[i][j] - switchingProbability[i][k]);
662 confusion_den(j, k) += switchingProbability[i][j] + switchingProbability[i][k];
671 if(confusion_den(i, j) != 0) {
672 confusion_num(i, j) /= confusion_den(i, j);
678 return confusion_num;
682 void merge(
const size_t &clusterIndex1,
const size_t &clusterIndex2) {
690 if(clusterIndex1 == clusterIndex2) {
694 std::vector<std::pair<size_t, size_t>> switches;
697 for(
size_t i = 0; i <
nObs_; i++) {
700 if(
labels_[i] == clusterIndex2) {
701 switches.push_back(std::make_pair(i, clusterIndex1));
712namespace covariance {
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));
764 for(
size_t i = 0; i < xObs.rows(); i++) {
769 double eval(
const Eigen::VectorXd& x1,
const Eigen::VectorXd &x2,
const Eigen::VectorXd& par)
const {
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;
780 double evalGradient(
const Eigen::VectorXd& x1,
const Eigen::VectorXd &x2,
const Eigen::VectorXd& par,
const size_t &i)
const {
784 double evalHessian(
const Eigen::VectorXd& x1,
const Eigen::VectorXd &x2,
const Eigen::VectorXd& par,
const size_t &i,
const size_t &j)
const {
789 return std::make_shared<ModelClusterCovariance>(modelCluster, classifier);
819 double eval(
const Eigen::VectorXd& x,
const Eigen::VectorXd &par)
const {
824 mu += probs[i] * (*pModelCluster_)[i].getMean()->eval(x, (*
pModelCluster_)[i].getParameters());
829 double evalGradient(
const Eigen::VectorXd& x,
const Eigen::VectorXd &par,
const size_t &i)
const {
832 double evalHessian(
const Eigen::VectorXd& x,
const Eigen::VectorXd &par,
const size_t &i,
const size_t &j)
const {
837 return std::make_shared<ModelClusterMean>(modelCluster, classifier);
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
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