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
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
57
|
-
self.
|
58
|
-
self.
|
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
|
-
#
|
61
|
-
self.
|
62
|
-
self.
|
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,
|
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
|
-
|
74
|
-
|
75
|
-
self.
|
76
|
-
self.
|
77
|
-
|
78
|
-
|
79
|
-
self.
|
80
|
-
|
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
|
-
#
|
112
|
+
# reset the gradients
|
87
113
|
self.optimizer.zero_grad()
|
88
|
-
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
#
|
93
|
-
loss = self.
|
94
|
-
|
95
|
-
#
|
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
|
-
#
|
123
|
+
|
124
|
+
# update model parameters
|
99
125
|
self.optimizer.step()
|
100
|
-
|
101
|
-
#
|
102
|
-
current_loss = loss.item()
|
103
|
-
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
130
|
-
X
|
131
|
-
|
154
|
+
Make predictions on the new/unseen data
|
155
|
+
|
156
|
+
Params:
|
157
|
+
- feature vector X for the test set.
|
132
158
|
Returns:
|
133
|
-
|
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,
|
175
|
+
def analysis_plot(self, show: bool = True, save_path: Optional[str] = None):
|
146
176
|
"""
|
147
|
-
Create a 2x2
|
148
|
-
- Original data
|
177
|
+
Create a 2x2 figure showing:
|
178
|
+
- Original data with fitted regression line
|
149
179
|
- Training loss over epochs
|
150
|
-
-
|
151
|
-
-
|
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
|
158
|
-
raise ValueError("
|
159
|
-
|
160
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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,
|
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
|
-
|
216
|
-
|
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.
|
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.
|
2
|
-
blindscrambler-0.1.
|
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=
|
13
|
+
blindscrambler/model/regression.py,sha256=Z1RptPbZ68NZByHG0N530ZocLOmcWs34eOWAoPqw1is,8792
|
14
14
|
blindscrambler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
blindscrambler-0.1.
|
15
|
+
blindscrambler-0.1.8.dist-info/RECORD,,
|
File without changes
|