Skip to content

Commit c9d1545

Browse files
clean up & fix adaptive method, update example, unit test accordingly
1 parent c50f5d3 commit c9d1545

File tree

4 files changed

+120
-272
lines changed

4 files changed

+120
-272
lines changed

examples/plot_reweighted_glasso_reg_path.py

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,21 @@
44
=======================================================================
55
Regularization paths for the Graphical Lasso and its Adaptive variation
66
=======================================================================
7-
Highlight the importance of using non-convex regularization for improved performance,
8-
solved using the reweighting strategy.
7+
This example demonstrates how non-convex penalties in the Adaptive Graphical Lasso
8+
can achieve superior sparsity recovery compared to the standard L1 penalty.
9+
10+
The Adaptive Graphical Lasso uses iterative reweighting to approximate non-convex
11+
penalties, following Candès et al. (2007). Non-convex penalties often produce
12+
better sparsity patterns by more aggressively shrinking small coefficients while
13+
preserving large ones.
14+
15+
We compare three approaches:
16+
- **L1**: Standard Graphical Lasso with L1 penalty
17+
- **Log**: Adaptive approach with logarithmic penalty
18+
- **L0.5**: Adaptive approach with L0.5 penalty
19+
20+
The plots show normalized mean square error (NMSE) for reconstruction accuracy
21+
and F1 score for sparsity pattern recovery across different regularization levels.
922
"""
1023

1124
import numpy as np
@@ -14,64 +27,77 @@
1427
from sklearn.metrics import f1_score
1528

1629
from skglm.covariance import GraphicalLasso, AdaptiveGraphicalLasso
30+
from skglm.penalties.separable import LogSumPenalty, L0_5
1731
from skglm.utils.data import make_dummy_covariance_data
1832

33+
# %%
34+
# Generate synthetic sparse precision matrix data
35+
# ===============================================
1936

2037
p = 100
2138
n = 1000
2239
S, Theta_true, alpha_max = make_dummy_covariance_data(n, p)
2340
alphas = alpha_max*np.geomspace(1, 1e-4, num=10)
2441

25-
penalties = [
26-
"L1",
27-
"Log",
28-
"L0.5",
29-
"MCP",
30-
]
31-
n_reweights = 5
42+
# %%
43+
# Setup models with different penalty functions
44+
# ============================================
45+
46+
penalties = ["L1", "Log", "L0.5"]
47+
n_reweights = 5 # Number of adaptive reweighting iterations
3248
models_tol = 1e-4
49+
3350
models = [
34-
GraphicalLasso(algo="primal",
35-
warm_start=True,
36-
tol=models_tol),
37-
AdaptiveGraphicalLasso(warm_start=True,
38-
strategy="log",
39-
n_reweights=n_reweights,
40-
tol=models_tol),
51+
# Standard Graphical Lasso with L1 penalty
52+
GraphicalLasso(algo="primal", warm_start=True, tol=models_tol),
53+
54+
# Adaptive Graphical Lasso with logarithmic penalty
4155
AdaptiveGraphicalLasso(warm_start=True,
42-
strategy="sqrt",
56+
penalty=LogSumPenalty(alpha=1.0, eps=1e-10),
4357
n_reweights=n_reweights,
4458
tol=models_tol),
59+
60+
# Adaptive Graphical Lasso with L0.5 penalty
4561
AdaptiveGraphicalLasso(warm_start=True,
46-
strategy="mcp",
62+
penalty=L0_5(alpha=1.0),
4763
n_reweights=n_reweights,
4864
tol=models_tol),
4965
]
5066

51-
my_glasso_nmses = {penalty: [] for penalty in penalties}
52-
my_glasso_f1_scores = {penalty: [] for penalty in penalties}
67+
# %%
68+
# Compute regularization paths
69+
# ============================
5370

54-
sk_glasso_nmses = []
55-
sk_glasso_f1_scores = []
71+
nmse_results = {penalty: [] for penalty in penalties}
72+
f1_results = {penalty: [] for penalty in penalties}
5673

5774

75+
# Fit models across regularization path
5876
for i, (penalty, model) in enumerate(zip(penalties, models)):
77+
print(f"Fitting {penalty} penalty across {len(alphas)} regularization values...")
5978
for alpha_idx, alpha in enumerate(alphas):
60-
print(f"======= {penalty} penalty, alpha {alpha_idx+1}/{len(alphas)} =======")
79+
print(
80+
f" alpha {alpha_idx+1}/{len(alphas)}: "
81+
f"lambda/lambda_max = {alpha/alpha_max:.1e}",
82+
end="")
83+
6184
model.alpha = alpha
6285
model.fit(S)
63-
Theta = model.precision_
6486

65-
my_nmse = norm(Theta - Theta_true)**2 / norm(Theta_true)**2
87+
Theta_est = model.precision_
88+
nmse = norm(Theta_est - Theta_true)**2 / norm(Theta_true)**2
89+
f1_val = f1_score(Theta_est.flatten() != 0., Theta_true.flatten() != 0.)
6690

67-
my_f1_score = f1_score(Theta.flatten() != 0.,
68-
Theta_true.flatten() != 0.)
91+
nmse_results[penalty].append(nmse)
92+
f1_results[penalty].append(f1_val)
6993

70-
my_glasso_nmses[penalty].append(my_nmse)
71-
my_glasso_f1_scores[penalty].append(my_f1_score)
94+
print(f"NMSE: {nmse:.3f}, F1: {f1_val:.3f}")
95+
print(f"{penalty} penalty complete!\n")
7296

7397

74-
plt.close('all')
98+
# %%
99+
# Plot results
100+
# ============
75101
fig, axarr = plt.subplots(2, 1, sharex=True, figsize=([6.11, 3.91]),
76102
layout="constrained")
77103
cmap = plt.get_cmap("tab10")
@@ -80,11 +106,11 @@
80106
for j, ax in enumerate(axarr):
81107

82108
if j == 0:
83-
metric = my_glasso_nmses
109+
metric = nmse_results
84110
best_idx = np.argmin(metric[penalty])
85111
ystop = np.min(metric[penalty])
86112
else:
87-
metric = my_glasso_f1_scores
113+
metric = f1_results
88114
best_idx = np.argmax(metric[penalty])
89115
ystop = np.max(metric[penalty])
90116

@@ -114,6 +140,28 @@
114140
axarr[0].set_title(f"{p=},{n=}", fontsize=18)
115141
axarr[0].set_ylabel("NMSE", fontsize=18)
116142
axarr[1].set_ylabel("F1 score", fontsize=18)
117-
axarr[1].set_xlabel(r"$\lambda / \lambda_\mathrm{{max}}$", fontsize=18)
118-
119-
plt.show(block=False)
143+
_ = axarr[1].set_xlabel(r"$\lambda / \lambda_\mathrm{{max}}$", fontsize=18)
144+
# %%
145+
# Results summary
146+
# ===============
147+
148+
print("Performance at optimal regularization:")
149+
print("-" * 50)
150+
151+
for penalty in penalties:
152+
best_nmse = min(nmse_results[penalty])
153+
best_f1 = max(f1_results[penalty])
154+
print(f"{penalty:>4}: NMSE = {best_nmse:.3f}, F1 = {best_f1:.3f}")
155+
156+
# %% [markdown]
157+
#
158+
# **Metrics explanation:**
159+
#
160+
# * **NMSE (Normalized Mean Square Error)**: Measures reconstruction accuracy
161+
# of the precision matrix. Lower values = better reconstruction.
162+
# * **F1 Score**: Measures sparsity pattern recovery (correctly identifying
163+
# which entries are zero/non-zero). Higher values = better sparsity.
164+
#
165+
# **Key finding**: Non-convex penalties achieve significantly
166+
# better sparsity recovery (F1 score) while maintaining
167+
# competitive reconstruction accuracy (NMSE).

0 commit comments

Comments
 (0)