validmind 2.0.0__py3-none-any.whl → 2.0.7__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.
- validmind/__init__.py +4 -1
- validmind/__version__.py +1 -1
- validmind/ai.py +197 -0
- validmind/api_client.py +16 -4
- validmind/client.py +23 -3
- validmind/datasets/classification/customer_churn.py +2 -2
- validmind/datasets/nlp/__init__.py +5 -0
- validmind/datasets/nlp/cnn_dailymail.py +98 -0
- validmind/datasets/nlp/datasets/cnn_dailymail_100_with_predictions.csv +255 -0
- validmind/datasets/nlp/datasets/cnn_dailymail_500_with_predictions.csv +1277 -0
- validmind/datasets/nlp/datasets/sentiments_with_predictions.csv +4847 -0
- validmind/errors.py +11 -1
- validmind/models/huggingface.py +2 -2
- validmind/models/pytorch.py +3 -3
- validmind/models/sklearn.py +4 -4
- validmind/tests/__init__.py +47 -9
- validmind/tests/data_validation/DatasetDescription.py +0 -1
- validmind/tests/data_validation/PiTCreditScoresHistogram.py +8 -3
- validmind/tests/data_validation/TargetRateBarPlots.py +3 -1
- validmind/tests/data_validation/nlp/StopWords.py +1 -6
- validmind/tests/data_validation/nlp/TextDescription.py +20 -9
- validmind/tests/decorator.py +189 -0
- validmind/tests/model_validation/MeteorScore.py +92 -0
- validmind/tests/model_validation/RegardHistogram.py +5 -6
- validmind/tests/model_validation/RegardScore.py +3 -5
- validmind/tests/model_validation/RougeMetrics.py +6 -4
- validmind/tests/model_validation/SelfCheckNLIScore.py +112 -0
- validmind/tests/model_validation/embeddings/DescriptiveAnalytics.py +17 -22
- validmind/tests/model_validation/sklearn/ClassifierPerformance.py +3 -1
- validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +30 -4
- validmind/tests/model_validation/sklearn/TrainingTestDegradation.py +9 -3
- validmind/tests/model_validation/statsmodels/ADF.py +27 -1
- validmind/tests/model_validation/statsmodels/RegressionModelsPerformance.py +1 -1
- validmind/tests/model_validation/statsmodels/ResidualsVisualInspection.py +1 -13
- validmind/tests/prompt_validation/ai_powered_test.py +2 -0
- validmind/unit_metrics/__init__.py +0 -2
- validmind/unit_metrics/composite.py +275 -0
- validmind/unit_metrics/regression/GiniCoefficient.py +39 -0
- validmind/unit_metrics/regression/HuberLoss.py +27 -0
- validmind/unit_metrics/regression/KolmogorovSmirnovStatistic.py +36 -0
- validmind/unit_metrics/regression/MeanAbsolutePercentageError.py +22 -0
- validmind/unit_metrics/regression/MeanBiasDeviation.py +22 -0
- validmind/unit_metrics/regression/QuantileLoss.py +25 -0
- validmind/unit_metrics/regression/sklearn/AdjustedRSquaredScore.py +27 -0
- validmind/unit_metrics/regression/sklearn/MeanAbsoluteError.py +22 -0
- validmind/unit_metrics/regression/sklearn/MeanSquaredError.py +22 -0
- validmind/unit_metrics/regression/sklearn/RSquaredScore.py +22 -0
- validmind/unit_metrics/regression/sklearn/RootMeanSquaredError.py +23 -0
- validmind/unit_metrics/sklearn/classification/Accuracy.py +2 -0
- validmind/unit_metrics/sklearn/classification/F1.py +2 -0
- validmind/unit_metrics/sklearn/classification/Precision.py +2 -0
- validmind/unit_metrics/sklearn/classification/ROC_AUC.py +2 -0
- validmind/unit_metrics/sklearn/classification/Recall.py +2 -0
- validmind/utils.py +17 -1
- validmind/vm_models/dataset.py +376 -21
- validmind/vm_models/figure.py +52 -17
- validmind/vm_models/test/metric.py +33 -30
- validmind/vm_models/test/output_template.py +0 -27
- validmind/vm_models/test/result_wrapper.py +57 -24
- validmind/vm_models/test/test.py +2 -1
- validmind/vm_models/test/threshold_test.py +24 -13
- validmind/vm_models/test_context.py +7 -0
- validmind/vm_models/test_suite/runner.py +1 -1
- validmind/vm_models/test_suite/test.py +1 -1
- {validmind-2.0.0.dist-info → validmind-2.0.7.dist-info}/METADATA +9 -13
- {validmind-2.0.0.dist-info → validmind-2.0.7.dist-info}/RECORD +69 -48
- validmind-2.0.7.dist-info/entry_points.txt +3 -0
- {validmind-2.0.0.dist-info → validmind-2.0.7.dist-info}/LICENSE +0 -0
- {validmind-2.0.0.dist-info → validmind-2.0.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,275 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
import ast
|
6
|
+
import inspect
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from typing import List
|
9
|
+
from uuid import uuid4
|
10
|
+
|
11
|
+
from ..errors import LoadTestError
|
12
|
+
from ..logging import get_logger
|
13
|
+
from ..utils import clean_docstring, run_async, test_id_to_name
|
14
|
+
from ..vm_models.test.metric import Metric
|
15
|
+
from ..vm_models.test.metric_result import MetricResult
|
16
|
+
from ..vm_models.test.result_summary import ResultSummary, ResultTable
|
17
|
+
from ..vm_models.test.result_wrapper import MetricResultWrapper
|
18
|
+
from . import _get_metric_class, run_metric
|
19
|
+
|
20
|
+
logger = get_logger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
def _extract_class_methods(cls):
|
24
|
+
source = inspect.getsource(cls)
|
25
|
+
tree = ast.parse(source)
|
26
|
+
|
27
|
+
class MethodVisitor(ast.NodeVisitor):
|
28
|
+
def __init__(self):
|
29
|
+
self.methods = {}
|
30
|
+
|
31
|
+
def visit_FunctionDef(self, node):
|
32
|
+
self.methods[node.name] = node
|
33
|
+
self.generic_visit(node)
|
34
|
+
|
35
|
+
visitor = MethodVisitor()
|
36
|
+
visitor.visit(tree)
|
37
|
+
|
38
|
+
return visitor.methods
|
39
|
+
|
40
|
+
|
41
|
+
def _extract_required_inputs(cls):
|
42
|
+
methods = _extract_class_methods(cls)
|
43
|
+
|
44
|
+
class Visitor(ast.NodeVisitor):
|
45
|
+
def __init__(self):
|
46
|
+
self.properties = set()
|
47
|
+
self.visited_methods = set()
|
48
|
+
|
49
|
+
def visit_Attribute(self, node):
|
50
|
+
if isinstance(node.value, ast.Attribute) and node.value.attr == "inputs":
|
51
|
+
self.properties.add(node.attr)
|
52
|
+
|
53
|
+
self.generic_visit(node)
|
54
|
+
|
55
|
+
def visit_Call(self, node):
|
56
|
+
if isinstance(node.func, ast.Attribute) and isinstance(
|
57
|
+
node.func.value, ast.Name
|
58
|
+
):
|
59
|
+
if node.func.value.id == "self" and node.func.attr in methods:
|
60
|
+
method_name = node.func.attr
|
61
|
+
|
62
|
+
if method_name not in self.visited_methods:
|
63
|
+
self.visited_methods.add(method_name)
|
64
|
+
self.visit(methods[method_name])
|
65
|
+
|
66
|
+
self.generic_visit(node)
|
67
|
+
|
68
|
+
visitor = Visitor()
|
69
|
+
visitor.visit(methods["run"])
|
70
|
+
|
71
|
+
return visitor.properties
|
72
|
+
|
73
|
+
|
74
|
+
@dataclass
|
75
|
+
class CompositeMetric(Metric):
|
76
|
+
unit_metrics: List[str] = None
|
77
|
+
|
78
|
+
def __post_init__(self):
|
79
|
+
if self._unit_metrics:
|
80
|
+
self.unit_metrics = self._unit_metrics
|
81
|
+
elif self.unit_metrics is None:
|
82
|
+
raise ValueError("unit_metrics must be provided")
|
83
|
+
|
84
|
+
if hasattr(self, "_output_template") and self._output_template:
|
85
|
+
self.output_template = self._output_template
|
86
|
+
|
87
|
+
def run(self):
|
88
|
+
self.result = run_metrics(
|
89
|
+
test_id=self.test_id,
|
90
|
+
metric_ids=self.unit_metrics,
|
91
|
+
description=self.description(),
|
92
|
+
inputs=self._get_input_dict(),
|
93
|
+
params=self.params,
|
94
|
+
output_template=self.output_template,
|
95
|
+
show=False,
|
96
|
+
)
|
97
|
+
|
98
|
+
return self.result
|
99
|
+
|
100
|
+
def summary(self, result: dict):
|
101
|
+
return ResultSummary(results=[ResultTable(data=[result])])
|
102
|
+
|
103
|
+
|
104
|
+
def load_composite_metric(
|
105
|
+
test_id: str = None,
|
106
|
+
metric_name: str = None,
|
107
|
+
unit_metrics: List[str] = None,
|
108
|
+
output_template: str = None,
|
109
|
+
) -> CompositeMetric:
|
110
|
+
# this function can either create a composite metric from a list of unit metrics or
|
111
|
+
# load a stored composite metric based on the test id
|
112
|
+
|
113
|
+
# TODO: figure out this circular import thing:
|
114
|
+
from ..api_client import get_metadata
|
115
|
+
|
116
|
+
if test_id:
|
117
|
+
# get the unit metric ids and output template (if any) from the metadata
|
118
|
+
try:
|
119
|
+
unit_metrics = run_async(
|
120
|
+
get_metadata, f"composite_metric_def:{test_id}:unit_metrics"
|
121
|
+
)["json"]
|
122
|
+
output_template = run_async(
|
123
|
+
get_metadata, f"composite_metric_def:{test_id}:output_template"
|
124
|
+
)["json"]["output_template"]
|
125
|
+
except Exception:
|
126
|
+
logger.error(f"Could not load composite metric {test_id}")
|
127
|
+
raise LoadTestError(f"Could not load composite metric {test_id}")
|
128
|
+
|
129
|
+
description = f"""
|
130
|
+
Composite metric built from the following unit metrics:
|
131
|
+
{', '.join([metric_id.split('.')[-1] for metric_id in unit_metrics])}
|
132
|
+
"""
|
133
|
+
|
134
|
+
class_def = type(
|
135
|
+
test_id.split(".")[-1] if test_id else metric_name,
|
136
|
+
(CompositeMetric,),
|
137
|
+
{
|
138
|
+
"__doc__": description,
|
139
|
+
"_unit_metrics": unit_metrics,
|
140
|
+
"_output_template": output_template,
|
141
|
+
},
|
142
|
+
)
|
143
|
+
|
144
|
+
required_inputs = set()
|
145
|
+
for metric_id in unit_metrics:
|
146
|
+
metric_cls = _get_metric_class(metric_id)
|
147
|
+
# required_inputs.update(_extract_required_inputs(metric_cls))
|
148
|
+
required_inputs.update(metric_cls.required_inputs or [])
|
149
|
+
|
150
|
+
class_def.required_inputs = list(required_inputs)
|
151
|
+
|
152
|
+
return class_def
|
153
|
+
|
154
|
+
|
155
|
+
def run_metrics(
|
156
|
+
name: str = None,
|
157
|
+
metric_ids: List[str] = None,
|
158
|
+
description: str = None,
|
159
|
+
output_template: str = None,
|
160
|
+
inputs: dict = None,
|
161
|
+
params: dict = None,
|
162
|
+
test_id: str = None,
|
163
|
+
show: bool = True,
|
164
|
+
) -> MetricResultWrapper:
|
165
|
+
"""Run a composite metric
|
166
|
+
|
167
|
+
Composite metrics are metrics that are composed of multiple unit metrics. This
|
168
|
+
works by running individual unit metrics and then combining the results into a
|
169
|
+
single "MetricResult" object that can be logged and displayed just like any other
|
170
|
+
metric result. The special thing about composite metrics is that when they are
|
171
|
+
logged to the platform, metadata describing the unit metrics and output template
|
172
|
+
used to generate the composite metric is also logged. This means that by grabbing
|
173
|
+
the metadata for a composite metric (identified by the test ID
|
174
|
+
`validmind.composite_metric.<name>`) the framework can rebuild and rerun it at
|
175
|
+
any time.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
name (str, optional): Name of the composite metric. Required if test_id is not
|
179
|
+
provided. Defaults to None.
|
180
|
+
metric_ids (list[str]): List of unit metric IDs to run. Required.
|
181
|
+
description (str, optional): Description of the composite metric. Defaults to
|
182
|
+
None.
|
183
|
+
output_template (_type_, optional): Output template to customize the result
|
184
|
+
table.
|
185
|
+
inputs (_type_, optional): Inputs to pass to the unit metrics. Defaults to None
|
186
|
+
params (_type_, optional): Parameters to pass to the unit metrics. Defaults to
|
187
|
+
None.
|
188
|
+
test_id (str, optional): Test ID of the composite metric. Required if name is
|
189
|
+
not provided. Defaults to None.
|
190
|
+
show (bool, optional): Whether to show the result immediately. Defaults to True
|
191
|
+
|
192
|
+
Raises:
|
193
|
+
ValueError: If metric_ids is not provided
|
194
|
+
ValueError: If name or key is not provided
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
MetricResultWrapper: The result wrapper object
|
198
|
+
"""
|
199
|
+
if not metric_ids:
|
200
|
+
raise ValueError("metric_ids must be provided")
|
201
|
+
|
202
|
+
if not name and not test_id:
|
203
|
+
raise ValueError("name or key must be provided")
|
204
|
+
|
205
|
+
# if name is provided, make sure to squash it into a camel case string
|
206
|
+
if name:
|
207
|
+
name = "".join(word[0].upper() + word[1:] for word in name.split())
|
208
|
+
|
209
|
+
results = {}
|
210
|
+
|
211
|
+
for metric_id in metric_ids:
|
212
|
+
result = run_metric(
|
213
|
+
metric_id=metric_id,
|
214
|
+
inputs=inputs,
|
215
|
+
params=params,
|
216
|
+
)
|
217
|
+
results[list(result.summary.keys())[0]] = result.value
|
218
|
+
|
219
|
+
test_id = f"validmind.composite_metric.{name}" if not test_id else test_id
|
220
|
+
|
221
|
+
if not output_template:
|
222
|
+
|
223
|
+
def row(key):
|
224
|
+
return f"""
|
225
|
+
<tr>
|
226
|
+
<td><strong>{key.upper()}</strong></td>
|
227
|
+
<td>{{{{ value['{key}'] | number }}}}</td>
|
228
|
+
</tr>
|
229
|
+
"""
|
230
|
+
|
231
|
+
output_template = f"""
|
232
|
+
<h1{test_id_to_name(test_id)}</h1>
|
233
|
+
<table>
|
234
|
+
<thead>
|
235
|
+
<tr>
|
236
|
+
<th>Metric</th>
|
237
|
+
<th>Value</th>
|
238
|
+
</tr>
|
239
|
+
</thead>
|
240
|
+
<tbody>
|
241
|
+
{"".join([row(key) for key in results.keys()])}
|
242
|
+
</tbody>
|
243
|
+
</table>
|
244
|
+
"""
|
245
|
+
|
246
|
+
result_wrapper = MetricResultWrapper(
|
247
|
+
result_id=test_id,
|
248
|
+
result_metadata=[
|
249
|
+
{
|
250
|
+
"content_id": f"metric_description:{test_id}",
|
251
|
+
"text": clean_docstring(description),
|
252
|
+
},
|
253
|
+
{
|
254
|
+
"content_id": f"composite_metric_def:{test_id}:unit_metrics",
|
255
|
+
"json": metric_ids,
|
256
|
+
},
|
257
|
+
{
|
258
|
+
"content_id": f"composite_metric_def:{test_id}:output_template",
|
259
|
+
"json": {"output_template": output_template},
|
260
|
+
},
|
261
|
+
],
|
262
|
+
inputs=list(inputs.keys()),
|
263
|
+
output_template=output_template,
|
264
|
+
metric=MetricResult(
|
265
|
+
key=test_id,
|
266
|
+
ref_id=str(uuid4()),
|
267
|
+
value=results,
|
268
|
+
summary=ResultSummary(results=[ResultTable(data=[results])]),
|
269
|
+
),
|
270
|
+
)
|
271
|
+
|
272
|
+
if show:
|
273
|
+
result_wrapper.show()
|
274
|
+
|
275
|
+
return result_wrapper
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class GiniCoefficient(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
# Sort true values and corresponding predicted values
|
21
|
+
idx = np.argsort(y_true)
|
22
|
+
y_true_sorted = y_true[idx]
|
23
|
+
y_pred_sorted = y_pred[idx]
|
24
|
+
|
25
|
+
# Compute cumulative sums
|
26
|
+
cumsum_true = np.cumsum(y_true_sorted)
|
27
|
+
cumsum_pred = np.cumsum(y_pred_sorted)
|
28
|
+
|
29
|
+
# Normalize cumulative sums
|
30
|
+
cumsum_true_norm = cumsum_true / np.max(cumsum_true)
|
31
|
+
cumsum_pred_norm = cumsum_pred / np.max(cumsum_pred)
|
32
|
+
|
33
|
+
# Compute area under the Lorenz curve
|
34
|
+
area_lorenz = np.trapz(cumsum_pred_norm, x=cumsum_true_norm)
|
35
|
+
|
36
|
+
# Compute Gini coefficient
|
37
|
+
gini_coeff = 1 - 2 * area_lorenz
|
38
|
+
|
39
|
+
return self.cache_results(metric_value=gini_coeff)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class HuberLoss(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
# delta - Threshold for the squared error to be linear or quadratic.
|
21
|
+
delta = 1.0
|
22
|
+
error = y_true - y_pred
|
23
|
+
quadratic_part = np.minimum(np.abs(error), delta)
|
24
|
+
linear_part = np.abs(error) - quadratic_part
|
25
|
+
value = np.mean(0.5 * quadratic_part**2 + delta * linear_part)
|
26
|
+
|
27
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class KolmogorovSmirnovStatistic(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y.flatten()
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
# Sort true values and corresponding predicted values
|
21
|
+
idx_true = np.argsort(y_true)
|
22
|
+
idx_pred = np.argsort(y_pred)
|
23
|
+
y_true_sorted = y_true[idx_true]
|
24
|
+
y_pred_sorted = y_pred[idx_pred]
|
25
|
+
|
26
|
+
# Compute cumulative distribution functions (CDFs)
|
27
|
+
cdf_true = np.arange(1, len(y_true_sorted) + 1) / len(y_true_sorted)
|
28
|
+
cdf_pred = np.arange(1, len(y_pred_sorted) + 1) / len(y_pred_sorted)
|
29
|
+
|
30
|
+
# Compute absolute differences between CDFs
|
31
|
+
diff_cdf = np.abs(cdf_true - cdf_pred)
|
32
|
+
|
33
|
+
# Find maximum absolute difference
|
34
|
+
ks_statistic = np.max(diff_cdf)
|
35
|
+
|
36
|
+
return self.cache_results(metric_value=ks_statistic)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class MeanAbsolutePercentageError(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
value = np.mean(np.abs((y_true - y_pred) / y_true)) * 100
|
21
|
+
|
22
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class MeanBiasDeviation(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
value = np.mean(y_pred - y_true)
|
21
|
+
|
22
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class QuantileLoss(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
error = y_true - y_pred
|
21
|
+
# Quantile value (between 0 and 1).
|
22
|
+
quantile = 0.5
|
23
|
+
value = np.mean(np.maximum(quantile * error, (quantile - 1) * error))
|
24
|
+
|
25
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import sklearn.metrics as metrics
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class AdjustedRSquaredScore(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
X_columns = self.inputs.dataset.get_features_columns()
|
21
|
+
row_count = len(y_true)
|
22
|
+
feature_count = len(X_columns)
|
23
|
+
value = 1 - (1 - metrics.r2_score(y_true, y_pred)) * (row_count - 1) / (
|
24
|
+
row_count - feature_count
|
25
|
+
)
|
26
|
+
|
27
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
from sklearn.metrics import mean_absolute_error
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class MeanAbsoluteError(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
value = mean_absolute_error(y_true, y_pred, **self.params)
|
21
|
+
|
22
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
from sklearn.metrics import mean_squared_error
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class MeanSquaredError(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
value = mean_squared_error(y_true, y_pred, **self.params)
|
21
|
+
|
22
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import sklearn.metrics as metrics
|
8
|
+
|
9
|
+
from validmind.vm_models import UnitMetric
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class RSquaredScore(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
16
|
+
def run(self):
|
17
|
+
y_true = self.inputs.dataset.y
|
18
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
19
|
+
|
20
|
+
value = metrics.r2_score(y_true, y_pred)
|
21
|
+
|
22
|
+
return self.cache_results(metric_value=value)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright © 2023-2024 ValidMind Inc. All rights reserved.
|
2
|
+
# See the LICENSE file in the root of this repository for details.
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
from sklearn.metrics import mean_squared_error
|
9
|
+
|
10
|
+
from validmind.vm_models import UnitMetric
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class RootMeanSquaredError(UnitMetric):
|
15
|
+
required_inputs = ["dataset", "model"]
|
16
|
+
|
17
|
+
def run(self):
|
18
|
+
y_true = self.inputs.dataset.y
|
19
|
+
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
20
|
+
|
21
|
+
value = np.sqrt(mean_squared_error(y_true, y_pred, **self.params))
|
22
|
+
|
23
|
+
return self.cache_results(metric_value=value)
|
@@ -11,6 +11,8 @@ from validmind.vm_models import UnitMetric
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class Accuracy(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
14
16
|
def run(self):
|
15
17
|
y_true = self.inputs.dataset.y
|
16
18
|
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
@@ -11,6 +11,8 @@ from validmind.vm_models import UnitMetric
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class Precision(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
14
16
|
def run(self):
|
15
17
|
y_true = self.inputs.dataset.y
|
16
18
|
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
@@ -11,6 +11,8 @@ from validmind.vm_models import UnitMetric
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class ROC_AUC(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
14
16
|
def run(self):
|
15
17
|
y_true = self.inputs.dataset.y
|
16
18
|
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
@@ -11,6 +11,8 @@ from validmind.vm_models import UnitMetric
|
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class Recall(UnitMetric):
|
14
|
+
required_inputs = ["dataset", "model"]
|
15
|
+
|
14
16
|
def run(self):
|
15
17
|
y_true = self.inputs.dataset.y
|
16
18
|
y_pred = self.inputs.dataset.y_pred(model_id=self.inputs.model.input_id)
|
validmind/utils.py
CHANGED
@@ -85,6 +85,8 @@ def nan_to_none(obj):
|
|
85
85
|
|
86
86
|
class NumpyEncoder(json.JSONEncoder):
|
87
87
|
def default(self, obj):
|
88
|
+
if isinstance(obj, pd.Interval):
|
89
|
+
return f"[{obj.left}, {obj.right}]"
|
88
90
|
if isinstance(obj, np.integer):
|
89
91
|
return int(obj)
|
90
92
|
if isinstance(obj, np.floating):
|
@@ -253,7 +255,21 @@ def clean_docstring(docstring: str) -> str:
|
|
253
255
|
# Join paragraphs with double newlines for markdown
|
254
256
|
description = "\n\n".join(paragraphs)
|
255
257
|
|
256
|
-
|
258
|
+
lines = description.split("\n")
|
259
|
+
in_bullet_list = False
|
260
|
+
for i, line in enumerate([line for line in lines]):
|
261
|
+
if line.strip().startswith("-") and not in_bullet_list:
|
262
|
+
if lines[i - 1] != "":
|
263
|
+
lines[i] = "\n" + line
|
264
|
+
|
265
|
+
in_bullet_list = True
|
266
|
+
continue
|
267
|
+
elif line.strip().startswith("-") and in_bullet_list:
|
268
|
+
continue
|
269
|
+
elif line.strip() == "" and in_bullet_list:
|
270
|
+
in_bullet_list = False
|
271
|
+
|
272
|
+
return "\n".join(lines)
|
257
273
|
|
258
274
|
|
259
275
|
def format_number(number):
|