Skip to content

Commit 10a0274

Browse files
Add naive and vectorized implementations of Linear Regression using Gradient Descent
1 parent a71618f commit 10a0274

2 files changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""
2+
Naive implementation of Linear Regression using Gradient Descent.
3+
4+
This version is intentionally less optimized and more verbose,
5+
designed for educational clarity. It shows the step-by-step
6+
gradient descent update and error calculation.
7+
8+
Dataset used: CSGO dataset (ADR vs Rating)
9+
"""
10+
11+
# /// script
12+
# requires-python = ">=3.13"
13+
# dependencies = [
14+
# "httpx",
15+
# "numpy",
16+
# ]
17+
# ///
18+
19+
import httpx
20+
import numpy as np
21+
22+
23+
def collect_dataset() -> np.ndarray:
24+
"""Collect dataset of CSGO (ADR vs Rating)
25+
26+
:return: dataset as numpy matrix
27+
"""
28+
response = httpx.get(
29+
"https://raw.githubusercontent.com/yashLadha/The_Math_of_Intelligence/"
30+
"master/Week1/ADRvsRating.csv",
31+
timeout=10,
32+
)
33+
lines = response.text.splitlines()
34+
data = [line.split(",") for line in lines]
35+
data.pop(0) # remove header row
36+
dataset = np.matrix(data)
37+
return dataset
38+
39+
40+
def run_steep_gradient_descent(
41+
data_x: np.ndarray, data_y: np.ndarray, len_data: int, alpha: float, theta: np.ndarray
42+
) -> np.ndarray:
43+
"""Run one step of steep gradient descent.
44+
45+
:param data_x: dataset features
46+
:param data_y: dataset labels
47+
:param len_data: number of samples
48+
:param alpha: learning rate
49+
:param theta: feature vector (weights)
50+
51+
:return: updated theta
52+
53+
>>> import numpy as np
54+
>>> data_x = np.array([[1, 2], [3, 4]])
55+
>>> data_y = np.array([5, 6])
56+
>>> len_data = len(data_x)
57+
>>> alpha = 0.01
58+
>>> theta = np.array([0.1, 0.2])
59+
>>> run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta)
60+
array([0.196, 0.343])
61+
"""
62+
prod = np.dot(theta, data_x.T)
63+
prod -= data_y.T
64+
grad = np.dot(prod, data_x)
65+
theta = theta - (alpha / len_data) * grad
66+
return theta
67+
68+
69+
def sum_of_square_error(
70+
data_x: np.ndarray, data_y: np.ndarray, len_data: int, theta: np.ndarray
71+
) -> float:
72+
"""Return sum of square error for error calculation.
73+
74+
>>> vc_x = np.array([[1.1], [2.1], [3.1]])
75+
>>> vc_y = np.array([1.2, 2.2, 3.2])
76+
>>> round(sum_of_square_error(vc_x, vc_y, 3, np.array([1])), 3)
77+
0.005
78+
"""
79+
prod = np.dot(theta, data_x.T)
80+
prod -= data_y.T
81+
error = np.sum(np.square(prod)) / (2 * len_data)
82+
return float(error)
83+
84+
85+
def run_linear_regression(data_x: np.ndarray, data_y: np.ndarray) -> np.ndarray:
86+
"""Run linear regression using gradient descent.
87+
88+
:param data_x: dataset features
89+
:param data_y: dataset labels
90+
:return: learned feature vector theta
91+
"""
92+
iterations = 100000
93+
alpha = 0.000155
94+
95+
no_features = data_x.shape[1]
96+
len_data = data_x.shape[0] - 1
97+
98+
theta = np.zeros((1, no_features))
99+
100+
for i in range(iterations):
101+
theta = run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta)
102+
error = sum_of_square_error(data_x, data_y, len_data, theta)
103+
print(f"Iteration {i + 1}: Error = {error:.5f}")
104+
105+
return theta
106+
107+
108+
def mean_absolute_error(predicted_y: np.ndarray, original_y: np.ndarray) -> float:
109+
"""Return mean absolute error.
110+
111+
>>> predicted_y = np.array([3, -0.5, 2, 7])
112+
>>> original_y = np.array([2.5, 0.0, 2, 8])
113+
>>> mean_absolute_error(predicted_y, original_y)
114+
0.5
115+
"""
116+
total = sum(abs(y - predicted_y[i]) for i, y in enumerate(original_y))
117+
return total / len(original_y)
118+
119+
120+
def main() -> None:
121+
"""Driver function."""
122+
data = collect_dataset()
123+
124+
len_data = data.shape[0]
125+
data_x = np.c_[np.ones(len_data), data[:, :-1]].astype(float)
126+
data_y = data[:, -1].astype(float)
127+
128+
theta = run_linear_regression(data_x, data_y)
129+
print("Resultant Feature vector:")
130+
for value in theta.ravel():
131+
print(f"{value:.5f}")
132+
133+
134+
if __name__ == "__main__":
135+
import doctest
136+
137+
doctest.testmod()
138+
main()
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Vectorized implementation of Linear Regression using Gradient Descent.
3+
4+
This version uses NumPy vectorization for efficiency.
5+
It is faster and cleaner than the naive version but assumes
6+
readers are familiar with matrix operations.
7+
8+
Dataset used: CSGO dataset (ADR vs Rating)
9+
"""
10+
11+
# /// script
12+
# requires-python = ">=3.13"
13+
# dependencies = [
14+
# "httpx",
15+
# "numpy",
16+
# ]
17+
# ///
18+
19+
import httpx
20+
import numpy as np
21+
22+
23+
def collect_dataset() -> np.ndarray:
24+
"""Collect dataset of CSGO (ADR vs Rating).
25+
26+
:return: dataset as numpy array
27+
"""
28+
response = httpx.get(
29+
"https://raw.githubusercontent.com/yashLadha/The_Math_of_Intelligence/"
30+
"master/Week1/ADRvsRating.csv",
31+
timeout=10,
32+
)
33+
lines = response.text.splitlines()
34+
data = [line.split(",") for line in lines]
35+
data.pop(0) # remove header row
36+
return np.array(data, dtype=float)
37+
38+
39+
def gradient_descent(
40+
x: np.ndarray, y: np.ndarray, alpha: float = 0.000155, iterations: int = 100000
41+
) -> np.ndarray:
42+
"""Run gradient descent in a fully vectorized form.
43+
44+
:param x: dataset features
45+
:param y: dataset labels
46+
:param alpha: learning rate
47+
:param iterations: number of iterations
48+
:return: learned feature vector theta
49+
"""
50+
m, n = x.shape
51+
theta = np.zeros((n, 1))
52+
53+
for i in range(iterations):
54+
predictions = x @ theta
55+
errors = predictions - y
56+
gradients = (x.T @ errors) / m
57+
theta -= alpha * gradients
58+
59+
if i % (iterations // 10) == 0: # log occasionally
60+
cost = np.sum(errors**2) / (2 * m)
61+
print(f"Iteration {i+1}: Error = {cost:.5f}")
62+
63+
return theta
64+
65+
66+
def mean_absolute_error(predicted_y: np.ndarray, original_y: np.ndarray) -> float:
67+
"""Return mean absolute error.
68+
69+
>>> pred = np.array([3, -0.5, 2, 7])
70+
>>> orig = np.array([2.5, 0.0, 2, 8])
71+
>>> mean_absolute_error(pred, orig)
72+
0.5
73+
"""
74+
return float(np.mean(np.abs(original_y - predicted_y)))
75+
76+
77+
def main() -> None:
78+
"""Driver function."""
79+
dataset = collect_dataset()
80+
81+
m = dataset.shape[0]
82+
x = np.c_[np.ones(m), dataset[:, :-1]] # add intercept term
83+
y = dataset[:, -1].reshape(-1, 1)
84+
85+
theta = gradient_descent(x, y)
86+
print("Resultant Feature vector:")
87+
for value in theta.ravel():
88+
print(f"{value:.5f}")
89+
90+
91+
if __name__ == "__main__":
92+
import doctest
93+
94+
doctest.testmod()
95+
main()
96+

0 commit comments

Comments
 (0)