Skip to content

Commit d2bf0f1

Browse files
authored
Merge pull request #1751 from Shaikh-Ubaid/regression_pkg
PKG: Add linear regression package
2 parents 269345b + 6111d46 commit d2bf0f1

File tree

11 files changed

+252
-46
lines changed

11 files changed

+252
-46
lines changed

integration_tests/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,8 @@ RUN(NAME str_to_list_cast LABELS cpython llvm c)
491491

492492
RUN(NAME test_package_01 LABELS cpython llvm)
493493
RUN(NAME test_pkg_lpdraw LABELS cpython llvm wasm)
494-
RUN(NAME test_pkg_lnn LABELS cpython llvm)
494+
RUN(NAME test_pkg_lnn_01 LABELS cpython llvm)
495+
RUN(NAME test_pkg_lnn_02 LABELS cpython llvm)
495496

496497
RUN(NAME generics_01 LABELS cpython llvm c)
497498
RUN(NAME generics_02 LABELS cpython llvm c)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .perceptron_main import init_perceptron, train_dataset, test_perceptron, normalize_input_vectors, print_perceptron, Perceptron
1+
from .perceptron_main import init_perceptron, train_dataset, test_perceptron, print_perceptron, Perceptron

integration_tests/lnn/perceptron/perceptron_main.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,6 @@ class Perceptron:
1111
cur_accuracy: f64
1212
epochs_cnt: i32
1313

14-
def normalize(value: f64, leftMin: f64, leftMax: f64, rightMin: f64, rightMax: f64) -> f64:
15-
# Figure out how 'wide' each range is
16-
leftSpan: f64 = leftMax - leftMin
17-
rightSpan: f64 = rightMax - rightMin
18-
19-
# Convert the left range into a 0-1 range (float)
20-
valueScaled: f64 = (value - leftMin) / leftSpan
21-
22-
# Convert the 0-1 range into a value in the right range.
23-
return rightMin + (valueScaled * rightSpan)
24-
25-
def normalize_input_vectors(input_vectors: list[list[f64]]):
26-
rows: i32 = len(input_vectors)
27-
cols: i32 = len(input_vectors[0])
28-
29-
j: i32
30-
for j in range(cols):
31-
colMinVal: f64 = input_vectors[0][j]
32-
colMaxVal: f64 = input_vectors[0][j]
33-
i: i32
34-
for i in range(rows):
35-
if input_vectors[i][j] > colMaxVal:
36-
colMaxVal = input_vectors[i][j]
37-
if input_vectors[i][j] < colMinVal:
38-
colMinVal = input_vectors[i][j]
39-
40-
for i in range(rows):
41-
input_vectors[i][j] = normalize(input_vectors[i][j], colMinVal, colMaxVal, -1.0, 1.0)
42-
43-
44-
4514
def get_inp_vec_with_bias(a: list[f64]) -> list[f64]:
4615
b: list[f64] = []
4716
i: i32
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .regression_main import init_perceptron, train_dataset, test_perceptron, print_perceptron, Perceptron
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from lpython import dataclass, i32, f64
2+
from sys import exit
3+
4+
@dataclass
5+
class Perceptron:
6+
no_of_inputs: i32
7+
weights: list[f64]
8+
learn_rate: f64
9+
iterations_limit: i32
10+
err_limit: f64
11+
err: f64
12+
epochs_cnt: i32
13+
14+
def get_inp_vec_with_bias(a: list[f64]) -> list[f64]:
15+
b: list[f64] = []
16+
i: i32
17+
for i in range(len(a)):
18+
b.append(a[i])
19+
b.append(1.0)
20+
return b
21+
22+
def init_weights(size: i32) -> list[f64]:
23+
weights: list[f64] = []
24+
i: i32
25+
for i in range(size):
26+
weights.append(0.0)
27+
weights.append(0.0) # append bias
28+
return weights
29+
30+
def init_perceptron(p: Perceptron, n: i32, rate: f64, iterations_limit: i32, err_limit: f64):
31+
p.no_of_inputs = n
32+
p.weights = init_weights(n)
33+
p.learn_rate = rate
34+
p.iterations_limit = iterations_limit
35+
p.err_limit = err_limit
36+
p.err = 1.0
37+
p.epochs_cnt = 0
38+
39+
def train_perceptron(p: Perceptron, input_vector: list[f64], actual_output: f64):
40+
predicted_output: f64 = predict_perceptron(p, input_vector)
41+
error: f64 = actual_output - predicted_output
42+
i: i32
43+
for i in range(len(input_vector)):
44+
p.weights[i] += p.learn_rate * f64(error) * f64(input_vector[i])
45+
46+
def predict_perceptron(p: Perceptron, input_vector: list[f64]) -> f64:
47+
weighted_sum: f64 = 0.0
48+
i: i32 = 0
49+
for i in range(len(input_vector)):
50+
weighted_sum = weighted_sum + p.weights[i] * f64(input_vector[i])
51+
return activation_function(weighted_sum)
52+
53+
def activation_function(value: f64) -> f64:
54+
return value
55+
56+
def train_epoch(p: Perceptron, input_vectors: list[list[f64]], outputs: list[f64]):
57+
i: i32
58+
for i in range(len(input_vectors)):
59+
input_vector: list[f64] = get_inp_vec_with_bias(input_vectors[i])
60+
if predict_perceptron(p, input_vector) != outputs[i]:
61+
train_perceptron(p, input_vector, outputs[i])
62+
63+
def train_dataset(p: Perceptron, input_vectors: list[list[f64]], outputs: list[f64]):
64+
prev_err: f64 = 0.0
65+
p.err = 1.0
66+
p.epochs_cnt = 0
67+
while abs(p.err - prev_err) >= p.err_limit and p.epochs_cnt < p.iterations_limit:
68+
p.epochs_cnt += 1
69+
train_epoch(p, input_vectors, outputs)
70+
prev_err = p.err
71+
p.err = test_perceptron(p, input_vectors, outputs)
72+
73+
def test_perceptron(p: Perceptron, input_vectors: list[list[f64]], outputs: list[f64]) -> f64:
74+
err: f64 = 0.0
75+
i: i32
76+
for i in range(len(input_vectors)):
77+
input_vector: list[f64] = get_inp_vec_with_bias(input_vectors[i])
78+
err = err + (outputs[i] - predict_perceptron(p, input_vector)) ** 2.0
79+
return err
80+
81+
def print_perceptron(p: Perceptron):
82+
print("weights = [", end = "")
83+
i: i32
84+
for i in range(p.no_of_inputs):
85+
print(p.weights[i], end = ", ")
86+
print(p.weights[p.no_of_inputs], end = "(bias)]\n")
87+
print("learn_rate = ", end = "")
88+
print(p.learn_rate)
89+
print("error = ", end = "")
90+
print(p.err)
91+
print("epochs_cnt = ", end = "")
92+
print(p.epochs_cnt)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .utils_main import normalize, normalize_input_vectors, normalize_output_vector
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from lpython import i32, f64
2+
3+
def normalize(value: f64, leftMin: f64, leftMax: f64, rightMin: f64, rightMax: f64) -> f64:
4+
# Figure out how 'wide' each range is
5+
leftSpan: f64 = leftMax - leftMin
6+
rightSpan: f64 = rightMax - rightMin
7+
8+
# Convert the left range into a 0-1 range (float)
9+
valueScaled: f64 = (value - leftMin) / leftSpan
10+
11+
# Convert the 0-1 range into a value in the right range.
12+
return rightMin + (valueScaled * rightSpan)
13+
14+
def normalize_input_vectors(input_vectors: list[list[f64]]):
15+
rows: i32 = len(input_vectors)
16+
cols: i32 = len(input_vectors[0])
17+
18+
j: i32
19+
for j in range(cols):
20+
colMinVal: f64 = input_vectors[0][j]
21+
colMaxVal: f64 = input_vectors[0][j]
22+
i: i32
23+
for i in range(rows):
24+
if input_vectors[i][j] > colMaxVal:
25+
colMaxVal = input_vectors[i][j]
26+
if input_vectors[i][j] < colMinVal:
27+
colMinVal = input_vectors[i][j]
28+
29+
for i in range(rows):
30+
input_vectors[i][j] = normalize(input_vectors[i][j], colMinVal, colMaxVal, -1.0, 1.0)
31+
32+
def normalize_output_vector(output_vector: list[f64]):
33+
rows: i32 = len(output_vector)
34+
colMinVal: f64 = output_vector[0]
35+
colMaxVal: f64 = output_vector[0]
36+
i: i32
37+
for i in range(rows):
38+
if output_vector[i] > colMaxVal:
39+
colMaxVal = output_vector[i]
40+
if output_vector[i] < colMinVal:
41+
colMinVal = output_vector[i]
42+
43+
for i in range(rows):
44+
output_vector[i] = normalize(output_vector[i], colMinVal, colMaxVal, -1.0, 1.0)

integration_tests/lpdraw/draw.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
def Pixel(H: i32, W: i32, Screen: i32[H, W], x: i32, y: i32) -> None:
88
if x >= 0 and y >= 0 and x < W and y < H:
9-
Screen[i32(int(H - 1 - y)), i32(int(x))] = 255
9+
Screen[H - 1 - y, x] = 255
1010

1111
def Clear(H: i32, W: i32, Screen: i32[H, W]):
1212
i: i32
@@ -53,30 +53,37 @@ def Display(H: i32, W: i32, Screen: i32[H, W]):
5353
def Line(H: i32, W: i32, Screen: i32[H, W], x1: i32, y1: i32, x2: i32, y2: i32) -> None:
5454
dx: i32 = abs(x2 - x1)
5555
dy: i32 = abs(y2 - y1)
56+
5657
sx: i32
5758
sy: i32
5859

59-
if x1 > x2:
60-
sx = -1
61-
else:
60+
if x1 < x2:
6261
sx = 1
63-
if y1 > y2:
64-
sy = -1
6562
else:
63+
sx = -1
64+
65+
if y1 < y2:
6666
sy = 1
67+
else:
68+
sy = -1
6769

6870
err: i32 = dx - dy
6971

7072
while x1 != x2 or y1 != y2:
7173
Pixel(H, W, Screen, x1, y1)
7274
e2: i32 = 2 * err
75+
7376
if e2 > -dy:
7477
err -= dy
7578
x1 += sx
79+
80+
if x1 == x2 and y1 == y2:
81+
Pixel(H, W, Screen, x1, y1)
82+
break
83+
7684
if e2 < dx:
7785
err += dx
7886
y1 += sy
79-
Pixel(H, W, Screen, x2, y2)
8087

8188
def Circle(H: i32, W: i32, Screen: i32[H, W], x: i32, y: i32, r: f64) -> None:
8289
x0: i32 = i32(int(r))
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from lnn.perceptron import init_perceptron, print_perceptron, normalize_input_vectors, Perceptron, train_dataset
1+
from lnn.perceptron import init_perceptron, print_perceptron, Perceptron, train_dataset
2+
from lnn.utils import normalize_input_vectors
23
from lpdraw import Line, Circle, Display, Clear
34
from lpython import i32, f64, Const
45
from numpy import empty, int32
@@ -30,7 +31,7 @@ def plot_graph(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]
3031
y2 *= scale_offset
3132

3233
# print (x1, y1, x2, y2)
33-
Line(Height, Width, Screen, i32(x1 + shift_offset), i32(y1 + shift_offset), i32(x2 + shift_offset), i32(y2 + shift_offset))
34+
Line(Height, Width, Screen, i32(int(x1 + shift_offset)), i32(int(y1 + shift_offset)), i32(int(x2 + shift_offset)), i32(int(y2 + shift_offset)))
3435

3536
i: i32
3637
point_size: i32 = 5
@@ -40,12 +41,12 @@ def plot_graph(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]
4041
input_vectors[i][0] += shift_offset
4142
input_vectors[i][1] += shift_offset
4243
if outputs[i] == 1:
43-
x: i32 = i32(input_vectors[i][0])
44-
y: i32 = i32(input_vectors[i][1])
44+
x: i32 = i32(int(input_vectors[i][0]))
45+
y: i32 = i32(int(input_vectors[i][1]))
4546
Line(Height, Width, Screen, x - point_size, y, x + point_size, y)
4647
Line(Height, Width, Screen, x, y - point_size, x, y + point_size)
4748
else:
48-
Circle(Height, Width, Screen, i32(input_vectors[i][0]), i32(input_vectors[i][1]), f64(point_size))
49+
Circle(Height, Width, Screen, i32(int(input_vectors[i][0])), i32(int(input_vectors[i][1])), f64(point_size))
4950

5051
Display(Height, Width, Screen)
5152

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from lnn.regression import init_perceptron, print_perceptron, Perceptron, train_dataset
2+
from lnn.utils import normalize_input_vectors, normalize_output_vector
3+
from lpdraw import Line, Circle, Display, Clear
4+
from lpython import i32, f64, Const
5+
from numpy import empty, int32
6+
7+
8+
def compute_decision_boundary(p: Perceptron, x: f64) -> f64:
9+
bias: f64 = p.weights[1]
10+
slope: f64 = p.weights[0]
11+
intercept: f64 = bias
12+
return slope * x + intercept
13+
14+
def plot_graph(p: Perceptron, input_vectors: list[list[f64]], outputs: list[f64]):
15+
Width: Const[i32] = 500 # x-axis limits [0, 499]
16+
Height: Const[i32] = 500 # y-axis limits [0, 499]
17+
Screen: i32[Height, Width] = empty((Height, Width), dtype=int32)
18+
Clear(Height, Width, Screen)
19+
20+
x1: f64 = 1.0
21+
y1: f64 = compute_decision_boundary(p, x1)
22+
x2: f64 = -1.0
23+
y2: f64 = compute_decision_boundary(p, x2)
24+
25+
# center the graph using the following offset
26+
scale_offset: f64 = Width / 4
27+
shift_offset: f64 = Width / 2
28+
x1 *= scale_offset
29+
y1 *= scale_offset
30+
x2 *= scale_offset
31+
y2 *= scale_offset
32+
33+
# print (x1, y1, x2, y2)
34+
Line(Height, Width, Screen, i32(int(x1 + shift_offset)), i32(int(y1 + shift_offset)), i32(int(x2 + shift_offset)), i32(int(y2 + shift_offset)))
35+
36+
i: i32
37+
point_size: i32 = 5
38+
for i in range(len(input_vectors)):
39+
input_vectors[i][0] *= scale_offset
40+
input_vectors[i][0] += shift_offset
41+
outputs[i] *= scale_offset
42+
outputs[i] += shift_offset
43+
44+
Circle(Height, Width, Screen, i32(int(input_vectors[i][0])), i32(int(outputs[i])), f64(point_size))
45+
46+
Display(Height, Width, Screen)
47+
48+
def main0():
49+
p: Perceptron = Perceptron(0, [0.0], 0.0, 0, 0.0, 0.0, 0)
50+
init_perceptron(p, 1, 0.0005, 10000, 1e-16)
51+
52+
input_vectors: list[list[f64]] = [[1.1], [1.3], [1.5], [2.0], [2.2], [2.9], [3.0], [3.2], [3.2], [3.7], [3.9], [4.0], [4.0], [4.1], [4.5], [4.9], [5.1], [5.3], [5.9], [6.0], [6.8], [7.1], [7.9], [8.2], [8.7], [9.0], [9.5], [9.6], [10.3], [10.5], [11.2], [11.5], [12.3], [12.9], [13.5]]
53+
outputs: list[f64] = [39343.0, 46205.0, 37731.0, 43525.0, 39891.0, 56642.0, 60150.0, 54445.0, 64445.0, 57189.0, 63218.0, 55794.0, 56957.0, 57081.0, 61111.0, 67938.0, 66029.0, 83088.0, 81363.0, 93940.0, 91738.0, 98273.0, 101302.0, 113812.0, 109431.0, 105582.0, 116969.0, 112635.0, 122391.0, 121872.0, 127345.0, 126756.0, 128765.0, 135675.0, 139465.0]
54+
55+
normalize_input_vectors(input_vectors)
56+
normalize_output_vector(outputs)
57+
58+
train_dataset(p, input_vectors, outputs)
59+
print_perceptron(p)
60+
61+
assert abs(p.weights[0] - (1.0640975812232145)) <= 1e-12
62+
assert abs(p.weights[1] - (0.0786977829749839)) <= 1e-12
63+
assert abs(p.err - (0.4735308448814293)) <= 1e-12
64+
assert p.epochs_cnt == 4515
65+
66+
plot_graph(p, input_vectors, outputs)
67+
68+
def main1():
69+
p: Perceptron = Perceptron(0, [0.0], 0.0, 0, 0.0, 0.0, 0)
70+
init_perceptron(p, 1, 0.0005, 10000, 1e-16)
71+
72+
input_vectors: list[list[f64]] = [[1.0], [3.0], [7.0]]
73+
outputs: list[f64] = [8.0, 4.0, -2.0]
74+
75+
normalize_input_vectors(input_vectors)
76+
normalize_output_vector(outputs)
77+
78+
train_dataset(p, input_vectors, outputs)
79+
print_perceptron(p)
80+
81+
assert abs(p.weights[0] - (-0.9856542200697508)) <= 1e-12
82+
assert abs(p.weights[1] - (-0.0428446744717655)) <= 1e-12
83+
assert abs(p.err - 0.011428579012311327) <= 1e-12
84+
assert p.epochs_cnt == 10000
85+
86+
plot_graph(p, input_vectors, outputs)
87+
88+
main0()
89+
main1()

0 commit comments

Comments
 (0)