Skip to content

Commit 7e4802b

Browse files
Perceptroniumfloriankozikowskimathurinm
authored
ENH Add support for GLasso and Adaptive (reweighted) GLasso (#280)
Co-authored-by: floriankozikowski <[email protected]> Co-authored-by: mathurinm <[email protected]>
1 parent 3851715 commit 7e4802b

File tree

9 files changed

+760
-2
lines changed

9 files changed

+760
-2
lines changed

doc/api.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ Estimators
3030
WeightedLasso
3131

3232

33+
Covariance
34+
==========
35+
36+
.. currentmodule:: skglm
37+
38+
.. autosummary::
39+
:toctree: generated/
40+
41+
GraphicalLasso
42+
AdaptiveGraphicalLasso
43+
44+
3345
Penalties
3446
=========
3547

doc/changes/0.5.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ Version 0.5 (in progress)
66
- Add experimental :ref:`QuantileHuber <skglm.experimental.quantile_huber.QuantileHuber>` and :ref:`SmoothQuantileRegressor <skglm.experimental.quantile_huber.SmoothQuantileRegressor>` for quantile regression, and an example script (PR: :gh:`312`).
77
- Add :ref:`GeneralizedLinearEstimatorCV <skglm.cv.GeneralizedLinearEstimatorCV>` for cross-validation with automatic parameter selection for L1 and elastic-net penalties (PR: :gh:`299`)
88
- Add :class:`skglm.datafits.group.PoissonGroup` datafit for group-structured Poisson regression. (PR: :gh:`317`)
9+
- Add :ref:`GraphicalLasso <skglm.covariance.GraphicalLasso>` for sparse inverse covariance estimation with both primal and dual algorithms
10+
- Add :ref:`AdaptiveGraphicalLasso <skglm.covariance.AdaptiveGraphicalLasso>` for non-convex penalty variations using iterative reweighting strategy
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""
2+
=======================================================================
3+
Regularization paths for the Graphical Lasso and its Adaptive variation
4+
=======================================================================
5+
This example demonstrates how non-convex penalties in the Adaptive Graphical Lasso
6+
can achieve superior sparsity recovery compared to the standard L1 penalty.
7+
8+
The Adaptive Graphical Lasso uses iterative reweighting to approximate non-convex
9+
penalties, following Candès et al. (2007). Non-convex penalties often produce
10+
better sparsity patterns by more aggressively shrinking small coefficients while
11+
preserving large ones.
12+
13+
We compare three approaches:
14+
- **L1**: Standard Graphical Lasso with L1 penalty
15+
- **Log**: Adaptive approach with logarithmic penalty
16+
- **L0.5**: Adaptive approach with L0.5 penalty
17+
18+
The plots show normalized mean square error (NMSE) for reconstruction accuracy
19+
and F1 score for sparsity pattern recovery across different regularization levels.
20+
"""
21+
22+
# Authors: Can Pouliquen
23+
# Mathurin Massias
24+
# Florian Kozikowski
25+
26+
import numpy as np
27+
from numpy.linalg import norm
28+
import matplotlib.pyplot as plt
29+
from sklearn.metrics import f1_score
30+
31+
from skglm.covariance import GraphicalLasso, AdaptiveGraphicalLasso
32+
from skglm.penalties.separable import LogSumPenalty, L0_5
33+
from skglm.utils.data import make_dummy_covariance_data
34+
35+
# %%
36+
# Generate synthetic sparse precision matrix data
37+
# ===============================================
38+
39+
p = 100
40+
n = 1000
41+
S, _, Theta_true, alpha_max = make_dummy_covariance_data(n, p)
42+
alphas = alpha_max*np.geomspace(1, 1e-4, num=10)
43+
44+
# %%
45+
# Setup models with different penalty functions
46+
# ============================================
47+
48+
penalties = ["L1", "Log", "L0.5"]
49+
n_reweights = 5 # Number of adaptive reweighting iterations
50+
models_tol = 1e-4
51+
52+
models = [
53+
# Standard Graphical Lasso with L1 penalty
54+
GraphicalLasso(algo="primal", warm_start=True, tol=models_tol),
55+
56+
# Adaptive Graphical Lasso with logarithmic penalty
57+
AdaptiveGraphicalLasso(warm_start=True,
58+
penalty=LogSumPenalty(alpha=1.0, eps=1e-10),
59+
n_reweights=n_reweights,
60+
tol=models_tol),
61+
62+
# Adaptive Graphical Lasso with L0.5 penalty
63+
AdaptiveGraphicalLasso(warm_start=True,
64+
penalty=L0_5(alpha=1.0),
65+
n_reweights=n_reweights,
66+
tol=models_tol),
67+
]
68+
69+
# %%
70+
# Compute regularization paths
71+
# ============================
72+
73+
nmse_results = {penalty: [] for penalty in penalties}
74+
f1_results = {penalty: [] for penalty in penalties}
75+
76+
77+
# Fit models across regularization path
78+
for i, (penalty, model) in enumerate(zip(penalties, models)):
79+
print(f"Fitting {penalty} penalty across {len(alphas)} regularization values...")
80+
for alpha_idx, alpha in enumerate(alphas):
81+
print(
82+
f" alpha {alpha_idx+1}/{len(alphas)}: "
83+
f"lambda/lambda_max = {alpha/alpha_max:.1e}",
84+
end="")
85+
86+
model.alpha = alpha
87+
model.fit(S, mode='precomputed')
88+
89+
Theta_est = model.precision_
90+
nmse = norm(Theta_est - Theta_true)**2 / norm(Theta_true)**2
91+
f1_val = f1_score(Theta_est.flatten() != 0., Theta_true.flatten() != 0.)
92+
93+
nmse_results[penalty].append(nmse)
94+
f1_results[penalty].append(f1_val)
95+
96+
print(f"NMSE: {nmse:.3f}, F1: {f1_val:.3f}")
97+
print(f"{penalty} penalty complete!\n")
98+
99+
100+
# %%
101+
# Plot results
102+
# ============
103+
fig, axarr = plt.subplots(2, 1, sharex=True, figsize=([6.11, 3.91]),
104+
layout="constrained")
105+
cmap = plt.get_cmap("tab10")
106+
for i, penalty in enumerate(penalties):
107+
108+
for j, ax in enumerate(axarr):
109+
110+
if j == 0:
111+
metric = nmse_results
112+
best_idx = np.argmin(metric[penalty])
113+
ystop = np.min(metric[penalty])
114+
else:
115+
metric = f1_results
116+
best_idx = np.argmax(metric[penalty])
117+
ystop = np.max(metric[penalty])
118+
119+
ax.semilogx(alphas/alpha_max,
120+
metric[penalty],
121+
color=cmap(i),
122+
linewidth=2.,
123+
label=penalty)
124+
125+
ax.vlines(
126+
x=alphas[best_idx] / alphas[0],
127+
ymin=0,
128+
ymax=ystop,
129+
linestyle='--',
130+
color=cmap(i))
131+
line = ax.plot(
132+
[alphas[best_idx] / alphas[0]],
133+
0,
134+
clip_on=False,
135+
marker='X',
136+
color=cmap(i),
137+
markersize=12)
138+
139+
ax.grid(which='both', alpha=0.9)
140+
141+
axarr[0].legend(fontsize=14)
142+
axarr[0].set_title(f"{p=},{n=}", fontsize=18)
143+
axarr[0].set_ylabel("NMSE", fontsize=18)
144+
axarr[1].set_ylabel("F1 score", fontsize=18)
145+
_ = axarr[1].set_xlabel(r"$\lambda / \lambda_\mathrm{{max}}$", fontsize=18)
146+
# %%
147+
# Results summary
148+
# ===============
149+
150+
print("Performance at optimal regularization:")
151+
print("-" * 50)
152+
153+
for penalty in penalties:
154+
best_nmse = min(nmse_results[penalty])
155+
best_f1 = max(f1_results[penalty])
156+
print(f"{penalty:>4}: NMSE = {best_nmse:.3f}, F1 = {best_f1:.3f}")
157+
158+
# %% [markdown]
159+
#
160+
# **Metrics explanation:**
161+
#
162+
# * **NMSE (Normalized Mean Square Error)**: Measures reconstruction accuracy
163+
# of the precision matrix. Lower values = better reconstruction.
164+
# * **F1 Score**: Measures sparsity pattern recovery (correctly identifying
165+
# which entries are zero/non-zero). Higher values = better sparsity.
166+
#
167+
# **Key finding**: Non-convex penalties achieve significantly
168+
# better sparsity recovery (F1 score) while maintaining
169+
# competitive reconstruction accuracy (NMSE).

skglm/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
SparseLogisticRegression, GeneralizedLinearEstimator, CoxEstimator, GroupLasso,
66
)
77
from .cv import GeneralizedLinearEstimatorCV # noqa F401
8+
from .covariance import GraphicalLasso, AdaptiveGraphicalLasso # noqa F401

0 commit comments

Comments
 (0)