Skip to content

Commit 74b2d30

Browse files
committed
sparse GP uses block diagonal LDLT
1 parent 06ee7c9 commit 74b2d30

File tree

2 files changed

+40
-18
lines changed

2 files changed

+40
-18
lines changed

include/albatross/src/models/sparse_gp.hpp

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
1111
*/
1212

13-
#ifndef INCLUDE_ALBATROSS_MODELS_SPARSE_GP_H_
14-
#define INCLUDE_ALBATROSS_MODELS_SPARSE_GP_H_
13+
#ifndef ALBATROSS_SRC_MODELS_SPARSE_GP_HPP
14+
#define ALBATROSS_SRC_MODELS_SPARSE_GP_HPP
1515

1616
namespace albatross {
1717

@@ -74,7 +74,7 @@ template <typename FeatureType> struct SparseGPFit {};
7474
template <typename FeatureType> struct Fit<SparseGPFit<FeatureType>> {
7575

7676
std::vector<FeatureType> train_features;
77-
Eigen::SerializableLDLT train_covariance;
77+
BlockDiagonalLDLT train_covariance;
7878
Eigen::MatrixXd sigma_R;
7979
Eigen::Matrix<int, Eigen::Dynamic, 1> permutation_indices;
8080
Eigen::VectorXd information;
@@ -83,7 +83,7 @@ template <typename FeatureType> struct Fit<SparseGPFit<FeatureType>> {
8383
Fit(){};
8484

8585
Fit(const std::vector<FeatureType> &features_,
86-
const Eigen::SerializableLDLT &train_covariance_,
86+
const BlockDiagonalLDLT &train_covariance_,
8787
const Eigen::MatrixXd sigma_R_,
8888
const Eigen::Matrix<int, Eigen::Dynamic, 1> permutation_indices_,
8989
const Eigen::VectorXd &information_, Eigen::Index numerical_rank_)
@@ -351,12 +351,12 @@ class SparseGaussianProcessRegression
351351
// Sigma = (B^T B)^-1
352352
//
353353
Eigen::ColPivHouseholderQR<Eigen::MatrixXd>
354-
compute_sigma_qr(const Eigen::SerializableLDLT &K_uu_ldlt,
354+
compute_sigma_qr(const BlockDiagonalLDLT &K_uu_ldlt,
355355
const BlockDiagonalLDLT &A_ldlt,
356356
const Eigen::MatrixXd &K_fu) const {
357357
Eigen::MatrixXd B(A_ldlt.rows() + K_uu_ldlt.rows(), K_uu_ldlt.rows());
358358
B.topRows(A_ldlt.rows()) = A_ldlt.sqrt_solve(K_fu);
359-
B.bottomRows(K_uu_ldlt.rows()) = K_uu_ldlt.sqrt_transpose();
359+
B.bottomRows(K_uu_ldlt.rows()) = K_uu_ldlt.sqrt_transpose().to_dense();
360360
return B.colPivHouseholderQr();
361361
};
362362

@@ -371,7 +371,12 @@ class SparseGaussianProcessRegression
371371
auto u = inducing_point_strategy_(this->covariance_function_, features);
372372
ALBATROSS_ASSERT(!u.empty() && "Empty inducing points!");
373373

374-
const auto K_uu_ldlt = compute_kuu_ldlt(&u);
374+
// Compute K_uu = P_cols.T * D * P_cols
375+
// where D is a block diagonal. Instead of storing the permutation
376+
// we can just use D and reorder the inducing points
377+
const auto structured_K_uu_ldlt = compute_kuu_ldlt(&u);
378+
const auto &K_uu_ldlt = structured_K_uu_ldlt.matrix;
379+
u = structured_K_uu_ldlt.P_cols * u;
375380

376381
BlockDiagonalLDLT A_ldlt;
377382
Eigen::MatrixXd K_fu;
@@ -401,14 +406,23 @@ class SparseGaussianProcessRegression
401406

402407
new_fit.train_features = new_inducing_points;
403408

404-
const Eigen::MatrixXd K_zz =
405-
this->covariance_function_(new_inducing_points, Base::threads_.get());
406-
new_fit.train_covariance = Eigen::SerializableLDLT(K_zz);
409+
auto K_zz = this->covariance_function_(new_fit.train_features,
410+
Base::threads_.get());
411+
K_zz.diagonal() +=
412+
inducing_nugget_.value * Eigen::VectorXd::Ones(K_zz.rows());
413+
const auto block_diag_K_zz = linalg::infer_block_diagonal_matrix(K_zz);
407414

415+
// Store only the inner block diagonal representation and reorder
416+
// the inducing points to match;
417+
new_fit.train_covariance = block_diag_K_zz.matrix.ldlt();
418+
419+
const auto &P = block_diag_K_zz.P_cols;
420+
new_fit.train_features = P * new_fit.train_features;
421+
JointDistribution prediction(P * prediction_.mean,
422+
P * prediction_.covariance * P.transpose());
408423
// We're going to need to take the sqrt of the new covariance which
409424
// could be extremely small, so here we add a small nugget to avoid
410425
// numerical instability
411-
JointDistribution prediction(prediction_);
412426
prediction.covariance.diagonal() += Eigen::VectorXd::Constant(
413427
cast::to_index(prediction.size()), 1, details::DEFAULT_NUGGET);
414428
new_fit.information = new_fit.train_covariance.solve(prediction.mean);
@@ -439,6 +453,7 @@ class SparseGaussianProcessRegression
439453
// We can then compute and store the QR decomposition of B
440454
// as we do in a normal fit.
441455
const Eigen::SerializableLDLT C_ldlt(prediction.covariance);
456+
// think about this, probably a better way:
442457
const Eigen::MatrixXd sigma_inv_sqrt = C_ldlt.sqrt_solve(K_zz);
443458
const auto B_qr = sigma_inv_sqrt.colPivHouseholderQr();
444459

@@ -529,7 +544,12 @@ class SparseGaussianProcessRegression
529544
auto u =
530545
inducing_point_strategy_(this->covariance_function_, dataset.features);
531546

532-
const auto K_uu_ldlt = compute_kuu_ldlt(&u);
547+
// Compute K_uu = P_cols.T * D * P_cols
548+
// where D is a block diagonal. Instead of storing the permutation
549+
// we can just use D and reorder the inducing points
550+
const auto structured_K_uu_ldlt = compute_kuu_ldlt(&u);
551+
const auto &K_uu_ldlt = structured_K_uu_ldlt.matrix;
552+
u = structured_K_uu_ldlt.P_cols * u;
533553

534554
BlockDiagonalLDLT A_ldlt;
535555
Eigen::MatrixXd K_fu;
@@ -599,13 +619,14 @@ class SparseGaussianProcessRegression
599619

600620
private:
601621
template <typename InducingFeatureType>
602-
auto
622+
Structured<BlockDiagonalLDLT>
603623
compute_kuu_ldlt(std::vector<InducingFeatureType> *inducing_features) const {
604624
auto K_uu =
605625
this->covariance_function_(*inducing_features, Base::threads_.get());
606626
K_uu.diagonal() +=
607627
inducing_nugget_.value * Eigen::VectorXd::Ones(K_uu.rows());
608-
return Eigen::SerializableLDLT(K_uu);
628+
const auto block_diag_K_uu = linalg::infer_block_diagonal_matrix(K_uu);
629+
return structured::ldlt(block_diag_K_uu);
609630
}
610631

611632
// This method takes care of a lot of the common book keeping required to
@@ -619,7 +640,7 @@ class SparseGaussianProcessRegression
619640
const std::vector<InducingFeatureType> &inducing_features,
620641
const std::vector<FeatureType> &out_of_order_features,
621642
const MarginalDistribution &out_of_order_targets,
622-
const Eigen::SerializableLDLT &K_uu_ldlt, BlockDiagonalLDLT *A_ldlt,
643+
const BlockDiagonalLDLT &K_uu_ldlt, BlockDiagonalLDLT *A_ldlt,
623644
Eigen::MatrixXd *K_fu, Eigen::VectorXd *y) const {
624645

625646
ALBATROSS_ASSERT(A_ldlt != nullptr);
@@ -660,7 +681,8 @@ class SparseGaussianProcessRegression
660681
// Q_ff = K_fu K_uu^-1 K_uf
661682
// = K_fu K_uu^-T/2 K_uu^-1/2 K_uf
662683
// = P^T P
663-
const Eigen::MatrixXd P = K_uu_ldlt.sqrt_solve(K_fu->transpose());
684+
const Eigen::MatrixXd P =
685+
K_uu_ldlt.sqrt_solve(K_fu->transpose(), Base::threads_.get());
664686

665687
// We only need the diagonal blocks of Q_ff to get A
666688
BlockDiagonal Q_ff_diag;
@@ -756,4 +778,4 @@ auto sparse_gp_from_covariance(CovFunc covariance_function,
756778

757779
} // namespace albatross
758780

759-
#endif /* INCLUDE_ALBATROSS_MODELS_SPARSE_GP_H_ */
781+
#endif // ALBATROSS_SRC_MODELS_SPARSE_GP_HPP

tests/test_sparse_gp.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ TYPED_TEST(SparseGaussianProcessTest, test_rebase_inducing_points) {
355355
auto high_res_pred =
356356
high_res_fit.predict_with_measurement_noise(test_features).joint();
357357
// Increasing the inducing points shouldn't change much
358-
EXPECT_LT((high_res_pred.mean - full_pred.mean).norm(), 1e-6);
358+
EXPECT_LT((high_res_pred.mean - full_pred.mean).norm(), 1e-5);
359359

360360
const auto low_high_res_fit =
361361
rebase_inducing_points(low_res_fit, high_res_features);

0 commit comments

Comments
 (0)