Skip to main content

πŸ“ Gradient Descent

Description​

< What is it? >​

  • [The idea]
    • Gradient descent is the iterative optimizer behind almost all model training: start from random parameters, measure which direction increases the loss, and step the other way. Repeat until the loss stops improving (convergence).

    • The gradient βˆ‡J(ΞΈ) (βˆ‡: Nabla = differential operator, here is the gradient) is the vector of partial derivatives of the loss with respect to every parameter. It points in the direction of steepest increase, so βˆ’βˆ‡J(ΞΈ) is the steepest descent direction β€” hence the minus sign:

      ΞΈ := ΞΈ βˆ’ Ξ± Β· βˆ‡J(ΞΈ)
      β”‚ β”‚ β”‚
      new β”‚ gradient of the loss w.r.t. every parameter
      params learning rate Ξ± (step size)
    • Nothing about this is specific to neural networks. It is the same rule for linear regression, logistic regression and a 100-billion-parameter transformer β€” only the way the gradient is computed differs (a closed-form expression for the simple cases, backpropagation for deep nets).

  • [What it requires]
    • A loss that is differentiable with respect to the parameters. This is why classification models optimize cross-entropy rather than accuracy: accuracy is a step function with zero gradient almost everywhere.
    • Models whose parameters are not fit by a continuous objective β€” decision trees and their ensembles β€” do not use it. They search over discrete splits instead.
  • [The training loop]
    repeat until converged:
    1. forward β€” compute predictions Ε· for a batch of examples
    2. loss β€” compare Ε· to y, get a single scalar J
    3. backward β€” backpropagate to get βˆ‡J(ΞΈ) for every parameter
    4. step β€” ΞΈ := ΞΈ βˆ’ Ξ± Β· βˆ‡J(ΞΈ)

< Training-loop terminology >​

TermMeaning
BatchA group of training examples processed together in one forward and backward pass
Mini-batchA small batchβ€”usually only part of the training setβ€”used for one step. In practice, β€œbatch” usually means β€œmini-batch”
Iteration / stepOne parameter update using one batch
EpochOne complete pass through all training batches

< Example >​

For example, suppose you have 1,000 training examples:

  • Batch size = 100
  • Each batch causes one parameter update
  • Number of updates per epoch:
1000100=10\frac{1000}{100}=10

Therefore:

  • 1 epoch = process all 1,000 examples once = 10 updates
  • 5 epochs = process them five times = 50 updates
  • 100 epochs = process them 100 times = 1,000 updates

< Batch, stochastic, mini-batch >​

VariantExamples per stepGradient qualityReality
Batch (full)All mExact, smooth descentOne update per epoch. Too slow, and the dataset may not fit in memory
Stochastic (SGD)1Very noisyUpdates constantly, but wastes hardware β€” no vectorization
Mini-batch32 – 8192Noisy but usableWhat everyone actually uses. Vectorizes on GPU and the noise is useful
  • The noise in mini-batch gradients is not purely a cost. It helps the optimizer escape saddle points and narrow minima, which is one reason a moderate batch size often generalizes better than an enormous one.
  • Batch size and learning rate are coupled: multiplying the batch size by k roughly permits multiplying the learning rate by k (the linear scaling rule), because each gradient estimate is proportionally less noisy.
  • Shuffle every epoch. Data that arrives sorted by label makes each batch's gradient point somewhere unrepresentative.
  • Where to start: 32, 64, 128 or 256. The upper end of the table is reserved for large-scale distributed runs; the practical ceiling on a single device is GPU memory, since activations for the whole batch must be held for the backward pass.
  • Too small has its own failure mode. Batch normalization estimates its statistics from the batch, so below roughly 8–16 examples those estimates get noisy enough to hurt, and a batch size of 1 leaves it undefined in training mode.

< The learning rate β€” the one knob that matters most >​

Ξ± too small Ξ± about right Ξ± too large
β•² β•² β•² β•±
β•².... β•² β•² β•± ↑ diverges
β•²....β€’ β•²___β€’ β•²β•±
creeps, may never lands in the basin overshoots the valley,
reach the minimum in few steps loss β†’ NaN
  • Schedules decay Ξ± over training: step decay, cosine annealing, or 1/√t. Large steps early to cover ground, small steps late to settle. Cosine decay is the default for transformer training.
  • Warmup ramps Ξ± up from ~0 over the first few hundred/thousand steps. Adaptive optimizers have unreliable variance estimates at step 1, and a full-size step then can wreck the initialization.
  • Find a starting value by sweeping Ξ± on a log scale (1e-5 … 1e-1) for a few hundred steps and taking the largest value whose loss still falls smoothly.

Key points​

< Optimizers: what each one fixes >​

OptimizerAddsFixes
SGDβ€”Baseline. Slow through ravines, sensitive to Ξ±
+ MomentumVelocity: an exponentially-weighted average of past gradientsDamps zig-zag across a narrow valley, accelerates along it
+ NesterovLook-ahead gradientSlightly better-damped momentum
AdaGradPer-parameter Ξ± scaled by accumulated squared gradientsRare features get larger steps β€” but Ξ± decays to zero and training stalls
RMSPropSame, with a moving average instead of a sumRemoves AdaGrad's terminal stall
AdamRMSProp + momentum + bias correctionThe robust default. Works well with little tuning
AdamWDecouples weight decay from the adaptive stepMakes regularization behave correctly β€” see Regularization
  • In practice: AdamW for transformers and most NLP; SGD with momentum still matches or beats it on vision CNNs and often generalizes slightly better; plain SGD for convex problems where you can tune Ξ± properly.
  • All of them are still gradient descent. They only reshape how the raw gradient is turned into a step.

< The landscape: why this works at all >​

  • For convex losses (linear regression's MSE, logistic regression's log-loss) there is a single minimum and gradient descent provably finds it. Everything is well behaved.
  • Deep networks are non-convex β€” a surface with countless critical points. The classical worry was getting trapped in a bad local minimum. In high dimensions that turns out to be rare: a local minimum requires every one of millions of directions to curve upward. What you actually hit are saddle points (up in some directions, down in others), and gradient noise plus momentum escape them.
  • Most minima found in practice reach a similar loss. Flat minima β€” wide basins where nearby parameters give similar loss β€” tend to generalize better than sharp ones, which is another argument for the noise in mini-batch SGD.

< Reading the loss curve >​

SymptomLikely causeMove
Loss β†’ NaN / inf after a few stepsΞ± too large, or exploding gradientsCut Ξ± 10Γ—, add gradient clipping, add warmup
Loss flat from step 0Ξ± far too small, dead ReLUs, or the gradient never reaches the paramsRaise Ξ±; check requires_grad, zero_grad(), and that the loss is connected to the graph
Loss falls then plateaus highUnderfitting, or Ξ± now too large to settleAdd capacity, or decay Ξ± β€” see Bias & Variance
Very spiky lossBatch too small, Ξ± too large, or unshuffled dataLarger batch, lower Ξ±, shuffle
Training loss falls, validation risesOverfitting β€” not an optimizer problemEarly stopping, regularization
Slow zig-zag progressIll-conditioned surface from unscaled featuresNormalize the inputs; use momentum or Adam

< Things that quietly break it >​

  1. [Unscaled features] elongated loss contours cap Ξ± at whatever the steepest direction tolerates β€” the geometry argument in Data Normalization.
  2. [Exploding / vanishing gradients] deep stacks multiply gradients through many layers. Fixes are structural: gradient clipping, residual connections, careful initialization (He/Xavier([ˈzΓ¦vΙͺr])), and normalization layers.
  3. [Forgetting to reset gradients] PyTorch accumulates into .grad; without optimizer.zero_grad() every step uses the sum of all previous gradients.
  4. [Loss scale under mixed precision] fp16 gradients underflow to zero. Use a gradient scaler, or bf16.
  5. [Tuning Ξ± against the test set] the learning rate is a hyperparameter like any other β€” see the validation-set rule in Bias & Variance.

< When not to use it >​

  • Closed form exists. OLS linear regression has the normal equation ΞΈ = (Xα΅€X)⁻¹Xα΅€y β€” exact, no Ξ± to tune. Gradient descent wins once n is large enough that inverting Xα΅€X is expensive.
  • Second-order methods (Newton, L-BFGS) use curvature and converge in far fewer iterations, but need the Hessian or an approximation of it β€” quadratic in the parameter count. Practical for small convex problems, hopeless at deep-learning scale, which is why first-order methods dominate.

Implementation​

< Linear Regression Using Gradient Descent >​

Question: Write a Python function that performs linear regression using gradient descent. The function should take NumPy arrays X (features with a column of ones for the intercept) and y (target) as input, along with the learning rate alpha and the number of iterations. Return the learned coefficients (weights) as a NumPy array.
Requirements:

  • Minimize the Mean Squared Error (MSE) loss function: L(ΞΈ)=12mβˆ‘i=1m(hΞΈ(x(i))βˆ’y(i))2L(\theta)=\frac{1}{2m}\sum_{i=1}^{m}\left(h_\theta\left(x^{(i)}\right)-y^{(i)}\right)^2 Here, hΞΈ(x(i))=(x(i))⊀θh_\theta\left(x^{(i)}\right)=\left(x^{(i)}\right)^\top\theta is the prediction for training example ii, and mm is the number of training examples. The factor 1/21/2 simplifies the gradient calculation.
  • Initialize all weights to zero
  • Use batch gradient descent (use all samples in each iteration)

The input matrix X has shape (m, n) where m is the number of training examples and n is the number of features (including the bias column of ones). The target vector y has shape (m,).

Input: X = np.array([[1, 1], [1, 2], [1, 3]]), y = np.array([3, 5, 7]), alpha = 0.1, iterations = 1000

Output: [1.0, 2.0]

Solution:
The vectorized loss is

L(ΞΈ)=12mβˆ₯XΞΈβˆ’yβˆ₯22L(\theta)=\frac{1}{2m}\lVert X\theta-y\rVert_2^2

so its gradient is

βˆ‡ΞΈL(ΞΈ)=1mX⊀(XΞΈβˆ’y)\nabla_\theta L(\theta)=\frac{1}{m}X^\top(X\theta-y)
Step-by-step derivation of the gradient

Let the prediction error vector be

r=XΞΈβˆ’y.r = X\theta-y.

The squared L2L_2 norm is the sum of squared errors:

βˆ₯XΞΈβˆ’yβˆ₯22=(XΞΈβˆ’y)⊀(XΞΈβˆ’y)=βˆ‘i=1m(y^iβˆ’yi)2.\lVert X\theta-y\rVert_2^2 =(X\theta-y)^\top(X\theta-y) =\sum_{i=1}^{m}(\hat y_i-y_i)^2.

Therefore,

L(ΞΈ)=12m(XΞΈβˆ’y)⊀(XΞΈβˆ’y).L(\theta)=\frac{1}{2m}(X\theta-y)^\top(X\theta-y).
Derivation by expansion​

Expand the product:

L(ΞΈ)=12m(XΞΈβˆ’y)⊀(XΞΈβˆ’y)=12m(θ⊀X⊀XΞΈβˆ’2y⊀XΞΈ+y⊀y).\begin{aligned} L(\theta) &=\frac{1}{2m}(X\theta-y)^\top(X\theta-y)\\ &=\frac{1}{2m}\left(\theta^\top X^\top X\theta-2y^\top X\theta+y^\top y\right). \end{aligned}

Differentiate each term with respect to ΞΈ\theta:

βˆ‡ΞΈ(θ⊀X⊀XΞΈ)=2X⊀XΞΈ,\nabla_\theta\left(\theta^\top X^\top X\theta\right)=2X^\top X\theta,

because X⊀XX^\top X is symmetric, and

βˆ‡ΞΈ(βˆ’2y⊀XΞΈ)=βˆ’2X⊀y.\nabla_\theta\left(-2y^\top X\theta\right)=-2X^\top y.

The final term does not depend on ΞΈ\theta:

βˆ‡ΞΈ(y⊀y)=0.\nabla_\theta(y^\top y)=0.

Putting everything together:

βˆ‡ΞΈL(ΞΈ)=12m(2X⊀XΞΈβˆ’2X⊀y)=1m(X⊀XΞΈβˆ’X⊀y)=1mX⊀(XΞΈβˆ’y).\begin{aligned} \nabla_\theta L(\theta) &=\frac{1}{2m}\left(2X^\top X\theta-2X^\top y\right)\\ &=\frac{1}{m}\left(X^\top X\theta-X^\top y\right)\\ &=\boxed{\frac{1}{m}X^\top(X\theta-y)}. \end{aligned}

The factor 1/21/2 in the loss cancels the 22 produced when differentiating the squared error.

Element-by-element interpretation​

For parameter ΞΈj\theta_j,

βˆ‚Lβˆ‚ΞΈj=1mβˆ‘i=1m(y^iβˆ’yi)xij.\frac{\partial L}{\partial\theta_j} =\frac{1}{m}\sum_{i=1}^{m}(\hat y_i-y_i)x_{ij}.

This means:

  1. Compute every prediction error, XΞΈβˆ’yX\theta-y.
  2. Multiply each error by its corresponding feature value.
  3. Sum across all training examples using X⊀X^\top.
  4. Divide by mm.

Stacking all parameter derivatives into one vector gives

βˆ‡ΞΈL(ΞΈ)=1mX⊀(XΞΈβˆ’y).\nabla_\theta L(\theta)=\frac{1}{m}X^\top(X\theta-y).

The gradient-descent update is consequently

ΞΈβ†ΞΈβˆ’Ξ±1mX⊀(XΞΈβˆ’y).\theta\leftarrow\theta-\alpha\frac{1}{m}X^\top(X\theta-y).
import numpy as np

def linear_regression_gradient_descent(X: np.ndarray, y: np.ndarray, alpha: float, iterations: int) -> np.ndarray:
"""
Perform linear regression using gradient descent.

Args:
X: Feature matrix of shape (m, n) where first column is all ones (for intercept)
y: Target vector of shape (m,)
alpha: Learning rate
iterations: Number of gradient descent iterations

Returns:
Learned weights as a 1D array of shape (n,)
"""
m, n = X.shape
y = y.reshape(-1, 1) # Ensure y is a column vector
theta = np.zeros((n, 1)) # Initialize weights to zeros

for _ in range(iterations):
predictions = X @ theta
errors = predictions - y
gradient = (X.T @ errors) / m
theta = theta - alpha * gradient
return theta.flatten()

Q&A​

< What is convergence? >​

  • Convergence is when the loss stops improving. In practice, this is usually defined as a plateau in the validation loss, or a small enough gradient norm. It does not necessarily mean that the model has reached the global minimum of the loss function, especially in non-convex problems like deep learning.

< Is convergence to the global optimum guaranteed? >​

  • Not always.
    For convex loss functions, gradient descent is guaranteed to converge to the global optimum.
    However, for non-convex loss functions, such as those encountered in deep learning, convergence to a global optimum is not guaranteed. Instead, the algorithm may converge to a local minimum or saddle point.

  • Convex Objectives​

    If ff is convex and differentiable, every local minimum is global. Gradient descent converges to a global optimum under standard conditions, such as:

    • The gradient is Lipschitz-continuous.
    • The learning rate is sufficiently small, often η≀1/L\eta \leq 1/L, where LL is the gradient smoothness constant.
    • An optimum exists.

    For a smooth convex function, the typical objective-gap rate is:

    f(ΞΈt)βˆ’f(ΞΈβˆ—)=O(1/t).f(\theta_t)-f(\theta^*)=O(1/t).

    If the function is also strongly convex, convergence is geometric:

    f(ΞΈt)βˆ’f(ΞΈβˆ—)=O(ρt),0<ρ<1.f(\theta_t)-f(\theta^*)=O(\rho^t), \qquad 0 < \rho < 1.
  • Non-Convex Objectives​

    For non-convex objectives, including most neural networks, global optimality is not generally guaranteed. Gradient descent can encounter:

    • Multiple local minima
    • Saddle points
    • Flat plateaus
    • Poorly conditioned regions
    • Vanishing or exploding gradients

    Under smoothness assumptions, a common guarantee is only convergence toward a stationary point:

    βˆ₯βˆ‡f(ΞΈt)βˆ₯β†’0.\|\nabla f(\theta_t)\|\rightarrow 0.

    A stationary point can be a local minimum, local maximum, or saddle pointβ€”not necessarily a global optimum.

  • Stochastic Gradient Descent​

    Stochastic gradient descent (SGD) uses a noisy gradient estimate from a sample or mini-batch:

    ΞΈt+1=ΞΈtβˆ’Ξ·tβˆ‡f^(ΞΈt).\theta_{t+1}=\theta_t-\eta_t\widehat{\nabla f}(\theta_t).

    Its noise makes updates cheaper and can help escape some saddle points or shallow local minima. However, with a fixed learning rate, SGD often fluctuates around a solution rather than converging exactly. Suitable decreasing learning rates, such as conditions

    βˆ‘tΞ·t=∞,βˆ‘tΞ·t2<∞,\sum_t \eta_t = \infty, \qquad \sum_t \eta_t^2 < \infty,

    can provide convergence guarantees under additional assumptions.

  • Practical Takeaway​
    • Convex problem:
      Global convergence is often guaranteed with an appropriate learning-rate schedule.
    • Strongly convex problem:
      Global convergence is usually fast and the optimum is unique.
    • Non-convex problem:
      Usually only stationary-point convergence is guaranteed.
    • Deep learning:
      There is generally no global-optimum guarantee, although overparameterization, initialization, normalization, adaptive optimizers, and SGD often produce good solutions empirically.

Crash course​

  • Gradient descent

Reference​