blindscrambler 0.1.6__cp39-abi3-macosx_11_0_arm64.whl → 0.1.8__cp39-abi3-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- blindscrambler/__init__.py +1 -0
- blindscrambler/model/__init__.py +2 -0
- blindscrambler/model/regression.py +285 -0
- {blindscrambler-0.1.6.dist-info → blindscrambler-0.1.8.dist-info}/METADATA +5 -1
- {blindscrambler-0.1.6.dist-info → blindscrambler-0.1.8.dist-info}/RECORD +6 -4
- {blindscrambler-0.1.6.dist-info → blindscrambler-0.1.8.dist-info}/WHEEL +1 -1
blindscrambler/__init__.py
CHANGED
@@ -0,0 +1,285 @@
|
|
1
|
+
# Author metadata
|
2
|
+
|
3
|
+
__Name__ = "Syed Raza"
|
4
|
+
__email__ = "sar0033@uah.edu"
|
5
|
+
|
6
|
+
# import statements
|
7
|
+
import torch
|
8
|
+
import torch.nn as nn
|
9
|
+
import torch.optim as optim
|
10
|
+
import numpy as np
|
11
|
+
import matplotlib.pyplot as plt
|
12
|
+
from scipy import stats
|
13
|
+
from typing import Tuple, Optional
|
14
|
+
import warnings
|
15
|
+
from torcheval.metrics import R2Score
|
16
|
+
import polars
|
17
|
+
from sklearn.model_selection import train_test_split
|
18
|
+
|
19
|
+
# add a linear Regression class:
|
20
|
+
class LinearRegression:
|
21
|
+
"""
|
22
|
+
A PyTorch-based Linear Regression implementation for one variable.
|
23
|
+
|
24
|
+
Model: y = w_1 * x + w_0
|
25
|
+
Loss: Mean Squared Error
|
26
|
+
|
27
|
+
Features:
|
28
|
+
- Gradient-based optimization using PyTorch
|
29
|
+
- Confidence intervals for parameters w_1 and w_0
|
30
|
+
"""
|
31
|
+
|
32
|
+
def __init__(self, learning_rate: float = 0.01, max_epochs: int = 1000,
|
33
|
+
tolerance: float = 1e-6):
|
34
|
+
|
35
|
+
"""
|
36
|
+
The Constructor function for LinearRegression Class
|
37
|
+
|
38
|
+
Params:
|
39
|
+
- learning rate, for the gradient descent algorithm
|
40
|
+
- maximum number of epochs
|
41
|
+
- tolerance, to know if things have converged
|
42
|
+
"""
|
43
|
+
|
44
|
+
# make the arguments
|
45
|
+
self.learning_rate = learning_rate
|
46
|
+
self.max_epochs = max_epochs
|
47
|
+
self.tolerance = tolerance
|
48
|
+
|
49
|
+
self.nsamples = None
|
50
|
+
self.X_train = None
|
51
|
+
self.y_train = None
|
52
|
+
self.X_test = None
|
53
|
+
self.y_test = None
|
54
|
+
|
55
|
+
# to see if the instance is fitted or not
|
56
|
+
self.fitted = False
|
57
|
+
|
58
|
+
# the model parameters
|
59
|
+
self.w_0 = nn.Parameter(torch.randn(1, requires_grad=True)) # intercept
|
60
|
+
self.w_1 = nn.Parameter(torch.randn(1, requires_grad=True)) # slope
|
61
|
+
|
62
|
+
# loss function and its optimizer
|
63
|
+
self.lossfunction = nn.MSELoss()
|
64
|
+
self.optimizer = optim.SGD([self.w_0, self.w_1], lr = self.learning_rate)
|
65
|
+
|
66
|
+
# hold intermediate values of w_0 and w_1 and loss
|
67
|
+
self.inter_w_0 = []
|
68
|
+
self.inter_w_1 = []
|
69
|
+
self.inter_loss = []
|
70
|
+
|
71
|
+
|
72
|
+
def forward(self, X: torch.tensor) -> torch.tensor:
|
73
|
+
"""
|
74
|
+
Forward function for to specify linear model and compute the response
|
75
|
+
|
76
|
+
Params:
|
77
|
+
- X: torch.tensor
|
78
|
+
the input vector of size (n_samples, )
|
79
|
+
Returns:
|
80
|
+
- self.w_1 * X + self.w_0
|
81
|
+
` the output is linear model result
|
82
|
+
|
83
|
+
"""
|
84
|
+
return self.w_1 * X + self.w_0
|
85
|
+
|
86
|
+
|
87
|
+
def fit(self, X_train: np.ndarray, y_train: np.ndarray, X_test: np.ndarray, y_test: np.ndarray) -> 'LinearRegression':
|
88
|
+
"""
|
89
|
+
The function where the training happens
|
90
|
+
|
91
|
+
Params:
|
92
|
+
- X, the training dataset of features
|
93
|
+
- y, the training dataset of target
|
94
|
+
"""
|
95
|
+
|
96
|
+
# convert to Pytorch tensors:
|
97
|
+
self.X_train = torch.tensor(X_train, dtype=torch.float32)
|
98
|
+
self.y_train = torch.tensor(y_train, dtype=torch.float32)
|
99
|
+
self.X_test = torch.tensor(X_test, dtype=torch.float32)
|
100
|
+
self.y_test = torch.tensor(y_test, dtype=torch.float32)
|
101
|
+
self.nsamples = len(X_train) # samples in the training set
|
102
|
+
|
103
|
+
# the training loop:
|
104
|
+
prev_loss = float('inf')
|
105
|
+
|
106
|
+
# reset history
|
107
|
+
self.inter_loss.clear()
|
108
|
+
self.inter_w_0.clear()
|
109
|
+
self.inter_w_1.clear()
|
110
|
+
|
111
|
+
for epoch in range(self.max_epochs):
|
112
|
+
# reset the gradients
|
113
|
+
self.optimizer.zero_grad()
|
114
|
+
|
115
|
+
# premature prediction
|
116
|
+
y_train_pred = self.forward(self.X_train)
|
117
|
+
|
118
|
+
# loss function
|
119
|
+
loss = self.lossfunction(y_train_pred, self.y_train)
|
120
|
+
|
121
|
+
# automatic gradient backward pass
|
122
|
+
loss.backward()
|
123
|
+
|
124
|
+
# update model parameters
|
125
|
+
self.optimizer.step()
|
126
|
+
|
127
|
+
# get the current loss and save it
|
128
|
+
current_loss = float(loss.detach().item())
|
129
|
+
|
130
|
+
# save intermediate loss and model parameters
|
131
|
+
self.inter_loss.append(current_loss)
|
132
|
+
self.inter_w_0.append(float(self.w_0.detach().item()))
|
133
|
+
self.inter_w_1.append(float(self.w_1.detach().item()))
|
134
|
+
|
135
|
+
if abs(prev_loss - current_loss) < self.tolerance:
|
136
|
+
print(f"Converged after {epoch + 1} epochs")
|
137
|
+
break
|
138
|
+
|
139
|
+
prev_loss = current_loss
|
140
|
+
|
141
|
+
# make predictions on the test set
|
142
|
+
y_test_pred = self.forward(self.X_test)
|
143
|
+
|
144
|
+
# create an R^2 metric type
|
145
|
+
R2 = R2Score()
|
146
|
+
R2.update(y_test_pred, self.y_test)
|
147
|
+
print("The R2 score for the test set is :", R2.compute())
|
148
|
+
|
149
|
+
self.fitted = True
|
150
|
+
return self
|
151
|
+
|
152
|
+
def predict(self, X: np.ndarray) -> np.ndarray:
|
153
|
+
"""
|
154
|
+
Make predictions on the new/unseen data
|
155
|
+
|
156
|
+
Params:
|
157
|
+
- feature vector X for the test set.
|
158
|
+
Returns:
|
159
|
+
- predictions in a numpy array
|
160
|
+
"""
|
161
|
+
|
162
|
+
# making sure that the model is fitted lol
|
163
|
+
if not self.fitted:
|
164
|
+
raise ValueError("Model must be fitted before making predictions")
|
165
|
+
|
166
|
+
# make it a tensor
|
167
|
+
X_tensor = torch.tensor(X, dtype=torch.float32)
|
168
|
+
|
169
|
+
with torch.no_grad():
|
170
|
+
predictions = self.forward(X_tensor)
|
171
|
+
|
172
|
+
return predictions.numpy()
|
173
|
+
|
174
|
+
|
175
|
+
def analysis_plot(self, show: bool = True, save_path: Optional[str] = None):
|
176
|
+
"""
|
177
|
+
Create a 2x2 figure showing:
|
178
|
+
- Original data with fitted regression line
|
179
|
+
- Training loss over epochs
|
180
|
+
- w0 trajectory over epochs
|
181
|
+
- w1 trajectory over epochs
|
182
|
+
"""
|
183
|
+
if not self.fitted:
|
184
|
+
raise ValueError("Model must be fitted before plotting.")
|
185
|
+
if len(self.inter_loss) == 0:
|
186
|
+
warnings.warn("No training history recorded; plots may be empty.")
|
187
|
+
|
188
|
+
fig, axs = plt.subplots(2, 2, figsize=(12, 8))
|
189
|
+
|
190
|
+
# 1) Data + fitted line
|
191
|
+
ax = axs[0, 0]
|
192
|
+
|
193
|
+
# scatter only the test set
|
194
|
+
if self.X_test is not None and self.y_test is not None:
|
195
|
+
ax.scatter(
|
196
|
+
self.X_test.detach().cpu().numpy(),
|
197
|
+
self.y_test.detach().cpu().numpy(),
|
198
|
+
s=12, alpha=0.7, label="Test"
|
199
|
+
)
|
200
|
+
# Line range from min/max of test X only
|
201
|
+
xmin = float(torch.min(self.X_test).item())
|
202
|
+
xmax = float(torch.max(self.X_test).item())
|
203
|
+
else:
|
204
|
+
xmin, xmax = -1.0, 1.0
|
205
|
+
|
206
|
+
x_line = torch.linspace(xmin, xmax, 200)
|
207
|
+
with torch.no_grad():
|
208
|
+
y_line = self.forward(x_line).detach().cpu().numpy()
|
209
|
+
w0 = float(self.w_0.detach().item())
|
210
|
+
w1 = float(self.w_1.detach().item())
|
211
|
+
ax.plot(
|
212
|
+
x_line.detach().cpu().numpy(),
|
213
|
+
y_line,
|
214
|
+
color="crimson",
|
215
|
+
label=f"Fit: y = {w1:.4f} x + {w0:.4f}"
|
216
|
+
)
|
217
|
+
|
218
|
+
ax.set_title("Test Data and Fitted Line")
|
219
|
+
ax.set_xlabel("X")
|
220
|
+
ax.set_ylabel("y")
|
221
|
+
ax.legend()
|
222
|
+
ax.grid(True, alpha=0.2)
|
223
|
+
|
224
|
+
# 2) Loss
|
225
|
+
ax = axs[0, 1]
|
226
|
+
if self.inter_loss:
|
227
|
+
ax.plot(range(1, len(self.inter_loss) + 1), self.inter_loss, color="steelblue")
|
228
|
+
ax.set_title("Training Loss (MSE)")
|
229
|
+
ax.set_xlabel("Epoch")
|
230
|
+
ax.set_ylabel("Loss")
|
231
|
+
ax.grid(True, alpha=0.2)
|
232
|
+
|
233
|
+
# 3) w0 trajectory
|
234
|
+
ax = axs[1, 0]
|
235
|
+
if self.inter_w_0:
|
236
|
+
ax.plot(range(1, len(self.inter_w_0) + 1), self.inter_w_0, color="darkgreen")
|
237
|
+
ax.set_title("w0 trajectory")
|
238
|
+
ax.set_xlabel("Epoch")
|
239
|
+
ax.set_ylabel("w0")
|
240
|
+
ax.grid(True, alpha=0.2)
|
241
|
+
|
242
|
+
# 4) w1 trajectory
|
243
|
+
ax = axs[1, 1]
|
244
|
+
if self.inter_w_1:
|
245
|
+
ax.plot(range(1, len(self.inter_w_1) + 1), self.inter_w_1, color="darkorange")
|
246
|
+
ax.set_title("w1 trajectory")
|
247
|
+
ax.set_xlabel("Epoch")
|
248
|
+
ax.set_ylabel("w1")
|
249
|
+
ax.grid(True, alpha=0.2)
|
250
|
+
|
251
|
+
fig.tight_layout()
|
252
|
+
if save_path:
|
253
|
+
fig.savefig(save_path, dpi=150, bbox_inches="tight")
|
254
|
+
if show:
|
255
|
+
plt.show()
|
256
|
+
else:
|
257
|
+
plt.close(fig)
|
258
|
+
return fig, axs
|
259
|
+
|
260
|
+
if __name__ == "__main__":
|
261
|
+
|
262
|
+
# the path of the file
|
263
|
+
csv_path = "/Users/syedraza/Desktop/UAH/Classes/Fall2025/CPE586-MachineLearning/HWs/hw3/Hydropower.csv"
|
264
|
+
|
265
|
+
# read in the needed data
|
266
|
+
data_frame = polars.read_csv(csv_path)["BCR", "AnnualProduction"]
|
267
|
+
|
268
|
+
# separate out features and targets
|
269
|
+
X = data_frame["BCR"]
|
270
|
+
y = data_frame["AnnualProduction"]
|
271
|
+
|
272
|
+
# train test split this
|
273
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
|
274
|
+
|
275
|
+
# make a LinearRegression() instance
|
276
|
+
model = LinearRegression()
|
277
|
+
|
278
|
+
# .fit() takes test set as well because it has to calculate the R2 score
|
279
|
+
model.fit(X_train, y_train, X_test, y_test)
|
280
|
+
|
281
|
+
# make predictions
|
282
|
+
predictions = model.predict(X_test)
|
283
|
+
|
284
|
+
# make the required plots
|
285
|
+
model.analysis_plot()
|
@@ -1,9 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: blindscrambler
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.8
|
4
4
|
Requires-Dist: matplotlib>=3.10.6
|
5
5
|
Requires-Dist: numpy>=2.3.3
|
6
|
+
Requires-Dist: polars>=1.34.0
|
7
|
+
Requires-Dist: scikit-learn>=1.7.2
|
8
|
+
Requires-Dist: scipy>=1.16.2
|
6
9
|
Requires-Dist: torch>=2.8.0
|
10
|
+
Requires-Dist: torcheval>=0.0.7
|
7
11
|
Requires-Dist: twine>=6.1.0
|
8
12
|
Summary: Add your description here
|
9
13
|
Author-email: blindscramblergh <blindscrambler@gmail.com>
|
@@ -1,6 +1,6 @@
|
|
1
|
-
blindscrambler-0.1.
|
2
|
-
blindscrambler-0.1.
|
3
|
-
blindscrambler/__init__.py,sha256=
|
1
|
+
blindscrambler-0.1.8.dist-info/METADATA,sha256=8Zyi1rLMfBYCXQ4_2llZm6qBHGBmGmPsKzRH6pMNoQQ,533
|
2
|
+
blindscrambler-0.1.8.dist-info/WHEEL,sha256=vpqC0tRn_8bTHidvtrPbrnFQPZnrhuKzsjDdeKwCd58,102
|
3
|
+
blindscrambler/__init__.py,sha256=fSGH3-DvmAl8iABUbfGYKYKfQ025MVuih4VPm_wbUqQ,148
|
4
4
|
blindscrambler/_core.abi3.so,sha256=4uKUtCwAO1Hbvzv0FXAt38rEHYbg-Quio8CdkJ_UMrk,440112
|
5
5
|
blindscrambler/_core.pyi,sha256=b6oJaUXUzEzqUE5rpqefV06hl8o_JCU8pgKgIIzQgmc,33
|
6
6
|
blindscrambler/differential/__init__.py,sha256=INnk5rX2ae6mG5yynAQYKzpQ0BYsHquUhA9ZzbPVLm8,45
|
@@ -9,5 +9,7 @@ blindscrambler/distributions/__init__.py,sha256=8O4VQvymecRFRP1njwAfbD4yUACA25Rc
|
|
9
9
|
blindscrambler/distributions/cvdistributions.py,sha256=lgZnlYdlCJEhk6K4cAkZmtIED81156ZnaJAQQbHx96c,2025
|
10
10
|
blindscrambler/matrix/__init__.py,sha256=qlItVU8AVj_mP2NUJ3gor-lsovxk3Wxf5tUfKynoUbg,157
|
11
11
|
blindscrambler/matrix/elementary.py,sha256=hArZLiBTA_vW1EZ0RniECf6ybJiJxO7KNuVHb_TZFQU,3987
|
12
|
+
blindscrambler/model/__init__.py,sha256=CUXjl7w9exeF60zz0pjhD2SX8BLlH4Q5NXjEx_azznQ,71
|
13
|
+
blindscrambler/model/regression.py,sha256=Z1RptPbZ68NZByHG0N530ZocLOmcWs34eOWAoPqw1is,8792
|
12
14
|
blindscrambler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
blindscrambler-0.1.
|
15
|
+
blindscrambler-0.1.8.dist-info/RECORD,,
|