Newton's Method Second-order Optimization Techniques

Line Search Newton's Method BFGS Method

Line Search

Our treatment of gradient descent used a fixed learning rate (or step size) \(\eta\) chosen by experiment. A principled alternative is the line search. Given a current iterate \(\boldsymbol{\theta}_t\) and a descent direction \(\mathbf{d}_t\) (that is, one with \(\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) \lt 0\)), we select a step that minimizes the objective along the ray \(\{\boldsymbol{\theta}_t + \eta\, \mathbf{d}_t : \eta \gt 0\}\), assuming such a step exists: \[ \eta_t \in \operatorname*{arg\,min}_{\eta \gt 0}\, \mathcal{L}(\boldsymbol{\theta}_t + \eta\, \mathbf{d}_t). \]

Exact Line Search on a Quadratic

On a strictly convex quadratic \[ \begin{align*} \mathcal{L}(\boldsymbol{\theta}) &= \tfrac{1}{2}\, \boldsymbol{\theta}^\top \mathbf{A} \boldsymbol{\theta} + \mathbf{b}^\top \boldsymbol{\theta} + c, \\\\ \mathbf{A} &\succ 0, \end{align*} \] the one-dimensional problem admits a closed-form solution.

Differentiating with respect to \(\eta\): \[ \frac{d}{d\eta}\, \mathcal{L}(\boldsymbol{\theta} + \eta\, \mathbf{d}) = \mathbf{d}^\top (\mathbf{A} \boldsymbol{\theta} + \mathbf{b}) + \eta\, \mathbf{d}^\top \mathbf{A}\, \mathbf{d}. \] Setting this to zero and using \(\mathbf{A} \boldsymbol{\theta} + \mathbf{b} = \nabla \mathcal{L}(\boldsymbol{\theta})\): \[ \eta^*_t = -\,\frac{\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t)}{\mathbf{d}_t^\top \mathbf{A}\, \mathbf{d}_t}. \] Positivity of \(\mathbf{A}\) makes the denominator positive, and the descent-direction condition makes the numerator negative, so \(\eta^*_t \gt 0\) as required.

Inexact Line Search

Solving the subproblem exactly at every outer iteration is wasteful. Inexact line searches instead seek any step \(\eta\) that delivers "sufficient" progress along \(\mathbf{d}_t\). The most common such rule is the Armijo condition.

Definition: Armijo (Sufficient-Decrease) Condition

Fix \(c_1 \in (0, 1)\). A step size \(\eta \gt 0\) satisfies the Armijo condition at \(\boldsymbol{\theta}_t\) along a descent direction \(\mathbf{d}_t\) if \[ \begin{align*} \mathcal{L}(\boldsymbol{\theta}_t + \eta\, \mathbf{d}_t) &\leq \mathcal{L}(\boldsymbol{\theta}_t) + c_1\, \eta\, \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t). \tag{1} \end{align*} \]

Because \(\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) \lt 0\), the right-hand side is strictly smaller than \(\mathcal{L}(\boldsymbol{\theta}_t)\). Thus (1) enforces a decrease proportional to the step and the directional derivative. A typical choice is \(c_1 = 10^{-4}\).

The standard backtracking implementation starts from an initial trial step \(\eta\) (often \(\eta = 1\) for Newton-type methods) and shrinks it by a factor \(\tau \in (0, 1)\) (commonly \(\tau = 0.5\)) until (1) holds. Since \(\mathcal{L}\) is differentiable at \(\boldsymbol{\theta}_t\), \[ \mathcal{L}(\boldsymbol{\theta}_t + \eta\, \mathbf{d}_t) = \mathcal{L}(\boldsymbol{\theta}_t) + \eta\, \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) + o(\eta), \] and because \(c_1 \lt 1\) and \(\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) \lt 0\), the leftover \((1 - c_1)\, \eta\, \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t)\) dominates the \(o(\eta)\) term, so (1) holds for all sufficiently small \(\eta\).

Newton's Method

Gradient descent uses a first-order linear model of \(\mathcal{L}\). Newton's method uses the second-order quadratic model. Instead of following the negative gradient, at each step it jumps to the minimizer of the local quadratic approximation, exploiting curvature information encoded in the Hessian.

The second-order Taylor expansion around the current iterate \(\boldsymbol{\theta}_t\) reads \[ \begin{align*} \mathcal{L}(\boldsymbol{\theta}_t + \Delta\boldsymbol{\theta}) &\approx \mathcal{L}(\boldsymbol{\theta}_t) + \mathbf{g}_t^\top \Delta\boldsymbol{\theta} \\\\ &\quad + \tfrac{1}{2}\, \Delta\boldsymbol{\theta}^\top \mathbf{H}_t\, \Delta\boldsymbol{\theta}, \end{align*} \] where \(\mathbf{g}_t = \nabla \mathcal{L}(\boldsymbol{\theta}_t)\) and \(\mathbf{H}_t = \nabla^2 \mathcal{L}(\boldsymbol{\theta}_t)\). Minimizing the right-hand side over \(\Delta\boldsymbol{\theta}\) leads to the Newton step.

Proposition: Newton Direction

Let \(\mathbf{H}_t \succ 0\). The quadratic model \(q(\Delta\boldsymbol{\theta}) = \mathbf{g}_t^\top \Delta\boldsymbol{\theta} + \tfrac{1}{2}\, \Delta\boldsymbol{\theta}^\top \mathbf{H}_t\, \Delta\boldsymbol{\theta}\) has a unique minimizer \[ \mathbf{d}_t = -\mathbf{H}_t^{-1}\, \mathbf{g}_t, \] called the Newton direction. It is a descent direction whenever \(\mathbf{g}_t \neq \mathbf{0}\): \(\mathbf{d}_t^\top \mathbf{g}_t = -\mathbf{g}_t^\top \mathbf{H}_t^{-1}\, \mathbf{g}_t \lt 0\).

Proof:

Completing the square gives \[ \begin{align*} q(\Delta\boldsymbol{\theta}) &= \tfrac{1}{2}\, (\Delta\boldsymbol{\theta} + \mathbf{H}_t^{-1}\mathbf{g}_t)^\top \mathbf{H}_t\, (\Delta\boldsymbol{\theta} + \mathbf{H}_t^{-1}\mathbf{g}_t) \\\\ &\quad - \tfrac{1}{2}\, \mathbf{g}_t^\top \mathbf{H}_t^{-1} \mathbf{g}_t. \end{align*} \] Since \(\mathbf{H}_t \succ 0\), the first term is nonnegative and vanishes exactly at \(\Delta\boldsymbol{\theta} = -\mathbf{H}_t^{-1}\mathbf{g}_t\), which is therefore the unique minimizer \(\mathbf{d}_t\). Positive-definiteness of \(\mathbf{H}_t^{-1}\), inherited from \(\mathbf{H}_t\) because \(\mathbf{H}_t^{-1}\) is symmetric and \(\mathbf{x}^\top \mathbf{H}_t^{-1} \mathbf{x} = (\mathbf{H}_t^{-1}\mathbf{x})^\top \mathbf{H}_t (\mathbf{H}_t^{-1}\mathbf{x}) \gt 0\) for \(\mathbf{x} \neq \mathbf{0}\), yields \(\mathbf{g}_t^\top \mathbf{H}_t^{-1} \mathbf{g}_t \gt 0\) whenever \(\mathbf{g}_t \neq \mathbf{0}\).

Pure Newton's method takes \(\boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t + \mathbf{d}_t\) (unit step). Near a minimizer at which the Hessian is positive definite and Lipschitz continuous, this scheme converges quadratically (local quadratic convergence), a classical result we state without proof on this page, but can diverge when started far from the optimum or when the Hessian is not positive definite.

To recover global convergence (under standard assumptions that we do not prove here), the Damped Newton's Method scales the Newton direction by a line-search step size: \[ \boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t + \eta_t\, \mathbf{d}_t, \quad \eta_t \text{ chosen to satisfy the } \href{#D-armijo_condition}{\textbf{Armijo condition}}. \]

Algorithm 1: DAMPED_NEWTON Input: objective \(\mathcal{L}\), tolerance \(\epsilon\);
Output: approximate stationary point \(\boldsymbol{\theta}_t\);
begin
 \(t \leftarrow 0\); choose initial point \(\boldsymbol{\theta}_0\);
repeat
  \(\mathbf{g}_t \leftarrow \nabla \mathcal{L}(\boldsymbol{\theta}_t)\);
  \(\mathbf{H}_t \leftarrow \nabla^2 \mathcal{L}(\boldsymbol{\theta}_t)\);
  Solve \(\mathbf{H}_t\, \mathbf{d}_t = -\mathbf{g}_t\) for \(\mathbf{d}_t\);
  \(\eta_t \leftarrow \mathrm{LINE\_SEARCH}(\boldsymbol{\theta}_t, \mathbf{d}_t)\) satisfying the Armijo condition;
  \(\boldsymbol{\theta}_{t+1} \leftarrow \boldsymbol{\theta}_t + \eta_t\, \mathbf{d}_t\);
  \(t \leftarrow t + 1\);
until \(\|\mathbf{g}_t\| \lt \epsilon\);
 Output \(\boldsymbol{\theta}_t\);
end

Computational Note: Solving vs. Inverting

Although we write \(\mathbf{d}_t = -\mathbf{H}_t^{-1}\, \mathbf{g}_t\), the inverse is never formed explicitly. Forming \(\mathbf{H}_t^{-1}\) costs \(O(n^3)\) and is numerically fragile. Instead we solve the linear system \(\mathbf{H}_t\, \mathbf{d}_t = -\mathbf{g}_t\) directly, by Cholesky factorization when \(\mathbf{H}_t \succ 0\) or by conjugate gradients for large sparse problems. The cost is the same asymptotically but the constants and numerical stability are dramatically better.

When Newton's Method Fails

Newton's direction is a descent direction only when \(\mathbf{H}_t \succ 0\). In non-convex landscapes, ubiquitous in deep learning, the Hessian is often indefinite near saddle points, so \(-\mathbf{H}_t^{-1} \mathbf{g}_t\) may point uphill. Practical remedies include trust-region methods, Levenberg-Marquardt damping \(\mathbf{H}_t \mapsto \mathbf{H}_t + \mu \mathbf{I}\), and saddle-free Newton (Dauphin et al., 2014), which replaces \(\mathbf{H}_t\) by \(|\mathbf{H}_t|\) (constructed from the absolute values of its eigenvalues).

Another concern is cost. Storing the full Hessian requires \(O(n^2)\) memory, and solving the Newton system is \(O(n^3)\), which is intractable for modern neural networks with \(n\) of order \(10^9\) or more. This motivates the quasi-Newton methods of the next section.

BFGS Method

To avoid the \(O(n^3)\) cost of Hessian computation and inversion, quasi-Newton methods maintain a symmetric positive-definite matrix \(\mathbf{B}_t \approx \mathbf{H}_t\) (or its inverse \(\mathbf{C}_t \approx \mathbf{H}_t^{-1}\)) that is updated cheaply at each iteration using only gradient information. The BFGS method of Broyden-Fletcher-Goldfarb-Shanno is the quasi-Newton method most widely used in practice.

Secant Condition

Let \(\mathbf{s}_t = \boldsymbol{\theta}_{t+1} - \boldsymbol{\theta}_t\) denote the parameter step and \(\mathbf{y}_t = \mathbf{g}_{t+1} - \mathbf{g}_t\) the gradient change. A first-order Taylor expansion of \(\nabla \mathcal{L}\) around \(\boldsymbol{\theta}_t\) gives \[ \begin{align*} \mathbf{g}_{t+1} &\approx \mathbf{g}_t + \mathbf{H}_t\, \mathbf{s}_t \\\\ &\Longrightarrow \mathbf{y}_t \approx \mathbf{H}_t\, \mathbf{s}_t. \end{align*} \] The quasi-Newton family therefore imposes a curvature-matching requirement on the updated approximation:

Definition: Secant Condition

A sequence of Hessian approximations \(\{\mathbf{B}_t\}\) satisfies the secant condition if \[ \mathbf{B}_{t+1}\, \mathbf{s}_t = \mathbf{y}_t \quad \text{for all } t. \] Equivalently, the inverse approximations \(\mathbf{C}_t = \mathbf{B}_t^{-1}\) satisfy \(\mathbf{C}_{t+1}\, \mathbf{y}_t = \mathbf{s}_t\).

Derivation of the BFGS Update

The secant condition alone under-determines \(\mathbf{B}_{t+1}\). In \(n\) dimensions there are \(n(n+1)/2\) free parameters in a symmetric matrix but only \(n\) linear constraints.

BFGS singles out one update by requiring (i) the update be a symmetric rank-2 correction of \(\mathbf{B}_t\), and (ii) \(\mathbf{B}_{t+1} \succ 0\) be preserved. Writing \(\mathbf{B}_{t+1} = \mathbf{B}_t + \mathbf{U}\), the secant condition forces \(\mathbf{U}\, \mathbf{s}_t = \mathbf{y}_t - \mathbf{B}_t\, \mathbf{s}_t\), and the specific rank-2 ansatz built from \(\mathbf{y}_t\) and \(\mathbf{B}_t \mathbf{s}_t\), the two vectors this equation involves, \[ \mathbf{U} = \alpha\, \mathbf{y}_t \mathbf{y}_t^\top + \beta\, (\mathbf{B}_t \mathbf{s}_t)(\mathbf{B}_t \mathbf{s}_t)^\top \] is chosen so that the first term injects curvature along \(\mathbf{y}_t\) while the second removes the now-stale curvature along \(\mathbf{B}_t \mathbf{s}_t\). Enforcing \(\mathbf{U}\, \mathbf{s}_t = \mathbf{y}_t - \mathbf{B}_t \mathbf{s}_t\) pins the scalars, by comparing coefficients when \(\mathbf{y}_t\) and \(\mathbf{B}_t \mathbf{s}_t\) are linearly independent (the formula is then adopted in all cases), to \(\alpha = 1/(\mathbf{y}_t^\top \mathbf{s}_t)\) and \(\beta = -1/(\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t)\), yielding the BFGS update formula.

Proposition: BFGS Hessian Update

Given a symmetric \(\mathbf{B}_t \succ 0\) and vectors \(\mathbf{s}_t, \mathbf{y}_t\) satisfying the curvature condition \(\mathbf{y}_t^\top \mathbf{s}_t \gt 0\), define \[ \begin{align*} \mathbf{B}_{t+1} &= \mathbf{B}_t + \frac{\mathbf{y}_t \mathbf{y}_t^\top}{\mathbf{y}_t^\top \mathbf{s}_t} \\\\ &\quad - \frac{(\mathbf{B}_t \mathbf{s}_t)(\mathbf{B}_t \mathbf{s}_t)^\top}{\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t}. \end{align*} \] Then \(\mathbf{B}_{t+1}\) is symmetric, positive definite, and satisfies the secant condition \(\mathbf{B}_{t+1}\, \mathbf{s}_t = \mathbf{y}_t\).

Proof:

Secant condition. Compute directly: \[ \begin{align*} \mathbf{B}_{t+1}\, \mathbf{s}_t &= \mathbf{B}_t \mathbf{s}_t + \mathbf{y}_t\, \frac{\mathbf{y}_t^\top \mathbf{s}_t}{\mathbf{y}_t^\top \mathbf{s}_t} - (\mathbf{B}_t \mathbf{s}_t)\, \frac{\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t}{\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t} \\\\ &= \mathbf{B}_t \mathbf{s}_t + \mathbf{y}_t - \mathbf{B}_t \mathbf{s}_t \\\\ &= \mathbf{y}_t. \end{align*} \]

Symmetry. Both rank-1 correction terms are outer products of a vector with itself, hence symmetric. \(\mathbf{B}_t\) is symmetric by hypothesis, so \(\mathbf{B}_{t+1}\) is symmetric.

Positive-definiteness. Note first that \(\mathbf{s}_t \neq \mathbf{0}\). If \(\mathbf{s}_t = \mathbf{0}\) then \(\mathbf{y}_t^\top \mathbf{s}_t = 0\), contradicting the curvature condition. Hence \(\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t \gt 0\) by \(\mathbf{B}_t \succ 0\), and the scalar below is well-defined. For any \(\mathbf{v} \neq \mathbf{0}\), decompose by projecting onto \(\mathbf{s}_t\) (in the \(\mathbf{B}_t\)-inner product): write \(\mathbf{w} = \mathbf{v} - \gamma \mathbf{s}_t\) with \(\gamma = (\mathbf{s}_t^\top \mathbf{B}_t \mathbf{v})/(\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t)\), so that \(\mathbf{s}_t^\top \mathbf{B}_t \mathbf{w} = 0\). A direct calculation then gives \[ \begin{align*} \mathbf{v}^\top \mathbf{B}_{t+1} \mathbf{v} &= \mathbf{w}^\top \mathbf{B}_t \mathbf{w} + \frac{(\mathbf{v}^\top \mathbf{y}_t)^2}{\mathbf{y}_t^\top \mathbf{s}_t}. \end{align*} \] Both terms are \(\geq 0\): the first by \(\mathbf{B}_t \succ 0\), the second because \(\mathbf{y}_t^\top \mathbf{s}_t \gt 0\). To see that the sum is strictly positive when \(\mathbf{v} \neq \mathbf{0}\), suppose both terms vanish. The first vanishes only when \(\mathbf{w} = \mathbf{0}\), that is, when \(\mathbf{v} = \gamma \mathbf{s}_t\). Substituting into the second term gives \(\mathbf{v}^\top \mathbf{y}_t = \gamma\, \mathbf{s}_t^\top \mathbf{y}_t\). Since \(\mathbf{s}_t^\top \mathbf{y}_t \gt 0\), this vanishes only when \(\gamma = 0\), forcing \(\mathbf{v} = \mathbf{0}\), a contradiction. Hence \(\mathbf{v}^\top \mathbf{B}_{t+1} \mathbf{v} \gt 0\) for every \(\mathbf{v} \neq \mathbf{0}\).

The curvature condition \(\mathbf{y}_t^\top \mathbf{s}_t \gt 0\) is not automatic. It fails on non-convex objectives unless the step size \(\eta_t\) is chosen carefully. The Wolfe conditions introduced below guarantee it, which is why Wolfe (not plain Armijo) is the standard line search for BFGS.

Proposition: BFGS Inverse Update

Under the same hypotheses as the previous proposition, the inverse \(\mathbf{C}_{t+1} = \mathbf{B}_{t+1}^{-1}\) admits the closed form \[ \begin{align*} \mathbf{C}_{t+1} &= \Bigl(\mathbf{I} - \frac{\mathbf{s}_t \mathbf{y}_t^\top}{\mathbf{y}_t^\top \mathbf{s}_t}\Bigr) \mathbf{C}_t \Bigl(\mathbf{I} - \frac{\mathbf{y}_t \mathbf{s}_t^\top}{\mathbf{y}_t^\top \mathbf{s}_t}\Bigr) \\\\ &\quad + \frac{\mathbf{s}_t \mathbf{s}_t^\top}{\mathbf{y}_t^\top \mathbf{s}_t}. \end{align*} \]

Proof (via two applications of Sherman-Morrison):

Apply the Sherman-Morrison formula first to the rank-1 addition \(\mathbf{B}_t + \mathbf{y}_t \mathbf{y}_t^\top / (\mathbf{y}_t^\top \mathbf{s}_t)\): with \(\mathbf{u} = \mathbf{y}_t\), \(\mathbf{v}^\top = \mathbf{y}_t^\top / (\mathbf{y}_t^\top \mathbf{s}_t)\) (the formula applies because \(1 + \mathbf{y}_t^\top \mathbf{C}_t \mathbf{y}_t / \mathbf{y}_t^\top \mathbf{s}_t \gt 0\), as \(\mathbf{C}_t \succ 0\) inherits positive-definiteness from \(\mathbf{B}_t\) exactly as \(\mathbf{H}_t^{-1}\) did above, and \(\mathbf{y}_t^\top \mathbf{s}_t \gt 0\)), \[ \begin{align*} \Bigl(\mathbf{B}_t + \frac{\mathbf{y}_t \mathbf{y}_t^\top}{\mathbf{y}_t^\top \mathbf{s}_t}\Bigr)^{-1} &= \mathbf{C}_t - \frac{\mathbf{C}_t \mathbf{y}_t \mathbf{y}_t^\top \mathbf{C}_t}{\mathbf{y}_t^\top \mathbf{s}_t + \mathbf{y}_t^\top \mathbf{C}_t \mathbf{y}_t}. \end{align*} \]

Next apply Sherman-Morrison to the rank-1 subtraction of \((\mathbf{B}_t \mathbf{s}_t)(\mathbf{B}_t \mathbf{s}_t)^\top / (\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t)\) from the intermediate matrix. This second application is again legitimate, because the scalar in the formula works out to \[ \frac{(\mathbf{y}_t^\top \mathbf{s}_t)^2}{(\mathbf{y}_t^\top \mathbf{s}_t + \mathbf{y}_t^\top \mathbf{C}_t \mathbf{y}_t)\, \mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t} \gt 0. \] After collecting terms and repeatedly using \(\mathbf{C}_t \mathbf{B}_t \mathbf{s}_t = \mathbf{s}_t\) (since \(\mathbf{C}_t = \mathbf{B}_t^{-1}\)) and the identity \(\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t \cdot (\mathbf{y}_t^\top \mathbf{s}_t + \mathbf{y}_t^\top \mathbf{C}_t \mathbf{y}_t) - (\mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t)(\mathbf{y}_t^\top \mathbf{C}_t \mathbf{y}_t) = \mathbf{s}_t^\top \mathbf{B}_t \mathbf{s}_t \cdot \mathbf{y}_t^\top \mathbf{s}_t\), the expression telescopes to the stated form. The final form can be verified mechanically by direct multiplication \(\mathbf{C}_{t+1} \mathbf{B}_{t+1} = \mathbf{I}\).

Why the two-factor form matters

The compact form \(\mathbf{C}_{t+1} = (\mathbf{I} - \rho_t \mathbf{s}_t \mathbf{y}_t^\top)\, \mathbf{C}_t\, (\mathbf{I} - \rho_t \mathbf{y}_t \mathbf{s}_t^\top) + \rho_t \mathbf{s}_t \mathbf{s}_t^\top\), where \(\rho_t = 1/(\mathbf{y}_t^\top \mathbf{s}_t)\), exposes the action of the update: \(\mathbf{C}_{t+1} \mathbf{v}\) can be computed using only inner products and outer-product sums involving \(\mathbf{s}_t, \mathbf{y}_t\) and \(\mathbf{C}_t \mathbf{v}\). No explicit matrix storage of the correction is needed. This is the structural observation that enables L-BFGS below. Unrolling this two-factor recurrence over the most recent \(m\) pairs yields a two-loop algorithm that never materializes \(\mathbf{C}_t\) at all.

Wolfe Conditions

BFGS's positive-definiteness preservation and fast convergence both depend on the step \(\eta_t\) satisfying conditions stronger than Armijo alone. The Wolfe conditions pair the sufficient-decrease condition with a curvature condition that prevents steps that are too short.

Definition: Wolfe Conditions

Fix \(0 \lt c_1 \lt c_2 \lt 1\). A step size \(\eta \gt 0\) satisfies the Wolfe conditions at \(\boldsymbol{\theta}_t\) along a descent direction \(\mathbf{d}_t\) if both hold:

(W1) Sufficient decrease (Armijo): \[ \begin{align*} \mathcal{L}(\boldsymbol{\theta}_t + \eta\, \mathbf{d}_t) &\leq \mathcal{L}(\boldsymbol{\theta}_t) + c_1\, \eta\, \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t). \end{align*} \]

(W2) Curvature: \[ \begin{align*} \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t + \eta\, \mathbf{d}_t) &\geq c_2\, \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t). \end{align*} \] The strong Wolfe conditions replace (W2) with the tighter two-sided form \(|\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t + \eta \mathbf{d}_t)| \leq c_2\, |\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t)|\). Typical choices for quasi-Newton methods are \(c_1 = 10^{-4}\) and \(c_2 = 0.9\).

The curvature condition (W2) is precisely what guarantees the BFGS prerequisite \(\mathbf{y}_t^\top \mathbf{s}_t \gt 0\). Indeed, for a descent direction \(\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) \lt 0\), (W2) gives \[ \begin{align*} \mathbf{y}_t^\top \mathbf{s}_t &= \eta_t\, \mathbf{d}_t^\top \bigl(\nabla \mathcal{L}(\boldsymbol{\theta}_{t+1}) - \nabla \mathcal{L}(\boldsymbol{\theta}_t)\bigr) \\\\ &\geq \eta_t\, (c_2 - 1)\, \mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) \\\\ &\gt 0, \end{align*} \] since \(\eta_t \gt 0\), \(c_2 \lt 1\), and \(\mathbf{d}_t^\top \nabla \mathcal{L}(\boldsymbol{\theta}_t) \lt 0\).

That chain of inequalities is the reason BFGS line searches are implemented with (strong) Wolfe rather than plain Armijo.

Limited-Memory BFGS (L-BFGS)

For large-scale problems, storing the \(n \times n\) matrix \(\mathbf{C}_t\) explicitly is infeasible. At \(n \sim 10^6\) the matrix already requires terabytes. L-BFGS sidesteps this by never forming \(\mathbf{C}_t\). It retains only the most recent \(m\) pairs \(\{(\mathbf{s}_i, \mathbf{y}_i)\}\) (typically \(m \in \{5, \ldots, 20\}\)) and computes \(\mathbf{C}_t\, \mathbf{g}_t\) implicitly via the two-loop recursion below.

Unrolling the two-factor recurrence over the last \(m\) pairs, applied to a vector \(\mathbf{g}_t\) against an implicit initial \(\mathbf{C}_t^{(0)}\) (often \(\mathbf{I}\)), produces exactly that recursion. The memory cost drops to \(O(mn)\) and each iteration costs \(O(mn)\).

Algorithm 2: L_BFGS Input: objective \(\mathcal{L}\), tolerance \(\epsilon\), memory size \(m\);
Output: approximate stationary point \(\boldsymbol{\theta}^*\);
begin
 Choose initial point \(\boldsymbol{\theta}_0\); initialise \((\mathbf{s}, \mathbf{y})\) storage for up to \(m\) past updates;
 \(t \leftarrow 0\);
repeat
  \(\mathbf{g}_t \leftarrow \nabla \mathcal{L}(\boldsymbol{\theta}_t)\); \(\mathbf{q} \leftarrow \mathbf{g}_t\);
  Backward loop (newest to oldest stored pair):
   \(\alpha_i \leftarrow \rho_i\, \mathbf{s}_i^\top \mathbf{q}\); \(\mathbf{q} \leftarrow \mathbf{q} - \alpha_i\, \mathbf{y}_i\);
  \(\mathbf{r} \leftarrow \mathbf{C}_t^{(0)}\, \mathbf{q}\) (typically \(\mathbf{C}_t^{(0)} = \mathbf{I}\) or a scaled identity);
  Forward loop (oldest to newest stored pair):
   \(\beta_i \leftarrow \rho_i\, \mathbf{y}_i^\top \mathbf{r}\); \(\mathbf{r} \leftarrow \mathbf{r} + \mathbf{s}_i (\alpha_i - \beta_i)\);
  \(\mathbf{d}_t \leftarrow -\mathbf{r}\);
  \(\eta_t \leftarrow \mathrm{LINE\_SEARCH}(\boldsymbol{\theta}_t, \mathbf{d}_t)\) satisfying Wolfe conditions;
  \(\boldsymbol{\theta}_{t+1} \leftarrow \boldsymbol{\theta}_t + \eta_t\, \mathbf{d}_t\);
  \(\mathbf{g}_{t+1} \leftarrow \nabla \mathcal{L}(\boldsymbol{\theta}_{t+1})\);
  Store \(\mathbf{s}_t \leftarrow \boldsymbol{\theta}_{t+1} - \boldsymbol{\theta}_t\), \(\mathbf{y}_t \leftarrow \mathbf{g}_{t+1} - \mathbf{g}_t\), \(\rho_t \leftarrow 1/(\mathbf{y}_t^\top \mathbf{s}_t)\);
  If the number of stored pairs exceeds \(m\), discard the oldest pair;
  \(t \leftarrow t + 1\);
until \(\|\mathbf{g}_t\| \lt \epsilon\);
 Output \(\boldsymbol{\theta}_t\);
end

For numerical efficiency, the scalar \(\rho_t = 1/(\mathbf{y}_t^\top \mathbf{s}_t)\) is computed once and stored alongside each \((\mathbf{s}_t, \mathbf{y}_t)\) pair rather than being recomputed whenever it is needed in the two loops.

Python Implementation

The sample implementation below tests L-BFGS on the Rosenbrock function \[ \begin{align*} f(\mathbf{x}) &= \sum_{i=1}^{n-1} \bigl[a\, (x_{i+1} - x_i^2)^2 + (b - x_i)^2\bigr], \\\\ a &= 100, \quad b = 1, \end{align*} \] a standard optimization benchmark with global minimum \(f(\mathbf{1}) = 0\).

Despite the simple appearance, the Rosenbrock function is notoriously difficult for optimization algorithms because its global minimum lies at the bottom of a narrow, curved, "banana-shaped" valley. The surrounding landscape is ill-conditioned (strong anisotropy of the Hessian) and along the valley floor the gradient is small, so naive gradient descent crawls while Newton-type methods must handle curvature carefully.

                            import numpy as np

                            # Line search with Wolfe conditions 
                            def line_search(f, grad_f, theta, p, c1 = 1e-4, c2 = 0.9, max_iter = 100):
                                eta = 1.0
                                eta_low = 0.0
                                eta_high = None
                            
                                phi_0 = f(theta)
                                grad_phi_0 = np.dot(grad_f(theta), p)
                            
                                for _ in range(max_iter):
                                    phi_eta = f(theta + eta * p)
                            
                                    # Check Armijo condition
                                    if phi_eta > phi_0 + c1 * eta * grad_phi_0:
                                        eta_high = eta
                                    else:
                                        # Check Curvature condition
                                        grad_phi_eta = np.dot(grad_f(theta + eta * p), p)
                                        if grad_phi_eta < c2 * grad_phi_0:
                                            eta_low = eta
                                        else:
                                            return eta
                            
                                    # Update step size using bisection method
                                    if eta_high is not None:
                                        eta = (eta_low + eta_high) / 2.0
                                    else:
                                        eta *= 2.0
                            
                                return eta
                            
                            # Limited memory BFGS 
                            def limited_bfgs(f, grad_f, theta0, m = 10, tol = 1e-6, max_iter = 2000):
                                
                                theta = theta0.copy()
                                g = grad_f(theta)
                                s_list = []
                                y_list = []
                                rho_list = [] # We introduce rho =  1/s^\top y instead of directly using 1/s^\top y for efficiency & stability. 
                            
                                for _ in range(max_iter):
                                    
                                    if np.linalg.norm(g) < tol:
                                        break
                            
                                    q = g.copy()
                                    alpha_list = []  # Need this for the second loop to avoid computing alpha again 
                            
                                    # Loop backward through stored (s, y) pairs
                                    for s, y, rho in reversed(list(zip(s_list, y_list, rho_list))):
                                        alpha = rho * np.dot(s, q)
                                        alpha_list.append(alpha)
                                        q -= alpha * y
                            
                                    # Initial Hessian approximation is identity: H0 = I, so H0 * q = q
                                    # Note: In the first iteration (when s_list is empty), the loops do nothing,
                                    # so r = q = g, and thus p = -r = -g (steepest descent direction)
                                    r = q
                            
                                    # Loop forward through stored (s, y) pairs
                                    for (s, y, rho), alpha in zip(zip(s_list, y_list, rho_list), reversed(alpha_list)):
                                        beta = rho * np.dot(y, r)
                                        r += s * (alpha - beta)
                            
                                    # Search direction
                                    p = -r
                            
                                    # Compute the step size by Line search satisfying Wolfe conditions
                                    eta = line_search(f, grad_f, theta, p)
                            
                                    # Update parameters
                                    theta += eta * p
                                    grad_next = grad_f(theta)
                            
                                    # Update memory for (s, y) pairs
                                    s = eta * p
                                    y = grad_next - g
                                    if np.dot(s, y) > 1e-10 * np.linalg.norm(s) * np.linalg.norm(y): 
                                        if len(s_list) == m:
                                            s_list.pop(0)
                                            y_list.pop(0)
                                            rho_list.pop(0)
                                        s_list.append(s)
                                        y_list.append(y)
                                        rho_list.append(1.0 / np.dot(y, s))
                                        
                                    # Update gradient
                                    g = grad_next
                            
                                return theta
                            
                            # Objective function and its gradient: 
                            # The Rosenbrock function is commonly used for testing optimization algorithms.
                            def rosenbrock(x):
                                return np.sum(100 * (x[1:] - x[:-1]**2)**2 + (1 - x[:-1])**2)
                            
                            def grad_rosenbrock(x):
                                grad = np.zeros_like(x)
                                grad[:-1] = -400 * x[:-1] * (x[1:] - x[:-1]**2) - 2 * (1 - x[:-1]) # # x_1 to x_n-1
                                grad[1:] += 200 * (x[1:] - x[:-1]**2) # x_2 to x_n
                                return grad
                            
                            # Finite difference gradient to check grad_rosenbrock().
                            def finite_difference_gradient(f, x, epsilon=1e-6):
                                grad = np.zeros_like(x)
                                for i in range(len(x)):
                                    x_forward = x.copy()
                                    x_backward = x.copy()
                                    x_forward[i] += epsilon
                                    x_backward[i] -= epsilon
                                    grad[i] = (f(x_forward) - f(x_backward)) / (2 * epsilon)
                                return grad
                            
                            if __name__ == "__main__":
                                
                                n = 50 # Dimensionality
                                
                                # Randomly generate an initial point x0:
                                # You could try : x0 = np.ones(n) + 0.3 * np.random.randn(n), which represents adding small 
                                # perturbation around the global minimum x* = [1, ... , 1]^\top 
                                x0 =  np.random.randn(n)  
                                numeric_opt = limited_bfgs(rosenbrock, grad_rosenbrock, x0)
                                print("Initial point: \n", x0.tolist())
                                print("\n Numerical optimum: \n", numeric_opt.tolist())
                            
                                '''
                                # If you are not sure about the gradient of the objective, always you can compare it with finite difference.
                                grad_analytic = grad_rosenbrock(x0)
                                grad_numeric = finite_difference_gradient(rosenbrock, x0)
                                relative_error_grad = np.linalg.norm(grad_analytic - grad_numeric) / np.linalg.norm(grad_analytic)
                                print("Relative error:", relative_error_grad) 
                                ''' 
                                # Relative error between the numerical optimum and the global optimum
                                global_opt = np.ones(n)  # The actual global optimum of Rosenbrock function is x* = [1, ... , 1]^\top
                                relative_error_optimal = np.linalg.norm(numeric_opt - global_opt) / np.linalg.norm(global_opt)
                                print(f"\n Relative error to the global minimum: {relative_error_optimal*100:.8f}%")
                            

Why Rosenbrock is a hard benchmark

Even though \(\mathbf{x}^* = (1, 1, \ldots, 1)\) is easy to read off analytically, numerical optimizers struggle with the Rosenbrock landscape for three compounding reasons:

  • Narrow, curved valley. The minimum lies at the bottom of a bent ravine. Steep walls deflect iterates and trigger over- or undershooting unless curvature information is used.
  • Ill-conditioning. Along the valley the Hessian has eigenvalues differing by several orders of magnitude. The condition number is large, and first-order methods slow dramatically (cf. the condition-number analysis later in this section).
  • Slow progress along the floor. Along the valley floor the gradient is small while the function is still far from its minimum, so progress requires long steps that must not overshoot the valley walls.

These three properties mirror pathologies commonly reported for loss landscapes in deep learning, namely anisotropic curvature, ill-conditioning, and flat plateaus, which is why Rosenbrock remains a standard testbed for new optimization algorithms.