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.
@@ -1,4 +1,5 @@
1
1
  from blindscrambler._core import hello_from_bin
2
+ from blindscrambler.model import LinearRegression
2
3
 
3
4
 
4
5
  def hello() -> str:
@@ -0,0 +1,2 @@
1
+ from .regression import LinearRegression
2
+ __all__ = ["LinearRegression"]
@@ -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.6
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.6.dist-info/METADATA,sha256=FuF5WPP-3OT7RMzQQttXRQafPUu3sOtJE7lxpYEMT4s,407
2
- blindscrambler-0.1.6.dist-info/WHEEL,sha256=DLqF2HZq4W_umZdP6RnfAuqhmtX_UrV4mkqrSIMhipE,102
3
- blindscrambler/__init__.py,sha256=N6o-PTyGSlQ4ny1UA4ByeNenVF-wCTALnyP4WJ8PGas,98
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.6.dist-info/RECORD,,
15
+ blindscrambler-0.1.8.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.9.4)
2
+ Generator: maturin (1.9.6)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp39-abi3-macosx_11_0_arm64