| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- import warnings
- import numpy as np
- import pytest
- from scipy import linalg
- from sklearn import datasets, linear_model
- from sklearn.base import clone
- from sklearn.exceptions import ConvergenceWarning
- from sklearn.linear_model import (
- Lars,
- LarsCV,
- LassoLars,
- LassoLarsCV,
- LassoLarsIC,
- lars_path,
- )
- from sklearn.linear_model._least_angle import _lars_path_residues
- from sklearn.model_selection import train_test_split
- from sklearn.pipeline import make_pipeline
- from sklearn.preprocessing import StandardScaler
- from sklearn.utils import check_random_state
- from sklearn.utils._testing import (
- TempMemmap,
- assert_allclose,
- assert_array_almost_equal,
- ignore_warnings,
- )
- # TODO: use another dataset that has multiple drops
- diabetes = datasets.load_diabetes()
- X, y = diabetes.data, diabetes.target
- G = np.dot(X.T, X)
- Xy = np.dot(X.T, y)
- n_samples = y.size
- # TODO(1.4): 'normalize' to be removed
- filterwarnings_normalize = pytest.mark.filterwarnings(
- "ignore:'normalize' was deprecated"
- )
- # TODO(1.4) 'normalize' to be removed
- @pytest.mark.parametrize(
- "LeastAngleModel", [Lars, LassoLars, LarsCV, LassoLarsCV, LassoLarsIC]
- )
- @pytest.mark.parametrize(
- "normalize, n_warnings", [(True, 1), (False, 1), ("deprecated", 0)]
- )
- def test_assure_warning_when_normalize(LeastAngleModel, normalize, n_warnings):
- # check that we issue a FutureWarning when normalize was set
- rng = check_random_state(0)
- n_samples = 200
- n_features = 2
- X = rng.randn(n_samples, n_features)
- X[X < 0.1] = 0.0
- y = rng.rand(n_samples)
- model = LeastAngleModel(normalize=normalize)
- with warnings.catch_warnings(record=True) as rec:
- warnings.simplefilter("always", FutureWarning)
- model.fit(X, y)
- assert len([w.message for w in rec]) == n_warnings
- def test_simple():
- # Principle of Lars is to keep covariances tied and decreasing
- # also test verbose output
- import sys
- from io import StringIO
- old_stdout = sys.stdout
- try:
- sys.stdout = StringIO()
- _, _, coef_path_ = linear_model.lars_path(X, y, method="lar", verbose=10)
- sys.stdout = old_stdout
- for i, coef_ in enumerate(coef_path_.T):
- res = y - np.dot(X, coef_)
- cov = np.dot(X.T, res)
- C = np.max(abs(cov))
- eps = 1e-3
- ocur = len(cov[C - eps < abs(cov)])
- if i < X.shape[1]:
- assert ocur == i + 1
- else:
- # no more than max_pred variables can go into the active set
- assert ocur == X.shape[1]
- finally:
- sys.stdout = old_stdout
- def test_simple_precomputed():
- # The same, with precomputed Gram matrix
- _, _, coef_path_ = linear_model.lars_path(X, y, Gram=G, method="lar")
- for i, coef_ in enumerate(coef_path_.T):
- res = y - np.dot(X, coef_)
- cov = np.dot(X.T, res)
- C = np.max(abs(cov))
- eps = 1e-3
- ocur = len(cov[C - eps < abs(cov)])
- if i < X.shape[1]:
- assert ocur == i + 1
- else:
- # no more than max_pred variables can go into the active set
- assert ocur == X.shape[1]
- def _assert_same_lars_path_result(output1, output2):
- assert len(output1) == len(output2)
- for o1, o2 in zip(output1, output2):
- assert_allclose(o1, o2)
- @pytest.mark.parametrize("method", ["lar", "lasso"])
- @pytest.mark.parametrize("return_path", [True, False])
- def test_lars_path_gram_equivalent(method, return_path):
- _assert_same_lars_path_result(
- linear_model.lars_path_gram(
- Xy=Xy, Gram=G, n_samples=n_samples, method=method, return_path=return_path
- ),
- linear_model.lars_path(X, y, Gram=G, method=method, return_path=return_path),
- )
- def test_x_none_gram_none_raises_value_error():
- # Test that lars_path with no X and Gram raises exception
- Xy = np.dot(X.T, y)
- with pytest.raises(ValueError):
- linear_model.lars_path(None, y, Gram=None, Xy=Xy)
- def test_all_precomputed():
- # Test that lars_path with precomputed Gram and Xy gives the right answer
- G = np.dot(X.T, X)
- Xy = np.dot(X.T, y)
- for method in "lar", "lasso":
- output = linear_model.lars_path(X, y, method=method)
- output_pre = linear_model.lars_path(X, y, Gram=G, Xy=Xy, method=method)
- for expected, got in zip(output, output_pre):
- assert_array_almost_equal(expected, got)
- # TODO(1.4): 'normalize' to be removed
- @filterwarnings_normalize
- @pytest.mark.filterwarnings("ignore: `rcond` parameter will change")
- # numpy deprecation
- def test_lars_lstsq():
- # Test that Lars gives least square solution at the end
- # of the path
- X1 = 3 * X # use un-normalized dataset
- clf = linear_model.LassoLars(alpha=0.0)
- clf.fit(X1, y)
- coef_lstsq = np.linalg.lstsq(X1, y, rcond=None)[0]
- assert_array_almost_equal(clf.coef_, coef_lstsq)
- @pytest.mark.filterwarnings("ignore:`rcond` parameter will change")
- # numpy deprecation
- def test_lasso_gives_lstsq_solution():
- # Test that Lars Lasso gives least square solution at the end
- # of the path
- _, _, coef_path_ = linear_model.lars_path(X, y, method="lasso")
- coef_lstsq = np.linalg.lstsq(X, y)[0]
- assert_array_almost_equal(coef_lstsq, coef_path_[:, -1])
- def test_collinearity():
- # Check that lars_path is robust to collinearity in input
- X = np.array([[3.0, 3.0, 1.0], [2.0, 2.0, 0.0], [1.0, 1.0, 0]])
- y = np.array([1.0, 0.0, 0])
- rng = np.random.RandomState(0)
- f = ignore_warnings
- _, _, coef_path_ = f(linear_model.lars_path)(X, y, alpha_min=0.01)
- assert not np.isnan(coef_path_).any()
- residual = np.dot(X, coef_path_[:, -1]) - y
- assert (residual**2).sum() < 1.0 # just make sure it's bounded
- n_samples = 10
- X = rng.rand(n_samples, 5)
- y = np.zeros(n_samples)
- _, _, coef_path_ = linear_model.lars_path(
- X,
- y,
- Gram="auto",
- copy_X=False,
- copy_Gram=False,
- alpha_min=0.0,
- method="lasso",
- verbose=0,
- max_iter=500,
- )
- assert_array_almost_equal(coef_path_, np.zeros_like(coef_path_))
- def test_no_path():
- # Test that the ``return_path=False`` option returns the correct output
- alphas_, _, coef_path_ = linear_model.lars_path(X, y, method="lar")
- alpha_, _, coef = linear_model.lars_path(X, y, method="lar", return_path=False)
- assert_array_almost_equal(coef, coef_path_[:, -1])
- assert alpha_ == alphas_[-1]
- def test_no_path_precomputed():
- # Test that the ``return_path=False`` option with Gram remains correct
- alphas_, _, coef_path_ = linear_model.lars_path(X, y, method="lar", Gram=G)
- alpha_, _, coef = linear_model.lars_path(
- X, y, method="lar", Gram=G, return_path=False
- )
- assert_array_almost_equal(coef, coef_path_[:, -1])
- assert alpha_ == alphas_[-1]
- def test_no_path_all_precomputed():
- # Test that the ``return_path=False`` option with Gram and Xy remains
- # correct
- X, y = 3 * diabetes.data, diabetes.target
- G = np.dot(X.T, X)
- Xy = np.dot(X.T, y)
- alphas_, _, coef_path_ = linear_model.lars_path(
- X, y, method="lasso", Xy=Xy, Gram=G, alpha_min=0.9
- )
- alpha_, _, coef = linear_model.lars_path(
- X, y, method="lasso", Gram=G, Xy=Xy, alpha_min=0.9, return_path=False
- )
- assert_array_almost_equal(coef, coef_path_[:, -1])
- assert alpha_ == alphas_[-1]
- @filterwarnings_normalize
- @pytest.mark.parametrize(
- "classifier", [linear_model.Lars, linear_model.LarsCV, linear_model.LassoLarsIC]
- )
- def test_lars_precompute(classifier):
- # Check for different values of precompute
- G = np.dot(X.T, X)
- clf = classifier(precompute=G)
- output_1 = ignore_warnings(clf.fit)(X, y).coef_
- for precompute in [True, False, "auto", None]:
- clf = classifier(precompute=precompute)
- output_2 = clf.fit(X, y).coef_
- assert_array_almost_equal(output_1, output_2, decimal=8)
- def test_singular_matrix():
- # Test when input is a singular matrix
- X1 = np.array([[1, 1.0], [1.0, 1.0]])
- y1 = np.array([1, 1])
- _, _, coef_path = linear_model.lars_path(X1, y1)
- assert_array_almost_equal(coef_path.T, [[0, 0], [1, 0]])
- def test_rank_deficient_design():
- # consistency test that checks that LARS Lasso is handling rank
- # deficient input data (with n_features < rank) in the same way
- # as coordinate descent Lasso
- y = [5, 0, 5]
- for X in ([[5, 0], [0, 5], [10, 10]], [[10, 10, 0], [1e-32, 0, 0], [0, 0, 1]]):
- # To be able to use the coefs to compute the objective function,
- # we need to turn off normalization
- lars = linear_model.LassoLars(0.1)
- coef_lars_ = lars.fit(X, y).coef_
- obj_lars = 1.0 / (2.0 * 3.0) * linalg.norm(
- y - np.dot(X, coef_lars_)
- ) ** 2 + 0.1 * linalg.norm(coef_lars_, 1)
- coord_descent = linear_model.Lasso(0.1, tol=1e-6)
- coef_cd_ = coord_descent.fit(X, y).coef_
- obj_cd = (1.0 / (2.0 * 3.0)) * linalg.norm(
- y - np.dot(X, coef_cd_)
- ) ** 2 + 0.1 * linalg.norm(coef_cd_, 1)
- assert obj_lars < obj_cd * (1.0 + 1e-8)
- def test_lasso_lars_vs_lasso_cd():
- # Test that LassoLars and Lasso using coordinate descent give the
- # same results.
- X = 3 * diabetes.data
- alphas, _, lasso_path = linear_model.lars_path(X, y, method="lasso")
- lasso_cd = linear_model.Lasso(fit_intercept=False, tol=1e-8)
- for c, a in zip(lasso_path.T, alphas):
- if a == 0:
- continue
- lasso_cd.alpha = a
- lasso_cd.fit(X, y)
- error = linalg.norm(c - lasso_cd.coef_)
- assert error < 0.01
- # similar test, with the classifiers
- for alpha in np.linspace(1e-2, 1 - 1e-2, 20):
- clf1 = linear_model.LassoLars(alpha=alpha).fit(X, y)
- clf2 = linear_model.Lasso(alpha=alpha, tol=1e-8).fit(X, y)
- err = linalg.norm(clf1.coef_ - clf2.coef_)
- assert err < 1e-3
- # same test, with normalized data
- X = diabetes.data
- X = X - X.sum(axis=0)
- X /= np.linalg.norm(X, axis=0)
- alphas, _, lasso_path = linear_model.lars_path(X, y, method="lasso")
- lasso_cd = linear_model.Lasso(fit_intercept=False, tol=1e-8)
- for c, a in zip(lasso_path.T, alphas):
- if a == 0:
- continue
- lasso_cd.alpha = a
- lasso_cd.fit(X, y)
- error = linalg.norm(c - lasso_cd.coef_)
- assert error < 0.01
- @filterwarnings_normalize
- def test_lasso_lars_vs_lasso_cd_early_stopping():
- # Test that LassoLars and Lasso using coordinate descent give the
- # same results when early stopping is used.
- # (test : before, in the middle, and in the last part of the path)
- alphas_min = [10, 0.9, 1e-4]
- X = diabetes.data
- for alpha_min in alphas_min:
- alphas, _, lasso_path = linear_model.lars_path(
- X, y, method="lasso", alpha_min=alpha_min
- )
- lasso_cd = linear_model.Lasso(fit_intercept=False, tol=1e-8)
- lasso_cd.alpha = alphas[-1]
- lasso_cd.fit(X, y)
- error = linalg.norm(lasso_path[:, -1] - lasso_cd.coef_)
- assert error < 0.01
- # same test, with normalization
- X = diabetes.data - diabetes.data.sum(axis=0)
- X /= np.linalg.norm(X, axis=0)
- for alpha_min in alphas_min:
- alphas, _, lasso_path = linear_model.lars_path(
- X, y, method="lasso", alpha_min=alpha_min
- )
- lasso_cd = linear_model.Lasso(tol=1e-8)
- lasso_cd.alpha = alphas[-1]
- lasso_cd.fit(X, y)
- error = linalg.norm(lasso_path[:, -1] - lasso_cd.coef_)
- assert error < 0.01
- @filterwarnings_normalize
- def test_lasso_lars_path_length():
- # Test that the path length of the LassoLars is right
- lasso = linear_model.LassoLars()
- lasso.fit(X, y)
- lasso2 = linear_model.LassoLars(alpha=lasso.alphas_[2])
- lasso2.fit(X, y)
- assert_array_almost_equal(lasso.alphas_[:3], lasso2.alphas_)
- # Also check that the sequence of alphas is always decreasing
- assert np.all(np.diff(lasso.alphas_) < 0)
- def test_lasso_lars_vs_lasso_cd_ill_conditioned():
- # Test lasso lars on a very ill-conditioned design, and check that
- # it does not blow up, and stays somewhat close to a solution given
- # by the coordinate descent solver
- # Also test that lasso_path (using lars_path output style) gives
- # the same result as lars_path and previous lasso output style
- # under these conditions.
- rng = np.random.RandomState(42)
- # Generate data
- n, m = 70, 100
- k = 5
- X = rng.randn(n, m)
- w = np.zeros((m, 1))
- i = np.arange(0, m)
- rng.shuffle(i)
- supp = i[:k]
- w[supp] = np.sign(rng.randn(k, 1)) * (rng.rand(k, 1) + 1)
- y = np.dot(X, w)
- sigma = 0.2
- y += sigma * rng.rand(*y.shape)
- y = y.squeeze()
- lars_alphas, _, lars_coef = linear_model.lars_path(X, y, method="lasso")
- _, lasso_coef2, _ = linear_model.lasso_path(X, y, alphas=lars_alphas, tol=1e-6)
- assert_array_almost_equal(lars_coef, lasso_coef2, decimal=1)
- def test_lasso_lars_vs_lasso_cd_ill_conditioned2():
- # Create an ill-conditioned situation in which the LARS has to go
- # far in the path to converge, and check that LARS and coordinate
- # descent give the same answers
- # Note it used to be the case that Lars had to use the drop for good
- # strategy for this but this is no longer the case with the
- # equality_tolerance checks
- X = [[1e20, 1e20, 0], [-1e-32, 0, 0], [1, 1, 1]]
- y = [10, 10, 1]
- alpha = 0.0001
- def objective_function(coef):
- return 1.0 / (2.0 * len(X)) * linalg.norm(
- y - np.dot(X, coef)
- ) ** 2 + alpha * linalg.norm(coef, 1)
- lars = linear_model.LassoLars(alpha=alpha)
- warning_message = "Regressors in active set degenerate."
- with pytest.warns(ConvergenceWarning, match=warning_message):
- lars.fit(X, y)
- lars_coef_ = lars.coef_
- lars_obj = objective_function(lars_coef_)
- coord_descent = linear_model.Lasso(alpha=alpha, tol=1e-4)
- cd_coef_ = coord_descent.fit(X, y).coef_
- cd_obj = objective_function(cd_coef_)
- assert lars_obj < cd_obj * (1.0 + 1e-8)
- @filterwarnings_normalize
- def test_lars_add_features():
- # assure that at least some features get added if necessary
- # test for 6d2b4c
- # Hilbert matrix
- n = 5
- H = 1.0 / (np.arange(1, n + 1) + np.arange(n)[:, np.newaxis])
- clf = linear_model.Lars(fit_intercept=False).fit(H, np.arange(n))
- assert np.all(np.isfinite(clf.coef_))
- @filterwarnings_normalize
- def test_lars_n_nonzero_coefs(verbose=False):
- lars = linear_model.Lars(n_nonzero_coefs=6, verbose=verbose)
- lars.fit(X, y)
- assert len(lars.coef_.nonzero()[0]) == 6
- # The path should be of length 6 + 1 in a Lars going down to 6
- # non-zero coefs
- assert len(lars.alphas_) == 7
- @filterwarnings_normalize
- @ignore_warnings
- def test_multitarget():
- # Assure that estimators receiving multidimensional y do the right thing
- Y = np.vstack([y, y**2]).T
- n_targets = Y.shape[1]
- estimators = [
- linear_model.LassoLars(),
- linear_model.Lars(),
- # regression test for gh-1615
- linear_model.LassoLars(fit_intercept=False),
- linear_model.Lars(fit_intercept=False),
- ]
- for estimator in estimators:
- estimator.fit(X, Y)
- Y_pred = estimator.predict(X)
- alphas, active, coef, path = (
- estimator.alphas_,
- estimator.active_,
- estimator.coef_,
- estimator.coef_path_,
- )
- for k in range(n_targets):
- estimator.fit(X, Y[:, k])
- y_pred = estimator.predict(X)
- assert_array_almost_equal(alphas[k], estimator.alphas_)
- assert_array_almost_equal(active[k], estimator.active_)
- assert_array_almost_equal(coef[k], estimator.coef_)
- assert_array_almost_equal(path[k], estimator.coef_path_)
- assert_array_almost_equal(Y_pred[:, k], y_pred)
- @filterwarnings_normalize
- def test_lars_cv():
- # Test the LassoLarsCV object by checking that the optimal alpha
- # increases as the number of samples increases.
- # This property is not actually guaranteed in general and is just a
- # property of the given dataset, with the given steps chosen.
- old_alpha = 0
- lars_cv = linear_model.LassoLarsCV()
- for length in (400, 200, 100):
- X = diabetes.data[:length]
- y = diabetes.target[:length]
- lars_cv.fit(X, y)
- np.testing.assert_array_less(old_alpha, lars_cv.alpha_)
- old_alpha = lars_cv.alpha_
- assert not hasattr(lars_cv, "n_nonzero_coefs")
- def test_lars_cv_max_iter(recwarn):
- warnings.simplefilter("always")
- with np.errstate(divide="raise", invalid="raise"):
- X = diabetes.data
- y = diabetes.target
- rng = np.random.RandomState(42)
- x = rng.randn(len(y))
- X = diabetes.data
- X = np.c_[X, x, x] # add correlated features
- X = StandardScaler().fit_transform(X)
- lars_cv = linear_model.LassoLarsCV(max_iter=5, cv=5)
- lars_cv.fit(X, y)
- # Check that there is no warning in general and no ConvergenceWarning
- # in particular.
- # Materialize the string representation of the warning to get a more
- # informative error message in case of AssertionError.
- recorded_warnings = [str(w) for w in recwarn]
- assert len(recorded_warnings) == 0
- def test_lasso_lars_ic():
- # Test the LassoLarsIC object by checking that
- # - some good features are selected.
- # - alpha_bic > alpha_aic
- # - n_nonzero_bic < n_nonzero_aic
- lars_bic = linear_model.LassoLarsIC("bic")
- lars_aic = linear_model.LassoLarsIC("aic")
- rng = np.random.RandomState(42)
- X = diabetes.data
- X = np.c_[X, rng.randn(X.shape[0], 5)] # add 5 bad features
- X = StandardScaler().fit_transform(X)
- lars_bic.fit(X, y)
- lars_aic.fit(X, y)
- nonzero_bic = np.where(lars_bic.coef_)[0]
- nonzero_aic = np.where(lars_aic.coef_)[0]
- assert lars_bic.alpha_ > lars_aic.alpha_
- assert len(nonzero_bic) < len(nonzero_aic)
- assert np.max(nonzero_bic) < diabetes.data.shape[1]
- def test_lars_path_readonly_data():
- # When using automated memory mapping on large input, the
- # fold data is in read-only mode
- # This is a non-regression test for:
- # https://github.com/scikit-learn/scikit-learn/issues/4597
- splitted_data = train_test_split(X, y, random_state=42)
- with TempMemmap(splitted_data) as (X_train, X_test, y_train, y_test):
- # The following should not fail despite copy=False
- _lars_path_residues(X_train, y_train, X_test, y_test, copy=False)
- def test_lars_path_positive_constraint():
- # this is the main test for the positive parameter on the lars_path method
- # the estimator classes just make use of this function
- # we do the test on the diabetes dataset
- # ensure that we get negative coefficients when positive=False
- # and all positive when positive=True
- # for method 'lar' (default) and lasso
- err_msg = "Positive constraint not supported for 'lar' coding method."
- with pytest.raises(ValueError, match=err_msg):
- linear_model.lars_path(
- diabetes["data"], diabetes["target"], method="lar", positive=True
- )
- method = "lasso"
- _, _, coefs = linear_model.lars_path(
- X, y, return_path=True, method=method, positive=False
- )
- assert coefs.min() < 0
- _, _, coefs = linear_model.lars_path(
- X, y, return_path=True, method=method, positive=True
- )
- assert coefs.min() >= 0
- # now we gonna test the positive option for all estimator classes
- default_parameter = {"fit_intercept": False}
- estimator_parameter_map = {
- "LassoLars": {"alpha": 0.1},
- "LassoLarsCV": {},
- "LassoLarsIC": {},
- }
- @filterwarnings_normalize
- def test_estimatorclasses_positive_constraint():
- # testing the transmissibility for the positive option of all estimator
- # classes in this same function here
- default_parameter = {"fit_intercept": False}
- estimator_parameter_map = {
- "LassoLars": {"alpha": 0.1},
- "LassoLarsCV": {},
- "LassoLarsIC": {},
- }
- for estname in estimator_parameter_map:
- params = default_parameter.copy()
- params.update(estimator_parameter_map[estname])
- estimator = getattr(linear_model, estname)(positive=False, **params)
- estimator.fit(X, y)
- assert estimator.coef_.min() < 0
- estimator = getattr(linear_model, estname)(positive=True, **params)
- estimator.fit(X, y)
- assert min(estimator.coef_) >= 0
- def test_lasso_lars_vs_lasso_cd_positive():
- # Test that LassoLars and Lasso using coordinate descent give the
- # same results when using the positive option
- # This test is basically a copy of the above with additional positive
- # option. However for the middle part, the comparison of coefficient values
- # for a range of alphas, we had to make an adaptations. See below.
- # not normalized data
- X = 3 * diabetes.data
- alphas, _, lasso_path = linear_model.lars_path(X, y, method="lasso", positive=True)
- lasso_cd = linear_model.Lasso(fit_intercept=False, tol=1e-8, positive=True)
- for c, a in zip(lasso_path.T, alphas):
- if a == 0:
- continue
- lasso_cd.alpha = a
- lasso_cd.fit(X, y)
- error = linalg.norm(c - lasso_cd.coef_)
- assert error < 0.01
- # The range of alphas chosen for coefficient comparison here is restricted
- # as compared with the above test without the positive option. This is due
- # to the circumstance that the Lars-Lasso algorithm does not converge to
- # the least-squares-solution for small alphas, see 'Least Angle Regression'
- # by Efron et al 2004. The coefficients are typically in congruence up to
- # the smallest alpha reached by the Lars-Lasso algorithm and start to
- # diverge thereafter. See
- # https://gist.github.com/michigraber/7e7d7c75eca694c7a6ff
- for alpha in np.linspace(6e-1, 1 - 1e-2, 20):
- clf1 = linear_model.LassoLars(
- fit_intercept=False, alpha=alpha, positive=True
- ).fit(X, y)
- clf2 = linear_model.Lasso(
- fit_intercept=False, alpha=alpha, tol=1e-8, positive=True
- ).fit(X, y)
- err = linalg.norm(clf1.coef_ - clf2.coef_)
- assert err < 1e-3
- # normalized data
- X = diabetes.data - diabetes.data.sum(axis=0)
- X /= np.linalg.norm(X, axis=0)
- alphas, _, lasso_path = linear_model.lars_path(X, y, method="lasso", positive=True)
- lasso_cd = linear_model.Lasso(fit_intercept=False, tol=1e-8, positive=True)
- for c, a in zip(lasso_path.T[:-1], alphas[:-1]): # don't include alpha=0
- lasso_cd.alpha = a
- lasso_cd.fit(X, y)
- error = linalg.norm(c - lasso_cd.coef_)
- assert error < 0.01
- def test_lasso_lars_vs_R_implementation():
- # Test that sklearn LassoLars implementation agrees with the LassoLars
- # implementation available in R (lars library) when fit_intercept=False.
- # Let's generate the data used in the bug report 7778
- y = np.array([-6.45006793, -3.51251449, -8.52445396, 6.12277822, -19.42109366])
- x = np.array(
- [
- [0.47299829, 0, 0, 0, 0],
- [0.08239882, 0.85784863, 0, 0, 0],
- [0.30114139, -0.07501577, 0.80895216, 0, 0],
- [-0.01460346, -0.1015233, 0.0407278, 0.80338378, 0],
- [-0.69363927, 0.06754067, 0.18064514, -0.0803561, 0.40427291],
- ]
- )
- X = x.T
- # The R result was obtained using the following code:
- #
- # library(lars)
- # model_lasso_lars = lars(X, t(y), type="lasso", intercept=FALSE,
- # trace=TRUE, normalize=FALSE)
- # r = t(model_lasso_lars$beta)
- #
- r = np.array(
- [
- [
- 0,
- 0,
- 0,
- 0,
- 0,
- -79.810362809499026,
- -83.528788732782829,
- -83.777653739190711,
- -83.784156932888934,
- -84.033390591756657,
- ],
- [0, 0, 0, 0, -0.476624256777266, 0, 0, 0, 0, 0.025219751009936],
- [
- 0,
- -3.577397088285891,
- -4.702795355871871,
- -7.016748621359461,
- -7.614898471899412,
- -0.336938391359179,
- 0,
- 0,
- 0.001213370600853,
- 0.048162321585148,
- ],
- [
- 0,
- 0,
- 0,
- 2.231558436628169,
- 2.723267514525966,
- 2.811549786389614,
- 2.813766976061531,
- 2.817462468949557,
- 2.817368178703816,
- 2.816221090636795,
- ],
- [
- 0,
- 0,
- -1.218422599914637,
- -3.457726183014808,
- -4.021304522060710,
- -45.827461592423745,
- -47.776608869312305,
- -47.911561610746404,
- -47.914845922736234,
- -48.039562334265717,
- ],
- ]
- )
- model_lasso_lars = linear_model.LassoLars(alpha=0, fit_intercept=False)
- model_lasso_lars.fit(X, y)
- skl_betas = model_lasso_lars.coef_path_
- assert_array_almost_equal(r, skl_betas, decimal=12)
- @filterwarnings_normalize
- @pytest.mark.parametrize("copy_X", [True, False])
- def test_lasso_lars_copyX_behaviour(copy_X):
- """
- Test that user input regarding copy_X is not being overridden (it was until
- at least version 0.21)
- """
- lasso_lars = LassoLarsIC(copy_X=copy_X, precompute=False)
- rng = np.random.RandomState(0)
- X = rng.normal(0, 1, (100, 5))
- X_copy = X.copy()
- y = X[:, 2]
- lasso_lars.fit(X, y)
- assert copy_X == np.array_equal(X, X_copy)
- @filterwarnings_normalize
- @pytest.mark.parametrize("copy_X", [True, False])
- def test_lasso_lars_fit_copyX_behaviour(copy_X):
- """
- Test that user input to .fit for copy_X overrides default __init__ value
- """
- lasso_lars = LassoLarsIC(precompute=False)
- rng = np.random.RandomState(0)
- X = rng.normal(0, 1, (100, 5))
- X_copy = X.copy()
- y = X[:, 2]
- lasso_lars.fit(X, y, copy_X=copy_X)
- assert copy_X == np.array_equal(X, X_copy)
- @filterwarnings_normalize
- @pytest.mark.parametrize("est", (LassoLars(alpha=1e-3), Lars()))
- def test_lars_with_jitter(est):
- # Test that a small amount of jitter helps stability,
- # using example provided in issue #2746
- X = np.array([[0.0, 0.0, 0.0, -1.0, 0.0], [0.0, -1.0, 0.0, 0.0, 0.0]])
- y = [-2.5, -2.5]
- expected_coef = [0, 2.5, 0, 2.5, 0]
- # set to fit_intercept to False since target is constant and we want check
- # the value of coef. coef would be all zeros otherwise.
- est.set_params(fit_intercept=False)
- est_jitter = clone(est).set_params(jitter=10e-8, random_state=0)
- est.fit(X, y)
- est_jitter.fit(X, y)
- assert np.mean((est.coef_ - est_jitter.coef_) ** 2) > 0.1
- np.testing.assert_allclose(est_jitter.coef_, expected_coef, rtol=1e-3)
- def test_X_none_gram_not_none():
- with pytest.raises(ValueError, match="X cannot be None if Gram is not None"):
- lars_path(X=None, y=[1], Gram="not None")
- def test_copy_X_with_auto_gram():
- # Non-regression test for #17789, `copy_X=True` and Gram='auto' does not
- # overwrite X
- rng = np.random.RandomState(42)
- X = rng.rand(6, 6)
- y = rng.rand(6)
- X_before = X.copy()
- linear_model.lars_path(X, y, Gram="auto", copy_X=True, method="lasso")
- # X did not change
- assert_allclose(X, X_before)
- @pytest.mark.parametrize(
- "LARS, has_coef_path, args",
- (
- (Lars, True, {}),
- (LassoLars, True, {}),
- (LassoLarsIC, False, {}),
- (LarsCV, True, {}),
- # max_iter=5 is for avoiding ConvergenceWarning
- (LassoLarsCV, True, {"max_iter": 5}),
- ),
- )
- @pytest.mark.parametrize("dtype", (np.float32, np.float64))
- @filterwarnings_normalize
- def test_lars_dtype_match(LARS, has_coef_path, args, dtype):
- # The test ensures that the fit method preserves input dtype
- rng = np.random.RandomState(0)
- X = rng.rand(20, 6).astype(dtype)
- y = rng.rand(20).astype(dtype)
- model = LARS(**args)
- model.fit(X, y)
- assert model.coef_.dtype == dtype
- if has_coef_path:
- assert model.coef_path_.dtype == dtype
- assert model.intercept_.dtype == dtype
- @pytest.mark.parametrize(
- "LARS, has_coef_path, args",
- (
- (Lars, True, {}),
- (LassoLars, True, {}),
- (LassoLarsIC, False, {}),
- (LarsCV, True, {}),
- # max_iter=5 is for avoiding ConvergenceWarning
- (LassoLarsCV, True, {"max_iter": 5}),
- ),
- )
- @filterwarnings_normalize
- def test_lars_numeric_consistency(LARS, has_coef_path, args):
- # The test ensures numerical consistency between trained coefficients
- # of float32 and float64.
- rtol = 1e-5
- atol = 1e-5
- rng = np.random.RandomState(0)
- X_64 = rng.rand(10, 6)
- y_64 = rng.rand(10)
- model_64 = LARS(**args).fit(X_64, y_64)
- model_32 = LARS(**args).fit(X_64.astype(np.float32), y_64.astype(np.float32))
- assert_allclose(model_64.coef_, model_32.coef_, rtol=rtol, atol=atol)
- if has_coef_path:
- assert_allclose(model_64.coef_path_, model_32.coef_path_, rtol=rtol, atol=atol)
- assert_allclose(model_64.intercept_, model_32.intercept_, rtol=rtol, atol=atol)
- @pytest.mark.parametrize("criterion", ["aic", "bic"])
- def test_lassolarsic_alpha_selection(criterion):
- """Check that we properly compute the AIC and BIC score.
- In this test, we reproduce the example of the Fig. 2 of Zou et al.
- (reference [1] in LassoLarsIC) In this example, only 7 features should be
- selected.
- """
- model = make_pipeline(StandardScaler(), LassoLarsIC(criterion=criterion))
- model.fit(X, y)
- best_alpha_selected = np.argmin(model[-1].criterion_)
- assert best_alpha_selected == 7
- @pytest.mark.parametrize("fit_intercept", [True, False])
- def test_lassolarsic_noise_variance(fit_intercept):
- """Check the behaviour when `n_samples` < `n_features` and that one needs
- to provide the noise variance."""
- rng = np.random.RandomState(0)
- X, y = datasets.make_regression(
- n_samples=10, n_features=11 - fit_intercept, random_state=rng
- )
- model = make_pipeline(StandardScaler(), LassoLarsIC(fit_intercept=fit_intercept))
- err_msg = (
- "You are using LassoLarsIC in the case where the number of samples is smaller"
- " than the number of features"
- )
- with pytest.raises(ValueError, match=err_msg):
- model.fit(X, y)
- model.set_params(lassolarsic__noise_variance=1.0)
- model.fit(X, y).predict(X)
|