Add 8.19.0 Snapshot to Build and Fix test_ml_model Tests to Normalized Expected Scores if Min Score is Less Than Zero (#783)

* backport #777 to 8.x

* forgot to return bounds from EnsembleModel
This commit is contained in:
Mark J. Hoy 2025-04-24 12:35:52 -04:00 committed by GitHub
parent 70233ec27d
commit 06cfa598d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 1 deletions

View File

@ -48,4 +48,5 @@ steps:
- '8.18.0' - '8.18.0'
- '8.16.6' - '8.16.6'
- '8.14.1' - '8.14.1'
- '8.19.0-SNAPSHOT'
command: ./.buildkite/run-tests command: ./.buildkite/run-tests

View File

@ -19,7 +19,7 @@ import base64
import gzip import gzip
import json import json
from abc import ABC from abc import ABC
from typing import Any, Dict, List, Optional, Sequence from typing import Any, Dict, List, Optional, Sequence, Tuple
def add_if_exists(d: Dict[str, Any], k: str, v: Any) -> None: def add_if_exists(d: Dict[str, Any], k: str, v: Any) -> None:
@ -58,6 +58,9 @@ class ModelSerializer(ABC):
"ascii" "ascii"
) )
def bounds(self) -> Tuple[float, float]:
raise NotImplementedError
class TreeNode: class TreeNode:
def __init__( def __init__(
@ -129,6 +132,14 @@ class Tree(ModelSerializer):
add_if_exists(d, "tree_structure", [t.to_dict() for t in self._tree_structure]) add_if_exists(d, "tree_structure", [t.to_dict() for t in self._tree_structure])
return {"tree": d} return {"tree": d}
def bounds(self) -> Tuple[float, float]:
leaf_values = [
tree_node._leaf_value[0]
for tree_node in self._tree_structure
if tree_node._leaf_value is not None
]
return min(leaf_values), max(leaf_values)
class Ensemble(ModelSerializer): class Ensemble(ModelSerializer):
def __init__( def __init__(
@ -158,3 +169,9 @@ class Ensemble(ModelSerializer):
add_if_exists(d, "classification_weights", self._classification_weights) add_if_exists(d, "classification_weights", self._classification_weights)
add_if_exists(d, "aggregate_output", self._output_aggregator) add_if_exists(d, "aggregate_output", self._output_aggregator)
return {"ensemble": d} return {"ensemble": d}
def bounds(self) -> Tuple[float, float]:
min_bound, max_bound = tuple(
map(sum, zip(*[model.bounds() for model in self._trained_models]))
)
return min_bound, max_bound

View File

@ -24,6 +24,7 @@ import pytest
import eland as ed import eland as ed
from eland.ml import MLModel from eland.ml import MLModel
from eland.ml.ltr import FeatureLogger, LTRModelConfig, QueryFeatureExtractor from eland.ml.ltr import FeatureLogger, LTRModelConfig, QueryFeatureExtractor
from eland.ml.transformers import get_model_transformer
from tests import ( from tests import (
ES_IS_SERVERLESS, ES_IS_SERVERLESS,
ES_TEST_CLIENT, ES_TEST_CLIENT,
@ -328,6 +329,34 @@ class TestMLModel:
# Clean up # Clean up
es_model.delete_model() es_model.delete_model()
def _normalize_ltr_score_from_XGBRanker(self, ranker, ltr_model_config, scores):
"""Normalize the scores of an XGBRanker model as ES implementation of LTR would do.
Parameters
----------
ranker : XGBRanker
The XGBRanker model to retrieve the minimum score from.
ltr_model_config : LTRModelConfig
LTR model config.
Returns
-------
scores : List[float]
Normalized scores for the model.
"""
if (ES_VERSION[0] == 8 and ES_VERSION >= (8, 19)) or ES_IS_SERVERLESS:
# In 8.19 and 9.1, the scores are normalized if there are negative scores
min_model_score, _ = (
get_model_transformer(
ranker, feature_names=ltr_model_config.feature_names
)
.transform()
.bounds()
)
if min_model_score < 0:
scores = [score - min_model_score for score in scores]
return scores
@requires_elasticsearch_version((8, 12)) @requires_elasticsearch_version((8, 12))
@requires_xgboost @requires_xgboost
@pytest.mark.parametrize("compress_model_definition", [True, False]) @pytest.mark.parametrize("compress_model_definition", [True, False])
@ -439,6 +468,11 @@ class TestMLModel:
], ],
reverse=True, reverse=True,
) )
expected_scores = self._normalize_ltr_score_from_XGBRanker(
ranker, ltr_model_config, expected_scores
)
np.testing.assert_almost_equal(expected_scores, doc_scores, decimal=2) np.testing.assert_almost_equal(expected_scores, doc_scores, decimal=2)
# Verify prediction is not supported for LTR # Verify prediction is not supported for LTR