Skip to content

Repository files navigation

ft_linear_regression

A single-feature linear regression that predicts a car's price from its mileage, trained with gradient descent — implemented from scratch in pure Python. No library does the regression (no numpy.polyfit, lstsq, or scikit-learn), so every step is inspectable and defensible at evaluation.

Mandatory — the two programs

python3 train.py            # gradient descent over data.csv -> theta.csv
python3 predict.py          # prompts for a mileage -> estimated price
python3 predict.py 50000    # or pass the mileage directly

predict.py uses the subject's hypothesis exactly:

estimatePrice(mileage) = theta0 + theta1 * mileage

Before training, theta0 = theta1 = 0, so the estimate is 0.

train.py applies the subject's update rules verbatim, updating both thetas simultaneously:

tmp_theta0 = learningRate * (1/m) * Σ (estimatePrice(x[i]) - y[i])
tmp_theta1 = learningRate * (1/m) * Σ (estimatePrice(x[i]) - y[i]) * x[i]

Why normalisation (the crux)

Real mileages are ~10⁵, so the x[i] factor in tmp_theta1 makes raw gradient descent diverge for any usable learning rate. The trainer min-max normalises the feature and target, runs the exact update above in that scaled space, then de-normalises the thetas back to real units before saving:

theta1 = t1 * yspan / xspan
theta0 = ymin + t0 * yspan - theta1 * xmin

The algorithm is unchanged — only the feature scale is. The saved thetas work directly on real kilometres and match the closed-form least-squares solution to 5+ significant figures (the test suite asserts this).

Performance

For the 24-point dataset training is instant either way (~1.5 ms). The default trainer is O(iterations × samples); train.py --fast uses the fact that both gradient sums are linear in the data and collapse to four precomputed moments, making each step O(1) — training time then stops depending on dataset size, for an identical result (make bench):

samples naive --fast speedup
100 8 ms 0.16 ms 50×
10 000 812 ms 1.15 ms 706×
100 000 8.2 s 9.5 ms 867×
1 000 000 ~80 s 93 ms ~860×

Bonus

python3 precision.py        # R² (coefficient of determination) + MSE / RMSE / MAE
python3 plot.py             # matplotlib PNG if installed, else SVG (+ ASCII preview)
python3 plot.py --show      # matplotlib window
python3 plot.py --svg       # force the dependency-free SVG

plot.py draws the data and the fitted line. It uses matplotlib when it is installed (regression.png, or a window with --show) and otherwise falls back to a hand-written, dependency-free SVG — so the visualisation works whether or not a plotting library is present. A quick ASCII scatter always prints to the terminal.

On this dataset: R² ≈ 0.733 (73 % of the price variance explained), theta0 ≈ 8499.6, theta1 ≈ -0.0214.

Develop

make test     # ~108 cases: core unit tests + end-to-end CLI stress (zero deps)
make bench    # naive vs --fast trainer benchmark
make lint     # ruff check + ruff format --check
make format   # ruff format + autofix
make fclean   # remove theta.csv / regression.svg / caches

The stress suite (tests/test_cli.py) drives the real programs through valid, invalid, weird and long inputs and asserts none ever crash. Requires only Python 3.10+ (developed on 3.12); ruff is the sole dev tool.

Files

File Role
linear_regression.py core: load, normalise→GD→de-normalise, metrics, IO
train.py mandatory: train and save theta.csv (--fast path)
predict.py mandatory: mileage → estimated price
precision.py bonus: model precision (R²/MSE/RMSE/MAE)
plot.py bonus: SVG + ASCII visualisation
tests/test_core.py core unit tests (fast≡naive, vs closed-form, edges)
tests/test_cli.py end-to-end CLI stress (~108 input scenarios)
bench.py naive vs --fast trainer benchmark
data.csv the provided dataset (km,price)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages