diff --git a/src/winml/modelkit/eval/metrics/spearman_correlation.py b/src/winml/modelkit/eval/metrics/spearman_correlation.py index 7113c9f69..3d93825ab 100644 --- a/src/winml/modelkit/eval/metrics/spearman_correlation.py +++ b/src/winml/modelkit/eval/metrics/spearman_correlation.py @@ -11,9 +11,14 @@ from __future__ import annotations +import logging +import math from typing import Any +logger = logging.getLogger(__name__) + + class SpearmanCorrelationMetric: """Spearman rank correlation between predicted and reference scores. @@ -54,4 +59,12 @@ def compute( ) corr, _ = spearmanr(predictions, references) + + if math.isnan(corr): + logger.warning( + "Spearman correlation is NaN. This typically means the model " + "produced constant outputs (zero variance). Returning 0.0.", + ) + corr = 0.0 + return {"cosine_spearman": round(float(corr) * 100, 4)} diff --git a/tests/unit/eval/test_feature_extraction_evaluator.py b/tests/unit/eval/test_feature_extraction_evaluator.py index a9abdadfd..518202c25 100644 --- a/tests/unit/eval/test_feature_extraction_evaluator.py +++ b/tests/unit/eval/test_feature_extraction_evaluator.py @@ -92,6 +92,12 @@ def test_score_range_is_0_to_100(self): result = metric.compute([0.1, 0.5, 0.9, 0.3], [1.0, 3.0, 5.0, 2.0]) assert -100.0 <= result["cosine_spearman"] <= 100.0 + def test_constant_predictions_returns_zero(self): + """Zero variance in predictions → correlation = 0.0, not NaN.""" + metric = SpearmanCorrelationMetric() + result = metric.compute([1.0] * 10, [0.5, 1.2, 3.1, 4.0, 2.0, 0.1, 3.5, 4.8, 1.7, 2.9]) + assert result["cosine_spearman"] == 0.0 + # --------------------------------------------------------------------------- # WinMLFeatureExtractionEvaluator._embed