Jaber Notes · 3 of 16
Linear Models
OLS, Ridge, Lasso, Elastic Net, logistic & softmax regression, GLMs.
The workhorses, derived properly: normal equations and Gauss-Markov, Ridge via SVD, Lasso soft-thresholding, the logistic cross-entropy gradient, and the convexity proof that makes it all trainable.
Visual reference
Sigmoid function
L1 vs L2 constraint regions
Full derivations from loss function through closed-form solution, gradient, and probabilistic interpretation.
Table of contents
- Linear regression (OLS)
- Ridge regression
- Lasso regression
- Elastic Net
- Logistic regression
- Multiclass: softmax regression
- Generalized linear models (overview)
- Code examples
1. Linear regression (OLS)
1.1 Model
Given features (we prepend 1 for intercept so ) and scalar target :
Stacked over samples: , where .
1.2 Objective: residual sum of squares
Expanded:
1.3 Normal equations (closed-form solution)
Gradient w.r.t. (using matrix calculus identities):
Setting to zero:
These are the normal equations. If is invertible (columns of linearly independent):
The Hessian (PSD) confirms this is a global minimum. Strictly so if has full column rank.
1.4 Geometric interpretation
.
is the hat/projection matrix. It is idempotent () and orthogonal projection onto column space of .
Residual vector: , which is orthogonal to column space of : .
1.5 MLE derivation
Assume , i.i.d.
Log-likelihood:
Maximizing over : same as minimizing = OLS. So OLS = MLE under Gaussian noise assumption.
1.6 Gauss-Markov theorem
OLS estimator is the Best Linear Unbiased Estimator (BLUE): among all linear estimators that are unbiased, OLS has minimum variance.
Conditions: , (homoscedastic, uncorrelated errors).
1.7 When OLS fails
| Problem | Cause | Fix |
|---|---|---|
| singular | Multicollinear features | Ridge, Lasso, drop features |
| Non-linear relationship | Model misspecification | Feature engineering, kernel methods |
| Heteroscedastic errors | Variance changes with | Weighted LS, transform |
| Outliers | Heavy-tailed noise | Robust regression (Huber loss) |
1.8 Evaluating regression models
Mean Squared Error (MSE): . Same units as . Root MSE (RMSE): . Same units as . Mean Absolute Error (MAE): . More robust to outliers. R² (coefficient of determination):
Fraction of variance explained. is perfect fit. Adding irrelevant features can only increase (or keep equal) — use adjusted to account for model complexity.
2. Ridge regression
2.1 Objective and motivation
Adding L2 penalty to OLS:
where excludes the intercept (common convention; center features to avoid penalizing intercept).
2.2 Closed-form solution
Gradient:
where (no penalty on intercept).
Setting to zero:
adds to the diagonal → always invertible for . Solves multicollinearity.
2.3 SVD analysis (why Ridge shrinks)
Let (thin SVD). Then:
Compare OLS: factor vs. 1. Ridge shrinks each component by , with more shrinkage for small (low-variance directions = unstable OLS directions).
2.4 Bayesian view
Ridge is MAP with Gaussian prior :
2.5 Choosing
Cross-validation is the standard approach. sklearn.linear_model.RidgeCV does LOO-CV efficiently via the hat matrix.
3. Lasso regression
3.1 Objective
No closed form (L1 is non-differentiable at zero). Solved by coordinate descent or proximal gradient.
3.2 Why Lasso induces sparsity (geometric argument)
Consider contours of RSS (ellipses in 2D) intersecting the constraint region:
- L2 constraint: — a smooth ball. Contours typically touch the ball interior → non-sparse.
- L1 constraint: — a diamond with sharp corners at axes. Contours often touch a corner → one coordinate exactly zero = sparsity.
3.3 Coordinate descent for Lasso
Update one coordinate at a time, holding others fixed. Let be the partial residual. Then:
where soft-thresholding (ST):
Intuition: if the correlation of feature with partial residual is small (below threshold), set .
3.4 Lasso path (LARS algorithm)
As decreases from to 0:
- Start: .
- First variable enters the model when its correlation with current residual exceeds threshold.
- LARS (Least Angle Regression) computes the entire regularization path at cost of one OLS fit.
3.5 Bayesian view
Lasso is MAP with Laplace (double-exponential) prior:
which has heavier tails than Gaussian → accommodates more extreme values in some components and zeros in others.
4. Elastic Net
4.1 Objective
Or parameterized with and overall penalty :
= Lasso, = Ridge. sklearn uses this parameterization.
4.2 Advantages over Lasso
- Handles groups of correlated features: Lasso picks one arbitrarily; ElasticNet can retain all.
- The L2 component ensures uniqueness of solution.
- Sparsity still encouraged by L1.
5. Logistic regression
5.1 From regression to classification
Linear regression for binary labels is poor: predictions can go outside , squared loss is not ideal for probabilities.
Logistic regression models the log-odds (logit) as a linear function:
Solving for the probability:
5.2 Sigmoid function properties
- , , .
- Derivative: . (Proof: apply quotient rule.)
- Symmetric: .
5.3 Negative log-likelihood (cross-entropy loss)
For binary labels, the Bernoulli likelihood of one sample:
Log-likelihood over all samples:
Cross-entropy loss = negative log-likelihood:
5.4 Gradient derivation
Gradient of loss for one sample :
Step 1 (chain rule through sigmoid):
Proof:
(Uses .)
Step 2: .
Combined (averaged):
Elegant: same form as linear regression gradient but with instead of .
5.5 Hessian (confirms convexity)
Since , , and (PSD). So logistic regression loss is convex (strictly, if full column rank).
5.6 Optimization
No closed-form. Use:
- Gradient descent / mini-batch SGD (scale to huge datasets).
- Newton-Raphson / IRLS: . Equivalent to iteratively reweighted least squares. Converges quadratically near optimum.
- L-BFGS: quasi-Newton, efficient for medium-sized data.
5.7 Decision boundary
Predict class 1 if , i.e., if .
Decision boundary: the hyperplane . Logistic regression is a linear classifier.
The threshold 0.5 is not always optimal: in imbalanced problems or asymmetric costs, choose threshold via ROC analysis.
5.8 Regularized logistic regression
Add L2: . sklearn uses (larger = less regularization).
6. Multiclass: softmax regression
6.1 Model
For classes, assign one weight vector per class. Let .
Softmax function turns logits into probabilities:
Numerical stability: subtract before exponentiating (doesn't change softmax, avoids overflow).
6.2 Categorical cross-entropy loss
With one-hot encoding for class , this simplifies to .
6.3 Gradient
Same "prediction minus target" structure as binary logistic.
6.4 Binary vs multiclass approaches
| Strategy | Description |
|---|---|
| One-vs-Rest (OvR) | Train binary classifiers; predict class with highest score |
| One-vs-One (OvO) | Train classifiers; majority vote |
| Softmax (Multinomial LR) | Train jointly; theoretically preferred |
7. Generalized linear models (overview)
GLMs unify many models: linear regression, logistic regression, Poisson regression, etc.
A GLM has three components:
- Random component: exponential family with mean .
- Systematic component: (linear predictor).
- Link function : .
| Model | Distribution | Link | Mean |
|---|---|---|---|
| Linear regression | Gaussian | Identity | |
| Logistic regression | Bernoulli | Logit | |
| Poisson regression | Poisson | Log | |
| Softmax regression | Multinoulli | Log-ratio | Softmax |
8. Code examples
See the classical_examples/ directory (created with ML_NOTES.md) for:
| File | What it shows |
|---|---|
linear_regression.py | OLS via normal equations vs LinearRegression |
regularization_ridge.py | Ridge closed form vs Ridge |
logistic_regression.py | Batch GD on binary cross-entropy vs LogisticRegression |
Sklearn cheatsheet for linear models
from sklearn.linear_model import (
LinearRegression,
Ridge, RidgeCV,
Lasso, LassoCV,
ElasticNet, ElasticNetCV,
LogisticRegression,
)
# Ridge with CV-selected alpha
model = RidgeCV(alphas=[0.1, 1.0, 10.0], cv=5)
model.fit(X_train, y_train)
print(model.alpha_) # best alpha
# Lasso (coordinate descent)
lasso = LassoCV(cv=5, max_iter=10_000)
lasso.fit(X_train, y_train)
print(lasso.alpha_)
print(np.sum(lasso.coef_ != 0)) # number of nonzero (selected) features
# Logistic regression (C = 1/lambda, larger = less regularization)
clf = LogisticRegression(C=1.0, penalty='l2', solver='lbfgs', max_iter=1000)
clf.fit(X_train, y_train)
clf.predict_proba(X_test) # calibrated probabilities*File: notes/03_linear_models.md — next: notes/04_optimization.md*
From-scratch code
Runnable implementations, each checked against its scikit-learn equivalent.
"""
Ordinary Least Squares (OLS) linear regression.
From scratch: normal equations w = (X^T X)^{-1} X^T y
Library: sklearn.linear_model.LinearRegression
"""
from __future__ import annotations
import numpy as np
from sklearn.datasets import make_regression
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
def add_intercept(X: np.ndarray) -> np.ndarray:
"""Prepend column of ones for bias term."""
return np.column_stack([np.ones(len(X)), X])
def ols_normal_equations(X: np.ndarray, y: np.ndarray) -> np.ndarray:
"""
Solve min_w ||y - X w||_2^2 via X^T X w = X^T y.
X must include intercept column if desired (use add_intercept).
"""
XtX = X.T @ X
Xty = X.T @ y
return np.linalg.solve(XtX, Xty)
def main() -> None:
rng = np.random.default_rng(0)
X_raw, y, coef = make_regression(
n_samples=200,
n_features=3,
noise=15.0,
coef=True,
random_state=0,
)
X = add_intercept(X_raw)
w_scratch = ols_normal_equations(X, y)
model = LinearRegression(fit_intercept=False)
# sklearn expects raw X; we pass X with manual intercept to compare coefficients directly
model.fit(X, y)
w_sklearn = model.coef_
y_pred_s = X @ w_scratch
y_pred_k = model.predict(X)
print("OLS coefficients (scratch):", w_scratch)
print("OLS coefficients (sklearn): ", w_sklearn)
print("Max abs diff:", np.max(np.abs(w_scratch - w_sklearn)))
print("MSE scratch:", mean_squared_error(y, y_pred_s))
print("R2 scratch :", r2_score(y, y_pred_s))
if __name__ == "__main__":
main()
"""
Binary logistic regression via gradient descent (from scratch) vs sklearn.
Model: P(y=1|x) = sigma(w^T x), sigma(z) = 1/(1+exp(-z))
Loss: average cross-entropy (negative log-likelihood).
"""
from __future__ import annotations
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
def sigmoid(z: np.ndarray) -> np.ndarray:
return 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500)))
def add_intercept(X: np.ndarray) -> np.ndarray:
return np.column_stack([np.ones(len(X)), X])
def logistic_gradient(X: np.ndarray, y: np.ndarray, w: np.ndarray) -> np.ndarray:
"""Gradient of mean cross-entropy: (1/n) X^T (sigma(Xw) - y)."""
p = sigmoid(X @ w)
n = X.shape[0]
return (X.T @ (p - y)) / n
def fit_logistic_gd(
X: np.ndarray,
y: np.ndarray,
lr: float = 0.5,
n_iter: int = 5000,
tol: float = 1e-7,
) -> np.ndarray:
w = np.zeros(X.shape[1])
for _ in range(n_iter):
g = logistic_gradient(X, y, w)
w_new = w - lr * g
if np.linalg.norm(w_new - w) < tol:
break
w = w_new
return w
def main() -> None:
iris = load_iris()
# Binary: class 0 vs rest (classes 1,2)
X = iris.data[iris.target != 2]
y = (iris.target[iris.target != 2]).astype(float)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=0, stratify=y
)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
X_tr = add_intercept(X_train)
X_te = add_intercept(X_test)
w = fit_logistic_gd(X_tr, y_train, lr=0.3, n_iter=8000)
prob_te = sigmoid(X_te @ w)
pred_scratch = (prob_te >= 0.5).astype(int)
clf = LogisticRegression(max_iter=2000, solver="lbfgs")
clf.fit(X_train, y_train)
pred_sklearn = clf.predict(X_test)
print("Scratch weights (intercept first):", w)
print("Sklearn coef + intercept:", np.r_[clf.intercept_, clf.coef_.ravel()])
print("Accuracy scratch:", accuracy_score(y_test, pred_scratch))
print("Accuracy sklearn :", accuracy_score(y_test, pred_sklearn))
if __name__ == "__main__":
main()
"""
Ridge regression (L2): closed-form solution vs sklearn.
Ridge: minimize ||y - Xw||^2 + lambda ||w'||^2 where w' are non-intercept weights.
We do not penalize intercept (common convention).
"""
from __future__ import annotations
import numpy as np
from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
def add_intercept(X: np.ndarray) -> np.ndarray:
return np.column_stack([np.ones(len(X)), X])
def ridge_closed_form(X: np.ndarray, y: np.ndarray, alpha: float) -> np.ndarray:
"""
w = (X^T X + R)^{-1} X^T y with R = diag(0, alpha, alpha, ...) penalizing all but intercept.
"""
p = X.shape[1]
R = np.eye(p)
R[0, 0] = 0.0
XtX = X.T @ X + alpha * R
Xty = X.T @ y
return np.linalg.solve(XtX, Xty)
def main() -> None:
X_raw, y = make_regression(n_samples=150, n_features=8, noise=5.0, random_state=1)
X_raw = np.asarray(X_raw, dtype=np.float64)
y = np.asarray(y, dtype=np.float64)
X = add_intercept(X_raw)
alpha = 10.0
# Closed form: intercept not penalized (first column of ones).
w = ridge_closed_form(X, y, alpha)
# Sklearn: same convention — intercept unpenalized, features penalized.
model = Ridge(alpha=alpha, fit_intercept=True)
model.fit(X_raw, y)
w_sk = np.r_[model.intercept_, model.coef_]
print("Ridge alpha:", alpha)
print("Closed-form w:", w)
print("Sklearn w :", w_sk)
print("Max abs diff :", np.max(np.abs(w - w_sk)))
print("MSE closed :", mean_squared_error(y, X @ w))
print("MSE sklearn :", mean_squared_error(y, model.predict(X_raw)))
if __name__ == "__main__":
main()