ML Study Hub

Home / MLP / Week 3

Week 3 · Linear regression and gradient descent in sklearn

Official syllabus line: Regression on scikit-learn - Linear regression, Gradient descent - batch and stochastic
About 90 minQuiz 1 material (see exam radar)BSCS2008 · Ashish Tendulkar
Before you start: MLP weeks 1-2. MLF week 3 (least squares) and week 7 (gradient descent) explain the maths behind the code.
Watch first: the animated story of this weekAbout 1-2 minutes of real 3D animation with a friendly voice. Pause any time.
Read the words of the video
  1. 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.
  2. 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.
  3. 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.
  4. 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

Think of it like this

You already know two ways to find the best straight line through some dots:

  1. 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.
  2. 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 LinearRegression and read coef_ and intercept_;
  • 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_fit for incremental learning and know which models have it.

Words you need

Words you need
  • Epoch - one full pass over the training data.
  • Learning rate η\eta (eta0) - the step size.
  • Learning schedule - a rule for shrinking η\eta 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 y^=76+12x\hat y=\frac76+\frac12x you derived by hand in MLF week 3. Under the hood it solves min⁡∥Xw−y∥2\min\|X\mathbf w-\mathbf y\|^2 using a singular value decomposition (more stable than literally inverting XTXX^TX).

  • No hyperparameters to tune (just fit_intercept=True by 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 y=4+3x+noisey=4+3x+\text{noise} (100 points) we got intercept_ = 4.222 and coef_ = [2.968]: close to the truth (4,3)(4,3) but not exactly, because of the noise.

Idea 2: Gradient descent, written out

The cost is the mean squared error. For parameters w\mathbf w (intercept included via a column of ones in XX):

J(w)=1n∥Xw−y∥2,∇J=2nXT(Xw−y)J(\mathbf w)=\frac1n\|X\mathbf w-\mathbf y\|^2,\qquad\nabla J=\frac2nX^T(X\mathbf w-\mathbf y)

and the update is w←w−η∇J\mathbf w\leftarrow\mathbf w-\eta\nabla J (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.

VariantExamples per stepSpeed per stepPathNotes
Batch GDall nnslowsmoothneeds the whole data in memory
Stochastic GD (SGD)1 randomvery fastnoisy, zig-zagcan escape shallow dips; never settles exactly unless η\eta shrinks
Mini-batch GDa handful (e.g. 32)fastin betweenthe 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: η\eta 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):

ParameterDefaultMeaning
loss'squared_error'regression loss (others: 'huber', 'epsilon_insensitive')
penalty'l2'regulariser: None, 'l2', 'l1', 'elasticnet'
alpha0.0001penalty strength (and part of the 'optimal' schedule)
max_iter1000maximum number of epochs
tol0.001stopping tolerance
learning_rate'invscaling'the schedule (below)
eta00.01starting learning rate
power_t0.25exponent for 'invscaling'
early_stoppingFalsehold out a validation slice and stop when it stops improving
n_iter_no_change5patience, in epochs
shuffleTruereshuffle each epoch
random_stateNoneset it for repeatable results

The four learning-rate schedules (learning_rate=):

ValueRule
'constant'η=eta0\eta=\texttt{eta0} always
'invscaling'η=eta0tpower_t\eta=\dfrac{\texttt{eta0}}{t^{\texttt{power\_t}}} (shrinks with the step count tt)
'adaptive'keeps η=eta0\eta=\texttt{eta0} while the training loss keeps improving; when it stalls for n_iter_no_change epochs, divides η\eta by 5
'optimal'η=1α (t+t0)\eta=\dfrac1{\alpha\,(t+t_0)}: 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

Solved example: 1. One gradient-descent step by hand

Question. Data (x,y)=(0,1),(1,2),(2,2)(x,y)=(0,1),(1,2),(2,2). Start from w=(w0,w1)=(0,0)\mathbf w=(w_0,w_1)=(0,0) with η=0.1\eta=0.1 and the MSE cost. Compute one batch gradient-descent step.

Solution. Predictions are all 0, so the residuals Xw−y=(−1,−2,−2)X\mathbf w-\mathbf y=(-1,-2,-2). With n=3n=3:

  • ∂J/∂w0=23(−1−2−2)=−3.333\partial J/\partial w_0=\frac23(-1-2-2)=-3.333
  • ∂J/∂w1=23(0⋅(−1)+1⋅(−2)+2⋅(−2))=23(−6)=−4\partial J/\partial w_1=\frac23\big(0\cdot(-1)+1\cdot(-2)+2\cdot(-2)\big)=\frac23(-6)=-4

Update: w0=0−0.1(−3.333)=0.333w_0=0-0.1(-3.333)=\mathbf{0.333} and w1=0−0.1(−4)=0.4w_1=0-0.1(-4)=\mathbf{0.4}. The line has moved from y=0y=0 towards the exact answer y=1.167+0.5xy=1.167+0.5x.

Solved example: 2. Reading 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).

Solved example: 3. Which learner for which job?

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

Your turn: 1. Spot the correct code

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.

Your turn: 2. Default facts

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'.

Your turn: 3. Learning rate schedule

With learning_rate="invscaling", eta0=0.1, power_t=0.5, what is η\eta at step t=100t=100?

Need a hint?

η=eta0/tpower_t\eta=\texttt{eta0}/t^{\texttt{power\_t}}.

Show the full solution

η=0.1/1000.5=0.1/10=0.01\eta=0.1/100^{0.5}=0.1/10=\mathbf{0.01}.

Your turn: 4. Which has partial_fit?

Pick all that support partial_fit: LinearRegression, SGDClassifier, GaussianNB, RandomForestClassifier, MiniBatchKMeans.

Need a hint?

Incremental learners.

Show the full solution

SGDClassifier, GaussianNB, MiniBatchKMeans.

Your turn: 5. Compare the routes

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

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?".

Watch out - real exam trap
  • The keyword is learning_rate (string) with eta0; not lr, not alpha.
  • partial_fit in a loop, not fit.
  • The classifier's default schedule is 'optimal'; the regressor's is 'invscaling'.
  • SGD needs scaled features; LinearRegression does not.
  • tol is about loss improvement, not about the size of the weights.

Quick quiz

Quick quiz

Which learning-rate schedule divides the rate by 5 when training stalls?

  • constant
  • invscaling
  • adaptive
  • optimal
'adaptive' keeps eta0 while the loss improves and divides by 5 when it stops improving for n_iter_no_change epochs.

Which estimator supports incremental learning through partial_fit?

  • LinearRegression
  • RandomForestRegressor
  • SGDRegressor
  • KNeighborsRegressor
SGDRegressor (and SGDClassifier, Naive Bayes, MiniBatchKMeans, MLP) offer partial_fit.

The default loss of SGDClassifier is...

  • log_loss
  • hinge
  • squared_error
  • perceptron
The default is the hinge loss, which gives a linear SVM.

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.
fit forgets earlier chunks, and LinearRegression solves least squares directly, with no learning rate.

Recap

Remember this
  • LinearRegression solves least squares directly: exact, no hyperparameters, slow for huge feature counts. coef_, intercept_.
  • Gradient descent: w←w−η2nXT(Xw−y)\mathbf w\leftarrow\mathbf w-\eta\frac2nX^T(X\mathbf w-\mathbf y); batch / stochastic / mini-batch. Scale features.
  • SGDRegressor dials: loss, penalty, alpha, max_iter (1000), tol (1e-3), learning_rate (constant/optimal/invscaling/adaptive), eta0, power_t, early_stopping.
  • partial_fit for incremental learning (SGD, Naive Bayes, MiniBatchKMeans, MLP); not for LinearRegression.
Formulas to keep
  • MSE cost J=1n∥Xw−y∥2J=\frac1n\|X\mathbf w-\mathbf y\|^2; gradient 2nXT(Xw−y)\frac2nX^T(X\mathbf w-\mathbf y)
  • invscaling: η=eta0/tpower_t\eta=\texttt{eta0}/t^{\texttt{power\_t}}; adaptive: divide by 5 when stalled; constant: η=eta0\eta=\texttt{eta0}
  • SGDRegressor defaults: squared_error, l2, alpha=1e-4, max_iter=1000, tol=1e-3, invscaling, eta0=0.01, power_t=0.25
  • SGDClassifier defaults: hinge, learning_rate="optimal"
Connected weeks (learn them together)

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