Getting Recommendations

The core functionality of BayBE is its ability to generate context-aware recommendations for your experiments. This page covers the basics of the corresponding user interface, assuming that a SearchSpace object and optional Objective and measurement objects are already in place (for more details, see the corresponding search space / objective user guides).

The recommend Call

BayBE offers two entry points for requesting recommendations:

  • Recommenders
    If a single (batch) recommendation is all you need, the most direct way to interact is to ask one of BayBE’s recommenders for it, by calling its recommend() method. To do so, simply pass all context information to the method call. This way, you interact with BayBE in a completely stateless way since all relevant components are explicitly provided at call time.

    Meta Recommenders

    A notable exception are MetaRecommenders which, depending on their configuration, may be stateless only with respect to the recommendation context but not the recommendation mechanism. More specifically, meta recommenders may – by design – generate different recommendations when confronted with an otherwise identical context twice, as indicated by their is_stateful property.

    For example, using the BotorchRecommender:

    recommender = BotorchRecommender()
    recommendation = recommender.recommend(batch_size, searchspace, objective, measurements)
    
  • Campaigns
    By contrast, if you plan to run an extended series of experiments where you feed newly arriving measurements back to BayBE and ask for a refined experimental design, creating a Campaign object that tracks the experimentation progress is a better choice. This offers stateful way of interaction where the context is fully maintained by the campaign object:

    recommender = BotorchRecommender()
    campaign = Campaign(searchspace, objective, recommender)
    campaign.add_measurements(measurements)
    recommendation = campaign.recommend(batch_size)
    

    For more details, have a look at our campaign user guide.

Excluding Configurations

When asking for recommendation, you often don’t want to consider all possible combinations of parameter values (a.k.a. the full Cartesian product space) but you may want to exclude certain configurations that are known to be infeasible or undesirable. There are several ways to do this, including using BayBE’s sophisticated constraint machinery. Which approach is the right choice for you depends on whether you want to exclude configurations permanently or (in-)activate them dynamically during your experimentation cycle.

Permanent Exclusion

Permanently excluding certain parameter configurations from the recommendation is generally done by adjusting the SearchSpace object accordingly, which defines the set of candidate configurations that will be considered.

BayBE provides several ways to achieve this, which we’ll illustrate by comparing against the following “full” search space:

searchspace_full = TaskParameter("p", ["A", "B", "C"]).to_searchspace()

Depending on the specific needs and complexity of the filtering operation, one approach may be preferred over the other, but generally these mechanisms exist:

  • Restricting individual parameter objects:

    searchspace_reduced = TaskParameter(
        "p", ["A", "B", "C"], active_values=["A", "B"]
    ).to_searchspace()
    

    Caution

    Note that this is not the same as defining the parameter with a reduced set of values ["A", "B"] since in this case the value “C” would be undefined. This makes adding measurements containing that value impossible.

    Experimental Feature

    Specifying active_values is currently an experimental feature only available to TaskParameter. It is likely that it will be made available for other categorical parameters in the future.

  • Specifying only a subset of configurations (discrete spaces only):

    searchspace_reduced = SearchSpace.from_dataframe(
        pd.DataFrame({"p": ["A", "B"]}),
        parameters=[TaskParameter("p", ["A", "B", "C"])],
    )
    
  • Filtering the search space using constraints:

    searchspace_reduced = SearchSpace.from_product(
        parameters=[CategoricalParameter("p", ["A", "B", "C"])],
        constraints=[DiscreteExcludeConstraint(["p"], [SubSelectionCondition(["C"])])],
    )
    
  • Using specialized constructors like from_simplex().

Dynamic Exclusion

Dynamic exclusion of candidates means to in-/exclude certain parameter configurations while you are already in the middle of your experimentation process. Here, we need to consider two different cases:

  • Recommenders
    Since recommender queries are stateless with respect to the experimental context, you can easily adjust your search space object for each query as needed using any of the permanent exclusion methods. For example:

    # Recommendation with full search space
    searchspace_full = CategoricalParameter("p", ["A", "B", "C"]).to_searchspace()
    recommender.recommend(batch_size, searchspace_full, objective, measurements)
    
    # Recommendation with reduced search space
    searchspace_reduced = TaskParameter(
        "p", ["A", "B", "C"], active_values=["A", "B"]
    ).to_searchspace()
    recommender.recommend(batch_size, searchspace_reduced, objective, measurements)
    
  • Campaigns
    Because the search space must be defined before a Campaign object can be created, a different approach is required for stateful queries. For this purpose, Campaigns provide a toggle_discrete_candidates() method that allows to dynamically enable or disable specific candidates while the campaign is running. The above example thus translates to:

    campaign = Campaign(searchspace_full, objective, measurements)
    campaign.add_measurements(measurements)
    
    # Recommendation with full search space
    campaign.recommend(batch_size)
    
    # Exclude *matching* rows
    campaign.toggle_discrete_candidates(
        pd.DataFrame({"p": ["C"]}),
        exclude=True,
    )
    # Alternatively: Exclude *non-matching* rows
    campaign.toggle_discrete_candidates(
        pd.DataFrame({"p": ["A", "B"]}),
        complement=True,
        exclude=True,
    )
    
    # Recommend from reduced search space using altered candidate set
    campaign.recommend(batch_size)
    

    Note that you can alternatively toggle candidates by passing the appropriate DiscreteConstraint objects. For more details, see toggle_discrete_candidates().

    Candidate Toggling vs. Applying Constraints

    Currently, dynamic exclusion via toggling is only possible for discrete candidates. To restrict the set of continuous candidates, use ContinuousConstraints when creating the space.

    Trajectory-Based Control

    Campaigns allow you to further control the candidate generation based on the experimental trajectory taken via their allow_* flags.