From c11d251516141c3bec586448f620c1a217ad249c Mon Sep 17 00:00:00 2001 From: Caparrini Date: Tue, 22 Oct 2024 23:35:47 +0200 Subject: [PATCH] Fixes: hyperspace and its tests --- .../hyperparameter_space_service.py | 44 ++++++++++++++----- mloptimizer/domain/hyperspace/hyperspace.py | 4 ++ .../interfaces/api/hyperspace_builder.py | 18 +++++--- .../api/test_hyperparameter_space_builder.py | 20 ++++++--- 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/mloptimizer/application/hyperparameter_space_service.py b/mloptimizer/application/hyperparameter_space_service.py index cd7871b..04ac7b6 100644 --- a/mloptimizer/application/hyperparameter_space_service.py +++ b/mloptimizer/application/hyperparameter_space_service.py @@ -7,31 +7,51 @@ class HyperparameterSpaceService: """ Service to manage hyperparameter spaces. """ + base_config_path: str = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "infrastructure", "config", "hyperspace") - def __init__(self, base_config_path=None): - if base_config_path is None: - base_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "..", "..", "infrastructure", "config", "hyperspace") - self.base_config_path = base_config_path + @staticmethod + def _detect_library(estimator_class): + module_name = estimator_class.__module__ + supported_modules = ['sklearn', 'catboost', 'xgboost'] - def load_hyperparameter_space(self, estimator_class): + for module in supported_modules: + if module_name.startswith(module): + return module + + raise ValueError(f"Estimator class {estimator_class.__name__} not supported") + + def load_default_hyperparameter_space(self, estimator_class): + """ + Load a default hyperparameter space for an estimator from a JSON file. + """ + file_name = f"{estimator_class.__name__}_default_HyperparamSpace.json" + library = self._detect_library(estimator_class) + file_path = os.path.join(self.base_config_path, library, file_name) + + return self.load_hyperparameter_space(file_path) + + @staticmethod + def load_hyperparameter_space(hyperparam_space_json_path): """ Load a hyperparameter space for an estimator from a JSON file. """ - file_name = f"{estimator_class.__name__.lower()}_hyperparameter_space.json" - file_path = os.path.join(self.base_config_path, file_name) + if not os.path.exists(hyperparam_space_json_path): + raise FileNotFoundError(f"The file {hyperparam_space_json_path} does not exist") # Delegate to repository - hyperparam_data = HyperparameterSpaceRepository.load_json(file_path) + hyperparam_data = HyperparameterSpaceRepository.load_json(hyperparam_space_json_path) return HyperparameterSpace.from_json_data(hyperparam_data) - def save_hyperparameter_space(self, hyperparam_space: HyperparameterSpace, estimator_class, overwrite=False): + @staticmethod + def save_hyperparameter_space(hyperparam_space: HyperparameterSpace, + file_path, overwrite=False): """ Save a hyperparameter space for an estimator to a JSON file. """ - file_name = f"{estimator_class.__name__.lower()}_hyperparameter_space.json" - file_path = os.path.join(self.base_config_path, file_name) + if os.path.exists(file_path) and not overwrite: + raise FileExistsError(f"The file {file_path} already exists.") # Delegate to repository hyperparam_data = { diff --git a/mloptimizer/domain/hyperspace/hyperspace.py b/mloptimizer/domain/hyperspace/hyperspace.py index 34a3b70..a4ba13e 100644 --- a/mloptimizer/domain/hyperspace/hyperspace.py +++ b/mloptimizer/domain/hyperspace/hyperspace.py @@ -192,3 +192,7 @@ def __str__(self): def __repr__(self): return (f"HyperparameterSpace(fixed_hyperparams={self.fixed_hyperparams}, " f"evolvable_hyperparams={self.evolvable_hyperparams})") + + def __eq__(self, other): + return (self.fixed_hyperparams == other.fixed_hyperparams and + self.evolvable_hyperparams == other.evolvable_hyperparams) diff --git a/mloptimizer/interfaces/api/hyperspace_builder.py b/mloptimizer/interfaces/api/hyperspace_builder.py index 68259b7..d2dad6a 100644 --- a/mloptimizer/interfaces/api/hyperspace_builder.py +++ b/mloptimizer/interfaces/api/hyperspace_builder.py @@ -8,32 +8,36 @@ def __init__(self): self.evolvable_hyperparams = {} self.service = HyperparameterSpaceService() - def add_int_param(self, name, min_value, max_value): + def add_int_param(self, name: str, min_value: int, max_value: int): param = Hyperparam(name=name, min_value=min_value, max_value=max_value, hyperparam_type='int') self.evolvable_hyperparams[name] = param return self - def add_float_param(self, name, min_value, max_value, scale=100): + def add_float_param(self, name: str, min_value: int, max_value: int, scale=100): param = Hyperparam(name=name, min_value=min_value, max_value=max_value, hyperparam_type='float', scale=scale) self.evolvable_hyperparams[name] = param return self - def add_categorical_param(self, name, values): + def add_categorical_param(self, name: str, values: list): param = Hyperparam.from_values_list(name=name, values_str=values) self.evolvable_hyperparams[name] = param return self - def set_fixed_param(self, name, value): + def set_fixed_param(self, name: str, value): self.fixed_hyperparams[name] = value return self + def set_fixed_params(self, fixed_params: dict): + self.fixed_hyperparams = fixed_params + return self + def build(self): return HyperparameterSpace(fixed_hyperparams=self.fixed_hyperparams, evolvable_hyperparams=self.evolvable_hyperparams) def load_default_space(self, estimator_class): """Load a default hyperparameter space using the application service.""" - return self.service.load_hyperparameter_space(estimator_class) + return self.service.load_default_hyperparameter_space(estimator_class) - def save_space(self, hyperparam_space, estimator_class, overwrite=False): + def save_space(self, hyperparam_space, file_path, overwrite=False): """Save the hyperparameter space using the application service.""" - self.service.save_hyperparameter_space(hyperparam_space, estimator_class, overwrite) \ No newline at end of file + self.service.save_hyperparameter_space(hyperparam_space, file_path, overwrite) \ No newline at end of file diff --git a/mloptimizer/test/interfaces/api/test_hyperparameter_space_builder.py b/mloptimizer/test/interfaces/api/test_hyperparameter_space_builder.py index f358d32..7ade318 100644 --- a/mloptimizer/test/interfaces/api/test_hyperparameter_space_builder.py +++ b/mloptimizer/test/interfaces/api/test_hyperparameter_space_builder.py @@ -1,8 +1,9 @@ # mloptimizer/test/interfaces/api/test_hyperparameter_space_builder.py - +import os.path import pytest -from mloptimizer.domain.hyperspace import HyperparameterSpace, Hyperparam -from mloptimizer.application import HyperparameterSpaceService +from sklearn.tree import DecisionTreeClassifier + +from mloptimizer.domain.hyperspace import HyperparameterSpace from mloptimizer.interfaces.api import HyperparameterSpaceBuilder from mloptimizer.infrastructure.repositories import HyperparameterSpaceRepository @@ -20,7 +21,7 @@ def test_add_int_param(mock_service): def test_add_float_param(mock_service): builder = HyperparameterSpaceBuilder() - builder.add_float_param("param2", 0.1, 1.0) + builder.add_float_param("param2", 10, 100) assert "param2" in builder.evolvable_hyperparams @@ -35,6 +36,11 @@ def test_set_fixed_param(mock_service): builder.set_fixed_param("fixed_param1", 5) assert "fixed_param1" in builder.fixed_hyperparams +def test_set_fixed_params(mock_service): + builder = HyperparameterSpaceBuilder() + builder.set_fixed_params({"fixed_param1": 5, "fixed_param2": 10}) + assert "fixed_param1" in builder.fixed_hyperparams + assert "fixed_param2" in builder.fixed_hyperparams def test_build(mock_service): builder = HyperparameterSpaceBuilder() @@ -55,16 +61,16 @@ def test_load_default_space(mock_service, mocker): }) builder = HyperparameterSpaceBuilder() - result = builder.load_default_space(estimator_class=object) + result = builder.load_default_space(estimator_class=DecisionTreeClassifier) assert isinstance(result, HyperparameterSpace) -def test_save_space(mock_service, mocker): +def test_save_space(mock_service, mocker, tmp_path): space = HyperparameterSpace({}, {}) mocker.patch('mloptimizer.infrastructure.repositories.HyperparameterSpaceRepository.save_json') builder = HyperparameterSpaceBuilder() - builder.save_space(space, estimator_class=object, overwrite=True) + builder.save_space(space, os.path.join(tmp_path, "hyperspace.json"), overwrite=True) HyperparameterSpaceRepository.save_json.assert_called_once() \ No newline at end of file