pytorch / botorch

Bayesian optimization in PyTorch
https://botorch.org/
MIT License
3.05k stars 390 forks source link

Chained Objectives #2418

Open kstone40 opened 1 month ago

kstone40 commented 1 month ago

Similar to constraints, it would be nice to send a list of objectives to optim_acqfs, but which are executed in sequence.

In my use cases, we have a number of (usually multioutput) custom objectives which are made of composite parts (scalarization, weighting, bounding, normalization to a reference or utopian point). But since only one objective can be sent to botorch, we have to construct the callables to make all of the combinations manually. It would be more convenient to define each part once, and be able to chain them together as the individual cases demand.

It's still possible in practice today, since I can do GenericMCObjective(callable) in a roundabout way with callable(x) = part1(part2(part3(x))), but it would be great if that also could be implemented natively in Botorch!

esantorella commented 1 month ago

I see the logic in this. But for the sake of figuring out the prioritization of this ask, I'm wondering if your particular use case could be achieved with existing BoTorch functionality.

All of this could be documented better, especially guidance on using posterior transforms and outcome transforms.

I may be misunderstanding your use case; if this doesn't address your needs, can you provide an example of what you're trying to do and what your preferred API would look like?

kstone40 commented 1 month ago

I was able to do this by creating a class that bases on MC multioutput acquisition but manually manages .is_mo for flexibility.

Anyway, I just had a really good example of where this was useful and wanted to follow up.

I have two responses in my data: yield (to all products), and selectivity (to desired product). I build a model to each of these. However, what I really want is their product, so that's one objective.

Second, I also want to calculate the cost of each experiment as a deterministic function of the inputs X.

Third, I actually want to divide the (yield*selectivity) by the cost objective.

So, that's three sequential operations based on simple objectives: product of y, weighted sum of X, divide.

Of course, I can do this easily with custom objectives in BoTorch, but I am working on a library with reusable parts, so I'm looking for generality.

I created an "objective_list" class that calls them sequentially, and does some management of output shape as appropriate (i.e. only squeeze output dimension of 1 if it's the last in the list).

I plan to put this up on GitHub soon, so if you are interested, I can circle back

Balandat commented 1 month ago

Computing the product of the individually modeled outcomes makes sense, this is a case of BO for composite functions (https://proceedings.mlr.press/v97/astudillo19a.html). I guess one could come up with some set of basic operations (e.g. a "ProductObjective") and make those easier to use (e.g. by implementing methods like __mul__ on objectives to auto-generate those). However, that seems like it could become arbitrarily complex, so one would have to find a good balance and think about what kinds of operations one wants to support given the maintenance burden.

The inverse-cost-weighting approach that you discuss is actually rather common and used also in multi-fidelity acquisition functions. In fact, there is an InverseCostWeightedUtility, which is used e.g. by the qKnowledgeGradient acquisition function.

We plan to work more on multi-fidelity methods in the near future, so there may be more of this / improvements to this coming down the pipe. cc @SebastianAment