Composite Objectives#
Many machine learning problems have a composite structure: a smooth loss plus a nonsmooth regulariser:
where is smooth (e.g., squared loss, logistic loss) and is convex but possibly nonsmooth (e.g., norm, indicator of a convex set, nuclear norm). The prototypical example is the lasso:
Gradient descent on the full objective fails because is not differentiable — the subgradient of at a coordinate where is the entire interval , giving no unique direction.
Proximal Operator#
The proximal operator of a convex function is:
The prox evaluates at while penalising distance from . Intuitively, it takes a step toward minimising but stays close to the reference point , with controlling the trade-off. The prox is well-defined for any closed convex , even nondifferentiable ones.
Key examples:
-
(lasso penalty): , where the soft-thresholding operator is applied coordinate-wise.
-
(indicator of closed convex set ): , the Euclidean projection onto .
-
: — prox of zero is the identity.
Worked soft-threshold example. Let , . Then . For with : (shrunk to exactly zero). The threshold acts as a "kill zone": any is mapped to zero, implementing sparsity.
ISTA and FISTA#
The Iterative Shrinkage-Thresholding Algorithm (ISTA) is the proximal equivalent of gradient descent for composite problems:
Each iteration: take a gradient step on the smooth , then apply the prox of the nonsmooth . For lasso with and :
ISTA converges at rate — the same as gradient descent on smooth problems. This is remarkable: the nondifferentiable term does not degrade the worst-case rate.
FISTA (Fast ISTA) adds Nesterov momentum to achieve — the optimal rate for first-order methods on this class:
where is the Nesterov sequence. The acceleration comes from extrapolation — evaluating the gradient at a point beyond the current iterate, extrapolated in the direction of recent progress.
Why slow convergence is acceptable for lasso. Lasso is typically solved once per for a fixed dataset of moderate dimension. with a well-tuned step size converges to high accuracy in hundreds of iterations — seconds in practice. The simplicity of ISTA/FISTA (no linear system solve) makes them the go-to first-order choice for structured convex problems.
Operator Splitting and ADMM#
ADMMAlternating Direction Method of Multipliers (Alternating Direction Method of Multipliers) solves problems of the form:
where and are convex (possibly nonsmooth). The variables and are decoupled in the objective but coupled through a linear constraint. ADMM iterates:
x_{k+1} &= \arg\min_x \left[ f(x) + \frac{\rho}{2}\|Ax + Bz_k - c + u_k\|_2^2 \right] \quad (\text{$x$-update})\\[4pt] z_{k+1} &= \arg\min_z \left[ g(z) + \frac{\rho}{2}\|Ax_{k+1} + Bz - c + u_k\|_2^2 \right] \quad (\text{$z$-update})\\[4pt] u_{k+1} &= u_k + Ax_{k+1} + Bz_{k+1} - c \quad (\text{dual update}) \end{aligned}$$ where $\rho > 0$ is a penalty parameter and $u$ is the scaled dual variable. Each $x$- and $z$-subproblem involves only one of the two objective terms — this is the **splitting** that gives ADMM its power. When $f$ and $g$ have simple proximal operators (e.g., $f$ is a quadratic, $g$ is $\ell_1$), each subproblem is cheap. **Consensus ADMM.** A common pattern is $f(x) = \sum_i f_i(x_i)$ and $g$ enforces consensus $x_i = z$ for all $i$. Each $f_i$ can be handled independently (in parallel), then the $z$-update averages the results. This maps directly to distributed optimisation. **When ADMM helps:** - Problems that decompose naturally into two blocks with cheap prox operators (e.g., lasso in consensus form across data shards). - Distributed settings where the $x$-update can be parallelised. - Problems where an IPM is too expensive but ISTA on the monolithic problem converges slowly due to coupling. **When ADMM is slower:** For tightly coupled objectives where the $x$- and $z$-updates are as hard as the original problem, ADMM adds overhead (the penalty parameter $\rho$ and dual variable $u$) without reducing per-iteration cost. ## Choosing Prox vs IPM vs SGD A quick decision guide for structured problems: | Situation | Method | |---|---| | Small-medium LP/QP/SOCP, need high accuracy | IPM | | Composite $f + g$ with simple $\operatorname{prox}_g$ | ISTA / FISTA | | Multi-block, distributed, or consensus structure | ADMM | | Nonconvex, huge data, general structure | <Glossary term="SGD" /> / <Glossary term="Adam" /> | | $\ell_1$ / nuclear norm regularisation with known $\operatorname{prox}$ | ISTA / FISTA | The patterns repeat across applications: find the structure, match it to the method, and exploit cheap proximal operators when they exist. <Exercise type="fill-blank" question="Soft-threshold $\operatorname{soft}(v, t)$ applied to $v = 0.8$ with $t = 1.5$ produces ____." correctAnswer="0" explanation="$\operatorname{soft}(0.8, 1.5) = \operatorname{sign}(0.8) \cdot \max(0.8 - 1.5, 0) = \max(-0.7, 0) = 0$. Any $|v| \leq t$ maps to zero." /> <Quiz questions={[ { type: "multiple-choice", question: "The proximal operator of an indicator function $g = I_C$ (for a closed convex set $C$) is:", options: ["The identity mapping", "The soft-threshold operator", "The Euclidean projection onto $C$", "The subgradient of $g$"], correctIndex: 2, explanation: "$\operatorname{prox}_{I_C}(v) = \arg\min_{x \in C} \|x - v\|_2^2 = \operatorname{proj}_C(v)$, the Euclidean projection." }, { type: "multiple-choice", question: "An ISTA iteration for $\min f(x) + g(x)$ with smooth $f$ and nonsmooth $g$ takes the form:", options: ["$x_{k+1} = x_k - \eta \nabla f(x_k) - \eta \partial g(x_k)$", "$x_{k+1} = \operatorname{prox}_{\eta g}(x_k - \eta \nabla f(x_k))$", "$x_{k+1} = \operatorname{prox}_{\eta f}(x_k - \eta \partial g(x_k))$", "$x_{k+1} = \operatorname{proj}_C(x_k) - \eta \nabla f(x_k)$"], correctIndex: 1, explanation: "ISTA = gradient step on $f$ followed by prox of $g$. The prox handles the nondifferentiable term." }, { type: "multiple-choice", question: "ADMM is particularly well-suited to problems where:", options: ["The objective is strictly convex", "The variables split naturally into blocks with cheap prox operators", "The problem is an LP with many constraints", "Stochastic gradients are available"], correctIndex: 1, explanation: "ADMM shines when the problem decomposes into blocks whose $x$ and $z$ updates are cheap to compute independently (e.g., via prox or quadratic solves)." } ]} /> ## Browser lab Compare GD on ridge regression (smooth) vs ISTA on lasso (composite) on a synthetic dataset. Plot objective value per iteration to contrast convergence behaviour. ```python import numpy as np import matplotlib.pyplot as plt np.random.seed(42) n, d = 100, 50 X = np.random.randn(n, d) beta_true = np.zeros(d) beta_true[:5] = [3, -2, 1.5, -1, 0.5] # sparse y = X @ beta_true + 0.5 * np.random.randn(n) lam = 0.5 def soft_thresh(v, t): return np.sign(v) * np.maximum(np.abs(v) - t, 0) # Ridge: smooth objective, GD def ridge_grad(beta): return X.T @ (X @ beta - y) / n + lam * beta # Lasso ISTA def lasso_grad_smooth(beta): return X.T @ (X @ beta - y) / n # GD on ridge eta_ridge = 0.1 n_iter = 200 beta_ridge = np.zeros(d) ridge_obj = [] for _ in range(n_iter): beta_ridge = beta_ridge - eta_ridge * ridge_grad(beta_ridge) obj = 0.5 * np.sum((X @ beta_ridge - y)**2) / n + 0.5 * lam * np.sum(beta_ridge**2) ridge_obj.append(obj) # ISTA on lasso eta_lasso = 0.1 beta_lasso = np.zeros(d) lasso_obj = [] for _ in range(n_iter): beta_lasso = soft_thresh(beta_lasso - eta_lasso * lasso_grad_smooth(beta_lasso), eta_lasso * lam) obj = 0.5 * np.sum((X @ beta_lasso - y)**2) / n + lam * np.sum(np.abs(beta_lasso)) lasso_obj.append(obj) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) ax1.semilogy(ridge_obj, "b-", linewidth=1.5, label="GD on Ridge") ax1.semilogy(lasso_obj, "r-", linewidth=1.5, label="ISTA on Lasso") ax1.set_xlabel("Iteration") ax1.set_ylabel("Objective (log scale)") ax1.set_title("Convergence: Ridge (GD) vs Lasso (ISTA)") ax1.legend() ax1.grid(True, alpha=0.3) ax2.stem(range(d), beta_lasso, linefmt="r-", markerfmt="ro", basefmt="k-", label="Lasso (ISTA)") ax2.stem(range(d), beta_true, linefmt="b--", markerfmt="b^", basefmt="k-", label="True") ax2.set_xlabel("Coefficient index") ax2.set_ylabel("Value") ax2.set_title("Recovered vs True Coefficients (Lasso)") ax2.legend() plt.tight_layout() plt.show() print(f"Lasso nonzero coefficients: {np.sum(np.abs(beta_lasso) > 1e-4)}") print(f"True nonzero coefficients: {np.sum(np.abs(beta_true) > 1e-4)}") print(f"Estimation error ||beta - beta_true||: {np.linalg.norm(beta_lasso - beta_true):.4f}") ```