Quasi-Newton methods (BFGS, L-BFGS)
Approximate the Hessian from gradient differences with the BFGS update and its limited-memory form.
Newton's method is fast but pays per step to invert the Hessian, and often the Hessian is unavailable anyway. Quasi-Newton methods build an approximation to the inverse Hessian from nothing but the gradients already computed, recovering most of Newton's speed at gradient-descent cost. BFGS and its limited-memory form L-BFGS are the workhorses of classical smooth optimization.
The secant condition
The idea is to infer curvature from how the gradient changes. Over one step define
For a quadratic with Hessian , the gradient change is exactly . A quasi-Newton method requires its Hessian approximation to reproduce this observed relationship, the secant condition:
This is the multidimensional analogue of estimating a second derivative from two first derivatives. One equation underdetermines the matrix, so the method adds a requirement that stay close to .
The BFGS update
BFGS updates the inverse Hessian approximation directly, so each step needs only a matrix-vector product, not a solve. With :
[1]This is a rank-two correction to chosen to satisfy the secant condition while keeping symmetric. As long as the curvature condition holds (guaranteed by a proper line search), the update preserves positive definiteness, so the search direction is always a descent direction. The step is
with from a line search. BFGS converges superlinearly: faster than any linear rate, approaching Newton, without ever forming a Hessian.
L-BFGS: limited memory
Storing the dense matrix is , still too much for large . L-BFGS never forms . It keeps only the last pairs (typically to ) and computes the product on the fly with the two-loop recursion: one backward pass over the stored pairs applying the factors, a scaling by an estimate of the Hessian, then one forward pass. Cost and memory drop to , linear in the dimension. L-BFGS is the default for medium-scale smooth problems and large-batch training where the gradients are clean enough to trust[2].
Check it on arrays
import numpy as np
# Minimize f(x) = 0.5 x^T A x - b^T x with BFGS (inverse-Hessian form).
rng = np.random.default_rng(0)
n = 20
Q = np.linalg.qr(rng.normal(size=(n, n)))[0]
A = Q @ np.diag(np.linspace(1.0, 50.0, n)) @ Q.T # SPD, condition number 50
b = rng.normal(size=n)
x_star = np.linalg.solve(A, b)
f = lambda x: 0.5 * x @ A @ x - b @ x
grad = lambda x: A @ x - b
def line_search(x, p): # backtracking (Armijo)
a, g = 1.0, grad(x)
while f(x + a * p) > f(x) + 1e-4 * a * (g @ p):
a *= 0.5
return a
x = np.zeros(n)
H = np.eye(n) # initial inverse-Hessian guess
g = grad(x)
for t in range(40):
p = -H @ g
alpha = line_search(x, p)
s = alpha * p
x_new = x + s
g_new = grad(x_new)
y = g_new - g
rho = 1.0 / (y @ s)
if y @ s > 1e-12: # curvature condition
I = np.eye(n)
H = (I - rho * np.outer(s, y)) @ H @ (I - rho * np.outer(y, s)) + rho * np.outer(s, s)
x, g = x_new, g_new
print("BFGS error:", f"{np.linalg.norm(x - x_star):.2e}")
print("gradient norm:", f"{np.linalg.norm(g):.2e}")BFGS reaches the minimizer of an ill-conditioned () quadratic in a few dozen steps using only gradients, with the inverse-Hessian estimate built from the secant pairs.
Exercises
Exercise 1. On a quadratic , take one step and form . Show by hand that the secant condition holds exactly, so the true Hessian satisfies the constraint BFGS imposes.
Solution
Step 1. For this quadratic the gradient is , an affine map.
Step 2. Compute the gradient difference directly:
Step 3. The constant cancels, leaving . So the exact Hessian satisfies the secant condition for every step. BFGS asks its approximation to match this one observed pairing; on a quadratic, after enough independent directions the approximation pins down on the whole space.
Exercise 2. The curvature condition guarantees BFGS keeps its inverse-Hessian estimate positive definite. Show that for a strictly convex quadratic with Hessian , the curvature condition holds for any nonzero step .
Solution
Step 1. On the quadratic, from Exercise 1, so
Step 2. Because is positive definite, for every nonzero , by the definition of positive definiteness.
Step 3. Therefore whenever the step is nonzero, so is well defined and positive, and the rank-two BFGS update preserves positive definiteness. On nonquadratic functions a line search (Wolfe conditions) enforces the same inequality.
Exercise 3 (code). Implement the L-BFGS two-loop recursion that computes the product from the last stored pairs , without ever forming a matrix. Verify it against the dense BFGS inverse-Hessian on the same pairs by checking the two search directions agree.
Solution
import numpy as np
rng = np.random.default_rng(0)
n, m = 12, 5
# Build m secant pairs from a fixed SPD Hessian so curvature condition holds.
M = rng.normal(size=(n, n)); H_true = M @ M.T + n * np.eye(n)
S = rng.normal(size=(m, n))
Y = S @ H_true # y_k = H s_k (rows)
# Dense BFGS inverse-Hessian built from the same pairs (gamma * I start).
gamma = (S[-1] @ Y[-1]) / (Y[-1] @ Y[-1])
Hdense = gamma * np.eye(n)
for k in range(m):
s, y = S[k], Y[k]
rho = 1.0 / (y @ s)
I = np.eye(n)
Hdense = (I - rho * np.outer(s, y)) @ Hdense @ (I - rho * np.outer(y, s)) + rho * np.outer(s, s)
g = rng.normal(size=n)
dense_dir = Hdense @ g
# Two-loop recursion: same H0 = gamma * I, same pairs, applied implicitly.
def two_loop(g, S, Y, gamma):
q = g.copy()
alpha = np.zeros(m)
rho = np.array([1.0 / (Y[k] @ S[k]) for k in range(m)])
for k in reversed(range(m)): # backward pass
alpha[k] = rho[k] * (S[k] @ q)
q = q - alpha[k] * Y[k]
r = gamma * q # H0 * q
for k in range(m): # forward pass
beta = rho[k] * (Y[k] @ r)
r = r + (alpha[k] - beta) * S[k]
return r
loop_dir = two_loop(g, S, Y, gamma)
assert np.allclose(loop_dir, dense_dir, atol=1e-8)
print("max abs difference:", float(np.max(np.abs(loop_dir - dense_dir))))The two-loop recursion reproduces the dense BFGS product to machine precision while touching only work, which is what lets L-BFGS scale to large .
The next lesson handles objectives that are not smooth, with proximal methods.
References
- [1]Nocedal, J., & Wright, S. J. (2006). Numerical Optimization (2nd ed.), §6.1. Springer. The secant condition and the BFGS inverse-Hessian update (doi:10.1007/978-0-387-40065-5).↩
- [2]Nocedal, J., & Wright, S. J. (2006). Numerical Optimization (2nd ed.), §7.2. Springer. Limited-memory L-BFGS and the two-loop recursion.↩