Skip to content

40. Linear Regression

import os

import torch
import torch.nn as nn
from torch_judge import check

torch.set_default_device(os.environ.get("TORCH_DEVICE", "cpu"))


class LinearRegression:
    def closed_form(self, X: torch.Tensor, y: torch.Tensor):
        """Normal equation: w = (X^T X)^{-1} X^T y."""
        X = torch.cat((torch.ones(len(X), 1), X), dim=1)
        closed_form = (X.T @ X).inverse() @ X.T @ y
        return closed_form[1:], closed_form[0]

    def gradient_descent(
        self, X: torch.Tensor, y: torch.Tensor, lr: float = 0.01, steps: int = 1000
    ):
        """Manual gradient descent loop."""
        w = torch.zeros(X.shape[1])
        b = torch.zeros(())
        N = X.shape[0]
        for _ in range(steps):
            pred = X @ w + b
            error = pred - y
            grad_w = 2 / N * X.T @ error
            grad_b = 2 / N * error.sum()
            w -= lr * grad_w
            b -= lr * grad_b

        return w, b

    def nn_linear(self, X: torch.Tensor, y: torch.Tensor, lr: float = 0.01, steps: int = 1000):
        """Train nn.Linear with autograd."""
        loss_fn = nn.MSELoss()
        _, d_out = X.shape
        linear_layer = nn.Linear(d_out, 1)
        optim = torch.optim.SGD(
            linear_layer.parameters(),
            lr=lr,
        )
        for _ in range(steps):
            optim.zero_grad()
            loss = loss_fn(linear_layer(X).squeeze(dim=1), y)
            loss.backward()
            optim.step()

        return linear_layer.weight.squeeze(dim=0), linear_layer.bias


if __name__ == "__main__":
    torch.manual_seed(42)
    X = torch.randn(100, 3)
    true_w = torch.tensor([2.0, -1.0, 0.5])
    y = X @ true_w + 3.0

    model = LinearRegression()

    w_cf, b_cf = model.closed_form(X, y)
    print(f"Closed-form:  w={w_cf}, b={b_cf.item():.4f}")

    w_gd, b_gd = model.gradient_descent(X, y, lr=0.05, steps=2000)
    print(f"Grad descent: w={w_gd}, b={b_gd.item():.4f}")

    w_nn, b_nn = model.nn_linear(X, y, lr=0.05, steps=2000)
    print(f"nn.Linear:    w={w_nn}, b={b_nn.item():.4f}")

    print(f"\nTrue:         w={true_w}, b=3.0")

    check("linear_regression")