Gradient Descent
After this lesson, you will:
- Understand how gradient descent (GD) finds loss function minima, and why moving opposite the gradient guarantees descent.
- Express the parameter update rule for MSE in vector form, and implement it from scratch using NumPy.
- Explain why small learning rates cause slow convergence while excessive rates induce oscillations or divergence, characterized by stability threshold ; why feature scaling accelerates optimization, and how to define stopping criteria.
- Distinguish batch GD, stochastic gradient descent (SGD), and mini-batch GD, define an epoch, and train models with scikit-learn’s
SGDRegressor.
Prerequisites
Section titled “Prerequisites”- Linear Regression, in Machine Learning (Learned)
- Calculus, in Math for AI(Not translated yet) (Learned)
Intuition
Section titled “Intuition”Imagine standing on a mountain slope blanketed in dense fog, attempting to reach the valley floor. You cannot see the destination, but you can feel the slope of the ground beneath your boots. The intuitive strategy: take a short step downward in the direction of steepest descent, pause, assess the slope once more, and step again. When the terrain flattens completely beneath your feet, you have reached the valley bottom.
This describes the entire operational concept of gradient descent (GD). The “mountain” is the loss function surface, your “position” represents the model parameters, and local “slope” is the gradient.
While our earlier lesson solved parameters directly via the normal equation, many advanced models (such as logistic regression and deep neural networks) possess no closed-form analytical formulas. Furthermore, when feature dimension scales into thousands, solving linear systems with unknowns requires approximately operations. Gradient descent only requires computing local derivatives at the current parameter position. We apply it to our 10-apartment dataset to trace and verify every step numerically.
| Floor area (m²) | Rent (million VND/month) |
|---|---|
| 25 | 6.7 |
| 30 | 6.5 |
| 36 | 8.8 |
| 42 | 8.8 |
| 48 | 9.4 |
| 55 | 12.2 |
| 60 | 12.0 |
| 68 | 14.1 |
| 75 | 13.9 |
| 85 | 16.8 |
Manually sensing the slope
Section titled “Manually sensing the slope”Begin with an uneducated initial baseline: and , predicting zero rent for every apartment. The resulting MSE is 129.75. Unsure where the minimum lies, nudge each parameter slightly and inspect the change in MSE:
| Trial | MSE |
|---|---|
| Initial state: , | 129.75 |
| Increase by 0.01 | 117.42 |
| Increase by 0.01 | 129.53 |
Nudging either parameter downward reduces loss, confirming both should increase. However, incrementing slashes MSE by 12.33, whereas incrementing reduces loss by merely 0.22—a 50-fold difference. The physical intuition: increasing by 0.01 raises the predicted rent of an average 52 m² apartment by 0.52 million VND, whereas increasing by 0.01 lifts all predictions uniformly by only 0.01 million VND. Local derivatives indicate which direction to step, and with what relative urgency. This directional disparity causes severe optimization challenges addressed by feature scaling.
The objective of this lesson is: from an arbitrary starting parameter vector, iteratively measure gradients and take descent steps until reaching the global MSE minimum, tuning step sizes to converge rapidly without overshooting the valley.
Theory
Section titled “Theory”The gradient: combining partial derivatives into a vector
Section titled “The gradient: combining partial derivatives into a vector”As established earlier, concatenate parameters into column vector . The gradient of at is the vector of all first-order partial derivatives, denoted :
For features, the gradient contains elements. Our linear regression lesson derived the partial derivatives of MSE, where :
Expressed via design matrix (feature column followed by the bias column of ones): vector collects residual errors . Multiplying by computes the respective weighted sums across both components. Multiplying by factor yields the exact gradient vector:
This matrix formulation holds for any arbitrary feature dimension . Setting the gradient to zero yields , which is the normal equation. The stationary termination point of gradient descent coincides with ordinary least squares.
At initial state , the gradient evaluates to . Scaling by step size 0.01 gives shifts of and , matching our manual sensitivity checks.
Why moving opposite the gradient descends the loss
Section titled “Why moving opposite the gradient descends the loss”Single-variable Taylor approximation. Inspected within an infinitesimal neighborhood of point , function closely matches its linear tangent line with slope . For a small perturbation :
Choosing step displacement for small scalar :
Because squared terms are non-negative, strictly decreases (unless , indicating a flat stationary point). In words: if the function ascends to the right (), step left; if it descends to the right (), step right.
Multivariate generalization. Displace parameters by vector . The total first-order differential is the inner product of the gradient and displacement:
Setting transforms the inner product into , which is strictly non-positive. Furthermore, among all candidate vectors of fixed Euclidean length, the inner product minimizes when angle . Thus, the gradient points along the direction of steepest ascent, and its negation points along steepest descent. On a topographic contour map connecting equal-loss coordinates, this descent vector is orthogonal to the local level curve.
The term “approximation” is vital: tangent approximations hold only locally, meaning excessively large steps can overshoot and cause loss to increase.
This foundational concept was formalized in 1847 by Augustin-Louis Cauchy, who solved astronomical orbit calculations by minimizing residual sums of squares through iterative coordinate updates opposite their partial derivatives.
The update rule
Section titled “The update rule”Gradient descent iteratively applies a single vector update:
Here indexes the iteration step, is the initial parameter vector (typically ), and is the learning rate hyperparameter. Substituting the MSE gradient:
Because each step computes gradients across all training samples, this variant is called batch gradient descent. Two notable properties emerge:
- Step lengths naturally shrink near the minimum. The step displacement is . As parameters approach stationary points, gradients diminish, smoothly dampening steps without requiring manually scheduled decay.
- MSE loss for linear regression is strictly convex. The loss surface forms an ideal paraboloid bowl containing a single global minimum, free from deceptive local minima. Neural networks lack this convexity guarantee.
Learning rate dynamics: the 1D case
Section titled “Learning rate dynamics: the 1D case”Consider the canonical 1D quadratic function:
where is the minimizer, is minimum loss, and represents curvature (the second derivative measuring valley steepness). Do not confuse curvature with loss .
The first derivative is . Substituting into update rule and subtracting from both sides:
At each step, parameter distance to the target is multiplied by constant factor . After steps, error scales as . System behavior depends entirely on multiplier :
| Learning rate | Contraction factor | Optimization behavior |
|---|---|---|
| Monotonic convergence from one side. Smaller yields , leading to sluggish progress | ||
| Exact convergence in a single step | ||
| Alternating sign jumps across the minimum: oscillatory yet asymptotically convergent | ||
| Undamped oscillation between boundary states, failing to converge | ||
| Exponentially expanding step lengths: divergence |
Evaluating against (where , optimum , stability bound , starting from ):
| Sequence | ||
|---|---|---|
| 0.1 | 0.8 | 1, 0.8, 0.64, 0.512, 0.4096, 0.3277 |
| 0.5 | 0 | 1, 0, 0, 0, 0, 0 |
| 0.9 | −0.8 | 1, −0.8, 0.64, −0.512, 0.4096, −0.3277 |
| 1.1 | −1.2 | 1, −1.2, 1.44, −1.728, 2.0736, −2.4883 |
Compare rows and : distance magnitudes are identical at every step, differing only in alternating signs. Taking 9-fold larger steps provides zero speed advantage.
Multivariate dynamics: anisotropic curvature
Section titled “Multivariate dynamics: anisotropic curvature”The MSE surface over is a 2D paraboloid bowl with asymmetric curvature along different axes. Second-order partial derivatives form the Hessian matrix:
Because is independent of parameter state , bowl curvature is uniform throughout.
Any quadratic bowl decomposes into orthogonal principal directions (the eigenvectors of ), along which gradient descent behaves identically to decoupled 1D systems, governed by their respective curvatures (the eigenvalues of ). Let denote the maximal eigenvalue (steepest curvature), and denote the minimal eigenvalue (flattest curvature). Distance to the minimum contracts by factors and along these respective axes:
- The steepest axis determines stability. Convergence strictly requires . Exceeding this threshold triggers explosive divergence along the steep coordinate.
- The slowest contraction factor dictates convergence rate. When , the flat axis bottlenecks progress: when is small. Near the upper bound, , creating undamped high-frequency oscillations across the steep valley walls.
The ratio defines the condition number. High condition numbers indicate highly ill-conditioned, elongated elliptical ravines where no single scalar satisfies both axes effectively.
Feature scaling
Section titled “Feature scaling”Evaluating raw apartment areas in square meters:
This produces condition number . The steep axis aligns with while the flat axis aligns with : the loss surface forms an extremely long, narrow canyon. Steep walls restrict learning rates to . At , progress along the canyon floor contracts by per step: after 100 iterations, advances from 0 to merely 0.017 (against true target 2.04).
The solution is feature scaling: normalizing features to comparable unit ranges. We apply min-max scaling, mapping floor areas from m² into dimensionless interval :
Normalizing strictly the input feature preserves target scale and MSE comparability. The transformed model is , where parameters carry clear intuitive physical interpretations:
- represents predicted rent for the smallest apartment (25 m²), where .
- represents rent differential between the largest (85 m²) and smallest unit, as traverses 0 to 1.
Re-evaluating the Hessian on normalized feature matrix :
The stability bound expands to , and the condition number drops from 27 200 down to —a well-conditioned ellipse with aspect ratio .
Starting from with , the first step shifts parameters to , collapsing MSE from 129.75 down to 12.05. Gradient descent rapidly converges to . Unpacking parameters back to physical units:
This yields and , recovering the analytical solution.
Standardization via z-scores (StandardScaler, detailed in Ridge, Lasso, Elastic Net) standardizes columns to zero mean and unit variance. For single features, it produces circular level curves (). Both transformers must derive scaling parameters strictly from training data.
Stopping criteria
Section titled “Stopping criteria”Gradient descent requires programmatic stopping criteria:
- Maximum iteration count. An essential safeguard preventing runaway execution during numerical divergence.
- Gradient norm threshold: (e.g. ), confirming local terrain is sufficiently flat.
- Loss improvement threshold. Tracking relative change in loss across iterations. Scikit-learn implements this via parameter
tol, which can trigger premature convergence along flat ravines.
Batch, SGD, and mini-batch
Section titled “Batch, SGD, and mini-batch”On datasets with million instances, evaluating full-batch gradients at every step is computationally prohibitive. Stochastic approximations evaluate gradients over data subsets:
- Stochastic gradient descent (SGD): computes gradients over a single randomly sampled instance at each step.
- Mini-batch gradient descent: computes gradients across small cohorts of size : . This constitutes the universal standard for deep neural network optimization.
- Epoch: a complete pass over the entire training corpus, encompassing gradient steps. Training data should be shuffled before each epoch.
Within a single epoch, SGD executes updates, whereas batch GD performs only one. However, SGD introduces stochastic gradient variance, causing parameters to fluctuate near the optimum under constant learning rates. Decaying learning rates over time (learning rate schedules) dampens these oscillations. Modern optimizers such as Momentum and Adam are explored in Advanced Optimization Algorithms.
Limitations
Section titled “Limitations”Gradient descent requires careful learning rate tuning and feature standardization, guarantees global convergence only on convex surfaces, and is computationally less efficient than direct analytical solutions on small tabular datasets. It becomes indispensable when feature counts or dataset sizes scale beyond memory limits, or when models lack closed-form estimators (as in Logistic Regression and Artificial Neural Networks).
Illustration
Section titled “Illustration”Explore optimization dynamics visually. The contour map plots MSE level curves over , with the global minimum marked at × and initial parameters at . Use Step for single updates, Run to simulate up to 100 iterations, and Reset to return to origin. The slider selects among five learning rate configurations. The secondary plot traces loss trajectories on logarithmic scales, with the dashed reference indicating optimal loss 0.3615. Stability bound evaluates to . Try:
- At : click Step. Loss progresses . Initial steps descend steeply into the basin, after which trajectories pivot along the ravine floor, entering within 1% of optimal loss by step 69.
- At : click Run. Step lengths contract gradually; after 100 iterations, loss remains stuck at 1.0023.
- At : trajectories oscillate in a narrowing zigzag across the valley, entering the 1% error margin by step 29 (the fastest among all five presets).
- At and : near the boundary, updates bounce across the full ravine width, finishing 100 steps at 0.4240. Exceeding the threshold () triggers immediate divergence: loss expands , blowing off the viewport by step 4.
- MSE contour
- Gradient descent path
- Start (0, 0)
- Current position
- Bottom (normal equation)
- Smallest MSE (0.3615)
Press Step for one step, or Run for up to 100 steps.
The contours are long, tilted ellipses around the bottom (10.17, 6.28), where the MSE is smallest, 0.3615. The path starts at (0, 0), where the MSE is 129.75.
The dashed line is the smallest MSE, 0.3615. Each mark on the MSE axis is 10 times the one below it.
Code examples
Section titled “Code examples”The three cells below run independently; each constructs the apartment dataset locally.
The first cell implements batch gradient descent from scratch in NumPy: min-max normalizes features, starts from with , halts when or at 10 000 steps, and maps learned parameters back to physical units.
Batch gradient descent in NumPy
import numpy as np
# The 10-apartment table (made-up data for the lesson).# area: floor area (m²), rent: monthly rent (million VND).# dataset: apartmentsarea = np.array([25, 30, 36, 42, 48, 55, 60, 68, 75, 85])rent = np.array([6.7, 6.5, 8.8, 8.8, 9.4, 12.2, 12.0, 14.1, 13.9, 16.8])m = len(area)
# Min-max scaling: area onto [0, 1], rent left as it is.lo, span = area.min(), area.max() - area.min() # 25 and 60x = (area - lo) / spanX = np.column_stack([x, np.ones(m)]) # a column of ones for b
eta = 0.3 # learning ratetheta = np.zeros(2) # starting point: w~ = 0, b~ = 0for step in range(10_000): # stopping criterion 1: a maximum number of steps grad = 2 / m * X.T @ (X @ theta - rent) # gradient of the MSE if np.linalg.norm(grad) < 1e-6: # stopping criterion 2: tiny gradient break theta = theta - eta * grad # the update rulew_s, b_s = theta # w~, b~ on the scaled areaprint(f"stopped after {step} steps")print(f"scaled: w~ = {w_s:.4f}, b~ = {b_s:.4f}")
# Back to original units: w~ (x - 25) / 60 + b~ = w x + b.w = w_s / spanb = b_s - lo * wmse = np.mean((w * area + b - rent) ** 2)print(f"GD: w = {w:.4f}, b = {b:.4f}, MSE = {mse:.4f}")
# Compare with the normal equation of the previous lesson, on the raw area.A = np.column_stack([area, np.ones(m)])w_ne, b_ne = np.linalg.solve(A.T @ A, A.T @ rent)mse_ne = np.mean((w_ne * area + b_ne - rent) ** 2)print(f"normal: w = {w_ne:.4f}, b = {b_ne:.4f}, MSE = {mse_ne:.4f}")The first run downloads Python, about 10 MB.
The loop terminates after 282 iterations at and , mapping to , , and MSE , replicating the analytical normal equation. Loss enters within 1% of optimality by step 69; remaining iterations refine gradient norms below .
The second cell runs 100 iterations across multiple learning rates, comparing unstandardized features (left) against normalized inputs (right), plotting log-scale loss curves.
Learning rates with and without feature scaling
import matplotlib.pyplot as pltimport numpy as np
# dataset: apartmentsarea = np.array([25, 30, 36, 42, 48, 55, 60, 68, 75, 85])rent = np.array([6.7, 6.5, 8.8, 8.8, 9.4, 12.2, 12.0, 14.1, 13.9, 16.8])m = len(area)x = (area - area.min()) / (area.max() - area.min()) # min-max
# Run GD from (0, 0). Return the MSE at steps 0, 1, ..., steps.def mse_curve(feature, eta, steps): X = np.column_stack([feature, np.ones(m)]) theta = np.zeros(2) curve = [np.mean((X @ theta - rent) ** 2)] for _ in range(steps): theta = theta - eta * 2 / m * X.T @ (X @ theta - rent) curve.append(np.mean((X @ theta - rent) ** 2)) return curve
runs = [("raw", area, [3e-4, 3.3e-4]), # left: raw area (m²) ("scaled", x, [0.05, 0.3, 0.7, 0.8, 0.85])] # right: min-maxfig, axes = plt.subplots(1, 2, figsize=(9, 3.6), sharey=True)for ax, (name, feature, etas) in zip(axes, runs): for eta in etas: curve = mse_curve(feature, eta, 100) ax.plot(curve, label=f"eta = {eta:g}") v = curve[-1] # MSE after 100 steps; huge values print as 1.234e+05 text = f"{v:.4f}" if v < 1e4 else f"{v:.3e}" print(f"{name}, eta = {eta:g}: {text}") ax.axhline(0.3615, color="k", ls=":") # the smallest MSE ax.set(title=name, xlabel="Step", yscale="log", ylim=(0.1, 1e3)) ax.legend()axes[0].set_ylabel("MSE (log scale)")fig.tight_layout()long_run = mse_curve(area, 3e-4, 10_000) # raw area, 10,000 stepsprint(f"raw, eta = 0.0003, 10000 steps: {long_run[-1]:.4f}")plt.show()The first run downloads Python, about 10 MB.
- Normalized features: matches interactive visualization metrics. Notice : loss curves appear smooth despite parameter coordinate oscillations. At , loss explodes to .
- Raw features: stability bound is . Setting descends into the canyon and stalls at 0.8273, taking 35 650 iterations to enter the 1% target window. Setting exceeds the stability bound and diverges to .
The third cell compares batch sizes using custom optimization loops (, 100 epochs), and benchmarks against scikit-learn’s SGDRegressor.
SGD, mini-batch, and SGDRegressor
import numpy as npfrom sklearn.linear_model import SGDRegressorfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import MinMaxScaler
# dataset: apartmentsarea = np.array([25, 30, 36, 42, 48, 55, 60, 68, 75, 85])rent = np.array([6.7, 6.5, 8.8, 8.8, 9.4, 12.2, 12.0, 14.1, 13.9, 16.8])m, lo, span = len(area), area.min(), area.max() - area.min()X = np.column_stack([(area - lo) / span, np.ones(m)]) # min-max, ones
def minibatch_gd(batch_size, eta=0.1, epochs=100, seed=0): rng = np.random.default_rng(seed) theta = np.zeros(2) for _ in range(epochs): # each epoch: shuffle, cut into m // batch_size for idx in np.array_split(rng.permutation(m), m // batch_size): Xb, yb = X[idx], rent[idx] theta = theta - eta * 2 / len(idx) * Xb.T @ (Xb @ theta - yb) w = theta[0] / span # back to original units return np.mean((X @ theta - rent) ** 2), w, theta[1] - lo * w
for size in [10, 2, 1]: # 10 = m: batch GD; 1: SGD mse, w, b = minibatch_gd(size) print(f"batch {size:2d}: MSE = {mse:.4f}, w = {w:.4f}, b = {b:.2f}")
A = area.reshape(-1, 1) # scikit-learn needs a 2-D tablesgd = SGDRegressor(random_state=0).fit(A, rent) # defaults, raw areamse = np.mean((sgd.predict(A) - rent) ** 2)print(f"default, raw area: MSE = {mse:.1e}, n_iter_ = {sgd.n_iter_}")
sgd = SGDRegressor(penalty=None, learning_rate="constant", eta0=0.05, max_iter=1000, tol=None, random_state=0)scaler = MinMaxScaler()model = make_pipeline(scaler, sgd).fit(A, rent)w = sgd.coef_[0] * scaler.scale_[0] # back to original unitsb = sgd.intercept_[0] + sgd.coef_[0] * scaler.min_[0]mse = np.mean((model.predict(A) - rent) ** 2)print(f"min-max + constant: MSE = {mse:.4f}, w = {w:.4f}, b = {b:.2f}")The first run downloads Python, about 10 MB.
Target parameters are , , and MSE 0.3615.
- Batch 10 (Full batch GD) performs 100 updates, reaching MSE 0.4885 (incomplete convergence).
- Batch 2 (Mini-batch) executes 500 updates, reaching closest to target: MSE 0.3632.
- Batch 1 (Stochastic GD) executes 1 000 updates but finishes farther from the optimum (MSE 0.3766, ) due to stochastic oscillations around the minimum.
Default SGDRegressor() on raw features diverges to MSE within 6 epochs: default eta0=0.01 is far too large for unscaled square-meter coordinates. Our final configuration chains MinMaxScaler, disables default L2 penalties (penalty=None), sets constant learning rate 0.05, and sets tol=None to ensure 1000 full epochs. It recovers optimal MSE 0.3615.
Common pitfalls
Section titled “Common pitfalls”Quick check
Section titled “Quick check”Summary and further reading
Section titled “Summary and further reading”Key takeaways:
- Gradient descent iteratively updates , where for MSE, converging to the exact solution of the normal equation.
- Convergence strictly requires , where is the maximal eigenvalue of Hessian matrix . Learning rates below converge monotonically; rates between and oscillate; rates above diverge.
- Min-max scaling reduces condition number from 27 200 to 15, reducing steps required to enter the 1% target window from 35 650 down to 69 iterations. Learned parameters map back to original coordinates via and .
- Full-batch GD computes gradients across all samples, mini-batch GD processes batches of size , and stochastic GD updates on single samples. A plateauing loss curve does not guarantee convergence along ill-conditioned ravines.
Further reading:
- Vũ Hữu Tiệp (2017). Machine Learning cơ bản, Lesson 7: Gradient Descent (Part 1) and Lesson 8: Gradient Descent (Part 2). Clear Vietnamese coverage of SGD, mini-batch, and momentum extensions.
- Google (updated 2025–2026). Machine Learning Crash Course: Linear regression: Gradient descent and Linear regression: Hyperparameters. Intuitive visual overviews.
- scikit-learn developers (2007–2026). scikit-learn 1.8 User Guide, Section 1.5: Stochastic Gradient Descent. Official documentation detailing objective formulations and learning rate schedules in
SGDRegressor. - Ruder, S. (2016). An overview of gradient descent optimization algorithms. arXiv:1609.04747. Authoritative survey covering modern optimizers including Momentum, Adagrad, RMSProp, and Adam.
- Landmark original papers. Cauchy, A. (1847). Méthode générale pour la résolution des systèmes d’équations simultanées (PDF, English translation by R. J. Pulskamp). Comptes rendus de l’Académie des sciences 25, 536–538: original mathematical introduction of gradient descent. Robbins, H., Monro, S. (1951). A Stochastic Approximation Method. The Annals of Mathematical Statistics 22(3), 400–407: foundational precursor to stochastic gradient descent.
Your browser is blocking storage. Your progress will be lost when you reload the page.
Content licensed under CC BY-NC-SA 4.0. Code under the MIT license.About