Skip to content

Commit

Permalink
refactor(SearchSpace): Improve validation and ==
Browse files Browse the repository at this point in the history
Refactored yaml pipeline_space
  • Loading branch information
eddiebergman authored Apr 30, 2024
2 parents 656f80c + 82e3e25 commit dbc3076
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 91 deletions.
2 changes: 1 addition & 1 deletion docs/neps_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ To define a budget, provide either or both of the following parameters:
- **`max_cost_total`** (int, default: None): Prevents the initiation of new evaluations once this cost
threshold is surpassed. This requires adding a cost value to the output of the `run_pipeline` function,
for example, return {'loss': loss, 'cost': cost}. For more details, please refer
[here](https://automl.github/io/neps/latest/run_pipeline)
[here](https://automl.github.io/neps/latest/run_pipeline)

You have the option to configure all arguments using a YAML file. This can be done by specifying the file path with
the following argument:
Expand Down
27 changes: 26 additions & 1 deletion neps/search_spaces/hyperparameters/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,39 @@ def __init__(
)
self.value: None | float | int | str = None

# Check if choices have valid types (float | int | str)
for choice in self.choices:
if not isinstance(choice, (float, int, str)):
raise TypeError(
f'Choice "{choice}" is not of a valid type (float, int, str)')

# Check if 'default' is in 'choices'
if default is not None and default not in self.choices:
raise ValueError(
f"Default value {default} is not in the provided choices {self.choices}"
)

# Check if 'is_fidelity' is a boolean
if not isinstance(is_fidelity, bool):
raise TypeError(
f"Expected 'is_fidelity' to be a boolean, but got type: "
f"{type(is_fidelity).__name__}"
)

@property
def id(self):
return self.value

def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.choices == other.choices and self.value == other.value
return (
self.choices == other.choices
and self.value == other.value
and self.is_fidelity == other.is_fidelity
and self.default == other.default
and self.default_confidence_score == other.default_confidence_score
)

def __repr__(self):
return f"<Categorical, choices: {self.choices}, value: {self.value}>"
Expand Down
3 changes: 2 additions & 1 deletion neps/search_spaces/hyperparameters/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def __init__(self, value: Union[float, int, str], is_fidelity: bool = False):
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.value == other.value
return (self.value == other.value
and self.is_fidelity == other.is_fidelity)

def __repr__(self):
return f"<Constant, value: {self.id}>"
Expand Down
26 changes: 25 additions & 1 deletion neps/search_spaces/hyperparameters/float.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,28 @@ def __init__(
self.upper = float(upper)

if self.lower >= self.upper:
raise ValueError("Float parameter: bounds error (lower >= upper).")
raise ValueError(
f"Float parameter: bounds error (lower >= upper). Actual values: "
f"lower={self.lower}, upper={self.upper}"
)

if self.default is not None:
if not self.lower <= self.default <= self.upper:
raise ValueError(
f"Float parameter: default bounds error. Expected lower <= default"
f" <= upper, but got lower={self.lower}, default={self.default},"
f" upper={self.upper}"
)

# Validate 'log' and 'is_fidelity' types to prevent configuration errors
# from the YAML input
for param, value in {"log": log, "is_fidelity": is_fidelity}.items():
if not isinstance(value, bool):
raise TypeError(
f"Expected '{param}' to be a boolean, but got type: "
f"{type(value).__name__}"
)

self.log = log

if self.log:
Expand All @@ -51,7 +72,10 @@ def __eq__(self, other):
self.lower == other.lower
and self.upper == other.upper
and self.log == other.log
and self.is_fidelity == other.is_fidelity
and self.value == other.value
and self.default == other.default
and self.default_confidence_score == other.default_confidence_score
)

def __repr__(self):
Expand Down
8 changes: 7 additions & 1 deletion neps/search_spaces/search_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ def pipeline_space_from_yaml(
try:
with open(yaml_file_path) as file:
config = yaml.safe_load(file)
except FileNotFoundError as e:
raise FileNotFoundError(
f"Unable to find the specified file for 'pipeline_space' at "
f"'{yaml_file_path}'. Please verify the path specified in the "
f"'pipeline_space' argument and try again."
) from e
except yaml.YAMLError as e:
raise ValueError(
f"The file at {str(yaml_file_path)} is not a valid YAML file."
Expand Down Expand Up @@ -178,7 +184,7 @@ def pipeline_space_from_yaml(
"For categorical parameter: cat, categorical\n"
"For constant parameter: const, constant\n"
)
except (KeyError, TypeError, ValueError) as e:
except (KeyError, TypeError, ValueError, FileNotFoundError) as e:
raise SearchSpaceFromYamlFileError(e) from e
return pipeline_space

Expand Down
9 changes: 8 additions & 1 deletion neps/search_spaces/yaml_search_space_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

import logging
import re

logger = logging.getLogger(__name__)


def convert_scientific_notation(value: str | int | float, show_usage_flag=False) \
-> float | (float, bool):
-> float | (float, bool):
"""
Convert a given value to a float if it's a string that matches scientific e notation.
This is especially useful for numbers like "3.3e-5" which YAML parsers may not
Expand Down Expand Up @@ -156,6 +159,10 @@ def deduce_param_type(name: str, details: dict[str, int | str | float]) -> str:

# check if one value is e notation and if so convert it to float
if flag_lower or flag_upper:
logger.info(
f"Because of e notation, Parameter {name} gets "
f"interpreted as float"
)
param_type = "float"
else:
raise TypeError(
Expand Down
7 changes: 7 additions & 0 deletions tests/test_yaml_search_space/default_not_in_range_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pipeline_space:
param_float1:
lower: 0.00001
upper: 0.1
default: 0.0000000001
log: false
is_fidelity: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pipeline_space:
cat1:
choices: ["a", "b", "c"]
default: "d"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pipeline_space:
cat1:
choices: ["a", "b", "c"]
is_fidelity: fals
default: "c"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pipeline_space:
param_float1:
lower: 0.00001
upper: 0.1
default: 0.001
log: false
is_fidelity: truee
7 changes: 7 additions & 0 deletions tests/test_yaml_search_space/not_boolean_type_log_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pipeline_space:
param_float1:
lower: 0.00001
upper: 0.1
default: 0.001
log: falsee
is_fidelity: true
Loading

0 comments on commit dbc3076

Please sign in to comment.