Chambolle-Pock Algorithm for Solving LASSO
This note aims at showing how to solve LASSO by using Chambolle-Pock (CP) algorithm at the most basic level. CP is a primal-dual method solving both the primal and dual problem at the same time. However, it is important to notice that the iterative dual solutions are not guaranteed to be feasible.
The code for this post is available on my Github.
Basic of Chambolle-Pock Algorithm
Problem:
Primal and dual problem
Initialization: , and . Here one should choose .
Iteration:
Applying on LASSO
The LASSO setup has primal and dual problem defined as follows
Here, the dual optimal solution is the closest point projection of onto .
This dual problem is a little bit different from aforementioned dual problem since we set this is possible since is symmetric, i.e. .
We obtain such above dual problem since
and
Let , then we should have
and let , this is soft thresholding operator evaluated by
The following figues illustrates the convergence of CP for a LASSO problem with dual problem in
Remark. Here since the dual solutions obtained from the CP are not dual feasible, we can not use the dual gap as a stopping metric/condition as usual. Here we consider another quite interesting metric as follows. Let be the hyper-plane defined by . Then it is clear by the optimality condition that , So for a point converging to , we can quantify the convergence of by two quantities , where is the projection of onto , which can be computed in closed form. Here or for and .
Code
The full code is available on my Github. Here we provide the main CP procedure written in Python.
def dual_prox(b, p, sigma):
return (p - sigma*b )/ (1+sigma)
def primal_prox(z, lbd, tau):
return np.sign(z) * np.maximum(np.abs(z) - lbd*tau, 0.)
def lasso_chambolle_pock(A, b, lbd, max_iters=100):
x0 = np.zeros(A.shape[1])
u0 = np.zeros(A.shape[0])
lip = np.sqrt((A**2).sum())
sigma = 0.5/lip
tau = 0.5/lip
theta=1
x = x0.copy()
u = u0.copy()
xbar = x0.copy()
x_list = []
u_list = []
for i in range(max_iters):
xpre = x.copy()
u = dual_prox(b, p= u + sigma * A @ xbar, sigma=sigma)
x = primal_prox(z= xpre - tau * A.T @ u, lbd=lbd, tau=tau)
xbar = x + theta * (x-xpre)
x_list += [x]
u_list += [u]
x_array= np.vstack(x_list).T
u_array = np.vstack(u_list).T # change sign of u
return x_array, u_array
x_array, u_array = lasso_chambolle_pock(A, b, lbd, max_iters=50)
u_array = -u_array # remember to change sign of u suitable for our dual problem
r_array = b.reshape(2, 1) - A @ x_array