Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable context for SMAC Optimizer #741

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4b325b4
progress on multi fidelity SMAC interface
jsfreischuetz Nov 17, 2023
a59a499
more ideas
jsfreischuetz Nov 17, 2023
14c80d9
SMAC updated
jsfreischuetz Nov 19, 2023
d2b4c9b
merge
jsfreischuetz May 10, 2024
1d8b455
merge
jsfreischuetz May 10, 2024
266d30c
Merge branch 'microsoft-main'
jsfreischuetz May 10, 2024
7d57cf5
merge
jsfreischuetz May 10, 2024
65e2170
fixes
jsfreischuetz May 10, 2024
c330be9
fix some of the tests
jsfreischuetz May 10, 2024
891c755
fix more tests
jsfreischuetz May 10, 2024
2693d2d
fix docstrings
jsfreischuetz May 10, 2024
ba7b975
finish mlos_core tests
jsfreischuetz May 11, 2024
708e9a3
merge
jsfreischuetz May 11, 2024
78fc91e
fix linter and loging error
jsfreischuetz May 13, 2024
79b9ed0
Merge branch 'microsoft:main' into main
jsfreischuetz May 13, 2024
d2bc22c
add support for python3 3.8
jsfreischuetz May 13, 2024
65dc1b7
Merge branch 'main' of github.com:jsfreischuetz/MLOS
jsfreischuetz May 13, 2024
c92924c
merge
jsfreischuetz May 13, 2024
f48f65f
mergE
jsfreischuetz May 13, 2024
5469503
typing
jsfreischuetz May 13, 2024
e3c238b
fix tests
jsfreischuetz May 13, 2024
4bc55ee
fix tests
jsfreischuetz May 13, 2024
311175a
fix typing for 3.8
jsfreischuetz May 13, 2024
9899089
fix another typing issue
jsfreischuetz May 13, 2024
ea391c7
another typing issue
jsfreischuetz May 13, 2024
17a5965
another type issue
jsfreischuetz May 13, 2024
d8fe76e
import order
jsfreischuetz May 13, 2024
cd3913f
lint checker
jsfreischuetz May 13, 2024
a5f3ce7
reorder imports
jsfreischuetz May 13, 2024
546cb35
fix more linting messages
jsfreischuetz May 13, 2024
17ae308
linter errors
jsfreischuetz May 14, 2024
12d725c
linter errors
jsfreischuetz May 14, 2024
ee99fd5
more linting
jsfreischuetz May 14, 2024
57103f5
oops typo
jsfreischuetz May 14, 2024
3d57a10
merge
jsfreischuetz May 14, 2024
02b8b9d
more linting
jsfreischuetz May 14, 2024
465dc69
auto format changes
jsfreischuetz May 14, 2024
0d48d5e
merge
jsfreischuetz May 14, 2024
c19d4c7
fix some tests
jsfreischuetz May 14, 2024
3841b42
tests working
jsfreischuetz May 14, 2024
23b1a83
fix for multi fidelity
jsfreischuetz May 14, 2024
d047836
hopefully fix all linting issues
jsfreischuetz May 14, 2024
96198d9
Merge branch 'microsoft:main' into main
jsfreischuetz May 15, 2024
77b46ee
some more changes
jsfreischuetz May 15, 2024
f3773ea
Merge branch 'main' of github.com:jsfreischuetz/MLOS
jsfreischuetz May 15, 2024
f45279b
fix linter
jsfreischuetz May 15, 2024
4f3101d
new typing
jsfreischuetz May 15, 2024
6725d74
mypy fixups
bpkroth May 16, 2024
9d5f973
Merge pull request #4 from bpkroth/jsfreischuetz-main
jsfreischuetz May 16, 2024
dab5c67
revert some changes
jsfreischuetz May 16, 2024
f70eacf
Merge branch 'main' of github.com:jsfreischuetz/MLOS
jsfreischuetz May 16, 2024
8faf321
fix launch.json being ignored
jsfreischuetz May 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .vscode/launch.json
jsfreischuetz marked this conversation as resolved.
Show resolved Hide resolved

This file was deleted.

11 changes: 7 additions & 4 deletions .vscode/settings.json
jsfreischuetz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
// See Also:
// - https://github.com/microsoft/vscode/issues/2809#issuecomment-1544387883
// - mlos_bench/config/schemas/README.md

{
"fileMatch": [
"mlos_bench/mlos_bench/tests/config/schemas/environments/test-cases/**/*.jsonc",
Expand Down Expand Up @@ -131,13 +130,17 @@
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications"
"editor.formatOnSaveMode": "modifications",
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.fixAll": "always"
},
},
// See Also .vscode/launch.json for environment variable args to pytest during debug sessions.
// For the rest, see setup.cfg
"python.testing.pytestArgs": [
"--log-level=DEBUG",
"."
],
"python.testing.unittestEnabled": false
"python.testing.unittestEnabled": false,
"restructuredtext.pythonRecommendation.disabled": true
}
15 changes: 15 additions & 0 deletions conda-envs/condaenv.ib_tklms.requirements.txt
jsfreischuetz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
autopep8>=1.7.0
bump2version
check-jsonschema
licenseheaders
mypy
pandas-stubs
types-beautifulsoup4
types-colorama
types-jsonschema
types-pygments
types-pytest-lazy-fixture
types-requests
types-setuptools
--editable ../mlos_core[full-tests]
--editable ../mlos_bench[full-tests]
107 changes: 68 additions & 39 deletions mlos_bench/mlos_bench/optimizers/base_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,23 @@

import logging
from abc import ABCMeta, abstractmethod
from distutils.util import strtobool # pylint: disable=deprecated-module

from distutils.util import strtobool # pylint: disable=deprecated-module
from types import TracebackType
from typing import Dict, Optional, Sequence, Tuple, Type, Union
from typing_extensions import Literal

from ConfigSpace import ConfigurationSpace

from typing_extensions import Literal
from mlos_bench.config.schemas import ConfigSchema
from mlos_bench.services.base_service import Service
from mlos_bench.environments.status import Status
from mlos_bench.optimizers.convert_configspace import tunable_groups_to_configspace
from mlos_bench.services.base_service import Service
from mlos_bench.tunables.tunable import TunableValue
from mlos_bench.tunables.tunable_groups import TunableGroups
from mlos_bench.optimizers.convert_configspace import tunable_groups_to_configspace

_LOG = logging.getLogger(__name__)


class Optimizer(metaclass=ABCMeta): # pylint: disable=too-many-instance-attributes
class Optimizer(metaclass=ABCMeta): # pylint: disable=too-many-instance-attributes
"""
An abstract interface between the benchmarking framework and mlos_core optimizers.
"""
Expand All @@ -40,11 +38,13 @@ class Optimizer(metaclass=ABCMeta): # pylint: disable=too-many-instance-attr
"start_with_defaults",
}

def __init__(self,
tunables: TunableGroups,
config: dict,
global_config: Optional[dict] = None,
service: Optional[Service] = None):
def __init__(
self,
tunables: TunableGroups,
config: dict,
global_config: Optional[dict] = None,
service: Optional[Service] = None,
):
"""
Create a new optimizer for the given configuration space defined by the tunables.

Expand All @@ -68,25 +68,30 @@ def __init__(self,
self._seed = int(config.get("seed", 42))
self._in_context = False

experiment_id = self._global_config.get('experiment_id')
experiment_id = self._global_config.get("experiment_id")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and everywhere: let's separate cosmetic fixes from functional changes. mixing the two bloats up the diff and makes reviewing PRs much harder. Please keep the essential functionality in this PR and make the diff as small as possible and move all code annotations, style, and docstring improvements to a separate PR. having more PRs is good for your github karma! 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! #744 should do just the style changes

Copy link
Contributor

@bpkroth bpkroth May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, @motus @jsfreischuetz and I already talked about that. #744 is the start of that, but I think will also need some additional work. #746 unearthed some related topics around pyproject.toml settings vs. setup.cfg for using black and how setup and build type dependencies are specified (right now we require conda for certain ones instead of pip, which as we saw, has some issues).

Am currently working on improvements for all of those and will send a split out series of PRs soon for:

  1. pyproject.toml settings and build related improvements
    • annoyed at this one since there's no "include" feature, so it means duplicating some content across files it looks like
  2. adding black and isort Makefile rules, vscode settings, configs, etc.
    • that's a part of this one, but I'm going to restrict it to a smaller set so it's easier to see the changes
  3. requiring them in the build (including all of the reformatting of all files)
    • this will be a larger change, so I want make sure it's just reformatting. Can take that up here or just create a new PR
  4. adding the revision from 3 in the .gitrevisions list to ignore it from most git blame types of analysis

self.experiment_id = str(experiment_id).strip() if experiment_id else None

self._iter = 0
# If False, use the optimizer to suggest the initial configuration;
# if True (default), use the already initialized values for the first iteration.
self._start_with_defaults: bool = bool(
strtobool(str(self._config.pop('start_with_defaults', True))))
self._max_iter = int(self._config.pop('max_suggestions', 100))
strtobool(str(self._config.pop("start_with_defaults", True)))
)
self._max_iter = int(self._config.pop("max_suggestions", 100))

opt_targets: Dict[str, str] = self._config.pop('optimization_targets', {'score': 'min'})
opt_targets: Dict[str, str] = self._config.pop(
"optimization_targets", {"score": "min"}
)
self._opt_targets: Dict[str, Literal[1, -1]] = {}
for (opt_target, opt_dir) in opt_targets.items():
for opt_target, opt_dir in opt_targets.items():
if opt_dir == "min":
self._opt_targets[opt_target] = 1
elif opt_dir == "max":
self._opt_targets[opt_target] = -1
else:
raise ValueError(f"Invalid optimization direction: {opt_dir} for {opt_target}")
raise ValueError(
f"Invalid optimization direction: {opt_dir} for {opt_target}"
)

def _validate_json_config(self, config: dict) -> None:
"""
Expand All @@ -108,7 +113,7 @@ def __repr__(self) -> str:
)
return f"{self.name}({opt_targets},config={self._config})"

def __enter__(self) -> 'Optimizer':
def __enter__(self) -> "Optimizer":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a whole bunch of style-only changes in here that makes reviewing a little difficult.

Can you please revert those, or separate them out to a different PR?

"""
Enter the optimizer's context.
"""
Expand All @@ -117,9 +122,12 @@ def __enter__(self) -> 'Optimizer':
self._in_context = True
return self

def __exit__(self, ex_type: Optional[Type[BaseException]],
ex_val: Optional[BaseException],
ex_tb: Optional[TracebackType]) -> Literal[False]:
def __exit__(
self,
ex_type: Optional[Type[BaseException]],
ex_val: Optional[BaseException],
ex_tb: Optional[TracebackType],
) -> Literal[False]:
"""
Exit the context of the optimizer.
"""
Expand Down Expand Up @@ -191,7 +199,9 @@ def config_space(self) -> ConfigurationSpace:
The ConfigSpace representation of the tunable parameters.
"""
if self._config_space is None:
self._config_space = tunable_groups_to_configspace(self._tunables, self._seed)
self._config_space = tunable_groups_to_configspace(
self._tunables, self._seed
)
_LOG.debug("ConfigSpace: %s", self._config_space)
return self._config_space

Expand All @@ -204,7 +214,7 @@ def name(self) -> str:
return self.__class__.__name__

@property
def targets(self) -> Dict[str, Literal['min', 'max']]:
def targets(self) -> Dict[str, Literal["min", "max"]]:
"""
A dictionary of {target: direction} of optimization targets.
"""
Expand All @@ -221,10 +231,12 @@ def supports_preload(self) -> bool:
return True

@abstractmethod
def bulk_register(self,
configs: Sequence[dict],
scores: Sequence[Optional[Dict[str, TunableValue]]],
status: Optional[Sequence[Status]] = None) -> bool:
def bulk_register(
self,
configs: Sequence[dict],
scores: Sequence[Optional[Dict[str, TunableValue]]],
status: Optional[Sequence[Status]] = None,
) -> bool:
"""
Pre-load the optimizer with the bulk data from previous experiments.

Expand All @@ -242,8 +254,12 @@ def bulk_register(self,
is_not_empty : bool
True if there is data to register, false otherwise.
"""
_LOG.info("Update the optimizer with: %d configs, %d scores, %d status values",
len(configs or []), len(scores or []), len(status or []))
_LOG.info(
"Update the optimizer with: %d configs, %d scores, %d status values",
len(configs or []),
len(scores or []),
len(status or []),
)
if len(configs or []) != len(scores or []):
raise ValueError("Numbers of configs and scores do not match.")
if status is not None and len(configs or []) != len(status or []):
Expand Down Expand Up @@ -272,8 +288,12 @@ def suggest(self) -> TunableGroups:
return self._tunables.copy()

@abstractmethod
def register(self, tunables: TunableGroups, status: Status,
score: Optional[Dict[str, TunableValue]] = None) -> Optional[Dict[str, float]]:
def register(
self,
tunables: TunableGroups,
status: Status,
score: Optional[Dict[str, TunableValue]] = None,
) -> Optional[Dict[str, float]]:
"""
Register the observation for the given configuration.

Expand All @@ -294,15 +314,22 @@ def register(self, tunables: TunableGroups, status: Status,
Benchmark scores extracted (and possibly transformed)
from the dataframe that's being MINIMIZED.
"""
_LOG.info("Iteration %d :: Register: %s = %s score: %s",
self._iter, tunables, status, score)
_LOG.info(
"Iteration %d :: Register: %s = %s score: %s",
self._iter,
tunables,
status,
score,
)
if status.is_succeeded() == (score is None): # XOR
raise ValueError("Status and score must be consistent.")
return self._get_scores(status, score)

def _get_scores(self, status: Status,
scores: Optional[Union[Dict[str, TunableValue], Dict[str, float]]]
) -> Optional[Dict[str, float]]:
def _get_scores(
self,
status: Status,
scores: Optional[Union[Dict[str, TunableValue], Dict[str, float]]],
) -> Optional[Dict[str, float]]:
"""
Extract a scalar benchmark score from the dataframe.
Change the sign if we are maximizing.
Expand All @@ -329,7 +356,7 @@ def _get_scores(self, status: Status,

assert scores is not None
target_metrics: Dict[str, float] = {}
for (opt_target, opt_dir) in self._opt_targets.items():
for opt_target, opt_dir in self._opt_targets.items():
val = scores[opt_target]
assert val is not None
target_metrics[opt_target] = float(val) * opt_dir
Expand All @@ -344,7 +371,9 @@ def not_converged(self) -> bool:
return self._iter < self._max_iter

@abstractmethod
def get_best_observation(self) -> Union[Tuple[Dict[str, float], TunableGroups], Tuple[None, None]]:
def get_best_observation(
self,
) -> Union[Tuple[Dict[str, float], TunableGroups], Tuple[None, None]]:
"""
Get the best observation so far.

Expand Down
Loading