Moral88 0.9.0__py3-none-any.whl → 0.11.0__py3-none-any.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.
- Moral88/__init__.py +1 -0
- Moral88/regression.py +30 -135
- Moral88/segmentation.py +166 -38
- Moral88/utils.py +116 -0
- {Moral88-0.9.0.dist-info → Moral88-0.11.0.dist-info}/METADATA +1 -1
- Moral88-0.11.0.dist-info/RECORD +11 -0
- {Moral88-0.9.0.dist-info → Moral88-0.11.0.dist-info}/top_level.txt +1 -0
- tests/__init__.py +0 -0
- tests/test_regression.py +100 -0
- Moral88-0.9.0.dist-info/RECORD +0 -8
- {Moral88-0.9.0.dist-info → Moral88-0.11.0.dist-info}/LICENSE +0 -0
- {Moral88-0.9.0.dist-info → Moral88-0.11.0.dist-info}/WHEEL +0 -0
Moral88/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
from .regression import mean_absolute_error, mean_squared_error, r2_score, mean_bias_deviation, adjusted_r2_score, root_mean_squared_error, mean_absolute_percentage_error, explained_variance_score
|
Moral88/regression.py
CHANGED
@@ -1,112 +1,13 @@
|
|
1
1
|
import numpy as np
|
2
|
-
import
|
3
|
-
from typing import Union, List, Tuple
|
4
|
-
from scipy import sparse
|
5
|
-
|
6
|
-
class DataValidator:
|
7
|
-
def __init__(self):
|
8
|
-
pass
|
9
|
-
|
10
|
-
def check_device_cpu(self, device):
|
11
|
-
if device not in {"cpu", None}:
|
12
|
-
raise ValueError(f"Unsupported device: {device!r}. Only 'cpu' is supported.")
|
13
|
-
|
14
|
-
def is_1d_array(self, array: Union[np.ndarray, list], warn: bool = False) -> np.ndarray:
|
15
|
-
"""
|
16
|
-
Ensures input is a 1D array. Raises an error if it's not 1D or convertible to 1D.
|
17
|
-
"""
|
18
|
-
array = np.asarray(array)
|
19
|
-
shape = array.shape
|
20
|
-
|
21
|
-
if len(shape) == 1:
|
22
|
-
return array
|
23
|
-
elif len(shape) == 2 and shape[1] == 1:
|
24
|
-
if warn:
|
25
|
-
warnings.warn("Input is 2D but will be converted to 1D.", UserWarning)
|
26
|
-
return array.ravel()
|
27
|
-
else:
|
28
|
-
raise ValueError(f"Input must be 1D. Found shape {shape}.")
|
29
|
-
|
30
|
-
def check_samples(self, array: Union[np.ndarray, list]) -> int:
|
31
|
-
"""
|
32
|
-
Returns the number of samples in the array.
|
33
|
-
"""
|
34
|
-
array = np.asarray(array)
|
35
|
-
if hasattr(array, 'shape') and len(array.shape) > 0:
|
36
|
-
return array.shape[0]
|
37
|
-
else:
|
38
|
-
raise TypeError("Input must be an array-like object with at least one dimension.")
|
39
|
-
|
40
|
-
def check_consistent_length(self, *arrays: Union[np.ndarray, list]):
|
41
|
-
"""
|
42
|
-
Ensures all input arrays have the same length.
|
43
|
-
"""
|
44
|
-
lengths = [self.check_samples(arr) for arr in arrays]
|
45
|
-
if len(set(lengths)) > 1:
|
46
|
-
raise ValueError(f"Inconsistent lengths: {lengths}")
|
47
|
-
|
48
|
-
def validate_regression_targets(self, y_true, y_pred, dtype=np.float64):
|
49
|
-
"""
|
50
|
-
Ensures regression target values are consistent and converted to the specified dtype.
|
51
|
-
"""
|
52
|
-
y_true = np.asarray(y_true, dtype=dtype)
|
53
|
-
y_pred = np.asarray(y_pred, dtype=dtype)
|
54
|
-
|
55
|
-
if y_true.shape != y_pred.shape:
|
56
|
-
raise ValueError(f"Shapes of y_true {y_true.shape} and y_pred {y_pred.shape} do not match.")
|
57
|
-
|
58
|
-
return y_true, y_pred
|
59
|
-
|
60
|
-
def check_array(self, array, ensure_2d: bool = True, dtype=np.float64, allow_nan: bool = False):
|
61
|
-
"""
|
62
|
-
Validates input array and converts it to specified dtype.
|
63
|
-
"""
|
64
|
-
array = np.asarray(array, dtype=dtype)
|
65
|
-
|
66
|
-
if ensure_2d and array.ndim == 1:
|
67
|
-
array = array.reshape(-1, 1)
|
68
|
-
|
69
|
-
if not allow_nan and np.isnan(array).any():
|
70
|
-
raise ValueError("Input contains NaN values, which are not allowed.")
|
71
|
-
|
72
|
-
return array
|
73
|
-
|
74
|
-
def check_sparse(self, array, accept_sparse: Tuple[str] = ('csr', 'csc')):
|
75
|
-
"""
|
76
|
-
Validates sparse matrices and converts to an acceptable format.
|
77
|
-
"""
|
78
|
-
if sparse.issparse(array):
|
79
|
-
if array.format not in accept_sparse:
|
80
|
-
return array.asformat(accept_sparse[0])
|
81
|
-
return array
|
82
|
-
else:
|
83
|
-
raise ValueError("Input is not a sparse matrix.")
|
84
|
-
|
85
|
-
def validate_r2_score_inputs(self, y_true, y_pred, sample_weight=None):
|
86
|
-
"""
|
87
|
-
Ensures inputs for R2 score computation are valid.
|
88
|
-
"""
|
89
|
-
y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
|
90
|
-
if sample_weight is not None:
|
91
|
-
sample_weight = self.is_1d_array(sample_weight)
|
92
|
-
return y_true, y_pred, sample_weight
|
93
|
-
|
94
|
-
def validate_mae_mse_inputs(self, y_true, y_pred, library=None):
|
95
|
-
"""
|
96
|
-
Ensures inputs for MAE and MSE computation are valid.
|
97
|
-
"""
|
98
|
-
y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
|
99
|
-
if library not in {None, 'sklearn', 'torch', 'tensorflow', 'Moral88'}:
|
100
|
-
raise ValueError(f"Invalid library: {library}. Choose from {{'Moral88', 'sklearn', 'torch', 'tensorflow'}}.")
|
101
|
-
return y_true, y_pred
|
102
|
-
|
2
|
+
from Moral88.utils import DataValidator
|
103
3
|
|
4
|
+
validator = DataValidator()
|
104
5
|
|
105
|
-
def mean_bias_deviation(
|
6
|
+
def mean_bias_deviation( y_true, y_pred, library=None, flatten=True):
|
106
7
|
"""
|
107
8
|
Computes Mean Bias Deviation (MBD).
|
108
9
|
"""
|
109
|
-
y_true, y_pred =
|
10
|
+
y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
|
110
11
|
|
111
12
|
if flatten and y_true.ndim > 1:
|
112
13
|
y_true = y_true.flatten()
|
@@ -132,14 +33,12 @@ def mean_bias_deviation(self, y_true, y_pred, library=None, flatten=True):
|
|
132
33
|
|
133
34
|
# Default implementation
|
134
35
|
return np.mean(y_pred - y_true)
|
135
|
-
def __init__(self):
|
136
|
-
self.validator = DataValidator()
|
137
36
|
|
138
|
-
def r2_score(
|
37
|
+
def r2_score( y_true, y_pred, sample_weight=None, library=None, flatten=True):
|
139
38
|
"""
|
140
39
|
Computes R2 score.
|
141
40
|
"""
|
142
|
-
y_true, y_pred, sample_weight =
|
41
|
+
y_true, y_pred, sample_weight = validator.validate_r2_score_inputs(y_true, y_pred, sample_weight)
|
143
42
|
|
144
43
|
if flatten and y_true.ndim > 1:
|
145
44
|
y_true = y_true.flatten()
|
@@ -161,11 +60,11 @@ def r2_score(self, y_true, y_pred, sample_weight=None, library=None, flatten=Tru
|
|
161
60
|
return 0.0
|
162
61
|
return 1 - (numerator / denominator)
|
163
62
|
|
164
|
-
def mean_absolute_error(
|
63
|
+
def mean_absolute_error( y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
|
165
64
|
"""
|
166
65
|
Computes Mean Absolute Error (MAE).
|
167
66
|
"""
|
168
|
-
y_true, y_pred =
|
67
|
+
y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
|
169
68
|
|
170
69
|
if flatten:
|
171
70
|
y_true = y_true.ravel()
|
@@ -211,11 +110,11 @@ def mean_absolute_error(self, y_true, y_pred, normalize=True, threshold=None, me
|
|
211
110
|
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
212
111
|
return tf.reduce_mean(tf.abs(y_true_tensor - y_pred_tensor)).numpy()
|
213
112
|
|
214
|
-
def mean_squared_error(
|
113
|
+
def mean_squared_error(y_true, y_pred, normalize=True, threshold=None, method='mean', library='Moral88', flatten=True):
|
215
114
|
"""
|
216
115
|
Computes Mean Squared Error (MSE).
|
217
116
|
"""
|
218
|
-
y_true, y_pred =
|
117
|
+
y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
|
219
118
|
|
220
119
|
if flatten:
|
221
120
|
y_true = y_true.ravel()
|
@@ -239,10 +138,6 @@ def mean_squared_error(self, y_true, y_pred, normalize=True, threshold=None, met
|
|
239
138
|
else:
|
240
139
|
raise ValueError("Invalid method. Choose from {'mean', 'sum', 'none'}.")
|
241
140
|
|
242
|
-
# if normalize and method != 'none':
|
243
|
-
# range_y = np.ptp(y_true)
|
244
|
-
# result = result / max(abs(range_y), 1)
|
245
|
-
|
246
141
|
return result
|
247
142
|
|
248
143
|
elif library == 'sklearn':
|
@@ -261,11 +156,11 @@ def mean_squared_error(self, y_true, y_pred, normalize=True, threshold=None, met
|
|
261
156
|
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
262
157
|
return tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor)).numpy()
|
263
158
|
|
264
|
-
def root_mean_squared_error(
|
159
|
+
def root_mean_squared_error(y_true, y_pred, library=None):
|
265
160
|
"""
|
266
161
|
Computes Root Mean Squared Error (RMSE).
|
267
162
|
"""
|
268
|
-
y_true, y_pred =
|
163
|
+
y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
|
269
164
|
|
270
165
|
if library == 'sklearn':
|
271
166
|
from sklearn.metrics import mean_squared_error as sklearn_mse
|
@@ -283,15 +178,15 @@ def root_mean_squared_error(self, y_true, y_pred, library=None):
|
|
283
178
|
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
284
179
|
return tf.sqrt(tf.reduce_mean(tf.square(y_true_tensor - y_pred_tensor))).numpy()
|
285
180
|
|
286
|
-
mse =
|
181
|
+
mse = mean_squared_error(y_true, y_pred)
|
287
182
|
return np.sqrt(mse)
|
288
183
|
|
289
|
-
def mean_absolute_percentage_error(
|
184
|
+
def mean_absolute_percentage_error(y_true, y_pred, library=None):
|
290
185
|
"""
|
291
186
|
Computes Mean Absolute Percentage Error (MAPE).
|
292
187
|
"""
|
293
|
-
y_true, y_pred =
|
294
|
-
y_true, y_pred =
|
188
|
+
y_true, y_pred = validator.validate_regression_targets(y_true, y_pred)
|
189
|
+
y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
|
295
190
|
y_true = np.clip(y_true, 1e-8, None)
|
296
191
|
|
297
192
|
if library == 'sklearn':
|
@@ -312,11 +207,11 @@ def mean_absolute_percentage_error(self, y_true, y_pred, library=None):
|
|
312
207
|
|
313
208
|
return np.mean(np.abs((y_true - y_pred) / np.clip(np.abs(y_true), 1e-8, None))) * 100
|
314
209
|
|
315
|
-
def explained_variance_score(
|
210
|
+
def explained_variance_score(y_true, y_pred, library=None, flatten=True):
|
316
211
|
"""
|
317
212
|
Computes Explained Variance Score.
|
318
213
|
"""
|
319
|
-
y_true, y_pred =
|
214
|
+
y_true, y_pred = validator.validate_mae_mse_inputs(y_true, y_pred, library)
|
320
215
|
|
321
216
|
if library == 'sklearn':
|
322
217
|
from sklearn.metrics import explained_variance_score as sklearn_evs
|
@@ -342,7 +237,7 @@ def explained_variance_score(self, y_true, y_pred, library=None, flatten=True):
|
|
342
237
|
denominator = np.var(y_true)
|
343
238
|
return 1 - numerator / denominator if denominator != 0 else 0
|
344
239
|
|
345
|
-
def adjusted_r2_score(
|
240
|
+
def adjusted_r2_score(y_true, y_pred, n_features, library=None, flatten=True):
|
346
241
|
"""
|
347
242
|
Computes Adjusted R-Squared Score.
|
348
243
|
|
@@ -363,7 +258,7 @@ def adjusted_r2_score(self, y_true, y_pred, n_features, library=None, flatten=Tr
|
|
363
258
|
If True, flattens multidimensional arrays before computation.
|
364
259
|
"""
|
365
260
|
# Validate inputs
|
366
|
-
y_true, y_pred, _ =
|
261
|
+
y_true, y_pred, _ = validator.validate_r2_score_inputs(y_true, y_pred)
|
367
262
|
|
368
263
|
# Ensure inputs are 1D arrays
|
369
264
|
if y_true.ndim == 0 or y_pred.ndim == 0:
|
@@ -395,9 +290,9 @@ def adjusted_r2_score(self, y_true, y_pred, n_features, library=None, flatten=Tr
|
|
395
290
|
return adjusted_r2
|
396
291
|
|
397
292
|
if __name__ == '__main__':
|
398
|
-
|
293
|
+
# Example usage
|
399
294
|
validator = DataValidator()
|
400
|
-
|
295
|
+
|
401
296
|
|
402
297
|
# Test validation
|
403
298
|
arr = [[1], [2], [3]]
|
@@ -408,12 +303,12 @@ if __name__ == '__main__':
|
|
408
303
|
y_true = [3, -0.5, 2, 7]
|
409
304
|
y_pred = [2.5, 0.0, 2, 8]
|
410
305
|
|
411
|
-
print("Mean Absolute Error:",
|
412
|
-
print("Mean Squared Error:",
|
413
|
-
print("R2 Score:",
|
414
|
-
print("Mean Bias Deviation: ",
|
415
|
-
print("Explained Variance Score: ",
|
416
|
-
print("Mean Absolute Percentage Error: ",
|
417
|
-
print("Root Mean Squared Error: ",
|
418
|
-
print("adjusted_r2_score: ",
|
306
|
+
print("Mean Absolute Error:", mean_absolute_error(y_true, y_pred))
|
307
|
+
print("Mean Squared Error:", mean_squared_error(y_true, y_pred))
|
308
|
+
print("R2 Score:", r2_score(y_true, y_pred))
|
309
|
+
print("Mean Bias Deviation: ", mean_bias_deviation(y_true, y_pred))
|
310
|
+
print("Explained Variance Score: ", explained_variance_score(y_true, y_pred))
|
311
|
+
print("Mean Absolute Percentage Error: ", mean_absolute_percentage_error(y_true, y_pred))
|
312
|
+
print("Root Mean Squared Error: ", root_mean_squared_error(y_true, y_pred))
|
313
|
+
print("adjusted_r2_score: ", adjusted_r2_score(y_true, y_pred, 2))
|
419
314
|
|
Moral88/segmentation.py
CHANGED
@@ -1,38 +1,166 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
return
|
37
|
-
|
38
|
-
|
1
|
+
import numpy as np
|
2
|
+
from Moral88.utils import DataValidator
|
3
|
+
from scipy.spatial.distance import directed_hausdorff
|
4
|
+
from sklearn.metrics import f1_score as sklearn_f1_score
|
5
|
+
|
6
|
+
validator = DataValidator()
|
7
|
+
|
8
|
+
def intersection_over_union(y_true, y_pred, num_classes, library=None, flatten=True):
|
9
|
+
"""
|
10
|
+
Computes Intersection over Union (IoU).
|
11
|
+
"""
|
12
|
+
y_true, y_pred = validator.validate_segmentation_inputs(y_true, y_pred)
|
13
|
+
validator.validate_classes(y_true, num_classes)
|
14
|
+
validator.validate_classes(y_pred, num_classes)
|
15
|
+
|
16
|
+
if flatten:
|
17
|
+
y_true = y_true.ravel()
|
18
|
+
y_pred = y_pred.ravel()
|
19
|
+
|
20
|
+
if library == 'Moral88' or library is None:
|
21
|
+
iou_per_class = []
|
22
|
+
for cls in range(num_classes):
|
23
|
+
intersection = np.logical_and(y_true == cls, y_pred == cls).sum()
|
24
|
+
union = np.logical_or(y_true == cls, y_pred == cls).sum()
|
25
|
+
iou = intersection / union if union > 0 else 0
|
26
|
+
iou_per_class.append(iou)
|
27
|
+
|
28
|
+
mean_iou = np.mean(iou_per_class)
|
29
|
+
return iou_per_class, mean_iou
|
30
|
+
|
31
|
+
elif library == 'torch':
|
32
|
+
import torch
|
33
|
+
y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
|
34
|
+
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
|
35
|
+
iou = torch.mean((y_true_tensor * y_pred_tensor).sum(dim=1) / (y_true_tensor + y_pred_tensor - y_true_tensor * y_pred_tensor).sum(dim=1))
|
36
|
+
return iou.item()
|
37
|
+
|
38
|
+
elif library == 'tensorflow':
|
39
|
+
import tensorflow as tf
|
40
|
+
intersection = tf.reduce_sum(tf.cast(y_true == y_pred, tf.float32))
|
41
|
+
union = tf.reduce_sum(tf.cast(y_true | y_pred, tf.float32))
|
42
|
+
iou = intersection / union if union > 0 else 0
|
43
|
+
return iou.numpy()
|
44
|
+
|
45
|
+
raise ValueError("Unsupported library for IoU.")
|
46
|
+
|
47
|
+
def dice_coefficient(y_true, y_pred, num_classes, library=None, flatten=True):
|
48
|
+
"""
|
49
|
+
Computes Dice Coefficient.
|
50
|
+
"""
|
51
|
+
y_true, y_pred = validator.validate_segmentation_inputs(y_true, y_pred)
|
52
|
+
validator.validate_classes(y_true, num_classes)
|
53
|
+
validator.validate_classes(y_pred, num_classes)
|
54
|
+
|
55
|
+
if flatten:
|
56
|
+
y_true = y_true.ravel()
|
57
|
+
y_pred = y_pred.ravel()
|
58
|
+
|
59
|
+
if library == 'Moral88' or library is None:
|
60
|
+
dice_per_class = []
|
61
|
+
for cls in range(num_classes):
|
62
|
+
intersection = np.logical_and(y_true == cls, y_pred == cls).sum()
|
63
|
+
total = (y_true == cls).sum() + (y_pred == cls).sum()
|
64
|
+
dice = (2 * intersection) / total if total > 0 else 0
|
65
|
+
dice_per_class.append(dice)
|
66
|
+
|
67
|
+
mean_dice = np.mean(dice_per_class)
|
68
|
+
return dice_per_class, mean_dice
|
69
|
+
|
70
|
+
elif library == 'torch':
|
71
|
+
import torch
|
72
|
+
y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
|
73
|
+
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
|
74
|
+
intersection = torch.sum(y_true_tensor * y_pred_tensor)
|
75
|
+
total = torch.sum(y_true_tensor) + torch.sum(y_pred_tensor)
|
76
|
+
dice = (2 * intersection) / total if total > 0 else 0
|
77
|
+
return dice.item()
|
78
|
+
|
79
|
+
elif library == 'tensorflow':
|
80
|
+
import tensorflow as tf
|
81
|
+
y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
|
82
|
+
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
83
|
+
intersection = tf.reduce_sum(y_true_tensor * y_pred_tensor)
|
84
|
+
total = tf.reduce_sum(y_true_tensor) + tf.reduce_sum(y_pred_tensor)
|
85
|
+
dice = (2 * intersection) / total if total > 0 else 0
|
86
|
+
return dice.numpy()
|
87
|
+
|
88
|
+
raise ValueError("Unsupported library for Dice Coefficient.")
|
89
|
+
|
90
|
+
def pixel_accuracy(y_true, y_pred, library=None):
|
91
|
+
"""
|
92
|
+
Computes Pixel Accuracy.
|
93
|
+
"""
|
94
|
+
y_true, y_pred = validator.validate_segmentation_inputs(y_true, y_pred)
|
95
|
+
|
96
|
+
if library == 'Moral88' or library is None:
|
97
|
+
correct = (y_true == y_pred).sum()
|
98
|
+
total = y_true.size
|
99
|
+
accuracy = correct / total
|
100
|
+
return accuracy
|
101
|
+
|
102
|
+
elif library == 'torch':
|
103
|
+
import torch
|
104
|
+
y_true_tensor = torch.tensor(y_true, dtype=torch.float32)
|
105
|
+
y_pred_tensor = torch.tensor(y_pred, dtype=torch.float32)
|
106
|
+
correct = torch.sum(y_true_tensor == y_pred_tensor)
|
107
|
+
total = torch.numel(y_true_tensor)
|
108
|
+
accuracy = correct / total
|
109
|
+
return accuracy.item()
|
110
|
+
|
111
|
+
elif library == 'tensorflow':
|
112
|
+
import tensorflow as tf
|
113
|
+
y_true_tensor = tf.convert_to_tensor(y_true, dtype=tf.float32)
|
114
|
+
y_pred_tensor = tf.convert_to_tensor(y_pred, dtype=tf.float32)
|
115
|
+
correct = tf.reduce_sum(tf.cast(y_true_tensor == y_pred_tensor, tf.float32))
|
116
|
+
total = tf.size(y_true_tensor, out_type=tf.float32)
|
117
|
+
accuracy = correct / total
|
118
|
+
return accuracy.numpy()
|
119
|
+
|
120
|
+
raise ValueError("Unsupported library for Pixel Accuracy.")
|
121
|
+
|
122
|
+
def hausdorff_distance(y_true, y_pred, library=None):
|
123
|
+
"""
|
124
|
+
Computes Hausdorff Distance.
|
125
|
+
"""
|
126
|
+
y_true, y_pred = validator.validate_segmentation_inputs(y_true, y_pred)
|
127
|
+
|
128
|
+
if library == 'Moral88' or library is None:
|
129
|
+
y_true_points = np.argwhere(y_true > 0)
|
130
|
+
y_pred_points = np.argwhere(y_pred > 0)
|
131
|
+
|
132
|
+
distance = max(directed_hausdorff(y_true_points, y_pred_points)[0],
|
133
|
+
directed_hausdorff(y_pred_points, y_true_points)[0])
|
134
|
+
return distance
|
135
|
+
|
136
|
+
raise ValueError("Unsupported library for Hausdorff Distance.")
|
137
|
+
|
138
|
+
def f1_score(y_true, y_pred, num_classes, library=None, flatten=True):
|
139
|
+
"""
|
140
|
+
Computes F1 Score.
|
141
|
+
"""
|
142
|
+
y_true, y_pred = validator.validate_segmentation_inputs(y_true, y_pred)
|
143
|
+
validator.validate_classes(y_true, num_classes)
|
144
|
+
validator.validate_classes(y_pred, num_classes)
|
145
|
+
|
146
|
+
if flatten:
|
147
|
+
y_true = y_true.ravel()
|
148
|
+
y_pred = y_pred.ravel()
|
149
|
+
|
150
|
+
if library == 'sklearn':
|
151
|
+
return sklearn_f1_score(y_true, y_pred, average='macro')
|
152
|
+
|
153
|
+
if library == 'Moral88' or library is None:
|
154
|
+
f1_per_class = []
|
155
|
+
for cls in range(num_classes):
|
156
|
+
tp = np.logical_and(y_pred == cls, y_true == cls).sum()
|
157
|
+
fp = np.logical_and(y_pred == cls, y_true != cls).sum()
|
158
|
+
fn = np.logical_and(y_pred != cls, y_true == cls).sum()
|
159
|
+
|
160
|
+
f1 = (2 * tp) / (2 * tp + fp + fn) if (2 * tp + fp + fn) > 0 else 0
|
161
|
+
f1_per_class.append(f1)
|
162
|
+
|
163
|
+
mean_f1 = np.mean(f1_per_class)
|
164
|
+
return f1_per_class, mean_f1
|
165
|
+
|
166
|
+
raise ValueError("Unsupported library for F1 Score.")
|
Moral88/utils.py
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import warnings
|
3
|
+
from typing import Union, List, Tuple
|
4
|
+
from scipy import sparse
|
5
|
+
|
6
|
+
class DataValidator:
|
7
|
+
def __init__(self):
|
8
|
+
pass
|
9
|
+
|
10
|
+
def check_device_cpu(self, device):
|
11
|
+
if device not in {"cpu", None}:
|
12
|
+
raise ValueError(f"Unsupported device: {device!r}. Only 'cpu' is supported.")
|
13
|
+
|
14
|
+
def is_1d_array(self, array: Union[np.ndarray, list], warn: bool = False) -> np.ndarray:
|
15
|
+
"""
|
16
|
+
Ensures input is a 1D array. Raises an error if it's not 1D or convertible to 1D.
|
17
|
+
"""
|
18
|
+
array = np.asarray(array)
|
19
|
+
shape = array.shape
|
20
|
+
|
21
|
+
if len(shape) == 1:
|
22
|
+
return array
|
23
|
+
elif len(shape) == 2 and shape[1] == 1:
|
24
|
+
if warn:
|
25
|
+
warnings.warn("Input is 2D but will be converted to 1D.", UserWarning)
|
26
|
+
return array.ravel()
|
27
|
+
else:
|
28
|
+
raise ValueError(f"Input must be 1D. Found shape {shape}.")
|
29
|
+
|
30
|
+
def check_samples(self, array: Union[np.ndarray, list]) -> int:
|
31
|
+
"""
|
32
|
+
Returns the number of samples in the array.
|
33
|
+
"""
|
34
|
+
array = np.asarray(array)
|
35
|
+
if hasattr(array, 'shape') and len(array.shape) > 0:
|
36
|
+
return array.shape[0]
|
37
|
+
else:
|
38
|
+
raise TypeError("Input must be an array-like object with at least one dimension.")
|
39
|
+
|
40
|
+
def check_consistent_length(self, *arrays: Union[np.ndarray, list]):
|
41
|
+
"""
|
42
|
+
Ensures all input arrays have the same length.
|
43
|
+
"""
|
44
|
+
lengths = [self.check_samples(arr) for arr in arrays]
|
45
|
+
if len(set(lengths)) > 1:
|
46
|
+
raise ValueError(f"Inconsistent lengths: {lengths}")
|
47
|
+
|
48
|
+
def validate_regression_targets(self, y_true, y_pred, dtype=np.float64):
|
49
|
+
"""
|
50
|
+
Ensures regression target values are consistent and converted to the specified dtype.
|
51
|
+
"""
|
52
|
+
y_true = np.asarray(y_true, dtype=dtype)
|
53
|
+
y_pred = np.asarray(y_pred, dtype=dtype)
|
54
|
+
|
55
|
+
if y_true.shape != y_pred.shape:
|
56
|
+
raise ValueError(f"Shapes of y_true {y_true.shape} and y_pred {y_pred.shape} do not match.")
|
57
|
+
|
58
|
+
return y_true, y_pred
|
59
|
+
|
60
|
+
def validate_segmentation_inputs(self, y_true, y_pred):
|
61
|
+
"""
|
62
|
+
Ensures segmentation inputs are valid, checking dimensions and consistency.
|
63
|
+
"""
|
64
|
+
y_true = np.asarray(y_true, dtype=np.int32)
|
65
|
+
y_pred = np.asarray(y_pred, dtype=np.int32)
|
66
|
+
|
67
|
+
if y_true.shape != y_pred.shape:
|
68
|
+
raise ValueError(f"Shapes of y_true {y_true.shape} and y_pred {y_pred.shape} do not match.")
|
69
|
+
|
70
|
+
if y_true.ndim < 2 or y_pred.ndim < 2:
|
71
|
+
raise ValueError("Segmentation inputs must have at least two dimensions.")
|
72
|
+
|
73
|
+
return y_true, y_pred
|
74
|
+
|
75
|
+
def check_array(self, array, ensure_2d: bool = True, dtype=np.float64, allow_nan: bool = False):
|
76
|
+
"""
|
77
|
+
Validates input array and converts it to specified dtype.
|
78
|
+
"""
|
79
|
+
array = np.asarray(array, dtype=dtype)
|
80
|
+
|
81
|
+
if ensure_2d and array.ndim == 1:
|
82
|
+
array = array.reshape(-1, 1)
|
83
|
+
|
84
|
+
if not allow_nan and np.isnan(array).any():
|
85
|
+
raise ValueError("Input contains NaN values, which are not allowed.")
|
86
|
+
|
87
|
+
return array
|
88
|
+
|
89
|
+
def check_sparse(self, array, accept_sparse: Tuple[str] = ('csr', 'csc')):
|
90
|
+
"""
|
91
|
+
Validates sparse matrices and converts to an acceptable format.
|
92
|
+
"""
|
93
|
+
if sparse.issparse(array):
|
94
|
+
if array.format not in accept_sparse:
|
95
|
+
return array.asformat(accept_sparse[0])
|
96
|
+
return array
|
97
|
+
else:
|
98
|
+
raise ValueError("Input is not a sparse matrix.")
|
99
|
+
|
100
|
+
def validate_r2_score_inputs(self, y_true, y_pred, sample_weight=None):
|
101
|
+
"""
|
102
|
+
Ensures inputs for R2 score computation are valid.
|
103
|
+
"""
|
104
|
+
y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
|
105
|
+
if sample_weight is not None:
|
106
|
+
sample_weight = self.is_1d_array(sample_weight)
|
107
|
+
return y_true, y_pred, sample_weight
|
108
|
+
|
109
|
+
def validate_mae_mse_inputs(self, y_true, y_pred, library=None):
|
110
|
+
"""
|
111
|
+
Ensures inputs for MAE and MSE computation are valid.
|
112
|
+
"""
|
113
|
+
y_true, y_pred = self.validate_regression_targets(y_true, y_pred)
|
114
|
+
if library not in {None, 'sklearn', 'torch', 'tensorflow', 'Moral88'}:
|
115
|
+
raise ValueError(f"Invalid library: {library}. Choose from {{'Moral88', 'sklearn', 'torch', 'tensorflow'}}.")
|
116
|
+
return y_true, y_pred
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Moral88/__init__.py,sha256=Z7iEZUqslxRyJU2to6iX6a5Ak1XBZxU3VT4RvOCjsEU,196
|
2
|
+
Moral88/regression.py,sha256=WjNMpX0t99KGTrUKMBFg6LccnPvlnWKnjimu65BLrkc,12061
|
3
|
+
Moral88/segmentation.py,sha256=N6Pg-220JfTxV01Bwjir8kOFMW3nfI0L_MM9zGrvDvg,6470
|
4
|
+
Moral88/utils.py,sha256=4dZ165tRtwCEyz-wESp26-cZp-5Pz8HkSrmNPrKEH38,4534
|
5
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
tests/test_regression.py,sha256=j-XG9j5D24OaJBJT3ROnlvrMDrSR2sH4182U5L3PA5k,3124
|
7
|
+
Moral88-0.11.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
Moral88-0.11.0.dist-info/METADATA,sha256=I9J_QFmYuvB-Q5YufPayenl5E4-WvCQznIMtoQlNC24,408
|
9
|
+
Moral88-0.11.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
10
|
+
Moral88-0.11.0.dist-info/top_level.txt,sha256=gg4pKIcQal4JhJAb77H5W6SHC77e-BeLTy4hxfXwmfw,14
|
11
|
+
Moral88-0.11.0.dist-info/RECORD,,
|
tests/__init__.py
ADDED
File without changes
|
tests/test_regression.py
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
import pytest
|
2
|
+
import numpy as np
|
3
|
+
from Moral88.regression import (
|
4
|
+
mean_absolute_error,
|
5
|
+
mean_absolute_error,
|
6
|
+
mean_squared_error,
|
7
|
+
r2_score,
|
8
|
+
mean_bias_deviation,
|
9
|
+
adjusted_r2_score,
|
10
|
+
root_mean_squared_error,
|
11
|
+
mean_absolute_percentage_error,
|
12
|
+
explained_variance_score
|
13
|
+
)
|
14
|
+
import warnings
|
15
|
+
from Moral88.utils import DataValidator
|
16
|
+
|
17
|
+
validator = DataValidator()
|
18
|
+
|
19
|
+
def test_is_1d_array():
|
20
|
+
validator = DataValidator()
|
21
|
+
array = [[1], [2], [3]]
|
22
|
+
with warnings.catch_warnings():
|
23
|
+
warnings.simplefilter("ignore", UserWarning)
|
24
|
+
result = validator.is_1d_array(array, warn=True)
|
25
|
+
assert result.ndim == 1
|
26
|
+
assert np.array_equal(result, np.array([1, 2, 3]))
|
27
|
+
|
28
|
+
def test_check_samples():
|
29
|
+
validator = DataValidator()
|
30
|
+
array = [[1, 2], [3, 4], [5, 6]]
|
31
|
+
result = validator.check_samples(array)
|
32
|
+
assert result == 3
|
33
|
+
|
34
|
+
def test_check_consistent_length():
|
35
|
+
validator = DataValidator()
|
36
|
+
array1 = [1, 2, 3]
|
37
|
+
array2 = [4, 5, 6]
|
38
|
+
validator.check_consistent_length(array1, array2) # Should not raise an error
|
39
|
+
|
40
|
+
array3 = [7, 8]
|
41
|
+
with pytest.raises(ValueError):
|
42
|
+
validator.check_consistent_length(array1, array3)
|
43
|
+
|
44
|
+
def test_mean_absolute_error():
|
45
|
+
|
46
|
+
y_true = [3, -0.5, 2, 7]
|
47
|
+
y_pred = [2.5, 0.0, 2, 8]
|
48
|
+
result = mean_absolute_error(y_true, y_pred)
|
49
|
+
assert result == pytest.approx(0.5, rel=1e-2)
|
50
|
+
|
51
|
+
def test_mean_squared_error():
|
52
|
+
|
53
|
+
y_true = [3, -0.5, 2, 7]
|
54
|
+
y_pred = [2.5, 0.0, 2, 8]
|
55
|
+
result = mean_squared_error(y_true, y_pred)
|
56
|
+
assert result == pytest.approx(0.375, rel=1e-2)
|
57
|
+
|
58
|
+
def test_r2_score():
|
59
|
+
|
60
|
+
y_true = [3, -0.5, 2, 7]
|
61
|
+
y_pred = [2.5, 0.0, 2, 8]
|
62
|
+
result = r2_score(y_true, y_pred)
|
63
|
+
assert result == pytest.approx(0.948, rel=1e-2)
|
64
|
+
|
65
|
+
def test_mean_bias_deviation():
|
66
|
+
|
67
|
+
y_true = [3, -0.5, 2, 7]
|
68
|
+
y_pred = [2.5, 0.0, 2, 8]
|
69
|
+
result = mean_bias_deviation(y_true, y_pred)
|
70
|
+
assert result == pytest.approx(0.25, rel=1e-2)
|
71
|
+
|
72
|
+
def test_explained_variance_score():
|
73
|
+
|
74
|
+
y_true = [3, -0.5, 2, 7]
|
75
|
+
y_pred = [2.5, 0.0, 2, 8]
|
76
|
+
result = explained_variance_score(y_true, y_pred)
|
77
|
+
assert result == pytest.approx(0.957, rel=1e-2)
|
78
|
+
|
79
|
+
def test_mean_absolute_percentage_error():
|
80
|
+
|
81
|
+
y_true = [3, -0.5, 2, 7]
|
82
|
+
y_pred = [2.5, 0.0, 2, 8]
|
83
|
+
result = mean_absolute_percentage_error(y_true, y_pred)
|
84
|
+
assert result == pytest.approx(32.738095, rel=1e-2)
|
85
|
+
|
86
|
+
def test_root_mean_squared_error():
|
87
|
+
|
88
|
+
y_true = [3, -0.5, 2, 7]
|
89
|
+
y_pred = [2.5, 0.0, 2, 8]
|
90
|
+
result = root_mean_squared_error(y_true, y_pred)
|
91
|
+
assert result == pytest.approx(0.612, rel=1e-2)
|
92
|
+
|
93
|
+
def test_adjusted_r2_score():
|
94
|
+
|
95
|
+
y_true = [3, -0.5, 2, 7]
|
96
|
+
y_pred = [2.5, 0.0, 2, 8]
|
97
|
+
n_features = 2
|
98
|
+
result = adjusted_r2_score(y_true, y_pred, n_features)
|
99
|
+
assert result == pytest.approx(0.8458, rel=1e-2)
|
100
|
+
|
Moral88-0.9.0.dist-info/RECORD
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
Moral88/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
Moral88/regression.py,sha256=vxkrbEvGW9Lys7TKVsM1SNqRtqBfCQjfUcm_hmyyGjU,16293
|
3
|
-
Moral88/segmentation.py,sha256=v0yqxdrKbM9LM7wVKLjJ4HrhrSrilNNeWS6-oK_27Ag,1363
|
4
|
-
Moral88-0.9.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
Moral88-0.9.0.dist-info/METADATA,sha256=RM87uHl8gYyDJmAre7sS49ZEAbkgYdmWcgwtNprcdEY,407
|
6
|
-
Moral88-0.9.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
7
|
-
Moral88-0.9.0.dist-info/top_level.txt,sha256=-dyn5iTprnSUHbtMpvRO-prJsIoaRxao7wlfCHLSsv4,8
|
8
|
-
Moral88-0.9.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|