From a0464bc41a923a18aadb3405676bdfa07f671411 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Wed, 27 Feb 2019 15:04:21 +0100 Subject: [PATCH 01/13] add skeleton for fit_intercept with sparse_cg --- sklearn/linear_model/ridge.py | 25 +++++++++++++++++------- sklearn/linear_model/tests/test_ridge.py | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index eed636622dcdc..5459c4480c3ab 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -33,10 +33,11 @@ from ..exceptions import ConvergenceWarning -def _solve_sparse_cg(X, y, alpha, max_iter=None, tol=1e-3, verbose=0): +def _solve_sparse_cg(X, y, alpha, max_iter=None, tol=1e-3, verbose=0, + fit_intercept=False): n_samples, n_features = X.shape X1 = sp_linalg.aslinearoperator(X) - coefs = np.empty((y.shape[1], n_features), dtype=X.dtype) + coefs = np.empty((y.shape[1], n_features + int(fit_intercept)), dtype=X.dtype) if n_features > n_samples: def create_mv(curr_alpha): @@ -64,7 +65,7 @@ def _mv(x): except TypeError: # old scipy coef, info = sp_linalg.cg(C, y_column, tol=tol) - coefs[i] = X1.rmatvec(coef) + coefs[i, :n_features] = X1.rmatvec(coef) else: # linear ridge # w = inv(X^t X + alpha*Id) * X.T y @@ -73,11 +74,11 @@ def _mv(x): (n_features, n_features), matvec=mv, dtype=X.dtype) # FIXME atol try: - coefs[i], info = sp_linalg.cg(C, y_column, maxiter=max_iter, + coefs[i, :n_features], info = sp_linalg.cg(C, y_column, maxiter=max_iter, tol=tol, atol='legacy') except TypeError: # old scipy - coefs[i], info = sp_linalg.cg(C, y_column, maxiter=max_iter, + coefs[i, :n_features], info = sp_linalg.cg(C, y_column, maxiter=max_iter, tol=tol) if info < 0: @@ -326,7 +327,7 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', ----- This function won't compute the intercept. """ - if return_intercept and sparse.issparse(X) and solver != 'sag': + if return_intercept and sparse.issparse(X) and solver not in ['sag', 'sparse_cg']: if solver != 'auto': warnings.warn("In Ridge, only 'sag' solver can currently fit the " "intercept when X is sparse. Solver has been " @@ -395,7 +396,17 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', n_iter = None if solver == 'sparse_cg': - coef = _solve_sparse_cg(X, y, alpha, max_iter, tol, verbose) + coef_ = _solve_sparse_cg(X, y, alpha, + max_iter=max_iter, + tol=tol, + verbose=verbose, + fit_intercept=return_intercept) + + if return_intercept: + coef = coef_[:, :-1] + intercept = coef_[:, -1] + else: + coef = coef_ elif solver == 'lsqr': coef, n_iter = _solve_lsqr(X, y, alpha, max_iter, tol) diff --git a/sklearn/linear_model/tests/test_ridge.py b/sklearn/linear_model/tests/test_ridge.py index eca4a53f4f507..63e25ef656984 100644 --- a/sklearn/linear_model/tests/test_ridge.py +++ b/sklearn/linear_model/tests/test_ridge.py @@ -817,7 +817,7 @@ def test_ridge_fit_intercept_sparse(): bias=10., random_state=42) X_csr = sp.csr_matrix(X) - for solver in ['saga', 'sag']: + for solver in ['saga', 'sag', 'sparse_cg']: dense = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) sparse = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) dense.fit(X, y) From 7b238a75d6c0bf8cceace3a58a9ef1f9a8284aba Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Thu, 28 Feb 2019 16:13:12 +0100 Subject: [PATCH 02/13] fix sparse_cg solver with fit_intercept=True --- sklearn/linear_model/ridge.py | 75 ++++++++++++++++-------- sklearn/linear_model/tests/test_ridge.py | 3 +- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index 5459c4480c3ab..ca2825afe0242 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -34,10 +34,34 @@ def _solve_sparse_cg(X, y, alpha, max_iter=None, tol=1e-3, verbose=0, - fit_intercept=False): + fit_intercept=False, X_offset=None, X_scale=None): + + def _get_rescaled_operator(X): + + X_offset_scale = X_offset / X_scale + + def matvec(b): + return X.dot(b) - b.dot(X_offset_scale) + + def rmatvec(b): + return X.T.dot(b) - X_offset_scale * np.sum(b) + + X1 = sparse.linalg.LinearOperator(shape=X.shape, + matvec=matvec, + rmatvec=rmatvec) + return X1 + n_samples, n_features = X.shape - X1 = sp_linalg.aslinearoperator(X) - coefs = np.empty((y.shape[1], n_features + int(fit_intercept)), dtype=X.dtype) + + if X_offset is None or X_scale is None: + X1 = sp_linalg.aslinearoperator(X) + else: + X1 = _get_rescaled_operator(X) + + coefs = np.empty((y.shape[1], n_features), dtype=X.dtype) + + dtype = X.dtype + del X if n_features > n_samples: def create_mv(curr_alpha): @@ -58,27 +82,27 @@ def _mv(x): # kernel ridge # w = X.T * inv(X X^t + alpha*Id) y C = sp_linalg.LinearOperator( - (n_samples, n_samples), matvec=mv, dtype=X.dtype) + (n_samples, n_samples), matvec=mv, dtype=dtype) # FIXME atol try: coef, info = sp_linalg.cg(C, y_column, tol=tol, atol='legacy') except TypeError: # old scipy coef, info = sp_linalg.cg(C, y_column, tol=tol) - coefs[i, :n_features] = X1.rmatvec(coef) + coefs[i] = X1.rmatvec(coef) else: # linear ridge # w = inv(X^t X + alpha*Id) * X.T y y_column = X1.rmatvec(y_column) C = sp_linalg.LinearOperator( - (n_features, n_features), matvec=mv, dtype=X.dtype) + (n_features, n_features), matvec=mv, dtype=dtype) # FIXME atol try: - coefs[i, :n_features], info = sp_linalg.cg(C, y_column, maxiter=max_iter, + coefs[i], info = sp_linalg.cg(C, y_column, maxiter=max_iter, tol=tol, atol='legacy') except TypeError: # old scipy - coefs[i, :n_features], info = sp_linalg.cg(C, y_column, maxiter=max_iter, + coefs[i], info = sp_linalg.cg(C, y_column, maxiter=max_iter, tol=tol) if info < 0: @@ -207,7 +231,8 @@ def _solve_svd(X, y, alpha): def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', max_iter=None, tol=1e-3, verbose=0, random_state=None, - return_n_iter=False, return_intercept=False): + return_n_iter=False, return_intercept=False, + X_scale=None, X_offset=None): """Solve the ridge equation by the method of normal equations. Read more in the :ref:`User Guide `. @@ -329,7 +354,7 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', """ if return_intercept and sparse.issparse(X) and solver not in ['sag', 'sparse_cg']: if solver != 'auto': - warnings.warn("In Ridge, only 'sag' solver can currently fit the " + warnings.warn("In Ridge, only 'sag' and 'sparse_cg' solvers can currently fit the " "intercept when X is sparse. Solver has been " "automatically changed into 'sag'.") solver = 'sag' @@ -396,17 +421,13 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', n_iter = None if solver == 'sparse_cg': - coef_ = _solve_sparse_cg(X, y, alpha, + coef = _solve_sparse_cg(X, y, alpha, max_iter=max_iter, tol=tol, verbose=verbose, - fit_intercept=return_intercept) - - if return_intercept: - coef = coef_[:, :-1] - intercept = coef_[:, -1] - else: - coef = coef_ + fit_intercept=return_intercept, + X_offset=X_offset, + X_scale=X_scale) elif solver == 'lsqr': coef, n_iter = _solve_lsqr(X, y, alpha, max_iter, tol) @@ -503,24 +524,32 @@ def fit(self, X, y, sample_weight=None): np.atleast_1d(sample_weight).ndim > 1): raise ValueError("Sample weights must be 1D array or scalar") + # when X is sparse we only remove offset from y X, y, X_offset, y_offset, X_scale = self._preprocess_data( X, y, self.fit_intercept, self.normalize, self.copy_X, - sample_weight=sample_weight) + sample_weight=sample_weight, return_mean=True) # temporary fix for fitting the intercept with sparse data using 'sag' - if sparse.issparse(X) and self.fit_intercept: - self.coef_, self.n_iter_, self.intercept_ = ridge_regression( + if sparse.issparse(X) and self.fit_intercept and self.solver in ['sag', 'saga']: + self.coef_, self.n_iter_, intercept = ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, random_state=self.random_state, return_n_iter=True, return_intercept=True) - self.intercept_ += y_offset + # add the offset which was subtracted by _preprocess_data + self.intercept_ = intercept + y_offset else: + if sparse.issparse(X): + params = {'X_offset': X_offset, + 'X_scale': X_scale} + else: + params = {} self.coef_, self.n_iter_ = ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, random_state=self.random_state, return_n_iter=True, - return_intercept=False) + return_intercept=False, **params) + self._set_intercept(X_offset, y_offset, X_scale) return self diff --git a/sklearn/linear_model/tests/test_ridge.py b/sklearn/linear_model/tests/test_ridge.py index 63e25ef656984..21dbccc168994 100644 --- a/sklearn/linear_model/tests/test_ridge.py +++ b/sklearn/linear_model/tests/test_ridge.py @@ -815,6 +815,7 @@ def test_n_iter(): def test_ridge_fit_intercept_sparse(): X, y = make_regression(n_samples=1000, n_features=2, n_informative=2, bias=10., random_state=42) + X_csr = sp.csr_matrix(X) for solver in ['saga', 'sag', 'sparse_cg']: @@ -822,8 +823,8 @@ def test_ridge_fit_intercept_sparse(): sparse = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) dense.fit(X, y) sparse.fit(X_csr, y) - assert_almost_equal(dense.intercept_, sparse.intercept_) assert_array_almost_equal(dense.coef_, sparse.coef_) + assert_almost_equal(dense.intercept_, sparse.intercept_) # test the solver switch and the corresponding warning sparse = Ridge(alpha=1., tol=1.e-15, solver='lsqr', fit_intercept=True) From f5b39482489ee02af806ec732e820f57ec95e535 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Thu, 28 Feb 2019 16:30:42 +0100 Subject: [PATCH 03/13] fix test --- sklearn/linear_model/ridge.py | 14 ++++++-------- sklearn/linear_model/tests/test_ridge.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index ca2825afe0242..38c332e5d0fab 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -60,9 +60,6 @@ def rmatvec(b): coefs = np.empty((y.shape[1], n_features), dtype=X.dtype) - dtype = X.dtype - del X - if n_features > n_samples: def create_mv(curr_alpha): def _mv(x): @@ -82,7 +79,7 @@ def _mv(x): # kernel ridge # w = X.T * inv(X X^t + alpha*Id) y C = sp_linalg.LinearOperator( - (n_samples, n_samples), matvec=mv, dtype=dtype) + (n_samples, n_samples), matvec=mv, dtype=X.dtype) # FIXME atol try: coef, info = sp_linalg.cg(C, y_column, tol=tol, atol='legacy') @@ -95,7 +92,7 @@ def _mv(x): # w = inv(X^t X + alpha*Id) * X.T y y_column = X1.rmatvec(y_column) C = sp_linalg.LinearOperator( - (n_features, n_features), matvec=mv, dtype=dtype) + (n_features, n_features), matvec=mv, dtype=X.dtype) # FIXME atol try: coefs[i], info = sp_linalg.cg(C, y_column, maxiter=max_iter, @@ -530,14 +527,15 @@ def fit(self, X, y, sample_weight=None): sample_weight=sample_weight, return_mean=True) # temporary fix for fitting the intercept with sparse data using 'sag' - if sparse.issparse(X) and self.fit_intercept and self.solver in ['sag', 'saga']: - self.coef_, self.n_iter_, intercept = ridge_regression( + if (sparse.issparse(X) and self.fit_intercept and + self.solver != 'sparse_cg'): + self.coef_, self.n_iter_, self.intercept_ = ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, random_state=self.random_state, return_n_iter=True, return_intercept=True) # add the offset which was subtracted by _preprocess_data - self.intercept_ = intercept + y_offset + self.intercept_ += y_offset else: if sparse.issparse(X): params = {'X_offset': X_offset, diff --git a/sklearn/linear_model/tests/test_ridge.py b/sklearn/linear_model/tests/test_ridge.py index 21dbccc168994..9dfc56112cee2 100644 --- a/sklearn/linear_model/tests/test_ridge.py +++ b/sklearn/linear_model/tests/test_ridge.py @@ -823,8 +823,8 @@ def test_ridge_fit_intercept_sparse(): sparse = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) dense.fit(X, y) sparse.fit(X_csr, y) - assert_array_almost_equal(dense.coef_, sparse.coef_) assert_almost_equal(dense.intercept_, sparse.intercept_) + assert_array_almost_equal(dense.coef_, sparse.coef_) # test the solver switch and the corresponding warning sparse = Ridge(alpha=1., tol=1.e-15, solver='lsqr', fit_intercept=True) From 750cb317d97005428f9f69551bd1ba0c48e2342d Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Thu, 28 Feb 2019 17:00:55 +0100 Subject: [PATCH 04/13] linting --- sklearn/linear_model/ridge.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index 38c332e5d0fab..f7c76c8c3366f 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -349,11 +349,12 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', ----- This function won't compute the intercept. """ - if return_intercept and sparse.issparse(X) and solver not in ['sag', 'sparse_cg']: + if (return_intercept and sparse.issparse(X) and + solver not in ['sag', 'sparse_cg']): if solver != 'auto': - warnings.warn("In Ridge, only 'sag' and 'sparse_cg' solvers can currently fit the " - "intercept when X is sparse. Solver has been " - "automatically changed into 'sag'.") + warnings.warn("In Ridge, only 'sag' and 'sparse_cg' solvers " + "can currently fit the intercept when X is sparse." + " Solver has been automatically changed into 'sag'.") solver = 'sag' _dtype = [np.float64, np.float32] @@ -419,12 +420,12 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', n_iter = None if solver == 'sparse_cg': coef = _solve_sparse_cg(X, y, alpha, - max_iter=max_iter, - tol=tol, - verbose=verbose, - fit_intercept=return_intercept, - X_offset=X_offset, - X_scale=X_scale) + max_iter=max_iter, + tol=tol, + verbose=verbose, + fit_intercept=return_intercept, + X_offset=X_offset, + X_scale=X_scale) elif solver == 'lsqr': coef, n_iter = _solve_lsqr(X, y, alpha, max_iter, tol) @@ -528,7 +529,7 @@ def fit(self, X, y, sample_weight=None): # temporary fix for fitting the intercept with sparse data using 'sag' if (sparse.issparse(X) and self.fit_intercept and - self.solver != 'sparse_cg'): + self.solver != 'sparse_cg'): self.coef_, self.n_iter_, self.intercept_ = ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, From e21192b7606a34de13ba2403e410bfbf4c4daffc Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Thu, 28 Feb 2019 17:01:07 +0100 Subject: [PATCH 05/13] add what's new entry --- doc/whats_new/v0.21.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/whats_new/v0.21.rst b/doc/whats_new/v0.21.rst index 6f504a721ec75..26a9d47c2cef8 100644 --- a/doc/whats_new/v0.21.rst +++ b/doc/whats_new/v0.21.rst @@ -225,11 +225,15 @@ Support for Python 3.4 and below has been officially dropped. was not returning the same coeffecients and intercepts with ``fit_intercept=True`` in sparse and dense case. :issue:`13279` by `Alexandre Gramfort`_ - + - |API| :func:`linear_model.logistic_regression_path` is deprecated in version 0.21 and will be removed in version 0.23. :issue:`12821` by :user:`Nicolas Hug `. +- |Enhancement| `sparse_cg` solver in :class:`linear_model.ridge.Ridge` + now supports fitting the intercept (i.e. ``fit_intercept=True``) when + inputs are sparse . :issue:`13336` by :user:`Bartosz Telenczuk ` + :mod:`sklearn.manifold` ............................ From cc67921a19a66a151d0335519b91ea48e9a1ea1a Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Thu, 28 Feb 2019 17:54:52 +0100 Subject: [PATCH 06/13] remove X_scale and X_offset from public interface of ridge_regression --- sklearn/linear_model/ridge.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index f7c76c8c3366f..69a216ffcd360 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -228,8 +228,7 @@ def _solve_svd(X, y, alpha): def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', max_iter=None, tol=1e-3, verbose=0, random_state=None, - return_n_iter=False, return_intercept=False, - X_scale=None, X_offset=None): + return_n_iter=False, return_intercept=False): """Solve the ridge equation by the method of normal equations. Read more in the :ref:`User Guide `. @@ -349,6 +348,25 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', ----- This function won't compute the intercept. """ + + return _ridge_regression(X, y, alpha, + sample_weight=sample_weight, + solver=solver, + max_iter=max_iter, + tol=tol, + verbose=verbose, + random_state=random_state, + return_n_iter=return_n_iter, + return_intercept=return_intercept, + X_scale=None, + X_offset=None) + + +def _ridge_regression(X, y, alpha, sample_weight=None, solver='auto', + max_iter=None, tol=1e-3, verbose=0, random_state=None, + return_n_iter=False, return_intercept=False, + X_scale=None, X_offset=None): + if (return_intercept and sparse.issparse(X) and solver not in ['sag', 'sparse_cg']): if solver != 'auto': @@ -530,7 +548,7 @@ def fit(self, X, y, sample_weight=None): # temporary fix for fitting the intercept with sparse data using 'sag' if (sparse.issparse(X) and self.fit_intercept and self.solver != 'sparse_cg'): - self.coef_, self.n_iter_, self.intercept_ = ridge_regression( + self.coef_, self.n_iter_, self.intercept_ = _ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, random_state=self.random_state, return_n_iter=True, @@ -543,7 +561,7 @@ def fit(self, X, y, sample_weight=None): 'X_scale': X_scale} else: params = {} - self.coef_, self.n_iter_ = ridge_regression( + self.coef_, self.n_iter_ = _ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, random_state=self.random_state, return_n_iter=True, From 454e685e2929adbb4f545ef56831c7f5d71543e7 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 10:13:13 +0100 Subject: [PATCH 07/13] reformat if clause --- sklearn/linear_model/ridge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index 69a216ffcd360..7872323480b9d 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -557,10 +557,10 @@ def fit(self, X, y, sample_weight=None): self.intercept_ += y_offset else: if sparse.issparse(X): - params = {'X_offset': X_offset, - 'X_scale': X_scale} + params = {'X_offset': X_offset, 'X_scale': X_scale} else: params = {} + self.coef_, self.n_iter_ = _ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, From d50c7d6778d5ad73badabbb64d1ee047fd8f3221 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 11:14:13 +0100 Subject: [PATCH 08/13] fixed linting issues --- sklearn/linear_model/ridge.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index 7872323480b9d..605d916314f17 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -363,9 +363,9 @@ def ridge_regression(X, y, alpha, sample_weight=None, solver='auto', def _ridge_regression(X, y, alpha, sample_weight=None, solver='auto', - max_iter=None, tol=1e-3, verbose=0, random_state=None, - return_n_iter=False, return_intercept=False, - X_scale=None, X_offset=None): + max_iter=None, tol=1e-3, verbose=0, random_state=None, + return_n_iter=False, return_intercept=False, + X_scale=None, X_offset=None): if (return_intercept and sparse.issparse(X) and solver not in ['sag', 'sparse_cg']): @@ -560,7 +560,7 @@ def fit(self, X, y, sample_weight=None): params = {'X_offset': X_offset, 'X_scale': X_scale} else: params = {} - + self.coef_, self.n_iter_ = _ridge_regression( X, y, alpha=self.alpha, sample_weight=sample_weight, max_iter=self.max_iter, tol=self.tol, solver=self.solver, From b7360c77a730545535d8a4845f99aa82941697be Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 13:50:36 +0100 Subject: [PATCH 09/13] add comments on about the conditions of different code branches --- sklearn/linear_model/ridge.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index 605d916314f17..6bb89669cf869 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -557,8 +557,10 @@ def fit(self, X, y, sample_weight=None): self.intercept_ += y_offset else: if sparse.issparse(X): + # required to fit intercept with sparse_cg solver params = {'X_offset': X_offset, 'X_scale': X_scale} else: + # for dense matrices or when intercept is set to 0 params = {} self.coef_, self.n_iter_ = _ridge_regression( From c3c6e8fd09088afea05f0720abe6d7959c005e2f Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 14:20:48 +0100 Subject: [PATCH 10/13] update warning --- sklearn/linear_model/ridge.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index 6bb89669cf869..b110f7d605dc1 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -367,12 +367,11 @@ def _ridge_regression(X, y, alpha, sample_weight=None, solver='auto', return_n_iter=False, return_intercept=False, X_scale=None, X_offset=None): - if (return_intercept and sparse.issparse(X) and - solver not in ['sag', 'sparse_cg']): + if return_intercept and sparse.issparse(X) and solver != 'sag': if solver != 'auto': - warnings.warn("In Ridge, only 'sag' and 'sparse_cg' solvers " - "can currently fit the intercept when X is sparse." - " Solver has been automatically changed into 'sag'.") + warnings.warn("In Ridge, only 'sag' solver can currently fit the " + "intercept when X is sparse. Solver has been " + "automatically changed into 'sag'.") solver = 'sag' _dtype = [np.float64, np.float32] From 5a99ae222ed6d4ce45cdcc01649e58a854b31b99 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 14:25:04 +0100 Subject: [PATCH 11/13] remove whitespace --- doc/whats_new/v0.21.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats_new/v0.21.rst b/doc/whats_new/v0.21.rst index 26a9d47c2cef8..e3e3ec9f88816 100644 --- a/doc/whats_new/v0.21.rst +++ b/doc/whats_new/v0.21.rst @@ -225,7 +225,7 @@ Support for Python 3.4 and below has been officially dropped. was not returning the same coeffecients and intercepts with ``fit_intercept=True`` in sparse and dense case. :issue:`13279` by `Alexandre Gramfort`_ - + - |API| :func:`linear_model.logistic_regression_path` is deprecated in version 0.21 and will be removed in version 0.23. :issue:`12821` by :user:`Nicolas Hug `. From 4fd28ae307a933f9ea0bea4ab19d925530e59f72 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 14:50:46 +0100 Subject: [PATCH 12/13] add extra checks in the test of ridge with fit_intercept --- sklearn/linear_model/tests/test_ridge.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sklearn/linear_model/tests/test_ridge.py b/sklearn/linear_model/tests/test_ridge.py index 9dfc56112cee2..a5ee524e8c557 100644 --- a/sklearn/linear_model/tests/test_ridge.py +++ b/sklearn/linear_model/tests/test_ridge.py @@ -818,19 +818,22 @@ def test_ridge_fit_intercept_sparse(): X_csr = sp.csr_matrix(X) - for solver in ['saga', 'sag', 'sparse_cg']: + for solver in ['sag', 'sparse_cg']: dense = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) sparse = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) dense.fit(X, y) - sparse.fit(X_csr, y) + with pytest.warns(None) as record: + sparse.fit(X_csr, y) + assert len(record) == 0 assert_almost_equal(dense.intercept_, sparse.intercept_) assert_array_almost_equal(dense.coef_, sparse.coef_) # test the solver switch and the corresponding warning - sparse = Ridge(alpha=1., tol=1.e-15, solver='lsqr', fit_intercept=True) - assert_warns(UserWarning, sparse.fit, X_csr, y) - assert_almost_equal(dense.intercept_, sparse.intercept_) - assert_array_almost_equal(dense.coef_, sparse.coef_) + for solver in ['saga', 'lsqr']: + sparse = Ridge(alpha=1., tol=1.e-15, solver=solver, fit_intercept=True) + assert_warns(UserWarning, sparse.fit, X_csr, y) + assert_almost_equal(dense.intercept_, sparse.intercept_) + assert_array_almost_equal(dense.coef_, sparse.coef_) def test_errors_and_values_helper(): From e0183c608b38562f4b2ba4c89e4c7c10828c72d3 Mon Sep 17 00:00:00 2001 From: Bartosz Telenczuk Date: Fri, 1 Mar 2019 16:04:14 +0100 Subject: [PATCH 13/13] remove unused argument --- sklearn/linear_model/ridge.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sklearn/linear_model/ridge.py b/sklearn/linear_model/ridge.py index b110f7d605dc1..e240db3f1cb06 100644 --- a/sklearn/linear_model/ridge.py +++ b/sklearn/linear_model/ridge.py @@ -34,7 +34,7 @@ def _solve_sparse_cg(X, y, alpha, max_iter=None, tol=1e-3, verbose=0, - fit_intercept=False, X_offset=None, X_scale=None): + X_offset=None, X_scale=None): def _get_rescaled_operator(X): @@ -440,7 +440,6 @@ def _ridge_regression(X, y, alpha, sample_weight=None, solver='auto', max_iter=max_iter, tol=tol, verbose=verbose, - fit_intercept=return_intercept, X_offset=X_offset, X_scale=X_scale)