Skip to content

Linear Regression

Foundation of supervised learning - regression, OLS, gradient descent


Overview

Linear regression models the relationship between features and a continuous target as a linear combination, finding weights that minimize prediction error.

Linear Regression Fit


Core Formulation

Model

y^=w0+w1x1+w2x2++wnxn=wTx+b

Matrix form: y^=Xw

Loss Function (Mean Squared Error)

L(w)=1ni=1n(yiy^i)2=1nyXw2

Goal

Find weights w that minimize MSE.


Solution Methods

Method 1: Normal Equation (Closed-Form)

w=(XTX)1XTy

Derivation:

  1. Take derivative: Lw=2nXT(yXw)
  2. Set to zero and solve: XTy=XTXw
  3. Result: w=(XTX)1XTy
AspectDetails
When to used<10,000 features, XTX invertible
Time complexityO(nd2+d3)
Space complexityO(d2)

Method 2: Gradient Descent

Gradient Descent Convergence

Update rule: wwαwL

Gradient: wL=2nXT(Xwy)

python
def gradient_descent_linear(X, y, lr=0.01, n_iters=1000):
    n, d = X.shape
    w = np.zeros(d)
    for _ in range(n_iters):
        predictions = X @ w
        gradient = (2/n) * X.T @ (predictions - y)
        w = w - lr * gradient
    return w
AspectDetails
When to useLarge d, online learning, with regularization
Time complexityO(nd) per iteration

Regularization

Regularization Effect

MethodLoss FunctionEffectClosed-Form
Ridge (L2)|yXw|2+λ|w|22Shrinks weights toward zerow=(XTX+λI)1XTy
Lasso (L1)|yXw|2+λ|w|1Drives weights to exactly zeroNo (use coordinate descent)
Elastic Net|yXw|2+λ1|w|1+λ2|w|22Combines sparsity + stabilityNo

Assumptions

AssumptionMeaningCheckIf Violated
Linearityy is linear in xResidual vs. fitted plotAdd polynomial features
IndependenceErrors are independentDurbin-Watson testTime-series models
HomoscedasticityConstant error varianceResiduals vs. fittedWeighted least squares
NormalityϵN(0,σ2)Q-Q plotLess critical for large n
No multicollinearityFeatures not highly correlatedVIF > 10 is concerningRemove features or regularize

Interpretation

Coefficient Meaning

  • Standardized features: "A 1 std increase in xj wj std change in y"
  • Raw features: "A 1 unit increase in xj wj unit change in y"

R2 (Coefficient of Determination)

R2=1SSresSStot=1(yiy^i)2(yiy¯)2
ValueInterpretation
R2=1Perfect fit
R2=0No better than mean
R2<0Worse than mean

Adjusted R2 penalizes adding features:

Radj2=1(1R2)n1nd1

Interview Questions

Q1: When use linear regression over complex models?

  • Interpretability needed - stakeholders must understand contributions
  • Approximately linear relationship - or can engineer features
  • Limited data - complex models overfit
  • Baseline model - always start simple

Q2: L1 vs L2 regularization?

L1 (Lasso)L2 (Ridge)
Drives weights to exactly zeroShrinks but keeps all features
Feature selectionHandles correlated features better
Sparse modelsMore stable coefficients

If unsure: Use Elastic Net (combines both) with cross-validation.

Q3: What does multicollinearity do?

  • Effect: Coefficients unstable, uninterpretable (high variance)
  • Detection: Correlation matrix, VIF > 10
  • Fix: Remove correlated features, combine them, or use regularization

Q4: Non-linear relationships?

  1. Feature engineering - polynomial (x2, x3), interactions (x1x2), log transforms
  2. Splines - piecewise polynomial fitting
  3. Non-linear models - trees, neural networks

Code Reference

python
import numpy as np

class LinearRegression:
    def __init__(self, method='normal', lr=0.01, n_iters=1000,
                 regularization=None, lambda_=0.1):
        self.method = method
        self.lr = lr
        self.n_iters = n_iters
        self.regularization = regularization
        self.lambda_ = lambda_
        self.weights = None

    def fit(self, X, y):
        X = np.c_[np.ones(len(X)), X]  # Add bias

        if self.method == 'normal':
            if self.regularization == 'l2':
                I = np.eye(X.shape[1])
                I[0, 0] = 0  # Don't regularize bias
                self.weights = np.linalg.inv(X.T @ X + self.lambda_ * I) @ X.T @ y
            else:
                self.weights = np.linalg.inv(X.T @ X) @ X.T @ y
        else:  # Gradient descent
            self.weights = np.zeros(X.shape[1])
            for _ in range(self.n_iters):
                grad = (2/len(X)) * X.T @ (X @ self.weights - y)
                if self.regularization == 'l2':
                    grad[1:] += 2 * self.lambda_ * self.weights[1:]
                self.weights -= self.lr * grad

    def predict(self, X):
        X = np.c_[np.ones(len(X)), X]
        return X @ self.weights

    def score(self, X, y):
        """R^2 score"""
        y_pred = self.predict(X)
        ss_res = np.sum((y - y_pred) ** 2)
        ss_tot = np.sum((y - np.mean(y)) ** 2)
        return 1 - ss_res / ss_tot

Quick Reference Card

LINEAR REGRESSION
-------------------------------------------------
Model:    y_hat = Xw
Loss:     MSE = (1/n)||y - Xw||^2
Solution: w = (X'X)^{-1}X'y  OR  gradient descent

REGULARIZATION
-------------------------------------------------
L2 (Ridge): + lambda||w||^2  -> shrinks weights
L1 (Lasso): + lambda|w|      -> sparse weights

ASSUMPTIONS
-------------------------------------------------
1. Linearity
2. Independence of errors
3. Homoscedasticity (constant variance)
4. Normality of errors
5. No multicollinearity

COMPLEXITY
-------------------------------------------------
Normal equation: O(nd^2 + d^3) time, O(d^2) space
Gradient descent: O(nd) per iteration

USE WHEN
-------------------------------------------------
- Interpretability needed
- Linear relationship (or engineerable)
- Baseline model
- Small-medium dataset