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

Several Local Bayesian Optimization Implementations #730

Merged
merged 79 commits into from
Jul 7, 2022

Conversation

dengdifan
Copy link
Contributor

@dengdifan dengdifan commented Jul 15, 2021

  1. a GPyTorch based Gaussian Process
  2. a Partial Sparse Gaussian Process
  3. Thompson Sampling acquisition function
  4. TurBO
  5. BOinG
  6. examples on turbo and boing

To run examples/fmin_rosenbrock_boing.py
You need to install pyrfr from this PR to get more interface (However, Ive not written test file for that PR)
automl/random_forest_run#64

@dengdifan dengdifan changed the title Several Local Bayesian Optimization Implementations and more EPM Several Local Bayesian Optimization Implementations Jul 15, 2021
@dengdifan dengdifan changed the base branch from master to development July 16, 2021 08:36
Copy link
Contributor

@AndreBiedenkapp AndreBiedenkapp left a comment

Choose a reason for hiding this comment

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

Please change the merge destination from master to development.
Also please add some tests.

requirements.txt Outdated
@@ -9,3 +9,6 @@ pyrfr>=0.8.0
lazy_import
dask
distributed
torch>=1.9.0
gpytorch>=1.5.0
pyro-ppl
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please also specify a minimal version that is required

@renesass
Copy link
Collaborator

python examples/fmin_rosenbrock_boing.py

File "/Users/rene/anaconda3/envs/SMAC/lib/python3.9/site-packages/smac/optimizer/local_bo/epm_chooser_boing.py", line 365, in choose_next bounds_ss_cont, bounds_ss_cat, ss_data_indices = subspace_extraction(X=X, File "/Users/rene/anaconda3/envs/SMAC/lib/python3.9/site-packages/smac/optimizer/local_bo/epm_chooser_boing.py", line 492, in subspace_extraction trees = model.rf.get_all_trees() AttributeError: 'binary_rss_forest' object has no attribute 'get_all_trees'

Copy link
Contributor

@AndreBiedenkapp AndreBiedenkapp left a comment

Choose a reason for hiding this comment

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

Partial Review since I'm loosing focus on the overall PR.
Since it is so large, it might be best to split this up into smaller PRs, one for each indivicual feature.

gpytorch.settings.debug.off()


class ExactGPModel(ExactGP):
Copy link
Contributor

Choose a reason for hiding this comment

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

Without any docstring it's difficult to see at a glance what this is used for.

Comment on lines 47 to 48
normalize_y: bool = True,
n_opt_restarts: int = 10,
Copy link
Contributor

Choose a reason for hiding this comment

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

Since they are not optional I suggest to move them before likelihood

Comment on lines 68 to 72
bounds_cont: np.ndarray
bounds of continuous hyperparameters
bounds_cat: typing.List[typing.List[typing.Tuple]],
bounds of categorical hyperparameters, need to be flattened, e.g. all the possible categorical needs to
be listed
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see whey those are necessary. The bounds argument should suffice to get these values already as the documentation states above. I.e. continuous hypers have the bounds (lower, upper) and categoricals have (n_cat, np.nan).
You can also have a look at the get_types function in util_funcs.py which takes care of getting the bounds.

def get_types(
config_space: ConfigurationSpace,
instance_features: typing.Optional[np.ndarray] = None,
) -> typing.Tuple[typing.List[int], typing.List[typing.Tuple[float, float]]]:
"""TODO"""
# Extract types vector for rf from config space and the bounds
types = [0] * len(config_space.get_hyperparameters())
bounds = [(np.nan, np.nan)] * len(types)
for i, param in enumerate(config_space.get_hyperparameters()):
parents = config_space.get_parents_of(param.name)
if len(parents) == 0:
can_be_inactive = False
else:
can_be_inactive = True
if isinstance(param, (CategoricalHyperparameter)):
n_cats = len(param.choices)
if can_be_inactive:
n_cats = len(param.choices) + 1
types[i] = n_cats
bounds[i] = (int(n_cats), np.nan)
elif isinstance(param, (OrdinalHyperparameter)):
n_cats = len(param.sequence)
types[i] = 0
if can_be_inactive:
bounds[i] = (0, int(n_cats))
else:
bounds[i] = (0, int(n_cats) - 1)
elif isinstance(param, Constant):
# for constants we simply set types to 0 which makes it a numerical
# parameter
if can_be_inactive:
bounds[i] = (2, np.nan)
types[i] = 2
else:
bounds[i] = (0, np.nan)
types[i] = 0
# and we leave the bounds to be 0 for now
elif isinstance(param, UniformFloatHyperparameter):
# Are sampled on the unit hypercube thus the bounds
# are always 0.0, 1.0
if can_be_inactive:
bounds[i] = (-1.0, 1.0)
else:
bounds[i] = (0, 1.0)
elif isinstance(param, UniformIntegerHyperparameter):
if can_be_inactive:
bounds[i] = (-1.0, 1.0)
else:
bounds[i] = (0, 1.0)
elif not isinstance(param, (UniformFloatHyperparameter,
UniformIntegerHyperparameter,
OrdinalHyperparameter,
CategoricalHyperparameter)):
raise TypeError("Unknown hyperparameter type %s" % type(param))
if instance_features is not None:
types = types + [0] * instance_features.shape[1]
return types, bounds

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I believe the resulting self.bound_cat and self.bound_cont are never used in the code anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is only used in another GP model, I will fix that

self.cont_dims = np.where(np.array(types) == 0)[0]

self.normalize_y = normalize_y
self.n_opt_restarts = n_opt_restarts
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add a check somewhere that this parameter is a positive integer or otherwise handle illegal values.


self.num_points = 0

def _train(self, X: np.ndarray, y: np.ndarray, do_optimize: bool = True) -> 'GaussianProcess':
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def _train(self, X: np.ndarray, y: np.ndarray, do_optimize: bool = True) -> 'GaussianProcess':
def _train(self, X: np.ndarray, y: np.ndarray, do_optimize: bool = True) -> GaussianProcessGPyTorch:

since it returns self it should be typhinted with the correct class.

self.bounds_cat = bounds_cat,
self.num_inducing_points = num_inducing_points

def update_attribute(self, **kwargs: typing.Any):
Copy link
Contributor

Choose a reason for hiding this comment

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

The type hint is wrong. kwargs (i.e. key-word arguments) will always be a dictionary.
The entries however can be of type typing.Any

Comment on lines 652 to 653
# A modification of from botorch.optim.utils._scipy_objective_and_grad,
# THe key difference is that we do an additional nature gradient update here
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make this an actual docstring

Comment on lines 727 to 729
If both in and out are None: return an empty models
If only in_x and in_y are given: return a vanilla GP model
If in_x, in_y, out_x, out_y are given: return a partial sparse GP model.
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the use case of an empty model?
I would rather suggest to have in_x and in_y as required arguments and only out_x and out_y as optional.
Further, to be consistent with naming in other classes it would be good if you could rename the "x" variables to "in_X" and "out_X" respectively.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

smac.epm.base_gp.BaseModel requires to run 'self._get_gp()' in 'init', I made all arguments optional to make it compatible with the super class

Comment on lines 795 to 798
except Exception as e:
if i == n_tries - 1:
raise e
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

Please try to be more specific in what exceptions are okay to skip and add some documentation.

Comment on lines 826 to 831
def check_points_in_ss(X: np.ndarray,
cont_dims: np.ndarray,
cat_dims: np.ndarray,
bounds_cont: np.ndarray,
bounds_cat: typing.List[typing.List[typing.Tuple]],
):
Copy link
Contributor

Choose a reason for hiding this comment

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

Might better live in the utils

@codecov
Copy link

codecov bot commented Jul 16, 2021

Codecov Report

Merging #730 (e46d898) into development (4399ada) will decrease coverage by 67.12%.
The diff coverage is 2.44%.

❗ Current head e46d898 differs from pull request most recent head dc24106. Consider uploading reports for the commit dc24106 to get more accurate results

@@               Coverage Diff                @@
##           development     #730       +/-   ##
================================================
- Coverage        87.27%   20.14%   -67.13%     
================================================
  Files               72       77        +5     
  Lines             6450     7600     +1150     
================================================
- Hits              5629     1531     -4098     
- Misses             821     6069     +5248     
Impacted Files Coverage Δ
smac/epm/gp_kernels.py 17.54% <0.00%> (-52.64%) ⬇️
smac/epm/partial_sparse_gaussian_process.py 0.00% <0.00%> (ø)
smac/epm/partial_sparse_gp_kernels.py 0.00% <0.00%> (ø)
smac/facade/smac_ac_facade.py 18.53% <0.00%> (-69.79%) ⬇️
smac/initial_design/initial_design.py 22.22% <0.00%> (-76.41%) ⬇️
smac/optimizer/acquisition.py 20.72% <ø> (-63.22%) ⬇️
smac/optimizer/local_bo/abstract_subspace.py 0.00% <0.00%> (ø)
smac/optimizer/local_bo/boing_subspace.py 0.00% <0.00%> (ø)
smac/optimizer/local_bo/epm_chooser_boing.py 0.00% <0.00%> (ø)
smac/optimizer/local_bo/epm_chooser_turbo.py 0.00% <0.00%> (ø)
... and 76 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a12ffa1...dc24106. Read the comment docs.

Copy link
Contributor

@AndreBiedenkapp AndreBiedenkapp left a comment

Choose a reason for hiding this comment

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

@dengdifan As promised I also provide some feedback to the abstract_subspace.
Except for a few minor things the abstract_subspace looks all good to me :)

Comment on lines 51 to 54
bounds_ss_cont: np.ndarray(D_cont, 2)
subspaces bounds of continuous hyperparameters, its length is the number of continuous hyperparameters
bounds_ss_cat: typing.List[typing.Tuple]
subspaces bounds of categorical hyperparameters, its length is the number of categorical hyperparameters
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the bounds be used for this again or is this a set of different bounds?

if rng is None:
self.rng = np.random.RandomState(1)
else:
self.rng = np.random.RandomState(rng.randint(0, 2 ** 20))
Copy link
Contributor

Choose a reason for hiding this comment

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

I would have expected that if rng is not None it would already be a numpy RandomState.
At least according to the above docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To avoid using the same rng from outside as this could probably be a reference?

Comment on lines 86 to 92
if activate_dims is None:
activate_dims = np.arange(n_hypers)

if activate_dims is None:
activate_dims_cont = cont_dims
activate_dims_cat = cat_dims
self.activate_dims = np.arange(n_hypers)
Copy link
Contributor

Choose a reason for hiding this comment

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

The if statement twice checks the same condition.


self.config_origin = "subspace"

def check_points_in_ss(self,
Copy link
Contributor

Choose a reason for hiding this comment

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

Wasn't this function also implemented in the PartialSparseGP?

@renesass
Copy link
Collaborator

renesass commented Jul 6, 2022

I made some interface changes as the new implementation caused some confusions:

  • We changed the location of gaussian processes and random forests. They are in the folders epm/gp
    and epm/rf now.
  • Also, we restructured the optimizer folder and therefore the location of the acquisition functions
    and configuration chooser.
  • Multi-objective functions are located in the folder multi_objective.

@renesass renesass merged commit 6b60582 into automl:development Jul 7, 2022
@dengdifan dengdifan deleted the boing branch July 7, 2022 09:28
github-actions bot pushed a commit that referenced this pull request Jul 7, 2022
renesass added a commit that referenced this pull request Jul 14, 2022
## Features
* [BOinG](https://arxiv.org/abs/2111.05834): A two-stage Bayesian optimization approach to allow the 
optimizer to focus on the most promising regions.
* [TurBO](https://arxiv.org/abs/1910.01739): Reimplementaion of TurBO-1 algorithm.
* Updated pSMAC: Can pass arbitrary SMAC facades now. Added example and fixed tests.

## Improvements
* Enabled caching for multi-objectives (#872). Costs are now normalized in `get_cost` 
or optionally in `average_cost`/`sum_cost`/`min_cost` to receive a single float value. Therefore,
the cached cost values do not need to be updated everytime a new entry to the runhistory was added.

## Interface changes
* We changed the location of Gaussian processes and random forests. They are in the folders
`epm/gaussian_process` and `epm/random_forest` now.
* Also, we restructured the optimizer folder and therefore the location of the acquisition functions
and configuration chooser.
* Multi-objective functions are located in the folder `multi_objective`.
* pSMAC facade was moved to the facade directory.

Co-authored-by: Difan Deng <deng@dengster.tnt.uni-hannover.de>
Co-authored-by: Eddie Bergman <eddiebergmanhs@gmail.com>
Co-authored-by: Carolin Benjamins <benjamins@tnt.uni-hannover.de>
Co-authored-by: timruhkopf <timruhkopf@gmail.com>
github-actions bot pushed a commit that referenced this pull request Jul 14, 2022
sharpe5 added a commit to sharpe5/SMAC3 that referenced this pull request Aug 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

3 participants