Skip to content

Commit

Permalink
Merge: Refactor Strategies to Recommenders (#146)
Browse files Browse the repository at this point in the history
- move strategies to recommender module
- restructure recommender module to have `.meta` and `.pure` for
subpackages
- new class `MetaRecommender` from which old strategies now derive,
their name has changed from `<xyz>Strategy` to `<xyz>MetaRecommender`
- deprecations for old strategies
- rename `Recommender` to `PureRecommender` in analogy to
`MetaRecommender`
- rename `NaiveHybridRecommender` to `NaiveHybridSpaceRecommender`
- refactor tests
- refactor examples
- reduce transfer learning example parameters during SMOKE_TEST

NOT included
- Followup PR1: MyPy activation for recommenders
- Followup PR2: user guide files in this PR can be mostly ignored, I
have mainly fixed references in examples and user guides, but also their
structure needs an overhaul (eg strategies don't exist anymore)
  • Loading branch information
Scienfitz authored Feb 27, 2024
2 parents ac3a640 + e60e09f commit 35a23f4
Show file tree
Hide file tree
Showing 62 changed files with 884 additions and 638 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- `Recommender`s now share their core logic via their base class
- Remove progress bars in examples
- Strategies are now called `MetaRecommender`'s and part of the `recommenders.meta`
module
- `Recommender`'s are now called `PureRecommender`'s and part of the `recommenders.pure`
module
- `strategy` keyword of `Campaign` renamed to `recommender`
- `NaiveHybridRecommender` renamed to `NaiveHybridSpaceRecommender`

### Fixed
- Unhandled exception in telemetry when username could not be inferred on Windows
- Metadata is now correctly updated for hybrid spaces
- Unintended deactivation of telemetry due to import problem
- Line wrapping in examples

### Deprecations
- `TwoPhaseStrategy`
- `SequentialStrategy`
- `StreamingSequentialStrategy`

## [0.7.3] - 2024-02-09
### Added
- Copy button for code blocks in documentation
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,13 @@ details, and their configuration settings, see the
of the user guide.

```python
from baybe.strategies import TwoPhaseStrategy
from baybe.recommenders import SequentialGreedyRecommender, FPSRecommender
from baybe.recommenders import (
SequentialGreedyRecommender,
FPSRecommender,
TwoPhaseMetaRecommender,
)

strategy = TwoPhaseStrategy(
strategy = TwoPhaseMetaRecommender(
initial_recommender=FPSRecommender(), # farthest point sampling
recommender=SequentialGreedyRecommender(), # Bayesian model-based optimization
)
Expand Down
2 changes: 1 addition & 1 deletion baybe/acquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class PartialAcquisitionFunction:
pinned_part: Tensor
"""The values that will be attached whenever evaluating the acquisition function."""

pin_discrete: Tensor
pin_discrete: bool
"""A flag for denoting whether ``pinned_part`` corresponds to the discrete
subspace."""

Expand Down
23 changes: 18 additions & 5 deletions baybe/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
from baybe.objective import Objective
from baybe.parameters.base import Parameter
from baybe.recommenders.base import RecommenderProtocol
from baybe.recommenders.meta.sequential import TwoPhaseMetaRecommender
from baybe.searchspace.core import (
SearchSpace,
validate_searchspace_from_config,
)
from baybe.serialization import SerialMixin, converter
from baybe.strategies import TwoPhaseStrategy
from baybe.targets.base import Target
from baybe.telemetry import (
TELEM_LABELS,
Expand All @@ -45,7 +45,7 @@ class Campaign(SerialMixin):
In particular, a campaign:
* Defines the objective of an experimentation process.
* Defines the search space over which the experimental parameter may vary.
* Defines a strategy for traversing the search space.
* Defines a recommender for exploring the search space.
* Records the measurement data collected during the process.
* Records metadata about the progress of the experimentation process.
"""
Expand All @@ -57,8 +57,8 @@ class Campaign(SerialMixin):
objective: Objective = field()
"""The optimization objective."""

strategy: RecommenderProtocol = field(factory=TwoPhaseStrategy)
"""The employed strategy"""
recommender: RecommenderProtocol = field(factory=TwoPhaseMetaRecommender)
"""The employed recommender"""

# Metadata
n_batches_done: int = field(default=0, init=False)
Expand All @@ -82,6 +82,9 @@ class Campaign(SerialMixin):
numerical_measurements_must_be_within_tolerance: bool = field(default=None)
"""Deprecated! Raises an error when used."""

strategy: RecommenderProtocol = field(default=None)
"""Deprecated! Raises an error when used."""

@numerical_measurements_must_be_within_tolerance.validator
def _validate_tolerance_flag(self, _, value) -> None:
"""Raise a DeprecationError if the tolerance flag is used."""
Expand All @@ -92,6 +95,15 @@ def _validate_tolerance_flag(self, _, value) -> None:
f"{self.__class__.__name__}.{Campaign.add_measurements.__name__}."
)

@strategy.validator
def _validate_strategy(self, _, value) -> None:
"""Raise a DeprecationError if the strategy attribute is used."""
if value is not None:
raise DeprecationError(
"Passing 'strategy' to the constructor is deprecated. The attribute "
"has been renamed to 'recommender'."
)

@property
def measurements(self) -> pd.DataFrame:
"""The experimental data added to the Campaign."""
Expand Down Expand Up @@ -275,7 +287,7 @@ def recommend(
self._measurements_exp["FitNr"].fillna(self.n_fits_done, inplace=True)

# Get the recommended search space entries
rec = self.strategy.recommend(
rec = self.recommender.recommend(
self.searchspace,
batch_size,
self._measurements_parameters_comp,
Expand Down Expand Up @@ -306,6 +318,7 @@ def _add_version(dict_: dict) -> dict:
_cattrs_include_init_false=True,
# TODO: Remove once deprecation got expired:
numerical_measurements_must_be_within_tolerance=cattrs.override(omit=True),
strategy=cattrs.override(omit=True),
)
structure_hook = cattrs.gen.make_dict_structure_fn(
Campaign, converter, _cattrs_include_init_false=True
Expand Down
4 changes: 3 additions & 1 deletion baybe/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ class ModelParamsNotSupportedError(Exception):


class NoRecommendersLeftError(Exception):
"""A recommender is requested by a strategy but there are no recommenders left."""
"""A recommender is requested by a meta recommender but there are no recommenders
left.
"""


class NumericalUnderflowError(Exception):
Expand Down
2 changes: 1 addition & 1 deletion baybe/parameters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class DiscreteParameter(Parameter, ABC):

# class variables
encoding: Optional[ParameterEncoding] = field(init=False, default=None)
"""An optional encoding strategy for the parameter."""
"""An optional encoding for the parameter."""

@property
@abstractmethod
Expand Down
23 changes: 18 additions & 5 deletions baybe/recommenders/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
"""BayBE recommenders."""

from baybe.recommenders.bayesian.sequential_greedy import SequentialGreedyRecommender
from baybe.recommenders.naive import NaiveHybridRecommender
from baybe.recommenders.nonpredictive.clustering import (
from baybe.recommenders.meta.sequential import (
SequentialMetaRecommender,
StreamingSequentialMetaRecommender,
TwoPhaseMetaRecommender,
)
from baybe.recommenders.naive import NaiveHybridSpaceRecommender
from baybe.recommenders.pure.bayesian.sequential_greedy import (
SequentialGreedyRecommender,
)
from baybe.recommenders.pure.nonpredictive.clustering import (
GaussianMixtureClusteringRecommender,
KMeansClusteringRecommender,
PAMClusteringRecommender,
)
from baybe.recommenders.nonpredictive.sampling import FPSRecommender, RandomRecommender
from baybe.recommenders.pure.nonpredictive.sampling import (
FPSRecommender,
RandomRecommender,
)

__all__ = [
"FPSRecommender",
"GaussianMixtureClusteringRecommender",
"KMeansClusteringRecommender",
"PAMClusteringRecommender",
"NaiveHybridRecommender",
"NaiveHybridSpaceRecommender",
"RandomRecommender",
"TwoPhaseMetaRecommender",
"SequentialGreedyRecommender",
"SequentialMetaRecommender",
"StreamingSequentialMetaRecommender",
]
Loading

0 comments on commit 35a23f4

Please sign in to comment.