# Code examples from: Linear Regression From Scratch in Python: How a Model Actually Learns
# https://www.atulvij.com/linear-regression-python-from-scratch
# Review and adapt before running; examples use sample values.

# ---- Example 1 (python) ----
import numpy as np
rng = np.random.default_rng(1)
x = rng.uniform(0, 10, 100)
y = 3 * x + 7 + rng.normal(0, 2, 100)

# ---- Example 2 (python) ----
w, b = 0.0, 0.0          # start with a bad guess
lr = 0.01                # learning rate: size of each nudge

for epoch in range(3000):
    pred = w * x + b
    err = pred - y
    loss = (err ** 2).mean()
    dw = 2 * (err * x).mean()       # slope of the loss with respect to w
    db = 2 * err.mean()             # ... and with respect to b
    w -= lr * dw
    b -= lr * db
    if epoch % 500 == 0:
        print(f"epoch {epoch:4d}  loss {loss:8.2f}  w {w:.2f}  b {b:.2f}")

print("learned:", round(w, 2), round(b, 2))

# ---- Example 3 (python) ----
print(np.polyfit(x, y, 1))                 # [slope, intercept], exact solution

from sklearn.linear_model import LinearRegression
m = LinearRegression().fit(x.reshape(-1, 1), y)
print(m.coef_[0], m.intercept_)

# ---- Example 4 (python) ----
X = np.column_stack([x, rng.uniform(0, 5, 100)])
y2 = 3 * X[:, 0] - 2 * X[:, 1] + 7 + rng.normal(0, 2, 100)
w, b = np.zeros(2), 0.0
for _ in range(5000):
    err = X @ w + b - y2
    w -= 0.01 * 2 * X.T @ err / len(y2)
    b -= 0.01 * 2 * err.mean()
print(w.round(2), round(b, 2))             # close to [3, -2] and 7

