Linear Regression from Scratch in Python
Build linear regression from scratch — the math, gradient descent, cost function, and a NumPy implementation compared to scikit-learn.
What you'll learn
- ✓The math behind linear regression (hypothesis, cost function)
- ✓Gradient descent step by step
- ✓Implementing linear regression with NumPy
- ✓Comparing your implementation to scikit-learn
Prerequisites
- •Python basics and NumPy fundamentals
- •Basic algebra (slopes, equations of lines)
Linear regression is the “hello world” of machine learning. It fits a straight line (or hyperplane) to your data by minimizing the distance between predictions and actual values. Building it from scratch teaches you the foundations that every ML model builds on.
The hypothesis
For simple linear regression with one feature:
$$y = wx + b$$
w(weight/slope): how muchychanges per unit ofxb(bias/intercept): the value ofywhenx = 0
For multiple features: y = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
The cost function
Mean Squared Error (MSE) measures how wrong our predictions are:
def cost_function(X, y, w, b):
m = len(y)
predictions = X @ w + b
cost = (1 / (2 * m)) * np.sum((predictions - y) ** 2)
return cost
The goal: find w and b that minimize this cost.
Gradient descent
Gradient descent iteratively adjusts w and b in the direction that reduces the cost.
def gradient_descent(X, y, w, b, learning_rate, iterations):
m = len(y)
history = []
for i in range(iterations):
predictions = X @ w + b
errors = predictions - y
dw = (1 / m) * (X.T @ errors)
db = (1 / m) * np.sum(errors)
w = w - learning_rate * dw
b = b - learning_rate * db
if i % 100 == 0:
cost = cost_function(X, y, w, b)
history.append(cost)
return w, b, history
Full implementation
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X.squeeze() + np.random.randn(100) * 0.5
w = np.zeros(X.shape[1])
b = 0.0
learning_rate = 0.1
iterations = 1000
w, b, history = gradient_descent(X, y, w, b, learning_rate, iterations)
print(f"w = {w[0]:.4f}, b = {b:.4f}")
# Expected: w ≈ 3.0, b ≈ 4.0
plt.scatter(X, y, alpha=0.5)
x_line = np.linspace(0, 2, 100)
plt.plot(x_line, w[0] * x_line + b, color="red", linewidth=2)
plt.xlabel("X")
plt.ylabel("y")
plt.title("Linear Regression from Scratch")
plt.show()
Comparing to scikit-learn
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X, y)
print(f"sklearn w = {model.coef_[0]:.4f}, b = {model.intercept_:.4f}")
print(f"ours w = {w[0]:.4f}, b = {b:.4f}")
The values should be nearly identical. scikit-learn uses the normal equation (closed-form solution) which is exact, while gradient descent converges to the same result iteratively.
Feature scaling
Gradient descent converges faster when features are on similar scales.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
Multiple features
X_multi = np.column_stack([
np.random.rand(100), # square footage
np.random.rand(100), # bedrooms
np.random.rand(100), # age
])
y_multi = 100 + 50 * X_multi[:, 0] + 30 * X_multi[:, 1] - 10 * X_multi[:, 2] + np.random.randn(100) * 5
w = np.zeros(3)
b = 0.0
w, b, history = gradient_descent(X_multi, y_multi, w, b, 0.1, 2000)
print(f"Weights: {w}")
print(f"Bias: {b:.2f}")
R² score
def r2_score(y_true, y_pred):
ss_res = np.sum((y_true - y_pred) ** 2)
ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
return 1 - (ss_res / ss_tot)
predictions = X @ w + b
print(f"R² = {r2_score(y, predictions):.4f}")
Summary
Linear regression minimizes MSE using gradient descent (or the normal equation). The weight tells you the slope, the bias tells you the intercept, and R² tells you how well your line fits. Every neural network, logistic regression, and complex ML model uses these same primitives — cost functions, gradients, and iterative optimization.
Related articles
- Machine Learning What Is Machine Learning? A Developer's Introduction
A clear introduction to machine learning for developers — supervised vs unsupervised, features and labels, train/test split, when ML beats rules, and a tiny scikit-learn example.
- Machine Learning Cross-Validation Strategies: K-Fold, Stratified, and Time Series
Learn when to use k-fold, stratified k-fold, and time series cross-validation to get reliable model performance estimates.
- Machine Learning Feature Engineering Techniques: Encoding, Scaling, and Binning
Master feature engineering with practical techniques for encoding categorical variables, scaling numerical features, and binning continuous data.
- Machine Learning Gradient Boosting with XGBoost: A Practical Tutorial
Learn how gradient boosting works and build high-performance models with XGBoost, including tuning, feature importance, and early stopping.