The current implementation of "constraints" is very restricted as one cannot call a function defined in the workspace to be evaluated, thus general black-box constraints, output constraints, etc. cannot be realized in current GPyOpt. Consider the example below and see the concerns.
from GPyOpt import Design_space, experiment_design
def fun(x):
return (6*x-2)**2*np.sin(12*x-4)
domain = [{'name': 'var_1', 'type': 'continuous', 'domain': (0,1)}]
constraint = [{'name': 'constr_1', 'constraint': 'fun(x) - 6.0'}]
feasible_region = Design_space(space = domain, constraints = constraint)
initial_design = experiment_design.initial_design('random', feasible_region, 10)
Error trace:
Fail to compile the constraint: {'name': 'constr_1', 'constraint': 'fun(x) - 6.0'}
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-41-d60bcb9c9d7d> in <module>
2 constraint = [{'name': 'constr_1', 'constraint': 'fun(x) - 6.0'}]
3 feasible_region = Design_space(space = domain, constraints = constraint)
----> 4 initial_design = experiment_design.initial_design('random', feasible_region, 10)
c:\users\212613144\appdata\local\continuum\anaconda3\envs\kchain\lib\site-packages\GPyOpt\experiment_design\__init__.py in initial_design(design_name, space, init_points_count)
18 raise ValueError('Unknown design type: ' + design_name)
19
---> 20 return design.get_samples(init_points_count)
c:\users\212613144\appdata\local\continuum\anaconda3\envs\kchain\lib\site-packages\GPyOpt\experiment_design\random_design.py in get_samples(self, init_points_count)
15 def get_samples(self, init_points_count):
16 if self.space.has_constraints():
---> 17 return self.get_samples_with_constraints(init_points_count)
18 else:
19 return self.get_samples_without_constraints(init_points_count)
c:\users\212613144\appdata\local\continuum\anaconda3\envs\kchain\lib\site-packages\GPyOpt\experiment_design\random_design.py in get_samples_with_constraints(self, init_points_count)
28 while samples.shape[0] < init_points_count:
29 domain_samples = self.get_samples_without_constraints(init_points_count)
---> 30 valid_indices = (self.space.indicator_constraints(domain_samples) == 1).flatten()
31 if sum(valid_indices) > 0:
32 valid_samples = domain_samples[valid_indices,:]
c:\users\212613144\appdata\local\continuum\anaconda3\envs\kchain\lib\site-packages\GPyOpt\core\task\space.py in indicator_constraints(self, x)
305 try:
306 exec('constraint = lambda x:' + d['constraint'], globals())
--> 307 ind_x = (constraint(x)<0)*1
308 I_x *= ind_x.reshape(x.shape[0],1)
309 except:
c:\users\212613144\appdata\local\continuum\anaconda3\envs\kchain\lib\site-packages\GPyOpt\core\task\space.py in <lambda>(x)
NameError: name 'fun' is not defined
However, fun
was defined just before being used to add an output constraint. In this case, one could have repeated fun
script again in constraint, but this might not always be possible.
1) Can you update exec('constraint = lambda x:' + d['constraint'], globals()) statement and constraint compiling codes to allow functions that are defined locally within the workspace?
2) Can you allow other blackbox constraints to be added just the way you allow external evaluation of objectives?
Thanks for the minimal code and stack trace.
I suspect this will not be satisfying, but this seems like a feature request that is motivated by a mis-specification of the optimization problem. In other words, you can indeed change the constraints dynamically if you insist by e.g. updating bo.context : ContextManager
(GPyOpt/optimization/acquisition_optimizer.py has class ContextManager
) - _but_ - that's an anti-pattern.
To move the issue forward, concretely, would you mind sharing the motivating problem for using fun(x)-6
in the domain?
Hi @ekalosak , Sorry for delay in my response.
The concrete objective is to implement Bayesian optimization with unknown constraints [1], where analytical structure of the constraint is not known to us, but it can be evaluated point-wise by that function "fun(x)" in my example above. For example, if I am trying to do hyper-parameter optimization to get best accuracy under constraint that inference time on each instance is less that 60ms, then function fun uses timers to evaluate 95% upper confidence bound on inference time and then constraint is "fun(x) - 60 <= 0". See [1] for other examples.
[1] Gelbart, M.A., Snoek, J. and Adams, R.P., 2014. Bayesian optimization with unknown constraints. arXiv preprint arXiv:1403.5607.
Meta
This is clearly a valuable addition to GPyOpt - thanks for bringing it up. It's a substantial feature addition. If you bring up a draft branch, I'd bet some other contributors would help with some parts. Linus's Law in action >:)
Implementation
It's one thing to explicitly dis-allow portions of the design space. It's another to model the constraints as a conjugate Gaussian process in the acquisition function (Section 3.1 Eqn (7) in Gelbart et al. 2014) with arbitrary function calls. In this case, implementation-wise, it appears to be:
self.Y_new, cost_new, constraints_violated = self.objective.evaluate(self.suggested_sample)
note the constraints_violated
addition to the return signature) - or a list/dict of constraint functions should they be decoupledg_k(x)
constraint functions (Section 2.2)run_optimization
loop to accommodate the previous additions e.g. how and when to evaluate g_k(x)
, when to optimize constraint surrogate model (g_k
) hyperparameters, I'm sure there are other things I'm leaving out.Do those requirements look right? As for an MVP (minimally viable product), the feature branch needs:
external_constraints: Dict[str, Callable[List[...], Union[bool,float]]] = None
optional kwarg in core/bo.pyexternal_constraints
in the ModularBayesianOptimization class for top-level user-facing access.run_optimization
function (i.e. do an if self.external_constraints is not None: self.check_external_constraints(...)
)Notes
Following the evaluation stack down, it looks like a lot of this can go through the acquisition_optimizer
's optimize()
(here).
Any update concerning this feature? Was also interested in.
Most helpful comment
Hi @ekalosak , Sorry for delay in my response.
The concrete objective is to implement Bayesian optimization with unknown constraints [1], where analytical structure of the constraint is not known to us, but it can be evaluated point-wise by that function "fun(x)" in my example above. For example, if I am trying to do hyper-parameter optimization to get best accuracy under constraint that inference time on each instance is less that 60ms, then function fun uses timers to evaluate 95% upper confidence bound on inference time and then constraint is "fun(x) - 60 <= 0". See [1] for other examples.
[1] Gelbart, M.A., Snoek, J. and Adams, R.P., 2014. Bayesian optimization with unknown constraints. arXiv preprint arXiv:1403.5607.