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)=1n∑i=1n(yi−y^i)2=1n∥y−Xw∥2

Goal ​

Find weights w that minimize MSE.


Solution Methods ​

Method 1: Normal Equation (Closed-Form) ​

w=(XTX)−1XTy

Derivation:

  1. Take derivative: ∂L∂w=−2nXT(y−Xw)
  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: w←w−α∇wL

Gradient: ∇wL=2nXT(Xw−y)

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)|y−Xw|2+λ|w|22Shrinks weights toward zerow=(XTX+λI)−1XTy
Lasso (L1)|y−Xw|2+λ|w|1Drives weights to exactly zeroNo (use coordinate descent)
Elastic Net|y−Xw|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=1−SSresSStot=1−∑(yi−y^i)2∑(yi−y¯)2
ValueInterpretation
R2=1Perfect fit
R2=0No better than mean
R2<0Worse than mean

Adjusted R2 penalizes adding features:

Radj2=1−(1−R2)n−1n−d−1

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 (x1⋅x2), 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