Skip to content

Commit b90e09d

Browse files
antoinebakerogriseljeremiedbb
authored
FIX Covariance matrix in BayesianRidge (#31094)
Co-authored-by: Olivier Grisel <olivier.grisel@ensta.org> Co-authored-by: Jérémie du Boisberranger <jeremie@probabl.ai>
1 parent 28ec3cf commit b90e09d

File tree

3 files changed

+35
-5
lines changed

3 files changed

+35
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- :class:`linear_model.BayesianRidge` now uses the full SVD to correctly estimate
2+
the posterior covariance matrix `sigma_` when `n_samples < n_features`.
3+
By :user:`Antoine Baker <antoinebaker>`

sklearn/linear_model/_bayes.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,19 @@ def fit(self, X, y, sample_weight=None):
293293
coef_old_ = None
294294

295295
XT_y = np.dot(X.T, y)
296-
U, S, Vh = linalg.svd(X, full_matrices=False)
296+
# Let M, N = n_samples, n_features and K = min(M, N).
297+
# The posterior covariance matrix needs Vh_full: (N, N).
298+
# The full SVD is only required when n_samples < n_features.
299+
# When n_samples < n_features, K=M and full_matrices=True
300+
# U: (M, M), S: M, Vh_full: (N, N), Vh: (M, N)
301+
# When n_samples > n_features, K=N and full_matrices=False
302+
# U: (M, N), S: N, Vh_full: (N, N), Vh: (N, N)
303+
U, S, Vh_full = linalg.svd(X, full_matrices=(n_samples < n_features))
304+
K = len(S)
297305
eigen_vals_ = S**2
306+
eigen_vals_full = np.zeros(n_features, dtype=dtype)
307+
eigen_vals_full[0:K] = eigen_vals_
308+
Vh = Vh_full[0:K, :]
298309

299310
# Convergence loop of the bayesian ridge regression
300311
for iter_ in range(self.max_iter):
@@ -353,11 +364,10 @@ def fit(self, X, y, sample_weight=None):
353364
self.scores_.append(s)
354365
self.scores_ = np.array(self.scores_)
355366

356-
# posterior covariance is given by 1/alpha_ * scaled_sigma_
357-
scaled_sigma_ = np.dot(
358-
Vh.T, Vh / (eigen_vals_ + lambda_ / alpha_)[:, np.newaxis]
367+
# posterior covariance
368+
self.sigma_ = np.dot(
369+
Vh_full.T, Vh_full / (alpha_ * eigen_vals_full + lambda_)[:, np.newaxis]
359370
)
360-
self.sigma_ = (1.0 / alpha_) * scaled_sigma_
361371

362372
self._set_intercept(X_offset_, y_offset_, X_scale_)
363373

sklearn/linear_model/tests/test_bayes.py

+17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sklearn.utils import check_random_state
1212
from sklearn.utils._testing import (
1313
_convert_container,
14+
assert_allclose,
1415
assert_almost_equal,
1516
assert_array_almost_equal,
1617
assert_array_less,
@@ -94,6 +95,22 @@ def test_bayesian_ridge_parameter():
9495
assert_almost_equal(rr_model.intercept_, br_model.intercept_)
9596

9697

98+
@pytest.mark.parametrize("n_samples, n_features", [(10, 20), (20, 10)])
99+
def test_bayesian_covariance_matrix(n_samples, n_features, global_random_seed):
100+
"""Check the posterior covariance matrix sigma_
101+
102+
Non-regression test for https://github.com/scikit-learn/scikit-learn/issues/31093
103+
"""
104+
X, y = datasets.make_regression(
105+
n_samples, n_features, random_state=global_random_seed
106+
)
107+
reg = BayesianRidge(fit_intercept=False).fit(X, y)
108+
covariance_matrix = np.linalg.inv(
109+
reg.lambda_ * np.identity(n_features) + reg.alpha_ * np.dot(X.T, X)
110+
)
111+
assert_allclose(reg.sigma_, covariance_matrix, rtol=1e-6)
112+
113+
97114
def test_bayesian_sample_weights():
98115
# Test correctness of the sample_weights method
99116
X = np.array([[1, 1], [3, 4], [5, 7], [4, 1], [2, 6], [3, 10], [3, 2]])

0 commit comments

Comments
 (0)