BRAGA

02 Writing02-02 Notes02-02-227

Saddle-Point Min-Max duality

Published 16 Apr 202610 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples
min⁡Ax=yf(y)+g(x)=max⁡umin⁡x,yf(y)+g(x)+(Ax−y)Tu\min_{Ax=y} f(y) + g(x) = \max_u \min_{x,y} f(y) + g(x) + (Ax-y)^T u
(1)

Deep Breakdown

What it tells me (Situation Awareness): This is a saddle-point reformulation of a constrained composite minimization problem. The primal problem min⁡Ax=yf(y)+g(x)\min_{Ax=y} f(y) + g(x) — minimize f(y)+g(x)f(y) + g(x) subject to the linear constraint Ax=yAx = y — is converted via Lagrangian duality to max⁡umin⁡x,y[f(y)+g(x)+(Ax−y)Tu]\max_u \min_{x,y} [f(y) + g(x) + (Ax-y)^T u], where uu is the dual variable (multiplier) for the constraint Ax−y=0Ax - y = 0. The (Ax−y)Tu(Ax-y)^T u term penalizes constraint violation. This is the foundation of ADMM (Alternating Direction Method of Multipliers), primal-dual algorithms, and operator splitting methods — all widely used in distributed optimization and sparse signal recovery. In DA/DS: ADMM is used for distributed Lasso, Elastic Net on large datasets, graph-regularized learning, and consensus optimization across compute nodes.

Flowchart

YES (f,g convex, Slaterholds)Primal:min_{Ax=y} f(y)+ g(x)Introduce dualvariable u forconstraint Ax=yLagrangian:L(x,y,u) = f(y) +g(x) + (Ax-y)^T uDual function:d(u) = min_{x,y}L(x,y,u)Dual problem:max_u d(u)Strong duality?(Slater'scondition)Primal = Dual:saddle pointexistsSolve via ADMMor primal-dualgradientx-update: min_xg(x) + rho/2*||Ax- y + u/rho||^2y-update: min_yf(y) + rho/2*||Ax- y + u/rho||^2u-update: u = u+ rho*(Ax - y)

Real-world use

Where it is used

Distributed Lasso: split data across machines, use ADMM to coordinate updates; basis pursuit (sparse signal recovery); total variation denoising; graph-regularized learning; SVM training for large-scale datasets; consensus optimization in federated learning.

Problem it solves

Many convex problems have the form min⁡f(y)+g(x)\min f(y) + g(x) subject to Ax=yAx = y, where ff and gg are each easy to minimize separately but hard to minimize jointly. For example: Lasso splits as f(y)=∥y−b∥2f(y) = \|y - b\|^2 (least squares on y=Xwy = Xw) and g(x)=λ∥x∥1g(x) = \lambda\|x\|_1 (L1 penalty on x=wx = w) with Ax=yAx = y enforcing x=wx = w. ADMM exploits this by solving the xx-update and yy-update separately, then updating the dual variable uu.

How it solves the problem

The saddle-point equality min⁡Ax=yf(y)+g(x)=max⁡umin⁡x,y[f(y)+g(x)+(Ax−y)Tu]\min_{Ax=y} f(y)+g(x) = \max_u \min_{x,y} [f(y)+g(x)+(Ax-y)^Tu] holds under convexity and Slater's condition. ADMM solves the saddle point by augmenting the Lagrangian with a proximal term ρ2∥Ax−y∥2\frac{\rho}{2}\|Ax-y\|^2 (penalty), then alternately minimizing over xx, minimizing over yy, and dually ascending over uu. Each sub-problem is often easily solvable (proximal operator of gg for xx-update; proximal of ff for yy-update).

Example in a business

A DS runs Lasso regression on a distributed dataset split across 4 machines. Each machine holds data XkX_k and labels yky_k. Using ADMM with the saddle-point structure: (1) each machine solves a local Lasso problem, (2) a central server averages the solutions and updates a shared multiplier uu, (3) repeat until consensus. This avoids sending all data to one machine while achieving the same solution as centralized Lasso.

The formula, piece by piece

  • min⁡Ax=y\min_{Ax=y}: constrained minimization subject to linear equality Ax=yAx = y
  • f(y)f(y): the "data fit" term in the yy-variable (e.g., least squares ∥y−b∥2\|y - b\|^2)
  • g(x)g(x): the "regularization" or "penalty" term in the xx-variable (e.g., λ∥x∥1\lambda\|x\|_1)
  • Ax=yAx = y: linear coupling constraint linking xx and yy variables (often just x=yx = y for consensus)
  • uu: the dual variable (Lagrange multiplier) for the constraint Ax−y=0Ax - y = 0; vector of same dimension as yy
  • (Ax−y)Tu(Ax - y)^T u: constraint residual (Ax−y)(Ax-y) dotted with dual variable; zero at feasibility
  • max⁡umin⁡x,y\max_u \min_{x,y}: minimax saddle-point structure; maximizing over dual uu while minimizing over primal (x,y)(x,y)
  • ADMM augmentation: add ρ2∥Ax−y∥2\frac{\rho}{2}\|Ax-y\|^2 to improve conditioning (augmented Lagrangian)

The formula in words

"Constrained minimization over linked variables xx and yy equals a minimax game: the primal player minimizes over (x,y)(x,y), and the dual player maximizes over the multiplier uu. At the saddle point, both players are satisfied and the constraint Ax=yAx = y is exactly enforced." Tiny example: min⁡x=yx2+∣y∣\min_{x=y} x^2 + |y| (i.e., min⁡xx2+∣x∣\min_x x^2 + |x|, which has solution x∗=0x^*=0). Saddle form: max⁡umin⁡x,y[x2+∣y∣+(x−y)u]\max_u \min_{x,y} [x^2 + |y| + (x-y)u]. yy-update: min⁡y∣y∣−yu=soft-threshold(u,1)=max⁡(∣u∣−1,0)⋅sign(u)\min_y |y| - yu = \text{soft-threshold}(u, 1) = \max(|u|-1, 0) \cdot \text{sign}(u). xx-update: min⁡xx2+xu=(x∗=−u/2)\min_x x^2 + xu = (x^* = -u/2). Dual: d(u)=(−u/2)2+∣u∣−(−u/2)u=−u2/4+∣u∣+u2/2=u2/4+∣u∣d(u) = (-u/2)^2 + |u| - (-u/2)u = -u^2/4 + |u| + u^2/2 = u^2/4 + |u|. At u=0u=0: d(0)=0=f(x∗)d(0) = 0 = f(x^*). ✓\checkmark

Critical Concepts

  • ADMM algorithm (Alternating Direction Method of Multipliers): Given augmented Lagrangian Lρ=f(y)+g(x)+uT(Ax−y)+ρ2∥Ax−y∥2\mathcal{L}_\rho = f(y) + g(x) + u^T(Ax-y) + \frac{\rho}{2}\|Ax-y\|^2:
  1. xx-update: xk+1=arg⁡min⁡x[g(x)+ρ2∥Ax−yk+uk/ρ∥2]x^{k+1} = \arg\min_x [g(x) + \frac{\rho}{2}\|Ax - y^k + u^k/\rho\|^2] 2. yy-update: yk+1=arg⁡min⁡y[f(y)+ρ2∥Axk+1−y+uk/ρ∥2]y^{k+1} = \arg\min_y [f(y) + \frac{\rho}{2}\|Ax^{k+1} - y + u^k/\rho\|^2] 3. Dual update: uk+1=uk+ρ(Axk+1−yk+1)u^{k+1} = u^k + \rho(Ax^{k+1} - y^{k+1})
  • Proximal operators: proxγf(v)=arg⁡min⁡x[f(x)+12γ∥x−v∥2]\text{prox}_{\gamma f}(v) = \arg\min_x [f(x) + \frac{1}{2\gamma}\|x-v\|^2]. ADMM yy-update is proxf/ρ(Ax+u/ρ)\text{prox}_{f/\rho}(Ax + u/\rho). For f=λ∥⋅∥1f = \lambda\|\cdot\|_1: proximal = soft-thresholding. For f=∥⋅∥2f = \|\cdot\|^2: proximal = ridge shrinkage.
  • Convergence: ADMM converges for convex f,gf, g (under mild conditions) at rate O(1/k)O(1/k) in the primal and dual residuals. Convergence does not require choosing ρ\rho optimally, but good ρ\rho speeds up practical convergence.
  • Distributed consensus ADMM: special case where A=IA = I and each node has its own xix_i and yy is the consensus variable (average of all xix_i).

Used in federated learning and parallel optimization.

Step-by-step calculation (tiny example)

Solve Lasso min⁡w∈R212∥y−Xw∥2+λ∥w∥1\min_{w \in \mathbb{R}^2} \frac{1}{2}\|y - Xw\|^2 + \lambda\|w\|_1 via ADMM with x=wx=w, z=wz=w (consensus variable). f(z)=λ∥z∥1f(z) = \lambda\|z\|_1, g(w)=12∥y−Xw∥2g(w) = \frac{1}{2}\|y-Xw\|^2, constraint w=zw = z. Augmented Lagrangian: Lρ=12∥y−Xw∥2+λ∥z∥1+uT(w−z)+ρ2∥w−z∥2\mathcal{L}_\rho = \frac{1}{2}\|y-Xw\|^2 + \lambda\|z\|_1 + u^T(w-z) + \frac{\rho}{2}\|w-z\|^2. ww-update (ridge with offset): (XTX+ρI)w=XTy+ρz−u(X^TX + \rho I)w = X^Ty + \rho z - u. zz-update (soft-threshold): z=Sλ/ρ(w+u/ρ)z = S_{\lambda/\rho}(w + u/\rho) where Sτ(v)=sign(v)max⁡(∣v∣−τ,0)S_\tau(v) = \text{sign}(v)\max(|v|-\tau, 0). Dual update: u←u+ρ(w−z)u \leftarrow u + \rho(w - z).

Code examples

Beginner example (Python)

python
import numpy as np

def soft_threshold(v: np.ndarray, threshold: float) -> np.ndarray:
    """Proximal operator of L1 norm: soft-thresholding."""
    return np.sign(v) * np.maximum(np.abs(v) - threshold, 0)


def admm_lasso(
    X: np.ndarray,
    y: np.ndarray,
    lam: float = 0.1,
    rho: float = 1.0,
    n_iter: int = 100,
    tol: float = 1e-6,
) -> np.ndarray:
    """ADMM for Lasso: min 0.5*||y-Xw||^2 + lam*||w||_1.

    Saddle-point form: min_{w=z} 0.5*||y-Xw||^2 + lam*||z||_1.
    """
    n, d = X.shape

    # Precompute for w-update: (X^T X + rho I)^{-1}
    XtX = X.T @ X
    XtX_rho = XtX + rho * np.eye(d)
    Xty = X.T @ y
    L = np.linalg.cholesky(XtX_rho)  # for fast solves

    w = np.zeros(d)
    z = np.zeros(d)
    u = np.zeros(d)
    for iteration in range(n_iter):
        # w-update: min_w 0.5*||y-Xw||^2 + rho/2*||w - z + u/rho||^2
        rhs = Xty + rho * (z - u / rho)
        w = np.linalg.solve(L.T, np.linalg.solve(L, rhs))
        z_prev = z.copy()

        # z-update: soft-threshold (proximal of L1)
        z = soft_threshold(w + u / rho, lam / rho)

        # Dual update
        u += rho * (w - z)

        # Check convergence
        primal_res = np.linalg.norm(w - z)
        dual_res = np.linalg.norm(rho * (z - z_prev))
        if primal_res < tol and dual_res < tol:
            print(f" ADMM converged at iteration {iteration}")
            break
    return w


np.random.seed(42)
n, d = 100, 20
X_data = np.random.randn(n, d)
true_w = np.zeros(d)
true_w[:5] = [2.0, -1.5, 1.0, -0.5, 0.3]
y_data = X_data @ true_w + 0.5 * np.random.randn(n)
w_lasso = admm_lasso(X_data, y_data, lam=0.5, rho=1.0, n_iter=200)
print(f"\nTrue non-zero: {np.where(true_w != 0)[0].tolist()}")
print(f"ADMM Lasso active (|w|>0.05): {np.where(np.abs(w_lasso) > 0.05)[0].tolist()}")
print(f"Top-5 estimated coefficients: {w_lasso[:5].round(3)}")
print(f"True coefficients (first 5): {true_w[:5]}")

Beginner example (R)

r
# ADMM for Lasso: saddle-point structure
soft_threshold <- function(v, threshold) sign(v) * pmax(abs(v) - threshold, 0)
admm_lasso_r <- function(X, y, lam = 0.1, rho = 1.0, n_iter = 100, tol = 1e-6) {
  n <- nrow(X); d <- ncol(X)
  XtX_rho <- t(X) %*% X + rho * diag(d)
  Xty <- t(X) %*% y
  w <- numeric(d); z <- numeric(d); u <- numeric(d)
  for (i in seq_len(n_iter)) {
    # w-update
    rhs <- Xty + rho * (z - u / rho)
    w <- solve(XtX_rho, rhs)
    z_prev <- z
    # z-update: soft-threshold
    z <- soft_threshold(w + u/rho, lam/rho)
    # dual update
    u <- u + rho * (w - z)
    if (sqrt(sum((w-z)^2)) < tol && sqrt(sum((rho*(z-z_prev))^2)) < tol) {
      cat(sprintf("Converged at iteration %d\n", i))
      break
    }
  }
  w
}
set.seed(42)
n <- 100; d <- 20
X_data <- matrix(rnorm(n*d), n, d)
true_w <- c(2, -1.5, 1, -0.5, 0.3, rep(0, 15))
y_data <- X_data %*% true_w + 0.5 * rnorm(n)
w_est <- admm_lasso_r(X_data, y_data, lam = 0.5, rho = 1.0)
cat("True w[1:5]:", round(true_w[1:5], 3), "\n")
cat("ADMM w[1:5]:", round(w_est[1:5], 3), "\n")

Professional example (Python)

python
import numpy as np
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
np.random.seed(42)

# -- Distributed consensus ADMM for Lasso (2 compute nodes) -------------------
# Split data across 2 nodes; each solves local subproblem; consensus via ADMM
N_TOTAL = 300
D = 15
n_nodes = 2
X_raw = np.random.randn(N_TOTAL, D)
scaler = StandardScaler()
X = scaler.fit_transform(X_raw)
true_w = np.array([1.5, -1.0, 0.8, -0.6, 0.4] + [0] * (D - 5))
y = X @ true_w + 0.3 * np.random.randn(N_TOTAL)
LAM = 0.3
RHO = 2.0
N_ITER = 200
TOL = 1e-6

def soft_threshold(v: np.ndarray, tau: float) -> np.ndarray:
    return np.sign(v) * np.maximum(np.abs(v) - tau, 0)


# Split data across nodes
indices = [np.arange(i * N_TOTAL // n_nodes, (i + 1) * N_TOTAL // n_nodes) for i in range(n_nodes)]
X_nodes = [X[idx] for idx in indices]
y_nodes = [y[idx] for idx in indices]

# Pre-compute per-node matrices
node_XtX_rho = [X_k.T @ X_k + RHO * np.eye(D) for X_k in X_nodes]
node_Xty = [X_k.T @ y_k for X_k, y_k in zip(X_nodes, y_nodes)]

# Consensus ADMM state
w_local = [np.zeros(D) for _ in range(n_nodes)]  # per-node estimates
z_global = np.zeros(D)  # consensus variable
u_dual = [np.zeros(D) for _ in range(n_nodes)]  # per-node dual vars

for iteration in range(N_ITER):
    # Step 1: each node updates its local w (ridge sub-problem)
    for k in range(n_nodes):
        rhs = node_Xty[k] + RHO * (z_global - u_dual[k])
        w_local[k] = np.linalg.solve(node_XtX_rho[k], rhs)
    z_prev = z_global.copy()

    # Step 2: consensus update (average + soft-threshold)
    w_avg = np.mean(w_local, axis=0)
    u_avg = np.mean(u_dual, axis=0)
    z_global = soft_threshold(w_avg + u_avg, LAM / (RHO * n_nodes))

    # Step 3: per-node dual update
    for k in range(n_nodes):
        u_dual[k] += w_local[k] - z_global

    # Convergence check
    primal_res = max(np.linalg.norm(w_local[k] - z_global) for k in range(n_nodes))
    dual_res = RHO * np.linalg.norm(z_global - z_prev)
    if primal_res < TOL and dual_res < TOL:
        print(f"Distributed ADMM converged at iteration {iteration}")
        break

# Compare to centralized Lasso
centralized = Lasso(alpha=LAM / N_TOTAL, fit_intercept=False, max_iter=10000)
centralized.fit(X, y)
print(f"\nDistributed vs Centralized Lasso")
print(f"{'Coef':<8} {'True':>8} {'Distributed':>14} {'Centralized':>14}")
for j in range(D):
    print(f" w[{j}] {true_w[j]:>8.3f} {z_global[j]:>14.4f} {centralized.coef_[j]:>14.4f}")
print(f"\n||w_dist - w_central||: {np.linalg.norm(z_global - centralized.coef_):.4e}")

Source: MML1 p. 200

#calculus#continuous-optimisation#constrained-optimisation-and-lagrange-multipliers

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close