Lesson 03
Gradient Descent
See how gradient descent minimises a loss function by taking small steps in the direction of steepest decrease, and how scikit-learn uses it inside SGDRegressor.
Most ML models have parameters (weights, coefficients) that need to be tuned to fit the data. Gradient descent is the engine that does the tuning: it repeatedly measures the slope of the loss function and takes a small step downhill.
Loss function
The loss function measures how wrong the model's predictions are. For regression the most common choice is mean squared error (MSE):
[ \text{MSE} = \frac{1}{n}\sum_{i=1}^{n}(\hat{y}_i - y_i)^2 ]
The goal is to find the weights that minimise this value.
Manual gradient descent
For a line y = w*x + b, the gradient (slope) of MSE with respect to w and b tells you which direction makes the loss grow. Moving in the opposite direction by a small learning rate moves you toward lower loss.
import numpy as np
from numpy.random import default_rng
rng = default_rng(42)
x = rng.uniform(-5, 5, 80)
y_true = 2.5 * x + 1.0 + rng.normal(0, 2, 80)
w, b, lr = 0.0, 0.0, 0.01
for step in range(3000):
pred = w * x + b
err = pred - y_true
dw = (err * x).mean()
db = err.mean()
w -= lr * dw
b -= lr * db
if (step + 1) % 1000 == 0:
loss = (err ** 2).mean()
print(f"step {step+1:4d} w={w:.3f} b={b:.3f} loss={loss:.3f}")Output:
step 1000 w=2.593 b=0.747 loss=3.417
step 2000 w=2.593 b=0.747 loss=3.417
step 3000 w=2.593 b=0.747 loss=3.417After 3 000 steps the slope is close to 2.5 and the intercept close to 1.0. The true values were 2.5 and 1.0, so gradient descent found them.
SGDRegressor in scikit-learn
Implementing gradient descent by hand is a learning exercise. In practice, SGDRegressor does it efficiently. The max_iter parameter controls how many passes through the data it makes.
import numpy as np
import pandas as pd
from numpy.random import default_rng
rng = default_rng(42)
n = 300
experience = rng.integers(1, 21, n)
certifications = rng.integers(0, 7, n)
overtime = rng.integers(0, 45, n)
education = rng.choice([10, 12, 14, 16, 18], n, p=[0.05, 0.25, 0.35, 0.25, 0.10])
noise = rng.normal(0, 4500, n)
salary = (20000 + experience * 3200 + (education - 10) * 1500 +
certifications * 1200 + overtime * 80 + noise).round(-2)
salary = np.clip(salary, 18000, 160000).astype(int)
senior = (salary >= 65650).astype(int)
top = ((experience >= 15) & (certifications >= 5)).astype(int)
employees = pd.DataFrame(dict(
experience=experience, certifications=certifications,
overtime=overtime, education=education, salary=salary,
senior=senior, top=top,
))
from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
X = employees[["experience", "certifications", "overtime", "education"]]
y = employees["salary"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
pipe = make_pipeline(StandardScaler(), SGDRegressor(max_iter=2000, random_state=0))
pipe.fit(X_train, y_train)
print(f"train R2: {pipe.score(X_train, y_train):.3f}")
print(f"test R2: {pipe.score(X_test, y_test):.3f}")Output:
train R2: 0.944
test R2: 0.931Learning rate matters
A learning rate that is too large causes the steps to overshoot the minimum. A rate that is too small makes convergence very slow. Scikit-learn defaults work for most problems, but SGDRegressor(eta0=...) lets you override.
Key takeaways
- The loss function measures prediction error; gradient descent minimises it
- Each step moves in the direction that reduces loss by the learning rate
- SGDRegressor applies stochastic gradient descent to regression
- Always scale features before gradient-based models
- A learning rate too large overshoots; too small is slow
Loading playground…
Check your understanding
Gradient Descent Quiz
What does the loss function measure?
1 / 10