Compare commits
11 Commits
fix/SIENTI
...
fix/SIENTI
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52f9f2724b | ||
|
|
f18f519adb | ||
| c9c85e35db | |||
|
|
9fa1430d75 | ||
|
|
fcb9bb263b | ||
|
|
2678c5d638 | ||
|
|
959a430314 | ||
|
|
5043137f97 | ||
|
|
0920d8138e | ||
|
|
8c81aa68bb | ||
|
|
9c05871fcf |
@@ -357,7 +357,8 @@ class DataManagerRepository(SientiaMonitoring):
|
|||||||
This helper mirrors the previous TrainingRepository.after_train_calculation
|
This helper mirrors the previous TrainingRepository.after_train_calculation
|
||||||
behavior, assuming that predictions (y_pred/y_train_pred) are already on the
|
behavior, assuming that predictions (y_pred/y_train_pred) are already on the
|
||||||
correct scale for metric calculation (any scaling is handled inside the
|
correct scale for metric calculation (any scaling is handled inside the
|
||||||
model wrapper).
|
model wrapper). Pairs with a NaN target or a NaN prediction are left out of
|
||||||
|
mse/mae/r2. With a single valid pair left, r2_val is NaN (R² needs 2 samples).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tmr: Training result containing:
|
tmr: Training result containing:
|
||||||
@@ -368,6 +369,10 @@ class DataManagerRepository(SientiaMonitoring):
|
|||||||
|
|
||||||
Return:
|
Return:
|
||||||
TrainModelResult: Same object with mse_val, mae_val and r2_val set.
|
TrainModelResult: Same object with mse_val, mae_val and r2_val set.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If y_pred is not set, val_data and y_pred share no indices,
|
||||||
|
or no pair has both a valid target and a valid prediction.
|
||||||
"""
|
"""
|
||||||
if tmr.y_pred is None:
|
if tmr.y_pred is None:
|
||||||
raise ValueError('y_pred must be set before computing regression metrics')
|
raise ValueError('y_pred must be set before computing regression metrics')
|
||||||
@@ -405,6 +410,24 @@ class DataManagerRepository(SientiaMonitoring):
|
|||||||
y_true_val = y_true_val.loc[common_index]
|
y_true_val = y_true_val.loc[common_index]
|
||||||
y_pred_val = y_pred_val.loc[common_index]
|
y_pred_val = y_pred_val.loc[common_index]
|
||||||
|
|
||||||
|
# NaN target, or NaN prediction for rows with NaN features, carries no error signal
|
||||||
|
# (SIENTIAPDE-2215).
|
||||||
|
valid = y_true_val.notna() & y_pred_val.notna()
|
||||||
|
if not valid.any():
|
||||||
|
raise ValueError(
|
||||||
|
'No validation rows with both target and prediction: all '
|
||||||
|
f'{len(valid)} overlapping rows have NaN'
|
||||||
|
)
|
||||||
|
if not valid.all():
|
||||||
|
n_valid = int(valid.sum())
|
||||||
|
message = (
|
||||||
|
f'Ignoring {len(valid) - n_valid} of {len(valid)} validation row(s) with NaN '
|
||||||
|
f'target or prediction; regression metrics use the remaining {n_valid}'
|
||||||
|
)
|
||||||
|
self.warning(message, metadata)
|
||||||
|
y_true_val = y_true_val[valid]
|
||||||
|
y_pred_val = y_pred_val[valid]
|
||||||
|
|
||||||
# Metrics helpers already round to 2 decimals.
|
# Metrics helpers already round to 2 decimals.
|
||||||
tmr.mse_val = mse(y_true_val, y_pred_val)
|
tmr.mse_val = mse(y_true_val, y_pred_val)
|
||||||
tmr.mae_val = mae(y_true_val, y_pred_val)
|
tmr.mae_val = mae(y_true_val, y_pred_val)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "model-manager"
|
name = "model-manager"
|
||||||
version = "2.0.3"
|
version = "2.0.4"
|
||||||
description = "Sientia DataOps Model Manager - ML Model Orchestration System"
|
description = "Sientia DataOps Model Manager - ML Model Orchestration System"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -309,6 +309,46 @@ def test_compute_regression_metrics_no_overlap():
|
|||||||
repo.compute_regression_metrics(tmr, MagicMock())
|
repo.compute_regression_metrics(tmr, MagicMock())
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_regression_metrics_ignores_nan_pairs():
|
||||||
|
"""NaN target or NaN prediction rows are left out of mse/mae/r2 (SIENTIAPDE-2215)."""
|
||||||
|
repo = dmr.DataManagerRepository(MagicMock())
|
||||||
|
p = TrainModelParams.from_dict(_minimal_dict_for_prepare())
|
||||||
|
idx = pd.Index([0, 1, 2, 3])
|
||||||
|
tmr = TrainModelResult(
|
||||||
|
params=p,
|
||||||
|
train_data=pd.DataFrame({'t': [1.0, 2.0, 3.0, 4.0]}, index=idx),
|
||||||
|
val_data=pd.DataFrame({'t': [1.0, np.nan, 3.0, 4.0]}, index=idx),
|
||||||
|
y_pred=pd.DataFrame({'p': [1.0, 2.0, np.nan, 4.0]}, index=idx),
|
||||||
|
)
|
||||||
|
|
||||||
|
wrapper = MagicMock()
|
||||||
|
wrapper.model = object() # no sklearn .regr -> equation extraction is skipped
|
||||||
|
with patch.object(repo, 'warning') as warn:
|
||||||
|
out = repo.compute_regression_metrics(tmr, wrapper)
|
||||||
|
|
||||||
|
assert out.mse_val == pytest.approx(0.0)
|
||||||
|
assert out.mae_val == pytest.approx(0.0)
|
||||||
|
assert out.r2_val == pytest.approx(1.0)
|
||||||
|
warn.assert_called_once()
|
||||||
|
assert 'Ignoring 2 of 4' in warn.call_args.args[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_regression_metrics_all_nan_pairs_raises():
|
||||||
|
repo = dmr.DataManagerRepository(MagicMock())
|
||||||
|
p = TrainModelParams.from_dict(_minimal_dict_for_prepare())
|
||||||
|
idx = pd.Index([0, 1])
|
||||||
|
tmr = TrainModelResult(
|
||||||
|
params=p,
|
||||||
|
train_data=pd.DataFrame({'t': [1.0, 2.0]}, index=idx),
|
||||||
|
val_data=pd.DataFrame({'t': [1.0, 2.0]}, index=idx),
|
||||||
|
y_pred=pd.DataFrame({'p': [np.nan, np.nan]}, index=idx),
|
||||||
|
)
|
||||||
|
wrapper = MagicMock()
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match='No validation rows with both target and prediction'):
|
||||||
|
repo.compute_regression_metrics(tmr, wrapper)
|
||||||
|
|
||||||
|
|
||||||
def test_compute_regression_metrics_linear_equation():
|
def test_compute_regression_metrics_linear_equation():
|
||||||
repo = dmr.DataManagerRepository(MagicMock())
|
repo = dmr.DataManagerRepository(MagicMock())
|
||||||
p = TrainModelParams.from_dict(_minimal_dict_for_prepare())
|
p = TrainModelParams.from_dict(_minimal_dict_for_prepare())
|
||||||
|
|||||||
Reference in New Issue
Block a user