When Gradients Don't Exist#
Not every useful loss function is differentiable everywhere. Common nondifferentiable functions in ML:
- — nondifferentiable at (the kink)
- — nondifferentiable at
- — the norm, nondifferentiable on the coordinate axes
- — nondifferentiable when
These functions are convex (Week 2's Jensen inequality still holds), so we can extend the notion of "gradient" to a set-valued generalisation.
Subgradients#
For a convex function , a vector is a subgradient at if:
This is the same first-order condition from Week 2, but now is not unique. The set of all subgradients at is the subdifferential .
Worked examples:
-
: (any value between and works). For , ; for , .
-
: . For , ; for , .
-
: At , — the convex combinations of the two active gradients.
The subgradient method is the direct extension of GD:
But it has a major drawback: the subgradient method is not a descent method. Unlike GD, taking a subgradient step does not guarantee . The function value can increase. For this reason, the subgradient method is less popular in ML than proximal methods.
Composite Objectives#
Many important problems have the form:
The classic example is the lasso:
where is -smooth (least squares) and is nonsmooth (sparsity-inducing regulariser).
The penalty encourages sparse solutions: many become exactly zero, which performs automatic feature selection.
Proximal Gradient (lite)#
The proximal gradient method (also called ISTA) elegantly handles composite objectives:
where the proximal operator of is:
This takes a gradient step on the smooth part , then "projects" the result through the proximal operator of .
The key example — soft thresholding for : For (vector case):
This shrinks each coordinate toward zero by , and sets it to exactly zero if it was within that threshold. This is why ISTA produces sparse solutions — coordinates smaller than the threshold vanish.
Worked example — 1D lasso step:
Take (gradient ), , .
- Gradient step: . If , .
- Proximal step: soft-threshold with . The result is .
The iterate moved from to — stopped at the threshold. If the gradient were stronger, the iterate would cross the threshold and move.
Early Stopping as Regularization#
An intriguing phenomenon: running GD on the unregularised least squares problem
and stopping early (after a small number of iterations) produces a solution that behaves similarly to ridge regression. The early-stopped GD solution has smaller norm and better generalisation than the fully-converged solution.
Intuition: GD started from explores the parameter space from the origin outward. The first few iterations capture large-scale, low-curvature directions (principal components); later iterations fit fine-grained, high-curvature directions that often correspond to noise. Stopping early implicitly truncates the high-frequency fitting.
This is a special case of implicit regularisation — the optimisation algorithm itself, not an explicit penalty term, biases the solution. This theme returns in Week 13 (nonconvex deep learning).
The subgradient of $|x|$ at $x = 0$ is any value in the interval ___.
A vector g is a subgradient of convex f at x if:
Browser lab#
Compare the subgradient method vs proximal gradient (ISTA) on L1-regularised least squares (lasso). Plot objective vs iteration.
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
n, d = 100, 50
w_true = rng.normal(size=d) * (rng.random(d) > 0.7)
X = rng.normal(size=(n, d))
y = X @ w_true + rng.normal(scale=0.3, size=n)
lam = 0.5
L = np.linalg.eigvalsh(X.T @ X / n).max()
eta = 1.0 / L
K = 200
def soft_thresh(v, thresh):
return np.sign(v) * np.maximum(np.abs(v) - thresh, 0.0)
def objective(w):
return 0.5 * np.mean((X @ w - y)**2) + lam * np.sum(np.abs(w))
w_sg = np.zeros(d)
loss_sg = [objective(w_sg)]
for k in range(1, K+1):
g = X.T @ (X @ w_sg - y) / n
subg = g + lam * np.sign(w_sg)
subg[np.abs(w_sg) < 1e-10] = g[np.abs(w_sg) < 1e-10] + lam * np.sign(g[np.abs(w_sg) < 1e-10])
w_sg = w_sg - (eta / np.sqrt(k)) * subg
loss_sg.append(objective(w_sg))
w_ista = np.zeros(d)
loss_ista = [objective(w_ista)]
for _ in range(K):
grad_g = X.T @ (X @ w_ista - y) / n
w_ista = soft_thresh(w_ista - eta * grad_g, eta * lam)
loss_ista.append(objective(w_ista))
plt.figure(figsize=(8, 5))
plt.plot(loss_sg, label="Subgradient method (η_k = η₀/√k)", alpha=0.8)
plt.plot(loss_ista, label="ISTA (proximal gradient)", linewidth=2)
plt.yscale("log"); plt.xlabel("Iteration"); plt.ylabel("Objective f(w)")
plt.title("Subgradient Method vs ISTA on Lasso")
plt.legend(); plt.grid(True, alpha=0.3); plt.show()