.. _atoms_example:
Using constraints
=================
This tutorial illustrates how to use RegReg to solve problems using the
container class. We illustrate with an example:
.. math::
\frac{1}{2}||y - \beta||^{2}_{2} \ \text{subject to} \
||D\beta||_{1} \leq \delta_1, \|\beta\|_1 \leq \delta_2
This problem is solved by solving a dual problem, following the general
derivation in the TFOCS paper
.. math::
\frac{1}{2}||y - D^Tu_1 - u_2||^{2}_{2} + \delta_1 \|u_1\|_{\infty}
+ \delta_2 \|u_2\|_{\infty}
For a general loss function, the general objective has the form
.. math::
{\cal L}_{\epsilon}(\beta) \ \text{subject to} \ ||D\beta||_{1}
\leq \delta_1, \|\beta\|_1 \leq \delta_2
which is solved by minimizing the dual
.. math::
{\cal L}^*_{\epsilon}(-D^Tu_1-u_2) + \delta_1 \|u_1\|_{\infty} +
\delta_2 \|u_2\|_{\infty}
Recall that for the sparse fused LASSO
.. math::
D = \left(\begin{array}{rrrrrr} -1 & 1 & 0 & 0 & \cdots & 0 \\ 0 &
-1 & 1 & 0 & \cdots & 0 \\ &&&&\cdots &\\ 0 &0&0&\cdots & -1 & 1
\end{array}\right)
To solve this problem using RegReg we begin by loading the necessary
numerical libraries
.. mpl-interactive::
.. nbplot::
:format: python
import matplotlib.pyplot as plt
import numpy as np
from scipy import sparse
import regreg.api as rr
from regreg.affine.fused_lasso import difference_transform
Next, let's generate an example signal, and solve the Lagrange form of
the problem
.. nbplot::
:format: python
fig = plt.figure(figsize=(12,8))
ax = fig.gca()
Y = np.random.standard_normal(500); Y[100:150] += 7; Y[250:300] += 14
ax.scatter(np.arange(Y.shape[0]), Y)
.. nbplot::
:format: python
loss = rr.signal_approximator(Y)
.. math::
\frac{C}{2}\left\|\beta - Y_{}\right\|^2_2
.. nbplot::
:format: python
sparsity = rr.l1norm(len(Y), lagrange=1.4)
D = difference_transform(np.arange(Y.shape[0]))
fused = rr.l1norm.linear(D, lagrange=15.5)
fused
.. math::
\lambda_{} \|X_{}\beta\|_1
.. nbplot::
:format: python
problem = rr.dual_problem.fromprimal(loss, sparsity, fused)
problem
.. math::
\begin{aligned} \text{minimize}_{\beta} & f(\beta) + g(\beta) \\
f(\beta) &= \sup_{u \in \mathbb{R}^{p} } \left[ \langle
X_{1}\beta, u \rangle - \left({\cal Z}(u) +
\frac{L_{1}}{2}\|u\|^2_2 + \left \langle \eta_{1}, u \right
\rangle + \gamma_{1} \right) \right] \\ g(\beta) &=
I^{\infty}(\|\beta[g0]\|_{\infty} \leq \delta_{0}) +
I^{\infty}(\|\beta[g1]\|_{\infty} \leq \delta_{1}) \\
\end{aligned}
.. nbplot::
:format: python
solution = problem.solve(tol=1.e-14)
.. nbplot::
:format: python
ax.plot(solution, c='yellow', linewidth=5, label='Lagrange')
fig
We will now solve this problem in constraint form, using the achieved
values
:math:`\delta_1 = \|D\widehat{\beta}\|_1, \delta_2=\|\widehat{\beta}\|_1`.
By default, the container class will try to solve this problem with the
two-loop strategy.
.. nbplot::
:format: python
delta1 = np.fabs(D * solution).sum()
delta2 = np.fabs(solution).sum()
fused_constraint = rr.l1norm.linear(D, bound=delta1)
sparsity_constraint = rr.l1norm(Y.shape[0], bound=delta2)
.. nbplot::
:format: python
constrained_problem = rr.dual_problem.fromprimal(loss,
fused_constraint, sparsity_constraint)
constrained_problem
.. math::
\begin{aligned} \text{minimize}_{\beta} & f(\beta) + g(\beta) \\
f(\beta) &= \sup_{u \in \mathbb{R}^{p} } \left[ \langle
X_{1}\beta, u \rangle - \left({\cal Z}(u) +
\frac{L_{1}}{2}\|u\|^2_2 + \left \langle \eta_{1}, u \right
\rangle + \gamma_{1} \right) \right] \\ g(\beta) &= \lambda_{0}
\|\beta[g0]\|_{\infty} + \lambda_{1} \|\beta[g1]\|_{\infty} \\
\end{aligned}
.. nbplot::
:format: python
constrained_solution = constrained_problem.solve(tol=1.e-12)
ax.plot(constrained_solution, c='green', linewidth=3, label='Constrained')
fig
Mixing penalties and constraints
--------------------------------
As atoms generally have both bound form and Lagrange form, we can solve
problems with a mix of the two penalties. For instance, we might try
minimizing this objective
.. math::
\frac{1}{2}||y - \beta||^{2}_{2} + \lambda \|\beta\|_1 \text{
subject to} \ ||D\beta||_{1} \leq \delta.
.. nbplot::
:format: python
mixed_problem = rr.dual_problem.fromprimal(loss, fused_constraint, sparsity)
mixed_problem
.. math::
\begin{aligned} \text{minimize}_{\beta} & f(\beta) + g(\beta) \\
f(\beta) &= \sup_{u \in \mathbb{R}^{p} } \left[ \langle
X_{1}\beta, u \rangle - \left({\cal Z}(u) +
\frac{L_{1}}{2}\|u\|^2_2 + \left \langle \eta_{1}, u \right
\rangle + \gamma_{1} \right) \right] \\ g(\beta) &= \lambda_{0}
\|\beta[g0]\|_{\infty} + I^{\infty}(\|\beta[g1]\|_{\infty} \leq
\delta_{1}) \\ \end{aligned}
.. nbplot::
:format: python
mixed_solution = mixed_problem.solve(tol=1.e-12)
ax.plot(mixed_solution, '--', linewidth=6, c='gray', label='Mixed')
ax.legend()
fig
.. nbplot::
:format: python
np.fabs(D * mixed_solution).sum(), fused_constraint.atom.bound
(33.67439163971784, 33.674299924228016)
Atoms have affine offsets
-------------------------
Suppose that instead of shrinking the values in the fused LASSO to 0, we
want to shrink them all towards a given vector :math:`\alpha`
This can be achieved, at least conceptually by minimizing
.. math::
\frac{1}{2}||y - \beta||^{2}_{2} + \lambda_{1}||D\beta||_{1} + \lambda_2 \|\beta-\alpha\|_1
with
Everything is roughly the same as in the fused LASSO, we just need to
change the second seminorm to have this affine offset.
Now we can create the problem object, beginning with the loss function
.. nbplot::
:format: python
alpha = np.linspace(0,10,500) - 3
shrink_to_alpha = rr.l1norm(Y.shape, offset=alpha, lagrange=3.)
shrink_to_alpha
.. math::
\lambda_{} \|\beta - \alpha_{}\|_1
which creates an affine\_atom object with :math:`\lambda_2=3`. That is,
it creates the penalty
.. math::
3 \|\beta-\alpha\|_{1}
that will be added to a smooth loss function. Next, we create the fused
lasso matrix and the associated l1norm object,
Here we first created D, converted it a sparse matrix, and then created
an l1norm object with the sparse version of D and
:math:`\lambda_1 = 25.5`. Finally, we can create the final problem
object, and solve it.
.. nbplot::
:format: python
loss_alpha = rr.signal_approximator(Y + alpha)
fig_alpha = plt.figure(figsize=(12,8))
ax_alpha = fig_alpha.gca()
alpha_problem = rr.dual_problem.fromprimal(loss, shrink_to_alpha, fused)
alpha_solution = alpha_problem.solve(tol=1.e-14)
ax_alpha.scatter(np.arange(Y.shape[0]), Y + alpha)
ax_alpha.plot(alpha_solution, c='gray', linewidth=5, label=r'$\hat{Y}$')
ax_alpha.plot(alpha, c='black', linewidth=3, label=r'$\alpha$')
ax_alpha.legend()
We can then plot solution to see the result of the regression,
Atoms can be smoothed
=====================
Atoms can be smoothed using the same smoothing techniques described in
`NESTA `__ and
`TFOCS `__
Recall that the sparse fused lasso minimizes the objective
.. math::
\frac{1}{2}||y - \beta||^{2}_{2} + \lambda_{1}||D\beta||_{1} + \lambda_2 \|\beta\|_1
The penalty can be smoothed to create a smooth function object which can
be solved with FISTA.
.. nbplot::
:format: python
Q = rr.identity_quadratic(0.1, 0, 0, 0)
smoothed_sparsity = sparsity.smoothed(Q)
smoothed_sparsity
.. math::
\sup_{u \in \mathbb{R}^{p} } \left[ \langle \beta, u \rangle - \left(I^{\infty}(\|u\|_{\infty} \leq \delta_{}) + \frac{L_{}}{2}\|u\|^2_2 \right) \right]
.. nbplot::
:format: python
smoothed_fused = fused.smoothed(Q)
.. nbplot::
:format: python
problem = rr.smooth_sum([loss, smoothed_sparsity, smoothed_fused])
solver = rr.FISTA(problem)
solver.fit(tol=1.e-10)
smooth_solution = solver.composite.coefs.copy()
.. nbplot::
:format: python
smooth_fig = plt.figure(figsize=(12,8))
smooth_ax = smooth_fig.gca()
smooth_ax.plot(solution, 'k', linewidth=5, label='Unsmoothed')
smooth_ax.plot(smooth_solution, '--', c='gray', linewidth=4, label='Smoothed')
smooth_ax.legend() #doctest: +ELLIPSIS
which has both the loss function and the seminorm represented in it. We
will estimate :math:`\beta` for various values of :math:`\epsilon`:
.. nbplot::
:format: python
solns = []
for eps in [.5**i for i in range(15)]:
Q = rr.identity_quadratic(eps, 0, 0, 0)
smoothed_sparsity = sparsity.smoothed(Q)
smoothed_fused = fused.smoothed(Q)
problem = rr.smooth_sum([loss, smoothed_sparsity, smoothed_fused])
solver = rr.FISTA(problem)
solver.fit(tol=1.e-10)
solns.append(solver.composite.coefs.copy())
smooth_ax.plot(solns[-1], '--')
smooth_fig
Of course, we don't have to smooth both atoms. We could just smooth the
fused term.
.. nbplot::
:format: python
smoothed_fused_constraint = fused_constraint.smoothed(rr.identity_quadratic(1e-3,0,0,0))
smooth_part = rr.smooth_sum([loss, smoothed_fused_constraint])
smoothed_constrained_problem = rr.simple_problem(smooth_part, sparsity_constraint)
smoothed_constrained_solution = smoothed_constrained_problem.solve(tol=1e-12)
.. nbplot::
:format: python
ax.plot(smoothed_constrained_solution, c='black', linewidth=1, label='Smoothed')
ax.legend()
fig
.. code-links::
:timeout: -1