Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
aabbf91
fix add support for intercept in SqrtLasso
PascalCarrivain Dec 14, 2023
a44c11f
fix line too long (91 > 88 characters)
PascalCarrivain Dec 14, 2023
d4e69f2
[CI trigger]
Badr-MOUFAD Dec 15, 2023
1975371
set intercept to false in unittest
Badr-MOUFAD Dec 15, 2023
6f68666
fix inverting the cases (fit_intercept)
PascalCarrivain Dec 18, 2023
a549bba
Merge branch 'fix_intercept_SqrtLasso' of https://github.com/PascalCa…
PascalCarrivain Dec 18, 2023
cdc21ea
fix undefined name 'X_coeff'
PascalCarrivain Jan 23, 2024
c38c00d
add fit_intercept to test_alpha_max and to self.solver_
PascalCarrivain Feb 22, 2024
ad47b19
add fit_intercept docstring, factorized duplicate code
PascalCarrivain Feb 23, 2024
e90add1
Fix imports in README (#211)
PascalCarrivain Dec 11, 2023
4a7d466
DOC - Add installation instructions for conda-forge (#210)
jjerphan Dec 11, 2023
a1507d5
FIX - Upload documentation only when merging to ``main`` (#209)
Badr-MOUFAD Dec 12, 2023
c1d6a15
cd to doc before (#212)
Badr-MOUFAD Dec 12, 2023
564fa61
DOC - Hide table of contents in documentation home page (#213)
Badr-MOUFAD Dec 12, 2023
a36c05c
Add Code of Conduct (#217)
Badr-MOUFAD Dec 23, 2023
240f11a
MNT - bump to version 0.3.2 (#218)
mathurinm Dec 23, 2023
a1662bb
DOC - Fix link to stable documentation (#219)
Badr-MOUFAD Jan 10, 2024
360b41a
Add Group Lasso with positive weigths (#221)
QB3 Jan 23, 2024
c1d16a2
Add prox vec L05 (#222)
mathurinm Feb 5, 2024
e17ffc6
DOC update contribution section (#224)
mathurinm Feb 20, 2024
3fe0cc6
FIX - computation of ``subdiff_distance`` in ``WeightedGroupL2`` pena…
Badr-MOUFAD Mar 6, 2024
fe27eb7
API change use_acc default value to False in GreedyCD (#236)
mathurinm Mar 28, 2024
c560dd1
MNT change solver quantile regressor sklearn in test (#235)
mathurinm Mar 28, 2024
b738b3f
ENH add GroupLasso estimator with sparse X support (#228)
tomaszkacprzak Apr 3, 2024
dbcc207
DOC add ucurve example (#239)
mathurinm Apr 4, 2024
891f75c
FIX objective function in docstring ``GroupLasso`` (#241)
mathurinm Apr 4, 2024
e7c25b2
DOC - update documentation (#242)
Badr-MOUFAD Apr 8, 2024
27dec4b
DOC add group logistic regression example (#246)
mathurinm Apr 26, 2024
e0c28d7
ENH implement gradient and allow `y_i = 0` in `Poisson` datafit (#253)
mathurinm May 31, 2024
840e8b2
ENH gradient, raw grad and hessian for Quadratic (#257)
mathurinm May 31, 2024
e60ca6a
FIX ProxNewton solver with fixpoint strategy (#259)
mathurinm Jun 3, 2024
553c3d6
FEAT add WeightedQuadratic datafit to allow sample weights (#258)
sujay-pandit Jun 24, 2024
cac34ae
DOC L1-regularization parameter tutorial (#264)
wassimmazouz Jul 2, 2024
3260ebd
FEAT implement sparse group lasso penalty and ws_strategy="fixpoint" …
mathurinm Jul 14, 2024
07c49bb
ENH - check ``datafit + penalty`` compatibility with solver (#137)
PABannier Jul 15, 2024
ab8b9d7
MNT Update pull_request_template.md (#271)
mathurinm Jul 17, 2024
b35ff5b
FIX make PDCD_WS solver usable in GeneralizedLinearEstimator (#274)
mathurinm Aug 1, 2024
c6325e9
ENH - Adds support for L1 + L2 regularization in SparseLogisticRegres…
AnavAgrawal Nov 5, 2024
7d274d8
FEAT Add QuadraticHessian datafit (#279)
tvayer Nov 7, 2024
75b92cc
Docstring update for L2 penalty in SparseLogisticRegression (#281)
floriankozikowski Apr 2, 2025
0837365
FIX/MNT install R with conda and use python 3.10 on test workflow (#282)
mathurinm Apr 4, 2025
a334d2a
MNT move citation up in readme (#284)
mathurinm Apr 7, 2025
554a93c
REL release 0.4 (#285)
mathurinm Apr 8, 2025
397b842
MNT start dev of 0.5 version (#286)
mathurinm Apr 8, 2025
3692944
MNT add celer test dep (#288)
jolars Apr 10, 2025
8985aaa
MNT add python version requirement (#289)
jolars Apr 10, 2025
cb284b0
MNT fix failing slope test (#287)
jolars Apr 14, 2025
185c17f
MNT add tags for sklearn (#293)
mathurinm Apr 15, 2025
7222e00
ENH - jit-compile datafits and penalties inside solver (#270)
Badr-MOUFAD Apr 15, 2025
4629e59
first try at unit test
floriankozikowski Apr 22, 2025
e7568bd
first try, add support for fit_intercept in sqrtLasso, TODOS: review …
floriankozikowski Apr 22, 2025
e3b9df2
Merge remote-tracking branch 'origin/main' into fix_intercept_SqrtLasso
floriankozikowski Apr 22, 2025
91a5608
fix merge errors
floriankozikowski Apr 22, 2025
791c8cd
fix pytest, should work now
floriankozikowski Apr 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@floriankozikowski remove these 3 files

Binary file not shown.
Binary file added skglm/.DS_Store
Binary file not shown.
Binary file added skglm/experimental/.DS_Store
Binary file not shown.
39 changes: 31 additions & 8 deletions skglm/experimental/sqrt_lasso.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,14 @@ class SqrtLasso(LinearModel, RegressorMixin):

verbose : bool, default False
Amount of verbosity. 0/False is silent.

fit_intercept: bool, optional (default=True)
Whether or not to fit an intercept.
"""

def __init__(self, alpha=1., max_iter=100, max_pn_iter=100, p0=10,
tol=1e-4, verbose=0):
tol=1e-4, verbose=0, fit_intercept=True):

super().__init__()
self.alpha = alpha
self.max_iter = max_iter
Expand All @@ -112,6 +116,7 @@ def __init__(self, alpha=1., max_iter=100, max_pn_iter=100, p0=10,
self.p0 = p0
self.tol = tol
self.verbose = verbose
self.fit_intercept = fit_intercept

def fit(self, X, y):
"""Fit the model according to the given training data.
Expand All @@ -130,8 +135,23 @@ def fit(self, X, y):
self :
Fitted estimator.
"""
self.coef_ = self.path(X, y, alphas=[self.alpha])[1][0]
self.intercept_ = 0. # TODO handle fit_intercept
# self.coef_ = self.path(X, y, alphas=[self.alpha])[1][0]
if self.fit_intercept:
X_mean = X.mean(axis=0)
y_mean = y.mean()
X_centered = X - X_mean
y_centered = y - y_mean
else:
X_centered = X
y_centered = y

self.coef_ = self.path(X_centered, y_centered, alphas=[self.alpha])[1][0]

if self.fit_intercept:
self.intercept_ = y_mean - X_mean @ self.coef_[:-1]
self.coef_ = self.coef_[:-1]
else:
self.intercept_ = 0.
return self

def path(self, X, y, alphas=None, eps=1e-3, n_alphas=10):
Expand Down Expand Up @@ -168,7 +188,7 @@ def path(self, X, y, alphas=None, eps=1e-3, n_alphas=10):
if not hasattr(self, "solver_"):
self.solver_ = ProxNewton(
tol=self.tol, max_iter=self.max_iter, verbose=self.verbose,
fit_intercept=False)
fit_intercept=self.fit_intercept)
# build path
if alphas is None:
alpha_max = norm(X.T @ y, ord=np.inf) / (np.sqrt(len(y)) * norm(y))
Expand All @@ -181,7 +201,7 @@ def path(self, X, y, alphas=None, eps=1e-3, n_alphas=10):
sqrt_quadratic = SqrtQuadratic()
l1_penalty = L1(1.) # alpha is set along the path

coefs = np.zeros((n_alphas, n_features))
coefs = np.zeros((n_alphas, n_features + self.fit_intercept))

for i in range(n_alphas):
if self.verbose:
Expand All @@ -192,12 +212,14 @@ def path(self, X, y, alphas=None, eps=1e-3, n_alphas=10):

l1_penalty.alpha = alphas[i]
# no warm start for the first alpha
coef_init = coefs[i].copy() if i else np.zeros(n_features)
coef_init = coefs[i].copy() if i else np.zeros(n_features
+ self.fit_intercept)

try:
coef, _, _ = self.solver_.solve(
X, y, sqrt_quadratic, l1_penalty,
w_init=coef_init, Xw_init=X @ coef_init)
w_init=coef_init, Xw_init=X @ coef_init[:-1] + coef_init[-1]
if self.fit_intercept else X @ coef_init)
coefs[i] = coef
except ValueError as val_exception:
# make sure to catch residual error
Expand All @@ -208,7 +230,8 @@ def path(self, X, y, alphas=None, eps=1e-3, n_alphas=10):
# save coef despite not converging
# coef_init holds a ref to coef
coef = coef_init
res_norm = norm(y - X @ coef)
X_coef = X @ coef[:-1] + coef[-1] if self.fit_intercept else X @ coef
res_norm = norm(y - X_coef)
warnings.warn(
f"Small residuals prevented the solver from converging "
f"at alpha={alphas[i]:.2e} (residuals' norm: {res_norm:.4e}). "
Expand Down
59 changes: 55 additions & 4 deletions skglm/experimental/tests/test_sqrt_lasso.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from skglm.experimental.sqrt_lasso import (SqrtLasso, SqrtQuadratic,
_chambolle_pock_sqrt)
from skglm.experimental.pdcd_ws import PDCD_WS
from skglm import Lasso


def test_alpha_max():
Expand All @@ -16,7 +17,10 @@ def test_alpha_max():

sqrt_lasso = SqrtLasso(alpha=alpha_max).fit(X, y)

np.testing.assert_equal(sqrt_lasso.coef_, 0)
if sqrt_lasso.fit_intercept:
np.testing.assert_equal(sqrt_lasso.coef_[:-1], 0)
else:
np.testing.assert_equal(sqrt_lasso.coef_, 0)
Comment on lines +20 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about this refactoring?

Suggested change
if sqrt_lasso.fit_intercept:
np.testing.assert_equal(sqrt_lasso.coef_[:-1], 0)
else:
np.testing.assert_equal(sqrt_lasso.coef_, 0)
np.testing.assert_equal(sqrt_lasso.coef_[:n_features], 0)



def test_vs_statsmodels():
Expand All @@ -31,7 +35,7 @@ def test_vs_statsmodels():
n_alphas = 3
alphas = alpha_max * np.geomspace(1, 1e-2, n_alphas+1)[1:]

sqrt_lasso = SqrtLasso(tol=1e-9)
sqrt_lasso = SqrtLasso(tol=1e-9, fit_intercept=False)
coefs_skglm = sqrt_lasso.path(X, y, alphas)[1]

coefs_statsmodels = np.zeros((len(alphas), n_features))
Expand All @@ -54,7 +58,7 @@ def test_prox_newton_cp():

alpha_max = norm(X.T @ y, ord=np.inf) / norm(y)
alpha = alpha_max / 10
clf = SqrtLasso(alpha=alpha, tol=1e-12).fit(X, y)
clf = SqrtLasso(alpha=alpha, fit_intercept=False, tol=1e-12).fit(X, y)
w, _, _ = _chambolle_pock_sqrt(X, y, alpha, max_iter=1000)
np.testing.assert_allclose(clf.coef_, w)

Expand All @@ -73,9 +77,56 @@ def test_PDCD_WS(with_dual_init):
penalty = L1(alpha)

w = PDCD_WS(dual_init=dual_init).solve(X, y, datafit, penalty)[0]
clf = SqrtLasso(alpha=alpha, tol=1e-12).fit(X, y)

clf = SqrtLasso(alpha=alpha, fit_intercept=False, tol=1e-12).fit(X, y)

np.testing.assert_allclose(clf.coef_, w, atol=1e-6)


def test_sqrt_lasso_with_intercept():
np.random.seed(0)
X = np.random.randn(10, 20)
y = np.random.randn(10)
y += 1

n = len(y)
alpha_max = norm(X.T @ y, ord=np.inf) / n
alpha = alpha_max / 10

# Fit standard Lasso with intercept
lass = Lasso(alpha=alpha, fit_intercept=True, tol=1e-8).fit(X, y)
w_lass = lass.coef_
assert norm(w_lass) > 0

scal = n / norm(y - lass.predict(X))

# Fit SqrtLasso with intercept
sqrt = SqrtLasso(alpha=alpha * scal, fit_intercept=True, tol=1e-8).fit(X, y)

# Make sure intercept was learned
assert abs(sqrt.intercept_) > 1e-6

y_pred = sqrt.predict(X)
assert y_pred.shape == y.shape

# Check that coef_ and intercept_ are handled separately
assert sqrt.coef_.shape == (20,)
assert np.isscalar(sqrt.intercept_)

# Confirm prediction matches manual computation
manual_pred = X @ sqrt.coef_ + sqrt.intercept_
np.testing.assert_allclose(manual_pred, y_pred, rtol=1e-6)

np.testing.assert_allclose(
sqrt.intercept_, y.mean() - X.mean(axis=0) @ sqrt.coef_, rtol=1e-6
)

sqrt_no_intercept = SqrtLasso(
alpha=alpha * scal, fit_intercept=False, tol=1e-8).fit(X, y)
assert np.isscalar(sqrt_no_intercept.intercept_)
np.testing.assert_allclose(sqrt_no_intercept.predict(
X), X @ sqrt_no_intercept.coef_ + sqrt_no_intercept.intercept_)


if __name__ == '__main__':
pass