|
4 | 4 | ======================================================================= |
5 | 5 | Regularization paths for the Graphical Lasso and its Adaptive variation |
6 | 6 | ======================================================================= |
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. |
9 | 22 | """ |
10 | 23 |
|
11 | 24 | import numpy as np |
|
14 | 27 | from sklearn.metrics import f1_score |
15 | 28 |
|
16 | 29 | from skglm.covariance import GraphicalLasso, AdaptiveGraphicalLasso |
| 30 | +from skglm.penalties.separable import LogSumPenalty, L0_5 |
17 | 31 | from skglm.utils.data import make_dummy_covariance_data |
18 | 32 |
|
| 33 | +# %% |
| 34 | +# Generate synthetic sparse precision matrix data |
| 35 | +# =============================================== |
19 | 36 |
|
20 | 37 | p = 100 |
21 | 38 | n = 1000 |
22 | 39 | S, Theta_true, alpha_max = make_dummy_covariance_data(n, p) |
23 | 40 | alphas = alpha_max*np.geomspace(1, 1e-4, num=10) |
24 | 41 |
|
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 |
32 | 48 | models_tol = 1e-4 |
| 49 | + |
33 | 50 | 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 |
41 | 55 | AdaptiveGraphicalLasso(warm_start=True, |
42 | | - strategy="sqrt", |
| 56 | + penalty=LogSumPenalty(alpha=1.0, eps=1e-10), |
43 | 57 | n_reweights=n_reweights, |
44 | 58 | tol=models_tol), |
| 59 | + |
| 60 | + # Adaptive Graphical Lasso with L0.5 penalty |
45 | 61 | AdaptiveGraphicalLasso(warm_start=True, |
46 | | - strategy="mcp", |
| 62 | + penalty=L0_5(alpha=1.0), |
47 | 63 | n_reweights=n_reweights, |
48 | 64 | tol=models_tol), |
49 | 65 | ] |
50 | 66 |
|
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 | +# ============================ |
53 | 70 |
|
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} |
56 | 73 |
|
57 | 74 |
|
| 75 | +# Fit models across regularization path |
58 | 76 | for i, (penalty, model) in enumerate(zip(penalties, models)): |
| 77 | + print(f"Fitting {penalty} penalty across {len(alphas)} regularization values...") |
59 | 78 | 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 | + |
61 | 84 | model.alpha = alpha |
62 | 85 | model.fit(S) |
63 | | - Theta = model.precision_ |
64 | 86 |
|
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.) |
66 | 90 |
|
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) |
69 | 93 |
|
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") |
72 | 96 |
|
73 | 97 |
|
74 | | -plt.close('all') |
| 98 | +# %% |
| 99 | +# Plot results |
| 100 | +# ============ |
75 | 101 | fig, axarr = plt.subplots(2, 1, sharex=True, figsize=([6.11, 3.91]), |
76 | 102 | layout="constrained") |
77 | 103 | cmap = plt.get_cmap("tab10") |
|
80 | 106 | for j, ax in enumerate(axarr): |
81 | 107 |
|
82 | 108 | if j == 0: |
83 | | - metric = my_glasso_nmses |
| 109 | + metric = nmse_results |
84 | 110 | best_idx = np.argmin(metric[penalty]) |
85 | 111 | ystop = np.min(metric[penalty]) |
86 | 112 | else: |
87 | | - metric = my_glasso_f1_scores |
| 113 | + metric = f1_results |
88 | 114 | best_idx = np.argmax(metric[penalty]) |
89 | 115 | ystop = np.max(metric[penalty]) |
90 | 116 |
|
|
114 | 140 | axarr[0].set_title(f"{p=},{n=}", fontsize=18) |
115 | 141 | axarr[0].set_ylabel("NMSE", fontsize=18) |
116 | 142 | 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