Lasso
This example demonstrates quadratic objectives, as well as reusing a cached workspace and using warm-starting.
In the lasso the goal is to find a sparse vector that fits some measurements. The \(\ell_1\) norm is used as a convex surrogate for sparsity, and a regularization parameter \(\lambda \geq 0\) trades off sparsity and quality of fit. Concretely the lasso solves
over variable \(x \in \mathbf{R}^{n}\), with data \(A \in \mathbf{R}^{m \times n}\) and \(b \in \mathbf{R}^n\). The problem has the following equivalent form,
over variables \(x \in \mathbf{R}^{n}\), \(t \in \mathbf{R}^{n}\), \(y \in \mathbf{R}^{m}\). From this formulation it is straightforward to convert it into the standard form accepted by SCS. The regularization parameter \(\lambda \geq 0\) trades off the sparsity of the solution and the quality of the fit, and so we solve the problem for many choices of \(\lambda\). Since \(\lambda\) enters only in the linear part of the objective function, we can reuse the matrix factorization and use warm starting to reduce the computation time.
Python code to solve this is below.
import scs
import numpy as np
import scipy as sp
from scipy import sparse
# Generate problem data
sp.random.seed(1)
np.random.seed(1)
n = 200 # Variables
m = 100 # Measurements
Ad = sparse.random(m, n, density=0.5) # Measurement matrix
x_true = sparse.random(n, 1, density=0.1) # True sparse vector
x_true = np.array(x_true.todense()).squeeze()
measurements = Ad @ x_true + 0.1 * np.random.randn(m)
measurements = np.array(measurements).squeeze()
# The smallest value of lambda with solution all-zeros
lambda_max = np.linalg.norm(Ad.T @ measurements, np.inf)
# Auxiliary data
In = sparse.eye(n)
Im = sparse.eye(m)
On = sparse.csc_matrix((n, n))
Onm = sparse.csc_matrix((n, m))
# SCS data
P = sparse.block_diag([On, sparse.eye(m), On], format="csc")
q = np.zeros(2 * n + m)
A = sparse.vstack(
[
# zero cone
sparse.hstack([Ad, -Im, Onm.T]),
# positive cones
sparse.hstack([In, Onm, -In]),
sparse.hstack([-In, Onm, -In]),
],
format="csc",
)
b = np.hstack([measurements, np.zeros(n), np.zeros(n)])
c = np.zeros(2 * n + m)
data = dict(P=P, A=A, b=b, c=c)
cone = dict(z=m, l=2 * n)
print(f"Solving for lambda = 0")
# Setup workspace
solver = scs.SCS(
data,
cone,
eps_abs=1e-6,
eps_rel=1e-6,
)
sol = solver.solve() # lambda = 0
x = sol["x"][:n]
print(f"Error : {np.linalg.norm(x_true - x) / np.linalg.norm(x_true)}")
# Solve for different values of lambda
lambdas = np.logspace(-2, np.log10(lambda_max), 11)
for lam in lambdas:
print(f"Solving for lambda = {lam}")
# Re-use workspace, just update the `c` vector
c_new = np.hstack([np.zeros(n + m), lam * np.ones(n)])
solver.update(c=c_new)
# Solve updated problem
sol = solver.solve() # will warm-start automatically
x = sol["x"][:n]
# What is the norm error?
print(f"Error : {np.linalg.norm(x_true - x) / np.linalg.norm(x_true)}")
After following the python install instructions, we can run the code yielding output:
Solving for lambda = 0
------------------------------------------------------------------
SCS v3.1.1 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 500, constraints m: 500
cones: z: primal zero / dual free vars: 100
l: linear vars: 400
settings: eps_abs: 1.0e-06, eps_rel: 1.0e-06, eps_infeas: 1.0e-07
alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
max_iters: 100000, normalize: 1, rho_x: 1.00e-06
acceleration_lookback: 10, acceleration_interval: 10
lin-sys: sparse-direct
nnz(A): 10900, nnz(P): 100
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 4.54e+00 7.55e-02 9.57e-01 4.89e-01 1.00e-01 1.68e-04
25| 1.56e-07 5.84e-08 3.28e-07 1.64e-07 1.00e-01 1.36e-03
------------------------------------------------------------------
status: solved
timings: total: 7.39e-03s = setup: 6.02e-03s + solve: 1.37e-03s
lin-sys: 1.06e-03s, cones: 3.43e-05s, accel: 6.73e-06s
------------------------------------------------------------------
objective = 0.000000
------------------------------------------------------------------
Error : 0.7256063655877213
Solving for lambda = 0.01
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.41e-01 1.00e-02 7.69e-02 3.85e-02 1.00e-01 1.48e-04
250| 1.34e-04 1.56e-06 1.40e-06 1.25e-01 1.69e-02 1.14e-02
500| 5.72e-05 8.48e-07 4.82e-07 1.25e-01 1.69e-02 2.19e-02
750| 2.01e-05 1.20e-07 5.32e-07 1.25e-01 1.69e-02 3.25e-02
875| 4.30e-06 1.16e-07 2.10e-07 1.25e-01 1.69e-02 3.79e-02
------------------------------------------------------------------
status: solved
timings: total: 3.80e-02s = setup: 4.35e-06s + solve: 3.80e-02s
lin-sys: 3.20e-02s, cones: 7.18e-04s, accel: 6.56e-04s
------------------------------------------------------------------
objective = 0.125039
------------------------------------------------------------------
Error : 0.2259107853405316
Solving for lambda = 0.02497591197515687
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.26e+00 1.50e-02 6.09e+00 -2.92e+00 1.69e-02 1.59e-04
250| 8.05e-04 1.01e-05 1.23e-04 3.10e-01 1.69e-02 1.37e-02
500| 2.98e-04 1.20e-06 1.85e-05 3.10e-01 1.69e-02 2.76e-02
750| 1.20e-04 1.01e-06 9.39e-06 3.10e-01 1.69e-02 4.12e-02
1000| 9.60e-05 7.37e-07 3.64e-06 3.10e-01 1.69e-02 5.46e-02
1250| 4.72e-05 8.34e-07 6.33e-06 3.10e-01 1.69e-02 6.76e-02
1500| 2.25e-05 3.04e-07 2.25e-06 3.10e-01 1.69e-02 7.83e-02
1750| 5.62e-06 2.28e-08 4.66e-07 3.10e-01 1.69e-02 8.91e-02
1950| 4.71e-06 6.17e-08 1.22e-07 3.10e-01 1.69e-02 9.78e-02
------------------------------------------------------------------
status: solved
timings: total: 9.78e-02s = setup: 4.45e-06s + solve: 9.78e-02s
lin-sys: 8.29e-02s, cones: 2.08e-03s, accel: 2.57e-03s
------------------------------------------------------------------
objective = 0.309833
------------------------------------------------------------------
Error : 0.21880188151191785
Solving for lambda = 0.06237961789907842
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 3.14e+00 3.74e-02 3.87e+01 -1.90e+01 1.69e-02 1.61e-04
250| 2.30e-03 2.35e-05 4.98e-04 7.59e-01 1.69e-02 1.09e-02
500| 6.38e-04 8.53e-06 1.13e-04 7.59e-01 1.69e-02 2.16e-02
750| 8.74e-04 9.97e-06 1.78e-05 7.59e-01 1.69e-02 3.20e-02
1000| 1.71e-04 3.89e-07 1.42e-05 7.59e-01 1.69e-02 4.27e-02
1250| 8.82e-05 2.24e-07 6.85e-06 7.59e-01 1.69e-02 5.30e-02
1500| 8.80e-06 1.58e-08 3.94e-07 7.59e-01 1.69e-02 6.34e-02
1675| 5.44e-06 8.79e-09 1.93e-07 7.59e-01 1.69e-02 7.04e-02
------------------------------------------------------------------
status: solved
timings: total: 7.04e-02s = setup: 4.46e-06s + solve: 7.04e-02s
lin-sys: 6.07e-02s, cones: 1.32e-03s, accel: 1.40e-03s
------------------------------------------------------------------
objective = 0.759146
------------------------------------------------------------------
Error : 0.19472289935884313
Solving for lambda = 0.15579878456913032
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 7.83e+00 9.34e-02 2.43e+02 -1.21e+02 1.69e-02 1.52e-04
250| 6.59e-03 2.83e-05 1.68e-03 1.81e+00 1.69e-02 1.07e-02
500| 5.37e-03 4.13e-05 8.56e-04 1.82e+00 1.69e-02 2.15e-02
750| 2.12e-03 7.02e-06 3.70e-04 1.82e+00 1.69e-02 3.22e-02
1000| 9.68e-04 1.98e-06 1.49e-04 1.82e+00 1.69e-02 4.30e-02
1250| 9.76e+00 1.16e-01 1.21e-01 1.66e+00 1.69e-02 5.40e-02
1500| 6.24e-05 2.01e-07 3.74e-06 1.82e+00 1.69e-02 6.48e-02
1750| 1.50e-05 1.46e-07 6.33e-07 1.82e+00 1.69e-02 7.51e-02
1900| 4.08e-06 1.56e-08 4.53e-08 1.82e+00 1.69e-02 8.15e-02
------------------------------------------------------------------
status: solved
timings: total: 8.15e-02s = setup: 4.07e-06s + solve: 8.15e-02s
lin-sys: 7.04e-02s, cones: 1.47e-03s, accel: 1.75e-03s
------------------------------------------------------------------
objective = 1.818814
------------------------------------------------------------------
Error : 0.16652260857219253
Solving for lambda = 0.3891216729235027
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.96e+01 2.33e-01 1.52e+03 -7.58e+02 1.69e-02 1.54e-04
250| 1.60e-02 1.13e-04 2.43e-03 4.22e+00 1.69e-02 1.15e-02
500| 3.91e-03 6.14e-05 1.38e-03 4.26e+00 5.49e-02 2.35e-02
750| 1.10e-03 8.02e-06 8.38e-04 4.27e+00 1.81e-01 3.44e-02
1000| 6.18e-06 3.09e-07 1.36e-06 4.27e+00 1.81e-01 4.43e-02
1025| 4.23e-06 1.61e-07 5.60e-07 4.27e+00 1.81e-01 4.53e-02
------------------------------------------------------------------
status: solved
timings: total: 4.53e-02s = setup: 3.94e-06s + solve: 4.53e-02s
lin-sys: 3.80e-02s, cones: 8.57e-04s, accel: 6.28e-04s
------------------------------------------------------------------
objective = 4.269514
------------------------------------------------------------------
Error : 0.16508930825747362
Solving for lambda = 0.9718668650563185
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 4.55e+00 5.83e-01 8.78e+02 -4.35e+02 1.81e-01 1.58e-04
250| 4.54e-03 3.43e-05 3.36e-03 1.01e+01 1.81e-01 1.07e-02
500| 6.93e-04 3.79e-06 4.76e-04 1.01e+01 1.81e-01 2.15e-02
725| 1.15e-07 5.49e-08 1.98e-10 1.01e+01 5.85e-01 3.20e-02
------------------------------------------------------------------
status: solved
timings: total: 3.20e-02s = setup: 3.95e-06s + solve: 3.20e-02s
lin-sys: 2.73e-02s, cones: 5.78e-04s, accel: 5.07e-04s
------------------------------------------------------------------
objective = 10.143419
------------------------------------------------------------------
Error : 0.17887188557683356
Solving for lambda = 2.427326127321828
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 3.52e+00 1.46e+00 1.69e+03 -8.37e+02 5.85e-01 5.24e-04
250| 9.19e-05 3.91e-06 1.18e-04 2.44e+01 5.85e-01 1.16e-02
350| 3.72e-08 1.71e-08 1.53e-07 2.44e+01 5.85e-01 1.63e-02
------------------------------------------------------------------
status: solved
timings: total: 1.63e-02s = setup: 3.98e-06s + solve: 1.63e-02s
lin-sys: 1.38e-02s, cones: 2.89e-04s, accel: 2.45e-04s
------------------------------------------------------------------
objective = 24.402281
------------------------------------------------------------------
Error : 0.2605486204939154
Solving for lambda = 6.062468369098836
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 8.79e+00 3.64e+00 1.06e+04 -5.29e+03 5.85e-01 1.55e-04
250| 8.09e-05 8.61e-05 3.41e-05 5.78e+01 5.85e-01 1.09e-02
325| 2.57e-06 1.02e-07 5.29e-06 5.78e+01 5.85e-01 1.46e-02
------------------------------------------------------------------
status: solved
timings: total: 1.46e-02s = setup: 4.46e-06s + solve: 1.46e-02s
lin-sys: 1.25e-02s, cones: 2.66e-04s, accel: 2.45e-04s
------------------------------------------------------------------
objective = 57.753523
------------------------------------------------------------------
Error : 0.4805364286021535
Solving for lambda = 15.141567633878541
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 2.20e+01 9.08e+00 6.64e+04 -3.31e+04 5.85e-01 1.54e-04
150| 1.74e-07 2.20e-06 8.87e-06 1.31e+02 6.39e+00 8.10e-03
------------------------------------------------------------------
status: solved
timings: total: 8.11e-03s = setup: 4.38e-06s + solve: 8.10e-03s
lin-sys: 6.28e-03s, cones: 1.46e-04s, accel: 1.09e-04s
------------------------------------------------------------------
objective = 130.731598
------------------------------------------------------------------
Error : 0.728897685138138
Solving for lambda = 37.81744603896349
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 5.02e+00 2.27e+01 3.78e+04 -1.88e+04 6.39e+00 3.06e-04
125| 1.11e-07 4.49e-07 3.88e-05 2.66e+02 6.39e+00 9.53e-03
------------------------------------------------------------------
status: solved
timings: total: 9.54e-03s = setup: 6.99e-06s + solve: 9.54e-03s
lin-sys: 7.88e-03s, cones: 2.06e-04s, accel: 1.81e-04s
------------------------------------------------------------------
objective = 265.601214
------------------------------------------------------------------
Error : 0.8689013367110517
Solving for lambda = 94.45252033943963
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 1.25e+01 5.66e+01 2.37e+05 -1.18e+05 6.39e+00 2.22e-04
250| 1.32e-06 1.19e-12 4.39e-03 3.80e+02 1.60e+01 1.32e-02
325| 1.47e-14 1.61e-08 2.15e-11 3.80e+02 6.73e+04 1.69e-02
------------------------------------------------------------------
status: solved
timings: total: 1.69e-02s = setup: 6.50e-06s + solve: 1.69e-02s
lin-sys: 1.28e-02s, cones: 2.90e-04s, accel: 2.29e-04s
------------------------------------------------------------------
objective = 379.601832
------------------------------------------------------------------
Error : 1.0000000000000042