blindscrambler 0.1.7__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.
@@ -12,6 +12,9 @@ import matplotlib.pyplot as plt
12
12
  from scipy import stats
13
13
  from typing import Tuple, Optional
14
14
  import warnings
15
+ from torcheval.metrics import R2Score
16
+ import polars
17
+ from sklearn.model_selection import train_test_split
15
18
 
16
19
  # add a linear Regression class:
17
20
  class LinearRegression:
@@ -24,193 +27,259 @@ class LinearRegression:
24
27
  Features:
25
28
  - Gradient-based optimization using PyTorch
26
29
  - Confidence intervals for parameters w_1 and w_0
27
- - Visualization with confidence bands
28
30
  """
29
31
 
30
32
  def __init__(self, learning_rate: float = 0.01, max_epochs: int = 1000,
31
33
  tolerance: float = 1e-6):
32
34
 
33
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
34
42
  """
35
43
 
36
- # the main variables
44
+ # make the arguments
37
45
  self.learning_rate = learning_rate
38
46
  self.max_epochs = max_epochs
39
47
  self.tolerance = tolerance
40
48
 
41
- # Model parameters
42
- self.w_0 = nn.Parameter(torch.randn(1, requires_grad=True)) # intercept
43
- self.w_1 = nn.Parameter(torch.randn(1, requires_grad=True)) # slope
44
-
45
- # training data storage
49
+ self.nsamples = None
46
50
  self.X_train = None
47
- self.y_train = None
51
+ self.y_train = None
52
+ self.X_test = None
53
+ self.y_test = None
48
54
 
49
- # Model statistics for confidence intervals
50
- self.n_samples = None
51
- self.residual_sum_squares = None
52
- self.X_mean = None
53
- self.X_var = None
55
+ # to see if the instance is fitted or not
54
56
  self.fitted = False
55
57
 
56
- # Loss function and optimizer
57
- self.criterion = nn.MSELoss()
58
- self.optimizer = optim.SGD([self.w_1, self.w_0], lr=self.learning_rate)
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
59
61
 
60
- # Training history
61
- self.loss_history = []
62
- self.w0_history = []
63
- self.w1_history = []
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)
64
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
+
65
72
  def forward(self, X: torch.tensor) -> torch.tensor:
66
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
+
67
83
  """
68
84
  return self.w_1 * X + self.w_0
85
+
69
86
 
70
- def fit(self, X: np.ndarray, y: np.ndarray) -> 'LinearRegression':
87
+ def fit(self, X_train: np.ndarray, y_train: np.ndarray, X_test: np.ndarray, y_test: np.ndarray) -> 'LinearRegression':
71
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
72
94
  """
73
- # Convert to PyTorch tensors
74
- self.X_train = torch.tensor(X, dtype=torch.float32)
75
- self.y_train = torch.tensor(y, dtype=torch.float32)
76
- self.n_samples = len(X)
77
-
78
- # Store statistics for confidence intervals
79
- self.X_mean = float(np.mean(X))
80
- self.X_var = float(np.var(X, ddof=1)) # Sample variance
81
-
82
- # Training loop
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:
83
104
  prev_loss = float('inf')
84
-
105
+
106
+ # reset history
107
+ self.inter_loss.clear()
108
+ self.inter_w_0.clear()
109
+ self.inter_w_1.clear()
110
+
85
111
  for epoch in range(self.max_epochs):
86
- # Zero gradients
112
+ # reset the gradients
87
113
  self.optimizer.zero_grad()
88
-
89
- # Forward pass
90
- y_pred = self.forward(self.X_train)
91
-
92
- # Compute loss
93
- loss = self.criterion(y_pred, self.y_train)
94
-
95
- # Backward pass
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
96
122
  loss.backward()
97
-
98
- # Update parameters
123
+
124
+ # update model parameters
99
125
  self.optimizer.step()
100
-
101
- # Store loss history
102
- current_loss = loss.item()
103
- self.loss_history.append(current_loss)
104
- # Track parameter history (after update)
105
- with torch.no_grad():
106
- self.w0_history.append(float(self.w_0.item()))
107
- self.w1_history.append(float(self.w_1.item()))
108
-
109
- # Check for convergence
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
+
110
135
  if abs(prev_loss - current_loss) < self.tolerance:
111
136
  print(f"Converged after {epoch + 1} epochs")
112
137
  break
113
-
138
+
114
139
  prev_loss = current_loss
115
-
116
- # Compute residual sum of squares for confidence intervals
117
- with torch.no_grad():
118
- y_pred = self.forward(self.X_train)
119
- residuals = self.y_train - y_pred
120
- self.residual_sum_squares = float(torch.sum(residuals ** 2))
121
-
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
+
122
149
  self.fitted = True
123
- return self
124
-
150
+ return self
151
+
125
152
  def predict(self, X: np.ndarray) -> np.ndarray:
126
153
  """
127
- Make predictions on new data.
128
-
129
- Args:
130
- X: Input features of shape (n_samples,)
131
-
154
+ Make predictions on the new/unseen data
155
+
156
+ Params:
157
+ - feature vector X for the test set.
132
158
  Returns:
133
- Predictions as numpy array
159
+ - predictions in a numpy array
134
160
  """
161
+
162
+ # making sure that the model is fitted lol
135
163
  if not self.fitted:
136
164
  raise ValueError("Model must be fitted before making predictions")
137
165
 
166
+ # make it a tensor
138
167
  X_tensor = torch.tensor(X, dtype=torch.float32)
139
-
168
+
140
169
  with torch.no_grad():
141
170
  predictions = self.forward(X_tensor)
142
-
171
+
143
172
  return predictions.numpy()
173
+
144
174
 
145
- def analysis_plot(self, w_0: Optional[float] = None, w_1: Optional[float] = None):
175
+ def analysis_plot(self, show: bool = True, save_path: Optional[str] = None):
146
176
  """
147
- Create a 2x2 analysis figure showing:
148
- - Original data and fitted regression line
177
+ Create a 2x2 figure showing:
178
+ - Original data with fitted regression line
149
179
  - Training loss over epochs
150
- - Intercept (w_0) trajectory over epochs
151
- - Slope (w_1) trajectory over epochs
152
-
153
- Args:
154
- w_0: Intercept to plot final fit; if None, uses current self.w_0
155
- w_1: Slope to plot final fit; if None, uses current self.w_1
180
+ - w0 trajectory over epochs
181
+ - w1 trajectory over epochs
156
182
  """
157
- if self.X_train is None or self.y_train is None:
158
- raise ValueError("No training data found. Fit the model before plotting.")
159
-
160
- # Resolve parameters for plotting
161
- if w_0 is None:
162
- w_0 = float(self.w_0.detach().cpu().item())
163
- if w_1 is None:
164
- w_1 = float(self.w_1.detach().cpu().item())
165
-
166
- X_np = self.X_train.detach().cpu().numpy().reshape(-1)
167
- y_np = self.y_train.detach().cpu().numpy().reshape(-1)
168
-
169
- # Build line for fit
170
- x_line = np.linspace(X_np.min(), X_np.max(), 200)
171
- y_line = w_1 * x_line + w_0
172
-
173
- fig, axes = plt.subplots(2, 2, figsize=(12, 8))
174
-
175
- # 1) Data + fit
176
- ax = axes[0, 0]
177
- ax.scatter(X_np, y_np, color='tab:blue', alpha=0.7, label='Data')
178
- ax.plot(x_line, y_line, color='tab:red', label=f'Fit: y={w_1:.3f}x+{w_0:.3f}')
179
- ax.set_title('Data and Fitted Line')
180
- ax.set_xlabel('X')
181
- ax.set_ylabel('y')
182
- ax.legend()
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.")
183
187
 
184
- # 2) Loss history
185
- ax = axes[0, 1]
186
- if len(self.loss_history) > 0:
187
- ax.plot(range(1, len(self.loss_history) + 1), self.loss_history, color='tab:green')
188
- ax.set_title('Training Loss')
189
- ax.set_xlabel('Epoch')
190
- ax.set_ylabel('MSE Loss')
191
- ax.grid(True, linestyle='--', alpha=0.3)
192
-
193
- # 3) w_0 history
194
- ax = axes[1, 0]
195
- if len(self.w0_history) > 0:
196
- ax.plot(range(1, len(self.w0_history) + 1), self.w0_history, color='tab:purple')
197
- ax.axhline(w_0, color='gray', linestyle='--', alpha=0.6, label='Final w_0')
198
- ax.set_title('w_0 (Intercept) over Epochs')
199
- ax.set_xlabel('Epoch')
200
- ax.set_ylabel('w_0')
201
- ax.legend()
202
- ax.grid(True, linestyle='--', alpha=0.3)
203
-
204
- # 4) w_1 history
205
- ax = axes[1, 1]
206
- if len(self.w1_history) > 0:
207
- ax.plot(range(1, len(self.w1_history) + 1), self.w1_history, color='tab:orange')
208
- ax.axhline(w_1, color='gray', linestyle='--', alpha=0.6, label='Final w_1')
209
- ax.set_title('w_1 (Slope) over Epochs')
210
- ax.set_xlabel('Epoch')
211
- ax.set_ylabel('w_1')
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")
212
221
  ax.legend()
213
- ax.grid(True, linestyle='--', alpha=0.3)
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)
214
283
 
215
- plt.tight_layout()
216
- return fig, axes
284
+ # make the required plots
285
+ model.analysis_plot()
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blindscrambler
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Requires-Dist: matplotlib>=3.10.6
5
5
  Requires-Dist: numpy>=2.3.3
6
6
  Requires-Dist: polars>=1.34.0
7
7
  Requires-Dist: scikit-learn>=1.7.2
8
8
  Requires-Dist: scipy>=1.16.2
9
9
  Requires-Dist: torch>=2.8.0
10
+ Requires-Dist: torcheval>=0.0.7
10
11
  Requires-Dist: twine>=6.1.0
11
12
  Summary: Add your description here
12
13
  Author-email: blindscramblergh <blindscrambler@gmail.com>
@@ -1,5 +1,5 @@
1
- blindscrambler-0.1.7.dist-info/METADATA,sha256=Jnz2SO-4sk-teZcAoH5cHZ4zAMnmDISbMBmK8rhpidc,501
2
- blindscrambler-0.1.7.dist-info/WHEEL,sha256=vpqC0tRn_8bTHidvtrPbrnFQPZnrhuKzsjDdeKwCd58,102
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
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
@@ -10,6 +10,6 @@ blindscrambler/distributions/cvdistributions.py,sha256=lgZnlYdlCJEhk6K4cAkZmtIED
10
10
  blindscrambler/matrix/__init__.py,sha256=qlItVU8AVj_mP2NUJ3gor-lsovxk3Wxf5tUfKynoUbg,157
11
11
  blindscrambler/matrix/elementary.py,sha256=hArZLiBTA_vW1EZ0RniECf6ybJiJxO7KNuVHb_TZFQU,3987
12
12
  blindscrambler/model/__init__.py,sha256=CUXjl7w9exeF60zz0pjhD2SX8BLlH4Q5NXjEx_azznQ,71
13
- blindscrambler/model/regression.py,sha256=srWs8XueH8oc62k_8jJJtTnBWfH2tq1CSf0iO0j4JUE,7061
13
+ blindscrambler/model/regression.py,sha256=Z1RptPbZ68NZByHG0N530ZocLOmcWs34eOWAoPqw1is,8792
14
14
  blindscrambler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- blindscrambler-0.1.7.dist-info/RECORD,,
15
+ blindscrambler-0.1.8.dist-info/RECORD,,