VivaPrep
← Jaber Notes

Jaber Notes · 4 of 16

Optimization

GD convergence, SGD, momentum, Adam/AdamW, LR schedules, Newton/L-BFGS.

How models actually get trained: convergence theory for gradient descent, the variance of SGD, why momentum accelerates, the full Adam/AdamW update rules, and learning-rate schedules that matter in practice.

Visual reference

Gradient descent & learning rate

lossgood LRLR too high (overshoots)LR too low (crawls)
A good learning rate takes steady steps down the loss bowl to the minimum. Too small crawls painfully slowly; too large overshoots and can bounce out of the bowl entirely.
How we actually find model parameters: gradient descent and its variants, convergence theory, and practical tricks.

Table of contents

  1. First-order vs second-order methods
  2. Gradient descent (batch)
  3. Stochastic and mini-batch SGD
  4. Momentum and Nesterov
  5. Adaptive learning rate methods
  6. Second-order methods (Newton, quasi-Newton)
  7. Learning rate schedules
  8. Convergence theory (overview)
  9. Practical optimization tips
  10. Coordinate descent

1. First-order vs second-order methods

PropertyFirst-orderSecond-order
Info usedGradients f\nabla fGradients + Hessian H\mathbf{H}
Cost per stepCheap (O(np)O(np))Expensive (O(p2)O(p^2) to O(p3)O(p^3))
Convergence rateLinearQuadratic (near optimum)
Scalability\leq billions of parametersUp to \simthousands
ExamplesGD, SGD, AdamNewton, BFGS, L-BFGS

Most deep learning uses first-order. Classical ML in medium-scale uses L-BFGS or IRLS.


2. Gradient descent (batch)

2.1 Algorithm

Initialize w0\mathbf{w}_0. At each step tt:

wt+1=wtηtf(wt),\mathbf{w}_{t+1} = \mathbf{w}_t - \eta_t \nabla f(\mathbf{w}_t),

where ηt>0\eta_t > 0 is the learning rate (step size).

Intuition: negative gradient points in the direction of steepest decrease. GD takes a step of size ηt\eta_t in that direction.

2.2 Why step in the negative gradient direction?

First-order Taylor expansion:

f(w+δ)f(w)+f(w)δ.f(\mathbf{w} + \boldsymbol{\delta}) \approx f(\mathbf{w}) + \nabla f(\mathbf{w})^\top \boldsymbol{\delta}.

To decrease ff most per unit of δ\|\boldsymbol{\delta}\|: choose δf(w)\boldsymbol{\delta} \propto -\nabla f(\mathbf{w}) (steepest descent direction, from Cauchy-Schwarz).

2.3 Convergence for LL-smooth convex functions

A function is LL-smooth if f(x)f(y)Lxy\|\nabla f(\mathbf{x}) - \nabla f(\mathbf{y})\| \leq L\|\mathbf{x}-\mathbf{y}\| (Lipschitz gradients, bounded curvature).

With η=1/L\eta = 1/L:

f(wT)fLw0w22T.f(\mathbf{w}_T) - f^\star \leq \frac{L\|\mathbf{w}_0 - \mathbf{w}^\star\|^2}{2T}.

This is O(1/T)O(1/T) convergence — to get ε\varepsilon-accurate, need T=O(1/ε)T = O(1/\varepsilon) iterations.

2.4 Convergence for strongly convex functions

A function is μ\mu-strongly convex if 2fμI\nabla^2 f \succeq \mu\mathbf{I} everywhere (μ>0\mu > 0).

With step η=1/L\eta = 1/L, GD converges linearly (exponentially fast):

wtw2(1μL)tw0w2.\|\mathbf{w}_t - \mathbf{w}^\star\|^2 \leq \left(1-\frac{\mu}{L}\right)^t \|\mathbf{w}_0 - \mathbf{w}^\star\|^2.

Condition number κ=L/μ\kappa = L/\mu: higher κ\kappa → slower convergence. Ridge regression adds λI\lambda\mathbf{I} which increases μ\mu and reduces κ\kappa → faster convergence.

2.5 Learning rate sensitivity

Learning rateBehavior
Too large (η>2/L\eta > 2/L)Diverges (oscillates then explodes)
Too smallConverges but very slowly
Just right (η1/L\eta \approx 1/L)Optimal convergence

Optimal η\eta requires knowing LL (Lipschitz constant of gradient), which is usually estimated via line search or set empirically.


3. Stochastic and mini-batch SGD

3.1 Motivation

Batch GD evaluates f(w)=1nii(w)\nabla f(\mathbf{w}) = \frac{1}{n}\sum_i \nabla \ell_i(\mathbf{w}) over all nn samples per step — expensive for large nn.

Stochastic GD (SGD): use gradient of one random sample as an estimate:

wt+1=wtηtit(wt),itUniform({1,,n}).\mathbf{w}_{t+1} = \mathbf{w}_t - \eta_t \nabla \ell_{i_t}(\mathbf{w}_t), \quad i_t \sim \text{Uniform}(\{1,\ldots,n\}).

it(w)\nabla \ell_{i_t}(\mathbf{w}) is an unbiased estimator of f(w)\nabla f(\mathbf{w}): E[it]=f\mathbb{E}[\nabla \ell_{i_t}] = \nabla f.

3.2 Mini-batch SGD

Compromise: use a batch of BB samples (batch size):

~f=1BiBti(wt).\tilde{\nabla}f = \frac{1}{B}\sum_{i \in \mathcal{B}_t}\nabla \ell_i(\mathbf{w}_t).
  • Gradient variance: σ2/B\propto \sigma^2/B. Larger batch → less noise → more stable updates.
  • Cost per step: O(Bp)O(B \cdot p). Number of steps to see all data once (epoch) = n/Bn/B.
  • Hardware: larger batches allow better GPU parallelism, but may require adjusting learning rate.

Linear scaling rule: if you multiply batch size by kk, multiply learning rate by kk (empirically works in certain regimes).

3.3 SGD vs batch GD tradeoffs

Batch GDMini-batch SGD
Gradient qualityExactNoisy but unbiased
ConvergenceSmoothNoisy (may oscillate near optimum)
GeneralizationSometimes slightly worseNoisy updates can regularize (implicit regularization)
MemoryNeed full datasetOnly batch size
ParallelismEasyBest with moderate batch

Noise as regularizer: SGD's gradient noise prevents convergence to sharp minima (which tend to generalize poorly) — an implicit regularization effect observed empirically.

3.4 Epoch vs iteration

  • Iteration: one gradient update.
  • Epoch: one full pass through the training data (n/Bn/B iterations).

4. Momentum and Nesterov

4.1 SGD with momentum

Problem with vanilla SGD: slow convergence in directions with low curvature (gradient zig-zags across narrow valleys).

Momentum accumulates a velocity vector:

vt+1=βvt+ηf(wt),\mathbf{v}_{t+1} = \beta \mathbf{v}_t + \eta\nabla f(\mathbf{w}_t),
wt+1=wtvt+1.\mathbf{w}_{t+1} = \mathbf{w}_t - \mathbf{v}_{t+1}.

β[0,1)\beta \in [0,1) is the momentum coefficient (typically 0.9). Velocity is an exponentially weighted moving average of past gradients.

Effect: accelerates in consistent directions, dampens oscillations across the valley.

Effective step size in consistent direction: if gradient is always the same g\mathbf{g}, velocity converges to v=η1βg\mathbf{v} = \frac{\eta}{1-\beta}\mathbf{g}, so effective step = η1β\frac{\eta}{1-\beta} times the learning rate.

4.2 Nesterov accelerated gradient (NAG)

Improvement: compute gradient at the look-ahead position:

vt+1=βvt+ηf(wtβvt),\mathbf{v}_{t+1} = \beta \mathbf{v}_t + \eta \nabla f(\mathbf{w}_t - \beta\mathbf{v}_t),
wt+1=wtvt+1.\mathbf{w}_{t+1} = \mathbf{w}_t - \mathbf{v}_{t+1}.

Theoretical convergence for smooth convex functions: O(1/T2)O(1/T^2) vs O(1/T)O(1/T) for vanilla GD.


5. Adaptive learning rate methods

5.1 Motivation

Different parameters can have very different scales and gradient magnitudes. A single learning rate is suboptimal. Adaptive methods maintain per-parameter learning rates.

5.2 AdaGrad

Accumulate squared gradients:

Gt,j=s=1t(f(ws))j2,G_{t,j} = \sum_{s=1}^t (\nabla f(\mathbf{w}_s))_j^2,
wt+1,j=wt,jηGt,j+ε(f(wt))j.w_{t+1,j} = w_{t,j} - \frac{\eta}{\sqrt{G_{t,j}+\varepsilon}} (\nabla f(\mathbf{w}_t))_j.

Effect: large learning rate for infrequent (sparse) features, small for frequent ones. Good for sparse data (NLP, embeddings). Problem: Gt,jG_{t,j} only grows → learning rate monotonically decreases → may stop learning.

5.3 RMSProp

Fix AdaGrad's vanishing lr with exponentially decaying average:

Gt,j=ρGt1,j+(1ρ)(f)j2,ρ0.9.G_{t,j} = \rho G_{t-1,j} + (1-\rho)(\nabla f)_j^2, \quad \rho \approx 0.9.
wt+1,j=wt,jηGt,j+ε(f)j.w_{t+1,j} = w_{t,j} - \frac{\eta}{\sqrt{G_{t,j}+\varepsilon}}(\nabla f)_j.

5.4 Adam (Adaptive Moment Estimation)

Most widely used in practice. Maintains both first and second moment estimates:

First moment (mean): mt=β1mt1+(1β1)f(wt)\mathbf{m}_t = \beta_1 \mathbf{m}_{t-1} + (1-\beta_1)\nabla f(\mathbf{w}_t) Second moment (variance): vt=β2vt1+(1β2)(f(wt))2\mathbf{v}_t = \beta_2 \mathbf{v}_{t-1} + (1-\beta_2)(\nabla f(\mathbf{w}_t))^2 (elementwise)

Bias correction (accounts for initialization at zero):

m^t=mt1β1t,v^t=vt1β2t.\hat{\mathbf{m}}_t = \frac{\mathbf{m}_t}{1-\beta_1^t}, \quad \hat{\mathbf{v}}_t = \frac{\mathbf{v}_t}{1-\beta_2^t}.

Update:

wt+1=wtηv^t+εm^t.\boxed{\mathbf{w}_{t+1} = \mathbf{w}_t - \frac{\eta}{\sqrt{\hat{\mathbf{v}}_t}+\varepsilon}\hat{\mathbf{m}}_t.}

Default hyperparameters: η=0.001\eta=0.001, β1=0.9\beta_1=0.9, β2=0.999\beta_2=0.999, ε=108\varepsilon=10^{-8}.

Intuition: like momentum (first moment), but adapts step size by second moment (variance of recent gradients). Large gradient variance → small step (cautious in noisy directions).

AdamW: Adam + decoupled weight decay. Standard in modern DL training. Adds λw-\lambda\mathbf{w} to update directly (not through gradient), properly implementing L2 regularization.

5.5 Comparison table

OptimizerFormula typeWhen to use
SGDFixed lrSimple convex, well-tuned
SGD+MomentumAcceleratedVision tasks with good tuning
AdaGradAdaptiveSparse features, NLP
RMSPropAdaptive decayRNNs, non-stationary
Adam/AdamWAdaptive + momentumGeneral default, DL
L-BFGSSecond-order quasi-NewtonClassical ML, small/medium nn

6. Second-order methods (Newton, quasi-Newton)

6.1 Newton's method

Use quadratic approximation:

f(w+δ)f(w)+fδ+12δHδ.f(\mathbf{w}+\boldsymbol{\delta}) \approx f(\mathbf{w}) + \nabla f^\top\boldsymbol{\delta} + \frac{1}{2}\boldsymbol{\delta}^\top\mathbf{H}\boldsymbol{\delta}.

Minimize over δ\boldsymbol{\delta}: δ=H1f\boldsymbol{\delta}^\star = -\mathbf{H}^{-1}\nabla f.

Update: wt+1=wtHt1f(wt)\mathbf{w}_{t+1} = \mathbf{w}_t - \mathbf{H}_t^{-1}\nabla f(\mathbf{w}_t).

Quadratic convergence near optimum: number of correct digits roughly doubles per step.

Problem: inverting HRp×p\mathbf{H} \in \mathbb{R}^{p\times p} costs O(p3)O(p^3) — impractical for large pp.

6.2 L-BFGS (Limited-memory BFGS)

BFGS approximates H1\mathbf{H}^{-1} via rank-1 updates using gradient differences. L-BFGS stores only the last mm (typically 5–20) gradient/position pairs, costing O(mp)O(mp) per step.

Used in sklearn's LogisticRegression(solver='lbfgs') and scipy.optimize.minimize(method='L-BFGS-B').

6.3 IRLS (Iteratively Reweighted Least Squares)

Newton's method applied to GLMs (logistic regression) can be rewritten as a weighted least squares problem at each iteration. sklearn's LogisticRegression(solver='newton-cg').


7. Learning rate schedules

7.1 Constant learning rate

Simple, often used with Adam. May oscillate near optimum.

7.2 Step decay

Reduce lr by a factor every kk epochs: ηt=η0γt/k\eta_t = \eta_0 \cdot \gamma^{\lfloor t/k \rfloor}.

7.3 Exponential decay

ηt=η0eλt\eta_t = \eta_0 \cdot e^{-\lambda t}.

7.4 Cosine annealing

\eta_t = \eta_\min + \frac{1}{2}(\eta_\max - \eta_\min)\left(1+\cos\frac{t\pi}{T}\right).

Smoothly decays from \eta_\max to \eta_\min. Often paired with warm restarts (restart at peak periodically).

7.5 Linear warmup + decay

Ramp learning rate up for first few steps/epochs, then decay. Important for large-batch training and transformers.

7.6 Reduce on plateau

Reduce lr when monitored metric stops improving. keras.callbacks.ReduceLROnPlateau.


8. Convergence theory (overview)

8.1 Summary for different function classes

Function classOptimal rateAlgorithm
Convex, LL-smoothO(1/T)O(1/T)GD
μ\mu-strongly convex, LL-smoothO(exp(μT/L))O(\exp(-\mu T/L))GD (linear)
Convex, LL-smooth (Nesterov)O(1/T2)O(1/T^2)NAG
Non-convex (DL)O(1/T)O(1/\sqrt{T})SGD (to stationary point)

8.2 Saddle points in non-convex optimization

Non-convex functions (neural nets) have saddle points — zero gradient, not a minimum. Second-order methods can get stuck; SGD's noise helps escape. Saddle points are less problematic in practice than once feared (high-dimensional saddles are usually surrounded by directions that lead downward).

8.3 Local minima in neural networks

Modern evidence: many local minima in deep networks have similar (near-optimal) loss values. The harder problem is plateaus and saddle points early in training.


9. Practical optimization tips

9.1 Gradient checking

Numerically verify gradient implementation:

f(w+εej)f(wεej)2ε(f)j.\frac{f(\mathbf{w}+\varepsilon\mathbf{e}_j) - f(\mathbf{w}-\varepsilon\mathbf{e}_j)}{2\varepsilon} \approx (\nabla f)_j.

Use ε105\varepsilon \approx 10^{-5}. Match to within relative error <104< 10^{-4}.

9.2 Feature scaling and conditioning

GD on unscaled features can be very slow: gradient points toward steepest descent direction in feature space, which may zig-zag across poorly scaled dimensions. Always scale features (StandardScaler, MinMaxScaler) before gradient-based optimization.

9.3 Gradient clipping

For exploding gradients (especially in RNNs): if f>threshold\|\nabla f\| > \text{threshold}, scale it: fthresholdff\nabla f \leftarrow \frac{\text{threshold}}{\|\nabla f\|}\nabla f.

9.4 Initialization

Weights initialized at zero → symmetric neurons learn the same features in neural nets. Use random initialization; scale to control gradient magnitude.

9.5 Batch size and learning rate tradeoff

Batch sizeEffect
Smaller (e.g., 32)More noise, may generalize better, slower per epoch
Larger (e.g., 2048)Less noise, faster epochs, may need LR warmup

Large-batch training is studied in "large-batch optimization" literature.


10. Coordinate descent

Optimize one parameter (or block of parameters) at a time:

wj(t+1)=argminwjf(w1(t+1),,wj1(t+1),wj,wj+1(t),,wp(t)).w_j^{(t+1)} = \arg\min_{w_j} f(w_1^{(t+1)},\ldots, w_{j-1}^{(t+1)}, w_j, w_{j+1}^{(t)},\ldots, w_p^{(t)}).

Convergence: for convex functions with separable non-smooth terms (like L1), coordinate descent with cyclic or random updates converges.

Used for: Lasso (per-coordinate soft-thresholding has closed form), SVM dual (SMO algorithm updates one pair of dual variables at a time).

# Coordinate descent for Lasso (simplified)
import numpy as np

def lasso_coordinate_descent(X, y, lam, n_iter=1000):
    n, p = X.shape
    w = np.zeros(p)
    for _ in range(n_iter):
        for j in range(p):
            r = y - X @ w + X[:, j] * w[j]  # partial residual
            z = X[:, j] @ r / n              # unconstrained update
            w[j] = np.sign(z) * max(abs(z) - lam / (2*n), 0)  # soft threshold
    return w

*File: notes/04_optimization.md — next: notes/05_evaluation_and_validation.md*

From-scratch code

Runnable implementations, each checked against its scikit-learn equivalent.

Gradient descent on MSE vs the OLS closed formclassical_examples/gradient_descent.py
"""
Gradient descent for linear regression (MSE) — compare to closed-form OLS.

Loss: J(w) = (1/2n) ||y - Xw||^2
Gradient: (1/n) X^T (Xw - y)
"""

from __future__ import annotations

import numpy as np
from sklearn.datasets import make_regression
from sklearn.metrics import mean_squared_error


def add_intercept(X: np.ndarray) -> np.ndarray:
    return np.column_stack([np.ones(len(X)), X])


def ols_normal_equations(X: np.ndarray, y: np.ndarray) -> np.ndarray:
    return np.linalg.solve(X.T @ X, X.T @ y)


def mse_grad(X: np.ndarray, y: np.ndarray, w: np.ndarray) -> np.ndarray:
    n = X.shape[0]
    return (X.T @ (X @ w - y)) / n


def gd_linear_regression(
    X: np.ndarray, y: np.ndarray, lr: float, n_iter: int
) -> np.ndarray:
    w = np.zeros(X.shape[1])
    for _ in range(n_iter):
        w -= lr * mse_grad(X, y, w)
    return w


def main() -> None:
    X_raw, y = make_regression(n_samples=300, n_features=4, noise=8.0, random_state=2)
    X_raw = np.asarray(X_raw, dtype=np.float64)
    y = np.asarray(y, dtype=np.float64)
    X = add_intercept(X_raw)

    w_closed = ols_normal_equations(X, y)
    w_gd = gd_linear_regression(X, y, lr=0.15, n_iter=5000)

    print("Closed-form w:", w_closed)
    print("GD w         :", w_gd)
    print("||diff||     :", np.linalg.norm(w_closed - w_gd))
    print("MSE closed   :", mean_squared_error(y, X @ w_closed))
    print("MSE GD       :", mean_squared_error(y, X @ w_gd))


if __name__ == "__main__":
    main()