Week 3 · Linear regression and gradient descent in sklearn
Read the words of the video
- Imagine you want to find the lowest point in a hilly park. You could check a map for the exact spot, or just start walking downhill, taking small steps. This week, you'll learn about two similar ways to find the best straight line through scattered data points, just like finding that lowest point.
- One way is like calculating the answer directly, very fast for small data, using 'LinearRegression'. The other is like walking downhill, taking many small steps to reduce error. This 'SGDRegressor' method is slower per step but great for huge datasets, even handling data in chunks.
- Let's say we have some points like (0,1), (1,2), (2,2). We start with a flat line, then adjust it a little. We measure how far off we are for each point. We take a tiny step, guided by the error, to move our line closer to fitting all the points.
- Remember, 'SGDRegressor' is key for huge data with 'partial_fit' and needs scaled features. 'LinearRegression' is perfect for smaller, exact problems. Always use 'learning_rate' as a string, not 'lr', and 'tol' helps it stop when improvement slows down. Keep learning!
The one-minute story
You already know two ways to find the best straight line through some dots:
- Do the algebra once and write the answer down (the normal equation). Fast and exact, but the algebra gets heavy when the table has lots of columns.
- Walk downhill (gradient descent): start anywhere, take small steps that reduce the error, stop when it stops improving. Slower per answer, but it copes with huge data and can be fed a few rows at a time.
In scikit-learn these are two classes: LinearRegression (the algebra) and SGDRegressor (the walking). This week you learn what each does, the dials on the walking one (learning rate, tolerance, iterations), and the clever partial_fit trick for data too big to load all at once.
By the end of this week you can:
- fit a line with
LinearRegressionand readcoef_andintercept_; - write batch gradient descent from scratch and see it match
LinearRegression; - explain batch, stochastic and mini-batch gradient descent;
- configure
SGDRegressor:loss,penalty,alpha,max_iter,tol,learning_rate,eta0,power_t,early_stopping; - use
partial_fitfor incremental learning and know which models have it.
Words you need
- Epoch - one full pass over the training data.
- Learning rate (
eta0) - the step size. - Learning schedule - a rule for shrinking over time.
- Tolerance
tol- "stop if the loss has not improved by at least this much". - Batch / stochastic / mini-batch - using all / one / a few examples per gradient step.
- Incremental (online) learning - updating a model as new data arrives, using
partial_fit. coef_,intercept_- the learned slope(s) and intercept (attributes end in_).
Idea 1: LinearRegression, the algebra route
from sklearn.linear_model import LinearRegression
import numpy as np
X = np.array([[0], [1], [2.]]); y = np.array([1, 2, 2.])
lr = LinearRegression().fit(X, y)
print(lr.coef_, lr.intercept_) # [0.5] 1.1666...
print(lr.predict([[3]])) # [2.6667]
It reproduces the line you derived by hand in MLF week 3. Under the hood it solves using a singular value decomposition (more stable than literally inverting ).
- No hyperparameters to tune (just
fit_intercept=Trueby default). - Cost grows very fast with the number of features (roughly cubic) but only linearly with the number of examples. With hundreds of thousands of features it becomes slow.
- No feature scaling needed for the answer (scaling just changes the units of the coefficients).
- Predictions are fast.
On a noisy line made from (100 points) we got intercept_ = 4.222 and coef_ = [2.968]: close to the truth but not exactly, because of the noise.
Idea 2: Gradient descent, written out
The cost is the mean squared error. For parameters (intercept included via a column of ones in ):
and the update is (MLF week 7). In NumPy:
Xb = np.c_[np.ones(len(X)), X] # add the column of ones
w = np.zeros(2); eta = 0.1
for _ in range(1000):
w -= eta * (2 / len(Xb)) * Xb.T @ (Xb @ w - y)
On the noisy-line data, after 1000 steps this gives [4.222, 2.968]: exactly what LinearRegression found. Same destination, different route.
| Variant | Examples per step | Speed per step | Path | Notes |
|---|---|---|---|---|
| Batch GD | all | slow | smooth | needs the whole data in memory |
| Stochastic GD (SGD) | 1 random | very fast | noisy, zig-zag | can escape shallow dips; never settles exactly unless shrinks |
| Mini-batch GD | a handful (e.g. 32) | fast | in between | the usual choice, good on GPUs |
Feature scaling matters for GD. If one feature is 1000 times bigger than another, the cost surface is a long narrow valley and gradient descent zig-zags and crawls. With StandardScaler the valley is round and it goes straight down. (Also: too big diverges, too small crawls.)
Idea 3: SGDRegressor
from sklearn.linear_model import SGDRegressor
sgd = SGDRegressor(max_iter=1000, tol=1e-5, penalty=None, eta0=0.1, random_state=42)
sgd.fit(X, y.ravel())
print(sgd.intercept_, sgd.coef_, sgd.n_iter_)
On our noisy line this gave intercept_ ≈ 4.31, coef_ ≈ 3.05 after n_iter_ = 17 epochs: near the exact answer, found by 17 passes of one-example-at-a-time updates.
The dials and their defaults in recent scikit-learn (your course version may differ slightly, so check the docs if a question gives a specific one):
| Parameter | Default | Meaning |
|---|---|---|
loss | 'squared_error' | regression loss (others: 'huber', 'epsilon_insensitive') |
penalty | 'l2' | regulariser: None, 'l2', 'l1', 'elasticnet' |
alpha | 0.0001 | penalty strength (and part of the 'optimal' schedule) |
max_iter | 1000 | maximum number of epochs |
tol | 0.001 | stopping tolerance |
learning_rate | 'invscaling' | the schedule (below) |
eta0 | 0.01 | starting learning rate |
power_t | 0.25 | exponent for 'invscaling' |
early_stopping | False | hold out a validation slice and stop when it stops improving |
n_iter_no_change | 5 | patience, in epochs |
shuffle | True | reshuffle each epoch |
random_state | None | set it for repeatable results |
The four learning-rate schedules (learning_rate=):
| Value | Rule |
|---|---|
'constant' | always |
'invscaling' | (shrinks with the step count ) |
'adaptive' | keeps while the training loss keeps improving; when it stalls for n_iter_no_change epochs, divides by 5 |
'optimal' | : a heuristic based on alpha (the default for SGDClassifier) |
How to write them in code (a favourite question):
SGDRegressor(learning_rate="invscaling", eta0=0.01, power_t=0.25)
SGDRegressor(learning_rate="adaptive", eta0=0.01)
SGDRegressor(learning_rate="constant", eta0=0.05)
SGDClassifier(learning_rate="optimal") # default for the classifier
(The argument is learning_rate, a string, plus eta0 for the number.)
When does it stop? SGD stops when the loss has failed to improve by more than tol for n_iter_no_change epochs in a row (this is the meaning of the tolerance), or when max_iter epochs have run, whichever is first. n_iter_ records how many epochs it actually used. A bigger tol stops earlier (less accurate); a smaller tol runs longer.
Scale first. SGD is sensitive to feature scales. Put a StandardScaler in front, in a pipeline.
Idea 4: Learning in instalments: partial_fit
If the data does not fit in memory (or arrives as a stream), feed it in chunks. partial_fit performs one pass over the batch you give it and keeps the learned weights, so you can call it again and again:
sg = SGDRegressor(random_state=0)
for X_chunk, y_chunk in data_in_batches(): # your own generator
sg.partial_fit(X_chunk, y_chunk)
(Do not use fit in a loop: each fit forgets what came before.)
Estimators that offer partial_fit ("incremental learners"):
- Yes:
SGDRegressor,SGDClassifier,Perceptron,PassiveAggressive*,MultinomialNB,BernoulliNB,GaussianNB,MiniBatchKMeans,MLPClassifier,MLPRegressor,IncrementalPCA. - No:
LinearRegression,LogisticRegression,Ridge,Lasso,SVC, decision trees, random forests,KNeighbors*.
(For SGDClassifier.partial_fit you must pass classes= on the first call.) Using the LinearRegression class is not possible for out-of-core learning; SGDRegressor is the standard answer.
Solved examples
Question. Data . Start from with and the MSE cost. Compute one batch gradient-descent step.
Solution. Predictions are all 0, so the residuals . With :
Update: and . The line has moved from towards the exact answer .
n_iter_Question. SGDRegressor(max_iter=1000, tol=1e-3).fit(X, y).n_iter_ returns 12. What does that tell you?
Solution. It stopped after 12 epochs, far below max_iter=1000: the loss stopped improving by more than tol for n_iter_no_change (default 5) epochs in a row. If it had returned 1000 it would mean it hit the iteration cap without converging (you would increase max_iter or scale the features).
Question. (a) A table of 5000 rows and 10 features. (b) A stream of 50 million rows that cannot fit in memory. (c) You want the exact least-squares answer with no tuning. Which class?
Solution. (a) and (c): LinearRegression (small data, exact, no hyperparameters). (b): SGDRegressor with partial_fit, one chunk at a time, after scaling.
Your turn
Which creates an SGDRegressor using the adaptive learning rate starting at 0.05? (a) SGDRegressor(lr="adaptive", eta0=0.05) (b) SGDRegressor(learning_rate="adaptive", eta0=0.05) (c) SGDRegressor(learning_rate=adaptive, eta0=0.05)
Need a hint?
The parameter name is learning_rate and the value is a string.
Show the full solution
(b). (a) has the wrong keyword; (c) forgot the quotes, so adaptive is an undefined name.
State the default loss of SGDClassifier, the default learning_rate of SGDRegressor, and the default penalty.
Need a hint?
Use the table.
Show the full solution
SGDClassifier default loss: 'hinge' (a linear SVM). SGDRegressor default learning_rate: 'invscaling'. Default penalty: 'l2'.
With learning_rate="invscaling", eta0=0.1, power_t=0.5, what is at step ?
Need a hint?
.
Show the full solution
.
Pick all that support partial_fit: LinearRegression, SGDClassifier, GaussianNB, RandomForestClassifier, MiniBatchKMeans.
Need a hint?
Incremental learners.
Show the full solution
SGDClassifier, GaussianNB, MiniBatchKMeans.
Explain in two sentences when you would prefer SGDRegressor over LinearRegression.
Need a hint?
Think about size and memory.
Show the full solution
When there are very many examples (or they arrive in a stream), SGD can update from small chunks with partial_fit and never needs the whole data or a heavy matrix computation. LinearRegression is simpler and exact, so it is better for modest-sized tables.
Exam radar
In MLP Quiz 1 summaries, SGDRegressor appears in 11 of 26 papers, the tolerance parameter tol in 7, Mean Squared Error in 13, R² score in 7, LinearRegression in 6, regression metrics in 8. In Quiz 2, "Which model supports incremental learning through partial_fit?" appears, and the End Term asks how to set an adaptive learning rate with SGDRegressor.
Real stems: "Which code block correctly takes the learning rate as 'invscaling' / 'optimal'?", "What is the default loss in SGDClassifier?", "Which code will train a large-scale regression model using partial_fit?", "what does tol do?".
- The keyword is
learning_rate(string) witheta0; notlr, notalpha. partial_fitin a loop, notfit.- The classifier's default schedule is
'optimal'; the regressor's is'invscaling'. - SGD needs scaled features;
LinearRegressiondoes not. tolis about loss improvement, not about the size of the weights.
Quick quiz
Which learning-rate schedule divides the rate by 5 when training stalls?
- constant
- invscaling
- adaptive
- optimal
Which estimator supports incremental learning through partial_fit?
- LinearRegression
- RandomForestRegressor
- SGDRegressor
- KNeighborsRegressor
The default loss of SGDClassifier is...
- log_loss
- hinge
- squared_error
- perceptron
Select all TRUE statements. (select all that apply)
- Batch gradient descent uses all the training data for every step.
- SGD benefits from scaled features.
- Calling fit repeatedly on chunks of data is the same as partial_fit.
- LinearRegression has a learning rate to tune.
Recap
LinearRegressionsolves least squares directly: exact, no hyperparameters, slow for huge feature counts.coef_,intercept_.- Gradient descent: ; batch / stochastic / mini-batch. Scale features.
SGDRegressordials:loss,penalty,alpha,max_iter(1000),tol(1e-3),learning_rate(constant/optimal/invscaling/adaptive),eta0,power_t,early_stopping.partial_fitfor incremental learning (SGD, Naive Bayes, MiniBatchKMeans, MLP); not forLinearRegression.
- MSE cost ; gradient
invscaling: ;adaptive: divide by 5 when stalled;constant:SGDRegressordefaults:squared_error,l2,alpha=1e-4,max_iter=1000,tol=1e-3,invscaling,eta0=0.01,power_t=0.25SGDClassifierdefaults:hinge,learning_rate="optimal"
- MLF week 3 - Vectors, matrices and the best-fit line: The same least squares, now typed out in scikit-learn.
- MLF week 7 - Finding the lowest point: gradient descent: Batch and stochastic gradient descent in scikit-learn.
- MLT week 5 - Regression: least squares and its Bayesian view: Linear regression: the theory, then the code.
Practise with real exam questions
These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week All Quiz 1 papers