06 / 27 · Linear Regression
Gradient descent
Minimizing a loss by repeated small steps against its gradient: why the negative gradient is the direction of steepest descent, the exact analysis on a quadratic that gives the largest safe step and the best step, the condition number as the speed limit and feature scaling as its cure, convergence guarantees for smooth, convex and nonconvex losses, backtracking line search, and losses without a closed form.
On this page 12
The least-squares note ended with two problems of the closed form. It needs the matrix and a solve that costs , out of reach for a million features; and it exists only for squared error, while the Huber loss, the absolute loss and every classification loss in this course have no formula for their minimizer. Both problems have one answer. Instead of solving for the point where the gradient vanishes, walk towards it: start anywhere, compute the gradient, take a small step against it, repeat. This note analyses that walk on least squares, where everything can be computed exactly, and then states what survives for general losses.
Notation. is the loss to minimize, here the mean squared error of the least-squares note, with design matrix , and its minimizer. is the gradient, the iterate after steps and the step size or learning rate. For least squares the Hessian is the constant matrix ; its smallest and largest eigenvalues are and , and is its condition number.
Which way is downhill?#
Near a point , a differentiable function is close to its linear approximation:
Among all steps of a fixed small length , the change is dominated by , and by the Cauchy–Schwarz inequality this is at least , with equality only for . The negative gradient is the direction of steepest descent, and a small enough step along it lowers whenever the gradient is not zero. Gradient descent repeats such steps:
For least squares, the gradient from the normal-equations derivation, divided by , is
two matrix–vector products, operations, and only when is sparse. Nothing of size is ever formed.
Worked example. The four points of the least-squares note, , , with columns and . From the residual is , so , and . With , and ; the next gradient is , and . The loss falls towards its minimum at , but the slope coefficient went from 0 past its target to and back to . The overshoot is the subject of the next section.
How large a step? The quadratic case#
For least squares the loss is exactly quadratic around its minimum. Its gradient vanishes at and its Hessian is everywhere, so
Write the error as . One step gives
is symmetric with eigenvalues and orthonormal eigenvectors . In that basis is diagonal, and the component of the error along is multiplied by at every step, independently of the others. Three consequences:
- The largest safe step. All components shrink, from every starting point, if and only if for all , that is (assuming ). At the steepest component flips sign and grows at every step: descent diverges from almost every start, however close to the minimum; only a start whose steepest component is exactly zero escapes.
- The best constant step. The speed is set by the worst factor . The first term decreases in and the second eventually increases, so the maximum is smallest where : , with .
- The number of steps. Shrinking the error by a factor takes steps, and for large , so about : linear in the condition number.
How much one step shrinks the error
Worked example. For the four points, . Its trace is 9 and its determinant 5, so the eigenvalues are : and , . Steps above diverge; the best step is , with factor , and a thousandfold reduction of the error takes steps. The step , the usual safe default, has the factor as a bound and needs 80 steps from . The data-features note’s example of two features on scales 1 and 100 has , and the same thousandfold reduction needs about steps.
Why is the error so slow along ? Because is the curvature in the flattest direction: the gradient there is small, so the steps are small, while the step size cannot grow because of the steepest direction. The iterate zigzags across a narrow valley and creeps along its floor.
Scaling the features#
The eigenvalues of depend on the units of the features. In the four-point example the off-diagonal entry of is , the coupling between intercept and slope created by the feature’s nonzero mean, and the diagonal entries 2 and 7 differ because has a larger second moment than the constant column. Centring zeroes the coupling; scaling it to unit variance equalizes the diagonal. For the standardized feature , , , every direction has the same curvature, and the gradient points straight at the minimum: the step reaches in a single step.
Gradient descent on a stretched and on a round bowl
The effect on real data is large. On the wine data bundled with scikit-learn, predicting alcohol content from the other 12 measurements (whose standard deviations range from about 0.1 to 300), the condition number of the least-squares Hessian is with raw features and 45 after standardization. Gradient descent with reduces the excess loss , relative to its initial value, to , , and after 10, 100, 1,000 and 10,000 steps on the raw features; on the standardized features to after 10 steps, after 100 and machine precision after 1,000 (the Code section). Scaling cannot remove the remaining , which comes from correlations between the features. That would need a rotation as well, which is what second-order methods effectively apply.
Standardization changes nothing about the model: a linear model on standardized features is a linear model on the original ones with rescaled weights and a shifted intercept. It changes only the geometry the optimizer walks through. The scaling statistics are computed on the training data, as the features note requires.
Beyond quadratics: what is guaranteed#
Most losses are not quadratic, but the analysis carries over through two properties. A differentiable is -smooth if its gradient is -Lipschitz, ; for twice differentiable this means that no eigenvalue of the Hessian exceeds in absolute value. It is -strongly convex if ; for twice differentiable , every Hessian eigenvalue is at least . Least squares with independent columns is both, with the and of .
The descent lemma. Smoothness bounds the function above by a parabola: . Put :
Every step with decreases the loss, the same threshold as in the quadratic case, and the guaranteed decrease is largest at , where it equals . Three guarantees follow. They are the theory behind the practical routine, which needs none of the rates: compute the gradient, check that the loss falls at every step, scale the inputs, and keep the largest step that does not diverge.
- Nonconvex, -smooth, . Summing the decreases over steps, , so the smallest gradient norm seen satisfies . The gradient becomes small, nothing more: the limit can be a local minimum or a saddle point. This is the only general guarantee for neural networks, and it assumes smoothness, which networks with ReLU activations do not have everywhere.
- Convex, -smooth, . (Bubeck, 2015, Theorem 3.3): the excess loss falls like .
- -strongly convex and -smooth, . (Bubeck, 2015, Theorem 3.12): linear convergence, with the number of steps proportional to , as on the quadratic.
When has dependent columns, least squares is convex but not strongly convex (): the minimizer is not unique, and the general convex theorem above gives only the rate. Least squares is still a fixed quadratic, though, and the eigenvector analysis of the quadratic case still applies: with , the error components along eigenvectors with positive eigenvalues shrink geometrically, at a speed set by the smallest positive eigenvalue, and the component in the null space of does not change at all. Gradient descent started at then converges geometrically to the minimum-norm solution, because every step adds a vector of the form and stays in the row space of .
Choosing the step in practice#
is rarely known. Three practical answers:
- A grid on a log scale. Try for a few hundred steps and keep the largest that does not diverge. A loss that grows from the first steps means is above .
- Backtracking line search. At each step, start from a large and halve it until with (Boyd and Vandenberghe, 2004, section 9.2, here with the constant 1/2). By the descent lemma every passes the test, so the search ends with and needs no knowledge of . It also accepts longer steps when the gradient points along a flat direction. On the standardized wine problem, where , 100 backtracking steps from accepted twelve times, 41 times, nine times, twelve times and 26 times, and ended with a gradient norm of , against for 100 steps of the fixed .
- Stopping. Stop when the gradient norm or the relative decrease of falls below a tolerance. In machine learning there is a better criterion than the training loss: stop when the validation loss stops improving. The number of steps then acts as a regularizer, as the training note shows.
Newton’s method uses the curvature too: . On a quadratic it reaches in one step from anywhere, because it solves the normal equations; in general it converges in a handful of steps near the minimum, at the price of a solve per step. The logistic-regression note uses it where is small. Gradient descent is the method that scales.
Losses without a closed form#
Gradient descent needs only the gradient, so it works unchanged for the losses that the closed form could not handle. For the Huber loss with threshold , the derivative of is the residual clipped to , so
The second derivative of is at most 1, so is -smooth with the largest eigenvalue of , and is safe. On the ten points with one outlier from the least-squares note, 5,000 steps reach , the Huber line of that note’s figure. The absolute loss is not differentiable at zero residual; its subgradient can replace the gradient, but subgradient steps must shrink over time to converge, a theme of the next note. The logistic regression and linear classifiers notes apply the same update to classification losses, backpropagation computes the gradient for neural networks, and gradient boosting takes the steps in a space of functions.
What changed#
| Normal equations | Gradient descent | |
|---|---|---|
| What it needs | and a solve | the gradient, per step |
| Cost | once | times about steps |
| Losses | squared error only | any differentiable loss |
| Accuracy | exact up to rounding | stops at a tolerance |
| Hyperparameters | none | step size , number of steps |
| Sensitivity to scaling | numerical accuracy ( if is formed) | speed ( steps); diverges for |
Limitations#
- Speed depends on the condition number. Badly scaled or correlated features make descent crawl; scaling fixes the first, not the second.
- The step size must be chosen. Too small wastes steps, above diverges; a line search costs extra loss evaluations.
- Every step is a full pass over the data. With in the millions one step is expensive, and most of the information in it is redundant.
- Nonconvex losses. Only a small gradient is guaranteed; the result depends on the starting point.
- Non-differentiable losses need subgradients or smoothing, and constant steps no longer converge.
What you should remember#
- The negative gradient is the direction of steepest descent; gradient descent iterates . For least squares .
- On a quadratic with Hessian eigenvalues in : converges iff ; the best constant step shrinks the error by per step; about steps.
- Standardize features before gradient descent: centring decouples the intercept, scaling equalizes the curvatures. On the wine data it lowers from to 45.
- For -smooth losses with : each step decreases by at least ; the excess loss is for convex, for strongly convex losses; nonconvex losses only get a small gradient.
- Backtracking line search finds a good step without knowing .
- Gradient descent handles losses with no closed form, such as Huber, whose gradient is the clipped residual.
Code#
The worked example, step sizes on the raw and standardized problem, the wine data, backtracking and the Huber fit:
import numpy as np
from sklearn.datasets import load_wine
def gd(grad, w0, eta, steps):
w = np.array(w0, float)
for _ in range(steps):
w = w - eta * grad(w)
return w
# the four-point example: Q(w) = mean squared error, gradient (2/n) X^T (Xw - y)
x = np.array([0., 1, 2, 3]); y = np.array([1., 3, 2, 5])
for name, col in (('raw', x), ('standardized', (x - x.mean()) / x.std())):
X = np.column_stack([np.ones(4), col])
A = 2 / 4 * X.T @ X # the Hessian of Q
grad = lambda w: A @ w - 2 / 4 * X.T @ y
w_hat = np.linalg.solve(X.T @ X, X.T @ y)
mu, L = np.linalg.eigvalsh(A)
print(name, A.ravel(), round(mu, 3), round(L, 3), round(L / mu, 2), round(2 / L, 3), round(2 / (L + mu), 3), round((L - mu) / (L + mu), 3))
for eta in (0.05, 1 / L, 2 / (L + mu), 0.24):
w, k = np.zeros(2), 0 # steps until the distance to w-hat falls below 1/1000 of the start
while np.linalg.norm(w - w_hat) > 1e-3 * np.linalg.norm(w_hat) and k < 1000 and np.linalg.norm(w) < 1e3:
w, k = w - eta * grad(w), k + 1
print(' ', round(eta, 3), k if k < 1000 and np.linalg.norm(w) < 1e3 else 'diverges')
# raw [2. 3. 3. 7.] 0.595 8.405 14.13 0.238 0.222 0.868
# 0.05 194 | 0.119 80 | 0.222 49 | 0.24 diverges
# standardized [2. 0. 0. 2.] 2.0 2.0 1.0 1.0 0.5 0.0
# 0.05 66 | 0.5 1 | 0.5 1 | 0.24 11
X = np.column_stack([np.ones(4), x])
w = np.zeros(2)
for k in range(3): # the worked example: eta = 0.2 from w = 0
print(k, w.round(2), (2 / 4 * X.T @ (X @ w - y)).round(2), round(np.mean((X @ w - y) ** 2), 3))
w = w - 0.2 * 2 / 4 * X.T @ (X @ w - y)
# 0 [0. 0.] [ -5.5 -11. ] 9.75 | 1 [1.1 2.2] [3.3 7.7] 4.91 | 2 [0.44 0.66] [-2.64 -5.06] 2.659
# wine: predict alcohol from the other 12 measurements; raw against standardized features, eta = 1/L
D = load_wine().data; t, F = D[:, 0], D[:, 1:]
for name, M in (('raw', F), ('standardized', (F - F.mean(0)) / F.std(0))):
Xw = np.column_stack([np.ones(len(t)), M])
A = 2 / len(t) * Xw.T @ Xw
ev = np.linalg.eigvalsh(A)
Q = lambda w: np.mean((Xw @ w - t) ** 2)
Q_hat, Q_0 = Q(np.linalg.lstsq(Xw, t, rcond=None)[0]), Q(np.zeros(13))
w, out = np.zeros(13), []
for k in range(1, 10_001):
w -= 1 / ev[-1] * 2 / len(t) * Xw.T @ (Xw @ w - t)
if k in (10, 100, 1000, 10_000): out.append(f'{(Q(w) - Q_hat) / (Q_0 - Q_hat):.0e}')
print(name, f'{ev[-1] / ev[0]:.2g}', out)
# raw 1.6e+08 ['1e-01', '9e-02', '2e-02', '1e-02']: relative excess loss after 10, 100, 1000, 10000 steps
# standardized 45 ['8e-03', '2e-08', '7e-19', '7e-19']
# backtracking line search (halve eta until Q drops by at least eta/2 * |grad|^2) finds a step near 1/L by itself
Xs = np.column_stack([np.ones(len(t)), (F - F.mean(0)) / F.std(0)])
Q = lambda w: np.mean((Xs @ w - t) ** 2); grad = lambda w: 2 / len(t) * Xs.T @ (Xs @ w - t)
w, etas = np.zeros(13), []
for k in range(100):
g, eta = grad(w), 1.0
while Q(w - eta * g) > Q(w) - eta / 2 * g @ g:
eta /= 2
w, etas = w - eta * g, etas + [eta]
L = np.linalg.eigvalsh(2 / len(t) * Xs.T @ Xs)[-1]
w_fixed = gd(grad, np.zeros(13), 1 / L, 100)
print(round(1 / L, 3), {e: etas.count(e) for e in sorted(set(etas))}, f'{np.linalg.norm(grad(w)):.0e} {np.linalg.norm(grad(w_fixed)):.0e}')
# 0.108 {0.0625: 12, 0.125: 41, 0.25: 9, 0.5: 12, 1.0: 26} 2e-06 1e-03
# the Huber loss of the least-squares note has no closed form; its gradient is the clipped residual
xo = np.arange(10.); yo = np.array([1.3, 1.1, 2.2, 3.0, 2.7, 3.6, 3.5, 4.9, 13.0, 5.3]); Xo = np.column_stack([np.ones(10), xo])
huber_grad = lambda w: -Xo.T @ np.clip(yo - Xo @ w, -1, 1) / 10
L = np.linalg.eigvalsh(Xo.T @ Xo / 10)[-1]
print(gd(huber_grad, [0, 0], 1 / L, 5000).round(3)) # [0.986 0.533]
Next#
Each step of gradient descent touches all examples, and the gradient is an average of per-example gradients. An average can be estimated from a small random sample, and a cheap noisy step is often worth more than an exact expensive one. The next note makes that trade: stochastic gradients and minibatches, why constant steps then stop short of the minimum, and the momentum and per-coordinate step sizes (AdaGrad, RMSProp, Adam) that attack the condition number from this note.
The same update trains models across the courses: the generative models course fits distributions by gradient steps on a KL estimate, and a GAN alternates descent steps for two players.
References#
- E. Sokolov et al. Машинное обучение (HSE), lecture notes, lecture 3: training linear regression, gradient descent and its convergence.
- S. Bubeck. Convex optimization: algorithms and complexity. Foundations and Trends in Machine Learning, 8(3–4), 2015. Theorem 3.3 (smooth convex functions), Theorem 3.12 (smooth and strongly convex functions).
- S. Boyd, L. Vandenberghe. Convex Optimization. Cambridge University Press, 2004. Section 9.2 (descent methods and backtracking line search), Section 9.3 (gradient descent and its convergence), Section 9.5 (Newton’s method).
- I. Goodfellow, Y. Bengio, A. Courville. Deep Learning. MIT Press, 2016. Section 4.2 (poor conditioning), Section 4.3 (gradient-based optimization and the Hessian).
- M. P. Deisenroth, A. A. Faisal, C. S. Ong. Mathematics for Machine Learning. Cambridge University Press, 2020. Section 7.1 (optimization using gradient descent).